[インデックス 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言語の公式な型チェッカーとして開発が進められている段階でした。初期の型チェッカーは、その機能や設定の柔軟性に限界がありました。特に、関数やメソッドのボディ内の詳細な型チェックが不十分であったり、エラー報告の形式がテストに適していなかったりする問題がありました。
このコミットの背景には、以下のような目的があったと考えられます。
- 型チェッカーの堅牢性向上: 言語仕様の複雑な側面(特にシフト演算や組み込み関数の振る舞い)を正確に処理し、より多くのGoコードを正しく型チェックできるようにすること。
- APIの柔軟性: 型チェッカーをGoコンパイラ以外のツール(IDE、リンター、コード分析ツールなど)から利用しやすくするため、設定可能なAPIを提供すること。
Context
型の導入は、この目的を達成するための重要なステップです。 - テスト容易性の改善: 型チェッカーのテストをより効率的かつ信頼性の高いものにするため、エラー位置の表現に依存しない汎用的なテストハーネスが必要とされていました。
- バグの修正: 開発中のパッケージであるため、発見された多数のバグを修正し、安定性を高めること。
これらの変更は、最終的に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プログラム内の名前付きエンティティ(変数、関数、型など)を表す抽象的なオブジェクトです。型チェッカーは、これらのオブジェクトに型情報を関連付けます。
技術的詳細
このコミットの技術的詳細は多岐にわたりますが、主要な変更点は以下の通りです。
-
Context
型の導入 (src/pkg/exp/types/api.go
の新規追加):- 型チェッカーの振る舞いを設定するための
types.Context
構造体が導入されました。 IntSize
とPtrSize
フィールドにより、int
およびポインタのサイズをコンテキストとして指定できるようになりました。これは、異なるアーキテクチャ(32ビット/64ビット)での型チェックの正確性を保証するために重要です。Error
フィールドは、型チェック中にエラーが発見された際に呼び出されるコールバック関数です。これにより、エラー処理のロジックを外部から注入できるようになり、テストハーネスやIDE統合が容易になります。Expr
フィールドは、各式が型チェックされた後に呼び出されるコールバック関数です。これにより、式の型や定数値にアクセスできるようになり、コード分析ツールなどが利用できます。Import
フィールドは、パッケージのインポート処理をカスタマイズするためのast.Importer
インターフェースを提供します。デフォルトではGcImport
が使用されます。Default
というグローバルなContext
インスタンスが提供され、デフォルトの設定(IntSize: 8
,PtrSize: 8
)を持ちます。Context.Check
メソッドが導入され、特定のコンテキストで型チェックを実行できるようになりました。これにより、従来のグローバルなCheck
関数がDefault.Check
のショートハンドとして機能するようになります。
- 型チェッカーの振る舞いを設定するための
-
関数・メソッドボディの型チェックの強化 (
src/pkg/exp/types/check.go
):- 以前はパッケージレベルの宣言のみが型チェックの対象でしたが、このコミットにより、すべての関数およびメソッドのボディが型チェックされるようになりました。
checker
構造体にfunclist
(型チェックが必要な関数/メソッドのリスト) とfuncsig
(現在型チェック中の関数のシグネチャ) が追加されました。later
メソッドが導入され、パッケージレベルの宣言が型チェックされた後に、関数ボディの型チェックをキューに入れるメカニズムが提供されました。これにより、宣言とボディの型チェックの順序が適切に管理されます。init
関数(Goの初期化関数)の型チェックが改善され、引数や戻り値がないことが強制されるようになりました。
-
シフトヒンティングの修正 (
src/pkg/exp/types/expr.go
):- シフト演算の左オペランドが型なし定数である場合の型推論ロジックが修正されました。
- 仕様に従い、非定数シフト式の場合、左オペランドの型なし定数は、シフト式がその左オペランド単独に置き換えられた場合の型に変換されるようになりました。コンテキストから型が決定できない場合は
int
になります。
-
テストハーネスの刷新 (
src/pkg/exp/types/check_test.go
):- エラーメッセージの解析方法が変更され、特定の位置表現(例:
filename:line:column
)に厳密に依存しないようになりました。 splitError
関数が導入され、エラーメッセージから位置情報と実際のメッセージを分離できるようになりました。errMap
関数が導入され、ソースコード内の/* ERROR "rx" */
コメントから期待されるエラーの正規表現を収集し、位置文字列をキーとするマップとして管理するようになりました。eliminate
関数が修正され、実際の型チェッカーのエラーと期待されるエラーを照合し、一致したものを削除するロジックが改善されました。これにより、テストの堅牢性と柔軟性が向上しました。
- エラーメッセージの解析方法が変更され、特定の位置表現(例:
-
組み込み関数の型チェックの改善 (
src/pkg/exp/types/builtins.go
):copy
組み込み関数の引数チェックがより厳密になりました。スライス引数が必要であること、および要素型が同一であることのチェックが追加されました。real
およびimag
組み込み関数が、Complex
型のRe
およびIm
フィールドを使用するように修正されました。sizeof
関数が導入され、Context
のIntSize
とPtrSize
を利用して型のサイズを計算するようになりました。これにより、unsafe.Sizeof
の型チェックがより正確になります。
-
定数表現の改善 (
src/pkg/exp/types/const.go
):complex
型がComplex
に、nilType
がNilType
にリネームされ、フィールド名もre
,im
からRe
,Im
に変更されました。これにより、Goの命名規則に沿ったものになりました。- 定数表現の正規化ロジックが改善され、
big.Rat
やComplex
型の扱いがより一貫性を持つようになりました。
-
エラー報告の改善 (
src/pkg/exp/types/errors.go
):checker.errorf
メソッドがContext.Error
コールバックを使用するように変更され、エラー報告がより柔軟になりました。- トレース出力のフォーマットが調整されました。
-
複合リテラルのアドレス取得 (
src/pkg/exp/types/expr.go
):- 複合リテラルのアドレス
&T{...}
が許可されるようになりました。これはGo言語の仕様に沿った変更です。
- 複合リテラルのアドレス
これらの変更は、exp/types
パッケージがGo言語の型システムを正確にモデル化し、堅牢な型チェックを提供するための基盤を強化するものです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。
-
src/pkg/exp/types/api.go
(新規追加):Context
構造体とそのフィールド (IntSize
,PtrSize
,Error
,Expr
,Import
) の定義。Context.Check
メソッドと、そのショートハンドであるグローバルなCheck
関数の定義。
-
src/pkg/exp/types/check.go
:checker
構造体の変更:ctxt
,files
,funclist
,funcsig
フィールドの追加。later
関数の追加: 関数/メソッドボディの型チェックを遅延実行するためのメカニズム。check
関数の変更:Context
を引数として受け取るように変更され、Context
の設定(エラーハンドラ、インポーターなど)を利用するように修正。関数/メソッドボディの型チェックループの追加。object
関数内の関数/メソッド宣言の処理ロジックの変更:check.later
を使用してボディの型チェックをキューに入れるように修正。
-
src/pkg/exp/types/builtins.go
:_Copy
組み込み関数の実装:copy
関数の引数チェックロジックの追加。sizeof
関数の追加:Context
のサイズ情報に基づいて型のサイズを計算。
-
src/pkg/exp/types/const.go
:complex
型をComplex
に、nilType
をNilType
にリネーム。complex
型のフィールド名をre
,im
からRe
,Im
に変更。- 関連する定数操作関数(
newComplex
,toImagConst
,isRepresentableConst
,complexity
,matchConst
,unaryOpConst
,binaryOpConst
,compareConst
)での型名の変更と、Complex
型のフィールドアクセス(c.Re
,c.Im
)への修正。
-
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
構造体は、型チェックの動作をカスタマイズするための設定ポイントを提供します。Error
と Expr
コールバックは、型チェッカーがエラーを報告したり、式の型情報を外部に公開したりするためのフックを提供し、これによりツールとの統合が容易になります。IntSize
と PtrSize
は、プラットフォーム依存の型サイズを考慮に入れることを可能にします。
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
への参照を持つようになり、型チェックの全体的な設定にアクセスできるようになりました。funclist
と later
メソッドの導入は、型チェッカーがパッケージレベルの宣言を処理した後に、関数やメソッドのボディを効率的に型チェックするための重要な変更です。これにより、前方参照や相互参照を含む複雑なコードベースでも、正しい順序で型チェックが行われるようになります。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
から IntSize
と PtrSize
を取得して型のサイズを計算するようになりました。これにより、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"
}
complex
と nilType
のリネームは、Goの慣習に合わせたもので、コードの可読性と一貫性を向上させます。特に Complex
型のフィールド名が Re
と Im
に変更されたことで、実部と虚部がより明確に表現されるようになりました。これにより、定数計算ロジック全体でこれらの新しい型名とフィールド名が使用されるようになります。
関連リンク
- Go言語の
go/types
パッケージのドキュメント (現在のバージョン): https://pkg.go.dev/go/types - Go言語の
go/ast
パッケージのドキュメント: https://pkg.go.dev/go/ast - Go言語の
go/token
パッケージのドキュメント: https://pkg.go.dev/go/token - Go言語の
math/big
パッケージのドキュメント: https://pkg.go.dev/math/big
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
go/types
パッケージの歴史) - Go言語のIssueトラッカーやメーリングリストの議論(当時の開発状況を把握するため)
- Go言語の仕様書 (The Go Programming Language Specification)
- コミットメッセージと変更されたファイルの差分情報