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

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

このコミットは、Go言語の型チェッカーの実験的なステージング領域 (src/pkg/exp/types/staging) における重要な更新を含んでいます。主な目的は、Go言語の仕様の最近の変更を実装し、レシーバーの型チェックを改善し、同じ位置での重複エラー報告を削減することです。

コミット

commit 7c03cd32b6612f153cf6362ead27e86af6e65336
Author: Robert Griesemer <gri@golang.org>
Date:   Mon Oct 22 11:28:21 2012 -0700

    exp/type/staging: implemented recent spec changes
    
    Also:
    - type-checking receivers
    - get rid of some multiple errors at the same position
    
    R=rsc, minux.ma
    CC=golang-dev
    https://golang.org/cl/6709061

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

https://github.com/golang/go/commit/7c03cd32b6612f153cf6362ead27e86af6e65336

元コミット内容

このコミットは、Go言語の型チェッカーの実験的な実装 (exp/types/staging) に対して行われたもので、以下の主要な変更を含んでいます。

  • Go言語の仕様における最近の変更を型チェッカーに反映。
  • メソッドのレシーバーの型チェック機能の追加または改善。
  • 型チェック中に同じコード位置で複数のエラーが報告される問題を削減。

変更の背景

Go言語は2012年3月にGo 1をリリースし、その仕様は安定性を持つことになりました。しかし、型チェッカーのようなコンパイラの基盤部分は、仕様の厳密な解釈やエッジケースへの対応、そしてより良いエラー報告のために継続的に改善されていました。このコミットは、exp/types/stagingという実験的なブランチで行われていることから、Go 1仕様のリリース後も、型システムの堅牢性と正確性を高めるための継続的な開発の一環として行われたと考えられます。

特に、make組み込み関数の引数検証の強化(負の値、長さと容量の入れ替わり)、スライスインデックスの定数評価の改善、そしてメソッドレシーバーの型チェックは、言語の正確なセマンティクスを保証し、開発者がより信頼性の高いコードを書けるようにするために不可欠な変更です。また、重複エラーの削減は、コンパイラのエラーメッセージの質を向上させ、開発者のデバッグ体験を改善する上で重要です。

前提知識の解説

  • Go言語の型システム: Goは静的型付け言語であり、コンパイル時に厳密な型チェックが行われます。型チェッカーは、プログラムが言語の型規則に準拠しているかを検証するコンパイラの重要な部分です。
  • make組み込み関数: Goには、スライス、マップ、チャネルといった組み込み型を初期化・作成するためのmake関数があります。スライスの場合、make([]T, length, capacity)のように、長さと容量を指定できます。
  • スライス: Goのスライスは、配列のセグメントを参照する動的なデータ構造です。スライス式 a[low:high]a[low:high:max] を使用して、既存のスライスや配列から新しいスライスを作成できます。
  • 定数式: Goでは、コンパイル時に評価できる式を定数式と呼びます。これには数値リテラル、constキーワードで宣言された定数、およびそれらを含む算術演算などが含まれます。型チェッカーは、定数式の値が型の制約(例: 負でないこと、範囲内であること)を満たしているかを検証する必要があります。
  • メソッドレシーバー: Goのメソッドは、特定の型に関連付けられた関数です。メソッドを定義する際、func (r ReceiverType) MethodName(...) のようにレシーバーを指定します。レシーバーも通常の変数と同様に型チェックの対象となります。
  • exp/types/staging: Goの標準ライブラリやツールには、新しい機能や大幅な変更を導入する前に、実験的な実装を置くためのexp(experimental)ディレクトリが存在します。stagingはその中でもさらに開発中の段階を示すことが多いです。これは、この型チェッカーがまだ開発途上であり、将来的にGoの公式な型チェッカーに統合される可能性のあるコードであることを示唆しています。
  • 空白識別子 _: Goでは、変数やインポートされたパッケージが使用されない場合にコンパイルエラーになります。これを回避し、意図的に値を破棄するために空白識別子 _ を使用します。

技術的詳細

