[インデックス 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の初期の設計ドキュメントやメーリングリストのアーカイブなど)