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

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

このコミットは、Goコンパイラ(cmd/gc)におけるエラー報告の精度を向上させるための修正です。具体的には、:=(ショート変数宣言)演算子を使用した際に「no new variables on left side of :=」(新しい変数が左辺にありません)というエラーメッセージが表示される場合、そのエラーが誤った行番号で報告される問題を解決します。この修正により、エラーメッセージが実際に問題が発生したコードの行を正確に指し示すようになります。

コミット

commit dd166b943752e884944672b81ab4783f558985ff
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Sun Jul 29 22:24:19 2012 -0400

    cmd/gc: point "no new variables" error at right line number.
    
    Fixes #3856.
    
    R=dsymonds, rsc
    CC=golang-dev
    https://golang.org/cl/6455056
---
 src/cmd/gc/dcl.c   |   7 +--
 src/cmd/gc/go.h    |   2 +-
 src/cmd/gc/go.y    |   8 +--
 src/cmd/gc/lex.c   |   1 +
 src/cmd/gc/y.tab.c | 958 ++++++++++++++++++++++++++---------------------------
 src/cmd/gc/y.tab.h |  16 +--
 test/declbad.go    |   6 +
 7 files changed, 499 insertions(+), 499 deletions(-)

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

https://github.com/golang/go/commit/dd166b943752e884944672b81ab4783f558985ff

元コミット内容

cmd/gc: point "no new variables" error at right line number.

Fixes #3856.

R=dsymonds, rsc
CC=golang-dev
https://golang.org/cl/6455056

変更の背景

Go言語のショート変数宣言 := は、新しい変数を宣言し、同時に初期化するための便利な構文です。しかし、この演算子の左辺に新しい変数が一つも含まれていない場合、コンパイラは「no new variables on left side of :=」というエラーを報告します。

このコミットが修正する問題は、このエラーメッセージが表示される際に、コンパイラが誤った行番号を報告してしまうというバグでした。特に、複数行にわたるコードブロック内でこのエラーが発生した場合、エラーメッセージが実際の発生箇所ではなく、そのブロックの開始行などを指してしまうことがありました。これにより、開発者はエラーの原因を特定するのに余計な手間がかかるという問題がありました。

このバグはGoのIssue #3856として報告されており、本コミットはその問題を解決することを目的としています。

前提知識の解説

このコミットの変更内容を理解するためには、以下の前提知識が役立ちます。

  • Goコンパイラ (cmd/gc): Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する役割を担っています。cmd/gcは、字句解析、構文解析、型チェック、コード生成などの複数のフェーズで構成されています。
  • 字句解析 (Lexical Analysis): ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換するプロセスです。src/cmd/gc/lex.cがこの役割を担っています。
  • 構文解析 (Syntax Analysis): 字句解析によって生成されたトークンストリームが、言語の文法規則に準拠しているかを検証し、抽象構文木(AST)を構築するプロセスです。Goコンパイラでは、Yacc/Bisonのようなパーサジェネレータが使用されており、src/cmd/gc/go.yが文法定義ファイル、src/cmd/gc/y.tab.csrc/cmd/gc/y.tab.hがそれから生成されるパーサのC言語ソースファイルとヘッダファイルです。
  • Yacc/Bison: LALR(1)パーサを生成するためのツールです。.yファイルで文法規則とそれに対応するアクション(C言語コード)を定義し、yaccコマンドを実行することで.tab.c.tab.hファイルが生成されます。
    • %token: トークン(終端記号)を定義します。<type>は、そのトークンに関連付けられる値の型を指定します。
    • $$: 現在の規則の左辺の非終端記号に関連付けられる値。
    • $n: 現在の規則の右辺のn番目の記号に関連付けられる値。
    • yyerror(): パーサが構文エラーを検出した際に呼び出されるエラー報告関数。
  • ショート変数宣言 (:=): Go言語特有の構文で、変数の宣言と初期化を同時に行います。左辺には少なくとも1つの新しい変数が含まれている必要があります。例: x := 10 (新しい変数 x を宣言)。x := 10 の後に x := 20 と書くと、x は既に宣言されているため「no new variables」エラーが発生します。
  • colas 関数: Goコンパイラの内部で、ショート変数宣言 (:=) のような代入と宣言を組み合わせた構文を処理する際に使用される関数です。この関数は、左辺と右辺の式リストを受け取り、新しい変数が宣言されているかなどをチェックします。
  • Node 構造体: 抽象構文木(AST)のノードを表す構造体で、ソースコードの各要素(変数、式、ステートメントなど)に対応します。この構造体には、通常、そのノードがソースコードのどの位置(行番号など)に対応するかを示す情報が含まれています。

技術的詳細

