[インデックス 13717] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における型チェックの挙動を修正するものです。具体的には、不正な型宣言、特に再帰的な型定義が正しくない場合に、その型を「壊れている」と明示的にマークすることで、誤った「無効な再帰型」エラーの発生を防ぎ、さらにその不正な型が後続のコードで使用された際に、コンパイラが余計なエラーを出力しないようにします。
コミット
commit 85ce3c724167f82793c0c2be0edf611c70cede49
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Fri Aug 31 13:02:29 2012 -0400
cmd/gc: mark broken type declarations as broken.
This fixes a spurious 'invalid recursive type' error, and stops the compiler from emitting errors on uses of the invalid type.
Fixes #3766.
R=golang-dev, dave, minux.ma, rsc
CC=golang-dev
https://golang.org/cl/6443100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/85ce3c724167f82793c0c2be0edf611c70cede49
元コミット内容
このコミットは、Goコンパイラ(cmd/gc
)の型チェックロジックを修正し、不正な型宣言を適切に処理するようにします。これにより、誤って「無効な再帰型」エラーが発生するのを防ぎ、また、一度不正と判断された型がコードの他の場所で使用された際に、コンパイラが追加のエラーを生成するのを停止させます。
変更の背景
この変更は、Go言語のIssue 3766 (https://github.com/golang/go/issues/3766) に対応するものです。このIssueでは、特定の不正な型宣言(例: type T x.T
のように未定義の型を参照する宣言)に対して、Goコンパイラが「invalid recursive type」(無効な再帰型)という誤ったエラーメッセージを出力するという問題が報告されていました。
本来、type T x.T
のような宣言は、x
が未定義であるため「undefined」(未定義)エラーを出すべきです。しかし、コンパイラの型チェックロジックが、このような不正な宣言を再帰型と誤認し、不適切なエラーメッセージを生成していました。さらに、一度この誤ったエラーが出た後も、コンパイラがその不正な型を「有効な型」として扱おうとし、その型がコードの他の場所で使われるたびに、関連する追加のエラーを吐き出すという問題も抱えていました。
このコミットの目的は、以下の2点です。
- 不正な型宣言に対して、より正確なエラーメッセージ(例: "undefined")を出力するようにする。
- 一度不正と判断された型は、それ以降の型チェックプロセスで「壊れた型」としてマークし、その型が使用されても追加の誤ったエラーが出ないようにする。これにより、コンパイラの出力がよりクリーンになり、開発者が問題の根本原因を特定しやすくなります。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの概念と型システムに関する知識が必要です。
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラの一つであり、Goソースコードを機械語に変換する主要なツールです。型チェック、構文解析、最適化、コード生成など、コンパイルプロセスの多くの段階を担当します。
型システムと型チェック
Goは静的型付け言語であり、プログラムの実行前にすべての変数の型が決定されます。型チェックは、プログラムが型の規則に違反していないかを確認するプロセスです。これにより、多くのバグが早期に発見され、プログラムの信頼性が向上します。
型宣言
Goでは、type
キーワードを使用して新しい型を宣言できます。
例:
type MyInt int // int型を基にした新しい型MyInt
type MyStruct struct { // 構造体型
Field1 int
Field2 string
}
再帰型
再帰型とは、型定義の中に自分自身、または自分自身を含む別の型が参照される型のことです。これは、リンクリストやツリー構造のようなデータ構造を表現する際によく使用されます。 例:
type Node struct {
Value int
Next *Node // Node型の中にNode型へのポインタが含まれる
}
再帰型は正しく定義されないと無限ループに陥る可能性があるため、コンパイラは厳密なチェックを行います。
Node
構造体と Type
構造体 (コンパイラ内部)
Goコンパイラの内部では、ソースコードの各要素(変数、関数、型など)が抽象構文木(AST)のノードとして表現されます。これらのノードは、Node
構造体で表されることが多いです。
また、コンパイラが認識する各型は、Type
構造体で表現されます。型チェックの過程で、Node
に関連付けられた型情報が Type
構造体として設定されます。
T
定数 (コンパイラ内部)
Goコンパイラの内部では、T
という特別な Type
ポインタが定義されていることがあります。これは、無効な型、エラーのある型、またはまだ型が決定されていないプレースホルダー型を示すために使用されます。このコミットでは、不正な型宣言を検出した際に、そのノードの型をこの T
に設定することで、「壊れた型」としてマークする役割を果たしています。
typecheckdeftype
関数
src/cmd/gc/typecheck.c
に存在する typecheckdeftype
関数は、Goコンパイラの型チェックフェーズの一部であり、type
キーワードによる新しい型定義をチェックする役割を担っています。この関数は、宣言された型の構造を解析し、その型が有効であるか、再帰的であるか、または他の型規則に違反していないかを確認します。
n->diag
フラグ
コンパイラ内部の Node
構造体には、diag
というフィールドが存在することがあります。これは、そのノードが既に診断(エラーまたは警告)を生成したかどうかを示すフラグです。このフラグがセットされている場合、コンパイラはそのノードに対して追加のエラーメッセージを生成しないように制御できます。
技術的詳細
このコミットの技術的な核心は、src/cmd/gc/typecheck.c
ファイル内の typecheckdeftype
関数における変更です。
変更前は、typecheckdeftype
関数内で型チェックが行われ、もし型が不正(t == T
、ここで T
は無効な型を示す内部定数)であると判断された場合、n->diag = 1;
を設定して、そのノードが既に診断済みであることをマークしていました。しかし、この時点では n->type
(ノードの型情報) は更新されていませんでした。
変更後のコードでは、n->diag = 1;
の直後に n->type = T;
という行が追加されています。
// src/cmd/gc/typecheck.c
typecheckdeftype(Node *n)
{
// ...
typecheck(&n->ntype, Etype); // n->ntype の型チェック
if((t = n->ntype->type) == T) { // n->ntype の型が不正 (T) である場合
n->diag = 1; // このノードは診断済みとマーク
n->type = T; // ★追加された行: ノード自身の型を不正な型 (T) に設定
goto ret;
}
// ...
}
この一行の追加がもたらす効果は以下の通りです。
- 不正な型の明示的なマーク:
n->type = T;
を追加することで、type T x.T
のような不正な型宣言が検出された際に、その宣言ノードn
自体の型情報が、コンパイラ内部で「無効な型」を示すT
に明示的に設定されます。 - 後続の誤ったエラーの抑制: 以前は、
n->diag = 1;
が設定されていても、n->type
がまだ有効な型として扱われる可能性がありました。そのため、コンパイラの他の部分がこの不正な型を参照しようとした際に、その不正な型を基にしたさらなる型チェックが行われ、結果として「invalid recursive type」のような誤った、あるいは余計なエラーが生成されることがありました。n->type = T;
と明示的に設定することで、コンパイラの他の部分がこのノードの型を参照した際に、それが「無効な型」であることを即座に認識し、それ以上の無意味な型チェックやエラー生成を停止させることができます。 - 正確なエラーメッセージの表示: この修正により、コンパイラは不正な型宣言の根本原因(例: 未定義の型を参照していること)を正確に診断し、適切なエラーメッセージ(例: "undefined")を出力するようになります。そして、その不正な型が後続のコードで使われても、追加の誤ったエラーは出なくなります。
test/fixedbugs/bug451.go
の変更も重要です。
// run
から// errorcheck
への変更は、このテストがコンパイル時に特定のエラーメッセージが出力されることを期待するテストであることを示しています。- 元のテストコード(Issue 3835に関連する算術最適化のテスト)が削除され、
type T x.T // ERROR "undefined"
という新しいテストケースが追加されました。この行は、x
が未定義であるため、T
の定義が不正であることを示しています。// ERROR "undefined"
は、この行で「undefined」というエラーメッセージが出力されることを期待していることを示します。 // bogus "invalid recursive type"
というコメントは、この修正前には「invalid recursive type」という誤ったエラーが出ていたことを示唆しており、この修正によってその誤ったエラーが解消されることを確認するためのものです。
コアとなるコードの変更箇所
src/cmd/gc/typecheck.c
ファイルの typecheckdeftype
関数内。
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -2715,6 +2715,7 @@ typecheckdeftype(Node *n)
typecheck(&n->ntype, Etype);
if((t = n->ntype->type) == T) {
n->diag = 1;
+ n->type = T; // この行が追加された
goto ret;
}
if(n->type == T) {
test/fixedbugs/bug451.go
ファイルの変更。
--- a/test/fixedbugs/bug451.go
+++ b/test/fixedbugs/bug451.go
@@ -1,38 +1,11 @@
-// run
+// errorcheck
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Issue 3835: 8g tries to optimize arithmetic involving integer
-// constants, but can run out of registers in the process.
-
package main
-var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G int
-
-func foo() int {
- return a + 1 + b + 2 + c + 3 + d + 4 + e + 5 + f + 6 + g + 7 + h + 8 + i + 9 + j + 10 +\
- k + 1 + l + 2 + m + 3 + n + 4 + o + 5 + p + 6 + q + 7 + r + 8 + s + 9 + t + 10 +\
- u + 1 + v + 2 + w + 3 + x + 4 + y + 5 + z + 6 + A + 7 + B + 8 + C + 9 + D + 10 +\
- E + 1 + F + 2 + G + 3
-}
-
-func bar() int8 {
- var (
- W int16
- X int32
- Y int32
- Z int32
- )
- return int8(W+int16(X+3)+3) * int8(Y+3+Z*3)
-}
+type T x.T // ERROR "undefined"
-func main() {
- if foo() == 0 {
- panic("foo")
- }
- if bar() == 0 {
- panic("bar")
- }
-}
+// bogus "invalid recursive type"
コアとなるコードの解説
src/cmd/gc/typecheck.c
の変更は、Goコンパイラの型チェックロジックにおける重要な改善です。
typecheckdeftype
関数は、type
キーワードで定義される新しい型を処理します。
追加された n->type = T;
の行は、if((t = n->ntype->type) == T)
という条件ブロックの中にあります。この条件は、n->ntype
(つまり、定義しようとしている型の基底型や参照している型) の型チェックの結果が T
(無効な型を示す内部定数) であった場合に真となります。
つまり、このコードは以下のように動作します。
- 新しい型
n
の定義をチェックする際に、その定義が参照している型 (n->ntype
) が既に不正であると判断された場合(例:type T x.T
のx.T
が未定義である場合)。 n->diag = 1;
を設定し、この型定義ノードn
に対して既に診断(エラー)が発行されたことをマークします。これにより、同じ問題に対して複数のエラーメッセージが重複して出力されるのを防ぎます。n->type = T;
を追加: ここがこのコミットの肝です。不正な型定義ノードn
自体の型を、明示的にT
(無効な型) に設定します。これにより、コンパイラの他の部分がこのn
を参照しようとした際に、それが「壊れた型」であることをすぐに認識できます。goto ret;
で関数の残りの処理をスキップし、型チェックを終了します。
この変更により、コンパイラは不正な型宣言を早期に「壊れた型」としてマークし、その後の処理でこの「壊れた型」が参照されても、無意味な追加エラー(特に誤った「invalid recursive type」エラー)を生成しなくなります。結果として、コンパイラの出力がより正確で、開発者にとって理解しやすいものになります。
test/fixedbugs/bug451.go
の変更は、この修正が意図通りに機能することを確認するためのテストケースの更新です。新しいテストケース type T x.T // ERROR "undefined"
は、未定義の型 x.T
を参照する不正な型宣言が、正しく「undefined」エラーとして検出されることを検証します。また、// bogus "invalid recursive type"
というコメントは、この修正が以前の誤ったエラーメッセージを修正したことを示しています。
関連リンク
- Go Issue 3766:
cmd/gc: spurious 'invalid recursive type' error
- https://github.com/golang/go/issues/3766 - Go CL 6443100:
cmd/gc: mark broken type declarations as broken.
- https://golang.org/cl/6443100
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/gc/typecheck.c
および関連ファイル) - Go言語のIssueトラッカー (Issue 3766)
- Go言語のコードレビューシステム (CL 6443100)
- Go言語のコンパイラに関する一般的なドキュメントや解説記事
- 静的型付け言語における型チェックの概念に関する一般的な情報
- 抽象構文木 (AST) とコンパイラの内部表現に関する一般的な情報
- 再帰型に関する一般的な情報