[インデックス 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/binary
や math/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
などと呼ばれ、文脈に応じて適切な型に変換されます。例えば、1
はUntypedInt
であり、1.0
はUntypedFloat
です。これらは、より広い範囲の型に代入できる柔軟性を提供します。
complex()
組み込み関数
complex()
はGo言語の組み込み関数で、2つの浮動小数点数(または型なし数値定数)を引数に取り、複素数型の値を生成します。
構文は complex(realPart, imagPart)
です。
引数の型によって、戻り値の型は complex64
または complex128
になります。例えば、complex(1.0, 2.0)
は complex128
を返します。
switch
ステートメント
Go言語の switch
ステートメントには主に2つの形式があります。
- 式スイッチ (Expression Switch):
switch
キーワードの後に式が続き、その式の値が各case
節の値と比較されます。switch x { case 1: // ... case 2, 3: // ... default: // ... }
switch
式が省略された場合(switch { ... }
)、それはswitch true { ... }
と同等に扱われ、各case
節の式がブール値として評価されます。 - 型スイッチ (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
ケースが大幅に拡張されました。
-
引数の型チェック:
check.complexArg(x)
およびcheck.complexArg(&y)
が導入され、complex()
の引数がfloat32
,float64
, または型なしの非複素数数値定数(UntypedInt
,UntypedRune
,UntypedFloat
)であることを検証します。これ以外の型(例:bool
,int32
,string
,complex64
など)が渡された場合はエラーとなります。complexArg
関数は、引数の基底型が浮動小数点型であるか、型なしの整数/ルーン型であるかをチェックします。
-
型変換と整合性:
check.convertUntyped(x, y.typ)
とcheck.convertUntyped(&y, x.typ)
を使用して、両方の引数(実部と虚部)が同じ型になるように型なし定数を変換します。これにより、complex(1, 2.0)
のような呼び出しで、1
がfloat64
に変換され、両方の引数がfloat64
に揃えられます。isIdentical(x.typ, y.typ)
で、変換後の実部と虚部の型が一致していることを確認します。complex(float32(1), float64(2))
のような型不一致はエラーとなります。
-
定数畳み込み:
- 両方の引数が定数である場合(
x.mode == constant && y.mode == constant
)、コンパイル時に複素数定数を計算します。 binaryOpConst(x.val, toImagConst(y.val), token.ADD, false)
を使用して、実部と虚部を結合します。ここでtoImagConst
は、非複素数定数を虚数部として扱うためのヘルパー関数です。
- 両方の引数が定数である場合(
-
結果の型推論:
- 実部または虚部の型に基づいて、結果の複素数型を決定します。
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
ケースが大幅に改善されました。
-
switch
式のデフォルト値:switch
ステートメントに式(タグ)が指定されていない場合(s.Tag == nil
)、Goの仕様に従い、暗黙的にtrue
がタグとして扱われます。このコミットでは、ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")}
を生成し、true
を明示的なタグとして扱います。これにより、case
節の式がブール値として評価されるようになります。
-
重複ケース値の検出:
seen := make(map[interface{}]token.Pos)
マップが導入され、各case
節の定数値を記録します。y.mode == constant
の場合、seen[y.val]
をチェックして、同じ値が以前に出現していないかを確認します。- 重複が検出された場合、
check.errorf
を使用してエラーを報告します。 - 注意点: コミットメッセージの
TODO(gri)
にあるように、この重複検出はint64
に収まらない大きな整数値、浮動小数点数、複素数値に対しては正確に機能しない可能性があります。これは、map
のキーとして使用されるinterface{}
の比較が、これらの複雑な値の深い比較を保証しないためです。
-
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()
関数に対応します。
check.complexArg(x)
: この呼び出しは、complex()
の実部と虚部の引数が有効な型(float32
,float64
, または型なしの数値定数)であるかを検証します。無効な型であれば、goto Error
でエラー処理にジャンプします。- 型変換と同一性チェック:
convertUntyped
は、型なし定数を他の引数の型に合わせるために使用されます。例えば、complex(1, 2.0)
の場合、1
はUntypedInt
ですが、2.0
がUntypedFloat
であるため、1
もUntypedFloat
に変換されます。isIdentical
は、変換後の実部と虚部の型が完全に一致していることを保証します。 - 定数畳み込み: 両方の引数がコンパイル時定数である場合、
binaryOpConst
とtoImagConst
を用いて、結果の複素数定数を計算します。これにより、コンパイル時に値が確定するcomplex(1, 2)
のような式は、実行時ではなくコンパイル時に評価されます。 - 結果の型決定:
switch underlying(x.typ).(*Basic).Kind
ブロックは、引数の型に基づいて、結果の複素数型をComplex64
,Complex128
, またはUntypedComplex
のいずれかに決定します。
complexArg
関数は、complex()
の引数として許容される型を厳密に定義し、型チェックのロジックをカプセル化しています。
src/pkg/exp/types/const.go
における toImagConst
関数
この関数は、数値定数を複素数の虚数部として扱うための補助関数です。例えば、complex(real, imag)
の imag
が int64
の 2
であった場合、toImagConst(2)
は 0 + 2i
という複素数定数表現を生成します。これは、complex()
関数が実部と虚部を結合する際に、虚数部を適切な複素数形式に変換するために必要です。
src/pkg/exp/types/stmt.go
における switch
ステートメントのチェック強化
*ast.SwitchStmt
の処理は、式スイッチのセマンティクスを正確に実装するために変更されました。
- 暗黙の
true
タグ:switch
式が省略された場合、tag = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true", Obj: Universe.Lookup("true")}
によって、switch true { ... }
と同等に扱われるように、明示的なtrue
の識別子が生成されます。これにより、後続の型チェックロジックが統一的に適用できます。 - 重複ケース検出:
seen
マップは、これまでに処理された定数case
値を記録します。if y.mode == constant
ブロック内で、現在のcase
値が既にseen
マップに存在するかをチェックし、存在すれば重複エラーを報告します。この機能は、Go言語の仕様で禁止されている重複するcase
値をコンパイル時に捕捉するために不可欠です。ただし、TODO(gri)
コメントが示すように、map
のキーとしてのinterface{}
の比較の限界により、非常に大きな数値や複雑な型の定数では、この重複検出が完全ではない可能性があります。 - 型変換と比較可能性チェック:
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言語の仕様: https://go.dev/ref/spec
- 組み込み関数: https://go.dev/ref/spec#Built-in_functions
switch
ステートメント: https://go.dev/ref/spec#Switch_statements- 比較演算子: https://go.dev/ref/spec#Comparison_operators
- Go言語の複素数型に関するブログ記事 (例): https://go.dev/blog/go-and-the-web (一般的なGoのブログ記事であり、直接複素数型に特化したものではないが、Goの機能に関する情報源として)
参考にした情報源リンク
- Go言語の公式ドキュメントと仕様書
- Go言語のソースコード (特に
src/go/types
パッケージの現在の実装) - Go言語のIssueトラッカーや変更履歴 (CLs)
- Go言語に関する一般的なプログラミング知識と型システムに関する概念