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

[インデックス 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 型を導入した。
  • StructFieldField にリネームした。
  • ObjList を削除した。
  • インターフェース内のメソッドは(現時点では)ソートされなくなった。

変更の背景

このコミットが行われた2012年後半は、Go言語がバージョン1.0をリリースし、その後の安定化と改善が進められていた時期にあたります。go/types パッケージは、Goプログラムの型チェックとセマンティック解析を行うための基盤を提供する重要なコンポーネントです。初期の設計では、ASTノードを直接参照する ast.Object 型が、型システム内の様々なエンティティ(変数、関数、型、フィールド、メソッドなど)を表すために広く使用されていました。

しかし、ast.Object はASTの汎用的なオブジェクトであり、特定のセマンティックな意味合い(例えば、それが構造体のフィールドなのか、インターフェースのメソッドなのか)を直接表現するものではありませんでした。このため、go/types パッケージの内部ロジックやAPIが ast.Object に依存していると、以下のような問題が生じる可能性がありました。

  1. セマンティックな不明瞭さ: ast.Object だけでは、それがどのような種類のエンティティを表しているのかがすぐに分かりません。型アサーションや追加のチェックが必要となり、コードの可読性や保守性が低下します。
  2. APIの安定性: ast.Object はASTの構造に密接に関連しており、ASTの変更が go/types のAPIに直接影響を与える可能性があります。go/types は型システムというより高レベルな概念を扱うため、ASTの低レベルな詳細から独立したAPIを持つことが望ましいです。
  3. 型安全性の向上: ast.Object を汎用的に使用する代わりに、MethodField のような専用の型を導入することで、コンパイル時の型チェックを強化し、ランタイムエラーのリスクを減らすことができます。
  4. 内部表現の最適化: go/types が独自のセマンティックな型を定義することで、ASTの構造に縛られずに、型チェックや解析に最適なデータ構造を選択できるようになります。

このコミットは、このような背景から、go/types パッケージの内部構造を改善し、より堅牢で保守性の高い型システムを構築するための「ast.Object を公開APIから削除する」という長期的な目標に向けた最初の一歩として位置づけられます。特に、api.go に追加された「WARNING: THE TYPES API IS SUBJECT TO SIGNIFICANT CHANGE.」というコメントは、この時期に go/types パッケージが活発に開発・リファクタリングされていたことを明確に示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラ関連の概念に関する知識が必要です。

  1. Go言語の型システム:

    • 構造体 (Struct): 複数のフィールド(メンバー変数)をまとめた複合型。各フィールドは名前と型を持ちます。
    • インターフェース (Interface): メソッドのシグネチャの集合を定義する型。インターフェースのすべてのメソッドを実装する型は、そのインターフェースを満たします。
    • メソッド (Method): 特定の型に関連付けられた関数。レシーバ引数を持つことで、その型の値に対して操作を行います。
    • 型アサーション (Type Assertion): インターフェース値の基になる具体的な型を動的にチェックし、その型に変換する操作。例: v.(T)
  2. Goコンパイラの内部構造:

    • go/ast パッケージ: Goソースコードの抽象構文木(AST)を表現するためのデータ構造を提供します。ast.Fileast.Declast.Expr などの型が含まれます。
    • ast.Object: go/ast パッケージで定義される汎用的なインターフェースで、識別子(変数、関数、型、ラベルなど)が参照する「オブジェクト」を表します。NameKindDecl などのフィールドを持ちます。
    • go/types パッケージ: Goプログラムの型チェックとセマンティック解析を担当します。ASTを解析し、型情報、スコープ、定義と使用の関係などを構築します。コンパイラのフロントエンドにおける重要なフェーズです。
    • go/importer パッケージ: コンパイル済みのGoパッケージの型情報をインポートするための機能を提供します。go/types パッケージが他のパッケージの型情報を参照する際に利用されます。
  3. 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 型自体が削除されました。これに伴い、SignatureParams および Results フィールド、ResultValues フィールド、そして InterfaceMethods フィールドの型が 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: collectParamscollectMethods のような関数が、ObjList の代わりに []*ast.Object[]*Method を返すように変更されました。特に collectMethods では、メソッドの重複チェックロジックが ObjList.Sort() に依存する代わりに、map[string]bool を使用して手動で実装されています。
  • src/pkg/go/types/gcimporter.go: コンパイル済みパッケージの型情報をインポートする際に、StructFieldObjList の代わりに新しい FieldMethod 型を使用するようにパーサーが更新されました。
  • src/pkg/go/types/operand.go: 型ルックアップのロジックで、obj.Type.(Type) のような型アサーションが m.Type のように直接 Method 型の Type フィールドを参照するように簡素化されました。
  • src/pkg/go/types/predicates.go: 型の同一性をチェックする isIdenticalidenticalTypes 関数が、新しい型構造に合わせて修正されました。特に、identicalMethods という新しい関数が導入され、[]*Method のリストを比較するロジックが実装されました。この関数は、メソッドの名前とシグネチャが同一であるかをチェックしますが、順序は考慮しません。
  • src/pkg/go/types/universe.go: Goの組み込み型や組み込み関数を定義する universe パッケージでも、error インターフェースの定義が新しい Method 型を使用するように更新されました。

これらの変更は、go/types パッケージがASTの低レベルな表現から独立し、よりセマンティックで高レベルな型システムを内部で構築しようとしていることを示しています。これにより、将来的なGo言語の進化や型システムの拡張に対して、より柔軟に対応できる基盤が築かれます。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。

  1. src/pkg/go/types/types.go:

    • Method 型の新規定義。
    • StructField 型を Field 型にリネーム。
    • ObjList 型の定義とその関連メソッド(Len, Less, Swap, Sort)の削除。
    • Struct, Result, Signature, Interface 型のフィールドが、ObjListStructField から、それぞれ []*Field, []*ast.Object, []*Method に変更。
    • import "sort" の削除。
  2. src/pkg/go/types/expr.go:

    • collectMethods 関数が ObjList の代わりに []*Method を返すように変更され、メソッドの重複チェックロジックが map を使用するように修正。
    • collectParams および collectFields 関数の戻り値の型が変更。
  3. src/pkg/go/types/predicates.go:

    • identicalMethods 関数が新規追加され、[]*Method のリストの同一性チェックを実装。
    • identicalTypes 関数が ObjList の代わりに []*ast.Object を受け取るように変更。
    • missingMethod 関数の戻り値の型が *ast.Object から *Method に変更。
  4. 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.goidenticalMethods 関数の新規追加

--- 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 の汎用的な表現から脱却し、型システム内の特定の概念(メソッド、フィールド)に対してより特化した、型安全な内部表現を採用していることを明確に示しています。これにより、コードの意図がより明確になり、将来的なメンテナンスや拡張が容易になります。

関連リンク

参考にした情報源リンク