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

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

このコミットは、Goコンパイラ(cmd/gc)における「const initializer is not a constant」(定数初期化子が定数ではありません)という誤ったエラー報告を修正するものです。具体的には、定数初期化の際に、実際には定数であるべき値が誤って非定数と判断され、不必要なエラーメッセージが表示されるバグ(Issue 6403)に対処しています。この修正により、コンパイラはより正確なエラー診断を行うようになり、開発者の混乱を減らします。

コミット

commit 833dae6d26c56bde5fbae27fde0cdc6efa63fefa
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date:   Mon Mar 24 20:36:42 2014 +0100

    cmd/gc: fix spurious 'const initializer is not a constant' error
    
    Fixes #6403
    
    LGTM=rsc
    R=iant, rsc
    CC=golang-codereviews
    https://golang.org/cl/72840044
---
 src/cmd/gc/subr.c           |  1 +\
 src/cmd/gc/typecheck.c      |  5 ++++-\
 test/fixedbugs/issue6403.go | 14 ++++++++++++++\
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c
index 01a5c435aa..f9746f0278 100644
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -2243,6 +2243,7 @@ adddot(Node *n)
 	int c, d;
 
 	typecheck(&n->left, Etype|Erv);\n+\tn->diag |= n->left->diag;\n 	t = n->left->type;\
 	if(t == T)\
 		goto ret;\
diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c
index ebff0694a0..f6e77acebd 100644
--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -3174,7 +3174,10 @@ typecheckdef(Node *n)
 		goto ret;
 	}
 	if(e->type != T && e->op != OLITERAL || !isgoconst(e)) {
-\t\tyyerror("const initializer %N is not a constant", e);\n+\t\tif(!e->diag) {\n+\t\t\tyyerror("const initializer %N is not a constant", e);\n+\t\t\te->diag = 1;\n+\t\t}\n 		goto ret;
 	}
 	t = n->type;
diff --git a/test/fixedbugs/issue6403.go b/test/fixedbugs/issue6403.go
new file mode 100644
index 0000000000..b61e2e225d
--- /dev/null
+++ b/test/fixedbugs/issue6403.go
@@ -0,0 +1,14 @@
+// errorcheck
+
+// Copyright 2014 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.
+
+// Issue 6403: fix spurious 'const initializer is not a constant' error
+
+package p
+
+import "syscall"
+
+const A int = syscall.X // ERROR "undefined: syscall.X"
+const B int = voidpkg.X // ERROR "undefined: voidpkg"

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

https://github.com/golang/go/commit/833dae6d26c56bde5fbae27fde0cdc6efa63fefa

元コミット内容

cmd/gc: fix spurious 'const initializer is not a constant' error

Fixes #6403

LGTM=rsc
R=iant, rsc
CC=golang-codereviews
https://golang.org/cl/72840044

変更の背景

このコミットは、Goコンパイラ(cmd/gc)が特定の状況下で「const initializer is not a constant」(定数初期化子が定数ではありません)という誤ったエラーメッセージを報告するバグ(Issue 6403)を修正するために導入されました。

Go言語では、定数はコンパイル時にその値が決定される必要があります。しかし、このバグが存在する環境では、例えば未定義のシンボルを参照するような、本来であれば別の種類のエラー(例: 「undefined: syscall.X」)が報告されるべきケースで、コンパイラが誤って「定数初期化子が定数ではありません」という、より一般的な、かつこの文脈では不適切なエラーを出力していました。

このような誤ったエラーメッセージは、開発者にとって混乱の原因となります。本来の問題(例: 未定義のシンボル)が不明瞭になり、デバッグ作業が困難になるため、正確なエラー報告はコンパイラのユーザビリティにおいて非常に重要です。このコミットは、コンパイラのエラー診断の精度を向上させ、開発体験を改善することを目的としています。

前提知識の解説

