Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.gosrc/pkg/exp/types/stmt.go の2つのファイルに大きな変更を加えています。

src/pkg/exp/types/expr.go の変更点

  1. index 関数の改善:

    • 以前は index 関数が interface{} を返していましたが、int64 を返すように変更されました。これにより、インデックスが常に整数値として扱われることが保証され、型安全性が向上します。
    • インデックスが負の値である場合のチェックが強化されました。
    • インデックスが配列/スライスの長さを超える場合のチェックが強化されました。特に、x.val.(int64) の型アサーションが追加され、インデックスが int64 に変換可能であることを確認しています。
    • stupid index というエラーメッセージが追加され、無効なインデックス式に対するより具体的なエラー報告が可能になりました。
  2. indexedElts 関数の新規追加:

    • この関数は、配列またはスライス複合リテラルの要素 (elts) をチェックするために導入されました。
    • インデックスの検証: kv.Key (キー付き要素の場合) を check.index 関数で検証し、有効なインデックスであることを確認します。
    • 重複インデックスの検出: visited マップを使用して、複合リテラル内で同じインデックスが複数回使用されていないかをチェックします。重複が検出された場合、duplicate index エラーを報告します。
    • 要素の型チェック: 各要素が複合リテラルの要素型 (typ) に割り当て可能であるかを x.isAssignable(typ) でチェックします。
    • 長さの計算: 複合リテラルの最大インデックスに基づいて、リテラルの実際の長さ (max) を計算して返します。これは、特に [...]T 形式の配列の長さを決定するために重要です。
  3. ast.Ellipsis の扱い:

    • rawExpr 関数内で、ast.Ellipsis (Goの ... 構文) が不正に使用された場合にエラーを報告するようになりました。以前は unimplemented() でした。
    • [...]T 形式の配列型が複合リテラルでのみ合法的に使用されることを考慮し、ast.ArrayType の処理において ellip.Elt == nil の条件で「オープンな [...]T 配列型」を識別し、その長さを -1 として初期化するロジックが追加されました。この長さは、indexedElts 関数によって実際の要素数に基づいて後で設定されます。
  4. 複合リテラル (ast.CompositeLit) の処理改善:

    • 配列 (*Array) およびスライス (*Slice) の複合リテラル処理が大幅に簡素化され、新しく導入された check.indexedElts 関数を使用するように変更されました。これにより、インデックスチェック、重複チェック、要素の型チェックが一元的に行われるようになりました。
    • 構造体リテラルにおける要素数のチェック (too many values in struct literal, too few values in struct literal) のエラー処理が改善され、エラー発生後もチェックを継続できるようになりました。
  5. スライス式 (ast.SliceExpr) のインデックスチェック:

    • スライス式の Low および High インデックスのチェックが強化されました。
    • compareConst の代わりに直接 int64 の比較を行うようになり、より明確になりました。
    • inverted slice range エラーの報告が改善されました。
  6. 配列型 (ast.ArrayType) の長さチェック:

    • 配列の長さが定数であること、および非負であることのチェックが強化されました。
    • [...]T 形式の配列の長さが、複合リテラルの要素数から推論されるロジックが追加されました。

src/pkg/exp/types/stmt.go の変更点

  1. call 関数の新規追加:

    • このヘルパー関数は、go および defer ステートメントの引数が有効な関数呼び出しであるかを検証するために導入されました。
    • ast.CallExpr ではない場合に invalidAST エラーを報告します。
    • check.rawExpr を呼び出して、関数呼び出しの型チェックを行います。
  2. go および defer ステートメントの型チェック:

    • ast.GoStmtast.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 indexduplicate 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)

この変更により、godefer ステートメントの引数に対する型チェックが有効になり、コンパイル時に不正な引数(関数呼び出しではないもの)が使用された場合にエラーが報告されるようになりました。

関連リンク

参考にした情報源リンク