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

[インデックス 14748] ファイルの概要

このコミットは、Go言語の実験的な型チェッカーパッケージ exp/types における大幅な改善とリファクタリングを導入しています。主な変更点は、型チェックAPIの構成可能性を高め、関数やメソッドのボディ全体を型チェックする機能を追加し、テストハーネスを刷新したことです。これにより、型チェッカーの堅牢性と柔軟性が向上しています。

コミット

commit 888111e081fc8eda605d5c2706f14d0f9473fae2
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Dec 26 12:48:26 2012 -0800

    exp/types: configurable types.Check API
    
    - added Context type for configuration of type checker
    - type check all function and method bodies
    - (partial) fixes to shift hinting (still not complete)
    - revamped test harness - does not rely on specific position
      representation anymore, just a standard (compiler) error
      message
    - lots of bug fixes
    
    R=adonovan, rsc
    CC=golang-dev
    https://golang.org/cl/6948071

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/888111e081fc8eda605d5c2706f14d0f9473fae2

元コミット内容

このコミットは、Go言語の実験的な型チェッカーパッケージ exp/types に以下の変更を加えています。

  • 型チェッカーの設定のための Context 型を追加。
  • すべての関数およびメソッドのボディを型チェックするように変更。
  • シフトヒンティングの部分的な修正(まだ未完了)。
  • テストハーネスの刷新。特定の位置表現に依存せず、標準的な(コンパイラ)エラーメッセージを使用するように変更。
  • 多数のバグ修正。

変更の背景

このコミットが行われた2012年当時、exp/types パッケージはGo言語の公式な型チェッカーとして開発が進められている段階でした。初期の型チェッカーは、その機能や設定の柔軟性に限界がありました。特に、関数やメソッドのボディ内の詳細な型チェックが不十分であったり、エラー報告の形式がテストに適していなかったりする問題がありました。

このコミットの背景には、以下のような目的があったと考えられます。

  1. 型チェッカーの堅牢性向上: 言語仕様の複雑な側面(特にシフト演算や組み込み関数の振る舞い)を正確に処理し、より多くのGoコードを正しく型チェックできるようにすること。
  2. APIの柔軟性: 型チェッカーをGoコンパイラ以外のツール(IDE、リンター、コード分析ツールなど)から利用しやすくするため、設定可能なAPIを提供すること。Context 型の導入は、この目的を達成するための重要なステップです。
  3. テスト容易性の改善: 型チェッカーのテストをより効率的かつ信頼性の高いものにするため、エラー位置の表現に依存しない汎用的なテストハーネスが必要とされていました。
  4. バグの修正: 開発中のパッケージであるため、発見された多数のバグを修正し、安定性を高めること。

