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

[インデックス 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 パッケージには、以下のような特定のシナリオで型チェックが不完全であったり、誤っていたりする問題が存在していました。

  1. 可変長引数(Variadic Parameters)の型チェック: Goの関数は、最後のパラメータを ...T の形式で宣言することで、任意の数の引数を受け取ることができます。この可変長引数に対する型チェックが、エッジケースで正しく機能していませんでした。
  2. 多値関数の結果の引数としての利用: f(g()) のように、複数の戻り値を返す関数 g() の結果を、別の関数 f() の引数として直接渡す場合、その型チェックが複雑であり、正しく処理されていないケースがありました。
  3. 符号なし定数に対するビット反転演算 (^x): 符号なし整数型に対するビット反転演算 (^) のセマンティクスが、定数評価の段階で正しく実装されていませんでした。
  4. 型なしブーリアンの割り当て可能性: truefalse のような型なしブーリアン定数が、特定のコンテキストで正しく型付けされたブーリアン変数に割り当てられない問題がありました。
  5. 既存の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 の場合、10int 型として扱われます。
  • 可変長引数 (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 パッケージで提供される、任意精度整数および任意精度有理数を扱うための型です。型チェッカーは、コンパイル時定数の評価において、オーバーフローを避けるためにこれらの型を使用することがあります。

型チェックのプロセス

型チェックは通常、以下のステップで行われます。

  1. 構文解析: ソースコードをASTに変換します。
  2. スコープ解決: 変数や関数の宣言と使用を紐付け、スコープを確立します。
  3. 型推論と伝播: 各式の型を推論し、その型情報をプログラム全体に伝播させます。
  4. 型検証: 型の互換性、割り当て可能性、演算子の適用可能性などを検証し、型エラーを検出します。
  5. 定数評価: コンパイル時定数式を評価し、その結果を定数値として扱います。

このコミットは、特にステップ4と5におけるロジックの改善に焦点を当てています。

技術的詳細

このコミットで行われた主要な技術的変更は以下の通りです。

  1. パラメータ渡しと関数呼び出しの型チェックの改善:

    • 可変長引数 (...) の処理: 以前は可変長引数の型チェックが不完全でした。このコミットでは、argument 関数に passSlice パラメータが追加され、引数が ... で渡される場合に、最後のパラメータがスライス型 []T に割り当て可能であるかを正確にチェックするようになりました。これにより、fv(s...) のようなスライスを可変長引数に展開して渡す構文が正しく処理されます。
    • 多値関数の結果の引数としての利用 (f(g())): expr.gorawExpr 関数内の関数呼び出しの処理が大幅に修正されました。以前は f(g()) のような形式で g() が多値関数である場合、その結果の型チェックが複雑でした。新しいロジックでは、引数が単一の関数呼び出しである場合に、その呼び出しの結果が複数の値を持つかどうかを判断し、それぞれの結果値を個別の引数として argument 関数に渡すようになりました。これにより、多値関数の結果が正しく展開され、対応するパラメータに型チェックされるようになります。
    • 引数の数のチェック: 関数呼び出しにおける引数の数が、関数のシグネチャと一致するかどうかのチェックが強化されました。可変長引数の場合も考慮に入れ、引数が少なすぎる場合に適切なエラーが報告されるようになりました。
  2. 符号なし定数に対するビット反転演算 (^x) の修正:

    • const.gounaryOpConst 関数が新しく追加されました。この関数は、単項演算子(+, -, ^, !)が定数に適用された場合の評価ロジックをカプセル化します。
    • 特に、token.XOR (ビット反転) 演算子の場合、オペランドが符号なし型 (IsUnsigned フラグを持つ型) である場合、結果のサイズを型のビット幅に制限するロジックが追加されました。これは、Goの仕様において、符号なし整数に対するビット反転演算は、その型のビット幅内で実行されるためです。以前の実装では、^x が負の大きな値になる可能性があり、符号なし型のセマンティクスと一致していませんでした。z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s)) の行がこのビット幅制限を実装しています。
  3. 型なしブーリアンの割り当て可能性の修正:

    • operand.goisAssignable 関数が修正されました。以前は、型なしブーリアンが定数である場合にのみ割り当て可能とされていましたが、比較演算の結果など、定数ではない型なしブーリアンも存在します。
    • 修正後、x.mode == constant のチェックに加えて、x が型なしブーリアンであり、ターゲットの型がブーリアン型である場合にも割り当て可能と判断されるようになりました。これにより、b = x < y のような比較結果の割り当てが正しく型チェックされるようになります。
  4. tuple から Result へのリネームと構造変更:

    • types.go で、多値戻り値を表す内部型 tupleResult にリネームされました。これは、Go言語には「タプル」という概念が公式には存在しないため、混乱を避けるための変更と考えられます。
    • Result 構造体は、list []Type の代わりに Values ObjList を持つようになりました。ObjList*ast.Object のリストであり、これにより戻り値の型だけでなく、そのオブジェクト(名前など)も保持できるようになり、よりリッチな型情報を提供できるようになります。
    • この変更に伴い、errors.gostmt.go など、tuple 型を使用していた箇所がすべて Result 型を使用するように更新されました。
  5. 定数評価ロジックの改善:

    • const.gobinaryOpConst 関数において、整数除算の判定が intDiv ブーリアンから typ.Info&IsInteger != 0 に変更されました。これにより、オペランドの型情報に基づいてより正確に整数除算が適用されるようになりました。
    • newComplex 関数が追加され、複素数定数の正規化が改善されました。
  6. TODOの解消とマイナーな修正:

    • expr.goconst.go など、複数のファイルでTODOコメントが解消されました。
    • gotype_test.go では、型チェックがパスするようになった標準パッケージのコメントアウトが解除され、テストカバレッジが大幅に拡大されました。これにより、型チェッカーの安定性が向上したことが示されています。

これらの変更は、exp/types パッケージの型チェックの正確性、堅牢性、およびGo言語のセマンティクスへの準拠を大幅に向上させるものです。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルに集中しています。

  1. 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 *operandpassSlice 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 {
      ```
    
  1. 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 関数のシグネチャ変更と、整数除算の判定ロジックの変更。
  1. 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:
      
  2. 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) を直接呼び出していました。これは、^xx 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 に設定されます。
    • passSlicetrue の場合、かつそれが最後の引数である場合 (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言語の型チェックの複雑な側面、特に可変長引数、多値戻り値、および定数評価における正確なセマンティクスを実装するために不可欠なものです。

関連リンク

参考にした情報源リンク