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

[インデックス 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パッケージの既存の実装では、特定の条件下(例えば、インポートパスが文字列リテラルとして正しく解析できなかった場合など)で、ImportSpecPathフィールドがnilになる可能性がありました。これは、go/astパッケージの設計意図と矛盾し、nilパスを期待しない後続のツールや分析器で問題を引き起こす可能性がありました。

このコミットは、このような不整合を解消し、go/parsergo/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.ImportSpecimport宣言を表します。これらの型は、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.ImportSpecPathフィールドが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.ImportSpecPathフィールドにnilではない*ast.BasicLit値を常に割り当てるように変更された点にあります。

変更前は、parseImportSpec関数内でインポートパスを解析する際に、path変数が*ast.BasicLit型として宣言されていました。インポートパスが文字列リテラルとして存在しない場合(例えば、構文エラーなど)、このpath変数は初期値であるnilのままになる可能性がありました。

変更後のアプローチは以下の通りです。

  1. path変数の型変更: var path *ast.BasicLitからvar path stringに変更されました。これにより、解析中のインポートパスの文字列値を一時的に保持する変数が、ポインタ型ではなく値型になりました。
  2. 位置情報の保持: pos := p.posという行が追加され、現在のトークンの開始位置(p.pos)をpos変数に保存するようになりました。これは、後でast.BasicLitを構築する際に、そのリテラルの正確なソースコード上の位置情報を提供するために使用されます。
  3. 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インスタンスが生成され、ImportSpecPathフィールドに割り当てられるようになりました。
  4. エラー報告の位置情報: p.error(p.pos, ...)p.error(pos, ...)に変更されました。これにより、インポートパスが無効であると判断された場合のエラーメッセージが、インポートパスの開始位置(pos)を正確に指すようになります。

この変更により、たとえインポートパスが構文的に無効であったとしても、parseImportSpecast.ImportSpecPathフィールドにnilではないast.BasicLit(ただし、そのValueは空文字列やエラーを示す値になる可能性はある)を割り当てるようになります。これにより、go/astImportSpecPathフィールドが常に非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)

コアとなるコードの解説

上記の差分に基づいて、主要な変更点を解説します。

  1. 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ノードの生成が分離されます。
  2. インポートパスの文字列値の取得と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.litstring型のpath変数に代入します。isValidImportチェックもこのpath変数に対して行われます。エラー報告時の位置情報がp.posからposに変更されています。これにより、エラーが報告される位置が、インポートパスの文字列リテラルの開始位置に正確に紐付けられます。
  3. ast.ImportSpecPathフィールドへの割り当て:

    • 変更前:
      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/astImportSpecの要件が満たされます。

これらの変更により、go/parserは、インポートパスの解析結果がどうであれ、ast.ImportSpecPathフィールドに常に有効な(nilではない)ast.BasicLitポインタを設定するようになり、パーサーの出力の堅牢性と一貫性が向上しました。

関連リンク

参考にした情報源リンク