[インデックス 16614] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)において、インポートパスが常にnil
ではないast.ImportSpec
を生成するように修正するものです。具体的には、インポート宣言の解析時に、インポートパスを表すast.BasicLit
が常に有効な値を持つように変更されました。
コミット
commit fc1e298ba1dfa404e2036d7461110511fdc28422
Author: Robert Griesemer <gri@golang.org>
Date: Fri Jun 21 15:09:04 2013 -0700
go/parser: always provide a non-nil path for imports
The go/ast ImportSpec always requires a non-nil path.
R=adonovan
CC=golang-dev
https://golang.org/cl/10402047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fc1e298ba1dfa404e2036d7461110511fdc28422
元コミット内容
go/parser: always provide a non-nil path for imports
The go/ast ImportSpec always requires a non-nil path.
変更の背景
このコミットの背景には、Go言語の抽象構文木(AST)を定義するgo/ast
パッケージにおけるImportSpec
構造体の要件があります。ast.ImportSpec
は、Goソースコード内のimport
宣言を表すために使用されます。この構造体には、インポートパスを保持するためのPath
フィールドがあります。コミットメッセージに明記されているように、「go/ast ImportSpec
は常にnil
ではないパスを要求する」という制約が存在しました。
しかし、go/parser
パッケージの既存の実装では、特定の条件下(例えば、インポートパスが文字列リテラルとして正しく解析できなかった場合など)で、ImportSpec
のPath
フィールドがnil
になる可能性がありました。これは、go/ast
パッケージの設計意図と矛盾し、nil
パスを期待しない後続のツールや分析器で問題を引き起こす可能性がありました。
このコミットは、このような不整合を解消し、go/parser
がgo/ast
の仕様に厳密に従い、常に有効な(nil
ではない)インポートパスをImportSpec
に設定するようにするために導入されました。これにより、パーサーの堅牢性が向上し、ASTを利用する他のツールとの互換性が保証されます。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とパッケージに関する知識が必要です。
-
Go言語のパーサー (
go/parser
):go/parser
パッケージは、Go言語のソースコードを解析し、抽象構文木(AST)を生成するための機能を提供します。ソースコードを読み込み、その構造をプログラムが扱えるデータ構造に変換する役割を担います。このパッケージは、コンパイラ、リンター、コードフォーマッター、IDEなどのGoツールチェインの基盤となります。 -
抽象構文木 (Abstract Syntax Tree - AST): ASTは、ソースコードの構造を木構造で表現したものです。各ノードはソースコードの構成要素(変数宣言、関数呼び出し、制御構造など)を表し、その子ノードはさらに詳細な構造を示します。ASTは、ソースコードの意味を理解し、分析、変換、最適化を行うための標準的な中間表現として広く利用されます。
-
go/ast
パッケージ:go/ast
パッケージは、Go言語のASTを構成する型と関数を定義しています。例えば、ast.File
はGoのソースファイル全体を表し、ast.FuncDecl
は関数宣言を、ast.ImportSpec
はimport
宣言を表します。これらの型は、Goのソースコードのあらゆる部分をプログラム的に操作するためのインターフェースを提供します。 -
ast.ImportSpec
構造体:ast.ImportSpec
は、Goのimport
宣言の個々のエントリを表すASTノードです。例えば、import "fmt"
やimport foo "bar/baz"
のような宣言に対応します。この構造体には、以下のような重要なフィールドが含まれます。Path *BasicLit
: インポートされるパッケージのパス(例:"fmt"
)。これは*ast.BasicLit
型であり、文字列リテラルを表します。Name *Ident
: パッケージのローカル名(例:foo
)。指定がない場合はnil
。Comment *CommentGroup
: インポート宣言に関連付けられたコメント。
-
nil
(Goにおけるゼロ値): Go言語において、nil
はポインタ、インターフェース、マップ、スライス、チャネル、関数などの参照型のゼロ値です。nil
は「値がない」状態を示します。このコミットの文脈では、ast.ImportSpec
のPath
フィールドがnil
であることは、インポートパスが設定されていない、または無効な状態であることを意味します。go/ast
の設計では、Path
フィールドは常に有効なast.BasicLit
へのポインタであるべきであり、nil
であってはならないという制約がありました。 -
token.STRING
:go/token
パッケージは、Go言語の字句解析(トークン化)中に識別されるトークンの種類を定義します。token.STRING
は、ソースコード内の文字列リテラル(例:"hello"
)を表すトークンタイプです。 -
ast.BasicLit
構造体:ast.BasicLit
は、GoのASTにおいて、数値、文字列、文字、浮動小数点数などの基本的なリテラルを表す構造体です。この構造体には、リテラルの値(Value
フィールド)と、そのリテラルがソースコード内で出現する位置(ValuePos
フィールド)、そしてリテラルの種類(Kind
フィールド、例:token.STRING
)が含まれます。
技術的詳細
このコミットの技術的な核心は、go/parser
パッケージ内のparseImportSpec
関数が、ast.ImportSpec
のPath
フィールドにnil
ではない*ast.BasicLit
値を常に割り当てるように変更された点にあります。
変更前は、parseImportSpec
関数内でインポートパスを解析する際に、path
変数が*ast.BasicLit
型として宣言されていました。インポートパスが文字列リテラルとして存在しない場合(例えば、構文エラーなど)、このpath
変数は初期値であるnil
のままになる可能性がありました。
変更後のアプローチは以下の通りです。
path
変数の型変更:var path *ast.BasicLit
からvar path string
に変更されました。これにより、解析中のインポートパスの文字列値を一時的に保持する変数が、ポインタ型ではなく値型になりました。- 位置情報の保持:
pos := p.pos
という行が追加され、現在のトークンの開始位置(p.pos
)をpos
変数に保存するようになりました。これは、後でast.BasicLit
を構築する際に、そのリテラルの正確なソースコード上の位置情報を提供するために使用されます。 ast.BasicLit
の生成ロジックの変更:- 変更前は、
token.STRING
が検出された場合、p.lit
(現在のトークンのリテラル値)を直接使用してast.BasicLit
が構築され、path
変数に割り当てられていました。 - 変更後は、まず
path = p.lit
として文字列値をpath
変数に代入します。そして、isValidImport
チェックの後、ast.ImportSpec
を構築する際に、&ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path}
という形で、常に新しいast.BasicLit
インスタンスが生成され、ImportSpec
のPath
フィールドに割り当てられるようになりました。
- 変更前は、
- エラー報告の位置情報:
p.error(p.pos, ...)
がp.error(pos, ...)
に変更されました。これにより、インポートパスが無効であると判断された場合のエラーメッセージが、インポートパスの開始位置(pos
)を正確に指すようになります。
この変更により、たとえインポートパスが構文的に無効であったとしても、parseImportSpec
はast.ImportSpec
のPath
フィールドにnil
ではないast.BasicLit
(ただし、そのValue
は空文字列やエラーを示す値になる可能性はある)を割り当てるようになります。これにより、go/ast
のImportSpec
のPath
フィールドが常に非nil
であるという不変条件が満たされることになります。
コアとなるコードの変更箇所
変更はsrc/pkg/go/parser/parser.go
ファイル内のparseImportSpec
関数に集中しています。
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -2151,12 +2151,13 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as
ident = p.parseIdent()
}
- var path *ast.BasicLit
+ pos := p.pos
+ var path string
if p.tok == token.STRING {
- if !isValidImport(p.lit) {
- p.error(p.pos, "invalid import path: "+p.lit)
+ path = p.lit
+ if !isValidImport(path) {
+ p.error(pos, "invalid import path: "+path)
}
- path = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
p.next()
} else {
p.expect(token.STRING) // use expect() error handling
@@ -2167,7 +2168,7 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as
spec := &ast.ImportSpec{
Doc: doc,
Name: ident,
- Path: path,
+ Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path},
Comment: p.lineComment,
}
p.imports = append(p.imports, spec)
コアとなるコードの解説
上記の差分に基づいて、主要な変更点を解説します。
-
var path *ast.BasicLit
の削除とpos := p.pos
およびvar path string
の追加:- 変更前:
path
変数は*ast.BasicLit
型として宣言されており、インポートパスのASTノードを直接保持しようとしていました。これがnil
になる可能性がありました。 - 変更後:
pos := p.pos
: 現在のトークン(文字列リテラル)の開始位置をpos
変数に保存します。これは、後でast.BasicLit
を構築する際に、そのリテラルの正確なソースコード上の位置情報を提供するために必要です。var path string
: インポートパスの文字列値を一時的に保持するためのstring
型のpath
変数が導入されました。これにより、文字列値の処理とASTノードの生成が分離されます。
- 変更前:
-
インポートパスの文字列値の取得と
isValidImport
チェック:- 変更前:
if !isValidImport(p.lit) { p.error(p.pos, "invalid import path: "+p.lit) } path = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
p.lit
(現在のトークンのリテラル値)を直接isValidImport
に渡し、その結果に基づいてエラーを報告し、その後ast.BasicLit
を構築していました。 - 変更後:
まずpath = p.lit if !isValidImport(path) { p.error(pos, "invalid import path: "+path) }
p.lit
をstring
型のpath
変数に代入します。isValidImport
チェックもこのpath
変数に対して行われます。エラー報告時の位置情報がp.pos
からpos
に変更されています。これにより、エラーが報告される位置が、インポートパスの文字列リテラルの開始位置に正確に紐付けられます。
- 変更前:
-
ast.ImportSpec
のPath
フィールドへの割り当て:- 変更前:
Path: path,
Path
フィールドには、以前に宣言された*ast.BasicLit
型のpath
変数が直接割り当てられていました。このpath
変数がnil
である可能性がありました。 - 変更後:
Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path},
Path
フィールドには、常に新しいast.BasicLit
インスタンスが生成されて割り当てられるようになりました。このインスタンスは、pos
(インポートパスの開始位置)、token.STRING
(リテラルの種類)、そしてpath
(インポートパスの文字列値)を使用して構築されます。これにより、Path
フィールドがnil
になることはなくなり、go/ast
のImportSpec
の要件が満たされます。
- 変更前:
これらの変更により、go/parser
は、インポートパスの解析結果がどうであれ、ast.ImportSpec
のPath
フィールドに常に有効な(nil
ではない)ast.BasicLit
ポインタを設定するようになり、パーサーの出力の堅牢性と一貫性が向上しました。
関連リンク
- Go Gerrit Change: https://golang.org/cl/10402047
参考にした情報源リンク
- Go言語の公式ドキュメント:
go/parser
パッケージ: https://pkg.go.dev/go/parsergo/ast
パッケージ: https://pkg.go.dev/go/astgo/token
パッケージ: https://pkg.go.dev/go/token
- Go言語のASTに関する一般的な情報源 (例: Go AST Explorerなど)