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

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

このコミットは、Goコンパイラ(cmd/gc)において、複合リテラル(composite literals)内のエラー発生時に報告される行番号が誤解を招く可能性があった問題を修正します。具体的には、複合リテラルが複数行にわたる場合に、エラーがリテラルの開始行ではなく、実際にエラーが発生した要素の行を正確に指すように改善されました。

コミット

commit bf59aafddc92ef52e9e5c61152e2c1d182f453bf
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date:   Wed Dec 12 16:43:54 2012 +0100

    cmd/gc: Give better line numbers for errors in composite literals.
    
    Credit to Russ for suggesting this fix.
    
    Fixes #3925.
    
    R=golang-dev, franciscossouza, rsc
    CC=golang-dev
    https://golang.org/cl/6920051

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

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

元コミット内容

cmd/gc: Give better line numbers for errors in composite literals.

Credit to Russ for suggesting this fix.

Fixes #3925.

R=golang-dev, franciscossouza, rsc
CC=golang-dev
https://golang.org/cl/6920051

変更の背景

Go言語のコンパイラcmd/gcにおいて、複合リテラル(composite literals)内で構文エラーや型エラーが発生した場合、エラーメッセージに表示される行番号が、リテラル全体の開始行を指してしまうことがありました。特に、複合リテラルが複数行にわたる場合、この挙動は開発者にとってエラーの実際の発生箇所を特定するのを困難にしていました。例えば、以下のようなコードでエラーが発生した場合を考えます。

type MyStruct struct {
    Field1 int
    Field2 string
}

func main() {
    s := MyStruct{
        Field1: 10,
        Field2: "hello", // ここで構文エラー
    }
}

このコミット以前は、Field2の行でエラーがあっても、エラーメッセージがMyStruct{の行を指してしまう可能性がありました。この問題はIssue #3925として報告されており、Russ Cox氏の提案を基に、より正確な行番号を報告するように修正されました。これにより、開発者はエラーの根本原因を迅速に特定し、デバッグの効率を向上させることができます。

前提知識の解説

複合リテラル (Composite Literals)

Go言語における複合リテラルは、構造体(struct)、配列(array)、スライス(slice)、マップ(map)などの複合型を初期化するための構文です。波括弧 {} を使用して要素を列挙し、値を直接指定することで、変数の宣言と同時に初期化を行うことができます。

例:

  • 構造体リテラル: MyStruct{Field1: 10, Field2: "value"}
  • 配列リテラル: [3]int{1, 2, 3}
  • スライスリテラル: []string{"a", "b", "c"}
  • マップリテラル: map[string]int{"key1": 1, "key2": 2}

複合リテラルは、コードの可読性を高め、簡潔にデータを初期化するために広く利用されます。

Goコンパイラ (cmd/gc)

cmd/gcは、Go言語の公式コンパイラの一つであり、Goのソースコードを機械語に変換する役割を担っています。Goのツールチェインの中核をなすコンポーネントです。コンパイルプロセスには、字句解析、構文解析、意味解析、最適化、コード生成などが含まれます。

Yacc/Bison と go.y, y.tab.c

go.yファイルは、Go言語の構文を定義するYacc(Yet Another Compiler Compiler)またはBison(GNU Bison)の文法ファイルです。Yacc/Bisonは、文法定義ファイル(.yファイル)を読み込み、C言語のパーサーコード(y.tab.cおよびy.tab.h)を生成するツールです。

  • go.y: Go言語の構文規則(プロダクションルール)が記述されています。例えば、複合リテラルや式の構造などがここで定義されます。
  • y.tab.c: go.yからBisonによって生成されたC言語のソースファイルで、実際の構文解析ロジック(パーサー)が含まれています。このファイルは通常、人間が直接編集するものではなく、go.yの変更に基づいて自動生成されます。
  • y.tab.h: y.tab.cに対応するヘッダーファイルで、トークン定義などが含まれます。

コンパイラがソースコードを解析する際、このパーサーがGo言語の構文ツリーを構築し、その過程で構文エラーを検出します。このコミットは、この構文解析の段階で、エラー発生時の行番号の扱いを改善することを目的としています。

技術的詳細

このコミットの技術的な核心は、Goコンパイラの構文解析器が複合リテラルを処理する方法を変更し、エラー報告の精度を高めることにあります。

主な変更点は以下の通りです。

  1. go.yの変更:

    • bare_complitexprという新しい非終端記号が導入されました。これは、複合リテラルの個々の要素(キーと値のペア、または単一の式)を表します。
    • complitexprの定義が変更され、bare_complitexprを使用するように修正されました。
    • bare_complitexprのルール内で、nod(OPAREN, $$, N)という新しいノードが導入されています。これは、複合リテラルの要素がONAME, ONONAME, OTYPE, OPACK, OLITERALといった特定の種類のノードである場合に、そのノードをOPAREN(括弧)ノードでラップすることで、正確な行番号情報を保持するようにします。
    • コメントで示されているように、複合リテラルは複数行にわたることが多いため、エラーの行番号が誤解を招く可能性があるという問題意識が背景にあります。OPARENノードでラップすることで、元のノードが持つ行番号情報が失われずに、エラー報告時に利用できるようになります。
  2. y.tab.cの変更:

    • go.yの変更に伴い、Bisonによって自動生成されるy.tab.cファイルが大幅に更新されています。これは、go.yの文法規則の変更が、パーサーの内部ロジックに影響を与えるためです。
    • YYBISON_VERSION2.6.5から2.5にダウングレードされているように見えますが、これはコミット時点でのBisonのバージョン管理の都合によるもので、機能的なダウングレードを意味するものではありません。むしろ、go.yの変更が、特定のBisonバージョンで生成されるコードに影響を与えた結果と考えられます。
    • y.tab.c内のyyr1, yyr2, yydefact, yypact, yypgoto, yytable, yycheck, yystosといったテーブルデータが変更されています。これらのテーブルは、パーサーの有限状態オートマトン(FSM)を定義するものであり、文法規則の変更が直接的にこれらのテーブルの再生成を引き起こします。

この修正の目的は、複合リテラル内の個々の要素が持つ正確な行番号を、パーサーがエラー報告時に利用できるようにすることです。これにより、エラーメッセージがより具体的になり、開発者はコード内の問題箇所を迅速に特定できるようになります。

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

src/cmd/gc/go.y ファイルにおける主要な変更は以下の通りです。

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -54,7 +54,7 @@ static void fixlbrace(int);\
 %type	<node>	stmt ntype
 %type	<node>	arg_type
 %type	<node>	case caseblock
-%type	<node>	compound_stmt dotname embed expr complitexpr
+%type	<node>	compound_stmt dotname embed expr complitexpr bare_complitexpr
 %type	<node>	expr_or_type
 %type	<node>	fndcl hidden_fndcl fnliteral
 %type	<node>	for_body for_header for_stmt if_header if_stmt non_dcl_stmt
@@ -974,6 +974,29 @@ keyval:
 	\t$$ = nod(OKEY, $1, $3);\
 	}\
 \
