[インデックス 14522] ファイルの概要
このコミットは、Go言語の実験的な型チェッカー (exp/types
) における、配列およびスライス複合リテラルのチェックの改善、特にインデックスの検証と [...]T
構文のサポート、そして go
および defer
ステートメントの型チェックに関する修正を目的としています。
コミット
commit 521f11de6b2b4a5d1c443cc88547e4d9ec4731ed
Author: Robert Griesemer <gri@golang.org>
Date: Thu Nov 29 09:57:37 2012 -0800
exp/types: various missing checks for array/slice composite literals
- check indices of array/slice composite literals
- handle [...]T
- also: go/defer statements
R=rsc
CC=golang-dev
https://golang.org/cl/6856107
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/521f11de6b2b4a5d1c443cc88547e4d9ec4731ed
元コミット内容
exp/types: various missing checks for array/slice composite literals
- 配列/スライス複合リテラルのインデックスをチェックする
[...]T
を処理するgo
/defer
ステートメントも対象
変更の背景
Go言語の型システムにおいて、配列やスライスの複合リテラル([]int{1, 2, 3}
や [3]string{"a", "b", "c"}
のような初期化構文)は、その要素のインデックスが正しい範囲内にあるか、重複していないか、また配列の長さが明示されていない [...]T
形式の場合に、その長さが正しく推論されるかといった厳密なチェックが必要でした。このコミット以前は、これらのチェックが不十分であったため、コンパイル時に検出されるべきエラーが見過ごされる可能性がありました。
また、go
ステートメント(ゴルーチンの起動)と defer
ステートメント(関数の終了時に実行される処理の登録)は、引数として関数呼び出しを期待しますが、型チェッカーがその引数が実際に有効な関数呼び出しであるかを適切に検証していませんでした。これらの不足しているチェックを追加することで、型チェッカーの堅牢性を高め、より早期に開発者にエラーを通知できるようになります。
前提知識の解説
Go言語の型システムと型チェッカー
Go言語は静的型付け言語であり、プログラムの実行前に型の一貫性を検証する型チェッカーが重要な役割を果たします。exp/types
は、Goコンパイラの一部として、この型チェックロジックを実装しているパッケージです。型チェッカーは、構文解析されたAST (Abstract Syntax Tree) を走査し、各式の型を決定し、型規則に違反がないかを確認します。
複合リテラル (Composite Literals)
複合リテラルは、構造体、配列、スライス、マップなどの複合型の値を初期化するための構文です。
- 配列複合リテラル:
[N]T{...}
または[...]T{...}
の形式で、固定長の配列を初期化します。[...]T
は、リテラルの要素数から配列の長さを推論させる特殊な構文です。 - スライス複合リテラル:
[]T{...}
の形式で、可変長のスライスを初期化します。
複合リテラルでは、要素を 値
または キー:値
の形式で指定できます。キー
は配列やスライスの場合、インデックスを指します。
go
ステートメントと defer
ステートメント
go
ステートメント:go 関数呼び出し
の形式で、新しいゴルーチン(軽量スレッド)を起動し、指定された関数呼び出しを並行して実行します。defer
ステートメント:defer 関数呼び出し
の形式で、現在の関数がリターンする直前に、指定された関数呼び出しを実行するようにスケジュールします。主にリソースの解放(ファイルのクローズ、ロックの解除など)に使用されます。
これらのステートメントは、引数として必ず関数呼び出しを必要とします。
技術的詳細
このコミットは、主に src/pkg/exp/types/expr.go
と src/pkg/exp/types/stmt.go
の2つのファイルに大きな変更を加えています。
src/pkg/exp/types/expr.go
の変更点
-
index
関数の改善:- 以前は
index
関数がinterface{}
を返していましたが、int64
を返すように変更されました。これにより、インデックスが常に整数値として扱われることが保証され、型安全性が向上します。 - インデックスが負の値である場合のチェックが強化されました。
- インデックスが配列/スライスの長さを超える場合のチェックが強化されました。特に、
x.val.(int64)
の型アサーションが追加され、インデックスがint64
に変換可能であることを確認しています。 stupid index
というエラーメッセージが追加され、無効なインデックス式に対するより具体的なエラー報告が可能になりました。
- 以前は
-
indexedElts
関数の新規追加:- この関数は、配列またはスライス複合リテラルの要素 (
elts
) をチェックするために導入されました。 - インデックスの検証:
kv.Key
(キー付き要素の場合) をcheck.index
関数で検証し、有効なインデックスであることを確認します。 - 重複インデックスの検出:
visited
マップを使用して、複合リテラル内で同じインデックスが複数回使用されていないかをチェックします。重複が検出された場合、duplicate index
エラーを報告します。 - 要素の型チェック: 各要素が複合リテラルの要素型 (
typ
) に割り当て可能であるかをx.isAssignable(typ)
でチェックします。 - 長さの計算: 複合リテラルの最大インデックスに基づいて、リテラルの実際の長さ (
max
) を計算して返します。これは、特に[...]T
形式の配列の長さを決定するために重要です。
- この関数は、配列またはスライス複合リテラルの要素 (
-
ast.Ellipsis
の扱い:rawExpr
関数内で、ast.Ellipsis
(Goの...
構文) が不正に使用された場合にエラーを報告するようになりました。以前はunimplemented()
でした。[...]T
形式の配列型が複合リテラルでのみ合法的に使用されることを考慮し、ast.ArrayType
の処理においてellip.Elt == nil
の条件で「オープンな[...]T
配列型」を識別し、その長さを-1
として初期化するロジックが追加されました。この長さは、indexedElts
関数によって実際の要素数に基づいて後で設定されます。
-
複合リテラル (
ast.CompositeLit
) の処理改善:- 配列 (
*Array
) およびスライス (*Slice
) の複合リテラル処理が大幅に簡素化され、新しく導入されたcheck.indexedElts
関数を使用するように変更されました。これにより、インデックスチェック、重複チェック、要素の型チェックが一元的に行われるようになりました。 - 構造体リテラルにおける要素数のチェック (
too many values in struct literal
,too few values in struct literal
) のエラー処理が改善され、エラー発生後もチェックを継続できるようになりました。
- 配列 (
-
スライス式 (
ast.SliceExpr
) のインデックスチェック:- スライス式の
Low
およびHigh
インデックスのチェックが強化されました。 compareConst
の代わりに直接int64
の比較を行うようになり、より明確になりました。inverted slice range
エラーの報告が改善されました。
- スライス式の
-
配列型 (
ast.ArrayType
) の長さチェック:- 配列の長さが定数であること、および非負であることのチェックが強化されました。
[...]T
形式の配列の長さが、複合リテラルの要素数から推論されるロジックが追加されました。
src/pkg/exp/types/stmt.go
の変更点
-
call
関数の新規追加:- このヘルパー関数は、
go
およびdefer
ステートメントの引数が有効な関数呼び出しであるかを検証するために導入されました。 ast.CallExpr
ではない場合にinvalidAST
エラーを報告します。check.rawExpr
を呼び出して、関数呼び出しの型チェックを行います。
- このヘルパー関数は、
-
go
およびdefer
ステートメントの型チェック:ast.GoStmt
とast.DeferStmt
の処理がunimplemented()
からcheck.call(s.Call)
を呼び出すように変更されました。これにより、これらのステートメントの引数が実際に有効な関数呼び出しであるかどうかが型チェッカーによって検証されるようになりました。
テストデータの変更点
src/pkg/exp/types/testdata/decls0.src
: 無効な配列型定義(例:[... /* ERROR "invalid use of '...'"
]byte`)に関するテストケースが追加されました。src/pkg/exp/types/testdata/expr3.src
:- シフト演算に関するテストが関数内に移動され、より整理されました。
- 配列およびスライス複合リテラルのインデックスチェック、重複インデックス、範囲外インデックス、
[...]T
の推論に関する多数の新しいテストケースが追加されました。これには、stupid index
やduplicate index
などの新しいエラーメッセージをトリガーするケースも含まれます。
src/pkg/exp/types/testdata/stmt0.src
:go
およびdefer
ステートメントが関数呼び出しを期待するが、そうでない場合にエラーを報告するテストケースが追加されました(例:go 1 /* ERROR "expected function/method call" */
)。
これらの変更により、Go言語の型チェッカーは、複合リテラルと go
/defer
ステートメントに関するより厳密で正確な型チェックを実行できるようになり、開発者がより堅牢なコードを記述するのに役立ちます。
コアとなるコードの変更箇所
src/pkg/exp/types/expr.go
func (check *checker) index(...)
のシグネチャと実装の変更。func (check *checker) indexedElts(...)
の新規追加。case *ast.Ellipsis:
ブロックの変更。case *ast.CompositeLit:
ブロック内の配列 (*Array
) およびスライス (*Slice
) の処理ロジックの変更。特にcheck.indexedElts
の呼び出し。case *ast.ArrayType:
ブロック内の配列の長さに関するチェックの変更。case *ast.SliceExpr:
ブロック内のスライス範囲チェックの変更。
src/pkg/exp/types/stmt.go
func (check *checker) call(...)
の新規追加。case *ast.GoStmt:
およびcase *ast.DeferStmt:
ブロックの変更。
コアとなるコードの解説
src/pkg/exp/types/expr.go
index
関数の変更
// 変更前
// func (check *checker) index(index ast.Expr, length int64, iota int) interface{} {
// 変更後
func (check *checker) index(index ast.Expr, length int64, iota int) int64 {
// ...
// 以前は x.val.(int64) のチェックが不十分だった
i, ok := x.val.(int64)
if !ok {
check.errorf(x.pos(), "stupid index %s", &x) // 新しいエラーメッセージ
return -1
}
if i < 0 {
check.errorf(x.pos(), "index %s must not be negative", &x)
return -1
}
if length >= 0 && i >= length {
check.errorf(x.pos(), "index %s is out of bounds (>= %d)", &x, length)
return -1
}
return i
}
この変更により、index
関数はインデックスが int64
型であることを厳密に保証し、負の値や範囲外のインデックスに対してより正確なエラーを報告するようになりました。特に stupid index
というエラーは、インデックスが整数定数として解釈できない場合に発生します。
indexedElts
関数の新規追加
func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota int) int64 {
visited := make(map[int64]bool, len(elts)) // 重複インデックス検出用
var index, max int64
for _, e := range elts {
validIndex := false
eval := e
if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
// キー付き要素の場合、キーをインデックスとしてチェック
if i := check.index(kv.Key, length, iota); i >= 0 {
index = i
validIndex = true
}
eval = kv.Value
} else if length >= 0 && index >= length {
// キーなし要素で、かつ配列の長さが既知の場合の範囲チェック
check.errorf(e.Pos(), "index %d is out of bounds (>= %d)", index, length)
} else {
validIndex = true // キーなし要素は順次インデックスが振られるため、基本的には有効
}
if validIndex {
if visited[index] {
check.errorf(e.Pos(), "duplicate index %d in array or slice literal", index) // 重複インデックスエラー
}
visited[index] = true
}
index++ // 次の要素のインデックスをインクリメント
if index > max {
max = index // 最大インデックスを更新
}
// 要素の型チェック
var x operand
check.expr(&x, eval, typ, iota)
if !x.isAssignable(typ) {
check.errorf(x.pos(), "cannot use %s as %s value in array or slice literal", &x, typ)
}
}
return max // 複合リテラルの実際の長さを返す
}
この関数は、配列やスライスの複合リテラルの型チェックの中心的なロジックをカプセル化しています。インデックスの妥当性、重複、そして要素の型の一貫性を効率的に検証します。特に、[...]T
形式の配列の最終的な長さを決定する上で重要な役割を果たします。
複合リテラル (ast.CompositeLit
) の処理
case *ast.CompositeLit:
// ...
switch utyp := underlying(deref(typ)).(type) {
case *Array:
// 以前の冗長なインデックスチェックロジックを削除し、indexedElts を呼び出す
n := check.indexedElts(e.Elts, utyp.Elt, utyp.Len, iota)
if openArray { // [...]T 配列の場合、ここで長さを設定
utyp.Len = n
}
case *Slice:
// 以前の冗長なインデックスチェックロジックを削除し、indexedElts を呼び出す
check.indexedElts(e.Elts, utyp.Elt, -1, iota) // スライスは長さが不定なので -1
// ...
}
この変更により、配列とスライスの複合リテラルの型チェックロジックが大幅に簡素化され、indexedElts
関数に処理が委譲されることで、コードの重複が排除され、保守性が向上しました。
src/pkg/exp/types/stmt.go
call
関数の新規追加
func (check *checker) call(c ast.Expr) {
call, _ := c.(*ast.CallExpr)
if call == nil {
// go/defer の引数が関数呼び出しでない場合にエラー
check.invalidAST(c.Pos(), "%s is not a function call", c)
return
}
var x operand
check.rawExpr(&x, call, nil, -1, false) // 関数呼び出しの型チェック
// TODO(gri) If a builtin is called, the builtin must be valid in statement
// context. However, the spec doesn't say that explicitly.
}
この関数は、go
および defer
ステートメントの引数が ast.CallExpr
(関数呼び出しのASTノード) であることを保証し、その関数呼び出し自体の型チェックを行います。
go
および defer
ステートメントの型チェック
case *ast.GoStmt:
// 以前は unimplemented()
check.call(s.Call)
case *ast.DeferStmt:
// 以前は unimplemented()
check.call(s.Call)
この変更により、go
と defer
ステートメントの引数に対する型チェックが有効になり、コンパイル時に不正な引数(関数呼び出しではないもの)が使用された場合にエラーが報告されるようになりました。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのGo CL (Change List): https://golang.org/cl/6856107
参考にした情報源リンク
- Go言語の仕様 (The Go Programming Language Specification): 特に「Composite literals」と「Go statements」「Defer statements」のセクション。
- Go言語のASTパッケージ (
go/ast
): Goのソースコードの抽象構文木を表現するための構造体定義。 - Go言語のトークンパッケージ (
go/token
): Goの言語トークン定義。 - Go言語の型パッケージ (
go/types
): Goの型システムを表現するためのパッケージ。exp/types
はこれの実験的な前身。