このコミットの核心は、Goコンパイラの字句解析器と構文解析器の間で、ショート変数宣言 (:=) の行番号情報を正確に伝達するように変更することです。

  1. 字句解析器 (lex.c) での行番号の取得:

    • src/cmd/gc/lex.cにおいて、LCOLASトークン(:=演算子に対応)が認識された際に、現在の字句解析器の行番号 (lexlineno) をyylval.iに格納するように変更されています。yylvalはYacc/Bisonが提供する共用体で、トークンに関連付けられる値を保持します。これにより、:=演算子自体の正確な行番号が取得できるようになります。
  2. 構文解析器 (go.y) での行番号の伝達:

    • src/cmd/gc/go.yはGo言語の文法規則を定義しています。ショート変数宣言を処理するsimple_stmt規則と、caseステートメント内でショート変数宣言が使用される規則が変更されています。
    • 以前は、colas関数は行番号情報を受け取っていませんでした。しかし、変更後は、字句解析器から取得したLCOLASトークンの行番号($2または$3として参照されるyylval.iの値)をcolas関数に渡すように修正されています。
  3. 宣言処理 (dcl.c) での行番号の使用:

    • src/cmd/gc/dcl.cには、変数の宣言やスコープに関する処理が含まれています。
    • colasdefn関数内で「no new variables on left side of :=」エラーを報告する際に、以前は汎用的なエラー報告関数yyerrorが使用されていました。これが、特定の行番号を引数として受け取るyyerrorl関数に変更され、colas関数から渡された正確な行番号 (defn->lineno) を使用してエラーを報告するようになりました。
    • また、colas関数のシグネチャも変更され、int32 lnoという行番号を表す引数を受け取るようになりました。この行番号は、Node構造体のlinenoフィールドに格納され、エラー報告時に利用されます。
  4. 生成されたパーサファイル (y.tab.c, y.tab.h) の更新:

    • src/cmd/gc/go.ysrc/cmd/gc/lex.cの変更に伴い、Yacc/Bisonによって生成されるsrc/cmd/gc/y.tab.csrc/cmd/gc/y.tab.hも更新されています。これには、トークンの内部番号の変更(LCOLASの番号がLBREAKなどよりも前に移動)、パーサテーブル(yypact, yytable, yycheckなど)の再生成、およびcolas関数の呼び出し箇所の更新が含まれます。これらのファイルは手動で編集されるものではなく、自動生成されるため、変更は上流の文法定義ファイルや字句解析器の変更を反映したものです。

これらの変更により、コンパイラは:=演算子に関連するエラーを、その演算子が実際に記述されている行で正確に報告できるようになり、デバッグの効率が向上します。

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

src/cmd/gc/dcl.c

--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -464,7 +464,7 @@ colasdefn(NodeList *left, Node *defn)
 		if(isblank(n))
 			continue;
 		if(!colasname(n)) {
-			yyerror("non-name %N on left side of :=", n);
+			yyerrorl(defn->lineno, "non-name %N on left side of :=", n);
 			nerr++;
 			continue;
 		}
@@ -479,11 +479,11 @@ colasdefn(NodeList *left, Node *defn)
 		l->n = n;
 	}
 	if(nnew == 0 && nerr == 0)
-		yyerror("no new variables on left side of :=");
+		yyerrorl(defn->lineno, "no new variables on left side of :=");
 }
 
 Node*
-colas(NodeList *left, NodeList *right)
+colas(NodeList *left, NodeList *right, int32 lno)
 {
 	Node *as;
 
@@ -491,6 +491,7 @@ colas(NodeList *left, NodeList *right)
 	as->list = left;
 	as->rlist = right;
 	as->colas = 1;
+	as->lineno = lno;
 	colasdefn(left, as);
 
 	// make the tree prettier; not necessary

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -928,7 +928,7 @@ void	nodfconst(Node *n, Type *t, Mpflt* fval);
 void	addmethod(Sym *sf, Type *t, int local);
 void	addvar(Node *n, Type *t, int ctxt);
 NodeList*	checkarglist(NodeList *all, int input);
-Node*	colas(NodeList *left, NodeList *right);
+Node*	colas(NodeList *left, NodeList *right, int32 lno);
 void	colasdefn(NodeList *left, Node *defn);
 NodeList*	constiter(NodeList *vl, Node *t, NodeList *cl);
 Node*	dclname(Sym *s);

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -37,8 +37,8 @@ static void fixlbrace(int);
 // |sed 's/.*	//' |9 fmt -l1 |sort |9 fmt -l50 | sed '^/%xxx	/'
 
 %token	<val>	LLITERAL
-%token	<i>	LASOP
-%token	<sym>	LBREAK LCASE LCHAN LCOLAS LCONST LCONTINUE LDDD
+%token	<i>	LASOP LCOLAS
+%token	<sym>	LBREAK LCASE LCHAN LCONST LCONTINUE LDDD
 %token	<sym>	LDEFAULT LDEFER LELSE LFALL LFOR LFUNC LGO LGOTO
 %token	<sym>	LIF LIMPORT LINTERFACE LMAP LNAME
 %token	<sym>	LPACKAGE LRANGE LRETURN LSELECT LSTRUCT LSWITCH
@@ -437,7 +437,7 @@ simple_stmt:
 				$$->left = dclname($1->n->sym);  // it's a colas, so must not re-use an oldname.
 			break;
 		}
