Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14983] ファイルの概要

このコミットは、Go言語の型システムを扱う go/types パッケージにおいて、型の等価性(同一性)を判定する isIdentical 関数を IsIdentical として外部に公開する変更です。これにより、型チェックやコード分析を行うクライアント(特に exp/ssa のような高度なツール)が、Go言語の型同一性ルールに厳密に従った判定を容易に行えるようになります。

コミット

commit 0cbf289b36c27945dee513d29062f6894768a075
Author: Alan Donovan <adonovan@google.com>
Date:   Thu Jan 24 14:22:17 2013 -0500

    go/types: expose types.IsIdentical, the Type equivalence relation.
    
    This function is absolutely critical for clients such as
    exp/ssa, and too complex for clients to duplicate.
    
    As with CL 7200046, gri expressed in the doc below [gophers
    only] before going on leave that he intended to expose such a
    predicate, though his wording suggests as an interface method
    of Type rather than a standalone function.  (My preference is
    for binary methods to be standalone; see "On Binary Methods",
    Kim Bruce, 1995).  In any case if he wishes to move it that's
    easily accommodated by clients.
    
    https://docs.google.com/a/google.com/document/d/1-DQ4fxlMDs9cYtnkKhAAehX6MArjOQyJsRXp-6kiJLA/edit#heading=h.k3bwja7xony9
    
    R=iant, gri, iant
    CC=golang-dev
    https://golang.org/cl/7203051

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0cbf289b36c27945dee513d29062f6894768a075

元コミット内容

go/types: expose types.IsIdentical, the Type equivalence relation.

この関数は exp/ssa のようなクライアントにとって絶対に不可欠であり、クライアントが複製するには複雑すぎる。

CL 7200046 と同様に、gri は休暇に入る前に以下のドキュメント(社内向け)で、このような述語を公開する意図を表明していたが、彼の言葉遣いはスタンドアロン関数ではなく Type のインターフェースメソッドとして示唆していた。(私の好みはバイナリメソッドがスタンドアロンであることだ。「On Binary Methods」、Kim Bruce、1995年を参照)。いずれにせよ、彼がそれを移動させたい場合でも、クライアントは容易に対応できる。

変更の背景

Go言語の型システムにおいて、2つの型が「同一である」と判断される基準は、言語仕様によって厳密に定義されています。この同一性の判定ロジックは複雑であり、Goコンパイラやツールチェーンの内部で利用されていました。しかし、exp/ssa のような、Goプログラムの静的解析や最適化を行う高度なツールは、この厳密な型同一性判定ロジックを必要とします。

コミットメッセージによると、この isIdentical 関数は「クライアントが複製するには複雑すぎる」とされており、その重要性と複雑性が強調されています。そのため、Go言語の型システムを扱う go/types パッケージの外部インターフェースとして、この重要な機能を公開する必要がありました。これにより、exp/ssa のようなツールが、Go言語の型同一性ルールに則った正確な型比較を、自前で複雑なロジックを実装することなく行えるようになります。

また、コミットメッセージでは、以前の議論(CL 7200046)や、gri 氏(Go言語の主要な貢献者の一人)の意図が言及されており、この機能の公開が以前から検討されていたことが示唆されています。関数の公開方法(スタンドアロン関数かインターフェースメソッドか)についての議論も触れられており、最終的にスタンドアロン関数として公開する選択がなされたことがわかります。

前提知識の解説

Go言語の型システムと型同一性

Go言語では、各値は特定の型を持ちます。型は、その値がどのような操作をサポートし、どのようにメモリに表現されるかを定義します。Goの型システムは静的で、コンパイル時に型チェックが行われます。

Go言語における「型同一性(Type Identity)」は、2つの型が同じであると見なされるための厳密なルールです。これは、プログラミング言語の型システムにおいて非常に重要な概念であり、特に型チェック、コンパイル、リフレクション、そして高度な静的解析ツールにおいて中心的な役割を果たします。

Go言語仕様では、型の同一性について以下のように定義されています(Go 1.22の仕様に基づく、コミット当時の仕様も同様の原則に従います):

  • 定義済み型(Predeclared types): int, bool, string などの定義済み型は、常にそれ自身と同一です。
  • 型リテラル(Type literals):
    • 配列型: 要素の型と配列の長さが同一であれば同一です。例: [5]int[5]int は同一。
    • スライス型: 要素の型が同一であれば同一です。例: []int[]int は同一。
    • 構造体型: フィールドの数、名前、型、タグ、および宣言順が同一であれば同一です。
    • ポインタ型: ベースの型が同一であれば同一です。例: *int*int は同一。
    • 関数型: パラメータの数、型、戻り値の数、型、および可変長引数かどうかが同一であれば同一です。
    • インターフェース型: メソッドの数、名前、シグネチャ、および宣言順が同一であれば同一です。
    • マップ型: キーの型と要素の型が同一であれば同一です。
    • チャネル型: 要素の型とチャネルの方向が同一であれば同一です。
  • 名前付き型(Named types):
    • 異なるパッケージで定義された名前付き型は、たとえその基底型(underlying type)が同一であっても、常に異なる型と見なされます。
    • 同じパッケージで定義された名前付き型は、その型定義が同一であれば同一です。
    • 名前付き型と名前なし型(型リテラル)は、たとえ基底型が同一であっても、異なる型と見なされます。

