[インデックス 11796] ファイルの概要
このコミットは、Goコンパイラ(gc
)における曖昧なセレクタ(ambiguous selector
)に関するエラーメッセージの改善を目的としています。具体的には、重複するエラーメッセージの排除と、エラーメッセージに実際の式(expression
)を表示することで、より分かりやすい情報を提供するように変更が加えられています。
コミット
commit 7ae1fe420e708acc62cdacb81a2eec7ed3250277
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 10 22:46:56 2012 -0500
gc: eliminate duplicate ambiguous selector message
Also show actual expression in message when possible.
Fixes #2599.
R=ken2
CC=golang-dev
https://golang.org/cl/5654059
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7ae1fe420e708acc62cdacb81a2eec7ed3250277
元コミット内容
Goコンパイラ: 曖昧なセレクタメッセージの重複を排除
可能な場合は、メッセージに実際の式も表示する。
Issue #2599 を修正。
変更の背景
このコミットは、Go言語のコンパイラ(gc
)が、構造体(struct
)の埋め込み(embedding
)や、同じ名前のフィールドが複数存在するなどの状況で発生する「曖昧なセレクタ」のエラー報告を改善するために行われました。
以前のコンパイラでは、曖昧なセレクタが検出された際に、同じ問題に対して複数のエラーメッセージが出力されたり、エラーメッセージが抽象的で、どの具体的な式が問題を引き起こしているのかが分かりにくいという問題がありました。これは、ユーザーがコードの問題を特定し、修正する上で不便でした。
特に、Go言語の設計思想として、エラーメッセージはユーザーにとって具体的で役立つものであるべきという考え方があります。このコミットは、その思想に基づき、コンパイラが生成するエラーメッセージの品質を向上させることを目的としています。
Fixes #2599
という記述から、この変更がGoのIssueトラッカーに登録されていた特定のバグ報告(Issue 2599)に対応するものであることが分かります。Issue 2599は、まさに曖昧なセレクタに関するエラーメッセージの重複と分かりにくさを指摘するものでした。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。
-
Go言語の構造体と埋め込み (Structs and Embedding): Go言語では、構造体の中に他の構造体を匿名で埋め込むことができます。これにより、埋め込まれた構造体のフィールドやメソッドが、外側の構造体のフィールドやメソッドであるかのように直接アクセスできるようになります。これはコードの再利用性を高める強力な機能ですが、異なる埋め込み構造体が同じ名前のフィールドやメソッドを持っている場合、アクセスが曖昧になる可能性があります。
type A struct { x int } type B struct { x int } type C struct { A B } func main() { var c C // c.x は A.x と B.x のどちらを指すか曖昧になる // fmt.Println(c.x) // これが「ambiguous selector」エラーを引き起こす }
-
セレクタ (Selector): Go言語において、
.
(ドット)演算子を使って構造体のフィールドやメソッドにアクセスする式をセレクタと呼びます。例:obj.field
やobj.method()
。 -
曖昧なセレクタ (Ambiguous Selector): セレクタが、複数の異なるフィールドやメソッドに解決されうる場合に発生するエラーです。前述の構造体の埋め込みが典型的な原因となります。コンパイラは、どのフィールドまたはメソッドを意図しているのかを判断できないため、エラーを報告します。
-
Goコンパイラ (
gc
): Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。gc
は、字句解析、構文解析、型チェック、最適化、コード生成などの複数のフェーズを経てコンパイルを行います。 -
型チェック (Type Checking): コンパイルフェーズの一つで、プログラム中のすべての式や変数の型が、言語の規則に従って正しく使用されているかを検証するプロセスです。曖昧なセレクタの検出は、この型チェックの段階で行われます。
-
抽象構文木 (Abstract Syntax Tree, AST): コンパイラがソースコードを解析して生成する、プログラムの構造を木構造で表現したものです。コンパイラはASTを操作して、型チェックや最適化などを行います。このコミットで登場する
Node
はASTのノードを指します。 -
yyerror
: コンパイラやパーサーの文脈でよく使われるエラー報告関数です。通常、yacc
やbison
のようなパーサー生成ツールによって生成されるコードで使用され、コンパイル時のエラーメッセージを出力します。 -
Sym
(Symbol): コンパイラ内部で、変数名、関数名、型名などの識別子(シンボル)を表すデータ構造です。
技術的詳細
このコミットの技術的な変更は、主にGoコンパイラのsrc/cmd/gc/subr.c
とsrc/cmd/gc/typecheck.c
の2つのファイルに集中しています。これらは、それぞれGoコンパイラのサブルーチンと型チェックロジックを実装している部分です。
変更の核心は、曖昧なセレクタのエラー報告メカニズムの改善にあります。
-
エラーメッセージの重複排除: 以前のコンパイラでは、
adddot
関数(セレクタ解決の一部を担う)とlookdot1
関数(型チェック中にセレクタを検索する)の両方で曖昧なセレクタのエラーが報告される可能性がありました。これにより、同じ問題に対して2つのエラーメッセージが出力されることがありました。 このコミットでは、adddot
関数で曖昧なセレクタが検出された場合、そのノードのn->left
をN
(nilノード)に設定し、関数を早期に終了させることで、typecheck.c
内の後続の処理で再度同じエラーが報告されるのを防いでいます。typecheck
関数内でif(n->left == N) goto error;
というチェックが追加され、adddot
でエラーが処理されたノードは、それ以上型チェックされないようになっています。 -
エラーメッセージの具体化: 以前のエラーメッセージは、
"ambiguous selector %T.%S"
のように、型とシンボル名のみを表示していました。これでは、ユーザーはコードのどの部分が問題なのかを特定しにくい場合がありました。 このコミットでは、lookdot1
関数のシグネチャにNode *errnode
という新しい引数が追加されました。これにより、エラーを報告する際に、問題となっている実際のASTノード(式)を渡すことができるようになりました。yyerror
関数への呼び出しも変更され、yyerror("ambiguous selector %N", errnode);
のように、%N
フォーマット指定子を使ってノード自体を表示できるようになりました。これにより、コンパイラはエラーメッセージにt.x
のような具体的な式を含めることが可能になり、ユーザーはどのセレクタが曖昧であるかを一目で理解できるようになります。lookdot1
関数内では、errnode
が提供されている場合はそれを使用し、そうでない場合は以前と同様に型とシンボル名を使用するフォールバックロジックが実装されています。また、ポインタ型の場合には(%T).%S
のように括弧を追加して、より正確な表現になるように配慮されています。 -
implicitstar
の改善:implicitstar
関数は、Goがポインタの暗黙的なデリファレンス(間接参照)を行う際に使用されます。例えば、p.field
という式でp
がポインタの場合、Goは自動的に(*p).field
として扱います。このコミットでは、implicitstar
によって生成されたノードにn->implicit = 1;
というフラグが設定されるようになりました。これは、デバッグやエラー報告の際に、そのノードがコンパイラによって暗黙的に挿入されたものであることを識別するために使用される可能性があります。
これらの変更により、Goコンパイラはよりユーザーフレンドリーなエラーメッセージを提供し、開発者が曖昧なセレクタの問題を迅速に診断し、修正できるようになりました。
コアとなるコードの変更箇所
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2170,8 +2170,11 @@ adddot(Node *n)
goto ret;
out:
- if(c > 1)
- yyerror("ambiguous selector %T.%S", t, s);
+ if(c > 1) {
+ yyerror("ambiguous selector %N", n);
+ n->left = N;
+ return n;
+ }
// rebuild elided dots
for(c=d-1; c>=0; c--)
adddot
関数内で、曖昧なセレクタが検出された場合(c > 1
)、yyerror
のメッセージが%T.%S
から%N
に変更され、実際のノードn
が表示されるようになりました。- エラー報告後、
n->left = N;
を設定し、n
を返すことで、このノードが既にエラー処理されたことを示し、後続の型チェックで重複エラーが発生しないようにしています。
src/cmd/gc/typecheck.c
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -20,7 +20,7 @@ static int twoarg(Node*);
static int lookdot(Node*, Type*, int);
static int looktypedot(Node*, Type*, int);
static void typecheckaste(int, Node*, int, Type*, NodeList*, char*);
-static Type* lookdot1(Sym *s, Type *t, Type *f, int);
+static Type* lookdot1(Node*, Sym *s, Type *t, Type *f, int);
static int nokeys(NodeList*);
static void typecheckcomplit(Node**);
static void typecheckas2(Node*);
@@ -581,6 +581,8 @@ reswitch:
case OXDOT:
n = adddot(n);
n->op = ODOT;
+ if(n->left == N)
+ goto error;
// fall through
case ODOT:
typecheck(&n->left, Erv|Etype);
@@ -1495,6 +1497,7 @@ implicitstar(Node **nn)
if(!isfixedarray(t))
return;
n = nod(OIND, n, N);
+ n->implicit = 1;
typecheck(&n, Erv);
*nn = n;
}
@@ -1554,7 +1557,7 @@ twoarg(Node *n)
}
static Type*
-lookdot1(Sym *s, Type *t, Type *f, int dostrcmp)
+lookdot1(Node *errnode, Sym *s, Type *t, Type *f, int dostrcmp)
{
Type *r;
@@ -1565,7 +1568,12 @@ lookdot1(Sym *s, Type *t, Type *f, int dostrcmp)
if(f->sym != s)
continue;
if(r != T) {
- yyerror("ambiguous selector %T.%S", t, s);
+ if(errnode)
+ yyerror("ambiguous selector %N", errnode);
+ else if(isptr[t->etype])
+ yyerror("ambiguous selector (%T).%S", t, s);
+ else
+ yyerror("ambiguous selector %T.%S", t, s);
break;
}
r = f;
@@ -1582,7 +1590,7 @@ looktypedot(Node *n, Type *t, int dostrcmp)
s = n->right->sym;
if(t->etype == TINTER) {
- f1 = lookdot1(s, t, t->type, dostrcmp);
+ f1 = lookdot1(n, s, t, t->type, dostrcmp);
if(f1 == T)
return 0;
@@ -1604,7 +1612,7 @@ looktypedot(Node *n, Type *t, int dostrcmp)
return 0;
expandmeth(f2->sym, f2);
- f2 = lookdot1(s, f2, f2->xmethod, dostrcmp);
+ f2 = lookdot1(n, s, f2, f2->xmethod, dostrcmp);
if(f2 == T)
return 0;
@@ -1643,7 +1651,7 @@ lookdot(Node *n, Type *t, int dostrcmp)
dowidth(t);
f1 = T;
if(t->etype == TSTRUCT || t->etype == TINTER)
- f1 = lookdot1(s, t, t->type, dostrcmp);
+ f1 = lookdot1(n, s, t, t->type, dostrcmp);
f2 = T;
if(n->left->type == t || n->left->type->sym == S) {
@@ -1651,7 +1659,7 @@ lookdot(Node *n, Type *t, int dostrcmp)
if(f2 != T) {
// Use f2->method, not f2->xmethod: adddot has
// already inserted all the necessary embedded dots.
- f2 = lookdot1(s, f2, f2->method, dostrcmp);
+ f2 = lookdot1(n, s, f2, f2->method, dostrcmp);
}
}
@@ -1666,6 +1674,7 @@ lookdot(Node *n, Type *t, int dostrcmp)
if(t->etype == TINTER) {
if(isptr[n->left->type->etype]) {
n->left = nod(OIND, n->left, N); // implicitstar
+ n->left->implicit = 1;
typecheck(&n->left, Erv);
}
n->op = ODOTINTER;
@@ -2194,7 +2203,7 @@ typecheckcomplit(Node **np)
if(s->pkg != localpkg && exportname(s->name))
s = lookup(s->name);
- f = lookdot1(s, t, t->type, 0);
+ f = lookdot1(nil, s, t, t->type, 0);
if(f == nil) {
yyerror("unknown %T field '%S' in struct literal", t, s);
continue;
lookdot1
関数のシグネチャが変更され、Node *errnode
が最初の引数として追加されました。lookdot1
内のyyerror
呼び出しが、errnode
の有無に応じて%N
または%T.%S
を使用するように条件分岐が追加されました。ポインタ型の場合は(%T).%S
という形式も追加されています。typecheck
関数内のOXDOT
およびODOT
ケースで、adddot
がエラーを報告した場合にn->left == N
をチェックし、goto error;
で早期にエラー処理を終了するロジックが追加されました。implicitstar
関数内で、暗黙的に挿入されたノードにn->implicit = 1;
が設定されるようになりました。looktypedot
、lookdot
、typecheckcomplit
など、lookdot1
を呼び出すすべての箇所で、新しいerrnode
引数(通常は現在のノードn
またはnil
)が渡されるように変更されました。
test/fixedbugs/bug412.go
--- /dev/null
+++ b/test/fixedbugs/bug412.go
@@ -0,0 +1,16 @@
+// errchk $G $D/$F.go
+
+// 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.
+
+package p
+
+type t struct {
+ x int // ERROR "duplicate field x"
+ x int
+}
+
+func f(t *t) int {
+ return t.x // ERROR "ambiguous selector t.x"
+}
- この新しいテストファイルは、重複するフィールドを持つ構造体
t
を定義し、そのフィールドx
にアクセスすることで、duplicate field x
とambiguous selector t.x
というエラーが正しく報告されることを検証しています。特に、ambiguous selector t.x
というメッセージが、具体的な式t.x
を含んでいることを確認するためのテストです。
コアとなるコードの解説
このコミットの主要な変更は、Goコンパイラの型チェックフェーズにおけるセレクタ解決とエラー報告のロジックにあります。
-
adddot
関数の役割と変更:adddot
関数は、Goのセレクタ式(例:a.b.c
)を処理する際に、埋め込み構造体を介したフィールドアクセスを解決する役割を担っています。例えば、a
が構造体で、その中にb
という構造体が埋め込まれており、b
の中にc
というフィールドがある場合、a.c
という式は内部的にa.b.c
として解決されます。 この関数内で、もしc > 1
(つまり、同じ名前のフィールドやメソッドが複数見つかり、解決が曖昧である場合)であれば、以前は単にyyerror("ambiguous selector %T.%S", t, s);
とエラーを報告していました。 変更後、yyerror("ambiguous selector %N", n);
とすることで、エラーメッセージに問題のセレクタ式全体(例:t.x
)を含めることができるようになりました。さらに重要なのは、n->left = N;
とreturn n;
を追加した点です。これは、adddot
が曖昧さを検出してエラーを報告した時点で、そのノードに対するそれ以上の型チェックは無意味であり、重複するエラーメッセージを避けるために、後続の処理をスキップさせるためのマーカーとして機能します。 -
lookdot1
関数の役割と変更:lookdot1
関数は、特定のシンボル(フィールド名やメソッド名)が与えられた型(構造体やインターフェース)の中に存在するかどうかを検索し、その型情報を返す役割を担っています。この関数は、型チェックの過程で頻繁に呼び出され、セレクタの解決において中心的な役割を果たします。 以前のシグネチャはlookdot1(Sym *s, Type *t, Type *f, int dostrcmp)
でした。変更後、Node *errnode
が追加され、lookdot1(Node *errnode, Sym *s, Type *t, Type *f, int dostrcmp)
となりました。このerrnode
は、エラーメッセージを生成する際に使用される、問題のセレクタ式を表すASTノードです。if(r != T)
のブロック内で、曖昧なセレクタが検出された場合(r
が既に設定されているのに別の候補が見つかった場合)、errnode
がnil
でなければyyerror("ambiguous selector %N", errnode);
が呼び出されます。これにより、adddot
と同様に、具体的な式がエラーメッセージに表示されます。errnode
がnil
の場合や、ポインタ型の場合の特別なメッセージングも追加され、エラーメッセージの精度が向上しています。 -
型チェックフローの調整:
typecheck.c
内のtypecheck
関数では、OXDOT
(セレクタ式)やODOT
(ドットアクセス)のケースで、adddot
関数が呼び出された後にif(n->left == N) goto error;
というチェックが追加されました。これは、adddot
が曖昧なセレクタを検出してn->left = N
を設定した場合、そのノードは既にエラー処理済みであるため、これ以上型チェックを続行せずにエラーパスに分岐させるためのものです。これにより、同じ曖昧なセレクタに対して複数のエラーメッセージが出力されるのを防ぎます。
これらの変更は、Goコンパイラの内部動作をより堅牢にし、同時に開発者にとってより有益なエラーメッセージを提供することで、開発体験を向上させることに貢献しています。
関連リンク
- Go Issue 2599: https://github.com/golang/go/issues/2599 このコミットが修正した元のバグ報告。曖昧なセレクタのエラーメッセージに関する問題が議論されています。
- Go Code Review 5654059: https://golang.org/cl/5654059 このコミットのGo公式コードレビューページ。変更の詳細な議論やレビューコメントが確認できます。
参考にした情報源リンク
- Go言語の公式ドキュメント: 構造体と埋め込みに関する情報
- Goコンパイラのソースコード:
src/cmd/gc/
ディレクトリ内のファイル構造と関数定義 - Go言語のIssueトラッカー: 過去のバグ報告と議論
- Go言語のコードレビューシステム: 変更の背景と意図に関する議論
- コンパイラの設計と実装に関する一般的な知識(AST、型チェック、エラー報告など)