このコミットの理解には、以下のGoコンパイラおよびGo言語の概念に関する知識が役立ちます。

  • Go言語における定数 (Constants): Go言語の定数は、コンパイル時にその値が決定される不変の値を指します。数値、真偽値、文字列などの基本的な型で宣言でき、constキーワードを用いて定義されます。定数式は、コンパイル時に評価可能でなければなりません。例えば、const PI = 3.14は有効ですが、const X = someFunction()のように実行時に評価される関数呼び出しは定数初期化子としては無効です。

  • Goコンパイラ (cmd/gc): cmd/gcは、Go言語の公式コンパイラの一つであり、Goソースコードを機械語に変換する主要なツールです。コンパイルプロセスには、字句解析、構文解析、型チェック、最適化、コード生成など、複数のフェーズが含まれます。

  • 型チェック (Type Checking): 型チェックはコンパイルプロセスの重要なフェーズであり、プログラムが型規則に準拠しているかを確認します。これには、変数の型が正しく使用されているか、関数呼び出しの引数が期待される型と一致しているか、そして定数初期化子が有効な定数式であるかどうかの検証が含まれます。このコミットが修正する問題は、この型チェックフェーズにおける定数式の評価とエラー報告のロジックに関連しています。

  • 抽象構文木 (AST) と Node 構造体: コンパイラはソースコードを解析し、その構造を抽象構文木(AST)として内部的に表現します。ASTの各要素はNode構造体で表され、プログラムの様々な構成要素(変数、式、ステートメントなど)に対応します。Node構造体には、そのノードに関する情報(型、操作、子ノードなど)が格納されます。

  • diag フィールド: Goコンパイラの内部では、Node構造体にはdiagというフィールドが含まれることがあります。このフィールドは、そのノードまたはその子ノードに対して既に診断メッセージ(エラーや警告)が報告されたかどうかを示すフラグとして機能します。diagフラグを使用することで、コンパイラは同じ問題に対して複数の重複したエラーメッセージを出力するのを防ぎ、よりクリーンなエラー報告を実現します。

  • yyerror 関数: yyerrorは、Goコンパイラ内部でエラーメッセージを報告するために使用される関数です。この関数が呼び出されると、指定されたエラーメッセージがコンパイルエラーとして出力されます。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの型チェックフェーズにおけるエラー報告のロジックの改善に焦点を当てています。問題は、ある式が既にエラー(例えば未定義のシンボル)を含んでいるにもかかわらず、その式が定数初期化子として評価される際に、さらに「定数ではない」という誤ったエラーが重複して報告されることでした。

修正は主に2つのファイルで行われています。

  1. src/cmd/gc/subr.c の変更: adddot関数は、セレクタ(例: a.bのようなドット演算子)の型チェックを行う際に使用されます。この関数内で、n->diag |= n->left->diag;という行が追加されました。

    • nは現在のノード(例: syscall.X全体)。
    • n->leftはドット演算子の左側の部分(例: syscall)。
    • n->left->diagは、左側の部分が既にエラー(例えばsyscallが未定義、あるいはsyscall.XXが未定義)を持っている場合に設定される診断フラグです。 この変更の目的は、式の左側(n->left)に既にエラーが報告されている場合、そのエラー情報(diagフラグ)を親ノード(n)に伝播させることです。これにより、後続の型チェックフェーズで、このノードが既に問題を持っていることが認識され、重複したエラー報告が抑制される可能性が生まれます。
  2. src/cmd/gc/typecheck.c の変更: typecheckdef関数は、定数、変数、関数の定義を型チェックする際に使用されます。特に、定数初期化子のチェック部分が修正されました。 変更前は、定数初期化子eが定数ではないと判断された場合、無条件にyyerror("const initializer %N is not a constant", e);が呼び出されていました。 変更後は、if(!e->diag)という条件が追加されました。

    • !e->diagは、「e(定数初期化子)に対してまだエラーが報告されていない場合」を意味します。 この条件が真の場合にのみ、yyerrorが呼び出され、「const initializer is not a constant」エラーが報告されます。そして、エラーが報告された後にはe->diag = 1;が設定され、このノードに対してエラーが報告済みであることをマークします。 このロジックにより、例えばsyscall.Xのような式で、syscall.X自体が未定義であるために既にadddot関数などでe->diagが設定されている場合、typecheckdef関数は重複して「const initializer is not a constant」エラーを報告しなくなります。これにより、コンパイラはより具体的で関連性の高いエラーメッセージ(この場合は「undefined: syscall.X」)のみを出力するようになります。