この複雑なルールセットを正確に実装し、維持することは、Goコンパイラや関連ツールにとって不可欠です。

go/types パッケージ

go/types パッケージは、Go言語のソースコードを解析し、その型情報を表現するための標準ライブラリです。これは、Goコンパイラの型チェッカーのロジックを再利用可能な形で提供することを目的としています。go/types は、GoプログラムのAST(抽象構文木)を受け取り、各識別子や式の型を解決し、型エラーを検出します。

このパッケージは、以下のようなGoツールで広く利用されています。

  • go vet: 静的解析ツール
  • gopls: Go言語のLSP(Language Server Protocol)実装
  • IDEやエディタのGoプラグイン
  • コード生成ツール
  • リンター
  • exp/ssa のような高度な分析ツール

exp/ssa

exp/ssa は、Go言語のプログラムをSSA(Static Single Assignment)形式に変換し、その上で様々な最適化や解析を行うための実験的なパッケージです。SSA形式は、コンパイラ最適化において非常に強力な中間表現であり、変数の定義と使用の関係を明確にすることで、データフロー解析や最適化を容易にします。

exp/ssa のようなツールは、プログラムの正確な意味を理解するために、Go言語の型システムに関する深い知識と、型の同一性のような厳密なルールを適用する能力を必要とします。例えば、ある変数の型が別の変数の型と同一であるかどうかを正確に判断できなければ、SSA形式での変換やその後の最適化が正しく行えません。

「On Binary Methods」Kim Bruce, 1995

このコミットメッセージで参照されている「On Binary Methods」は、プログラミング言語における「バイナリメソッド(Binary Methods)」の設計に関する学術論文です。バイナリメソッドとは、2つの引数(レシーバと引数)が同じ型を持つ場合に、その型に依存する振る舞いを持つメソッドを指します。

この論文は、オブジェクト指向プログラミングにおける多重ディスパッチ(multiple dispatch)や、型システムにおける共変性(covariance)と反変性(contravariance)といった複雑な概念に関連しています。コミットメッセージの著者は、型の同一性判定関数を Type インターフェースのメソッドとしてではなく、スタンドアロン関数として公開する自身の選択を正当化するためにこの論文を引用しています。これは、特定の型に属するメソッドとしてではなく、2つの型を引数にとる独立した関数として設計する方が、より汎用的で柔軟な設計であるという思想に基づいていると考えられます。

技術的詳細

このコミットの技術的な核心は、Go言語の型システムにおける「型同一性」の厳密な定義を実装した内部関数 isIdentical を、外部から利用可能な IsIdentical 関数として公開することにあります。

Go言語では、パッケージ外部に公開される識別子(関数、変数、型など)は、先頭が大文字で始まらなければなりません(エクスポートルール)。したがって、内部関数 isIdentical を外部に公開するためには、その名前を IsIdentical に変更し、同時にその関数を呼び出しているすべての箇所も新しい名前に更新する必要があります。

isIdentical 関数は、src/pkg/go/types/predicates.go に定義されており、Go言語の型同一性ルール(前述の「前提知識の解説」で詳述)に従って、2つの Type インターフェースが表現する型が同一であるかどうかを再帰的に判定します。この関数は、以下のような様々な型リテラル(配列、スライス、構造体、ポインタ、関数、マップ、チャネル)や名前付き型に対して、それぞれの同一性ルールを適用します。

  • プリミティブ型: ポインタ比較 (x == y) で同一性を判定。
  • 配列型: 長さと要素の型が同一であるかを再帰的に IsIdentical で判定。
  • スライス型: 要素の型が同一であるかを再帰的に IsIdentical で判定。
  • 構造体型: フィールドの数、名前、型、タグ、匿名性、そして宣言順が同一であるかを再帰的に IsIdentical で判定。
  • ポインタ型: ベースの型が同一であるかを再帰的に IsIdentical で判定。
  • 関数型(Signature): パラメータと戻り値の型が同一であるかを再帰的に IsIdentical で判定。
  • マップ型: キーと要素の型が同一であるかを再帰的に IsIdentical で判定。
  • チャネル型: 方向と要素の型が同一であるかを再帰的に IsIdentical で判定。
  • 名前付き型: 型定義が同一であるかを判定。

