[インデックス 13344] ファイルの概要
このコミットは、Go言語の抽象構文木(AST)を扱う go/ast
パッケージに、コメントとASTノードの関連付けを管理する CommentMap
の実装を追加するものです。これにより、ASTが操作された際にコメントリストが正しく更新されるようになります。
コミット
commit f5f23e075e179ddbb518683e0762f27cd59018dd
Author: Robert Griesemer <gri@golang.org>
Date: Wed Jun 13 13:32:29 2012 -0700
go/ast: comment map implementation
A comment map associates comments with AST nodes
and permits correct updating of the AST's comment
list when the AST is manipulated.
R=rsc
CC=golang-dev
https://golang.org/cl/6281044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f5f23e075e179ddbb518683e0762f27cd59018dd
元コミット内容
go/ast: comment map implementation
A comment map associates comments with AST nodes
and permits correct updating of the AST's comment
list when the AST is manipulated.
変更の背景
Go言語のコンパイラやツールは、ソースコードを解析して抽象構文木(AST)を生成します。ASTはプログラムの構造を木構造で表現したもので、コンパイラの最適化、コード生成、静的解析ツール、リファクタリングツールなど、様々な用途で利用されます。
しかし、ASTは通常、コメント情報を含みません。コメントはプログラムの実行には影響しないため、解析の段階で破棄されることが一般的です。しかし、コードフォーマッタ(gofmt
など)やリファクタリングツールのように、ソースコードを再生成したり、ASTを操作したりするツールにとっては、コメントの位置や内容を保持し、ASTの変更に合わせて適切にコメントも移動・更新されることが非常に重要になります。
このコミット以前は、ASTを操作する際にコメントの整合性を保つのが困難でした。ASTノードが追加、削除、移動されると、それに関連するコメントが孤立したり、誤った位置に配置されたりする可能性がありました。この問題に対処するため、ASTノードとコメントグループを関連付けるメカニズム、すなわち「コメントマップ」が必要とされました。これにより、ASTの操作とコメントの管理を同期させ、より堅牢なツール開発を可能にすることが変更の背景にあります。
前提知識の解説
抽象構文木 (Abstract Syntax Tree, AST)
ASTは、プログラミング言語のソースコードを抽象的な構文構造で表現した木構造のデータ構造です。コンパイラのフロントエンド(字句解析、構文解析)によって生成されます。ASTは、ソースコードの具体的な構文(括弧やセミコロンなど)を抽象化し、プログラムの意味的な構造に焦点を当てます。
例えば、a = b + c;
というコードは、以下のようなASTで表現されることがあります。
Assignment
/ \
Identifier Binary Expression
(a) / | \
Identifier Operator Identifier
(b) (+) (c)
Go言語では、標準ライブラリの go/ast
パッケージがASTの定義と操作を提供しています。
go/ast
パッケージ
go/ast
パッケージは、Goプログラムのソースコードの抽象構文木を表現するための型と関数を提供します。主要な型には以下のようなものがあります。
Node
: ASTのすべてのノードが実装するインターフェース。File
: 単一のGoソースファイルを表すASTのルートノード。パッケージ宣言、インポート、トップレベルの宣言(関数、変数、型など)を含みます。Decl
: 宣言(変数宣言、関数宣言、型宣言など)を表すインターフェース。Stmt
: 文(代入文、if文、for文など)を表すインターフェース。Expr
: 式(リテラル、識別子、演算など)を表すインターフェース。CommentGroup
: 複数のコメント(// comment
や/* comment */
)をまとめたグループ。
go/ast
パッケージは、ASTの走査(ast.Inspect
)や、ASTのノードの作成・変更を行うための機能を提供します。
go/token
パッケージ
go/token
パッケージは、Goソースコード内の位置情報(ファイル、行番号、列番号、オフセット)を扱うための型と関数を提供します。token.Pos
はソースコード内の特定のオフセットを表し、token.FileSet
は複数のファイルにわたる位置情報を管理します。ASTノードは通常、そのノードがソースコードのどこからどこまでを占めるかを示す Pos()
と End()
メソッドを持ち、これらは token.Pos
型を返します。
コメントとASTの関連付けの課題
コメントは、プログラムの実行には影響しないため、ASTの直接的な一部として扱われないことが多いです。しかし、人間がコードを理解する上で非常に重要であり、コードの意図や設計を説明します。ASTを操作するツールがコメントを無視すると、生成されるコードが元のコメントを失ったり、コメントが不適切な位置に配置されたりする問題が発生します。
例えば、ある変数宣言に付随するコメントがあったとして、その変数宣言が別のスコープに移動された場合、コメントも一緒に移動されるべきです。コメントマップは、このようなASTの構造変更に伴うコメントの適切な管理を可能にするためのデータ構造です。
技術的詳細
このコミットで導入される CommentMap
は、go/ast
パッケージ内でASTノードとコメントグループを関連付けるための新しいデータ構造です。
CommentMap
の定義
CommentMap
は map[Node][]*CommentGroup
として定義されています。これは、ASTの任意の Node
をキーとして、そのノードに関連付けられた CommentGroup
のスライスを値として保持します。
NewCommentMap
関数
NewCommentMap
関数は、*token.FileSet
と *ast.File
を引数に取り、新しい CommentMap
を生成します。この関数が CommentMap
のコアロジックを実装しており、コメントグループを適切なASTノードに関連付けるための複雑なルールを含んでいます。
コメントグループ g
がノード n
に関連付けられる条件は以下の通りです。
g
がn
の終了行と同じ行で始まる場合。g
がn
の直後の行で始まり、かつg
の後に次のノードの前に少なくとも1つの空行がある場合。g
がn
の前に始まり、かつ上記のルールで前のノードに関連付けられていない場合。
NewCommentMap
は、コメントグループを「可能な限り大きな」ノードに関連付けようとします。例えば、代入文の末尾にある行コメントは、代入文全体に関連付けられます。
この関連付けロジックは、以下のステップで実行されます。
- コメントのソート:
f.Comments
からコメントグループのコピーを作成し、sortComments
関数(byPos
型を使用してコメントグループの開始位置でソート)でソースコード順にソートします。 - ノードリストの作成:
nodeList
関数(ast.Inspect
を使用してASTを走査し、コメントグループ以外のすべてのノードをソースコード順に収集)で、ASTf
内のすべてのノードをソースコード順にリスト化します。末尾にセンチネル(nil
ノード)を追加し、すべてのコメントが処理されるようにします。 - コメントとノードの関連付け:
commentListReader
を使用してコメントグループを順次読み込みます。nodeStack
を使用して、現在処理中のコメントを包含する可能性のある「重要な」ノード(File
,Field
,Decl
,Spec
,Stmt
など)のスタックを管理します。nodeStack
は、ノードの開始位置に基づいてスタックを操作し、現在のコメントの範囲外に出たノードをポップします。- 各コメントグループについて、以下の優先順位で関連付けを行うノードを決定します。
- ノードグループへの関連付け: まず、現在のコメントを包含する可能性のある最も最近の「ノードグループ」(
pg
)に対して関連付けを試みます。これは、コメントがノードグループの終了行と同じ行にあるか、または直後の行にあり、かつその後に空行がある場合に適用されます。 - 直前のノードへの関連付け: ノードグループに関連付けられなかった場合、直前のノード(
p
)に対して同様のルールで関連付けを試みます。 - 現在のノードへの関連付け: 上記のいずれにも当てはまらない場合、現在のノード(
q
)に関連付けられます。
- ノードグループへの関連付け: まず、現在のコメントを包含する可能性のある最も最近の「ノードグループ」(
この複雑なロジックにより、コメントがその意味的な文脈に最も近いASTノードに正確に関連付けられるようになります。
その他のメソッド
CommentMap.addComment(n Node, c *CommentGroup)
: 指定されたノードn
にコメントグループc
を追加します。CommentMap.Filter(nodes ...Node) CommentMap
: 既存のCommentMap
から、引数で与えられたノードツリー内に存在するノードに関連付けられたコメントのみを含む新しいCommentMap
を返します。これは、ASTの一部を抽出したり、特定のサブツリーに関連するコメントのみを扱いたい場合に有用です。CommentMap.Comments() []*CommentGroup
:CommentMap
内のすべてのコメントグループをソースコード順にソートして返します。
テスト (commentmap_test.go
)
commentmap_test.go
は、NewCommentMap
の動作を検証するための包括的なテストケースを含んでいます。src
という定数に実際のGoソースコードの文字列が定義されており、res
というマップには、各ASTノード(行番号と型で識別)に期待されるコメントテキストが定義されています。
TestCommentMap
関数は、以下のことを行います。
parser.ParseFile
を使用してsrc
からASTをパースし、コメントも同時にパースします。NewCommentMap
を呼び出してCommentMap
を生成します。- 生成された
CommentMap
をイテレートし、各ノードに関連付けられたコメントがres
マップの期待値と一致するかを検証します。 CommentMap
内のコメントグループの総数が、元のファイルからパースされたコメントグループの総数と一致するかを検証し、コメントが失われていないことを確認します。
このテストは、CommentMap
の関連付けロジックが複雑なGoコードに対しても正しく機能することを確認するための重要な役割を果たします。
コアとなるコードの変更箇所
このコミットでは、以下の2つの新しいファイルが追加されています。
src/pkg/go/ast/commentmap.go
:CommentMap
の主要な実装が含まれています。src/pkg/go/ast/commentmap_test.go
:CommentMap
の機能と正確性を検証するためのテストコードが含まれています。
これらのファイルは、既存の go/ast
パッケージに新しい機能を追加するものであり、既存のコードの変更は含まれていません。
コアとなるコードの解説
src/pkg/go/ast/commentmap.go
このファイルには、CommentMap
の型定義と、その操作に関連する関数が実装されています。
byPos
型:[]*CommentGroup
のエイリアスで、sort.Interface
を実装し、コメントグループをその開始位置 (Pos()
) でソートするために使用されます。sortComments(list []*CommentGroup)
: コメントグループのスライスをソースコード順にソートします。CommentMap
型:map[Node][]*CommentGroup
として定義され、ASTノードとコメントグループの関連付けを保持します。addComment(n Node, c *CommentGroup)
:CommentMap
にノードとコメントグループの関連付けを追加するヘルパーメソッド。byInterval
型:[]Node
のエイリアスで、sort.Interface
を実装し、ノードをその開始位置 (Pos()
) でソートし、開始位置が同じ場合は終了位置 (End()
) で逆順にソートするために使用されます。これは、nodeList
でノードをソートする際に考慮される可能性がありますが、現在の実装ではInspect
の走査順序に依存しています。nodeList(n Node) []Node
: 与えられたASTn
から、コメントグループ以外のすべてのノードをソースコード順に収集してリストとして返します。commentListReader
型: コメントグループのリストを効率的にイテレートするためのヘルパー構造体。nodeStack
型: ネストされたノードを追跡するためのスタック実装。コメントの関連付けロジックで、コメントを包含する「重要な」ノードを特定するために使用されます。NewCommentMap(fset *token.FileSet, f *File) CommentMap
: このファイルで最も重要な関数であり、コメントグループをASTノードにマッピングする主要なロジックを含んでいます。前述の「技術的詳細」セクションで説明した複雑な関連付けルールがここで実装されています。Filter(nodes ...Node) CommentMap
: 指定されたノードツリー内に存在するノードに関連付けられたコメントのみを含む新しいCommentMap
を返します。Comments() []*CommentGroup
:CommentMap
内のすべてのコメントグループをソースコード順にソートして返します。
src/pkg/go/ast/commentmap_test.go
このファイルは、commentmap.go
で実装された CommentMap
の機能が正しく動作するかを検証するための単体テストを提供します。
src
定数: テストに使用されるGoソースコードの文字列。様々な種類のコメントとAST構造を含んでいます。res
変数:src
のASTをパースし、NewCommentMap
で生成されたCommentMap
が期待するノードとコメントの関連付けを定義するマップ。キーは"行番号: *ast.ノード型"
の形式で、値は関連付けられるコメントのテキストです。ctext(list []*CommentGroup) string
:CommentGroup
のスライスから、それらのコメントテキストを連結した文字列を生成するヘルパー関数。TestCommentMap(t *testing.T)
: メインのテスト関数。go/parser
を使用してsrc
をパースし、ASTとコメント情報を取得します。NewCommentMap
を呼び出してCommentMap
を生成します。- 生成された
CommentMap
をイテレートし、各ノードと関連付けられたコメントがres
マップの期待値と一致するかを検証します。 cmap.Comments()
の結果と元のコメントの数を比較し、すべてのコメントがマップに含まれていることを確認します。genMap
フラグがtrue
の場合、テスト結果を元にres
マップを再生成するためのデバッグコードも含まれています。
このテストファイルは、CommentMap
のロジックが意図した通りに機能し、Goソースコードの複雑なコメント配置にも対応できることを保証します。
関連リンク
参考にした情報源リンク
- https://github.com/golang/go/commit/f5f23e075e179ddbb518683e0762f27cd59018dd
- Go言語の公式ドキュメント (
go/ast
,go/token
,go/parser
パッケージ) - 抽象構文木に関する一般的な情報源 (Wikipediaなど)