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