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

[インデックス 14613] ファイルの概要

このコミットは、Go言語の実験的な型チェッカーパッケージである exp/types において、不足していた機能とチェック機構を補完するものです。具体的には、組み込み関数 complex() の実装と、式スイッチ(expression switch)における網羅的なチェック機能の追加が主要な変更点です。これにより、型チェッカーの堅牢性とGo言語の仕様への準拠が向上しています。

コミット

commit 1a6f8dcbaf123bcf25f5ebebebc481326a5f806a
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Dec 11 10:17:33 2012 -0800

    exp/types: filling in more blanks
    
    - implemented built-in complex()
    - implemented missing expression switch checks
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6920046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1a6f8dcbaf123bcf25f5ebebebc481326a5f806a

元コミット内容

exp/types: filling in more blanks

- implemented built-in complex()
- implemented missing expression switch checks

R=rsc
CC=golang-dev
https://golang.org/cl/6920046

変更の背景

Go言語の exp/types パッケージは、Goコンパイラの型チェック部分を再設計・実験するためのものでした。このパッケージがGo言語の完全な型システムを正確に反映するためには、すべての組み込み関数と制御フロー構造が適切に処理される必要がありました。

このコミットが行われた2012年当時、complex() 組み込み関数はまだ exp/types パッケージ内で完全に実装されておらず、そのために encoding/binarymath/cmplx といった標準ライブラリの一部が gotype_test.go でテスト対象から除外されていました。complex() の実装は、Go言語が提供する複素数型(complex64, complex128)を完全にサポートするために不可欠でした。

また、switch ステートメント、特に式スイッチ(expression switch)においては、ケース値の型チェック、比較可能性、そして重複するケース値の検出といった重要な検証が不足していました。これらのチェックは、コンパイル時に論理的な誤りを捕捉し、開発者がより堅牢なコードを書く上で極めて重要です。このコミットは、これらの「空白」を埋め、exp/types パッケージがGo言語の仕様にさらに厳密に準拠するようにすることを目的としています。

前提知識の解説

Go言語の型システムと組み込み型

Go言語は静的型付け言語であり、変数は使用前に型が宣言されます。このコミットに関連する主要な型は以下の通りです。

  • 浮動小数点型: float32 (単精度浮動小数点数), float64 (倍精度浮動小数点数)。
  • 複素数型: complex64 (実部と虚部が float32 の複素数), complex128 (実部と虚部が float64 の複素数)。
  • 型なし定数: Go言語には、型が明示的に指定されていない数値定数があります。これらは UntypedInt, UntypedRune, UntypedFloat, UntypedComplex などと呼ばれ、文脈に応じて適切な型に変換されます。例えば、1UntypedInt であり、1.0UntypedFloat です。これらは、より広い範囲の型に代入できる柔軟性を提供します。

complex() 組み込み関数

complex() はGo言語の組み込み関数で、2つの浮動小数点数(または型なし数値定数)を引数に取り、複素数型の値を生成します。 構文は complex(realPart, imagPart) です。 引数の型によって、戻り値の型は complex64 または complex128 になります。例えば、complex(1.0, 2.0)complex128 を返します。

switch ステートメント

Go言語の switch ステートメントには主に2つの形式があります。

  1. 式スイッチ (Expression Switch): switch キーワードの後に式が続き、その式の値が各 case 節の値と比較されます。
    switch x {
    case 1:
        // ...
    case 2, 3:
        // ...
    default:
        // ...
    }
    
    switch 式が省略された場合(switch { ... })、それは switch true { ... } と同等に扱われ、各 case 節の式がブール値として評価されます。
  2. 型スイッチ (Type Switch): switch キーワードの後に型アサーションが続き、インターフェース変数の動的な型が各 case 節の型と比較されます。このコミットの変更は式スイッチに焦点を当てています。

式スイッチにおける重要な概念:

  • 比較可能性: switch 式の値と case 節の値は比較可能でなければなりません。Go言語では、すべての型が比較可能ではありません(例: スライス、マップ、関数は比較不可)。
  • 定数式: case 節の式は通常、コンパイル時に評価可能な定数式である必要があります。
  • 重複ケース: 同じ値を持つ case 節が複数存在することは許可されません。

exp/types パッケージ

