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

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

このコミットは、Goコンパイラ(gc)における代入文の要素数不一致に関するエラー報告の改善を目的としています。具体的には、エラーメッセージをより明確にし、同じエラーが複数回報告されるのを防ぐための修正が加えられています。

コミット

assignment count mismatch: 2 = 1.

R=ken
OCL=23534
CL=23534

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

https://github.com/golang/go/commit/5b129cda5f53aa217203a24bd44936092668c154

元コミット内容

このコミットは、Go言語の初期開発段階におけるコンパイラの改善の一部です。元々のコミットメッセージは「assignment count mismatch: 2 = 1.」と非常に簡潔ですが、これは具体的なエラーの例を示しており、代入の左辺と右辺の要素数が一致しない場合に発生する問題に対処していることを示唆しています。R=kenはレビュー担当者がken氏であることを示し、OCLCLはGoogle社内の変更リスト番号(Changelist Number)を指します。

変更の背景

Go言語のコンパイラは、コードの構文解析と意味解析を行う際に、様々なルールに基づいてコードの正当性を検証します。代入文はプログラミングにおいて非常に基本的な操作ですが、Go言語では多値代入(multiple assignment)がサポートされており、左辺と右辺の式の数が一致しない場合にエラーとなるべきです。

このコミット以前は、代入の要素数不一致に関するエラーメッセージが「bad shape across assignment」や「shape error across :=」といった、やや抽象的で一般的なものでした。これらのメッセージは、問題の具体的な原因(代入の要素数不一致)を直接的に示していなかったため、開発者がエラーの原因を特定しにくいという課題がありました。

また、コンパイラがAST(抽象構文木)を走査する過程で、同じ問題のあるノードに対して複数回エラー報告を行ってしまう可能性がありました。これにより、コンパイルエラーの出力が冗長になり、本当に重要なエラーメッセージが埋もれてしまうというユーザビリティの問題も存在しました。

このコミットは、これらの問題を解決し、コンパイラのエラー報告の質を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念に関する基本的な知識が必要です。

  1. Go言語のコンパイラ(gc: Go言語の公式コンパイラは、初期にはgc(Go Compiler)と呼ばれていました。これはC言語で書かれており、Goソースコードを機械語に変換する役割を担います。
  2. AST (Abstract Syntax Tree): 抽象構文木は、ソースコードの構文構造を木構造で表現したものです。コンパイラはソースコードを解析し、まずASTを構築します。その後の意味解析、型チェック、コード生成などのフェーズは、このASTを走査(walk)しながら行われます。
  3. コンパイラのフェーズ:
    • 字句解析 (Lexical Analysis): ソースコードをトークン(単語)の列に変換します。
    • 構文解析 (Syntax Analysis): トークンの列からASTを構築します。
    • 意味解析 (Semantic Analysis): ASTを走査し、型チェック、名前解決、スコープチェックなど、プログラムの意味的な正当性を検証します。このフェーズで、代入の要素数不一致のようなエラーが検出されます。
    • 中間コード生成 (Intermediate Code Generation): ASTから中間表現を生成します。
    • 最適化 (Optimization): 中間コードを最適化します。
    • コード生成 (Code Generation): 最終的な機械語コードを生成します。
  4. src/cmd/gc/go.h: Goコンパイラの内部で使用されるデータ構造や定数、関数プロトタイプなどが定義されたヘッダファイルです。特に、ASTのノードを表すNode構造体の定義が含まれます。
  5. src/cmd/gc/walk.c: コンパイラの「walk」フェーズ、すなわちASTを走査して意味解析や型チェックを行う部分のC言語ソースファイルです。代入文の処理やエラーチェックがここで行われます。
  6. yyerror: yyerrorは、YaccやBisonといったパーサジェネレータによって生成されるパーサで一般的に使用されるエラー報告関数です。コンパイラが構文エラーや意味エラーを検出した際に、この関数を呼び出してエラーメッセージを出力します。

技術的詳細

このコミットの技術的な核心は、以下の2点に集約されます。

  1. Node構造体へのdiagフィールドの追加:

    • src/cmd/gc/go.hにおいて、ASTの各ノードを表すNode構造体にuchar diag;という新しいフィールドが追加されました。
    • ucharは符号なし文字型で、通常1バイトのメモリを占めます。これはフラグとして使用され、特定のノードについて既にエラーメッセージが出力されたかどうかを記録します。
    • このフィールドの導入により、コンパイラがASTを走査する際に、同じノードで同じ種類のエラーを複数回検出しても、一度しかエラーメッセージを出力しないように制御することが可能になります。これは、コンパイルエラーの出力を簡潔にし、開発者にとって読みやすくするために非常に重要です。
  2. エラーメッセージの具体化と重複報告の抑制:

    • src/cmd/gc/walk.c内の、代入文を処理するロジックにおいて、エラー報告の挙動が変更されました。
    • エラーメッセージの変更: 以前の「bad shape across assignment」や「shape error across :=」といった一般的なメッセージが、「assignment count mismatch: %d = %d」という、より具体的で分かりやすいメッセージに置き換えられました。%d = %dの部分には、それぞれ左辺と右辺の要素数が埋め込まれ、エラーの具体的な状況が示されます。これにより、開発者はエラーメッセージを見ただけで、代入の要素数が一致していないことが明確に理解できます。
    • 重複報告の抑制: 新しく追加されたdiagフィールドが活用されています。エラーを報告する前に、対象のノード(lまたはnl)のdiagフィールドが0(未報告)であるかを確認します。もし0であれば、エラーメッセージを出力し、その後diagフィールドを1に設定します。これにより、同じノードに対して二度目以降にエラーが検出されても、diag1であるためエラーメッセージは出力されず、重複報告が防がれます。

これらの変更は、コンパイラのエラー報告の品質を大幅に向上させ、開発者のデバッグ体験を改善する上で重要な役割を果たします。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -185,6 +185,7 @@ struct	Node
 	uchar	iota;		// OLITERAL made from iota
 	uchar	embedded;	// ODCLFIELD embedded type
 	uchar	colas;		// OAS resulting from :=
+	uchar	diag;		// already printed error about this
 
 	// most nodes
 	Node*	left;

src/cmd/gc/walk.c

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -534,8 +534,10 @@ loop:
 			}
 			break;
 		}
