[インデックス 17509] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における「imported and not used」(インポートされたが使用されていない)エラーメッセージの改善に関するものです。具体的には、パッケージがリネームインポートされた場合や、インポートパスの最後の要素と実際のパッケージ名が異なる場合に、エラーメッセージにそのパッケージ名が明示的に表示されるように変更されました。これにより、開発者はどのパッケージが未使用であるかをより正確に把握できるようになります。
コミット
commit 8d530f2472f1c4ef5cf0513a6735869fb606fa96
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 9 12:21:09 2013 -0400
cmd/gc: show package name in 'imported and not used' error
Fixes #5957.
R=ken2
CC=golang-dev
https://golang.org/cl/13250046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8d530f2472f1c4ef5cf0513a6735869fb606fa96
元コミット内容
cmd/gc: show package name in 'imported and not used' error
このコミットの目的は、Goコンパイラが「imported and not used」エラーを報告する際に、インポートされたパッケージの実際の名前をエラーメッセージに含めるようにすることです。これは、Go言語のIssue #5957を修正するものです。
変更の背景
Go言語では、インポートされたパッケージがコード内で使用されない場合、コンパイル時に「imported and not used」というエラーが発生します。これは、不要な依存関係を排除し、コードの可読性を高めるためのGoの設計思想の一部です。
しかし、従来のこのエラーメッセージには、インポートパスのみが表示され、リネームインポート(例: import foo "fmt"
)や、インポートパスの最後の要素と実際のパッケージ名が異なる場合(例: import "github.com/user/repo/mypackage"
で package mypkg
と宣言されている場合)に、どの名前でインポートされたパッケージが未使用なのかが不明瞭になるという問題がありました。
例えば、import foo "fmt"
と記述し、fmt
パッケージをfoo
という名前でインポートしたが使用しなかった場合、従来のエラーメッセージは単に「imported and not used: "fmt"」と表示され、foo
というエイリアスが使われていないことが直接的に示されませんでした。これにより、特に大規模なプロジェクトや複雑なインポート構造を持つコードベースでは、開発者がエラーの原因を特定し、修正するのに時間がかかる可能性がありました。
このコミットは、この問題を解決し、エラーメッセージの明確性を向上させることを目的としています。
前提知識の解説
Go言語のパッケージとインポート
Go言語では、コードは「パッケージ」という単位で整理されます。各Goファイルはpackage
宣言を持ち、そのファイルがどのパッケージに属するかを示します。他のパッケージの機能を利用するには、import
キーワードを使ってそのパッケージをインポートする必要があります。
インポートの形式にはいくつか種類があります。
- 標準的なインポート:
import "path/to/package"
- この場合、パッケージ内のエクスポートされた要素は、インポートパスの最後の要素(例:
package
)をプレフィックスとして使用してアクセスされます(例:package.Function()
)。
- この場合、パッケージ内のエクスポートされた要素は、インポートパスの最後の要素(例:
- リネームインポート:
import alias "path/to/package"
- パッケージを別の名前(
alias
)でインポートします。この場合、エクスポートされた要素はalias.Function()
のようにアクセスされます。
- パッケージを別の名前(
- ドットインポート:
import . "path/to/package"
- パッケージ内のエクスポートされた要素を、パッケージ名をプレフィックスなしで直接アクセスできるようにします。これは通常、非推奨とされていますが、テストコードなどで使用されることがあります。
- ブランクインポート:
import _ "path/to/package"
- パッケージをインポートしますが、そのパッケージ内のエクスポートされた要素を直接使用することはありません。主に、パッケージの
init
関数を実行するためや、サイドエフェクトのために使用されます。
- パッケージをインポートしますが、そのパッケージ内のエクスポートされた要素を直接使用することはありません。主に、パッケージの
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードをコンパイルして実行可能なバイナリを生成する役割を担っています。コンパイルプロセス中に、構文エラー、型エラー、未使用の変数やインポートなど、様々な種類の問題を検出して報告します。
imported and not used
エラー
Goコンパイラが報告するエラーの一つで、ソースコード内でインポートされたパッケージが実際には使用されていない場合に発生します。このエラーは、コードの品質を保ち、不要な依存関係を排除するためのGoの厳格なルールの一部です。
yyerrorl
関数
yyerrorl
は、Goコンパイラのフロントエンド(字句解析器/構文解析器)で使用されるエラー報告関数です。指定された行番号でエラーメッセージを出力するために使用されます。
Strlit
構造体
Goコンパイラの内部で使用される文字列リテラルを表す構造体です。path->s
のようにアクセスされる場合、これは通常、インポートパスの文字列を指します。
技術的詳細
このコミットの主要な変更は、src/cmd/gc/lex.c
ファイルに新しい静的関数pkgnotused
が導入され、既存のyyerrorl
呼び出しがこの新しい関数に置き換えられた点です。
pkgnotused
関数の導入
新しい関数pkgnotused
は、以下の引数を取ります。
lineno
: エラーが発生した行番号。path
: インポートされたパッケージの完全なインポートパス(Strlit
型)。name
: ソースコード内でそのパッケージがインポートされた際に使用された名前(リネームインポートの場合のエイリアス、または標準インポートの場合のパッケージ名)。
この関数は、インポートパスの最後の要素(例: "path/to/package"
の "package"
)を抽出し、それがname
引数と一致するかどうかを比較します。
-
インポートパスの最後の要素の抽出:
strrchr(path->s, '/')
を使用して、インポートパス文字列 (path->s
) の中で最後のスラッシュ (/
) の位置を探します。- もしスラッシュが見つかれば、その次の文字から文字列の最後までがパッケージの要素名とみなされます(
elem++
)。 - スラッシュが見つからなければ、インポートパス全体が要素名とみなされます(
elem = path->s
)。 - GoのインポートパスはWindows上でも常にフォワードスラッシュを使用するため、この処理はOSに依存しません。
- もしスラッシュが見つかれば、その次の文字から文字列の最後までがパッケージの要素名とみなされます(
-
エラーメッセージの生成:
- 抽出された要素名(
elem
)と、ソースコードで使われた名前(name
)がstrcmp
で比較されます。 - もし両者が一致する場合(例:
import "fmt"
でfmt
が使われていない場合)、従来通りのエラーメッセージ「imported and not used: "%Z"
」が出力されます。ここで%Z
はインポートパスに置き換えられます。 - もし両者が一致しない場合(例:
import foo "fmt"
でfoo
が使われていない場合、またはimport "./a"
でpackage surprise
が宣言されている場合)、より詳細なエラーメッセージ「imported and not used: "%Z" as %s
」が出力されます。ここで%Z
はインポートパスに、%s
はname
(エイリアスまたは実際のパッケージ名)に置き換えられます。
- 抽出された要素名(
既存のyyerrorl
呼び出しの置き換え
mkpackage
関数内で、未使用のインポートを検出した際にyyerrorl
を直接呼び出していた箇所が、新しく定義されたpkgnotused
関数への呼び出しに置き換えられました。これにより、エラーメッセージの生成ロジックが一元化され、より詳細な情報が提供されるようになりました。
具体的には、以下の2つの箇所で変更が行われています。
s->def->used
がfalse
の場合(通常の未使用インポート)s->def->pack->used
がfalse
の場合(ドットインポートなど、トップレベルの名前が未使用の場合)
これらの変更により、コンパイラはインポートパスだけでなく、そのパッケージがコード内でどのような名前で参照されようとしていたか(または参照されなかったか)をエラーメッセージに含めることができるようになりました。
コアとなるコードの変更箇所
src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -2279,6 +2279,28 @@ yytinit(void)\
}\t\t
}\
\
+static void
+pkgnotused(int lineno, Strlit *path, char *name)
+{\
+ char *elem;
+
+ // If the package was imported with a name other than the final
+ // import path element, show it explicitly in the error message.
+ // Note that this handles both renamed imports and imports of
+ // packages containing unconventional package declarations.
+ // Note that this uses / always, even on Windows, because Go import
+ // paths always use forward slashes.
+ elem = strrchr(path->s, '/');
+ if(elem != nil)
+ elem++;
+ else
+ elem = path->s;
+ if(strcmp(elem, name) == 0)
+ yyerrorl(lineno, "imported and not used: \"%Z\"", path);
+ else
+ yyerrorl(lineno, "imported and not used: \"%Z\" as %s", path, name);
+}
+\
void
mkpackage(char* pkgname)
{
@@ -2304,7 +2326,7 @@ mkpackage(char* pkgname)\
// errors if a conflicting top-level name is
// introduced by a different file.
if(!s->def->used && !nsyntaxerrors)
- yyerrorl(s->def->lineno, "imported and not used: \"%Z\"", s->def->pkg->path);\
+ pkgnotused(s->def->lineno, s->def->pkg->path, s->name);\
s->def = N;
continue;
}
@@ -2312,7 +2334,7 @@ mkpackage(char* pkgname)\
// throw away top-level name left over
// from previous import . "x"
if(s->def->pack != N && !s->def->pack->used && !nsyntaxerrors) {
- yyerrorl(s->def->pack->lineno, "imported and not used: \"%Z\"", s->def->pack->pkg->path);\
+ pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, s->name);\
s->def->pack->used = 1;\
}\
s->def = N;
コアとなるコードの解説
pkgnotused
関数
static void
pkgnotused(int lineno, Strlit *path, char *name)
{
char *elem;
// If the package was imported with a name other than the final
// import path element, show it explicitly in the error message.
// Note that this handles both renamed imports and imports of
// packages containing unconventional package declarations.
// Note that this uses / always, even on Windows, because Go import
// paths always use forward slashes.
elem = strrchr(path->s, '/'); // インポートパスの最後の '/' を検索
if(elem != nil)
elem++; // '/' の次の文字からが要素名
else
elem = path->s; // '/' がなければパス全体が要素名
// インポートパスの最後の要素と、コードで使われた名前を比較
if(strcmp(elem, name) == 0)
// 一致する場合(通常のインポートで未使用)
yyerrorl(lineno, "imported and not used: \"%Z\"", path);
else
// 一致しない場合(リネームインポートや、パッケージ名とパスの最後の要素が異なる場合)
yyerrorl(lineno, "imported and not used: \"%Z\" as %s", path, name);
}
この関数は、エラーメッセージを生成する際の中心的なロジックを担っています。path
(インポートパス)とname
(コード内で使用された、または使用されるべきだった名前)を比較し、それに応じて異なるエラーメッセージを生成します。これにより、エラーメッセージがより文脈に即したものになります。
mkpackage
関数内の変更
mkpackage
関数は、Goコンパイラがパッケージのインポートと定義を処理する際に使用される重要な関数です。この関数内で、未使用のインポートを検出する既存のロジックが変更されました。
変更前:
yyerrorl(s->def->lineno, "imported and not used: \"%Z\"", s->def->pkg->path);
変更後:
pkgnotused(s->def->lineno, s->def->pkg->path, s->name);
この変更により、直接yyerrorl
を呼び出す代わりに、新しく定義されたpkgnotused
関数が呼び出されるようになりました。pkgnotused
関数には、インポートパス(s->def->pkg->path
)だけでなく、そのパッケージがコード内でどのような名前で参照されようとしていたかを示すs->name
も渡されます。これにより、pkgnotused
関数がより詳細なエラーメッセージを生成できるようになります。
同様の変更が、ドットインポートに関連する未使用のトップレベル名を処理する箇所でも行われています。
これらの変更により、Goコンパイラは、単に「どのパスのパッケージが未使用か」だけでなく、「どの名前でインポートされたパッケージが未使用か」という情報もエラーメッセージに含めることができるようになり、開発者にとってのデバッグ体験が向上しました。
関連リンク
- Go Issue #5957: cmd/gc: show package name in 'imported and not used' error
- Go CL 13250046: cmd/gc: show package name in 'imported and not used' error
参考にした情報源リンク
- Go言語の公式ドキュメント (パッケージとインポートに関する情報)
- Goコンパイラのソースコード (
src/cmd/gc/lex.c
の周辺コード) strrchr
関数のC言語リファレンスstrcmp
関数のC言語リファレンス- Go言語のIssueトラッカー (Issue #5957の詳細)
- Go Code Review (CL 13250046の詳細)