exp/types は、Go言語のコンパイラにおける型チェックのロジックを実験的に実装したパッケージです。これは、Go言語の進化に伴う型システムの変更や改善を、本番のコンパイラに統合する前に試行するためのサンドボックスのような役割を果たしていました。このパッケージの目標は、Go言語の仕様に完全に準拠した、正確で効率的な型チェッカーを提供することでした。

技術的詳細

complex() 組み込み関数の実装 (src/pkg/exp/types/builtins.go)

このコミットでは、checker 構造体の builtin メソッド内の _Complex ケースが大幅に拡張されました。

  1. 引数の型チェック:

    • check.complexArg(x) および check.complexArg(&y) が導入され、complex() の引数が float32, float64, または型なしの非複素数数値定数(UntypedInt, UntypedRune, UntypedFloat)であることを検証します。これ以外の型(例: bool, int32, string, complex64 など)が渡された場合はエラーとなります。
    • complexArg 関数は、引数の基底型が浮動小数点型であるか、型なしの整数/ルーン型であるかをチェックします。
  2. 型変換と整合性:

    • check.convertUntyped(x, y.typ)check.convertUntyped(&y, x.typ) を使用して、両方の引数(実部と虚部)が同じ型になるように型なし定数を変換します。これにより、complex(1, 2.0) のような呼び出しで、1float64 に変換され、両方の引数が float64 に揃えられます。
    • isIdentical(x.typ, y.typ) で、変換後の実部と虚部の型が一致していることを確認します。complex(float32(1), float64(2)) のような型不一致はエラーとなります。
  3. 定数畳み込み:

    • 両方の引数が定数である場合(x.mode == constant && y.mode == constant)、コンパイル時に複素数定数を計算します。
    • binaryOpConst(x.val, toImagConst(y.val), token.ADD, false) を使用して、実部と虚部を結合します。ここで toImagConst は、非複素数定数を虚数部として扱うためのヘルパー関数です。
  4. 結果の型推論:

    • 実部または虚部の型に基づいて、結果の複素数型を決定します。
      • Float32 -> Complex64
      • Float64 -> Complex128
      • UntypedInt, UntypedRune, UntypedFloat -> UntypedComplex
    • これにより、complex(1, 2) のような型なし定数から UntypedComplex が生成され、文脈に応じて complex64 または complex128 に変換される柔軟性が提供されます。

虚数定数への変換ヘルパー (src/pkg/exp/types/const.go)

toImagConst 関数が追加されました。この関数は、非複素数型の定数 x を受け取り、複素数 0 + xi に相当する定数表現を返します。これは complex() 組み込み関数が虚数部を処理する際に使用されます。int64, *big.Int, *big.Rat の各型に対応しています。

式スイッチのチェック機能の強化 (src/pkg/exp/types/stmt.go)

checker 構造体の stmt メソッド内の *ast.SwitchStmt ケースが大幅に改善されました。

  1. switch 式のデフォルト値:

    • switch ステートメントに式(タグ)が指定されていない場合(s.Tag == nil)、Goの仕様に従い、暗黙的に true がタグとして扱われます。このコミットでは、ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")} を生成し、true を明示的なタグとして扱います。これにより、case 節の式がブール値として評価されるようになります。
  2. 重複ケース値の検出:

    • seen := make(map[interface{}]token.Pos) マップが導入され、各 case 節の定数値を記録します。
    • y.mode == constant の場合、seen[y.val] をチェックして、同じ値が以前に出現していないかを確認します。
    • 重複が検出された場合、check.errorf を使用してエラーを報告します。
    • 注意点: コミットメッセージの TODO(gri) にあるように、この重複検出は int64 に収まらない大きな整数値、浮動小数点数、複素数値に対しては正確に機能しない可能性があります。これは、map のキーとして使用される interface{} の比較が、これらの複雑な値の深い比較を保証しないためです。
  3. case 式の型チェックと比較可能性:

    • case 節の式 expr に対して check.expr(&y, expr, nil, -1) を呼び出し、その型と値を評価します。
    • check.convertUntyped(&y, x.typ)check.convertUntyped(&x, y.typ) を使用して、switch 式の型 (x.typ) と case 式の型 (y.typ) を互いに変換可能であることを確認し、必要に応じて型なし定数を変換します。
    • check.comparison(&y, &x, token.EQL) を呼び出し、switch 式の値と case 式の値が比較可能であること(== 演算子が適用可能であること)を検証します。これにより、比較不可能な型(例: スライス)が case 値として使用された場合にエラーが報告されます。

