[インデックス 14758] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である go/types
パッケージにおける重要な内部APIリファクタリングを目的としています。具体的には、抽象構文木(AST)を表す go/ast
パッケージの ast.Object
型が go/types
パッケージの公開APIから段階的に削除され、より型安全でセマンティックな内部表現に置き換えられています。
コミット
commit c8eb71b057dc28874902e9344d8a82de3edbb9eb
Author: Robert Griesemer <gri@golang.org>
Date: Fri Dec 28 14:30:36 2012 -0800
go/types: Steps towards removing ast.Object from exported API.
- introduced type Method for methods
- renamed StructField -> Field
- removed ObjList
- methods are not sorted anymore in interfaces (for now)
R=adonovan
CC=golang-dev
https://golang.org/cl/7023043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c8eb71b057dc28874902e9344d8a82de3edbb9eb
元コミット内容
このコミットの元の内容は以下の通りです。
- メソッドを表すために
Method
型を導入した。 StructField
をField
にリネームした。ObjList
を削除した。- インターフェース内のメソッドは(現時点では)ソートされなくなった。
変更の背景
このコミットが行われた2012年後半は、Go言語がバージョン1.0をリリースし、その後の安定化と改善が進められていた時期にあたります。go/types
パッケージは、Goプログラムの型チェックとセマンティック解析を行うための基盤を提供する重要なコンポーネントです。初期の設計では、ASTノードを直接参照する ast.Object
型が、型システム内の様々なエンティティ(変数、関数、型、フィールド、メソッドなど)を表すために広く使用されていました。
しかし、ast.Object
はASTの汎用的なオブジェクトであり、特定のセマンティックな意味合い(例えば、それが構造体のフィールドなのか、インターフェースのメソッドなのか)を直接表現するものではありませんでした。このため、go/types
パッケージの内部ロジックやAPIが ast.Object
に依存していると、以下のような問題が生じる可能性がありました。
- セマンティックな不明瞭さ:
ast.Object
だけでは、それがどのような種類のエンティティを表しているのかがすぐに分かりません。型アサーションや追加のチェックが必要となり、コードの可読性や保守性が低下します。 - APIの安定性:
ast.Object
はASTの構造に密接に関連しており、ASTの変更がgo/types
のAPIに直接影響を与える可能性があります。go/types
は型システムというより高レベルな概念を扱うため、ASTの低レベルな詳細から独立したAPIを持つことが望ましいです。 - 型安全性の向上:
ast.Object
を汎用的に使用する代わりに、Method
やField
のような専用の型を導入することで、コンパイル時の型チェックを強化し、ランタイムエラーのリスクを減らすことができます。 - 内部表現の最適化:
go/types
が独自のセマンティックな型を定義することで、ASTの構造に縛られずに、型チェックや解析に最適なデータ構造を選択できるようになります。
このコミットは、このような背景から、go/types
パッケージの内部構造を改善し、より堅牢で保守性の高い型システムを構築するための「ast.Object
を公開APIから削除する」という長期的な目標に向けた最初の一歩として位置づけられます。特に、api.go
に追加された「WARNING: THE TYPES API IS SUBJECT TO SIGNIFICANT CHANGE.」というコメントは、この時期に go/types
パッケージが活発に開発・リファクタリングされていたことを明確に示しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラ関連の概念に関する知識が必要です。
-
Go言語の型システム:
- 構造体 (Struct): 複数のフィールド(メンバー変数)をまとめた複合型。各フィールドは名前と型を持ちます。
- インターフェース (Interface): メソッドのシグネチャの集合を定義する型。インターフェースのすべてのメソッドを実装する型は、そのインターフェースを満たします。
- メソッド (Method): 特定の型に関連付けられた関数。レシーバ引数を持つことで、その型の値に対して操作を行います。
- 型アサーション (Type Assertion): インターフェース値の基になる具体的な型を動的にチェックし、その型に変換する操作。例:
v.(T)
。
-
Goコンパイラの内部構造:
go/ast
パッケージ: Goソースコードの抽象構文木(AST)を表現するためのデータ構造を提供します。ast.File
、ast.Decl
、ast.Expr
などの型が含まれます。ast.Object
:go/ast
パッケージで定義される汎用的なインターフェースで、識別子(変数、関数、型、ラベルなど)が参照する「オブジェクト」を表します。Name
、Kind
、Decl
などのフィールドを持ちます。go/types
パッケージ: Goプログラムの型チェックとセマンティック解析を担当します。ASTを解析し、型情報、スコープ、定義と使用の関係などを構築します。コンパイラのフロントエンドにおける重要なフェーズです。go/importer
パッケージ: コンパイル済みのGoパッケージの型情報をインポートするための機能を提供します。go/types
パッケージが他のパッケージの型情報を参照する際に利用されます。
-
Goのコードレビューシステム (Gerrit):
https://golang.org/cl/7023043
のようなURLは、Goプロジェクトが使用しているGerritというコードレビューシステムにおける変更リスト(Change-ID)を指します。これはGitHubのプルリクエストに相当するもので、コミットがマージされる前にレビューが行われた場所を示します。
これらの知識を前提として、このコミットが go/types
パッケージの内部でどのように型表現を改善し、ast.Object
への依存を減らしているかを詳細に見ていきます。
技術的詳細
このコミットの技術的な詳細は、主に go/types
パッケージ内のデータ構造の変更と、それに伴う関連コードの修正に集約されます。
1. Method
型の導入
以前は、インターフェースのメソッドは ast.Object
として表現され、その Type
フィールドに *Signature
型が格納されていました。このコミットでは、メソッド専用の新しい型 types.Method
が導入されました。
// src/pkg/go/types/types.go
type Method struct {
Name string
Type *Signature
}
この変更により、メソッドに関する情報をよりセマンティックに、かつ型安全に扱うことができるようになりました。ast.Object
の汎用的な Kind
フィールドや Decl
フィールドに依存することなく、Method
型自体がメソッドの名前とシグネチャを直接保持します。
2. StructField
から Field
へのリネーム
構造体のフィールドを表す types.StructField
型が types.Field
にリネームされました。
// src/pkg/go/types/types.go
// A Field represents a field of a struct.
type Field struct {
Name string // unqualified type name for anonymous fields
Type Type
Tag string
Anonymous bool
}
これは、StructField
という名前が冗長であり、Field
だけで十分その役割を表現できるという判断に基づいていると考えられます。また、Method
型の導入と合わせて、go/types
パッケージ内で「フィールド」と「メソッド」という概念を明確に区別し、それぞれに特化した型を提供することで、APIの一貫性と明瞭性を高める意図があるでしょう。
3. ObjList
の削除
ObjList
は []*ast.Object
のエイリアスであり、sort.Interface
を実装して ast.Object
のリストを名前でソートする機能を提供していました。
// src/pkg/go/types/types.go (削除前)
type ObjList []*ast.Object
func (list ObjList) Len() int { return len(list) }
func (list ObjList) Less(i, j int) bool { return list[i].Name < list[j].Name }
func (list ObjList) Swap(i, j int) { list[i], list[j] = list[j], list[i] }
func (list ObjList) Sort() { sort.Sort(list) }
このコミットでは、ObjList
型自体が削除されました。これに伴い、Signature
の Params
および Results
フィールド、Result
の Values
フィールド、そして Interface
の Methods
フィールドの型が ObjList
から []*ast.Object
または []*Method
に変更されました。
特に重要なのは、インターフェースのメソッドリスト (Interface.Methods
) が ObjList
から []*Method
に変更され、それに伴い ObjList
が提供していたソート機能が削除された点です。コミットメッセージにある「methods are not sorted anymore in interfaces (for now)」という記述は、この変更を反映しています。これは、インターフェースのメソッドの順序がセマンティックな意味を持たないため、go/types
パッケージが内部でソートを強制する必要がない、あるいはソートの責任を別の場所に移行するという意図を示唆しています。実際、Go言語の仕様ではインターフェースのメソッドの順序は定義されていません。
4. ast.Object
への依存の削減
上記の変更は、go/types
パッケージ全体で ast.Object
への直接的な依存を減らすための広範な修正を伴います。
src/pkg/go/types/expr.go
:collectParams
やcollectMethods
のような関数が、ObjList
の代わりに[]*ast.Object
や[]*Method
を返すように変更されました。特にcollectMethods
では、メソッドの重複チェックロジックがObjList.Sort()
に依存する代わりに、map[string]bool
を使用して手動で実装されています。src/pkg/go/types/gcimporter.go
: コンパイル済みパッケージの型情報をインポートする際に、StructField
やObjList
の代わりに新しいField
やMethod
型を使用するようにパーサーが更新されました。src/pkg/go/types/operand.go
: 型ルックアップのロジックで、obj.Type.(Type)
のような型アサーションがm.Type
のように直接Method
型のType
フィールドを参照するように簡素化されました。src/pkg/go/types/predicates.go
: 型の同一性をチェックするisIdentical
やidenticalTypes
関数が、新しい型構造に合わせて修正されました。特に、identicalMethods
という新しい関数が導入され、[]*Method
のリストを比較するロジックが実装されました。この関数は、メソッドの名前とシグネチャが同一であるかをチェックしますが、順序は考慮しません。src/pkg/go/types/universe.go
: Goの組み込み型や組み込み関数を定義するuniverse
パッケージでも、error
インターフェースの定義が新しいMethod
型を使用するように更新されました。
これらの変更は、go/types
パッケージがASTの低レベルな表現から独立し、よりセマンティックで高レベルな型システムを内部で構築しようとしていることを示しています。これにより、将来的なGo言語の進化や型システムの拡張に対して、より柔軟に対応できる基盤が築かれます。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。
-
src/pkg/go/types/types.go
:Method
型の新規定義。StructField
型をField
型にリネーム。ObjList
型の定義とその関連メソッド(Len
,Less
,Swap
,Sort
)の削除。Struct
,Result
,Signature
,Interface
型のフィールドが、ObjList
やStructField
から、それぞれ[]*Field
,[]*ast.Object
,[]*Method
に変更。import "sort"
の削除。
-
src/pkg/go/types/expr.go
:collectMethods
関数がObjList
の代わりに[]*Method
を返すように変更され、メソッドの重複チェックロジックがmap
を使用するように修正。collectParams
およびcollectFields
関数の戻り値の型が変更。
-
src/pkg/go/types/predicates.go
:identicalMethods
関数が新規追加され、[]*Method
のリストの同一性チェックを実装。identicalTypes
関数がObjList
の代わりに[]*ast.Object
を受け取るように変更。missingMethod
関数の戻り値の型が*ast.Object
から*Method
に変更。
-
src/pkg/go/types/gcimporter.go
:parseField
およびparseInterfaceType
関数が、新しいField
およびMethod
型を使用するように変更。特にparseInterfaceType
では、インポート時にメソッドをソートする処理が削除。
これらの変更は、go/types
パッケージの内部データモデルの根本的なシフトを反映しており、ast.Object
への依存を減らし、よりセマンティックな型表現を導入するというコミットの目的を達成しています。
コアとなるコードの解説
src/pkg/go/types/types.go
の変更
--- a/src/pkg/go/types/types.go
+++ b/src/pkg/go/types/types.go
@@ -4,13 +4,9 @@
package types
-import (
- "go/ast"
- "sort"
-)
+import "go/ast"
// All types implement the Type interface.
-// TODO(gri) Eventually determine what common Type functionality should be exported.
type Type interface {
aType()
}
@@ -95,7 +91,8 @@ type Slice struct {
Elt Type
}
-type StructField struct {
+// A Field represents a field of a struct.
+type Field struct {
Name string // unqualified type name for anonymous fields
Type Type
Tag string
@@ -105,7 +102,7 @@ type StructField struct {
// A Struct represents a struct type struct{...}.
type Struct struct {
implementsType
- Fields []*StructField
+ Fields []*Field
}
func (typ *Struct) fieldIndex(name string) int {
@@ -126,16 +123,16 @@ type Pointer struct {
// A Result represents a (multi-value) function call result.
type Result struct {
implementsType
- Values ObjList // Signature.Results of the function called
+ Values []*ast.Object // Signature.Results of the function called
}
// A Signature represents a user-defined function type func(...) (...).
type Signature struct {
implementsType
- Recv *ast.Object // nil if not a method
- Params ObjList // (incoming) parameters from left to right; or nil
- Results ObjList // (outgoing) results from left to right; or nil
- IsVariadic bool // true if the last parameter\'s type is of the form ...T
+ Recv *ast.Object // nil if not a method
+ Params []*ast.Object // (incoming) parameters from left to right; or nil
+ Results []*ast.Object // (outgoing) results from left to right; or nil
+ IsVariadic bool // true if the last parameter\'s type is of the form ...T
}
// builtinId is an id of a builtin function.
@@ -180,10 +177,16 @@ type builtin struct {\n \tisStatement bool // true if the built-in is valid as an expression statement
}\n \n+// A Method represents a method of an interface.\n+type Method struct {\n+\tName string\n+\tType *Signature\n+}\n+\n // An Interface represents an interface type interface{...}.\n type Interface struct {\n implementsType
- Methods ObjList // interface methods sorted by name; or nil
+ Methods []*Method // TODO(gri) consider keeping them in sorted order
}\n \n // A Map represents a map type map[Key]Elt.\
@@ -206,17 +209,6 @@ type NamedType struct {\n Underlying Type // nil if not fully declared yet; never a *NamedType
}\n \n-// An ObjList represents an ordered (in some fashion) list of objects.\n-type ObjList []*ast.Object\n-\n-// ObjList implements sort.Interface.\n-func (list ObjList) Len() int { return len(list) }\n-func (list ObjList) Less(i, j int) bool { return list[i].Name < list[j].Name }\n-func (list ObjList) Swap(i, j int) { list[i], list[j] = list[j], list[i] }\n-\n-// Sort sorts an object list by object name.\n-func (list ObjList) Sort() { sort.Sort(list) }\n-\n // All concrete types embed implementsType which\n // ensures that all types implement the Type interface.\n type implementsType struct{}\n```
* **`import "sort"` の削除**: `ObjList` が削除され、そのソート機能が不要になったため、`sort` パッケージのインポートが削除されました。
* **`StructField` から `Field` へのリネーム**: `StructField` 型が `Field` 型に名前が変更され、構造体 `Struct` の `Fields` フィールドの型も `[]*StructField` から `[]*Field` に変更されました。これにより、構造体のフィールドをより簡潔に表現できるようになりました。
* **`Method` 型の導入**: インターフェースのメソッドを表現するための新しい `Method` 型が導入されました。これは `Name` (メソッド名) と `Type` (メソッドのシグネチャである `*Signature`) を持ちます。
* **`ObjList` の削除と型変更**:
* `ObjList` 型自体が完全に削除されました。
* `Result.Values`、`Signature.Params`、`Signature.Results` の各フィールドの型が `ObjList` から `[]*ast.Object` に変更されました。これは、これらのリストが `ast.Object` のスライスとして扱われることを明確にしますが、`ObjList` が提供していたソート機能は失われます。
* `Interface.Methods` フィールドの型が `ObjList` から `[]*Method` に変更されました。これにより、インターフェースのメソッドが `ast.Object` ではなく、新しく導入された `Method` 型のリストとして直接表現されるようになります。コメント `// TODO(gri) consider keeping them in sorted order` は、将来的にソートが必要になる可能性を考慮していることを示唆していますが、このコミット時点ではソートは行われません。
### `src/pkg/go/types/expr.go` の `collectMethods` 関数の変更
```diff
--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -70,7 +70,7 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
return
}
-func (check *checker) collectMethods(list *ast.FieldList) (methods ObjList) {
+func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) {
if list == nil {
return
}
@@ -81,14 +81,13 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods ObjList) {
if len(f.Names) > 0 {
// methods (the parser ensures that there's only one
// and we don't care if a constructed AST has more)
- if _, ok := typ.(*Signature); !ok {
+ sig, ok := typ.(*Signature)
+ if !ok {
check.invalidAST(f.Type.Pos(), "%s is not a method signature", typ)
continue
}
for _, name := range f.Names {
- obj := name.Obj
- obj.Type = typ
- methods = append(methods, obj)
+ methods = append(methods, &Method{name.Name, sig})
}
} else {
// embedded interface
@@ -101,14 +100,20 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods ObjList) {
}
}
}
- // check for double declarations
- methods.Sort()
- prev := ""
- for _, obj := range methods {
- if obj.Name == prev {
- check.errorf(list.Pos(), "multiple methods named %s", prev)
+ // Check for double declarations.
+ // The parser inserts methods into an interface-local scope, so local
+ // double declarations are reported by the parser already. We need to
+ // check again for conflicts due to embedded interfaces. This will lead
+ // to a 2nd error message if the double declaration was reported before
+ // by the parser.
+ // TODO(gri) clean this up a bit
+ seen := make(map[string]bool)
+ for _, m := range methods {
+ if seen[m.Name] {
+ check.errorf(list.Pos(), "multiple methods named %s", m.Name)
return // keep multiple entries, lookup will only return the first entry
}
+ seen[m.Name] = true
}
return
}
- 戻り値の型変更:
collectMethods
関数がObjList
の代わりに[]*Method
を返すように変更されました。 Method
型のインスタンス化:ast.Object
を作成してType
を設定する代わりに、直接&Method{name.Name, sig}
を使用してMethod
型のインスタンスを作成し、リストに追加しています。これにより、メソッドの表現がより直接的になりました。- 重複チェックロジックの変更:
ObjList.Sort()
に依存していたメソッドの重複チェックが、map[string]bool
を使用した手動のチェックに置き換えられました。これは、ObjList
の削除と、インターフェースのメソッドがソートされなくなったことによる必然的な変更です。seen
マップを使用して、既に処理したメソッド名を追跡し、重複があればエラーを報告します。
src/pkg/go/types/predicates.go
の identicalMethods
関数の新規追加
--- a/src/pkg/go/types/predicates.go
+++ b/src/pkg/go/types/predicates.go
@@ -194,17 +194,36 @@ func isIdentical(x, y Type) bool {\n
// identicalTypes returns true if both lists a and b have the
// same length and corresponding objects have identical types.
-func identicalTypes(a, b ObjList) bool {\n- if len(a) == len(b) {\n- for i, x := range a {\n- y := b[i]\n- if !isIdentical(x.Type.(Type), y.Type.(Type)) {\n- return false\n- }\n+func identicalTypes(a, b []*ast.Object) bool {\n+ if len(a) != len(b) {\n+ return false
+ }\n+ for i, x := range a {\n+ y := b[i]\n+ if !isIdentical(x.Type.(Type), y.Type.(Type)) {\n+ return false
}\n- return true
}\n- return false
+ return true
+}\n+\n+// identicalMethods returns true if both lists a and b have the
+// same length and corresponding methods have identical types.
+// TODO(gri) make this more efficient
+func identicalMethods(a, b []*Method) bool {\n+ if len(a) != len(b) {\n+ return false
+ }\n+ m := make(map[string]*Method)\n+ for _, x := range a {\n+ m[x.Name] = x
+ }\n+ for _, y := range b {\n+ if x := m[y.Name]; x == nil || !isIdentical(x.Type, y.Type) {\n+ return false
+ }\n+ }\n+ return true
}
identicalMethods
の導入: インターフェースのメソッドリスト ([]*Method
) の同一性をチェックするための新しい関数identicalMethods
が追加されました。- 順序の無視: この関数は、メソッドの順序を無視して同一性をチェックします。これは、
map[string]*Method
を使用して一方のリストのメソッドを名前でインデックス付けし、もう一方のリストのメソッドが同じ名前と同一のシグネチャを持つかを検証することで実現されます。Go言語の仕様ではインターフェースのメソッドの順序は重要ではないため、このアプローチは適切です。 - 効率性に関するTODO: コメント
// TODO(gri) make this more efficient
は、現在の実装がO(N^2)に近い複雑度を持つ可能性があり、将来的に最適化の余地があることを示唆しています。
これらの変更は、go/types
パッケージが ast.Object
の汎用的な表現から脱却し、型システム内の特定の概念(メソッド、フィールド)に対してより特化した、型安全な内部表現を採用していることを明確に示しています。これにより、コードの意図がより明確になり、将来的なメンテナンスや拡張が容易になります。
関連リンク
- Go言語の
go/types
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/go/types - Go言語の
go/ast
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/go/ast - Go言語の仕様 - インターフェース型: https://go.dev/ref/spec#Interface_types
- Go言語の仕様 - 構造体型: https://go.dev/ref/spec#Struct_types
参考にした情報源リンク
- GoのGerritコードレビューシステム: https://go-review.googlesource.com/
- このコミットのGerrit変更リスト: https://golang.org/cl/7023043
- Go言語の歴史と進化に関する情報 (一般的なGoの進化の文脈を理解するため): https://go.dev/doc/history
- Go言語のコンパイラ設計に関する一般的な情報 (
go/types
の役割を理解するため): https://go.dev/doc/articles/go_command (Goコマンドの概要だが、コンパイルプロセスに触れている) - Go言語の型システムに関する議論 (一般的なGoの型システムの設計思想を理解するため): https://go.dev/blog/go-type-system (Go 1.0リリース後のブログ記事など)
ast.Object
の役割に関する一般的な情報 (GoのASTとセマンティック解析の関連を理解するため): https://pkg.go.dev/go/ast#Object (現在のドキュメント)- Go言語のインターフェースの内部表現に関する議論 (一般的なインターフェースの実装詳細を理解するため): https://research.swtch.com/interfaces (Russ Coxによるブログ記事)