[インデックス 14676] ファイルの概要
このコミットは、Go言語の実験的な型チェッカーである exp/types
パッケージにおける、型チェックの正確性と堅牢性を向上させるための広範な変更を含んでいます。主に、関数呼び出しにおけるパラメータの型チェック、定数式の評価、および型の割り当て可能性に関する修正が中心です。
変更された主なファイルとその役割は以下の通りです。
src/pkg/exp/gotype/gotype_test.go
: 型チェッカーのテストスイート。型チェックが成功するパッケージのリストが更新され、より多くの標準パッケージがテスト対象に含まれるようになりました。src/pkg/exp/types/builtins.go
: 組み込み関数の型チェックロジック。特に複素数型の定数評価に関する修正が含まれています。src/pkg/exp/types/const.go
: 定数式の表現と評価に関するロジック。符号なし定数に対するビット反転演算 (^x
) の修正や、定数表現の正規化に関する改善が含まれます。src/pkg/exp/types/errors.go
: 型チェックエラーの報告に関するユーティリティ。tuple
型の代わりにResult
型を使用するように変更されています。src/pkg/exp/types/expr.go
: 式の型チェックに関する主要なロジック。単項演算子、二項演算子、関数呼び出しにおけるパラメータの型チェックロジックが大幅に修正されています。特に、可変長引数(variadic parameters)の処理と、f(g())
のような多値関数の結果を引数として渡すケースの対応が改善されました。src/pkg/exp/types/operand.go
: 型チェック中に使用されるオペランド(式の結果)の表現と、割り当て可能性のチェックに関するロジック。特に、型なしブーリアンの割り当て可能性に関する修正が含まれます。src/pkg/exp/types/predicates.go
: 型に関する述語(例: 型が整数型か、符号なしかなど)を定義するファイル。defaultType
関数のロジックが修正されています。src/pkg/exp/types/stmt.go
: ステートメントの型チェックに関するロジック。多値関数の結果の割り当てや、switch
ステートメントにおける重複ケースの検出ロジックが修正されています。src/pkg/exp/types/testdata/*.src
: 型チェッカーのテストケース。新しいテストケースの追加や既存のテストケースの修正が含まれ、特に可変長引数や型なしnilの使用に関するテストが追加されています。src/pkg/exp/types/types.go
: 型システムの基本構造の定義。多値関数の結果を表すためにtuple
型がResult
型にリネームされ、その構造が変更されました。src/pkg/exp/types/types_test.go
: 型定義のテスト。
コミット
exp/types: completed typechecking of parameter passing
Details:
- fixed variadic parameter passing and calls of the form f(g())
- fixed implementation of ^x for unsigned constants x
- fixed assignability of untyped booleans
- resolved a few TODOs, various minor fixes
- enabled many more tests (only 6 std packages don't typecheck)
R=rsc
CC=golang-dev
https://golang.org/cl/6930053
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/50d8787822e1919815ecca4f30600a118841cc7c
元コミット内容
exp/types: completed typechecking of parameter passing
Details:
- fixed variadic parameter passing and calls of the form f(g())
- fixed implementation of ^x for unsigned constants x
- fixed assignability of untyped booleans
- resolved a few TODOs, various minor fixes
- enabled many more tests (only 6 std packages don't typecheck)
R=rsc
CC=golang-dev
https://golang.org/cl/6930053
変更の背景
このコミットは、Go言語の実験的な型チェッカーである exp/types
パッケージの機能強化とバグ修正を目的としています。特に、関数呼び出しにおける引数の型チェックの正確性を向上させることが主要な目標でした。
Go言語の型システムは厳密であり、コンパイル時に可能な限り多くのエラーを検出することを目指しています。しかし、初期の exp/types
パッケージには、以下のような特定のシナリオで型チェックが不完全であったり、誤っていたりする問題が存在していました。
- 可変長引数(Variadic Parameters)の型チェック: Goの関数は、最後のパラメータを
...T
の形式で宣言することで、任意の数の引数を受け取ることができます。この可変長引数に対する型チェックが、エッジケースで正しく機能していませんでした。 - 多値関数の結果の引数としての利用:
f(g())
のように、複数の戻り値を返す関数g()
の結果を、別の関数f()
の引数として直接渡す場合、その型チェックが複雑であり、正しく処理されていないケースがありました。 - 符号なし定数に対するビット反転演算 (
^x
): 符号なし整数型に対するビット反転演算 (^
) のセマンティクスが、定数評価の段階で正しく実装されていませんでした。 - 型なしブーリアンの割り当て可能性:
true
やfalse
のような型なしブーリアン定数が、特定のコンテキストで正しく型付けされたブーリアン変数に割り当てられない問題がありました。 - 既存のTODOの解消とテストカバレッジの拡大: コードベース内に残っていた未解決のTODOコメントの解消と、型チェッカーの堅牢性を高めるためのテストカバレッジの拡大も重要な課題でした。
これらの問題は、型チェッカーの信頼性を低下させ、開発者が予期しないコンパイルエラーに遭遇したり、逆に実行時エラーを見逃したりする可能性がありました。このコミットは、これらの課題に対処し、exp/types
パッケージをより実用的なものにすることを目的としています。特に、より多くの標準パッケージが型チェックをパスするようになったことは、型チェッカーの成熟度を示す重要な指標となります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と、exp/types
パッケージの内部構造に関する知識が役立ちます。
Go言語の型システム
Go言語は静的型付け言語であり、すべての変数と式には型があります。型システムは、プログラムの安全性と正確性を保証する上で重要な役割を果たします。
- 基本型 (Basic Types):
int
,float64
,bool
,string
など。 - 複合型 (Composite Types): 配列、スライス、マップ、構造体、インターフェース、関数など。
- 型なし定数 (Untyped Constants): Goには、型が明示的に指定されていない定数があります(例:
10
,3.14
,true
,"hello"
)。これらは、使用されるコンテキストによって適切な型に「デフォルト型付け」されます。例えば、var i int = 10
の場合、10
はint
型として扱われます。 - 可変長引数 (Variadic Parameters): 関数の最後のパラメータに
...T
を付けることで、その関数はT
型の任意の数の引数を受け取ることができます。関数内部では、可変長引数は[]T
型のスライスとして扱われます。 - 多値戻り値 (Multiple Return Values): Goの関数は複数の値を返すことができます。例えば
func foo() (int, string)
。
exp/types
パッケージ
exp/types
は、Go言語の公式な型チェッカー(go/types
パッケージ)の前身または実験的なバージョンです。Goコンパイラの一部として、ソースコードの構文木(AST)を解析し、各式の型を決定し、型エラーを検出する役割を担います。
- AST (Abstract Syntax Tree): Goのソースコードは、
go/ast
パッケージによって抽象構文木にパースされます。型チェッカーはこのASTを走査して型情報を収集し、検証します。 operand
構造体:exp/types
パッケージ内で、式の結果や定数値を表現するために使用される内部構造体です。mode
(定数、変数など)、typ
(型)、val
(定数値) などのフィールドを持ちます。Signature
構造体: 関数のシグネチャ(パラメータと戻り値の型)を表現する構造体です。Result
構造体 (旧tuple
): 多値戻り値を持つ関数の結果や、複数の値を返す式の結果を表現するために使用される構造体です。このコミットでtuple
からResult
にリネームされました。token.Token
:go/token
パッケージで定義されている、Go言語のキーワード、演算子、区切り文字などを表す列挙型です。型チェックのロジックで演算子を識別するために使用されます。big.Int
,big.Rat
: Goのmath/big
パッケージで提供される、任意精度整数および任意精度有理数を扱うための型です。型チェッカーは、コンパイル時定数の評価において、オーバーフローを避けるためにこれらの型を使用することがあります。
型チェックのプロセス
型チェックは通常、以下のステップで行われます。
- 構文解析: ソースコードをASTに変換します。
- スコープ解決: 変数や関数の宣言と使用を紐付け、スコープを確立します。
- 型推論と伝播: 各式の型を推論し、その型情報をプログラム全体に伝播させます。
- 型検証: 型の互換性、割り当て可能性、演算子の適用可能性などを検証し、型エラーを検出します。
- 定数評価: コンパイル時定数式を評価し、その結果を定数値として扱います。
このコミットは、特にステップ4と5におけるロジックの改善に焦点を当てています。
技術的詳細
このコミットで行われた主要な技術的変更は以下の通りです。
-
パラメータ渡しと関数呼び出しの型チェックの改善:
- 可変長引数 (
...
) の処理: 以前は可変長引数の型チェックが不完全でした。このコミットでは、argument
関数にpassSlice
パラメータが追加され、引数が...
で渡される場合に、最後のパラメータがスライス型[]T
に割り当て可能であるかを正確にチェックするようになりました。これにより、fv(s...)
のようなスライスを可変長引数に展開して渡す構文が正しく処理されます。 - 多値関数の結果の引数としての利用 (
f(g())
):expr.go
のrawExpr
関数内の関数呼び出しの処理が大幅に修正されました。以前はf(g())
のような形式でg()
が多値関数である場合、その結果の型チェックが複雑でした。新しいロジックでは、引数が単一の関数呼び出しである場合に、その呼び出しの結果が複数の値を持つかどうかを判断し、それぞれの結果値を個別の引数としてargument
関数に渡すようになりました。これにより、多値関数の結果が正しく展開され、対応するパラメータに型チェックされるようになります。 - 引数の数のチェック: 関数呼び出しにおける引数の数が、関数のシグネチャと一致するかどうかのチェックが強化されました。可変長引数の場合も考慮に入れ、引数が少なすぎる場合に適切なエラーが報告されるようになりました。
- 可変長引数 (
-
符号なし定数に対するビット反転演算 (
^x
) の修正:const.go
にunaryOpConst
関数が新しく追加されました。この関数は、単項演算子(+
,-
,^
,!
)が定数に適用された場合の評価ロジックをカプセル化します。- 特に、
token.XOR
(ビット反転) 演算子の場合、オペランドが符号なし型 (IsUnsigned
フラグを持つ型) である場合、結果のサイズを型のビット幅に制限するロジックが追加されました。これは、Goの仕様において、符号なし整数に対するビット反転演算は、その型のビット幅内で実行されるためです。以前の実装では、^x
が負の大きな値になる可能性があり、符号なし型のセマンティクスと一致していませんでした。z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s))
の行がこのビット幅制限を実装しています。
-
型なしブーリアンの割り当て可能性の修正:
operand.go
のisAssignable
関数が修正されました。以前は、型なしブーリアンが定数である場合にのみ割り当て可能とされていましたが、比較演算の結果など、定数ではない型なしブーリアンも存在します。- 修正後、
x.mode == constant
のチェックに加えて、x
が型なしブーリアンであり、ターゲットの型がブーリアン型である場合にも割り当て可能と判断されるようになりました。これにより、b = x < y
のような比較結果の割り当てが正しく型チェックされるようになります。
-
tuple
からResult
へのリネームと構造変更:types.go
で、多値戻り値を表す内部型tuple
がResult
にリネームされました。これは、Go言語には「タプル」という概念が公式には存在しないため、混乱を避けるための変更と考えられます。Result
構造体は、list []Type
の代わりにValues ObjList
を持つようになりました。ObjList
は*ast.Object
のリストであり、これにより戻り値の型だけでなく、そのオブジェクト(名前など)も保持できるようになり、よりリッチな型情報を提供できるようになります。- この変更に伴い、
errors.go
やstmt.go
など、tuple
型を使用していた箇所がすべてResult
型を使用するように更新されました。
-
定数評価ロジックの改善:
const.go
のbinaryOpConst
関数において、整数除算の判定がintDiv
ブーリアンからtyp.Info&IsInteger != 0
に変更されました。これにより、オペランドの型情報に基づいてより正確に整数除算が適用されるようになりました。newComplex
関数が追加され、複素数定数の正規化が改善されました。
-
TODOの解消とマイナーな修正:
expr.go
やconst.go
など、複数のファイルでTODOコメントが解消されました。gotype_test.go
では、型チェックがパスするようになった標準パッケージのコメントアウトが解除され、テストカバレッジが大幅に拡大されました。これにより、型チェッカーの安定性が向上したことが示されています。
これらの変更は、exp/types
パッケージの型チェックの正確性、堅牢性、およびGo言語のセマンティクスへの準拠を大幅に向上させるものです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。
src/pkg/exp/types/expr.go
:unary
関数: 単項演算子 (^x
) の定数評価ロジックがunaryOpConst
関数を呼び出すように変更されました。--- a/src/pkg/exp/types/expr.go +++ b/src/pkg/exp/types/expr.go @@ -212,21 +211,11 @@ func (check *checker) unary(x *operand, op token.Token) { } if x.mode == constant { - switch op { - case token.ADD: - // nothing to do - case token.SUB: - x.val = binaryOpConst(zeroConst, x.val, token.SUB, false) - case token.XOR: - x.val = binaryOpConst(minusOneConst, x.val, token.XOR, false) - case token.NOT: - x.val = !x.val.(bool) - default: - unreachable() // operators where checked by check.op - } + typ := underlying(x.typ).(*Basic) + x.val = unaryOpConst(x.val, op, typ) // Typed constants must be representable in // their type after each constant operation. - check.isRepresentable(x, underlying(x.typ).(*Basic)) + check.isRepresentable(x, typ) return }
argument
関数: 可変長引数と多値関数の引数渡しを処理するために、x *operand
とpassSlice bool
パラメータが追加され、ロジックが大幅に修正されました。--- a/src/pkg/exp/types/expr.go +++ b/src/pkg/exp/types/expr.go @@ -554,9 +546,15 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota return max } -func (check *checker) argument(sig *Signature, i int, arg ast.Expr) { +// argument typechecks passing an argument arg (if arg != nil) or +// x (if arg == nil) to the i'th parameter of the given signature. +// If passSlice is set, the argument is followed by ... in the call. +// +func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, passSlice bool) { + // determine parameter var par *ast.Object - if n := len(sig.Params); i < n { + n := len(sig.Params) + if i < n { par = sig.Params[i] } else if sig.IsVariadic { par = sig.Params[n-1] @@ -565,16 +563,32 @@ func (check *checker) argument(sig *Signature, i int, arg ast.Expr) { return } - // TODO(gri) deal with ... last argument - var z, x operand + // determine argument + var z operand z.mode = variable - z.expr = nil // TODO(gri) can we do better here? - z.typ = par.Type.(Type) // TODO(gri) should become something like checkObj(&z, ...) eventually - check.expr(&x, arg, z.typ, -1) + z.expr = nil // TODO(gri) can we do better here? (for good error messages) + z.typ = par.Type.(Type) + + if arg != nil { + check.expr(x, arg, z.typ, -1) + } if x.mode == invalid { return // ignore this argument } - check.assignOperand(&z, &x) + + // check last argument of the form x... + if passSlice { + if i+1 != n { + check.errorf(x.pos(), "can only use ... with matching parameter") + return // ignore this argument + } + // spec: "If the final argument is assignable to a slice type []T, + // it may be passed unchanged as the value for a ...T parameter if + // the argument is followed by ..." + z.typ = &Slice{Elt: z.typ} // change final parameter type to []T + } + + check.assignOperand(&z, x) } func (check *checker) recordType(x *operand) {
rawExpr
関数内の関数呼び出し (ast.CallExpr
) の処理が、多値関数の引数渡しと可変長引数の処理を考慮して大幅に書き換えられました。--- a/src/pkg/exp/types/expr.go +++ b/src/pkg/exp/types/expr.go @@ -1052,25 +1066,79 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle check.conversion(x, e, x.typ, iota) } else if sig, ok := underlying(x.typ).(*Signature); ok { // check parameters - // TODO(gri) - // - deal with single multi-valued function arguments: f(g()) - // - variadic functions only partially addressed - for i, arg := range e.Args { - check.argument(sig, i, arg) + // If we have a trailing ... at the end of the parameter + // list, the last argument must match the parameter type + // []T of a variadic function parameter x ...T. + passSlice := false + if e.Ellipsis.IsValid() { + if sig.IsVariadic { + passSlice = true + } else { + check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun) + // ok to continue + } } - // determine result - x.mode = value - if len(sig.Results) == 1 { - x.typ = sig.Results[0].Type.(Type) + // If we have a single argument that is a function call + // we need to handle it separately. Determine if this + // is the case without checking the argument. + var call *ast.CallExpr + if len(e.Args) == 1 { + call, _ = unparen(e.Args[0]).(*ast.CallExpr) + } + + n := 0 // parameter count + if call != nil { + // We have a single argument that is a function call. + check.expr(x, call, nil, -1) + if x.mode == invalid { + goto Error // TODO(gri): we can do better + } + if t, _ := x.typ.(*Result); t != nil { + // multiple result values + n = len(t.Values) + for i, obj := range t.Values { + x.mode = value + x.expr = nil // TODO(gri) can we do better here? (for good error messages) + x.typ = obj.Type.(Type) + check.argument(sig, i, nil, x, passSlice && i+1 == n) + } + } else { + // single result value + n = 1 + check.argument(sig, 0, nil, x, passSlice) + } + } else { - // TODO(gri) change Signature representation to use tuples, - // then this conversion is not required - list := make([]Type, len(sig.Results)) - for i, obj := range sig.Results { - list[i] = obj.Type.(Type) + // We don't have a single argument or it is not a function call. + n = len(e.Args) + for i, arg := range e.Args { + check.argument(sig, i, arg, x, passSlice && i+1 == n) } - x.typ = &tuple{list: list} + } + + // determine if we have enough arguments + if sig.IsVariadic { + // a variadic function accepts an "empty" + // last argument: count one extra + n++
-
}
-
if n < len(sig.Params) {
-
check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun)
-
// ok to continue
-
}
-
// determine result
-
switch len(sig.Results) {
-
case 0:
-
x.mode = novalue
-
case 1:
-
x.mode = value
-
x.typ = sig.Results[0].Type.(Type)
-
default:
-
x.mode = value
-
x.typ = &Result{Values: sig.Results} } } else if bin, ok := x.typ.(*builtin); ok { ```
src/pkg/exp/types/const.go
:unaryOpConst
関数の追加: 符号なし定数に対するビット反転 (^x
) のロジックを含む。--- a/src/pkg/exp/types/const.go +++ b/src/pkg/exp/types/const.go @@ -386,13 +388,71 @@ func is63bit(x int64) bool { return -1<<62 <= x && x <= 1<<62-1 } +// unaryOpConst returns the result of the constant evaluation op x where x is of the given type. +func unaryOpConst(x interface{}, op token.Token, typ *Basic) interface{} { + switch op { + case token.ADD: + return x // nothing to do + case token.SUB: + switch x := x.(type) { + case int64: + if z := -x; z != x { + return z // no overflow + } + // overflow - need to convert to big.Int + return normalizeIntConst(new(big.Int).Neg(big.NewInt(x))) + case *big.Int: + return normalizeIntConst(new(big.Int).Neg(x)) + case *big.Rat: + return normalizeRatConst(new(big.Rat).Neg(x)) + case complex: + return newComplex(new(big.Rat).Neg(x.re), new(big.Rat).Neg(x.im)) + } + case token.XOR: + var z big.Int + switch x := x.(type) { + case int64: + z.Not(big.NewInt(x)) + case *big.Int: + z.Not(x) + default: + unreachable() + } + // For unsigned types, the result will be negative and + // thus "too large": We must limit the result size to + // the type's size. + if typ.Info&IsUnsigned != 0 { + s := uint(typ.Size) * 8 + if s == 0 { + // platform-specific type + // TODO(gri) this needs to be factored out + switch typ.Kind { + case Uint: + s = intBits + case Uintptr: + s = ptrBits
-
default:
-
unreachable()
-
}
-
}
-
// z &^= (-1)<<s
-
z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s))
-
}
-
return normalizeIntConst(&z)
-
case token.NOT:
-
return !x.(bool)
-
}
-
unreachable()
-
return nil +}
-
// binaryOpConst returns the result of the constant evaluation x op y; -// both operands must be of the same "kind" (boolean, numeric, or string). -// If intDiv is true, division (op == token.QUO) is using integer division +// both operands must be of the same constant "kind" (boolean, numeric, or string). +// If typ is an integer type, division (op == token.QUO) is using integer division // (and the result is guaranteed to be integer) rather than floating-point // division. Division by zero leads to a run-time panic. // -func binaryOpConst(x, y interface{}, op token.Token, intDiv bool) interface{} { +func binaryOpConst(x, y interface{}, op token.Token, typ *Basic) interface{} { x, y = matchConst(x, y)
switch x := x.(type) { ```
binaryOpConst
関数のシグネチャ変更と、整数除算の判定ロジックの変更。
-
src/pkg/exp/types/operand.go
:isAssignable
関数: 型なしブーリアンの割り当て可能性の修正。--- a/src/pkg/exp/types/operand.go +++ b/src/pkg/exp/types/operand.go @@ -182,7 +182,14 @@ func (x *operand) isAssignable(T Type) bool { if isUntyped(Vu) { switch t := Tu.(type) { case *Basic: - return x.mode == constant && isRepresentableConst(x.val, t.Kind) + if x.mode == constant { + return isRepresentableConst(x.val, t.Kind) + } + // The result of a comparison is an untyped boolean, + // but may not be a constant. + if Vb, _ := Vu.(*Basic); Vb != nil { + return Vb.Kind == UntypedBool && isBoolean(Tu) + } case *Interface: return x.isNil() || len(t.Methods) == 0 case *Pointer, *Signature, *Slice, *Map, *Chan:
-
src/pkg/exp/types/types.go
:tuple
型がResult
型にリネームされ、その構造が変更されました。--- a/src/pkg/exp/types/types.go +++ b/src/pkg/exp/types/types.go @@ -141,15 +141,15 @@ type Pointer struct { Base Type } -// A tuple represents a multi-value function return. -// TODO(gri) use better name to avoid confusion (Go doesn't have tuples). -type tuple struct { +// A Result represents a (multi-value) function call result. +// TODO(gri) consider using an empty Result (Values == nil) +// as representation for the novalue operand mode. +type Result struct { implementsType - list []Type + Values ObjList // Signature.Results of the function called } // A Signature represents a user-defined function type func(...) (...). -// TODO(gri) consider using "tuples" to represent parameters and results (see comment on tuples). type Signature struct { implementsType Recv *ast.Object // nil if not a method
これらの変更は、型チェッカーのコアロジックに深く関わっており、Go言語の型システムの正確なセマンティクスを反映するために不可欠です。
コアとなるコードの解説
src/pkg/exp/types/expr.go
の変更
-
unary
関数における^x
の修正:- 以前は、単項演算子
^
(ビット反転) の定数評価はbinaryOpConst(minusOneConst, x.val, token.XOR, false)
を直接呼び出していました。これは、^x
がx XOR -1
と等価であるという数学的性質を利用したものですが、符号なし整数型の場合、このアプローチは問題がありました。Goの仕様では、符号なし整数に対するビット反転は、その型のビット幅内で実行されるため、結果が負になることはありません。 - 新しい
unaryOpConst
関数は、オペランドの型 (typ
) を受け取るようになりました。typ.Info&IsUnsigned != 0
で符号なし型であるかをチェックし、もし符号なし型であれば、z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s))
を使用して、結果を型のビット幅 (s
) に制限します。これにより、符号なし整数に対する^x
が正しく評価されるようになりました。
- 以前は、単項演算子
-
argument
関数におけるパラメータ渡しの改善:- この関数は、関数呼び出しの個々の引数を、対応するパラメータに対して型チェックする役割を担います。
- 変更前は、可変長引数 (
...
) の処理が不完全でした。新しいpassSlice
パラメータは、引数が...
で渡された場合にtrue
に設定されます。 passSlice
がtrue
の場合、かつそれが最後の引数である場合 (i+1 != n
)、引数の型が対応する可変長パラメータのスライス型 ([]T
) に変更されます (z.typ = &Slice{Elt: z.typ}
)。これにより、fv(s...)
のようなスライス展開の構文が正しく型チェックされるようになります。- また、
x *operand
パラメータが追加されたことで、rawExpr
関数から多値関数の結果を直接operand
として渡せるようになり、より柔軟な引数処理が可能になりました。
-
rawExpr
関数における関数呼び出しの型チェックの抜本的改善:- この変更は、このコミットの最も重要な部分の一つです。特に
f(g())
のような、多値関数g()
の結果を別の関数f()
の引数として渡すケースの処理が改善されました。 - 以前は、このようなケースの処理が複雑で、
TODO
コメントが残されていました。 - 新しいロジックでは、まず引数が単一の関数呼び出し (
ast.CallExpr
) であるかをチェックします。 - もしそうであれば、その関数呼び出し (
call
) をcheck.expr
で型チェックし、その結果 (x
) が多値 (x.typ.(*Result)
) であるかを判断します。 - 多値である場合、
t.Values
(旧t.list
) をループし、それぞれの結果値を個別の引数としてcheck.argument
に渡します。これにより、多値が正しく「展開」され、対応するパラメータに型チェックされるようになります。 - 引数の数が関数のパラメータ数と一致するかどうかのチェックも強化され、可変長引数の場合も考慮して、引数が少なすぎる場合に適切なエラーが報告されるようになりました。
- この変更は、このコミットの最も重要な部分の一つです。特に
src/pkg/exp/types/const.go
の変更
-
unaryOpConst
関数の導入:- 前述の通り、単項演算子の定数評価を集中管理する関数です。特に符号なし整数に対する
^
演算の正確なセマンティクスを実装するために重要です。
- 前述の通り、単項演算子の定数評価を集中管理する関数です。特に符号なし整数に対する
-
binaryOpConst
関数の改善:- 二項演算子の定数評価を行う関数です。以前は整数除算の判定に
intDiv
というブーリアンフラグを使用していましたが、これをオペランドの基本型情報 (typ.Info&IsInteger != 0
) に基づいて行うように変更されました。これにより、より汎用的で正確な整数除算の判定が可能になりました。
- 二項演算子の定数評価を行う関数です。以前は整数除算の判定に
src/pkg/exp/types/operand.go
の変更
isAssignable
関数における型なしブーリアンの修正:- この関数は、あるオペランドが特定の型に割り当て可能であるかを判断します。
- 以前は、型なしブーリアンが割り当て可能であるのは、それが定数である場合に限られていました。しかし、Goでは
x < y
のような比較演算の結果も型なしブーリアンですが、これは定数ではありません。 - 修正後、オペランドが定数であるかどうかに加えて、オペランドが型なしブーリアンであり、ターゲットの型がブーリアン型である場合にも割り当て可能と判断されるようになりました。これにより、比較結果のブーリアン値が正しく変数に割り当てられるようになります。
src/pkg/exp/types/types.go
の変更
tuple
からResult
へのリネームと構造変更:- これは、型システム内部の概念をより明確にするための重要な変更です。Go言語の公式なドキュメントでは「タプル」という用語は使われないため、
Result
という名前に変更することで、多値戻り値の概念をより適切に表現しています。 list []Type
からValues ObjList
への変更は、単に型のリストを持つだけでなく、*ast.Object
のリストを持つことで、戻り値の名前などのより詳細な情報を保持できるようになり、型チェックやエラー報告の精度向上に寄与します。
- これは、型システム内部の概念をより明確にするための重要な変更です。Go言語の公式なドキュメントでは「タプル」という用語は使われないため、
これらの変更は相互に関連しており、Go言語の型チェックの複雑な側面、特に可変長引数、多値戻り値、および定数評価における正確なセマンティクスを実装するために不可欠なものです。
関連リンク
- Go言語の型システムに関する公式ドキュメント: https://go.dev/ref/spec#Types
- Go言語の関数宣言(可変長引数を含む)に関する公式ドキュメント: https://go.dev/ref/spec#Function_declarations
- Go言語の定数に関する公式ドキュメント: https://go.dev/ref/spec#Constants
- Go言語の
go/types
パッケージ(公式型チェッカー)のドキュメント: https://pkg.go.dev/go/types (このコミットのexp/types
はその前身にあたりますが、概念は共通しています)
参考にした情報源リンク
- https://github.com/golang/go/commit/50d8787822e1919815ecca4f30600a118841cc7c (コミットページ)
- https://golang.org/cl/6930053 (Gerrit Code Review)
- Go言語の仕様書 (The Go Programming Language Specification): https://go.dev/ref/spec
math/big
パッケージのドキュメント: https://pkg.go.dev/math/biggo/ast
パッケージのドキュメント: https://pkg.go.dev/go/astgo/token
パッケージのドキュメント: https://pkg.go.dev/go/token