-
-\t\tyyerror(\"bad shape across assignment - cr=%d cl=%d\\n\", cr, cl);\n+\t\tif(l->diag == 0) {\n+\t\t\tl->diag = 1;\n+\t\t\tyyerror(\"assignment count mismatch: %d = %d\", cl, cr);\n+\t\t}\n \t\tgoto ret;\n 
 \tcase OBREAK:
@@ -3083,7 +3085,10 @@ multi:
 	return n;
 
 badt:
-\tyyerror(\"shape error across :=\");\n+\tif(nl->diag == 0) {\n+\t\tnl->diag = 1;\n+\t\tyyerror(\"assignment count mismatch: %d = %d\", cl, cr);\n+\t}\n \treturn nl;\n }

コアとなるコードの解説

src/cmd/gc/go.h の変更

Node構造体は、Go言語のソースコードを抽象化した構文木(AST)の各要素(ノード)を表現します。この構造体にuchar diag;というフィールドが追加されました。

  • diagフィールドは、そのノードに関連するエラーが既に報告されたかどうかを示すフラグとして機能します。0は未報告、1は報告済みを意味します。
  • これにより、コンパイラがASTを走査する際に、同じノードに対して複数のエラーチェックが走ったとしても、一度しかエラーメッセージを出力しないように制御できるようになります。これは、コンパイルエラーの出力を簡潔にし、開発者にとって読みやすくするために非常に重要な改善です。

src/cmd/gc/walk.c の変更

walk.cは、ASTを走査し、型チェックや意味解析を行うGoコンパイラの重要な部分です。このファイルでは、主に2つの箇所で変更が行われています。

  1. loop: ラベル付近の変更 (通常の代入文):

    • このセクションは、通常の代入文(=)における左辺と右辺の要素数不一致を検出するロジックに関連しています。
    • 変更前は、yyerror("bad shape across assignment - cr=%d cl=%d\\n", cr, cl); という一般的なエラーメッセージが出力されていました。crclはそれぞれ右辺と左辺の要素数を表していると推測されます。
    • 変更後、エラー報告の前にif(l->diag == 0)という条件が追加されました。lは代入の左辺のノードを指します。
    • この条件が真(まだエラーが報告されていない)の場合にのみ、l->diag = 1;としてフラグを立て、yyerror("assignment count mismatch: %d = %d", cl, cr);という、より具体的で分かりやすいエラーメッセージが出力されます。
    • これにより、エラーメッセージが改善されるだけでなく、同じ代入ノードに対して複数回エラーが報告されるのを防ぎます。
  2. badt: ラベル付近の変更 (短変数宣言 :=):

    • このセクションは、短変数宣言(:=)における要素数不一致を検出するロジックに関連しています。
    • 変更前は、yyerror("shape error across :="); という一般的なエラーメッセージが出力されていました。
    • 変更後、同様にif(nl->diag == 0)という条件が追加されました。nlは短変数宣言のノードを指します。
    • この条件が真の場合にのみ、nl->diag = 1;としてフラグを立て、yyerror("assignment count mismatch: %d = %d", cl, cr);という具体的なエラーメッセージが出力されます。
    • ここでも、エラーメッセージの具体化と重複報告の抑制が同時に実現されています。

これらの変更により、Goコンパイラはよりユーザーフレンドリーなエラーメッセージを提供し、コンパイル時の出力のノイズを減らすことに成功しました。

関連リンク

  • Go言語の公式リポジトリ: https://github.com/golang/go
  • Go言語の初期のコミット履歴を辿ることで、当時の開発状況や設計思想を垣間見ることができます。

参考にした情報源リンク