[インデックス 13362] ファイルの概要
このコミットは、Go言語の抽象構文木(AST)を扱う go/ast
パッケージにおいて、複数のブランクインポート(_ "package"
)が正しく処理されるように修正を加えるものです。具体的には、go/ast/resolve.go
内のパッケージ解決ロジックが変更され、ブランクインポートがファイルスコープ内で重複して宣言されることによるバグが修正されました。これに伴い、関連するテストファイル src/pkg/exp/types/testdata/test0.src
に複数のブランクインポートを含むテストケースが追加されています。
コミット
commit ca2ae27dd08ac33fa2bca010906633d8b8432e4d
Author: Robert Griesemer <gri@golang.org>
Date: Mon Jun 18 21:56:41 2012 -0700
go/ast: multiple "blank" imports are permitted
R=rsc, dsymonds
CC=golang-dev
https://golang.org/cl/6303099
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ca2ae27dd08ac33fa2bca010906633d8b8432e4d
元コミット内容
go/ast: multiple "blank" imports are permitted
R=rsc, dsymonds
CC=golang-dev
https://golang.org/cl/6303099
変更の背景
Go言語では、パッケージをインポートする際に、そのパッケージの識別子を明示的に使用しない場合でも、パッケージの初期化処理(init
関数など)を実行するためにインポートすることが可能です。これを「ブランクインポート(blank import)」と呼び、_ "package/path"
の形式で記述します。
このコミットが作成された時点では、go/ast
パッケージ、特にASTの解決(resolve)フェーズにおいて、複数のブランクインポートが存在する場合に内部的なバグが発生していました。具体的には、ブランクインポートされたパッケージがファイルスコープ内で不適切に処理され、複数のブランクインポートが許可されない、またはエラーを引き起こす可能性がありました。このバグにより、開発者は複数のブランクインポートを安全に使用することができませんでした。
この変更は、この既存のバグを修正し、Go言語の仕様に沿って複数のブランクインポートが正しく機能するようにするために行われました。
前提知識の解説
Go言語のインポート文
Go言語の import
文は、他のパッケージで定義された識別子(関数、変数、型など)を現在のパッケージで使用するために必要です。一般的な形式は import "package/path"
です。
ブランクインポート (_ "package"
)
ブランクインポートは、インポートしたパッケージの識別子をコード内で直接使用しない場合に用いられます。主な用途は以下の通りです。
- パッケージの副作用の利用: インポートされたパッケージの
init
関数が実行されることを目的とします。例えば、データベースドライバの登録や、特定のプロトコルのハンドラの登録など、パッケージの初期化時に行われる副作用を利用したい場合に用いられます。 - コンパイルエラーの回避: インポートしたパッケージ内の識別子をコード内で使用しない場合、Goコンパイラは「インポートされたが使用されていないパッケージ」としてエラーを報告します。ブランクインポートを使用することで、このエラーを回避しつつ、パッケージの初期化処理を実行できます。
go/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードの抽象構文木(Abstract Syntax Tree, AST)を表現するための型と関数を提供します。コンパイラやツール(go fmt
, go vet
など)は、ソースコードをASTにパースし、そのASTを操作することで、コードの解析、変換、検証などを行います。
- AST: ソースコードの構造を木構造で表現したものです。各ノードは、変数宣言、関数呼び出し、制御構造などの言語要素に対応します。
resolve.go
:go/ast
パッケージ内で、識別子の解決(どの識別子がどの宣言に対応するかを決定するプロセス)や、パッケージのインポート処理など、ASTのセマンティックな解析を行う部分に関連するロジックが含まれていると考えられます。
技術的詳細
このコミットの技術的な核心は、src/pkg/go/ast/resolve.go
内の NewPackage
関数(またはその周辺のロジック)における変更です。
Goのコンパイラやツールがソースコードを処理する際、インポートされたパッケージは内部的に「スコープ」に登録されます。スコープは、特定のコードブロック内でアクセス可能な識別子の集合を管理するものです。通常のインポート(例: import "fmt"
)では、fmt
パッケージがファイルスコープに登録され、fmt.Println
のようにアクセスできるようになります。
しかし、ブランクインポート (_ "package"
) の場合、そのパッケージは識別子としてファイルスコープに登録されるべきではありません。なぜなら、ブランクインポートされたパッケージは名前で参照されることがなく、その目的はあくまで初期化処理の実行だからです。
以前の実装では、ブランクインポートされたパッケージも通常のパッケージと同様にファイルスコープに登録しようとしていた可能性があります。これにより、複数のブランクインポートが存在する場合、同じ「ブランク」識別子 _
が複数回登録されようとし、これがバグ(重複宣言エラーなど)を引き起こしていたと考えられます。
今回の修正では、resolve.go
内のパッケージをファイルスコープに宣言するロジックに if name != "_"
という条件が追加されました。この条件は、「インポートされたパッケージの名前が _
でない場合にのみ、そのパッケージをファイルスコープに宣言する」ことを意味します。これにより、ブランクインポートされたパッケージはファイルスコープに登録されなくなり、複数のブランクインポートが共存しても問題が発生しなくなりました。
テストケース src/pkg/exp/types/testdata/test0.src
の変更は、この修正が正しく機能することを確認するためのものです。このテストファイルに複数のブランクインポートを追加することで、修正前のバグが再現されなくなり、期待通りの動作が保証されます。
コアとなるコードの変更箇所
src/pkg/exp/types/check_test.go
--- a/src/pkg/exp/types/check_test.go
+++ b/src/pkg/exp/types/check_test.go
@@ -203,7 +203,7 @@ func check(t *testing.T, testname string, testfiles []string) {
func TestCheck(t *testing.T) {
// For easy debugging w/o changing the testing code,
// if there is a local test file, only test that file.
- const testfile = "test.go"
+ const testfile = "testdata/test.go"
if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() {
fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\\n", testfile)
check(t, testfile, []string{testfile})
この変更は、テストファイルのパスを test.go
から testdata/test.go
に修正したものです。これは、テストデータの配置場所の変更に対応するためのもので、直接的なバグ修正とは異なりますが、新しいテストケースの追加と関連しています。
src/pkg/exp/types/testdata/test0.src
--- a/src/pkg/exp/types/testdata/test0.src
+++ b/src/pkg/exp/types/testdata/test0.src
@@ -6,7 +6,12 @@
package test0
-import "unsafe"
+import (
+ "unsafe"
+ // we can have multiple blank imports (was bug)
+ _ "math"
+ _ "net/rpc"
+)
const pi = 3.1415
この変更は、単一の import "unsafe"
を、unsafe
に加えて _ "math"
と _ "net/rpc"
という2つのブランクインポートを含むグループインポートに置き換えたものです。コメント // we can have multiple blank imports (was bug)
が、このテストケースの目的を明確に示しています。
src/pkg/go/ast/resolve.go
--- a/src/pkg/go/ast/resolve.go
+++ b/src/pkg/go/ast/resolve.go
@@ -136,7 +136,7 @@ func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer,
for _, obj := range pkg.Data.(*Scope).Objects {
p.declare(fileScope, pkgScope, obj)
}
- } else {
+ } else if name != "_" {
// declare imported package object in file scope
// (do not re-use pkg in the file scope but create
// a new object instead; the Decl field is different
この変更が、このコミットの主要なロジック修正です。else
ブロックが else if name != "_"
に変更されました。
コアとなるコードの解説
src/pkg/go/ast/resolve.go
の変更は、GoのAST処理におけるパッケージ解決の挙動を修正しています。
元のコードでは、インポートされたパッケージがファイルスコープに宣言されるべきかどうかを判断する else
ブロックがありました。このブロックは、インポートされたパッケージが通常のパッケージである場合に、そのパッケージのオブジェクトをファイルスコープに登録する役割を担っていました。
しかし、ブランクインポート (_ "package"
) の場合、パッケージ名が _
となります。元の else
ブロックは、この _
という名前のパッケージもファイルスコープに登録しようとしていたため、複数のブランクインポートが存在すると、同じ _
という名前で複数のオブジェクトを登録しようとして衝突が発生し、バグとなっていたと考えられます。
修正後の else if name != "_"
という条件は、この問題を解決します。
name != "_"
: この条件は、「インポートされたパッケージの名前が_
ではない場合」にのみ、そのパッケージをファイルスコープに宣言するロジックを実行することを意味します。- これにより、ブランクインポートされたパッケージ(名前が
_
のパッケージ)は、このファイルスコープへの宣言ロジックから除外されます。結果として、ブランクインポートはファイルスコープに識別子として登録されなくなり、複数のブランクインポートが存在しても衝突が発生しなくなります。
この変更により、Go言語のコンパイラやツールが、複数のブランクインポートを含むソースコードを正しく解析・処理できるようになり、Go言語の仕様に準拠した挙動が保証されます。
関連リンク
- Go CL (Change List): https://golang.org/cl/6303099
参考にした情報源リンク
- Go言語の公式ドキュメント(
import
文、init
関数、go/ast
パッケージに関する一般的な知識) - Go言語のソースコード(
go/ast
パッケージの内部実装に関する一般的な理解)