[インデックス 14772] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部であるgo/types
パッケージに対する変更です。go/types
パッケージは、Goプログラムの型チェックとセマンティック分析を行うための重要なコンポーネントです。このコミットでは、主に以下のファイルが変更されています。
src/pkg/go/types/check.go
: 型チェックの主要ロジックとエラーハンドリング。src/pkg/go/types/errors.go
: 型チェックエラーの報告とフォーマット。src/pkg/go/types/expr.go
: 式の型チェック。src/pkg/go/types/gcimporter.go
:gc
コンパイラによって生成されたバイナリから型情報をインポートするロジック。src/pkg/go/types/predicates.go
: 型に関する述語(例: 型が同一かどうかのチェック)。src/pkg/go/types/stmt.go
: ステートメントの型チェック。src/pkg/go/types/types.go
:go/types
パッケージの主要な型定義。src/pkg/go/types/universe.go
: 組み込み型やユニバーススコープの定義。
これらの変更は、go/types
パッケージの内部APIからgo/ast
パッケージのast.Object
型への依存を減らし、よりクリーンで独立した型システムを構築することを目的としています。また、エラーハンドリングに関するバグ修正も含まれています。
コミット
go/types
パッケージの公開APIからast.Object
のほとんどの残りの使用箇所を削除しました。
また、エラーハンドラがコンテキストに設定されているかどうかにかかわらず、最初のエラーを返すように修正しました(バグ修正)。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a846d479b319c54780ce83c94d7f7b5cc28b94c2
元コミット内容
go/types: remove most remaining uses of ast.Object from exported API
Also: Return first error whether an error handler is set in the
context or not (bug fix).
R=adonovan
CC=golang-dev
https://golang.org/cl/7024043
変更の背景
このコミットの主な背景は、Go言語のコンパイラツールチェーンにおけるモジュール間の依存関係の整理と、より堅牢なエラーハンドリングの実現です。
-
go/types
とgo/ast
の分離: Go言語のツールチェーンでは、ソースコードはまずgo/parser
によって抽象構文木(AST)にパースされ、そのASTがgo/types
パッケージによって型チェックされます。初期の実装では、go/types
パッケージの内部構造やAPIがgo/ast
パッケージのast.Object
型に直接依存している部分が多く存在しました。 しかし、理想的には、型システムはASTの具体的な表現から独立しているべきです。これにより、go/types
パッケージはAST以外のソース(例えば、コンパイル済みバイナリからの型情報)からも型チェックを行えるようになり、モジュール性が向上し、将来的な拡張性やパフォーマンス改善の余地が生まれます。ast.Object
への依存を減らすことで、go/types
パッケージはより抽象的な「オブジェクト」の概念(名前と型を持つエンティティ)を独自に定義し、ASTの具体的なノードに縛られずに型情報を扱うことができるようになります。これは、コンパイラのフロントエンドとバックエンドの間の明確な分離を促進する一般的な設計原則です。 -
エラーハンドリングの改善(バグ修正):
go/types
パッケージは、型チェック中に発生したエラーを報告するメカニズムを持っています。このコミット以前は、エラーハンドラが設定されているかどうかに応じて、最初のエラーの扱いが異なるというバグがありました。これは、型チェックのプロセスが途中でパニック(panic
)し、そのパニックがrecover
される際に、エラーハンドラの有無によって最終的に返されるエラーが意図せず変わってしまう可能性があったことを示唆しています。このバグは、型チェックの結果の一貫性を損なうものであり、修正が必要でした。
これらの変更は、Goコンパイラの内部構造をよりクリーンで保守しやすいものにし、将来的な開発の基盤を強化することを目的としています。
前提知識の解説
Go言語のgo/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードを抽象構文木(Abstract Syntax Tree, AST)として表現するためのデータ構造と関数を提供します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラやリンター、コード分析ツールなどがソースコードを理解し、操作するために利用します。
ast.Node
: ASTのすべてのノードが実装するインターフェース。ast.Expr
: 式を表すノード。ast.Stmt
: ステートメントを表すノード。ast.Decl
: 宣言を表すノード。ast.Object
: 識別子(変数、関数、型など)の宣言を表す汎用的な構造体。名前、種類(変数、関数など)、宣言された場所などの情報を含みます。ast.Object
は、AST内の異なるノード間でシンボル解決を行うためのリンケージを提供します。
Go言語のgo/types
パッケージ
go/types
パッケージは、Go言語のプログラムに対して型チェックとセマンティック分析を行うためのパッケージです。これは、Goコンパイラの重要な部分であり、プログラムがGo言語の型規則に準拠していることを保証します。
- 型チェック: 変数の使用、関数の呼び出し、演算子の適用などが、定義された型と互換性があるかを検証します。
- セマンティック分析: プログラムの意味論的な正当性を検証します。例えば、未宣言の変数の使用、到達不能なコード、インポートされたパッケージの解決などを行います。
- 型システム:
go/types
は独自の型システムを定義しており、ast.Expr
などのASTノードから独立した形で型情報を表現します。
ast.Object
とgo/types
の関連性
go/types
パッケージは、ASTを走査しながら型チェックを行うため、go/ast
パッケージの構造体と密接に連携します。特に、識別子(変数名、関数名など)の型情報を管理する際に、ast.Object
が利用されることがありました。しかし、ast.Object
はASTの具体的なノードに紐づくため、go/types
がより抽象的な型システムを構築する上で、直接的な依存は望ましくありません。このコミットは、その依存を解消し、go/types
が独自の「シンボル」表現を持つようにする一環です。
型システムとASTの分離の重要性
型システムとASTの分離は、コンパイラ設計における重要な原則です。
- モジュール性: 型システムがASTの具体的な表現から独立することで、型チェックロジックをASTの変更から隔離できます。これにより、ASTの変更が型チェッカーに与える影響を最小限に抑えられます。
- 再利用性: 型システムをASTから分離することで、異なるフロントエンド(例えば、異なる言語のパーサーや、コンパイル済みバイナリからの情報)からでも同じ型チェッカーを利用できるようになります。
- パフォーマンス: ASTノードは多くの情報を持つため、型情報だけが必要な場合にASTノード全体を扱うのは非効率な場合があります。より軽量な独自の型表現を用いることで、パフォーマンスが向上する可能性があります。
- 抽象化: 型システムは、プログラムのセマンティクス(意味)を扱うべきであり、その構文(AST)の具体的な詳細に縛られるべきではありません。分離することで、より高レベルな抽象化が可能になります。
Go言語のエラーハンドリング(panic
とrecover
)
Go言語では、エラーは通常、多値戻り値(error
型)で扱われますが、回復不可能な例外的な状況ではpanic
とrecover
が使用されます。
panic
: プログラムの実行を中断し、現在のゴルーチンのスタックをアンワインドします。recover
:defer
関数内で呼び出されると、panic
からの回復を試み、パニックに渡された値を返します。recover
がnil
を返した場合、パニックは発生していません。
go/types
パッケージのような複雑なシステムでは、内部的な整合性チェックや予期せぬ状態を検出するためにpanic
が使用されることがあります。このコミットでは、check.go
のcheck
関数内でpanic
をrecover
するロジックがあり、その中でエラーハンドリングのバグが修正されています。
技術的詳細
このコミットの技術的詳細は、主にgo/types
パッケージ内でのast.Object
から新しいVar
型への移行、およびエラーハンドリングの修正に集約されます。
1. ast.Object
から*Var
への移行
最も重要な変更は、go/types
パッケージの内部および公開APIで、go/ast
パッケージのast.Object
型への依存を減らし、代わりにgo/types
パッケージ内で新しく定義されたVar
型を使用する点です。
-
Var
構造体の導入:src/pkg/go/types/types.go
に新しい構造体Var
が導入されました。// A Variable represents a variable (including function parameters and results). type Var struct { Name string Type Type }
この
Var
構造体は、変数、関数パラメータ、関数結果など、名前と型を持つエンティティを表現するためのものです。これにより、go/types
パッケージはast.Object
に依存することなく、独自のセマンティックなオブジェクト表現を持つことができます。 -
Signature
構造体の変更: 関数のシグネチャを表すSignature
構造体も変更されました。--- a/src/pkg/go/types/types.go +++ b/src/pkg/go/types/types.go @@ -120,19 +120,25 @@ type Pointer struct { Base Type } +// A Variable represents a variable (including function parameters and results). +type Var struct { + Name string + Type Type +} + // A Result represents a (multi-value) function call result. type Result struct { implementsType - Values []*ast.Object // Signature.Results of the function called + Values []*Var // 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 []*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 + Recv *Var // nil if not a method + Params []*Var // (incoming) parameters from left to right; or nil + Results []*Var // (outgoing) results from left to right; or nil + IsVariadic bool // true if the last parameter's type is of the form ...T }
Recv
(レシーバ)、Params
(パラメータ)、Results
(結果) の各フィールドが、*ast.Object
のスライスから*Var
のスライスに変更されました。これにより、関数のシグネチャがASTの具体的な表現から完全に独立しました。 -
Result
構造体の変更: 関数の結果を表すResult
構造体も同様に、Values
フィールドが*ast.Object
のスライスから*Var
のスライスに変更されました。 -
関連する関数のシグネチャ変更:
writeParams
(errors.go
)、collectParams
(expr.go
)、parseParameter
(gcimporter.go
)、parseParameters
(gcimporter.go
)、parseSignature
(gcimporter.go
)、identicalTypes
(predicates.go
) など、ast.Object
を引数や戻り値として使用していた多くの関数が、*Var
を使用するようにシグネチャが変更されました。 -
gcimporter.go
での変更:gcimporter.go
は、コンパイル済みGoバイナリから型情報をインポートする役割を担っています。このファイルでは、パラメータや結果の解析時にast.NewObj
を呼び出してast.Object
を作成する代わりに、直接&Var{name, typ}
のような形でVar
構造体を生成するように変更されました。これにより、インポートされた型情報もast.Object
に依存しない形で表現されるようになります。 -
predicates.go
からのgo/ast
のインポート削除:src/pkg/go/types/predicates.go
からimport "go/ast"
が削除されました。これは、このファイルがast.Object
に直接依存しなくなったことを明確に示しています。
2. エラーハンドリングのバグ修正
src/pkg/go/types/check.go
のcheck
関数におけるpanic
/recover
ロジックが修正されました。
--- a/src/pkg/go/types/check.go
+++ b/src/pkg/go/types/check.go
@@ -379,10 +379,8 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
// handle panics
defer func() {
switch p := recover().(type) {
- case nil:
- // normal return - nothing to do
- case bailout:
- // early exit
+ case nil, bailout:
+ // normal return or early exit
err = check.firsterr
default:
// unexpected panic: don't crash clients
以前のコードでは、recover()
がnil
を返した場合(パニックが発生しなかった場合)と、bailout
型のパニック(早期終了を示す内部的なパニック)の場合で、err = check.firsterr
の代入が行われるかどうかが異なっていました。この修正により、nil
またはbailout
のいずれの場合でもerr = check.firsterr
が実行されるようになりました。これにより、型チェック中に発生した最初のエラーが、エラーハンドラの有無にかかわらず、常に適切に返されることが保証されます。これは、型チェック結果の一貫性を保つための重要なバグ修正です。
まとめ
これらの変更は、go/types
パッケージをgo/ast
パッケージからより独立させ、Goコンパイラの型チェック部分の設計を改善することを目的としています。Var
型の導入は、型システムが独自のセマンティックなオブジェクト表現を持つための基盤を築き、将来的なコンパイラの進化に貢献します。エラーハンドリングの修正は、システムの堅牢性と予測可能性を高めます。
コアとなるコードの変更箇所
src/pkg/go/types/types.go
(Var構造体の導入と主要な型定義の変更)
--- a/src/pkg/go/types/types.go
+++ b/src/pkg/go/types/types.go
@@ -120,19 +120,25 @@ type Pointer struct {
Base Type
}
+// A Variable represents a variable (including function parameters and results).
+type Var struct {
+ Name string
+ Type Type
+}
+
// A Result represents a (multi-value) function call result.
type Result struct {
implementsType
- Values []*ast.Object // Signature.Results of the function called
+ Values []*Var // 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 []*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
+ Recv *Var // nil if not a method
+ Params []*Var // (incoming) parameters from left to right; or nil
+ Results []*Var // (outgoing) results from left to right; or nil
+ IsVariadic bool // true if the last parameter's type is of the form ...T
}
src/pkg/go/types/errors.go
(writeParams関数のシグネチャ変更)
--- a/src/pkg/go/types/errors.go
+++ b/src/pkg/go/types/errors.go
@@ -197,7 +197,7 @@ func typeString(typ Type) string {
return buf.String()
}
-func writeParams(buf *bytes.Buffer, params []*ast.Object, isVariadic bool) {
+func writeParams(buf *bytes.Buffer, params []*Var, isVariadic bool) {
buf.WriteByte('(')
for i, par := range params {
if i > 0 {
@@ -210,7 +210,7 @@ func writeParams(buf *bytes.Buffer, params []*ast.Object, isVariadic bool) {
if isVariadic && i == len(params)-1 {
buf.WriteString("...")
}
- writeType(buf, par.Type.(Type))
+ writeType(buf, par.Type)
}
buf.WriteByte(')')
}
src/pkg/go/types/expr.go
(collectParams関数のシグネチャ変更と内部ロジック)
--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -17,12 +17,13 @@ import (
// - simplify invalid handling: maybe just use Typ[Invalid] as marker, get rid of invalid Mode for values?
// - rethink error handling: should all callers check if x.mode == valid after making a call?
// - at the moment, iota is passed around almost everywhere - in many places we know it cannot be used
+// - use "" or "_" consistently for anonymous identifiers? (e.g. reeceivers that have no name)
// TODO(gri) API issues
// - clients need access to builtins type information
// - API tests are missing (e.g., identifiers should be handled as expressions in callbacks)
-func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*ast.Object, isVariadic bool) {
+func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) {
if list == nil {
return
}
@@ -46,26 +47,22 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
for _, name := range field.Names {
obj := name.Obj
obj.Type = typ
- params = append(params, obj)
last = obj
+ params = append(params, &Var{obj.Name, typ})
}
} else {
// anonymous parameter
obj := ast.NewObj(ast.Var, "")
obj.Type = typ
- params = append(params, obj)
last = obj
+ params = append(params, &Var{obj.Name, typ})
}
}
// For a variadic function, change the last parameter's object type
// from T to []T (this is the type used inside the function), but
- // keep a copy of the object with the original type T in the params
- // list (this is the externally visible type).
+ // keep the params list unchanged (this is the externally visible type).
if isVariadic {
- // if isVariadic is set, last must exist and len(params) > 0
- copy := *last
last.Type = &Slice{Elt: last.Type.(Type)}
- params[len(params)-1] = ©
}
return
}
@@ -576,7 +573,7 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota
//
func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, passSlice bool) {
// determine parameter
- var par *ast.Object
+ var par *Var
n := len(sig.Params)
if i < n {
par = sig.Params[i]
@@ -922,11 +919,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
// argument of the method expression's function type
// TODO(gri) at the moment, method sets don't correctly track
// pointer vs non-pointer receivers => typechecker is too lenient
- arg := ast.NewObj(ast.Var, "")
- arg.Type = x.typ
x.mode = value
x.typ = &Signature{
- Params: append([]*ast.Object{arg}, sig.Params...),\
+ Params: append([]*Var{{"", x.typ}}, sig.Params...),\
Results: sig.Results,\
IsVariadic: sig.IsVariadic,\
}
src/pkg/go/types/check.go
(エラーハンドリングのバグ修正)
--- a/src/pkg/go/types/check.go
+++ b/src/pkg/go/types/check.go
@@ -379,10 +379,8 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
// handle panics
defer func() {
switch p := recover().(type) {
- case nil:
- // normal return - nothing to do
- case bailout:
- // early exit
+ case nil, bailout:
+ // normal return or early exit
err = check.firsterr
default:
// unexpected panic: don't crash clients
コアとなるコードの解説
src/pkg/go/types/types.go
の変更
-
Var
構造体の追加:type Var struct { Name string; Type Type }
が追加されました。これは、Go言語の変数、関数パラメータ、関数結果など、名前と型を持つすべてのエンティティを抽象的に表現するための新しい型です。これにより、go/types
パッケージはgo/ast
パッケージのast.Object
に直接依存することなく、独自のセマンティックなオブジェクト表現を持つことができます。 -
Result
およびSignature
構造体の変更:Result
構造体のValues
フィールドと、Signature
構造体のRecv
、Params
、Results
フィールドの型が、それぞれ[]*ast.Object
や*ast.Object
から、新しく定義された[]*Var
や*Var
に変更されました。これは、go/types
パッケージがASTの具体的な表現から独立した型システムを構築するための中心的な変更です。これにより、型チェックのロジックがASTの構造に縛られず、より柔軟で再利用可能なものになります。
src/pkg/go/types/errors.go
の変更
writeParams
関数のシグネチャ変更:func writeParams(buf *bytes.Buffer, params []*ast.Object, isVariadic bool)
がfunc writeParams(buf *bytes.Buffer, params []*Var, isVariadic bool)
に変更されました。これは、関数のパラメータリストを文字列としてフォーマットする際に、もはやast.Object
ではなく、新しいVar
型を使用することを示しています。また、内部でpar.Type.(Type)
として型アサーションを行っていた箇所がpar.Type
に直接アクセスするように変更されており、Var
構造体が直接Type
フィールドを持つため、よりシンプルになっています。
src/pkg/go/types/expr.go
の変更
-
collectParams
関数のシグネチャ変更と内部ロジック:func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*ast.Object, isVariadic bool)
がfunc (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool)
に変更されました。この関数は、ASTのフィールドリストから関数のパラメータを収集する役割を担っています。 変更前は、ast.NewObj
でast.Object
を作成し、それをparams
スライスに追加していました。変更後は、params = append(params, &Var{obj.Name, typ})
のように、直接Var
構造体のポインタを生成して追加しています。これにより、パラメータの収集プロセスがast.Object
から独立しました。 特に、可変長引数(variadic function)の処理において、以前はast.Object
のコピーを作成して元の型を保持していましたが、新しいVar
型ではparams
リストをそのまま保持し、last.Type
をSlice
型に変更するだけで済むようになりました。これは、Var
が名前と型を直接持つため、より直感的でシンプルな表現が可能になったことを示しています。 -
argument
およびrawExpr
関数の変更: これらの関数も、引数やレシーバの型を扱う際にast.Object
を生成する代わりに、直接Var
構造体を使用するように変更されています。例えば、メソッド式の引数を表現する際に、ast.NewObj(ast.Var, "")
を使って匿名ast.Object
を作成していた箇所が、&Var{"", x.typ}
のように匿名Var
構造体を直接生成する形に変わっています。
src/pkg/go/types/check.go
のエラーハンドリング修正
defer
ブロック内のrecover
ロジックの変更:check
関数のdefer
ブロック内で、recover()
が返す値に応じた処理が変更されました。 以前は、case nil:
(パニックなし)とcase bailout:
(早期終了パニック)が別々に扱われ、err = check.firsterr
の代入がbailout
の場合にのみ行われるようなロジックでした。 修正後は、case nil, bailout:
とまとめて処理されるようになり、パニックがなかった場合でも、早期終了のパニックの場合でも、常にerr = check.firsterr
が実行されるようになりました。これにより、型チェック中に捕捉された最初のエラーが、どのような終了パスを辿ったとしても、一貫して呼び出し元に返されることが保証されます。これは、型チェックの信頼性と予測可能性を高めるための重要なバグ修正です。
これらの変更は、go/types
パッケージがより自己完結的で、go/ast
パッケージの具体的な実装詳細から独立した、堅牢な型システムへと進化していることを示しています。
関連リンク
- Go Gerrit Change-Id:
7024043
- Go Gerrit Code Review: https://golang.org/cl/7024043
参考にした情報源リンク
- Go言語の公式ドキュメント(
go/ast
およびgo/types
パッケージに関する一般的な情報) - Go言語のソースコード(変更点の詳細な分析)