[インデックス 13818] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)において、シンボルの再宣言エラーメッセージを改善し、特にインポートによって引き起こされた再宣言の場合に、どのパッケージが原因であるかを明確に表示するように変更を加えるものです。これにより、開発者が再宣言エラーの原因を特定しやすくなり、デバッグの効率が向上します。
コミット
commit d06dcd45959f83c3b82c10116acc9a64beff14cd
Author: Daniel Morsing <daniel.morsing@gmail.com>
Date: Thu Sep 13 18:40:50 2012 +0200
cmd/gc: Specify which package import caused an redeclaration error.
Fixes #4012.
R=dave, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/6490082
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d06dcd45959f83c3b82c10116acc9a64beff14cd
元コミット内容
cmd/gc: Specify which package import caused an redeclaration error.
Fixes #4012.
R=dave, remyoudompheng, rsc
CC=golang-dev
https://golang.org/cl/6490082
変更の背景
Go言語では、同じスコープ内で同じ名前の識別子(変数、関数、型など)を複数回宣言することはできません。これを「再宣言(redeclaration)」と呼び、コンパイル時にエラーとなります。特に、複数のパッケージをインポートする際に、異なるパッケージが同じ名前の識別子をエクスポートしている場合、名前の衝突が発生し、再宣言エラーとなることがあります。
このコミットが作成された時点では、Goコンパイラがインポートによって引き起こされた再宣言エラーを報告する際、エラーメッセージがどのパッケージが衝突の原因であるかを具体的に示していませんでした。例えば、「X redeclared during import
」のような一般的なメッセージが表示されるだけで、開発者はどのインポートが問題を引き起こしているのかを手動で特定する必要がありました。これは、特に大規模なプロジェクトや多数の依存関係を持つ場合に、デバッグ作業を非常に困難にしていました。
この問題は、Goのイシュートラッカーで「Issue #4012」として報告されていたと考えられます(ただし、現在の公開されているGoのイシュートラッカーで直接この番号のイシューが見つからない場合、内部的なイシュー番号であるか、または既に解決済みでクローズされている可能性があります)。このコミットは、このユーザーエクスペリエンスの課題を解決し、より具体的で役立つエラーメッセージを提供することを目的としています。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGoコンパイラの内部構造とGo言語の概念に関する知識が必要です。
1. Goコンパイラ (cmd/gc
) の役割
cmd/gc
は、Go言語の公式コンパイラのフロントエンド部分であり、Goのソースコードを解析し、中間表現を生成する主要な役割を担っています。これには、字句解析、構文解析、型チェック、シンボル解決などが含まれます。再宣言エラーの検出と報告も、この段階で行われます。
2. シンボルテーブルと Sym
構造体
コンパイラは、プログラム内のすべての識別子(変数名、関数名、型名、パッケージ名など)を管理するために「シンボルテーブル」を使用します。シンボルテーブルは、各識別子に関する情報(名前、型、スコープ、定義場所など)を格納します。
Goコンパイラの内部では、これらの識別子情報は Sym
(Symbol) 構造体によって表現されます。Sym
構造体は、シンボルの名前、それが属するパッケージ、そしてそのシンボルが定義されているASTノードへの参照など、様々なメタデータを保持します。
Sym->Pkg
: シンボルが属するパッケージへのポインタ。Sym->Name
: シンボルの名前(例:fmt.Println
のPrintln
)。Sym->Def
: シンボルが定義されている抽象構文木(AST)ノードへの参照。Sym->lastlineno
: シンボルが最後に宣言されたソースコード上の行番号。これが0
の場合、通常はインポート処理中に宣言されたことを示します。
3. パッケージとインポート
Go言語はパッケージによってコードをモジュール化します。import
文を使用して、他のパッケージで定義された識別子を現在のパッケージで使用できるようになります。
- 通常インポート:
import "fmt"
のように、パッケージ名を指定してインポートします。この場合、fmt.Println
のようにパッケージ名をプレフィックスとして使用します。 - ドットインポート (
import . "pkg"
):import . "fmt"
のように、パッケージ名の代わりにドットを使用します。これにより、インポートされたパッケージの識別子を、パッケージ名をプレフィックスなしで直接使用できるようになります(例:Println
)。ドットインポートは名前の衝突を引き起こしやすいため、注意が必要です。
4. 再宣言エラー (redeclaration error
)
Goでは、同じスコープ内で同じ名前の識別子を複数回宣言することはできません。例えば、以下のようなコードは再宣言エラーになります。
package main
func main() {
var x int = 10
var x int = 20 // エラー: x redeclared in this block
}
インポートにおいても同様で、異なるパッケージから同じ名前の識別子がインポートされた場合、名前の衝突が発生し、再宣言エラーとなります。
5. redeclare
関数
redeclare
関数は、Goコンパイラの内部で再宣言エラーを検出した際に、エラーメッセージを生成し報告するために使用されるユーティリティ関数です。この関数は、再宣言されたシンボルと、それがどこで再宣言されたかを示す文字列(例: "during import")を受け取ります。
6. importsym
および importdot
関数
importsym
: コンパイラが外部パッケージからシンボルをインポートする際に使用される内部関数です。インポートされたシンボルが既に現在のスコープで宣言されていないかを確認し、もし再宣言であればredeclare
を呼び出します。importdot
: ドットインポート(import . "pkg"
)を処理するための内部関数です。インポートされたパッケージのすべてのエクスポートされたシンボルを現在のパッケージのスコープに導入し、名前の衝突がないかを確認します。
技術的詳細
このコミットは、Goコンパイラのcmd/gc
ディレクトリ内の4つのファイル(dcl.c
, export.c
, go.h
, subr.c
)に変更を加えています。主な変更点は、Sym
構造体に新しいフィールドを追加し、再宣言エラーを報告するredeclare
関数が、インポート元のパッケージパスをエラーメッセージに含めるように修正されたことです。
1. Sym
構造体への origpkg
フィールドの追加 (src/cmd/gc/go.h
)
src/cmd/gc/go.h
ファイルでは、Sym
構造体に新しいフィールドorigpkg
が追加されました。
// src/cmd/gc/go.h
struct Sym
{
// ... 既存のフィールド ...
int32 lastlineno; // last declaration for diagnostic
Pkg* origpkg; // original package for . import
};
Pkg* origpkg;
: このフィールドは、ドットインポート(import . "pkg"
)によって現在のパッケージに導入されたシンボルが、元々どのパッケージに属していたかを追跡するために使用されます。これにより、再宣言エラーが発生した際に、そのシンボルがどの元のパッケージから来たのかを特定できるようになります。
2. redeclare
関数の変更 (src/cmd/gc/dcl.c
)
src/cmd/gc/dcl.c
のredeclare
関数が修正され、インポートによる再宣言の場合に、元のパッケージパスをエラーメッセージに含めるようになりました。
// src/cmd/gc/dcl.c
void
redeclare(Sym *s, char *where)
{
Strlit *pkgstr;
if(s->lastlineno == 0) { // インポート時に宣言された場合
pkgstr = s->origpkg ? s->origpkg->path : s->pkg->path;
yyerror("%S redeclared %s\n"
"\tprevious declaration during import \"%Z\"",
s, where, pkgstr);
} else
yyerror("%S redeclared %s\n"
"\tprevious declaration at %L",
s, where, s->lastlineno);
}
s->lastlineno == 0
という条件は、シンボルがインポート処理中に宣言されたことを示します。- この条件が真の場合、
pkgstr
変数に、シンボルの元のパッケージ(s->origpkg
が設定されていればそれを使用、そうでなければ通常のs->pkg
)のパスが格納されます。 - エラーメッセージのフォーマット文字列が変更され、
\"%Z\"
というプレースホルダーが追加されました。これはsmprint
関数(後述)によってパッケージパスに置き換えられます。これにより、エラーメッセージに「previous declaration during import "path/to/package"
」のような具体的な情報が含まれるようになります。
3. importsym
関数の変更 (src/cmd/gc/export.c
)
src/cmd/gc/export.c
のimportsym
関数は、外部パッケージからシンボルをインポートする際に呼び出されます。この関数が、再宣言エラーを報告する際に、インポート元のパッケージパスをredeclare
関数に渡すように変更されました。
// src/cmd/gc/export.c
Sym*
importsym(Sym *s, int op)
{
char *pkgstr;
if(s->def != N && s->def->op != op) {
pkgstr = smprint("during import \"%Z\"", importpkg->path);
redeclare(s, pkgstr);
}
// ... 既存の処理 ...
}
- 再宣言が検出された場合、
smprint
関数を使用して「during import "path/to/package"
」という形式の文字列を生成し、これをredeclare
関数に渡します。importpkg
は現在インポート中のパッケージを指します。
4. importdot
関数の変更 (src/cmd/gc/subr.c
)
src/cmd/gc/subr.c
のimportdot
関数は、ドットインポートを処理します。この関数が、ドットインポートされたシンボルに対してSym->origpkg
フィールドを設定し、再宣言エラー時にこの情報を使用するように変更されました。
// src/cmd/gc/subr.c
void
importdot(Pkg *opkg, Node *pack)
{
// ... 既存の処理 ...
char *pkgerror;
// ... ループ内 ...
if(s1->def != N) {
pkgerror = smprint("during import \"%Z\"", opkg->path);
redeclare(s1, pkgerror);
continue;
}
s1->def = s->def;
s1->block = s->block;
s1->def->pack = pack;
s1->origpkg = opkg; // ここでorigpkgを設定
n++;
// ... ループの終わり ...
}
- ドットインポートによってシンボル
s
がs1
として現在のスコープに導入される際、s1->origpkg = opkg;
という行が追加され、そのシンボルが元々opkg
(インポート元のパッケージ)に属していたことを記録します。 - 再宣言エラーが発生した場合、
importsym
と同様にsmprint
を使ってパッケージパスを含むエラー文字列を生成し、redeclare
に渡します。
smprint
関数について
smprint
は、Goコンパイラ内部で使用される文字列フォーマット関数です。%Z
というフォーマット指定子は、Pkg
構造体のパス(Pkg->path
)を文字列として展開するために使用されます。これにより、動的にパッケージパスをエラーメッセージに埋め込むことが可能になります。
コアとなるコードの変更箇所
src/cmd/gc/dcl.c
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -150,11 +150,14 @@ testdclstack(void)
void
redeclare(Sym *s, char *where)
{
- if(s->lastlineno == 0)
- yyerror("%S redeclared %s\n"
- "\tprevious declaration during import",
- s, where);
- else
+ Strlit *pkgstr;
+
+ if(s->lastlineno == 0) {
+ pkgstr = s->origpkg ? s->origpkg->path : s->pkg->path;
+ yyerror("%S redeclared %s\n"
+ "\tprevious declaration during import \"%Z\"",
+ s, where, pkgstr);
+ } else
yyerror("%S redeclared %s\n"
"\tprevious declaration at %L",
s, where, s->lastlineno);
src/cmd/gc/export.c
--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -349,8 +349,12 @@ dumpexport(void)
Sym*
importsym(Sym *s, int op)
{
- if(s->def != N && s->def->op != op)
- redeclare(s, "during import");
+ char *pkgstr;
+
+ if(s->def != N && s->def->op != op) {
+ pkgstr = smprint("during import \"%Z\"", importpkg->path);
+ redeclare(s, pkgstr);
+ }
// mark the symbol so it is not reexported
if(s->def == N) {
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -365,6 +365,7 @@ struct Sym
Label* label; // corresponding label (ephemeral)
int32 block; // blocknumber to catch redeclaration
int32 lastlineno; // last declaration for diagnostic
+ Pkg* origpkg; // original package for . import
};
#define S ((Sym*)0)
src/cmd/gc/subr.c
--- a/src/cmd/gc/subr.c
+++ b/src/cmd/gc/subr.c
@@ -382,6 +382,7 @@ importdot(Pkg *opkg, Node *pack)
Sym *s, *s1;
uint32 h;
int n;
+ char *pkgerror;
n = 0;
for(h=0; h<NHASH; h++) {
@@ -394,12 +395,14 @@ importdot(Pkg *opkg, Node *pack)
continue;
s1 = lookup(s->name);
if(s1->def != N) {
- redeclare(s1, "during import");
+ pkgerror = smprint("during import \"%Z\"", opkg->path);
+ redeclare(s1, pkgerror);
continue;
}
s1->def = s->def;
s1->block = s->block;
s1->def->pack = pack;
+ s1->origpkg = opkg;
n++;
}
// ... 既存の処理 ...
コアとなるコードの解説
このコミットの核心は、Goコンパイラが再宣言エラーを報告する際の診断メッセージの質を向上させることにあります。
-
Sym
構造体の拡張 (go.h
):Sym
構造体にorigpkg
という新しいフィールドが追加されました。これは、特にドットインポート(import . "pkg"
)によって現在のスコープに導入されたシンボルが、元々どのパッケージに由来するのかを記録するためのものです。これにより、コンパイラはシンボルの「真の」起源を追跡できるようになります。
-
redeclare
関数の診断強化 (dcl.c
):redeclare
関数は、再宣言エラーが発生した際に呼び出される中心的な関数です。- 変更前は、インポートによる再宣言の場合(
s->lastlineno == 0
)、単に「previous declaration during import
」と表示されていました。 - 変更後、
s->origpkg
(ドットインポートの場合)またはs->pkg
(通常のインポートの場合)からパッケージパスを取得し、yyerror
関数に渡されるメッセージフォーマットに\"%Z\"
というプレースホルダーを追加しました。この%Z
は、smprint
関数によって実際のパッケージパスに置き換えられます。 - 結果として、エラーメッセージは「
previous declaration during import "path/to/conflicting/package"
」のように、より具体的で役立つ情報を提供するようになりました。
-
インポート処理におけるパッケージパスの伝達 (
export.c
,subr.c
):importsym
関数(一般的なシンボルインポート)とimportdot
関数(ドットインポート)の両方で、再宣言エラーが発生する可能性がある箇所が修正されました。- これらの関数は、再宣言エラーを
redeclare
関数に報告する際に、smprint
関数を使用して、現在インポート中のパッケージのパスを含む文字列を生成し、redeclare
関数に渡すようになりました。 - 特に
importdot
では、ドットインポートされたシンボルs1
に対して、その元のパッケージopkg
をs1->origpkg
に明示的に設定する行が追加されました。これにより、redeclare
関数がorigpkg
フィールドを利用して正確なパッケージパスを特定できるようになります。
これらの変更により、Goコンパイラは、インポートによる名前の衝突が原因で発生する再宣言エラーに対して、より詳細でデバッグに役立つ情報を提供できるようになりました。これは、開発者の生産性向上に大きく貢献する改善です。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Goコンパイラのソースコード(GitHub): https://github.com/golang/go
参考にした情報源リンク
- Goコンパイラ内部の
Sym
構造体に関する情報: - Go言語の再宣言エラーに関する一般的な情報:
- Go CL 6490082: 検索結果ではこのCLが別のコミット(Go 1.22.4への更新)を指しているため、このコミットの直接のCLではない可能性があります。コミットメッセージに記載されているCL番号は、古いまたは内部的な参照である可能性があります。
- https://go.googlesource.com/go/+/6490082 (これは現在のコミットとは異なる内容を示しています)