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

[インデックス 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 + bf(x)1 << s など。
  • オペランド (Operand): 演算子によって操作される値です。例えば、a + b における ab
  • 型推論 (Type Inference): プログラマが明示的に型を指定しなくても、コンパイラが文脈から自動的に型を決定する機能です。Go言語では、変数宣言や一部の式で型推論が利用されます。
  • Untyped Expressions (型なし式): Go言語には、特定の数値リテラル(例: 103.141i)や、それらを含む演算の結果など、初期段階では具体的なGoの型を持たない「型なし」の概念があります。これらは、最終的に使用される文脈(変数への代入、関数呼び出しの引数など)に応じて、適切なGoの型に「型付け」されます。例えば、var x float64 = 1010 は最初は型なし整数ですが、float64 に型付けされます。
  • シフト演算子 (Shift Operators): << (左シフト) と >> (右シフト) があります。x << yxy ビット左にシフトします。Go言語の仕様では、シフト演算子の左オペランドは整数型である必要があり、右オペランドは符号なし整数型であるか、符号なし整数型に変換可能な型なし定数である必要があります。
  • 定数式 (Constant Expression): コンパイル時に値が決定される式です。型なし定数は、定数式の一種です。
  • go/astパッケージ: Goの抽象構文木 (AST) を表現するためのパッケージです。ソースコードはまずASTにパースされ、その後型チェックなどの処理が行われます。
  • go/tokenパッケージ: Goのトークン(キーワード、識別子、演算子など)とソースコード上の位置情報を扱うパッケージです。

技術的詳細

このコミットの核心は、go/typesパッケージ内の型チェックロジック、特にchecker構造体とupdateExprTypeメソッドの変更にあります。

  1. exprInfo構造体の導入とuntypedマップの変更:

    • 以前は、型なし式とその型を格納するuntypedマップと、型なし定数式とその値を格納するconstantsマップが別々に存在していました。
    • このコミットでは、exprInfoという新しい構造体が導入され、isConst(定数であるか)、isLhs(シフト演算子の左オペランドであるか)、typ(型)、val(定数値)をまとめて保持するようになりました。
    • untypedマップはmap[ast.Expr]exprInfo型に変更され、型なし式のすべての関連情報が一元的に管理されるようになりました。これにより、コードの複雑性が軽減され、型なし式の状態管理がより明確になりました。
    • 特にisLhsフィールドは、シフト演算子の左オペランドの型チェックを遅延させるための重要なフラグです。
  2. updateExprTypeメソッドの改善:

    • このメソッドは、型なし式が最終的な型を持つようになったときに呼び出され、その型を式ツリーに伝播させます。
    • 変更前は、shiftOpというブール引数でシフト演算子の左オペランドであるかを判定していましたが、新しいexprInfo.isLhsフラグを使用することで、より汎用的なメカニズムになりました。
    • 比較演算子 (isComparison) やシフト演算子 (isShift) のように、結果の型がオペランドの型に直接依存しない式の場合、オペランドの型を早期に確定させるようになりました。これにより、例えば 1 << s == 1.0 のような式で、1 << s の結果が比較の文脈で int に型付けされる前に、1int に型付けされるようになります。
    • 定数式の場合、オペランドの型更新は不要になりました。これは、定数オペランドは最終的な型に「具現化」される必要がなく、型チェックの最後にまとめて処理されるためです。
    • Context.Exprコールバックの呼び出しロジックが改善され、式が最終的な型を持つようになったときにのみ呼び出されるようになりました。
  3. シフト演算子の型チェックの改善:

    • シフト演算子 (shift関数) のロジックが大幅に修正されました。
    • 左オペランドが整数型であるか、整数型に変換可能な型なし定数であるかのチェックが早期に行われるようになりました。
    • 右オペランドが符号なし整数型であるか、符号なし整数型に変換可能な型なし定数であるかのチェックも強化されました。
    • 非定数シフトの左オペランドが型なし定数である場合、その型チェックを遅延させるメカニズムがexprInfo.isLhsフラグを使って実装されました。これにより、シフト演算子の結果が使用される文脈で、左オペランドが適切な整数型に型付けされることが保証されます。
  4. 定数式のサイクル検出:

    • 定数式における循環参照の検出メカニズムが、変数と同様にConstant.visitedフィールドを使用して実装されました。これにより、無限ループやスタックオーバーフローを防ぎます。
  5. index関数の改善:

    • index関数(配列やスライスのインデックス、make関数の引数などで使用される)が、引数の型チェックをより厳密に行うように変更されました。
    • 型なし定数がInt型に変換可能であるかをチェックし、インデックスが負でないこと、および境界内にあることを確認します。
  6. テストの拡充:

    • go/types/testdata/shifts.srcという新しいテストファイルが追加され、シフト演算子に関する網羅的なテストケースが多数追加されました。
    • builtins.srcexpr3.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: complexmake組み込み関数のテストケースの追加・修正。
  • 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にあります。

  1. 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フィールドは、シフト演算子の左オペランドの特殊な型チェックロジックを管理するために重要です。

  2. checker構造体のuntypedフィールド:

    // 変更前:
    // untyped   map[ast.Expr]*Basic
    // constants map[ast.Expr]interface{}
    // shiftOps  map[ast.Expr]bool
    
    // 変更後:
    untyped     map[ast.Expr]exprInfo
    

    これにより、型なし式の型、定数かどうか、定数値、そしてシフト演算子の左オペランドであるかどうかの情報が、単一のマップエントリで管理されるようになりました。

  3. 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コールバックを通じてクライアントに通知されます。
  4. 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の型チェッカーは、特に型なし式とシフト演算子に関連する複雑なシナリオにおいて、より正確で堅牢な型推論とエラー検出を実現しています。

関連リンク

参考にした情報源リンク