これらの変更により、exp/types パッケージはGo言語の switch ステートメントのセマンティクスをより正確にモデル化し、コンパイル時のエラー検出能力を向上させました。

コアとなるコードの変更箇所

src/pkg/exp/types/builtins.go

builtin メソッド内の case _Complex: ブロック全体と、新しく追加された complexArg 関数。

// func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) { ... }
case _Complex:
    if !check.complexArg(x) {
        goto Error
    }

    var y operand
    check.expr(&y, args[1], nil, iota)
    if y.mode == invalid {
        goto Error
    }
    if !check.complexArg(&y) {
        goto Error
    }

    check.convertUntyped(x, y.typ)
    if x.mode == invalid {
        goto Error
    }
    check.convertUntyped(&y, x.typ)
    if y.mode == invalid {
        goto Error
    }

    if !isIdentical(x.typ, y.typ) {
        check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ)
        goto Error
    }

    if x.mode == constant && y.mode == constant {
        x.val = binaryOpConst(x.val, toImagConst(y.val), token.ADD, false)
    } else {
        x.mode = value
    }

    switch underlying(x.typ).(*Basic).Kind {
    case Float32:
        x.typ = Typ[Complex64]
    case Float64:
        x.typ = Typ[Complex128]
    case UntypedInt, UntypedRune, UntypedFloat:
        x.typ = Typ[UntypedComplex]
    default:
        check.invalidArg(x.pos(), "float32 or float64 arguments expected")
        goto Error
    }

// ...

func (check *checker) complexArg(x *operand) bool {
    t, _ := underlying(x.typ).(*Basic)
    if t != nil && (t.Info&IsFloat != 0 || t.Kind == UntypedInt || t.Kind == UntypedRune) {
        return true
    }
    check.invalidArg(x.pos(), "%s must be a float32, float64, or an untyped non-complex numeric constant", x)
    return false
}

src/pkg/exp/types/const.go

新しく追加された toImagConst 関数。

// toImagConst returns the constant complex(0, x) for a non-complex x.
func toImagConst(x interface{}) interface{} {
    var im *big.Rat
    switch x := x.(type) {
    case int64:
        im = big.NewRat(x, 1)
    case *big.Int:
        im = new(big.Rat).SetFrac(x, int1)
    case *big.Rat:
        im = x
    default:
        unreachable()
    }
    return complex{rat0, im}
}

src/pkg/exp/types/stmt.go

stmt メソッド内の case *ast.SwitchStmt: ブロック。

// func (check *checker) stmt(s ast.Stmt) { ... }
case *ast.SwitchStmt:
    check.optionalStmt(s.Init)
    var x operand
    tag := s.Tag
    if tag == nil {
        // create true tag value and position it at the opening { of the switch
        tag = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")}
    }
    check.expr(&x, tag, nil, -1)

    check.multipleDefaults(s.Body.List)
    seen := make(map[interface{}]token.Pos) // Added for duplicate case detection
    for _, s := range s.Body.List {
        clause, _ := s.(*ast.CaseClause)
        if clause == nil {
            continue // error reported before
        }
        if x.mode != invalid { // Only proceed if switch expression is valid
            for _, expr := range clause.List {
                x := x // copy of x (don't modify original)
                var y operand
                check.expr(&y, expr, nil, -1)
                if y.mode == invalid {
                    continue // error reported before
                }
                // If we have a constant case value, it must appear only
                // once in the switch statement. Determine if there is a
                // duplicate entry, but only report an error there are no
                // other errors.
                var dupl token.Pos
                if y.mode == constant {
                    // TODO(gri) This code doesn't work correctly for
                    //           large integer, floating point, or
                    //           complex values - the respective struct
                    //           comparison is shallow. Need to use a
                    //           has function to index the seen map.
                    dupl = seen[y.val]
                    seen[y.val] = y.pos()
                }
                // TODO(gri) The convertUntyped call pair below appears in other places. Factor!
                // Order matters: By comparing y against x, error positions are at the case values.
                check.convertUntyped(&y, x.typ)
                if y.mode == invalid {
                    continue // error reported before
                }
                check.convertUntyped(&x, y.typ)
                if x.mode == invalid {
                    continue // error reported before
                }
                check.comparison(&y, &x, token.EQL) // Check comparability
                if y.mode != invalid && dupl.IsValid() {
                    check.errorf(y.pos(), "%s is duplicate case in switch\n\tprevious case at %s",
                        &y, check.fset.Position(dupl))
                }
            }
        }
        check.stmtList(clause.Body)
    }