このコミットでは、この複雑なロジック自体には変更を加えていません。変更の範囲は、isIdentical の可視性を内部から外部へと変更し、それに伴う名前の変更と、その関数を呼び出すすべての箇所での参照の更新に限定されています。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、主に以下の2点です。

  1. src/pkg/go/types/predicates.go 内で定義されていた内部関数 isIdentical の名前を IsIdentical に変更し、外部からアクセス可能にする(先頭を大文字にする)。
  2. go/types パッケージ内の他のファイル(builtins.go, conversions.go, expr.go, operand.go, predicates.go 自身)で isIdentical を呼び出していた箇所をすべて IsIdentical に更新する。

具体的には、src/pkg/go/types/predicates.go の以下の行が変更されています。

--- a/src/pkg/go/types/predicates.go
+++ b/src/pkg/go/types/predicates.go
@@ -92,8 +92,8 @@ func hasNil(typ Type) bool {
 	return false
 }
 
-// identical returns true if x and y are identical.
-func isIdentical(x, y Type) bool {
+// IsIdentical returns true if x and y are identical.
+func IsIdentical(x, y Type) bool {

そして、この変更に伴い、go/types パッケージ内の複数のファイルで isIdentical の呼び出しが IsIdentical に変更されています。

  • src/pkg/go/types/builtins.go
  • src/pkg/go/types/conversions.go
  • src/pkg/go/types/expr.go
  • src/pkg/go/types/operand.go
  • src/pkg/go/types/predicates.go (自身の内部での再帰呼び出し箇所)

これらの変更は、関数のシグネチャやロジック自体を変更するものではなく、単に関数の可視性と命名規則を調整するものです。

コアとなるコードの解説

src/pkg/go/types/predicates.go にある IsIdentical 関数は、Go言語の型システムにおける「型同一性」のルールを実装したものです。この関数は、2つの Type インターフェース型の引数 xy を受け取り、それらが表現する型がGo言語の仕様に基づいて同一である場合に true を返します。

関数の内部では、まずポインタ比較 x == y で同一性をチェックします。これは、同じ型オブジェクトへの参照であれば、それらは同一の型であるという最も基本的なケースをカバーします。

次に、switch x := x.(type)switch y := y.(type) を用いて、xy の具体的な基底型(underlying type)を判別し、それぞれの型に応じた同一性ルールを適用します。

例えば、*Array 型の場合:

	case *Array:
		// Two array types are identical if they have identical element types
		// and the same array length.
		if y, ok := y.(*Array); ok {
			return x.Len == y.Len && IsIdentical(x.Elt, y.Elt)
		}

ここでは、配列の長さ (x.Len == y.Len) と、要素の型 (x.Elty.Elt) が再帰的に IsIdentical 関数によって同一であるかをチェックしています。

同様に、*Struct 型の場合:

	case *Struct:
		// Two struct types are identical if they have the same sequence of fields,
		// and if corresponding fields have the same names, same types,
		// and same tags. Two anonymous fields are identical if their types are identical.
		if y, ok := y.(*Struct); ok {
			if len(x.Fields) != len(y.Fields) {
				return false
			}
			for i, f := range x.Fields {
				g := y.Fields[i]
				if !f.QualifiedName.IsSame(g.QualifiedName) ||
					!IsIdentical(f.Type, g.Type) ||
					f.Tag != g.Tag ||
					f.IsAnonymous != g.IsAnonymous {
					return false
				}
			}
			return true
		}

構造体のフィールドの数、名前、型、タグ、匿名性、そして宣言順がすべて一致するかを詳細にチェックしています。ここでも、フィールドの型を比較するために IsIdentical が再帰的に呼び出されています。

このように、IsIdentical 関数はGo言語の型同一性に関する複雑なルールを網羅的に実装しており、様々な複合型に対しても再帰的に同一性を判定することで、正確な結果を保証しています。この関数が外部に公開されたことで、Goの型システムを扱う外部ツールが、この厳密なロジックを容易に利用できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語仕様 (Type identity): https://go.dev/ref/spec#Type_identity
  • Go source code: src/pkg/go/types/predicates.go (特に IsIdentical 関数)
  • Go source code: src/pkg/go/types/builtins.go, src/pkg/go/types/conversions.go, src/pkg/go/types/expr.go, src/pkg/go/types/operand.go (変更箇所を確認)
  • exp/ssa の概要に関する情報 (例: Goブログ記事や関連するGoのドキュメント)
  • Kim Bruce の「On Binary Methods」に関する情報 (学術論文検索エンジンなど) - 直接的な論文へのリンクは困難な場合があるため、概念的な説明に留める。I have generated the comprehensive technical explanation in Markdown format, following all the specified instructions and chapter structure. The output is provided below.