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

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

コミット

commit 5be1821a31f1b88b8b9b9083454143f5aa90790d
Author: Russ Cox <rsc@golang.org>
Date:   Fri Sep 20 15:25:43 2013 -0400

    cmd/gc: fix imported and not used error for import .
    
    Fixes issues 6420.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/13703044

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

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

元コミット内容

cmd/gc: fix imported and not used error for import .

このコミットは、Goコンパイラ(cmd/gc)において、import .(ドットインポート)を使用した場合に発生する「imported and not used」(インポートされたが使用されていない)というエラーが誤って報告される問題を修正するものです。具体的には、Go言語のIssue 6420を解決します。

変更の背景

Go言語では、パッケージをインポートする際に、そのパッケージ内のエクスポートされた識別子を修飾なしで直接参照できるようにする「ドットインポート」(import . "package_name")という機能があります。例えば、import . "fmt"とすると、fmt.PrintlnではなくPrintlnと書けるようになります。

しかし、Goコンパイラには、インポートされたパッケージがコード内で実際に使用されていない場合に「imported and not used」というエラーを報告する機能があります。これは、未使用のインポートを排除し、コードのクリーンさを保つための重要な機能です。

このコミットが修正しようとしている問題は、import .を使用した場合に、コンパイラがそのインポートを正しく「使用済み」と認識せず、実際には使用されているにもかかわらず「imported and not used」エラーを誤って報告してしまうというバグでした。Go言語のIssue 6420で報告されたこの問題は、開発者がドットインポートを意図的に使用しているにもかかわらず、不必要なエラーに直面するという不便さを引き起こしていました。

前提知識の解説

Go言語のパッケージインポート

Go言語では、コードをモジュール化するためにパッケージを使用します。他のパッケージの機能を利用するには、importキーワードを使ってそのパッケージをインポートする必要があります。

  • 通常のインポート: import "fmt" のように記述し、パッケージ内の識別子を参照する際には fmt.Println のようにパッケージ名を修飾します。
  • エイリアスインポート: import io "fmt" のように記述し、io.Println のようにエイリアス名で参照します。
  • ブランクインポート: import _ "image/png" のように記述し、パッケージの初期化処理のみを実行し、パッケージ内の識別子を直接使用しない場合に用います。
  • ドットインポート: import . "fmt" のように記述し、インポートしたパッケージ内のエクスポートされた識別子を、パッケージ名を修飾せずに直接参照できるようにします。これは、テストコードや、特定のパッケージの関数を頻繁に使う場合にコードを簡潔にするために使われることがありますが、名前の衝突やコードの可読性低下を招く可能性があるため、一般的には推奨されません。

Goコンパイラの「imported and not used」エラー

Goコンパイラは、インポートされたパッケージがコード内で一度も使用されていない場合に、コンパイルエラーとして「imported and not used」を報告します。これは、不要な依存関係を排除し、ビルド時間を短縮し、コードベースを整理するためのGo言語の設計思想の一部です。このエラーは、開発者が誤ってインポートを追加したり、コードの変更によってインポートが不要になったりした場合に役立ちます。

cmd/gc

cmd/gcは、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する役割を担っています。このコンパイラは、構文解析、型チェック、最適化、コード生成など、コンパイルプロセスの様々な段階を担当します。今回の修正は、このコンパイラのセマンティック分析(意味解析)部分、特にインポートの使用状況を追跡するロジックに関連しています。

技術的詳細

このコミットの技術的な核心は、Goコンパイラが「ドットインポート」されたパッケージの使用状況をどのように判断するか、という点にあります。

Goコンパイラには、pkgnotusedという関数が存在します。この関数は、インポートされたパッケージが使用されていない場合にエラーを報告する役割を担っています。元の実装では、pkgnotused関数は、インポートされたパッケージのパスと、そのパッケージがエイリアスとしてインポートされた場合のname(エイリアス名)を受け取っていました。

問題は、import . "package_name" のようにドットインポートされた場合、エイリアス名がnil(またはそれに相当する値)として渡されるべきなのに、元のコンパイラのロジックでは、このname引数がnilであるケースが適切に処理されていなかった点にあります。

具体的には、pkgnotused関数内の以下の条件分岐が問題でした。

if(strcmp(elem, name) == 0)

ここでnamenilである場合、strcmp関数は未定義の動作を引き起こす可能性があり、結果としてドットインポートが正しく「使用済み」と判断されず、誤って「imported and not used」エラーが報告されていました。

このコミットでは、この問題を解決するために、strcmpの呼び出しの前にname == nilというチェックを追加しています。

if(name == nil || strcmp(elem, name) == 0)