-		$$ = colas($1, $3);
+		$$ = colas($1, $3, $2);
 	}
 |	expr LINC
 	{
@@ -496,7 +496,7 @@ case:
 		// done in casebody()
 		markdcl();
 		$$ = nod(OXCASE, N, N);
-		$$->list = list1(colas($2, list1($4)));
+		$$->list = list1(colas($2, list1($4), $3));
 	}
 |	LDEFAULT ':'
 	{

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -973,6 +973,7 @@ l0:
 		c1 = getc();
 		if(c1 == '=') {
 			c = LCOLAS;
+			yylval.i = lexlineno;
 			goto lx;
 		}
 		break;

test/declbad.go

--- a/test/declbad.go
+++ b/test/declbad.go
@@ -38,6 +38,12 @@ func main() {
 		i, f := f2() // ERROR "redeclared|no new"
 		_, _, _ = i, f, s
 	}
+	{
+		// multiline no new variables
+		i := f1
+		i := func() int { // ERROR "redeclared|no new|incompatible"
+		}
+	}
 	{
 		// single redeclaration
 		i, f, s := f3()

コアとなるコードの解説

このコミットの主要な変更点は、Goコンパイラがショート変数宣言 (:=) に関連するエラーメッセージの行番号を正確に報告するように修正したことです。

  1. src/cmd/gc/lex.c:

    • l0ラベルの箇所で、:=演算子(LCOLASトークン)が検出された際に、現在の字句解析器の行番号であるlexlinenoyylval.iに格納しています。yylvalはYacc/Bisonが生成するパーサにおいて、トークンに関連付けられたセマンティックな値を保持するための共用体です。これにより、:=演算子が出現した正確な行番号が、後続の構文解析フェーズに渡されるようになります。
  2. src/cmd/gc/go.y:

    • Go言語の文法定義ファイルです。
    • %token <i> LASOP LCOLAS の行は、LCOLASトークンが整数値(i)を持つことを宣言しています。この整数値はlex.cで設定された行番号です。
    • simple_stmt規則とcase規則において、colas関数の呼び出しが変更されています。以前はcolas($1, $3)のように引数が2つでしたが、変更後はcolas($1, $3, $2)またはcolas($2, list1($4), $3)のように、:=演算子に対応するトークンの行番号($2または$3)が追加の引数として渡されるようになりました。これは、Yacc/Bisonの規則において、$nがn番目の記号のセマンティックな値を参照するためです。
  3. src/cmd/gc/go.h:

    • colas関数のプロトタイプ宣言がNode* colas(NodeList *left, NodeList *right, int32 lno);に変更され、行番号を表すlno引数が追加されました。
  4. src/cmd/gc/dcl.c:

    • colasdefn関数は、ショート変数宣言の左辺を定義する際に呼び出されます。
    • この関数内で「no new variables on left side of :=」エラーを報告する際に、以前はyyerrorという汎用的なエラー報告関数が使われていました。これがyyerrorl(defn->lineno, ...)に変更されています。yyerrorlは特定の行番号を引数として受け取るエラー報告関数であり、defn->linenocolas関数に渡された正確な行番号が格納されているNode構造体のフィールドです。
    • colas関数の実装も、int32 lno引数を受け取るように変更され、このlnoas->linenoに代入しています。これにより、:=演算子を含むNodeオブジェクトが正しい行番号を持つようになります。
  5. test/declbad.go:

    • 新しいテストケースが追加されました。これは、複数行にわたるショート変数宣言で「no new variables」エラーが発生するシナリオを意図的に作成し、修正が正しく機能しているか(つまり、エラーが正しい行で報告されるか)を検証するためのものです。

これらの変更により、Goコンパイラは、ショート変数宣言における「no new variables」エラーを、実際に問題が発生したソースコードの行で正確に報告できるようになり、開発者のデバッグ体験が大幅に改善されます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に cmd/gc ディレクトリ)
  • Yacc/Bisonのドキュメント (パーサジェネレータの動作とyylval, $n, $$などの概念について)
  • Go言語のショート変数宣言に関する公式ドキュメントやチュートリアル