[インデックス 14737] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc)において、initという名前が変数、型、または定数として宣言された場合に発生する、紛らわしいエラーメッセージを改善するための診断機能を追加するものです。Go言語では、initは特別な関数名であり、パッケージの初期化時に自動的に実行される関数を定義するために予約されています。このコミット以前は、initという名前で変数などを宣言すると、コンパイラが暗黙的に生成するパッケージレベルのinit関数との名前衝突が発生し、「再宣言」に関する分かりにくいエラーが表示されていました。この変更により、initが関数として宣言されていない場合に、より明確なエラーメッセージを出すようになります。また、func init()が関数本体を伴わない場合にエラーを出すissue 3705のテストも追加されています。
コミット
commit 3aed92f81136da6dc5848593fcf6f8b8d5c65c5e
Author: Russ Cox <rsc@golang.org>
Date: Sat Dec 22 17:23:33 2012 -0500
cmd/gc: add diagnostic for var, type, const named init
Before this CL, defining the variable worked fine, but then when
the implicit package-level init func was created, that caused a
name collision and a confusing error about the redeclaration.
Also add a test for issue 3705 (func init() needs body).
Fixes #4517.
R=ken2
CC=golang-dev
https://golang.org/cl/7008045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3aed92f81136da6dc5848593fcf6f8b8d5c65c5e
元コミット内容
cmd/gc: add diagnostic for var, type, const named init
Before this CL, defining the variable worked fine, but then when
the implicit package-level init func was created, that caused a
name collision and a confusing error about the redeclaration.
Also add a test for issue 3705 (func init() needs body).
Fixes #4517.
R=ken2
CC=golang-dev
https://golang.org/cl/7008045
変更の背景
Go言語には、各パッケージが持つことができる特別な関数initがあります。この関数は、パッケージがインポートされた際に、そのパッケージ内のすべての変数が初期化された後に自動的に実行されます。init関数は引数を取らず、戻り値もありません。また、明示的に呼び出すことはできません。
このコミット以前のGoコンパイラでは、開発者が誤ってinitという名前を変数、型、または定数として宣言した場合、コンパイラはすぐにエラーを報告しませんでした。代わりに、コンパイラがパッケージの初期化処理のために暗黙的にinit関数を生成しようとした際に、ユーザーが宣言したinitという名前のエンティティと、コンパイラが生成しようとしたinit関数との間で名前の衝突が発生していました。この衝突は、ユーザーにとって「再宣言」という、原因が分かりにくいエラーメッセージとして報告されていました。
このエラーメッセージは、なぜinitという名前が再宣言されたと見なされるのか、その根本的な理由(initが特別な関数名であること)を明確に示していなかったため、開発者を混乱させる可能性がありました。このコミットは、この混乱を解消し、initという名前が関数以外の目的で使用された場合に、より具体的で理解しやすい診断メッセージを即座に提供することを目的としています。
また、関連する問題として、func init()と宣言されたにもかかわらず、その関数本体が欠落している場合にコンパイラが適切なエラーを出さないというissue 3705も存在しました。このコミットでは、その問題に対するテストケースも追加され、コンパイラがこの状況を正しく検出できるようになっています。
前提知識の解説
Go言語のinit関数
Go言語のinit関数は、パッケージの初期化ロジックをカプセル化するための特別な関数です。
- 自動実行:
init関数は、パッケージがインポートされた際に、そのパッケージ内のすべてのグローバル変数が初期化された後に自動的に実行されます。 - 引数なし、戻り値なし:
func init()というシグネチャを持ち、引数も戻り値もありません。 - 複数定義可能: 1つのパッケージ内に複数の
init関数を定義できます。これらは定義された順序で実行されます。 - 呼び出し不可:
init関数はユーザーコードから明示的に呼び出すことはできません。 - 用途: データベース接続の確立、設定ファイルの読み込み、外部リソースの初期化など、プログラムの実行開始前に一度だけ実行する必要がある処理に利用されます。
Goコンパイラ(cmd/gc)
cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスは、字句解析、構文解析、意味解析、中間コード生成、最適化、コード生成といった複数のフェーズに分かれています。
dcl.cファイル
src/cmd/gc/dcl.cは、Goコンパイラのソースコードの一部であり、主に宣言(declaration)に関連する処理を扱っています。変数、関数、型などの宣言が構文的に正しいか、意味的に適切かなどをチェックし、コンパイラの内部表現に変換する役割を担っています。このファイルには、シンボルテーブルの管理や、名前解決に関するロジックが含まれています。
PEXTERNコンテキスト
Goコンパイラの内部では、宣言がどのスコープで行われているかを示すために様々なコンテキストが使用されます。PEXTERNは、パッケージレベル(グローバルスコープ)での宣言を示すコンテキストの一つです。このコンテキストは、関数内ではなく、パッケージのトップレベルで宣言される変数、型、定数、関数などに適用されます。init関数は常にパッケージレベルで宣言されるため、このPEXTERNコンテキストが関連します。
技術的詳細
このコミットの技術的詳細の核心は、Goコンパイラの宣言処理に、initという名前の特別なチェックを追加した点にあります。
変更はsrc/cmd/gc/dcl.cファイル内のdeclare関数に対して行われました。declare関数は、Goソースコード内で見つかった宣言を処理するコンパイラの重要な部分です。
追加されたコードは以下の条件をチェックします。
- 宣言コンテキストが
PEXTERNであること: これは、宣言がパッケージレベル(グローバルスコープ)で行われていることを意味します。init関数は常にパッケージレベルで定義されるため、このチェックは適切です。 - 宣言されるシンボルの名前が"init"であること: 宣言される識別子が文字列"init"と完全に一致するかどうかを確認します。
initが関数として宣言されていないこと:initが変数、型、または定数として宣言されている場合にこの診断がトリガーされます。
これらの条件がすべて満たされた場合、コンパイラはyyerror("cannot declare init - must be func", s);というエラーメッセージを出力します。このメッセージは、「initを宣言することはできません - 関数でなければなりません」という意味で、開発者に対してinitが特別な関数名であり、他の目的で使用できないことを明確に伝えます。
この変更により、コンパイラは以前のように暗黙のinit関数との名前衝突を待つのではなく、宣言の段階で即座に、より具体的で分かりやすいエラーを報告できるようになりました。これにより、開発者は問題を早期に特定し、修正することができます。
また、issue 3705に関連して、func init()と宣言されたにもかかわらず、関数本体が欠落している場合にエラーを出すためのテストケースが追加されました。これは、コンパイラがこのような構文エラーを正しく捕捉し、適切な診断メッセージを提供することを保証します。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index bf226d92a3..7bc9ce988e 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -188,6 +188,9 @@ declare(Node *n, int ctxt)
if(importpkg == nil && !typecheckok && s->pkg != localpkg)
yyerror("cannot declare name %S", s);
+ if(ctxt == PEXTERN && strcmp(s->name, "init") == 0)
+ yyerror("cannot declare init - must be func", s);
+
gen = 0;
if(ctxt == PEXTERN) {
externdcl = list(externdcl, n);
コアとなるコードの解説
上記のコードスニペットは、src/cmd/gc/dcl.cファイル内のdeclare関数への変更を示しています。
追加された行は以下の通りです。
if(ctxt == PEXTERN && strcmp(s->name, "init") == 0)
yyerror("cannot declare init - must be func", s);
if(ctxt == PEXTERN ...): この条件は、現在の宣言がパッケージレベル(グローバルスコープ)で行われているかどうかをチェックします。PEXTERNは、外部リンケージを持つシンボル、つまりパッケージのトップレベルで宣言されたシンボルを示すコンテキストです。strcmp(s->name, "init") == 0: この部分は、宣言されているシンボルの名前(s->name)が文字列"init"と完全に一致するかどうかをチェックします。strcmp関数は、2つの文字列が等しい場合に0を返します。yyerror("cannot declare init - must be func", s);: 上記の2つの条件が両方とも真である場合、つまり、パッケージレベルでinitという名前が宣言された場合、この行が実行されます。yyerrorはコンパイラのエラー報告関数であり、指定されたエラーメッセージ("cannot declare init - must be func")を出力します。sは、エラーが発生したシンボルに関連する情報を提供するために使用されます。
この変更により、Goコンパイラは、initという特別な名前がパッケージレベルで関数以外の目的(変数、型、定数など)で宣言された場合に、即座に明確なエラーメッセージを生成するようになります。これにより、開発者はGo言語のinit関数の特殊性をより早く理解し、コードの誤りを修正できるようになります。
関連リンク
- Go CL (Change List): https://golang.org/cl/7008045
- Issue 4517: https://golang.org/issue/4517 -
initが変数、型、定数として宣言された場合の紛らわしいエラーに関する問題。 - Issue 3705: https://golang.org/issue/3705 -
func init()が関数本体を伴わない場合にエラーを出すべきであるという問題。
参考にした情報源リンク
- Go言語の
init関数に関する公式ドキュメントやチュートリアル (一般的な知識として参照) - Goコンパイラのソースコード(
src/cmd/gc/dcl.c)の構造と機能に関する一般的な理解 strcmp関数のC言語標準ライブラリドキュメント (一般的な知識として参照)- Go言語のIssueトラッカー (上記Issueへのリンク)
- Go言語のChange List (上記CLへのリンク)