[インデックス 14209] ファイルの概要
このコミットは、Go言語の型チェッカーの実験的なステージング領域 (src/pkg/exp/types/staging
) における重要な更新を含んでいます。主な目的は、Go言語の仕様の最近の変更を実装し、レシーバーの型チェックを改善し、同じ位置での重複エラー報告を削減することです。
コミット
commit 7c03cd32b6612f153cf6362ead27e86af6e65336
Author: Robert Griesemer <gri@golang.org>
Date: Mon Oct 22 11:28:21 2012 -0700
exp/type/staging: implemented recent spec changes
Also:
- type-checking receivers
- get rid of some multiple errors at the same position
R=rsc, minux.ma
CC=golang-dev
https://golang.org/cl/6709061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7c03cd32b6612f153cf6362ead27e86af6e65336
元コミット内容
このコミットは、Go言語の型チェッカーの実験的な実装 (exp/types/staging
) に対して行われたもので、以下の主要な変更を含んでいます。
- Go言語の仕様における最近の変更を型チェッカーに反映。
- メソッドのレシーバーの型チェック機能の追加または改善。
- 型チェック中に同じコード位置で複数のエラーが報告される問題を削減。
変更の背景
Go言語は2012年3月にGo 1をリリースし、その仕様は安定性を持つことになりました。しかし、型チェッカーのようなコンパイラの基盤部分は、仕様の厳密な解釈やエッジケースへの対応、そしてより良いエラー報告のために継続的に改善されていました。このコミットは、exp/types/staging
という実験的なブランチで行われていることから、Go 1仕様のリリース後も、型システムの堅牢性と正確性を高めるための継続的な開発の一環として行われたと考えられます。
特に、make
組み込み関数の引数検証の強化(負の値、長さと容量の入れ替わり)、スライスインデックスの定数評価の改善、そしてメソッドレシーバーの型チェックは、言語の正確なセマンティクスを保証し、開発者がより信頼性の高いコードを書けるようにするために不可欠な変更です。また、重複エラーの削減は、コンパイラのエラーメッセージの質を向上させ、開発者のデバッグ体験を改善する上で重要です。
前提知識の解説
- Go言語の型システム: Goは静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。型チェッカーは、プログラムが言語の型規則に準拠しているかを検証するコンパイラの重要な部分です。
make
組み込み関数: Goには、スライス、マップ、チャネルといった組み込み型を初期化・作成するためのmake
関数があります。スライスの場合、make([]T, length, capacity)
のように、長さと容量を指定できます。- スライス: Goのスライスは、配列のセグメントを参照する動的なデータ構造です。スライス式
a[low:high]
やa[low:high:max]
を使用して、既存のスライスや配列から新しいスライスを作成できます。 - 定数式: Goでは、コンパイル時に評価できる式を定数式と呼びます。これには数値リテラル、
const
キーワードで宣言された定数、およびそれらを含む算術演算などが含まれます。型チェッカーは、定数式の値が型の制約(例: 負でないこと、範囲内であること)を満たしているかを検証する必要があります。 - メソッドレシーバー: Goのメソッドは、特定の型に関連付けられた関数です。メソッドを定義する際、
func (r ReceiverType) MethodName(...)
のようにレシーバーを指定します。レシーバーも通常の変数と同様に型チェックの対象となります。 exp/types/staging
: Goの標準ライブラリやツールには、新しい機能や大幅な変更を導入する前に、実験的な実装を置くためのexp
(experimental)ディレクトリが存在します。staging
はその中でもさらに開発中の段階を示すことが多いです。これは、この型チェッカーがまだ開発途上であり、将来的にGoの公式な型チェッカーに統合される可能性のあるコードであることを示唆しています。- 空白識別子
_
: Goでは、変数やインポートされたパッケージが使用されない場合にコンパイルエラーになります。これを回避し、意図的に値を破棄するために空白識別子_
を使用します。
技術的詳細
このコミットは、Go言語の型チェッカーの内部ロジックに複数の重要な変更を加えています。
-
make
組み込み関数の引数検証の強化:src/pkg/exp/types/staging/builtins.go
のbuiltin
関数内で、make
呼び出しの引数(特にスライスの長さと容量)に対する検証がより厳密になりました。- 以前は単に整数であるかを確認していましたが、変更後は引数が定数である場合に、その値が負でないか、そして長さが容量を超えていないか(
length
とcapacity
が入れ替わっていないか)をcompareConst
関数を用いて詳細にチェックするようになりました。これにより、コンパイル時に不正なmake
呼び出しをより早期に検出できるようになります。 - テストデータ (
builtins.src
) には、make([]int, -1, 10)
やmake([]int, 1<<100 + 1, 1<<100)
のような不正な呼び出しに対する新しいエラーメッセージが追加されています。
-
スライスインデックスの定数評価の改善:
src/pkg/exp/types/staging/expr.go
のindex
関数は、スライスインデックスの値を評価します。この関数の戻り値の型がint64
からinterface{}
に変更されました。これは、Goの定数式が任意精度整数をサポートしているため、int64
の範囲を超える大きな定数インデックスも正確に扱えるようにするためです。- インデックスが負であるか、または範囲外であるかのチェックも、
compareConst
関数を使用するように更新され、interface{}
型の定数値を適切に比較できるようになりました。 - スライス式 (
e.Low
,e.High
) の処理においても、lo
とhi
変数がinterface{}
型になり、inverted slice range
エラーのチェックもcompareConst
を使用するように変更されています。
-
メソッドレシーバーの型チェック:
src/pkg/exp/types/staging/check.go
のident
関数内で、関数宣言のレシーバー (fdecl.Recv
) が存在する場合にcheck.collectFields(token.FUNC, fdecl.Recv, true)
が呼び出されるようになりました。これは、メソッドのレシーバーも通常のフィールドやパラメータと同様に型チェッカーによって適切に処理されることを保証します。これにより、レシーバーの型が有効であるか、そのフィールドがアクセス可能であるかなどのチェックが可能になります。
-
重複エラー報告の削減:
src/pkg/exp/types/staging/expr.go
のexpr
およびtyp
関数において、x.mode == invalid
のケースが追加され、既にエラーが報告されている無効な式や型については、それ以上のエラー報告を抑制するようになりました。これにより、同じ根本原因から派生する複数のエラーメッセージがユーザーに表示されることを防ぎ、エラー報告の質が向上します。- テストデータ (
decls0.src
,decls1.src
) から、冗長な/* ERROR "not a type" */
コメントが削除されているのは、この改善の結果です。
-
テストデータの更新:
- 多くのテストファイル (
builtins.src
,decls0.src
,decls1.src
,decls2a.src
,expr3.src
) で、未使用の変数に値を代入する際に_0 := ...
のような形式から_ = ...
へと変更されています。これは、Goの慣用的な書き方であり、型チェッカーが未使用変数を厳密にチェックするようになったことに対応しています。
- 多くのテストファイル (
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は以下のファイルに集中しています。
src/pkg/exp/types/staging/builtins.go
:make
組み込み関数の引数検証ロジックの強化。src/pkg/exp/types/staging/check.go
: メソッドレシーバーの型チェックロジックの追加。src/pkg/exp/types/staging/expr.go
: スライスインデックスの定数評価の改善と、重複エラー報告の削減。
コアとなるコードの解説
src/pkg/exp/types/staging/builtins.go
の変更
// ...
func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) {
// ...
}
+ var sizes []interface{} // constant integer arguments, if any
for _, arg := range args[1:] {
check.expr(x, arg, nil, iota)
- if !x.isInteger() {
+ if x.isInteger() {
+ if x.mode == constant {
+ if isNegConst(x.val) {
+ check.invalidArg(x.pos(), "%s must not be negative", x)
+ // safe to continue
+ } else {
+ sizes = append(sizes, x.val) // x.val >= 0
+ }
+ }
+ } else {
check.invalidArg(x.pos(), "%s must be an integer", x)
// safe to continue
}
}
+ if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) {
+ check.invalidArg(args[1].Pos(), "length and capacity swapped")
+ // safe to continue
+ }
x.mode = variable
x.typ = typ0
// ...
この変更では、make
関数の引数(特にスライスの長さと容量)が整数であるかだけでなく、定数である場合にはその値が負でないか、そして長さが容量より大きくないか(length
とcapacity
が入れ替わっていないか)をcompareConst
関数を使って厳密にチェックしています。これにより、コンパイル時に不正なスライス作成を検出できます。
src/pkg/exp/types/staging/check.go
の変更
// ...
func (check *checker) ident(name *ast.Ident, cycleOk bool) {
// ...
if fdecl.Recv != nil {
- // TODO(gri) handle method receiver
+ // TODO(gri) is this good enough for the receiver?
+ check.collectFields(token.FUNC, fdecl.Recv, true)
}
check.stmt(fdecl.Body)
// ...
fdecl.Recv != nil
のブロック内で check.collectFields
が呼び出されるようになりました。これは、メソッドのレシーバーも型チェッカーによって適切に処理され、その型やフィールドが検証されることを意味します。
src/pkg/exp/types/staging/expr.go
の変更
// ...
func (check *checker) index(index ast.Expr, length int64, iota int) interface{} {
var x operand
- var i int64 // index value, valid if >= 0
check.expr(&x, index, nil, iota)
if !x.isInteger() {
check.errorf(x.pos(), "index %s must be integer", &x)
- return -1
+ return nil
}
if x.mode != constant {
- return -1 // we cannot check more
+ return nil // we cannot check more
}
// x.mode == constant and the index value must be >= 0
if isNegConst(x.val) {
check.errorf(x.pos(), "index %s must not be negative", &x)
- return -1
+ return nil
}
- var ok bool
- if i, ok = x.val.(int64); !ok {
- // index value doesn't fit into an int64
- i = length // trigger out of bounds check below if we know length (>= 0)
- }
-
- if length >= 0 && i >= length {
+ // x.val >= 0
+ if length >= 0 && compareConst(x.val, length, token.GEQ) {
check.errorf(x.pos(), "index %s is out of bounds (>= %d)", &x, length)
- return -1
+ return nil
}
- return i
+ return x.val
}
// ...
func (check *checker) exprOrType(x *operand, e ast.Expr, hint Type, iota int, cy bool) {
// ...
}
- var lo int64
+ var lo interface{} = zeroConst
if e.Low != nil {
lo = check.index(e.Low, length, iota)
}
- var hi int64 = length
+ var hi interface{}
if e.High != nil {
hi = check.index(e.High, length, iota)
+ } else if length >= 0 {
+ hi = length
}
- if hi >= 0 && lo > hi {
- check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi)
+ if lo != nil && hi != nil && compareConst(lo, hi, token.GTR) {
+ check.errorf(e.Low.Pos(), "inverted slice range: %v > %v", lo, hi)
// ok to continue
}
// ...
func (check *checker) expr(x *operand, e ast.Expr, hint Type, iota int) {
check.exprOrType(x, e, hint, iota, false)
switch x.mode {
+ case invalid:
+ // ignore - error reported before
case novalue:
check.errorf(x.pos(), "%s used as value", x)
- \tx.mode = invalid
case typexpr:
check.errorf(x.pos(), "%s is not an expression", x)
- \tx.mode = invalid
+ default:
+ return
}
+ x.mode = invalid
}
// ...
func (check *checker) typ(e ast.Expr, cycleOk bool) Type {
var x operand
check.exprOrType(&x, e, nil, -1, cycleOk)
- switch {
- case x.mode == novalue:
+ switch x.mode {
+ case invalid:
+ // ignore - error reported before
+ case novalue:
check.errorf(x.pos(), "%s used as type", &x)
-\t\tx.typ = Typ[Invalid]
- case x.mode != typexpr:
+\tcase typexpr:
+\t\treturn x.typ
+\tdefault:
check.errorf(x.pos(), "%s is not a type", &x)
-\t\tx.typ = Typ[Invalid]
}
- return x.typ
+ return Typ[Invalid]
}
index
関数の戻り値がinterface{}
になり、任意精度整数を扱えるようになりました。また、compareConst
を使用して定数値を比較することで、より正確な範囲チェックが可能になっています。スライス式においても同様にinterface{}
が使用され、inverted slice range
エラーの報告も改善されています。expr
とtyp
関数では、invalid
モードの追加により、既に報告されたエラーに対する重複報告が抑制され、エラーメッセージの質が向上しています。
関連リンク
- Go言語の仕様: https://go.dev/ref/spec
- Go 1リリースノート (2012年3月): https://go.dev/doc/go1
- Goの組み込み関数: https://go.dev/ref/spec#Predeclared_functions
- Goのスライス: https://go.dev/blog/slices-intro
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
src/go/types
パッケージの進化に関する情報) - Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://go-review.googlesource.com/ (コミットメッセージに記載されているCLリンク
https://golang.org/cl/6709061
は、Gerrit上の変更セットを指します。) - Go言語のメーリングリストやフォーラム(過去の議論から仕様変更の背景を推測)
- Go言語の空白識別子に関する公式ブログ: https://go.dev/doc/effective_go#blank
- Go言語の定数に関する公式ドキュメント: https://go.dev/ref/spec#Constants
- Go言語のメソッドに関する公式ドキュメント: https://go.dev/ref/spec#Method_declarations