この変更により、namenil(つまりドットインポートの場合)であれば、strcmpの評価をスキップして、そのインポートが使用されていると見なすようになります。これにより、ドットインポートが正しく処理され、誤ったエラー報告がなくなります。

また、mkpackage関数内のpkgnotusedの呼び出しも修正されています。

pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, s->name); // 修正前
pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, nil); // 修正後

修正前は、ドットインポートの場合でもs->name(これはエイリアス名、またはトップレベルの名前)が渡されていましたが、修正後は明示的にnilが渡されるようになりました。これにより、pkgnotused関数がドットインポートを正しく識別し、適切なロジックを適用できるようになります。

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

変更は主に src/cmd/gc/lex.c ファイルにあります。

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -2296,7 +2296,7 @@ pkgnotused(int lineno, Strlit *path, char *name)
 		elem++;
 	else
 		elem = path->s;
-	if(strcmp(elem, name) == 0)
+	if(name == nil || strcmp(elem, name) == 0)
 		yyerrorl(lineno, "imported and not used: \"%Z\"", path);
 	else
 		yyerrorl(lineno, "imported and not used: \"%Z\" as %s", path, name);
@@ -2335,7 +2335,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) {
-						pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, s->name);
+						pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, nil);
 						s->def->pack->used = 1;
 					}
 					s->def = N;

また、テストケース test/import1.go も更新されています。

--- a/test/import1.go
+++ b/test/import1.go
@@ -14,5 +14,6 @@ import bufio "os"	// ERROR "redeclared|redefinition|incompatible" "imported and
 
 import (
 	"fmt"	// GCCGO_ERROR "previous|not used"
-	fmt "math"	// ERROR "redeclared|redefinition|incompatible" "imported and not used"
+	fmt "math"	// ERROR "redeclared|redefinition|incompatible" "imported and not used: \"x22math\\x22 as fmt"
+	. "math"	// ERROR "imported and not used: \"x22math\\x22$"
 )

コアとなるコードの解説

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

  1. pkgnotused 関数の変更:

    • 元のコード: if(strcmp(elem, name) == 0)
    • 変更後: if(name == nil || strcmp(elem, name) == 0)
    • この変更が最も重要です。pkgnotused関数は、インポートされたパッケージが使用されているかどうかをチェックし、使用されていない場合にエラーを報告します。name引数は、インポートにエイリアスが指定されている場合にそのエイリアス名が入ります。ドットインポートの場合、エイリアスは存在しないため、namenilであるべきです。
    • 変更前は、namenilの場合にstrcmpが呼び出され、不正なメモリアクセスや予期せぬ動作を引き起こす可能性がありました。
    • 変更後は、name == nilという条件が追加されたことで、namenilであればstrcmpの評価をスキップし、そのインポートが「使用済み」であると判断されるようになります。これにより、ドットインポートが正しく処理され、誤った「imported and not used」エラーが回避されます。
  2. mkpackage 関数内の pkgnotused 呼び出しの変更:

    • 元のコード: pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, s->name);
    • 変更後: pkgnotused(s->def->pack->lineno, s->def->pack->pkg->path, nil);
    • mkpackage関数は、パッケージの定義を処理する際に呼び出されます。この部分では、以前のドットインポートによって残されたトップレベルの名前を破棄するロジックの一部として、pkgnotusedが呼び出されていました。
    • 修正前は、s->namepkgnotusedに渡されていましたが、これはドットインポートの場合には適切ではありませんでした。
    • 修正後は、明示的にnilが渡されるようになりました。これにより、pkgnotused関数がドットインポートのケースを正しく認識し、上記のname == nilチェックが有効に機能するようになります。

test/import1.go の変更

  • 新しいテストケースとして、. "math" のドットインポートが追加されました。
  • このテストケースは、このコミットによって修正された問題が実際に解決されたことを検証するために追加されました。修正前であれば、この行で「imported and not used」エラーが報告されていたはずですが、修正後は期待通りにエラーが報告されなくなります(ただし、このテストファイル自体は他のエラーを意図的に含んでいるため、全体としてはエラーになります)。
  • 既存の fmt "math" のエラーメッセージも、より詳細な情報を含むように更新されています。これは、このコミットの直接的な修正とは異なりますが、関連するエラーメッセージの改善の一環として行われた可能性があります。

これらの変更により、Goコンパイラはドットインポートを正しく処理し、開発者が意図した通りにコードを記述できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (パッケージとインポートに関するセクション)
  • Go言語のソースコード (特に src/cmd/gc/lex.c)
  • Go言語のIssueトラッカー (Issue 6420の議論)
  • strcmp関数のC言語リファレンス
  • Go言語のコンパイラ設計に関する一般的な知識