コアとなるコードの解説

src/pkg/exp/types/builtins.go における complex() の実装

builtin メソッドは、Goの組み込み関数呼び出しを型チェックする役割を担っています。_Complex ケースは complex() 関数に対応します。

  1. check.complexArg(x): この呼び出しは、complex() の実部と虚部の引数が有効な型(float32, float64, または型なしの数値定数)であるかを検証します。無効な型であれば、goto Error でエラー処理にジャンプします。
  2. 型変換と同一性チェック: convertUntyped は、型なし定数を他の引数の型に合わせるために使用されます。例えば、complex(1, 2.0) の場合、1UntypedInt ですが、2.0UntypedFloat であるため、1UntypedFloat に変換されます。isIdentical は、変換後の実部と虚部の型が完全に一致していることを保証します。
  3. 定数畳み込み: 両方の引数がコンパイル時定数である場合、binaryOpConsttoImagConst を用いて、結果の複素数定数を計算します。これにより、コンパイル時に値が確定する complex(1, 2) のような式は、実行時ではなくコンパイル時に評価されます。
  4. 結果の型決定: switch underlying(x.typ).(*Basic).Kind ブロックは、引数の型に基づいて、結果の複素数型を Complex64, Complex128, または UntypedComplex のいずれかに決定します。

complexArg 関数は、complex() の引数として許容される型を厳密に定義し、型チェックのロジックをカプセル化しています。

src/pkg/exp/types/const.go における toImagConst 関数

この関数は、数値定数を複素数の虚数部として扱うための補助関数です。例えば、complex(real, imag)imagint642 であった場合、toImagConst(2)0 + 2i という複素数定数表現を生成します。これは、complex() 関数が実部と虚部を結合する際に、虚数部を適切な複素数形式に変換するために必要です。

src/pkg/exp/types/stmt.go における switch ステートメントのチェック強化

*ast.SwitchStmt の処理は、式スイッチのセマンティクスを正確に実装するために変更されました。

  1. 暗黙の true タグ: switch 式が省略された場合、tag = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")} によって、switch true { ... } と同等に扱われるように、明示的な true の識別子が生成されます。これにより、後続の型チェックロジックが統一的に適用できます。
  2. 重複ケース検出: seen マップは、これまでに処理された定数 case 値を記録します。if y.mode == constant ブロック内で、現在の case 値が既に seen マップに存在するかをチェックし、存在すれば重複エラーを報告します。この機能は、Go言語の仕様で禁止されている重複する case 値をコンパイル時に捕捉するために不可欠です。ただし、TODO(gri) コメントが示すように、map のキーとしての interface{} の比較の限界により、非常に大きな数値や複雑な型の定数では、この重複検出が完全ではない可能性があります。
  3. 型変換と比較可能性チェック:
    • check.convertUntyped(&y, x.typ)check.convertUntyped(&x, y.typ) は、switch 式の型と case 式の型の間で型なし定数の変換を試みます。これにより、例えば switch 1.0 { case 1: ... } のように、型なし定数が異なる型を持つ式と比較される場合に、適切な型変換が行われることを保証します。
    • check.comparison(&y, &x, token.EQL) は、switch 式の値と case 式の値が == 演算子で比較可能であるかを検証します。Go言語では、スライスやマップなど、一部の型は比較できません。このチェックにより、比較不可能な型が case 値として使用された場合にコンパイルエラーが発生し、実行時エラーを防ぎます。

これらの変更は、Go言語の型チェッカーがより多くの種類の論理的誤りをコンパイル時に検出し、開発者がより安全で正確なコードを書くことを支援します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントと仕様書
  • Go言語のソースコード (特に src/go/types パッケージの現在の実装)
  • Go言語のIssueトラッカーや変更履歴 (CLs)
  • Go言語に関する一般的なプログラミング知識と型システムに関する概念