これらの変更は、最終的にGo言語の公式な型チェッカーである go/types パッケージへと発展していく過程における重要なマイルストーンとなります。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と標準ライブラリの知識が必要です。

  • go/ast パッケージ: Goのソースコードの抽象構文木(AST: Abstract Syntax Tree)を表現するためのデータ構造を提供します。コンパイラやコード分析ツールがGoコードを解析する際に使用します。
  • go/token パッケージ: Goのソースコードにおけるトークン(識別子、キーワード、演算子など)と、それらのソースコード上の位置(ファイル、行、列)を扱うための型と関数を提供します。
  • go/scanner パッケージ: Goのソースコードをトークンに分割(字句解析)するための機能を提供します。
  • exp/types パッケージ(後の go/types: Go言語の型システムをモデル化し、Goプログラムの型チェックを行うためのパッケージです。このパッケージは、ASTを走査し、各式の型を決定し、型エラーを検出します。
  • big.Int および big.Rat: math/big パッケージで提供される、任意精度整数および任意精度有理数を扱うための型です。Goのコンパイル時定数(特に大きな数値や浮動小数点数)の表現に用いられます。
  • コンパイル時定数: Go言語では、コンパイル時に値が決定される定数があります。これらの定数は、型付けされていない(untyped)状態で存在し、必要に応じて特定の型に変換されます。
  • シフト演算: << (左シフト) および >> (右シフト) 演算子。Go言語では、シフト演算の右オペランドは符号なし整数型であるか、符号なし整数型に変換可能な型なし定数である必要があります。
  • 組み込み関数: len, cap, new, make, copy, append, panic, recover, print, println など、Go言語に組み込まれている特別な関数です。これらは通常の関数とは異なり、コンパイラによって特別に扱われます。
  • token.Pos: go/token パッケージで定義される、ソースコード上の位置を表す型です。通常、ファイルセット内のオフセットとして表現されます。
  • ast.Object: go/ast パッケージで定義される、Goプログラム内の名前付きエンティティ(変数、関数、型など)を表す抽象的なオブジェクトです。型チェッカーは、これらのオブジェクトに型情報を関連付けます。

技術的詳細

このコミットの技術的詳細は多岐にわたりますが、主要な変更点は以下の通りです。

  1. Context 型の導入 (src/pkg/exp/types/api.go の新規追加):

    • 型チェッカーの振る舞いを設定するための types.Context 構造体が導入されました。
    • IntSizePtrSize フィールドにより、int およびポインタのサイズをコンテキストとして指定できるようになりました。これは、異なるアーキテクチャ(32ビット/64ビット)での型チェックの正確性を保証するために重要です。
    • Error フィールドは、型チェック中にエラーが発見された際に呼び出されるコールバック関数です。これにより、エラー処理のロジックを外部から注入できるようになり、テストハーネスやIDE統合が容易になります。
    • Expr フィールドは、各式が型チェックされた後に呼び出されるコールバック関数です。これにより、式の型や定数値にアクセスできるようになり、コード分析ツールなどが利用できます。
    • Import フィールドは、パッケージのインポート処理をカスタマイズするための ast.Importer インターフェースを提供します。デフォルトでは GcImport が使用されます。
    • Default というグローバルな Context インスタンスが提供され、デフォルトの設定(IntSize: 8, PtrSize: 8)を持ちます。
    • Context.Check メソッドが導入され、特定のコンテキストで型チェックを実行できるようになりました。これにより、従来のグローバルな Check 関数が Default.Check のショートハンドとして機能するようになります。
  2. 関数・メソッドボディの型チェックの強化 (src/pkg/exp/types/check.go):

    • 以前はパッケージレベルの宣言のみが型チェックの対象でしたが、このコミットにより、すべての関数およびメソッドのボディが型チェックされるようになりました。
    • checker 構造体に funclist (型チェックが必要な関数/メソッドのリスト) と funcsig (現在型チェック中の関数のシグネチャ) が追加されました。
    • later メソッドが導入され、パッケージレベルの宣言が型チェックされた後に、関数ボディの型チェックをキューに入れるメカニズムが提供されました。これにより、宣言とボディの型チェックの順序が適切に管理されます。
    • init 関数(Goの初期化関数)の型チェックが改善され、引数や戻り値がないことが強制されるようになりました。
  3. シフトヒンティングの修正 (src/pkg/exp/types/expr.go):

    • シフト演算の左オペランドが型なし定数である場合の型推論ロジックが修正されました。
    • 仕様に従い、非定数シフト式の場合、左オペランドの型なし定数は、シフト式がその左オペランド単独に置き換えられた場合の型に変換されるようになりました。コンテキストから型が決定できない場合は int になります。
  4. テストハーネスの刷新 (src/pkg/exp/types/check_test.go):

    • エラーメッセージの解析方法が変更され、特定の位置表現(例: filename:line:column)に厳密に依存しないようになりました。
    • splitError 関数が導入され、エラーメッセージから位置情報と実際のメッセージを分離できるようになりました。
    • errMap 関数が導入され、ソースコード内の /* ERROR "rx" */ コメントから期待されるエラーの正規表現を収集し、位置文字列をキーとするマップとして管理するようになりました。
    • eliminate 関数が修正され、実際の型チェッカーのエラーと期待されるエラーを照合し、一致したものを削除するロジックが改善されました。これにより、テストの堅牢性と柔軟性が向上しました。
  5. 組み込み関数の型チェックの改善 (src/pkg/exp/types/builtins.go):

    • copy 組み込み関数の引数チェックがより厳密になりました。スライス引数が必要であること、および要素型が同一であることのチェックが追加されました。
    • real および imag 組み込み関数が、Complex 型の Re および Im フィールドを使用するように修正されました。
    • sizeof 関数が導入され、ContextIntSizePtrSize を利用して型のサイズを計算するようになりました。これにより、unsafe.Sizeof の型チェックがより正確になります。
  6. 定数表現の改善 (src/pkg/exp/types/const.go):

    • complex 型が Complex に、nilTypeNilType にリネームされ、フィールド名も re, im から Re, Im に変更されました。これにより、Goの命名規則に沿ったものになりました。
    • 定数表現の正規化ロジックが改善され、big.RatComplex 型の扱いがより一貫性を持つようになりました。
  7. エラー報告の改善 (src/pkg/exp/types/errors.go):

    • checker.errorf メソッドが Context.Error コールバックを使用するように変更され、エラー報告がより柔軟になりました。
    • トレース出力のフォーマットが調整されました。
  8. 複合リテラルのアドレス取得 (src/pkg/exp/types/expr.go):

    • 複合リテラルのアドレス &T{...} が許可されるようになりました。これはGo言語の仕様に沿った変更です。

これらの変更は、exp/types パッケージがGo言語の型システムを正確にモデル化し、堅牢な型チェックを提供するための基盤を強化するものです。

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

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

  1. src/pkg/exp/types/api.go (新規追加):

    • Context 構造体とそのフィールド (IntSize, PtrSize, Error, Expr, Import) の定義。
    • Context.Check メソッドと、そのショートハンドであるグローバルな Check 関数の定義。
  2. src/pkg/exp/types/check.go:

    • checker 構造体の変更: ctxt, files, funclist, funcsig フィールドの追加。
    • later 関数の追加: 関数/メソッドボディの型チェックを遅延実行するためのメカニズム。
    • check 関数の変更: Context を引数として受け取るように変更され、Context の設定(エラーハンドラ、インポーターなど)を利用するように修正。関数/メソッドボディの型チェックループの追加。
    • object 関数内の関数/メソッド宣言の処理ロジックの変更: check.later を使用してボディの型チェックをキューに入れるように修正。
  3. src/pkg/exp/types/builtins.go:

    • _Copy 組み込み関数の実装: copy 関数の引数チェックロジックの追加。
    • sizeof 関数の追加: Context のサイズ情報に基づいて型のサイズを計算。
  4. src/pkg/exp/types/const.go:

    • complex 型を Complex に、nilTypeNilType にリネーム。
    • complex 型のフィールド名を re, im から Re, Im に変更。
    • 関連する定数操作関数(newComplex, toImagConst, isRepresentableConst, complexity, matchConst, unaryOpConst, binaryOpConst, compareConst)での型名の変更と、Complex 型のフィールドアクセス(c.Re, c.Im)への修正。
  5. src/pkg/exp/types/check_test.go:

    • splitError, errMap 関数の追加: テストハーネスにおけるエラーメッセージ解析の改善。
    • eliminate 関数の修正: エラー照合ロジックの改善。
    • checkFiles 関数の変更: 新しいテストハーネスの利用と、Context を使用した型チェックの呼び出し。

コアとなるコードの解説

src/pkg/exp/types/api.go

// A Context specifies the supporting context for type checking.
type Context struct {
	IntSize int64 // size in bytes of int and uint values
	PtrSize int64 // size in bytes of pointers

	// If Error is not nil, it is called with each error found
	// during type checking.
	Error func(err error)

	// If Expr is not nil, it is called for each expression x that is
	// type-checked: typ is the expression type, and val is the value
	// if x is constant, val is nil otherwise.
	// ... (constant representation details)
	Expr func(x ast.Expr, typ Type, val interface{})

	// If Import is not nil, it is used instead of GcImport.
	Import ast.Importer
}

// Default is the default context for type checking.
var Default = Context{
	// TODO(gri) Perhaps this should depend on GOARCH?
	IntSize: 8,
	PtrSize: 8,
}

// Check resolves and typechecks a set of package files within the given
// context. ...
func (ctxt *Context) Check(fset *token.FileSet, files map[string]*ast.File) (*ast.Package, error) {
	return check(ctxt, fset, files)
}

// Check is shorthand for Default.Check.
func Check(fset *token.FileSet, files map[string]*ast.File) (*ast.Package, error) {
	return Default.Check(fset, files)
}

このファイルは、型チェッカーの外部インターフェースを定義する上で最も重要な変更です。Context 構造体は、型チェックの動作をカスタマイズするための設定ポイントを提供します。ErrorExpr コールバックは、型チェッカーがエラーを報告したり、式の型情報を外部に公開したりするためのフックを提供し、これによりツールとの統合が容易になります。IntSizePtrSize は、プラットフォーム依存の型サイズを考慮に入れることを可能にします。

src/pkg/exp/types/check.go

type checker struct {
	ctxt  *Context
	fset  *token.FileSet
	files []*ast.File

	// lazily initialized
	firsterr  error
	initexprs map[*ast.ValueSpec][]ast.Expr // "inherited" initialization expressions for constant declarations
	funclist  []function                    // list of functions/methods with correct signatures and non-empty bodies
	funcsig   *Signature                    // signature of currently typechecked function
	pos       []token.Pos                   // stack of expr positions; debugging support, used if trace is set
}

type function struct {
	obj  *ast.Object // for debugging/tracing only
	sig  *Signature
	body *ast.BlockStmt
}

// later adds a function with non-empty body to the list of functions
// that need to be processed after all package-level declarations
// are typechecked.
func (check *checker) later(obj *ast.Object, sig *Signature, body *ast.BlockStmt) {
	// functions implemented elsewhere (say in assembly) have no body
	if body != nil {
		check.funclist = append(check.funclist, function{obj, sig, body})
	}
}

func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg *ast.Package, err error) {
	check := checker{
		ctxt:      ctxt,
		fset:      fset,
		files:     sortedFiles(files),
		initexprs: make(map[*ast.ValueSpec][]ast.Expr),
	}
	// ... (error handling, identifier resolution)

	// typecheck all declarations
	check.iterate((*checker).decl)

	// typecheck all function/method bodies
	// (funclist may grow when checking statements - do not use range clause!)
	for i := 0; i < len(check.funclist); i++ {
		f := check.funclist[i]
		// ... (tracing)
		check.funcsig = f.sig
		check.stmtList(f.body.List)
	}

	return
}