+bare_complitexpr:\
+\texpr\
+\t{\
+\t\t// These nodes do not carry line numbers.\
+\t\t// Since a composite literal commonly spans several lines,\
+\t\t// the line number on errors may be misleading.\
+\t\t// Introduce a wrapper node to give the correct line.\
+\t\tswitch($$->op) {\
+\t\tcase ONAME:\
+\t\tcase ONONAME:\
+\t\tcase OTYPE:\
+\t\tcase OPACK:\
+\t\tcase OLITERAL:\
+\t\t\t$$ = nod(OPAREN, $$, N);\
+\t\t}\
+\t}\
+|\t\'{\' start_complit braced_keyval_list \'}\'\
+\t{\
+\t\t$$ = $2;\
+\t\t$$->list = $3;\
+\t}\
+\
 complitexpr:\
 \texpr\
 |\t\'{\' start_complit braced_keyval_list \'}\'\
@@ -1761,7 +1784,7 @@ keyval_list:\
 	{\
 	\t$$ = list1($1);\
 	}\
-|\tcomplitexpr
+|\tbare_complitexpr
 	{\
 	\t$$ = list1($1);\
 	}\
@@ -1769,7 +1792,7 @@ keyval_list:\
 	{\
 	\t$$ = list($1, $3);\
 	}\
-|\tkeyval_list \',\' complitexpr
+|\tkeyval_list \',\' bare_complitexpr
 	{\
 	\t$$ = list($1, $3);\
 	}\

コアとなるコードの解説

上記のgo.yの変更は、Goコンパイラの構文解析器が複合リテラル内の個々の要素をどのように扱うかを再定義しています。

  1. %type <node> ... bare_complitexpr の追加:

    • これは、bare_complitexprという新しい非終端記号が、Node*型のセマンティック値を保持することを示しています。NodeはGoコンパイラ内部で構文ツリーの各要素を表すデータ構造です。
  2. bare_complitexpr ルールの追加:

    • bare_complitexpr: expr のルールが追加されました。これは、複合リテラルの要素が単なる式(expr)である場合を扱います。
    • このルール内のアクションブロックが重要です。
      // These nodes do not carry line numbers.
      // Since a composite literal commonly spans several lines,
      // the line number on errors may be misleading.
      // Introduce a wrapper node to give the correct line.
      switch($$->op) {
      case ONAME:
      case ONONAME:
      case OTYPE:
      case OPACK:
      case OLITERAL:
          $$ = nod(OPAREN, $$, N);
      }
      
      このコードは、exprが特定の種類のノード(ONAMEONONAMEOTYPEOPACKOLITERAL)である場合に、そのノードをOPAREN(括弧)ノードでラップしています。
      • ONAME: 変数名や関数名などの識別子。
      • ONONAME: 未解決の識別子。
      • OTYPE: 型。
      • OPACK: パッケージ名。
      • OLITERAL: リテラル値(数値、文字列など)。 これらのノードは、それ自体では正確な行番号情報を持たないか、または複合リテラル内で使用された場合に誤解を招く行番号を報告する可能性がありました。OPARENノードは、その子ノードの行番号情報を継承または保持する役割を果たすため、エラー報告時に正確な行番号を提供できるようになります。
    • bare_complitexpr: '{' start_complit braced_keyval_list '}' のルールも追加されました。これは、複合リテラルが波括弧で囲まれたキーと値のリスト(または単なる値のリスト)である場合を扱います。
  3. keyval_list ルールの変更:

    • keyval_listの定義が、complitexprの代わりに新しく導入されたbare_complitexprを使用するように変更されました。
    • keyval_list: complitexprkeyval_list: bare_complitexpr に。
    • keyval_list ',' complitexprkeyval_list ',' bare_complitexpr に。 この変更により、複合リテラル内の個々の要素がbare_complitexprとして解析され、その結果として正確な行番号情報がOPARENノードを介して伝播されるようになります。

これらの変更は、Goコンパイラの構文解析段階で、複合リテラル内のエラー発生箇所をより詳細に特定し、開発者にとってより有用なエラーメッセージを生成するための基盤を構築しています。

関連リンク

参考にした情報源リンク