これらの変更は、コンパイラのエラー報告メカニズムを洗練させ、単一の根本的な問題に対して複数のエラーメッセージが生成される「エラーの連鎖」を防ぐことに貢献します。

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

src/cmd/gc/subr.c

 // adddot関数内
 typecheck(&n->left, Etype|Erv);
+n->diag |= n->left->diag; // 追加された行
 t = n->left->type;
 if(t == T)
  goto ret;

src/cmd/gc/typecheck.c

 // typecheckdef関数内、定数初期化子のチェック部分
 if(e->type != T && e->op != OLITERAL || !isgoconst(e)) {
- yyerror("const initializer %N is not a constant", e); // 変更前
+ if(!e->diag) { // 追加された条件
+  yyerror("const initializer %N is not a constant", e);
+  e->diag = 1; // 追加された行
+ }
  goto ret;
 }

test/fixedbugs/issue6403.go

// 新規追加されたテストファイル
// errorcheck

// Copyright 2014 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.

// Issue 6403: fix spurious 'const initializer is not a constant' error

package p

import "syscall"

const A int = syscall.X // ERROR "undefined: syscall.X"
const B int = voidpkg.X // ERROR "undefined: voidpkg"

コアとなるコードの解説

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

n->diag |= n->left->diag;

この行は、ビットOR演算子 (|=) を使用して、現在のノードnの診断フラグdiagに、その左の子ノードn->leftの診断フラグを結合しています。

  • 目的: n->left(例えばsyscall)が既に何らかのエラー(例えば未定義)によってdiagフラグが設定されている場合、そのエラー状態を親ノードn(例えばsyscall.X全体)に伝播させます。これにより、syscall.Xという式全体が既にエラーを含んでいることがコンパイラの他の部分に認識され、後続の処理で重複したエラーメッセージが生成されるのを防ぐための準備となります。

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

if(!e->diag) {
 yyerror("const initializer %N is not a constant", e);
 e->diag = 1;
}

このブロックは、定数初期化子eが定数ではないと判断された場合に実行されます。

  • if(!e->diag): この条件は、定数初期化子eに対してまだエラーが報告されていない場合にのみ、内部のyyerror呼び出しを実行するようにします。これにより、eが既に別の理由(例: 未定義のシンボル)でエラーが報告され、e->diagが設定されている場合、この「const initializer is not a constant」というエラーは出力されません。
  • yyerror("const initializer %N is not a constant", e);: この行は、定数初期化子が定数ではないというエラーメッセージを報告します。
  • e->diag = 1;: エラーが報告された後、ediagフラグを1に設定します。これにより、このノードに対してエラーが報告済みであることがマークされ、将来的に同じノードに対して重複してエラーが報告されるのを防ぎます。

これらの変更は連携して機能し、コンパイラがより正確で、重複のないエラーメッセージを生成するようにします。特に、未定義のシンボルなど、より具体的なエラーが存在する場合に、一般的な「定数ではない」というエラーが誤って表示されるのを防ぎます。

test/fixedbugs/issue6403.go の追加

このテストファイルは、修正が正しく機能することを確認するために追加されました。

  • const A int = syscall.Xconst B int = voidpkg.X のようなコードは、syscall.Xvoidpkg.Xが未定義であるため、本来は「undefined」エラーを発生させるべきです。
  • このテストの目的は、これらのケースで「const initializer is not a constant」という誤ったエラーが報告されず、代わりに期待される「undefined」エラーのみが報告されることを検証することです。// ERROR "..."コメントは、期待されるエラーメッセージを示しており、errorcheckディレクティブは、コンパイラがこれらのエラーを正しく検出するかどうかをテストツールが確認するように指示します。

関連リンク

参考にした情報源リンク