このコミットは、Go言語の型チェッカーの内部ロジックに複数の重要な変更を加えています。

  1. make組み込み関数の引数検証の強化:

    • src/pkg/exp/types/staging/builtins.gobuiltin 関数内で、make呼び出しの引数(特にスライスの長さと容量)に対する検証がより厳密になりました。
    • 以前は単に整数であるかを確認していましたが、変更後は引数が定数である場合に、その値が負でないか、そして長さが容量を超えていないか(lengthcapacityが入れ替わっていないか)をcompareConst関数を用いて詳細にチェックするようになりました。これにより、コンパイル時に不正なmake呼び出しをより早期に検出できるようになります。
    • テストデータ (builtins.src) には、make([]int, -1, 10)make([]int, 1<<100 + 1, 1<<100) のような不正な呼び出しに対する新しいエラーメッセージが追加されています。
  2. スライスインデックスの定数評価の改善:

    • src/pkg/exp/types/staging/expr.goindex 関数は、スライスインデックスの値を評価します。この関数の戻り値の型が int64 から interface{} に変更されました。これは、Goの定数式が任意精度整数をサポートしているため、int64の範囲を超える大きな定数インデックスも正確に扱えるようにするためです。
    • インデックスが負であるか、または範囲外であるかのチェックも、compareConst関数を使用するように更新され、interface{}型の定数値を適切に比較できるようになりました。
    • スライス式 (e.Low, e.High) の処理においても、lohi変数がinterface{}型になり、inverted slice rangeエラーのチェックもcompareConstを使用するように変更されています。
  3. メソッドレシーバーの型チェック:

    • src/pkg/exp/types/staging/check.goident 関数内で、関数宣言のレシーバー (fdecl.Recv) が存在する場合に check.collectFields(token.FUNC, fdecl.Recv, true) が呼び出されるようになりました。これは、メソッドのレシーバーも通常のフィールドやパラメータと同様に型チェッカーによって適切に処理されることを保証します。これにより、レシーバーの型が有効であるか、そのフィールドがアクセス可能であるかなどのチェックが可能になります。
  4. 重複エラー報告の削減:

    • src/pkg/exp/types/staging/expr.goexpr および typ 関数において、x.mode == invalid のケースが追加され、既にエラーが報告されている無効な式や型については、それ以上のエラー報告を抑制するようになりました。これにより、同じ根本原因から派生する複数のエラーメッセージがユーザーに表示されることを防ぎ、エラー報告の質が向上します。
    • テストデータ (decls0.src, decls1.src) から、冗長な /* ERROR "not a type" */ コメントが削除されているのは、この改善の結果です。
  5. テストデータの更新:

    • 多くのテストファイル (builtins.src, decls0.src, decls1.src, decls2a.src, expr3.src) で、未使用の変数に値を代入する際に _0 := ... のような形式から _ = ... へと変更されています。これは、Goの慣用的な書き方であり、型チェッカーが未使用変数を厳密にチェックするようになったことに対応しています。

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

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

  • src/pkg/exp/types/staging/builtins.go: make組み込み関数の引数検証ロジックの強化。
  • src/pkg/exp/types/staging/check.go: メソッドレシーバーの型チェックロジックの追加。
  • src/pkg/exp/types/staging/expr.go: スライスインデックスの定数評価の改善と、重複エラー報告の削減。

コアとなるコードの解説

src/pkg/exp/types/staging/builtins.go の変更

 // ...
 func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) {
 	// ...
 		}
+		var sizes []interface{} // constant integer arguments, if any
 		for _, arg := range args[1:] {
 			check.expr(x, arg, nil, iota)
-			if !x.isInteger() {
+			if x.isInteger() {
+				if x.mode == constant {
+					if isNegConst(x.val) {
+						check.invalidArg(x.pos(), "%s must not be negative", x)
+						// safe to continue
+					} else {
+						sizes = append(sizes, x.val) // x.val >= 0
+					}
+				}
+			} else {
 				check.invalidArg(x.pos(), "%s must be an integer", x)
 				// safe to continue
 			}
 		}
+		if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) {
+			check.invalidArg(args[1].Pos(), "length and capacity swapped")
+			// safe to continue
+		}
 		x.mode = variable
 		x.typ = typ0
 // ...

この変更では、make関数の引数(特にスライスの長さと容量)が整数であるかだけでなく、定数である場合にはその値が負でないか、そして長さが容量より大きくないか(lengthcapacityが入れ替わっていないか)をcompareConst関数を使って厳密にチェックしています。これにより、コンパイル時に不正なスライス作成を検出できます。