checker 構造体は Context への参照を持つようになり、型チェックの全体的な設定にアクセスできるようになりました。funclistlater メソッドの導入は、型チェッカーがパッケージレベルの宣言を処理した後に、関数やメソッドのボディを効率的に型チェックするための重要な変更です。これにより、前方参照や相互参照を含む複雑なコードベースでも、正しい順序で型チェックが行われるようになります。check 関数の最後のループは、キューに入れられたすべての関数ボディを順次処理し、型チェックを完了させます。

src/pkg/exp/types/builtins.go

func sizeof(ctxt *Context, typ Type) int64 {
	switch typ := underlying(typ).(type) {
	case *Basic:
		switch typ.Kind {
		case Int, Uint:
			return ctxt.IntSize
		case Uintptr:
			return ctxt.PtrSize
		}
		return typ.Size
	case *Array:
		return sizeof(ctxt, typ.Elt) * typ.Len
	case *Struct:
		var size int64
		for _, f := range typ.Fields {
			size += sizeof(ctxt, f.Type)
		}
		return size
	}
	return ctxt.PtrSize // good enough
}

sizeof 関数は、Context から IntSizePtrSize を取得して型のサイズを計算するようになりました。これにより、unsafe.Sizeof のような組み込み関数の型チェックが、ターゲットアーキテクチャの特性を考慮して正確に行われるようになります。配列や構造体のサイズ計算も再帰的に行われます。

src/pkg/exp/types/const.go

type Complex struct {
	Re, Im *big.Rat
}

func (c Complex) String() string {
	if c.Re.Sign() == 0 {
		return fmt.Sprintf("%si", c.Im)
	}
	// normalized complex values always have an imaginary part
	return fmt.Sprintf("(%s + %si)", c.Re, c.Im)
}

type NilType struct{}

func (NilType) String() string {
	return "nil"
}

complexnilType のリネームは、Goの慣習に合わせたもので、コードの可読性と一貫性を向上させます。特に Complex 型のフィールド名が ReIm に変更されたことで、実部と虚部がより明確に表現されるようになりました。これにより、定数計算ロジック全体でこれらの新しい型名とフィールド名が使用されるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特に go/types パッケージの歴史)
  • Go言語のIssueトラッカーやメーリングリストの議論(当時の開発状況を把握するため)
  • Go言語の仕様書 (The Go Programming Language Specification)
  • コミットメッセージと変更されたファイルの差分情報