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

[インデックス 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.fieldobj.method()

  • 曖昧なセレクタ (Ambiguous Selector): セレクタが、複数の異なるフィールドやメソッドに解決されうる場合に発生するエラーです。前述の構造体の埋め込みが典型的な原因となります。コンパイラは、どのフィールドまたはメソッドを意図しているのかを判断できないため、エラーを報告します。

  • Goコンパイラ (gc): Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。gcは、字句解析、構文解析、型チェック、最適化、コード生成などの複数のフェーズを経てコンパイルを行います。

  • 型チェック (Type Checking): コンパイルフェーズの一つで、プログラム中のすべての式や変数の型が、言語の規則に従って正しく使用されているかを検証するプロセスです。曖昧なセレクタの検出は、この型チェックの段階で行われます。

  • 抽象構文木 (Abstract Syntax Tree, AST): コンパイラがソースコードを解析して生成する、プログラムの構造を木構造で表現したものです。コンパイラはASTを操作して、型チェックや最適化などを行います。このコミットで登場する Node はASTのノードを指します。

  • yyerror: コンパイラやパーサーの文脈でよく使われるエラー報告関数です。通常、yaccbisonのようなパーサー生成ツールによって生成されるコードで使用され、コンパイル時のエラーメッセージを出力します。

  • Sym (Symbol): コンパイラ内部で、変数名、関数名、型名などの識別子(シンボル)を表すデータ構造です。

技術的詳細

このコミットの技術的な変更は、主にGoコンパイラのsrc/cmd/gc/subr.csrc/cmd/gc/typecheck.cの2つのファイルに集中しています。これらは、それぞれGoコンパイラのサブルーチンと型チェックロジックを実装している部分です。

変更の核心は、曖昧なセレクタのエラー報告メカニズムの改善にあります。

  1. エラーメッセージの重複排除: 以前のコンパイラでは、adddot関数(セレクタ解決の一部を担う)とlookdot1関数(型チェック中にセレクタを検索する)の両方で曖昧なセレクタのエラーが報告される可能性がありました。これにより、同じ問題に対して2つのエラーメッセージが出力されることがありました。 このコミットでは、adddot関数で曖昧なセレクタが検出された場合、そのノードのn->leftN(nilノード)に設定し、関数を早期に終了させることで、typecheck.c内の後続の処理で再度同じエラーが報告されるのを防いでいます。typecheck関数内でif(n->left == N) goto error;というチェックが追加され、adddotでエラーが処理されたノードは、それ以上型チェックされないようになっています。

  2. エラーメッセージの具体化: 以前のエラーメッセージは、"ambiguous selector %T.%S"のように、型とシンボル名のみを表示していました。これでは、ユーザーはコードのどの部分が問題なのかを特定しにくい場合がありました。 このコミットでは、lookdot1関数のシグネチャにNode *errnodeという新しい引数が追加されました。これにより、エラーを報告する際に、問題となっている実際のASTノード(式)を渡すことができるようになりました。 yyerror関数への呼び出しも変更され、yyerror("ambiguous selector %N", errnode);のように、%Nフォーマット指定子を使ってノード自体を表示できるようになりました。これにより、コンパイラはエラーメッセージにt.xのような具体的な式を含めることが可能になり、ユーザーはどのセレクタが曖昧であるかを一目で理解できるようになります。 lookdot1関数内では、errnodeが提供されている場合はそれを使用し、そうでない場合は以前と同様に型とシンボル名を使用するフォールバックロジックが実装されています。また、ポインタ型の場合には(%T).%Sのように括弧を追加して、より正確な表現になるように配慮されています。

  3. 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;が設定されるようになりました。
  • looktypedotlookdottypecheckcomplitなど、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 xambiguous selector t.xというエラーが正しく報告されることを検証しています。特に、ambiguous selector t.xというメッセージが、具体的な式t.xを含んでいることを確認するためのテストです。

コアとなるコードの解説

このコミットの主要な変更は、Goコンパイラの型チェックフェーズにおけるセレクタ解決とエラー報告のロジックにあります。

  1. 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が曖昧さを検出してエラーを報告した時点で、そのノードに対するそれ以上の型チェックは無意味であり、重複するエラーメッセージを避けるために、後続の処理をスキップさせるためのマーカーとして機能します。

  2. 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が既に設定されているのに別の候補が見つかった場合)、errnodenilでなければyyerror("ambiguous selector %N", errnode);が呼び出されます。これにより、adddotと同様に、具体的な式がエラーメッセージに表示されます。errnodenilの場合や、ポインタ型の場合の特別なメッセージングも追加され、エラーメッセージの精度が向上しています。

  3. 型チェックフローの調整: 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、型チェック、エラー報告など)