[インデックス 13554] ファイルの概要
このコミットは、Go言語の実験的な型チェッカーである exp/types
パッケージにおける重要な改善を導入しています。主な目的は、型宣言における循環参照(サイクル)の検出機能を再度有効にし、エラーメッセージの出力順序を再現可能にすることです。これにより、型チェックの信頼性とテストの安定性が向上します。
コミット
commit a4ac339f438f07e815b14338c5cccb60eeaac0ad
Author: Robert Griesemer <gri@golang.org>
Date: Wed Aug 1 16:37:06 2012 -0700
exp/types: enable cycle checks again
Process a package's object in a reproducible
order (rather then in map order) so that we
get error messages in reproducible order.
R=r
CC=golang-dev
https://golang.org/cl/6449076
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a4ac339f438f07e815b14338c5cccb60eeaac0ad
元コミット内容
exp/types: enable cycle checks again
Process a package's object in a reproducible order (rather then in map order) so that we get error messages in reproducible order.
変更の背景
Go言語の型チェッカーは、プログラム内の型定義が正しいことを検証する役割を担っています。型定義の中には、互いに参照し合うことで循環参照(サイクル)を形成する場合があります。例えば、type A B
と type B A
のように定義されると、AとBは互いに依存し、無限ループのような状態になります。このような不正な循環は、コンパイルエラーとして報告されるべきです。
しかし、このコミット以前の exp/types
パッケージでは、型チェックの過程でパッケージ内のオブジェクト(型や変数など)を処理する際に、Goのマップ(map
)のイテレーション順序に依存していました。Goのマップは意図的にイテレーション順序が保証されていません。このため、同じソースコードを複数回コンパイルしても、マップのイテレーション順序が異なることで、循環参照のエラーが報告される位置や順序が非決定論的になる問題がありました。
この非決定論的な挙動は、特にテストの際に問題となります。テストが不安定になり、CI/CDパイプラインでの再現性が損なわれるため、開発効率が低下します。この問題を解決するため、以前は循環参照のチェック自体が一時的に無効化されていました。
このコミットの背景には、以下の2つの主要な動機があります。
- 循環参照チェックの再有効化: 型システムの健全性を保つために不可欠な循環参照の検出機能を再度有効にすること。
- エラーメッセージの再現性確保: 非決定論的なエラー報告を排除し、テストの安定性と開発者のデバッグ体験を向上させること。
前提知識の解説
- Go言語の
exp/types
パッケージ: これはGo言語の公式リポジトリに含まれる実験的な型チェッカーです。Goコンパイラの型チェック部分のプロトタイプや研究のために使用されていました。Go 1.5で導入された公式のgo/types
パッケージの前身にあたります。 - 抽象構文木 (AST): プログラムのソースコードを解析して得られる、ツリー構造の表現です。型チェッカーはASTを走査して、型情報を収集し、型規則に違反がないかを確認します。
- 型システムと循環参照: 型システムは、プログラムの各部分がどのような種類の値を扱うかを定義する規則の集合です。循環参照は、型定義が直接的または間接的に自分自身を参照する状況を指します。これは通常、不正な状態であり、コンパイル時に検出されるべきエラーです。
- 例:
type A struct { B B }; type B struct { A A }
(直接的な循環) - 例:
type A B; type B C; type C A
(間接的な循環)
- 例:
- Goのマップのイテレーション順序: Go言語の組み込み型である
map
は、キーと値のペアを格納するハッシュテーブルです。Goの仕様では、マップのイテレーション(for range
ループなど)の順序は保証されていません。これは、マップの実装がハッシュテーブルの内部構造に依存するためであり、異なる実行環境やGoのバージョン、あるいは同じプログラムの異なる実行でも順序が変わる可能性があります。 - 再現可能なエラーメッセージ: コンパイラやリンターなどのツールにおいて、同じ入力に対して常に同じエラーメッセージが、同じ順序で、同じ位置に報告されることは非常に重要です。これにより、自動テストの信頼性が高まり、開発者が問題を特定しやすくなります。
技術的詳細
このコミットの技術的な核心は、Goのマップの非決定論的なイテレーション順序に起因する問題を解決し、型チェックの再現性を確保することにあります。
-
オブジェクトのソート処理の導入:
src/pkg/exp/types/check.go
のCheck
関数内で、パッケージスコープ内のすべてのオブジェクト(pkg.Scope.Objects
)を一時的なObjList
にコピーしています。- この
ObjList
は、list.Sort()
メソッドを呼び出すことで、オブジェクトを決定論的な順序(おそらく名前順や宣言位置順)でソートします。 - これにより、以降の型チェック処理が、マップの非決定論的なイテレーション順序に左右されず、常に同じ順序でオブジェクトを処理できるようになります。コミットメッセージにある「this is only needed for testing」というコメントは、このソートが主にテストの再現性確保のために導入されたことを示唆しています。
-
循環参照エラーメッセージの再有効化:
- 以前は、非決定論的なエラー報告を避けるためにコメントアウトされていた、より詳細な循環参照エラーメッセージ (
illegal cycle in declaration of %s
) が再度有効化されました。 - 具体的には、
c.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)
が使用されるようになり、どの型宣言で循環が発生したかを明確に報告できるようになりました。
- 以前は、非決定論的なエラー報告を避けるためにコメントアウトされていた、より詳細な循環参照エラーメッセージ (
-
テストデータの更新:
src/pkg/exp/types/testdata/test0.src
内のテストケースが更新され、以前は/* DISABLED "illegal cycle" */
とマークされていた箇所が/* ERROR "illegal cycle" */
に変更されています。- これは、循環参照チェックが再び有効になり、これらのテストケースでエラーが期待されるようになったことを意味します。また、エラーメッセージの再現性が確保されたことで、テストが安定して実行できるようになりました。
これらの変更により、exp/types
パッケージは、より堅牢で予測可能な型チェック動作を提供するようになりました。
コアとなるコードの変更箇所
src/pkg/exp/types/check.go
--- a/src/pkg/exp/types/check.go
+++ b/src/pkg/exp/types/check.go
@@ -112,10 +113,7 @@ func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) {
\t\t}\n \tc.checkObj(obj, cycleOk)\n \tif !cycleOk && obj.Type.(*Name).Underlying == nil {\n-\t\t\t// TODO(gri) Enable this message again once its position\n-\t\t\t// is independent of the underlying map implementation.\n-\t\t\t// msg := c.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)\n-\t\t\tmsg := "illegal cycle"\n+\t\t\tmsg := c.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)\n \t\treturn &Bad{Msg: msg}\n \t}\n \treturn obj.Type.(Type)\n@@ -227,11 +225,25 @@ func (c *checker) checkObj(obj *ast.Object, ref bool) {\n // there are errors.\n //\n func Check(fset *token.FileSet, pkg *ast.Package) (types map[ast.Expr]Type, err error) {\n+\t// Sort objects so that we get reproducible error\n+\t// positions (this is only needed for testing).\n+\t// TODO(gri): Consider ast.Scope implementation that\n+\t// provides both a list and a map for fast lookup.\n+\t// Would permit the use of scopes instead of ObjMaps\n+\t// elsewhere.\n+\tlist := make(ObjList, len(pkg.Scope.Objects))\n+\ti := 0\n+\tfor _, obj := range pkg.Scope.Objects {\n+\t\tlist[i] = obj\n+\t\ti++\n+\t}\n+\tlist.Sort()\n+\n var c checker\n c.fset = fset\n c.types = make(map[ast.Expr]Type)\n \n-\tfor _, obj := range pkg.Scope.Objects {\n+\tfor _, obj := range list {\n \tc.checkObj(obj, false)\n }\n \n```
### `src/pkg/exp/types/testdata/test0.src`
```diff
--- a/src/pkg/exp/types/testdata/test0.src
+++ b/src/pkg/exp/types/testdata/test0.src
@@ -44,15 +44,15 @@ type (
type (
Pi pi /* ERROR "not a type" */
-\ta /* DISABLED "illegal cycle" */ a
+\ta /* ERROR "illegal cycle" */ a
a /* ERROR "redeclared" */ int
// where the cycle error appears depends on the\n // order in which declarations are processed\n // (which depends on the order in which a map\n // is iterated through)\n-\tb c
-\tc /* DISABLED "illegal cycle" */ d
+\tb /* ERROR "illegal cycle" */ c
+\tc d
d e
e b
@@ -79,13 +79,13 @@ type (
\tS3 struct {\n \t\tx S2\n \t}\n-\tS4/* DISABLED "illegal cycle" */ struct {\n+\tS4/* ERROR "illegal cycle" */ struct {\n \t\tS4\n \t}\n-\tS5 struct {\n+\tS5 /* ERROR "illegal cycle" */ struct {\
\t\tS6\n \t}\n-\tS6 /* DISABLED "illegal cycle" */ struct {\
+\tS6 struct {\
\t\tfield S7\n \t}\n \tS7 struct {\
@@ -96,8 +96,8 @@ type (
\tL2 []int
A1 [10]int
-\tA2 /* DISABLED "illegal cycle" */ [10]A2
-\tA3 /* DISABLED "illegal cycle" */ [10]struct {\
+\tA2 /* ERROR "illegal cycle" */ [10]A2
+\tA3 /* ERROR "illegal cycle" */ [10]struct {\
\t\tx A4\n \t}\n \tA4 [10]A3\
@@ -132,17 +132,21 @@ type (
\t\tI1\n \t\tI1\n \t}\n-\tI8 /* DISABLED "illegal cycle" */ interface {\
+\tI8 /* ERROR "illegal cycle" */ interface {\
\t\tI8\n \t}\n-\tI9 /* DISABLED "illegal cycle" */ interface {\
+\t// Use I09 (rather than I9) because it appears lexically before\n+\t// I10 so that we get the illegal cycle here rather then in the\n+\t// declaration of I10. If the implementation sorts by position\n+\t// rather than name, the error message will still be here.\n+\tI09 /* ERROR "illegal cycle" */ interface {\
\t\tI10\n \t}\n \tI10 interface {\
\t\tI11\n \t}\n \tI11 interface {\
-\t\t\tI9\
+\t\t\tI09\
\t}\n \n C1 chan int\
コアとなるコードの解説
src/pkg/exp/types/check.go
の変更点
-
循環参照エラーメッセージの再有効化:
makeType
関数内で、型宣言における循環参照が検出された際のエラーメッセージが変更されました。- 以前は
msg := "illegal cycle"
という一般的なメッセージが使用されていましたが、msg := c.errorf(obj.Pos(), "illegal cycle in declaration of %s", obj.Name)
に変更され、どの型 (obj.Name
) で循環が発生したかを具体的に報告するようになりました。 - この変更は、以前のコードに存在した
TODO(gri)
コメント(「このメッセージを再度有効にする。その位置が基盤となるマップの実装に依存しないようになったら」)に対応するものです。これは、マップのイテレーション順序の非決定性が解決されたため、より詳細なエラーメッセージを安全に表示できるようになったことを示しています。
-
オブジェクト処理順序の再現性確保:
Check
関数は、パッケージ全体の型チェックを開始するエントリポイントです。- この関数内で、
pkg.Scope.Objects
(パッケージスコープ内のすべてのオブジェクトを含むマップ) からObjList
というスライスを作成し、そこにオブジェクトをコピーしています。 list.Sort()
を呼び出すことで、このスライス内のオブジェクトが決定論的な順序でソートされます。- その後のループ
for _, obj := range list
では、ソートされたlist
をイテレートすることで、checkObj
関数が常に同じ順序でオブジェクトを処理するようになります。 - これにより、型チェック中に発生するエラーメッセージの順序が、実行ごとに変動することがなくなり、再現性が保証されます。
src/pkg/exp/types/testdata/test0.src
の変更点
- このファイルは、
exp/types
パッケージの型チェッカーのテストデータとして使用されるGoのソースコードです。 - 変更のほとんどは、コメント
/* DISABLED "illegal cycle" */
を/* ERROR "illegal cycle" */
に変更するものです。 - これは、
check.go
で循環参照チェックが再有効化されたことに伴い、これらのテストケースが実際に循環参照エラーを発生させ、それが期待される結果であることを示しています。 - 特に、
I09
とI10
、I11
のインターフェースの循環参照に関するコメントは、ソートによってエラーの報告位置が安定したことを示唆しています。以前はI9
とI10
の順序によってエラー位置が変動する可能性がありましたが、I09
とすることで字句順序が安定し、エラーがI09
で報告されることが期待されています。
これらの変更は、Goの型チェッカーの堅牢性とテスト可能性を大幅に向上させるものでした。
関連リンク
- Go CL 6449076: https://golang.org/cl/6449076
参考にした情報源リンク
- Go言語のマップの順序に関する議論: https://go.dev/blog/maps (Go公式ブログのマップに関する記事)
- Go言語の型システムと型チェックの概念 (一般的な情報源): https://go.dev/doc/effective_go#interfaces (Effective Goのインターフェースに関するセクションなど、Goの型に関する一般的なドキュメント)
- Go言語のASTパッケージ: https://pkg.go.dev/go/ast
- Go言語のgo/typesパッケージ (exp/typesの後継): https://pkg.go.dev/go/types