[インデックス 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など)