src/pkg/exp/types/staging/check.go の変更

 // ...
 func (check *checker) ident(name *ast.Ident, cycleOk bool) {
 	// ...
 		if fdecl.Recv != nil {
-			// TODO(gri) handle method receiver
+			// TODO(gri) is this good enough for the receiver?
+			check.collectFields(token.FUNC, fdecl.Recv, true)
 		}
 		check.stmt(fdecl.Body)
 // ...

fdecl.Recv != nil のブロック内で check.collectFields が呼び出されるようになりました。これは、メソッドのレシーバーも型チェッカーによって適切に処理され、その型やフィールドが検証されることを意味します。

src/pkg/exp/types/staging/expr.go の変更

 // ...
 func (check *checker) index(index ast.Expr, length int64, iota int) interface{} {
 	var x operand
-	var i int64 // index value, valid if >= 0
 
 	check.expr(&x, index, nil, iota)
 	if !x.isInteger() {
 		check.errorf(x.pos(), "index %s must be integer", &x)
-		return -1
+		return nil
 	}
 	if x.mode != constant {
-		return -1 // we cannot check more
+		return nil // we cannot check more
 	}
 	// x.mode == constant and the index value must be >= 0
 	if isNegConst(x.val) {
 		check.errorf(x.pos(), "index %s must not be negative", &x)
-		return -1
+		return nil
 	}
-	var ok bool
-	if i, ok = x.val.(int64); !ok {
-		// index value doesn't fit into an int64
-		i = length // trigger out of bounds check below if we know length (>= 0)
-	}
-
-	if length >= 0 && i >= length {
+	// x.val >= 0
+	if length >= 0 && compareConst(x.val, length, token.GEQ) {
 		check.errorf(x.pos(), "index %s is out of bounds (>= %d)", &x, length)
-		return -1
+		return nil
 	}
 
-	return i
+	return x.val
 }
 // ...
 func (check *checker) exprOrType(x *operand, e ast.Expr, hint Type, iota int, cy bool) {
 	// ...
 		}
 
-		var lo int64
+		var lo interface{} = zeroConst
 		if e.Low != nil {
 			lo = check.index(e.Low, length, iota)
 		}
 
-		var hi int64 = length
+		var hi interface{}
 		if e.High != nil {
 			hi = check.index(e.High, length, iota)
+		} else if length >= 0 {
+			hi = length
 		}
 
-		if hi >= 0 && lo > hi {
-			check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi)
+		if lo != nil && hi != nil && compareConst(lo, hi, token.GTR) {
+			check.errorf(e.Low.Pos(), "inverted slice range: %v > %v", lo, hi)
 			// ok to continue
 		}
 // ...
 func (check *checker) expr(x *operand, e ast.Expr, hint Type, iota int) {
 	check.exprOrType(x, e, hint, iota, false)
 	switch x.mode {
+	case invalid:
+		// ignore - error reported before
 	case novalue:
 		check.errorf(x.pos(), "%s used as value", x)
-	\tx.mode = invalid
 	case typexpr:
 		check.errorf(x.pos(), "%s is not an expression", x)
-	\tx.mode = invalid
+	default:
+		return
 	}
+	x.mode = invalid
 }
 // ...
 func (check *checker) typ(e ast.Expr, cycleOk bool) Type {
 	var x operand
 	check.exprOrType(&x, e, nil, -1, cycleOk)
-	switch {
-	case x.mode == novalue:
+	switch x.mode {
+	case invalid:
+		// ignore - error reported before
+	case novalue:
 		check.errorf(x.pos(), "%s used as type", &x)
-\t\tx.typ = Typ[Invalid]
-	case x.mode != typexpr:
+\tcase typexpr:
+\t\treturn x.typ
+\tdefault:
 		check.errorf(x.pos(), "%s is not a type", &x)
-\t\tx.typ = Typ[Invalid]
 	}
-	return x.typ
+	return Typ[Invalid]
 }

index関数の戻り値がinterface{}になり、任意精度整数を扱えるようになりました。また、compareConstを使用して定数値を比較することで、より正確な範囲チェックが可能になっています。スライス式においても同様にinterface{}が使用され、inverted slice rangeエラーの報告も改善されています。exprtyp関数では、invalidモードの追加により、既に報告されたエラーに対する重複報告が抑制され、エラーメッセージの質が向上しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード (特にsrc/go/typesパッケージの進化に関する情報)
  • Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://go-review.googlesource.com/ (コミットメッセージに記載されているCLリンク https://golang.org/cl/6709061 は、Gerrit上の変更セットを指します。)
  • Go言語のメーリングリストやフォーラム(過去の議論から仕様変更の背景を推測)
  • Go言語の空白識別子に関する公式ブログ: https://go.dev/doc/effective_go#blank
  • Go言語の定数に関する公式ドキュメント: https://go.dev/ref/spec#Constants
  • Go言語のメソッドに関する公式ドキュメント: https://go.dev/ref/spec#Method_declarations