[インデックス 14578] ファイルの概要
このコミットは、Go言語の型チェッカーの実験的なパッケージ exp/types
における重要な改善とバグ修正を含んでいます。特に、type switch
文と range
句の型チェック機能が大幅に強化され、型アサーションの扱いが改善され、可変引数関数の初期的なサポートが追加されています。また、組み込みの error
型の実装も行われています。
コミット
commit 69d0f0cc473d42fd7a49125d6b4667fe301c0d86
Author: Robert Griesemer <gri@golang.org>
Date: Thu Dec 6 09:21:30 2012 -0800
exp/types: checking of type switches and range clauses
Also:
- better handling of type assertions
- implemented built-in error type
- first cut at handling variadic function signatures
- several bug fixes
R=rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/6846131
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/69d0f0cc473d42fd7a49125d6b4667fe301c0d86
元コミット内容
exp/types: checking of type switches and range clauses
Also:
- better handling of type assertions
- implemented built-in error type
- first cut at handling variadic function signatures
- several bug fixes
R=rsc, rogpeppe
CC=golang-dev
https://golang.org/cl/6846131
変更の背景
このコミットが行われた2012年12月は、Go言語がまだ比較的新しい時期であり、言語仕様の策定とコンパイラ/ツールチェインの実装が活発に進められていました。exp/types
パッケージは、Goの型システムと型チェックロジックを実験的に開発するためのものでした。
当時のGo言語では、type switch
や for...range
といった制御構造、そして可変引数関数や型アサーションといった機能が既に存在していましたが、それらの型チェックの厳密性や正確性にはまだ改善の余地がありました。特に、Goのインターフェース型と具象型の間の関係性、そしてそれらが動的な型チェックにどのように影響するかは、型チェッカーにとって複雑な課題でした。
このコミットの主な目的は、これらの言語機能に対する型チェックのロジックを強化し、より堅牢で正確なコンパイル時エラー検出を可能にすることでした。これにより、開発者はより信頼性の高いコードを書くことができ、Go言語の型システムが意図する安全性と表現力を最大限に引き出すことが期待されました。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念と型チェックの基本を把握しておく必要があります。
- Go言語の型システム: Goは静的型付け言語であり、変数は宣言時に型を持ちます。型チェックは、プログラムが型の規則に従っていることをコンパイル時に検証するプロセスです。
- AST (Abstract Syntax Tree): Goのソースコードは、パーサーによって抽象構文木(AST)に変換されます。型チェッカーはこのASTを走査し、各ノードの型情報を解決し、型規則に違反がないかを確認します。
- インターフェース型: Goのインターフェースは、メソッドのシグネチャの集合を定義します。ある型がインターフェースのすべてのメソッドを実装していれば、その型は自動的にそのインターフェースを実装しているとみなされます(構造的型付け)。
- 型アサーション (
x.(T)
): インターフェース型の値x
が、特定の具象型T
であるかどうかを動的にチェックし、もしそうであればその具象型の値として取り出すための構文です。 type switch
文: インターフェース型の値の動的な型に基づいて、異なるコードブロックを実行するための制御構造です。switch x.(type)
の形式で記述されます。for...range
句: スライス、配列、文字列、マップ、チャネルなどのコレクションを反復処理するための制御構造です。- 可変引数関数 (
func(...T)
): 最後のパラメータの前に...
を付けることで、任意の数の引数を受け取ることができる関数です。可変引数は関数内部ではスライスとして扱われます。 exp/types
パッケージ: Goの標準ライブラリgo/types
パッケージの前身となる実験的な型チェッカーの実装です。Go言語の進化の過程で、型チェックのロジックを開発・検証するために使用されました。
技術的詳細
このコミットは、Goの型チェッカーの複数の側面を改善しています。
-
type switch
文の型チェックの強化:type switch
のガード (switch x.(type)
) の構文と意味論がより厳密にチェックされるようになりました。特に、x
がインターフェース型であることの検証が強化されています。- 各
case
句で指定された型が、スイッチ対象のインターフェース型を正しく実装しているかどうかのチェックが導入されました。これは、新しく追加されたmissingMethod
関数によって行われます。missingMethod
は、ある型が特定のインターフェースを実装しているかどうか、または実装していない場合にどのメソッドが不足しているか、あるいはシグネチャが間違っているかを判断します。 case
句内で宣言される変数の型が、そのcase
に対応する型に正しく設定されるようになりました。特に、単一の型が指定されたcase
ではその型に、複数の型が指定されたcase
やdefault
句では元のインターフェース型に設定されます。- 複数の
default
句が存在する場合のエラー検出が追加されました。
-
for...range
句の型チェックの強化:for...range
の対象となる式の型に基づいて、イテレーション変数(キーと値)の型が正確に推論され、チェックされるようになりました。- 文字列、配列、スライス、ポインタ配列、マップ、チャネルに対する
range
の振る舞いが適切に型チェックされます。 - 特に、チャネルに対する
range
の場合、送信専用チャネル (chan<- T
) をrange
できないことや、イテレーション変数が1つしか許されないことなどがチェックされます。 - 無効な
range
対象に対するエラーメッセージが改善されました。
-
型アサーションの改善:
- 型アサーション
x.(T)
において、x
がインターフェース型でない場合にエラーが報告されるようになりました。以前は「non-interface type」という一般的なエラーでしたが、より具体的な「not an interface」というメッセージに変わっています。 - アサーションされる型
T
が、x
のインターフェース型を正しく実装しているかどうかのチェックにmissingMethod
関数が活用されるようになりました。これにより、コンパイル時にインターフェースの実装不足やメソッドシグネチャの不一致を検出できるようになりました。 .(type)
構文がtype switch
の外部で使用された場合にエラーを報告するようになりました。
- 型アサーション
-
可変引数関数の初期サポート:
collectParams
関数がvariadicOk
パラメータを受け取るようになり、可変引数 (...T
) の処理がより正確になりました。最後のパラメータが可変引数である場合にのみisVariadic
がtrue
に設定されます。- 可変引数関数の最後のパラメータの型が、関数内部でスライス型 (
[]T
) として扱われるように変更されました。ただし、外部から見える型は元の要素型T
のまま保持されます。 - 関数呼び出しにおける引数の型チェック (
argument
関数) が、可変引数関数のシグネチャを考慮するようになりました。
-
組み込み
error
型の実装:src/pkg/exp/types/universe.go
において、組み込みのerror
インターフェースが、Error() string
メソッドを持つインターフェースとして明示的に定義されるようになりました。これにより、型チェッカーがerror
型を正しく認識し、そのメソッドを解決できるようになります。
-
バグ修正とコードのクリーンアップ:
check.go
でのcollectParams
の呼び出しにfalse
が追加され、メソッドレシーバの型チェックがより正確になりました。const.go
でnilType
がUnsafePointer
にも表現可能であるとマークされました。expr.go
でのcallRecord
がrecordType
にリネームされ、より汎用的な型記録メカニズムになりました。- ポインタ配列 (
*[N]T
) のインデックス付けやスライス化の型チェックが追加されました。 - テストデータ (
decls1.src
,expr3.src
,stmt0.src
) が更新され、新しい型チェックルールが反映されています。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に src/pkg/exp/types/stmt.go
と src/pkg/exp/types/expr.go
、そして src/pkg/exp/types/predicates.go
に集中しています。
-
src/pkg/exp/types/predicates.go
におけるmissingMethod
関数の追加:// missingMethod returns (nil, false) if typ implements T, otherwise // it returns the first missing method required by T and whether it // is missing or simply has the wrong type. func missingMethod(typ Type, T *Interface) (method *ast.Object, wrongType bool) { // ... (implementation details for interface and concrete types) }
この関数は、型アサーションや
type switch
におけるインターフェース適合性のチェックの基盤となります。 -
src/pkg/exp/types/stmt.go
におけるTypeSwitchStmt
の型チェックロジック:case *ast.TypeSwitchStmt: // ... // rhs must be of the form: expr.(type) and expr must be an interface // ... var T *Interface if T, _ = underlying(x.typ).(*Interface); T == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) // ... // Check each type in this type switch case. var typ Type for _, expr := range clause.List { typ = check.typOrNil(expr, false) if typ != nil && typ != Typ[Invalid] { if method, wrongType := missingMethod(typ, T); method != nil { // ... report error for missing/wrong method ... } } } // If lhs exists, set its type for each clause. if lhs != nil { if len(clause.List) != 1 || typ == nil { typ = x.typ } lhs.Type = typ } check.stmtList(clause.Body) } // ...
このセクションは、
type switch
のガードの検証、case
句の型のインターフェース適合性チェック、およびcase
句内の変数lhs
の型設定を処理します。 -
src/pkg/exp/types/stmt.go
におけるRangeStmt
の型チェックロジック:case *ast.RangeStmt: // ... // determine key/value types var key, val Type switch typ := underlying(x.typ).(type) { case *Basic: if isString(typ) { key = Typ[UntypedInt] val = Typ[UntypedRune] } case *Array: key = Typ[UntypedInt] val = typ.Elt case *Slice: key = Typ[UntypedInt] val = typ.Elt case *Pointer: if typ, _ := underlying(typ.Base).(*Array); typ != nil { key = Typ[UntypedInt] val = typ.Elt } case *Map: key = typ.Key val = typ.Elt case *Chan: key = typ.Elt if typ.Dir&ast.RECV == 0 { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) } } // ... (error handling for invalid range types) // check assignment to/declaration of iteration variables // ...
このセクションは、
range
対象の型に基づいてキーと値の型を決定し、それらの型がイテレーション変数に正しく割り当てられるかをチェックします。 -
src/pkg/exp/types/expr.go
における型アサーションの改善:case *ast.TypeAssertExpr: check.expr(x, e.X, hint, iota) if x.mode == invalid { goto Error } var T *Interface if T, _ = underlying(x.typ).(*Interface); T == nil { check.errorf(x.pos(), "%s is not an interface", x) goto Error } // x.(type) expressions are handled explicitly in type switches if e.Type == nil { check.errorf(e.Pos(), "use of .(type) outside type switch") goto Error } typ := check.typ(e.Type, false) if typ == Typ[Invalid] { goto Error } if method, wrongType := missingMethod(typ, T); method != nil { // ... report error for missing/wrong method ... // ok to continue } x.mode = valueok x.expr = e x.typ = typ
このコードは、型アサーションの対象がインターフェース型であることの確認、
.(type)
の誤用チェック、そしてmissingMethod
を用いたインターフェース適合性の検証を行います。 -
src/pkg/exp/types/expr.go
における可変引数関数のパラメータ収集 (collectParams
):func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params ObjList, isVariadic bool) { // ... if t, _ := ftype.(*ast.Ellipsis); t != nil { if variadicOk && i == len(list.List)-1 { isVariadic = true } else { check.invalidAST(field.Pos(), "... not permitted") // ok to continue } } // ... // For a variadic function, change the last parameter's object type // from T to []T (this is the type used inside the function), but // keep a copy of the object with the original type T in the params // list (this is the externally visible type). if isVariadic { copy := *last last.Type = &Slice{Elt: last.Type.(Type)} params[len(params)-1] = © } return }
この関数は、可変引数パラメータの正しい識別と、関数内部でのスライス型への変換を処理します。
コアとなるコードの解説
上記の変更箇所は、Go言語の型チェックの正確性と堅牢性を大幅に向上させています。
-
missingMethod
関数: この関数は、Goのインターフェースの構造的型付けの核心をなすものです。ある具象型がインターフェースを実装しているかどうかを判断するために、インターフェースが要求するすべてのメソッドが具象型に存在し、かつそのシグネチャが一致するかどうかを検証します。この関数が導入されたことで、型アサーションやtype switch
において、より詳細で正確なコンパイル時エラーメッセージを提供できるようになりました。例えば、メソッドが完全に不足しているのか、それともシグネチャが間違っているのかを区別して報告できます。 -
TypeSwitchStmt
の型チェック:type switch
はGoの強力な動的型チェック機能ですが、その型チェックは複雑です。このコミットでは、スイッチ対象がインターフェース型であることの厳密な検証、各case
句で指定された型がそのインターフェースを実際に実装しているかどうかのmissingMethod
を用いたチェック、そしてcase
句内で宣言される変数の型がそのcase
のコンテキストに応じて正しく設定されるロジックが追加されました。これにより、type switch
の誤用や型不一致によるランタイムエラーをコンパイル時に捕捉できるようになりました。 -
RangeStmt
の型チェック:for...range
句は、Goのコレクション型を扱う上で非常に便利ですが、その振る舞いはコレクションの型によって異なります。このコミットでは、range
の対象が文字列、配列、スライス、マップ、チャネルのいずれであるかに応じて、イテレーション変数key
とval
の型を正確に推論し、それらが正しく使用されているかを検証するロジックが実装されました。特に、チャネルのrange
における送信専用チャネルの制限や、イテレーション変数の数の制限など、Goの仕様に厳密に従ったチェックが行われるようになりました。これにより、range
句の誤った使用によるコンパイルエラーや予期せぬ振る舞いを防ぎます。 -
可変引数関数のパラメータ処理: 可変引数関数はGoの柔軟な機能ですが、その型チェックは通常の関数とは異なります。
collectParams
関数におけるvariadicOk
パラメータの導入と、最後の可変引数パラメータの型を関数内部でスライス型として扱うロジックは、可変引数関数の正しい型チェックを可能にします。これにより、可変引数関数への引数の渡し方や、関数内部での可変引数の使用方法に関する型エラーを正確に検出できるようになりました。
これらの変更は、Go言語の型チェッカーの成熟度を高め、開発者がより安全で信頼性の高いGoプログラムを記述するための基盤を強化するものでした。
関連リンク
- Go CL 6846131: https://golang.org/cl/6846131 - このコミットに対応するGoの変更リスト(Change List)です。Goのコミットは通常、このようなCLとしてレビュー・承認されます。
参考にした情報源リンク
- Go言語仕様: Go言語の型システム、
type switch
、for...range
、可変引数関数、型アサーションに関する公式な定義は、Go言語仕様に記載されています。 - Goの型チェックに関する資料:
go/types
パッケージやGoのコンパイラに関する技術文書は、Goの公式ドキュメントやブログで参照できます。 - GoのASTパッケージ:
go/ast
パッケージのドキュメントは、GoのAST構造を理解する上で役立ちます。 - Goのトークンパッケージ:
go/token
パッケージのドキュメントは、Goの字句要素を理解する上で役立ちます。