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

[インデックス 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パッケージの基礎となります。

変更の背景には、以下の課題があったと考えられます。

  1. 型チェックの効率性: 大規模なコードベースや特定のシナリオにおいて、型チェック処理全体を完了する前に、クライアントが特定の条件(例えば、最初のエラーが見つかった場合や、特定の型情報が解決された場合)で処理を中断したいというニーズがあったと考えられます。従来のAPIでは、型チェックが完了するまで待つ必要があり、効率的ではありませんでした。
  2. _(ブランク識別子)の扱い: Go言語では、_は値を破棄するために使用されるブランク識別子です。しかし、型チェックの過程で、この_が適切に扱われない場合、予期せぬ挙動や不正確な型情報につながる可能性がありました。特に、宣言における_の扱いは、型チェッカーの正確性に直結します。
  3. コードの複雑性: 実験的なパッケージであるため、初期の実装には改善の余地があり、コードの可読性や保守性を向上させるためのクリーンアップが必要でした。

これらの課題に対処するため、Check関数のAPIをより柔軟にし、型チェックロジックを改善することで、型チェッカーの堅牢性と実用性を高めることが目的でした。

前提知識の解説

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

  1. go/ast (Abstract Syntax Tree): Go言語のソースコードを解析して生成される抽象構文木(AST)を表現するためのパッケージです。コンパイラやツールがGoのコードをプログラム的に操作するために使用します。ASTは、プログラムの構造を木構造で表現し、各ノードが宣言、式、文などの言語要素に対応します。

    • ast.Package: 複数のGoソースファイルから構成されるパッケージ全体を表します。
    • ast.Decl: 宣言(変数宣言、関数宣言、型宣言など)を表すインターフェースです。
    • ast.GenDecl: var, const, type などのキーワードで始まる一般的な宣言を表します。
    • ast.ValueSpec: varconst 宣言における個々の変数や定数の仕様を表します。
    • ast.FuncDecl: 関数宣言を表します。
    • ast.Ident: 識別子(変数名、関数名など)を表します。
    • ast.Object: 識別子によって参照されるオブジェクト(変数、関数、型など)を表します。型チェックの過程で、識別子とそれが参照するオブジェクトが関連付けられます。
    • ast.Expr: 式を表すインターフェースです。
  2. go/token (Token Package): Goソースコードの字句解析(トークン化)に関連する定数や型を定義するパッケージです。

    • token.FileSet: 複数のソースファイルにわたる位置情報(行番号、列番号など)を管理します。エラーメッセージの正確な位置特定に不可欠です。
    • token.Pos: ソースコード内の位置を表す型です。
  3. 型チェッカー (Type Checker): プログラムの型が言語の規則に従っているかを検証するコンパイラのフェーズです。Go言語のような静的型付け言語では、型チェックはコンパイル時に行われ、型エラーはプログラムの実行前に検出されます。型チェッカーは、ASTを走査し、各式の型を推論し、型の一貫性を検証します。

  4. iota: Go言語の定数宣言で使用される特殊な識別子です。const ブロック内で使用され、連続する定数に自動的にインクリメントされる整数値を割り当てます。各 const 宣言の行で iota の値は1ずつ増加します。

  5. ブランク識別子 (_): Go言語では、_は値を破棄するために使用される特殊な識別子です。例えば、関数の複数の戻り値のうち一部だけが必要な場合や、インポートしたパッケージを直接使用しないが副作用のためにインポートする必要がある場合などに使用されます。型チェッカーは、_で宣言された変数や定数に対しても、その型を正しく推論し、必要に応じて型チェックを行う必要があります。

  6. パニックとリカバリー (Panic and Recover): Go言語のエラーハンドリングメカニズムの一つです。panic はプログラムの異常終了を引き起こし、通常の実行フローを中断します。recoverdefer 関数内で panic から回復し、プログラムの実行を継続するために使用されます。このコミットでは、型チェックの早期終了メカニズムとして panicrecover が利用されています。

  7. build.Import: Goのビルドシステムがパッケージをインポートする際に使用する機能です。このコミットでは、gcimporter(Goコンパイラのインポート機能)がパッケージのオブジェクトファイルを見つける際に、ソースファイルが存在しなくてもバイナリを許可するオプション (build.AllowBinary) を追加しています。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. 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 というカスタムのパニック型が導入されました。errhnil の場合、最初のエラーが見つかると bailout パニックが発生し、Check 関数は defer された recover によってこのパニックを捕捉し、型チェックを早期に終了します。これにより、クライアントは最初のエラーだけを報告したい場合に、無駄な型チェック処理をスキップできます。
  2. 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 を使用しない連続定数宣言で、前の定数の初期化式が次の定数に適用される場合)を管理するようになりました。
  3. 定数と変数の型チェックロジックの改善 (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) を呼び出すことで、_ 宣言も型チェックの対象となりました。
  4. メソッドと定数初期化式の関連付けロジックの追加:

    • assocInitvals 関数が追加され、const 宣言で明示的な初期化式がない場合に、前の定数から初期化式を「継承」するロジックが実装されました。これは iota を使用しない連続定数宣言で重要です。
    • assocMethod 関数が追加され、メソッド宣言を対応するレシーバの基本型に関連付けるロジックが実装されました。これにより、型チェッカーがメソッドを正しく解決できるようになります。
    • assocInitvalsOrMethod 関数が追加され、宣言の種類に応じて assocInitvals または assocMethod を呼び出すようになりました。
  5. 型チェックループのクリーンアップとイテレーションの改善:

    • check 関数のメインループが check.iterate 関数に抽象化されました。check.iterate は、パッケージ内のすべての宣言をソートされたファイル名順に走査し、指定されたコールバック関数 (f) を各宣言に対して呼び出します。これにより、型チェックの順序が再現可能になり、テストの安定性が向上しました。
    • 以前は scanner.ErrorList を使用してエラーを収集していましたが、新しい errh コールバックにより、エラー報告がより柔軟になりました。
  6. 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.Importbuild.AllowBinary フラグが追加されました。

特に、src/pkg/exp/types/staging/check.goCheck 関数と checker 構造体、および関連するヘルパー関数の変更が最も重要です。

コアとなるコードの解説

src/pkg/exp/types/staging/check.go の変更

  1. 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 が削除され、errhmapf という関数型のフィールドが追加されました。これにより、エラー報告と型マッピングの動作を外部から注入できるようになり、APIの柔軟性が向上しました。firsterr は、早期終了のために最初のエラーを記録します。initexprs は、定数宣言における初期化式の継承を処理するためのマップです。

  2. 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 関数のシグネチャが変更され、errhf コールバックを受け取るようになりました。defer ブロック内で recover を使用して bailout パニックを捕捉し、型チェックの早期終了を実現しています。 型チェックのプロセスは、まず assocInitvalsOrMethod を呼び出して定数の初期化式とメソッドを関連付け、次に decl を呼び出してすべての宣言を型チェックするという2段階になりました。

  3. 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 を適切に処理するように変更されました。

  4. assocInitvalsOrMethoditerate:

    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.errhnil であれば、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言語のコミット履歴 (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の初期の設計ドキュメントやメーリングリストのアーカイブなど)