[インデックス 14082] ファイルの概要
このコミットは、Go言語の実験的な型チェッカーパッケージ exp/types/staging
におけるAPIの柔軟性向上と内部のクリーンアップを目的としています。特に、型チェックの途中でクライアントが処理を中断できるようなメカニズムの導入と、型チェックロジックの改善が含まれています。
コミット
commit 0d9474206f2822f8a892a42bc36b2809a6be3184
Author: Robert Griesemer <gri@golang.org>
Date: Sun Oct 7 18:02:19 2012 -0700
exp/types/staging: more flexible API, cleanups
- Changed Check signature to take function parameters for
more flexibility: Now a client can interrupt type checking
early (via panic in one the upcalls) once the desired
type information or number of errors is reached. Default
use is still simple.
- Cleaned up main typechecking loops. Now does not neglect
_ declarations anymore.
- Various other cleanups.
R=golang-dev, r, rsc
CC=golang-dev
https://golang.org/cl/6612049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d9474206f2822f8a892a42bc36b2809a6be3184
元コミット内容
exp/types/staging: more flexible API, cleanups
Check
関数のシグネチャを関数パラメータを受け取るように変更し、より柔軟性を持たせた。これにより、クライアントは必要な型情報やエラー数に達した場合、アップコール(コールバック関数)内でパニックを発生させることで、型チェックを早期に中断できるようになった。デフォルトの使用方法は引き続きシンプルである。- 主要な型チェックループをクリーンアップした。これにより、
_
(ブランク識別子)の宣言が無視されなくなった。 - その他の様々なクリーンアップ。
変更の背景
このコミットが行われた2012年当時、Go言語の型システムはまだ進化の途中にあり、exp/types
パッケージはGoコンパイラ(gc
)とは独立した、より厳密で正確な型チェッカーを実験的に開発するためのステージングエリアでした。このパッケージは、後のgo/types
パッケージの基礎となります。
変更の背景には、以下の課題があったと考えられます。
- 型チェックの効率性: 大規模なコードベースや特定のシナリオにおいて、型チェック処理全体を完了する前に、クライアントが特定の条件(例えば、最初のエラーが見つかった場合や、特定の型情報が解決された場合)で処理を中断したいというニーズがあったと考えられます。従来のAPIでは、型チェックが完了するまで待つ必要があり、効率的ではありませんでした。
_
(ブランク識別子)の扱い: Go言語では、_
は値を破棄するために使用されるブランク識別子です。しかし、型チェックの過程で、この_
が適切に扱われない場合、予期せぬ挙動や不正確な型情報につながる可能性がありました。特に、宣言における_
の扱いは、型チェッカーの正確性に直結します。- コードの複雑性: 実験的なパッケージであるため、初期の実装には改善の余地があり、コードの可読性や保守性を向上させるためのクリーンアップが必要でした。
これらの課題に対処するため、Check
関数のAPIをより柔軟にし、型チェックロジックを改善することで、型チェッカーの堅牢性と実用性を高めることが目的でした。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラ関連の概念を理解しておく必要があります。
-
go/ast
(Abstract Syntax Tree): Go言語のソースコードを解析して生成される抽象構文木(AST)を表現するためのパッケージです。コンパイラやツールがGoのコードをプログラム的に操作するために使用します。ASTは、プログラムの構造を木構造で表現し、各ノードが宣言、式、文などの言語要素に対応します。ast.Package
: 複数のGoソースファイルから構成されるパッケージ全体を表します。ast.Decl
: 宣言(変数宣言、関数宣言、型宣言など)を表すインターフェースです。ast.GenDecl
:var
,const
,type
などのキーワードで始まる一般的な宣言を表します。ast.ValueSpec
:var
やconst
宣言における個々の変数や定数の仕様を表します。ast.FuncDecl
: 関数宣言を表します。ast.Ident
: 識別子(変数名、関数名など)を表します。ast.Object
: 識別子によって参照されるオブジェクト(変数、関数、型など)を表します。型チェックの過程で、識別子とそれが参照するオブジェクトが関連付けられます。ast.Expr
: 式を表すインターフェースです。
-
go/token
(Token Package): Goソースコードの字句解析(トークン化)に関連する定数や型を定義するパッケージです。token.FileSet
: 複数のソースファイルにわたる位置情報(行番号、列番号など)を管理します。エラーメッセージの正確な位置特定に不可欠です。token.Pos
: ソースコード内の位置を表す型です。
-
型チェッカー (Type Checker): プログラムの型が言語の規則に従っているかを検証するコンパイラのフェーズです。Go言語のような静的型付け言語では、型チェックはコンパイル時に行われ、型エラーはプログラムの実行前に検出されます。型チェッカーは、ASTを走査し、各式の型を推論し、型の一貫性を検証します。
-
iota
: Go言語の定数宣言で使用される特殊な識別子です。const
ブロック内で使用され、連続する定数に自動的にインクリメントされる整数値を割り当てます。各const
宣言の行でiota
の値は1ずつ増加します。 -
ブランク識別子 (
_
): Go言語では、_
は値を破棄するために使用される特殊な識別子です。例えば、関数の複数の戻り値のうち一部だけが必要な場合や、インポートしたパッケージを直接使用しないが副作用のためにインポートする必要がある場合などに使用されます。型チェッカーは、_
で宣言された変数や定数に対しても、その型を正しく推論し、必要に応じて型チェックを行う必要があります。 -
パニックとリカバリー (Panic and Recover): Go言語のエラーハンドリングメカニズムの一つです。
panic
はプログラムの異常終了を引き起こし、通常の実行フローを中断します。recover
はdefer
関数内でpanic
から回復し、プログラムの実行を継続するために使用されます。このコミットでは、型チェックの早期終了メカニズムとしてpanic
とrecover
が利用されています。 -
build.Import
: Goのビルドシステムがパッケージをインポートする際に使用する機能です。このコミットでは、gcimporter
(Goコンパイラのインポート機能)がパッケージのオブジェクトファイルを見つける際に、ソースファイルが存在しなくてもバイナリを許可するオプション (build.AllowBinary
) を追加しています。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
Check
関数のシグネチャ変更と早期終了メカニズム:- 変更前:
func Check(fset *token.FileSet, pkg *ast.Package, types map[ast.Expr]Type) error
- 変更後:
func Check(fset *token.FileSet, pkg *ast.Package, errh func(token.Pos, string), f func(ast.Expr, Type)) error
- 新しいシグネチャでは、エラーハンドラ関数
errh
と式-型マッピング関数f
が追加されました。 errh
: 型チェック中にエラーが発生した場合に呼び出されるコールバック関数です。これにより、クライアントはエラーをカスタムで処理したり、特定のエラー条件で型チェックを中断したりできます。f
: 各AST式とその対応する型が解決されたときに呼び出されるコールバック関数です。これにより、クライアントは型情報をリアルタイムで取得できます。- 早期終了メカニズムとして、
bailout
というカスタムのパニック型が導入されました。errh
がnil
の場合、最初のエラーが見つかるとbailout
パニックが発生し、Check
関数はdefer
されたrecover
によってこのパニックを捕捉し、型チェックを早期に終了します。これにより、クライアントは最初のエラーだけを報告したい場合に、無駄な型チェック処理をスキップできます。
- 変更前:
-
checker
構造体の変更:errors scanner.ErrorList
が削除され、errh func(token.Pos, string)
とmapf func(ast.Expr, Type)
が追加されました。これにより、エラー報告と型マッピングのロジックが外部に委譲され、checker
の責務が明確化されました。types map[ast.Expr]Type
が削除されました。型マッピングはmapf
コールバックを通じて行われるようになりました。firsterr error
が追加され、早期終了のために最初のエラーを記録するようになりました。filenames []string
が追加され、パッケージファイルのイテレーション順序を再現可能にするために、ファイル名をソートして保持するようになりました。これは主にテストの再現性のために重要です。initexprs map[*ast.ValueSpec][]ast.Expr
が追加され、定数宣言における「継承された」初期化式(iota
を使用しない連続定数宣言で、前の定数の初期化式が次の定数に適用される場合)を管理するようになりました。
-
定数と変数の型チェックロジックの改善 (
obj
->ident
,decl
->valueSpec
):checker.obj
関数がchecker.ident
にリネームされ、識別子の型チェックに特化しました。checker.decl
関数がchecker.valueSpec
にリネームされ、ast.ValueSpec
の型チェックに特化しました。- 定数 (
ast.Con
) と変数 (ast.Var
) の型チェックロジックが統合され、obj.Data
フィールドがiota
の値や評価中の状態を示すために使用されるようになりました。 - 特に、
_
(ブランク識別子)の宣言が適切に型チェックされるように修正されました。以前は_
宣言が無視される可能性がありましたが、check.decl
(現在のcheck.valueSpec
) がname.Name == "_"
の場合でもcheck.ident(name, false)
を呼び出すことで、_
宣言も型チェックの対象となりました。
-
メソッドと定数初期化式の関連付けロジックの追加:
assocInitvals
関数が追加され、const
宣言で明示的な初期化式がない場合に、前の定数から初期化式を「継承」するロジックが実装されました。これはiota
を使用しない連続定数宣言で重要です。assocMethod
関数が追加され、メソッド宣言を対応するレシーバの基本型に関連付けるロジックが実装されました。これにより、型チェッカーがメソッドを正しく解決できるようになります。assocInitvalsOrMethod
関数が追加され、宣言の種類に応じてassocInitvals
またはassocMethod
を呼び出すようになりました。
-
型チェックループのクリーンアップとイテレーションの改善:
check
関数のメインループがcheck.iterate
関数に抽象化されました。check.iterate
は、パッケージ内のすべての宣言をソートされたファイル名順に走査し、指定されたコールバック関数 (f
) を各宣言に対して呼び出します。これにより、型チェックの順序が再現可能になり、テストの安定性が向上しました。- 以前は
scanner.ErrorList
を使用してエラーを収集していましたが、新しいerrh
コールバックにより、エラー報告がより柔軟になりました。
-
gcimporter
の変更:src/pkg/exp/types/staging/gcimporter.go
において、build.Import
関数にbuild.AllowBinary
フラグが追加されました。これにより、パッケージのオブジェクトファイルを探す際に、ソースファイルが存在しなくてもバイナリファイル(コンパイル済みのパッケージ)を許可するようになりました。これは、依存関係の解決をより堅牢にするための変更です。
コアとなるコードの変更箇所
主要な変更は以下のファイルに集中しています。
src/pkg/exp/types/staging/check.go
: 型チェックの主要ロジック、checker
構造体、Check
関数のシグネチャ、定数/変数の型チェック、メソッド関連付け、イテレーションロジックが変更されました。src/pkg/exp/types/staging/errors.go
: エラー報告メカニズムが変更され、scanner.ErrorList
の代わりにerrh
コールバックとfirsterr
が使用されるようになりました。早期終了のためのbailout
パニックも導入されました。src/pkg/exp/types/staging/types.go
:Check
関数のシグネチャが変更され、新しいエラーハンドラと型マッピングのコールバックを受け取るようになりました。src/pkg/exp/types/staging/gcimporter.go
:build.Import
にbuild.AllowBinary
フラグが追加されました。
特に、src/pkg/exp/types/staging/check.go
の Check
関数と checker
構造体、および関連するヘルパー関数の変更が最も重要です。
コアとなるコードの解説
src/pkg/exp/types/staging/check.go
の変更
-
checker
構造体:type checker struct { fset *token.FileSet pkg *ast.Package errh func(token.Pos, string) // New: Error handler callback mapf func(ast.Expr, Type) // New: Expression-to-type mapping callback // lazily initialized firsterr error filenames []string // sorted list of package file names for reproducible iteration order initexprs map[*ast.ValueSpec][]ast.Expr // "inherited" initialization expressions for constant declarations }
errors scanner.ErrorList
が削除され、errh
とmapf
という関数型のフィールドが追加されました。これにより、エラー報告と型マッピングの動作を外部から注入できるようになり、APIの柔軟性が向上しました。firsterr
は、早期終了のために最初のエラーを記録します。initexprs
は、定数宣言における初期化式の継承を処理するためのマップです。 -
Check
関数のシグネチャとロジック:func Check(fset *token.FileSet, pkg *ast.Package, errh func(token.Pos, string), f func(ast.Expr, Type)) (err error) { var check checker check.fset = fset check.pkg = pkg check.errh = errh check.mapf = f check.initexprs = make(map[*ast.ValueSpec][]ast.Expr) defer func() { if p := recover(); p != nil { _ = p.(bailout) // re-panic if not a bailout } err = check.firsterr }() // determine missing constant initialization expressions // and associate methods with types check.iterate((*checker).assocInitvalsOrMethod) // typecheck all declarations check.iterate((*checker).decl) return }
Check
関数のシグネチャが変更され、errh
とf
コールバックを受け取るようになりました。defer
ブロック内でrecover
を使用してbailout
パニックを捕捉し、型チェックの早期終了を実現しています。 型チェックのプロセスは、まずassocInitvalsOrMethod
を呼び出して定数の初期化式とメソッドを関連付け、次にdecl
を呼び出してすべての宣言を型チェックするという2段階になりました。 -
ident
関数とvalueSpec
関数: 以前のobj
関数がident
関数に、decl
関数がvalueSpec
関数にリネームされ、それぞれの責務がより明確になりました。 特にident
関数内で、定数 (ast.Con
) と変数 (ast.Var
) の型チェックロジックが統合され、iota
の値の処理や、初期化式の決定ロジックが改善されました。func (check *checker) ident(name *ast.Ident, cycleOk bool) { obj := name.Obj // ... switch obj.Kind { case ast.Con, ast.Var: // Both constants and variables are handled here // ... logic for handling iota and initialization expressions ... check.valueSpec(spec.Pos(), obj, spec.Names, spec.Type, values, iota) // ... other cases ... } } func (check *checker) valueSpec(pos token.Pos, obj *ast.Object, lhs []*ast.Ident, typ ast.Expr, rhs []ast.Expr, iota int) { // ... // This function now correctly handles the type checking of value specifications, // including those with blank identifiers ('_'). // The previous 'specValues' helper is removed, and 'initexprs' map is used instead. }
valueSpec
関数は、_
宣言を含むすべてのValueSpec
を適切に処理するように変更されました。 -
assocInitvalsOrMethod
とiterate
:func (check *checker) assocInitvals(decl *ast.GenDecl) { // Logic to associate "inherited" initialization expressions for constants // using check.initexprs map. } func (check *checker) assocMethod(meth *ast.FuncDecl) { // Logic to associate method declarations with their receiver base types. } func (check *checker) assocInitvalsOrMethod(decl ast.Decl) { switch d := decl.(type) { case *ast.GenDecl: if d.Tok == token.CONST { check.assocInitvals(d) } case *ast.FuncDecl: if d.Recv != nil { check.assocMethod(d) } } } func (check *checker) iterate(f func(*checker, ast.Decl)) { // Iterates through all declarations in the package files in a reproducible order. // Calls the provided function 'f' for each declaration. }
これらの関数は、型チェックの前の準備段階として、定数の初期化式の継承とメソッドの関連付けを行います。
iterate
関数は、パッケージ内のすべての宣言を安定した順序で処理するための汎用的なメカニズムを提供します。
src/pkg/exp/types/staging/errors.go
の変更
type bailout struct{} // New: Custom panic type for early termination
func (check *checker) errorf(pos token.Pos, format string, args ...interface{}) {
msg := check.formatMsg(format, args)
if check.firsterr == nil {
check.firsterr = fmt.Errorf("%s: %s", check.fset.Position(pos), msg)
}
if check.errh == nil {
panic(bailout{}) // New: Panic for early termination if no error handler is provided
}
check.errh(pos, msg) // New: Call the provided error handler
}
errorf
関数が大幅に変更されました。scanner.ErrorList
への追加ではなく、check.errh
コールバックを呼び出すようになりました。もし check.errh
が nil
であれば、bailout{}
パニックを発生させ、型チェックを早期に終了させます。これにより、クライアントは最初のエラーで処理を中断するか、すべてのエラーを収集するかを選択できるようになりました。
src/pkg/exp/types/staging/gcimporter.go
の変更
func FindPkg(path, srcDir string) (filename, id string) {
switch {
default:
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
// Don't require the source files to be present.
bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) // Added build.AllowBinary
if bp.PkgObj == "" {
return
}
// ...
}
}
build.Import
関数呼び出しに build.AllowBinary
フラグが追加されました。これは、パッケージのオブジェクトファイルを探す際に、ソースファイルが存在しなくてもバイナリファイル(コンパイル済みのパッケージ)を許可することを意味します。これにより、依存関係の解決がより柔軟になります。
これらの変更により、exp/types/staging
パッケージはより堅牢で、柔軟性があり、効率的な型チェッカーへと進化しました。特に、APIの柔軟性向上は、このパッケージが将来的に go/types
としてGoエコシステムに統合される上で重要なステップでした。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語のASTパッケージ (
go/ast
): https://pkg.go.dev/go/ast - Go言語のTokenパッケージ (
go/token
): https://pkg.go.dev/go/token - Go言語の型パッケージ (
go/types
): https://pkg.go.dev/go/types (このコミットのexp/types/staging
が発展したもの) - Go言語の
iota
について: https://go.dev/blog/constants - Go言語のブランク識別子 (
_
) について: https://go.dev/doc/effective_go#blank - Go言語のパニックとリカバリー: https://go.dev/blog/defer-panic-and-recover
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- このコミットのGerritレビューページ: https://golang.org/cl/6612049
- Go言語の型システムに関する議論やドキュメント (当時の情報源を特定するのは困難ですが、Goの公式ブログやデザインドキュメントが参考になります)
- Go言語のコンパイラ設計に関する資料 (例: Russ CoxによるGoコンパイラの進化に関する講演など)
- Go言語の
build
パッケージ: https://pkg.go.dev/go/build - Go言語の
exp
パッケージの歴史と目的に関する情報 (Goの初期の設計ドキュメントやメーリングリストのアーカイブなど)