[インデックス 15694] ファイルの概要
コミット
commit 5183bfda7590bb14f3be77fdc1852c6b314c0762
Author: Robert Griesemer <gri@golang.org>
Date: Mon Mar 11 13:38:45 2013 -0700
go/types: update operand types early
For expressions where the result type is independent
of the argument types (comparisons, conversions, rhs
of shifts), set the final expression types for those
subtrees early.
This fixes several bugs where incorrect lhs shift
operands where used (say in a comparison), but were
not reported.
Together with the changes listed below this CL fixes
many type-checker bugs.
Also:
- better documented updateExprType
- added larger comment to expr.go explaining
the basic expression checking algorithm
- use latest definition for indices and make
arguments; use the same code to check both
- use the same mechanism for cycle detection
in constant expressions as for variables
(new field Constant.visited)
- more tests for complex and make builtins
- many more and systematic tests for shifts;
moved them into separate testfile
- in the testing code, don't compare the
expected error pattern against itself
(the actual message was always ignored...)
- fix affected error patterns in the test files
- various cleanups along the way
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5183bfda7590bb14f3be77fdc1852c6b314c0762
元コミット内容
go/types: update operand types early
For expressions where the result type is independent
of the argument types (comparisons, conversions, rhs
of shifts), set the final expression types for those
subtrees early.
This fixes several bugs where incorrect lhs shift
operands where used (say in a comparison), but were
not reported.
Together with the changes listed below this CL fixes
many type-checker bugs.
Also:
- better documented updateExprType
- added larger comment to expr.go explaining
the basic expression checking algorithm
- use latest definition for indices and make
arguments; use the same code to check both
- use the same mechanism for cycle detection
in constant expressions as for variables
(new field Constant.visited)
- more tests for complex and make builtins
- many more and systematic tests for shifts;
moved them into separate testfile
- in the testing code, don't compare the
expected error pattern against itself
(the actual message was always ignored...)
- fix affected error patterns in the test files
- various cleanups along the way
変更の背景
このコミットは、Go言語の型チェッカー(go/types
パッケージ)における複数のバグを修正し、特に式評価時の型推論の正確性を向上させることを目的としています。これまでの型チェッカーでは、比較演算子、型変換、シフト演算子の右オペランドなど、結果の型が引数の型に依存しない特定の種類の式において、サブツリーの最終的な式型が早期に設定されていませんでした。このため、例えばシフト演算子の左オペランドが誤った型で使用されていても、それが報告されないといった問題が発生していました。
この変更の主な動機は、型チェックのロジックを改善し、より厳密で正確な型エラー検出を可能にすることにあります。特に、シフト演算子の左オペランドの型チェックの遅延評価が原因で発生していたバグの修正が挙げられます。また、型チェッカーの内部動作に関するドキュメントの改善や、テストの拡充も行われています。
前提知識の解説
- Go言語の型システム: Go言語は静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。
go/types
パッケージは、Goコンパイラの一部として、ソースコードの型チェックを担当します。 - 式 (Expression): プログラムにおいて値を生成するコードの断片です。例えば、
a + b
、f(x)
、1 << s
など。 - オペランド (Operand): 演算子によって操作される値です。例えば、
a + b
におけるa
とb
。 - 型推論 (Type Inference): プログラマが明示的に型を指定しなくても、コンパイラが文脈から自動的に型を決定する機能です。Go言語では、変数宣言や一部の式で型推論が利用されます。
- Untyped Expressions (型なし式): Go言語には、特定の数値リテラル(例:
10
、3.14
、1i
)や、それらを含む演算の結果など、初期段階では具体的なGoの型を持たない「型なし」の概念があります。これらは、最終的に使用される文脈(変数への代入、関数呼び出しの引数など)に応じて、適切なGoの型に「型付け」されます。例えば、var x float64 = 10
の10
は最初は型なし整数ですが、float64
に型付けされます。 - シフト演算子 (Shift Operators):
<<
(左シフト) と>>
(右シフト) があります。x << y
はx
をy
ビット左にシフトします。Go言語の仕様では、シフト演算子の左オペランドは整数型である必要があり、右オペランドは符号なし整数型であるか、符号なし整数型に変換可能な型なし定数である必要があります。 - 定数式 (Constant Expression): コンパイル時に値が決定される式です。型なし定数は、定数式の一種です。
go/ast
パッケージ: Goの抽象構文木 (AST) を表現するためのパッケージです。ソースコードはまずASTにパースされ、その後型チェックなどの処理が行われます。go/token
パッケージ: Goのトークン(キーワード、識別子、演算子など)とソースコード上の位置情報を扱うパッケージです。
技術的詳細
このコミットの核心は、go/types
パッケージ内の型チェックロジック、特にchecker
構造体とupdateExprType
メソッドの変更にあります。
-
exprInfo
構造体の導入とuntyped
マップの変更:- 以前は、型なし式とその型を格納する
untyped
マップと、型なし定数式とその値を格納するconstants
マップが別々に存在していました。 - このコミットでは、
exprInfo
という新しい構造体が導入され、isConst
(定数であるか)、isLhs
(シフト演算子の左オペランドであるか)、typ
(型)、val
(定数値)をまとめて保持するようになりました。 untyped
マップはmap[ast.Expr]exprInfo
型に変更され、型なし式のすべての関連情報が一元的に管理されるようになりました。これにより、コードの複雑性が軽減され、型なし式の状態管理がより明確になりました。- 特に
isLhs
フィールドは、シフト演算子の左オペランドの型チェックを遅延させるための重要なフラグです。
- 以前は、型なし式とその型を格納する
-
updateExprType
メソッドの改善:- このメソッドは、型なし式が最終的な型を持つようになったときに呼び出され、その型を式ツリーに伝播させます。
- 変更前は、
shiftOp
というブール引数でシフト演算子の左オペランドであるかを判定していましたが、新しいexprInfo.isLhs
フラグを使用することで、より汎用的なメカニズムになりました。 - 比較演算子 (
isComparison
) やシフト演算子 (isShift
) のように、結果の型がオペランドの型に直接依存しない式の場合、オペランドの型を早期に確定させるようになりました。これにより、例えば1 << s == 1.0
のような式で、1 << s
の結果が比較の文脈でint
に型付けされる前に、1
がint
に型付けされるようになります。 - 定数式の場合、オペランドの型更新は不要になりました。これは、定数オペランドは最終的な型に「具現化」される必要がなく、型チェックの最後にまとめて処理されるためです。
Context.Expr
コールバックの呼び出しロジックが改善され、式が最終的な型を持つようになったときにのみ呼び出されるようになりました。
-
シフト演算子の型チェックの改善:
- シフト演算子 (
shift
関数) のロジックが大幅に修正されました。 - 左オペランドが整数型であるか、整数型に変換可能な型なし定数であるかのチェックが早期に行われるようになりました。
- 右オペランドが符号なし整数型であるか、符号なし整数型に変換可能な型なし定数であるかのチェックも強化されました。
- 非定数シフトの左オペランドが型なし定数である場合、その型チェックを遅延させるメカニズムが
exprInfo.isLhs
フラグを使って実装されました。これにより、シフト演算子の結果が使用される文脈で、左オペランドが適切な整数型に型付けされることが保証されます。
- シフト演算子 (
-
定数式のサイクル検出:
- 定数式における循環参照の検出メカニズムが、変数と同様に
Constant.visited
フィールドを使用して実装されました。これにより、無限ループやスタックオーバーフローを防ぎます。
- 定数式における循環参照の検出メカニズムが、変数と同様に
-
index
関数の改善:index
関数(配列やスライスのインデックス、make
関数の引数などで使用される)が、引数の型チェックをより厳密に行うように変更されました。- 型なし定数が
Int
型に変換可能であるかをチェックし、インデックスが負でないこと、および境界内にあることを確認します。
-
テストの拡充:
go/types/testdata/shifts.src
という新しいテストファイルが追加され、シフト演算子に関する網羅的なテストケースが多数追加されました。builtins.src
やexpr3.src
などの既存のテストファイルも更新され、型チェックの改善に対応するエラーパターンが修正されました。- テストコード内で、期待されるエラーパターンと実際のエラーメッセージの比較方法が修正され、より正確なテストが可能になりました。
これらの変更により、Goの型チェッカーはより堅牢になり、以前は見過ごされていた型エラーを正確に検出できるようになりました。
コアとなるコードの変更箇所
このコミットの主要な変更は以下のファイルに集中しています。
src/pkg/go/types/builtins.go
:make
組み込み関数の引数処理における型チェックの改善。src/pkg/go/types/check.go
:checker
構造体の定義変更(untyped
マップの型変更、exprInfo
の導入)、オブジェクトの型チェックロジックの修正、check
関数の終了処理におけるuntyped
マップの処理変更。src/pkg/go/types/check_test.go
: テストユーティリティの修正、特にエラーメッセージの比較ロジックの改善と新しいシフトテストファイルの追加。src/pkg/go/types/const.go
:isNegConst
関数の削除(不要になったため)。src/pkg/go/types/conversions.go
: 型変換時のupdateExprType
呼び出しの追加。src/pkg/go/types/expr.go
:exprInfo
構造体の定義。updateExprType
関数の大幅な変更とドキュメントの改善。rawExpr
関数のコメントに基本的な式チェックアルゴリズムの説明を追加。comparison
関数とshift
関数のロジック修正。index
関数のシグネチャと実装の変更。callExpr
関数でのexprInfo
の使用。
src/pkg/go/types/operand.go
:isInteger
関数のシグネチャ変更(ctxt
引数の削除)。src/pkg/go/types/stmt.go
: 代入文におけるupdateExprType
呼び出しの追加。src/pkg/go/types/testdata/builtins.src
:complex
とmake
組み込み関数のテストケースの追加・修正。src/pkg/go/types/testdata/decls1.src
: エラーメッセージのパターン修正。src/pkg/go/types/testdata/expr3.src
: シフト演算子に関するテストケースをshifts.src
に移動し、残りのテストケースのエラーパターンを修正。src/pkg/go/types/testdata/shifts.src
: 新規追加されたシフト演算子に関する網羅的なテストファイル。
コアとなるコードの解説
最も重要な変更はsrc/pkg/go/types/expr.go
にあります。
-
exprInfo
構造体:type exprInfo struct { isConst bool // expression has a, possibly unknown, constant value isLhs bool // expression is lhs operand of a shift with delayed type check typ *Basic val interface{} // constant value (may be nil if unknown); valid if isConst }
この構造体は、型チェッカーが式を処理する際に必要なすべての情報を一箇所にまとめるために導入されました。特に
isLhs
フィールドは、シフト演算子の左オペランドの特殊な型チェックロジックを管理するために重要です。 -
checker
構造体のuntyped
フィールド:// 変更前: // untyped map[ast.Expr]*Basic // constants map[ast.Expr]interface{} // shiftOps map[ast.Expr]bool // 変更後: untyped map[ast.Expr]exprInfo
これにより、型なし式の型、定数かどうか、定数値、そしてシフト演算子の左オペランドであるかどうかの情報が、単一のマップエントリで管理されるようになりました。
-
updateExprType
関数の変更: この関数は、式の最終的な型が決定されたときに、その型を式ツリーに伝播させる役割を担います。func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) { old, found := check.untyped[x] if !found { return // nothing to do } // ... (operand updates based on expression kind) ... // If the new type is not final and still untyped, just // update the recorded type. if !final && isUntyped(typ) { old.typ = underlying(typ).(*Basic) check.untyped[x] = old return } // Otherwise we have the final (typed or untyped type). // Remove it from the map. delete(check.untyped, x) // If x is the lhs of a shift, its final type must be integer. // ... if old.isLhs && !isInteger(typ) { check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) return } // Everything's fine, notify client of final type for x. if f := check.ctxt.Expr; f != nil { var val interface{} if old.isConst { val = old.val } f(x, typ, val) } }
final
引数が追加され、型が最終的なものであるかどうかが明示的に示されるようになりました。old.isLhs
フラグを使用して、シフト演算子の左オペランドに対する特別な型チェック(最終的な型が整数型であることの確認)が行われます。- 式が最終的な型を持つと判断された場合、
untyped
マップから削除され、Context.Expr
コールバックを通じてクライアントに通知されます。
-
shift
関数の変更:func (check *checker) shift(x, y *operand, op token.Token) { untypedx := isUntyped(x.typ) // The lhs must be of integer type or be representable // as an integer; otherwise the shift has no chance. if !isInteger(x.typ) && (!untypedx || !isRepresentableConst(x.val, nil, UntypedInt)) { check.invalidOp(x.pos(), "shifted operand %s must be integer", x) x.mode = invalid return } // ... (rhs operand checks) ... if x.mode == constant { if y.mode == constant { // constant shift - direct evaluation // ... } else { // non-constant shift with constant lhs if untypedx { // Delay operand checking until we know the final type: // The lhs expression must be in the untyped map, mark // the entry as lhs shift operand. if info, ok := check.untyped[x.expr]; ok { info.isLhs = true check.untyped[x.expr] = info } else { unreachable() } // keep x's type x.mode = value return } // ... } } // ... }
非定数シフトの左オペランドが型なし定数である場合、
untyped
マップの対応するexprInfo
エントリのisLhs
フラグをtrue
に設定することで、その型チェックを遅延させるロジックが追加されました。これにより、updateExprType
が後で呼び出されたときに、適切な型チェックが行われます。
これらの変更により、Goの型チェッカーは、特に型なし式とシフト演算子に関連する複雑なシナリオにおいて、より正確で堅牢な型推論とエラー検出を実現しています。
関連リンク
- Go言語の仕様: https://go.dev/ref/spec
go/types
パッケージのドキュメント: https://pkg.go.dev/go/types
参考にした情報源リンク
- https://github.com/golang/go/commit/5183bfda7590bb14f3be77fdc1852c6b314c0762
- https://golang.org/cl/7432051 (Go Gerrit Code Review)
- Go言語の型システムに関する一般的な情報 (Web検索)
- Go言語のシフト演算子に関する情報 (Web検索)
- Go言語の型なし定数に関する情報 (Web検索)
- Go言語のコンパイラと型チェッカーの内部動作に関する情報 (Web検索)