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

[インデックス 13392] ファイルの概要

このコミットは、gofmt ツールにおけるコードの書き換え(リライト)処理中にコメントが正しく扱われるようにするための修正を含んでいます。具体的には、以下のファイルが変更されました。

  • src/cmd/gofmt/gofmt_test.go: gofmt のテストスイートに新しいテストケースが追加されました。このテストケースは、リライト時にコメントが適切に処理されることを検証します。
  • src/cmd/gofmt/rewrite.go: gofmt のリライトロジックのコア部分が修正されました。ast.CommentMap を使用して、リライトによって削除されるコードに関連付けられたコメントも適切に削除されるように変更されています。
  • src/cmd/gofmt/testdata/rewrite5.golden: 新しいテストケースの期待される出力(ゴールデンファイル)です。
  • src/cmd/gofmt/testdata/rewrite5.input: 新しいテストケースの入力ファイルです。

コミット

  • コミットハッシュ: 96a609c2d7fcb8e1dc370e0cda9e4eb3bfb77412
  • 作者: Robert Griesemer gri@golang.org
  • 日付: Mon Jun 25 13:58:28 2012 -0700
  • コミットメッセージ:
    gofmt: handle comments correctly in rewrites
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6294076
    

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/96a609c2d7fcb8e1dc370e0cda9e4eb3bfb77412

元コミット内容

gofmt: handle comments correctly in rewrites

R=rsc
CC=golang-dev
https://golang.org/cl/6294076

変更の背景

gofmt はGo言語のコードを整形するためのツールであり、コードの抽象構文木(AST)を操作して整形や書き換えを行います。このコミット以前の gofmt のリライト機能には、コードの書き換え時にコメントの扱いに関する問題がありました。具体的には、リライトによって特定のコード要素が削除された場合、その要素に付随していたコメントが適切に削除されずに残ってしまう可能性がありました。

この問題は、コードの整形結果が意図しないコメントを含んでしまい、コードの可読性や正確性を損なう原因となります。特に、gofmt -r コマンドでコードのパターンマッチングと置換を行う際に顕著でした。このコミットは、このような状況でコメントが正しく処理され、不要なコメントが確実に削除されるようにするために導入されました。

新しいテストケース rewrite5.inputrewrite5.golden は、この問題の具体的なシナリオを示しています。x + x という式を 2 * x に書き換える際に、元の式に付随するコメントがどのように扱われるべきかを定義し、リライトによって削除されるべきコメントが正しく消えることを検証しています。

前提知識の解説

gofmt

gofmt は、Go言語のソースコードを標準的なスタイルに自動的に整形するツールです。コードのインデント、スペース、改行などを統一し、Goコミュニティ全体で一貫したコードスタイルを維持するのに役立ちます。また、単なる整形だけでなく、ASTを操作してコードの構造的な変更(例: gofmt -r によるパターンベースのリライト)も行うことができます。

Go言語の抽象構文木 (AST)

Goコンパイラやツール(gofmt など)は、Goのソースコードを直接テキストとして扱うのではなく、まず抽象構文木(AST: Abstract Syntax Tree)と呼ばれるデータ構造に変換します。ASTは、プログラムの構造を木構造で表現したもので、各ノードがプログラムの要素(変数、関数、式、文など)に対応します。

Goの go/ast パッケージは、このASTを扱うための型と関数を提供します。例えば、ast.File はGoのソースファイル全体を表すASTのルートノードです。ast.Expr は式を表すインターフェースで、ast.BinaryExpr(二項演算式)や ast.Ident(識別子)などがその具体的な型です。

コメントとAST

GoのASTでは、コメントはコードとは別に管理されますが、特定のASTノードに関連付けられることがあります。ast.File 構造体には Comments フィールドがあり、ファイル内のすべてのコメントグループ(ast.CommentGroup)のリストを保持します。ast.CommentGroup は、複数の連続するコメント(ast.Comment)をまとめたものです。

gofmt がコードをリライトする際、ASTノードが追加、削除、または変更されると、それに伴って関連するコメントも適切に処理される必要があります。特に、ノードが削除された場合、そのノードにのみ関連付けられていたコメントも削除されるべきです。

ast.CommentMap

ast.CommentMap は、go/ast パッケージで提供されるユーティリティで、ASTノードとコメントグループの関連付けを管理するために使用されます。これは、ASTを変換する際にコメントの配置を維持したり、不要なコメントをフィルタリングしたりするのに非常に役立ちます。

ast.NewCommentMap(fileset, node, comments) は、指定されたASTノードとコメントのリストから CommentMap を構築します。このマップは、各ASTノードがどのコメントグループに関連付けられているかを効率的に検索できるようにします。

cmap.Filter(node) メソッドは、新しいASTノード(または変換後のAST)を受け取り、元の CommentMap に基づいて、新しいASTに属するコメントのみを抽出します。これにより、リライトによって削除されたASTノードに関連付けられていたコメントは自動的に除外され、残ったASTノードに関連するコメントのみが保持されます。

技術的詳細

このコミットの技術的な核心は、gofmt のリライト処理において ast.CommentMap を導入し、コメントのライフサイクルをASTの変換と同期させる点にあります。

rewriteFile 関数は、与えられたパターンと置換式に基づいて、Goのソースファイル全体(ASTとして表現される *ast.File)を書き換えます。

  1. ast.NewCommentMap の作成: リライト処理の開始時に、元のAST (p) とそのコメント (p.Comments) を基に ast.CommentMap (cmap) が作成されます。この cmap は、元のコードにおけるASTノードとコメントの関連付けのスナップショットを保持します。これにより、どのコメントがどのコード要素に属していたかを追跡できます。

    cmap := ast.NewCommentMap(fileSet, p, p.Comments)
    

    ここで fileSet は、ソースファイルの位置情報(行番号、列番号など)を管理する token.FileSet オブジェクトです。

  2. ASTの変換: apply(f, reflect.ValueOf(p)).Interface().(*ast.File) の呼び出しによって、実際のリライト処理が行われ、元のAST (p) が新しいAST (r) に変換されます。この apply 関数は、リフレクションを使用してASTノードを再帰的に走査し、パターンに一致するノードを置換します。この段階では、コメント自体は直接操作されません。

  3. コメントのフィルタリング: 変換された新しいAST (r) が得られた後、cmap.Filter(r).Comments() が呼び出されます。

    • cmap.Filter(r) は、元の CommentMap (cmap) を使用して、新しいAST (r) に含まれるノードに関連付けられているコメントグループのみを抽出します。つまり、リライトによって削除されたASTノードに付随していたコメントは、このフィルタリングプロセスで自動的に除外されます。
    • .Comments() メソッドは、フィルタリングされたコメントグループのリストを返します。
  4. 新しいASTへのコメントの割り当て: 最後に、フィルタリングされたコメントのリストが、新しいAST (r) の Comments フィールドに割り当てられます。

    r.Comments = cmap.Filter(r).Comments() // recreate comments list
    

    これにより、リライト後のASTは、その構造に合致する適切なコメントのみを持つことになります。

このメカニズムにより、gofmt はコードのリライト時に、削除されたコード要素に付随するコメントを自動的にクリーンアップし、整形後のコードの正確性と整合性を保つことができます。

コアとなるコードの変更箇所

src/cmd/gofmt/rewrite.go ファイルの rewriteFile 関数における変更点です。

--- a/src/cmd/gofmt/rewrite.go
+++ b/src/cmd/gofmt/rewrite.go
@@ -55,6 +55,7 @@ func dump(msg string, val reflect.Value) {
 
 // rewriteFile applies the rewrite rule 'pattern -> replace' to an entire file.
 func rewriteFile(pattern, replace ast.Expr, p *ast.File) *ast.File {
+	cmap := ast.NewCommentMap(fileSet, p, p.Comments)
 	m := make(map[string]reflect.Value)
 	pat := reflect.ValueOf(pattern)
 	repl := reflect.ValueOf(replace)
@@ -73,7 +74,9 @@ func rewriteFile(pattern, replace ast.Expr, p *ast.File) *ast.File {
 		}
 		return val
 	}\n-	return apply(f, reflect.ValueOf(p)).Interface().(*ast.File)
+	r := apply(f, reflect.ValueOf(p)).Interface().(*ast.File)
+	r.Comments = cmap.Filter(r).Comments() // recreate comments list
+	return r
 }
 
 // setValue is a wrapper for x.SetValue(y); it protects

コアとなるコードの解説

変更された rewriteFile 関数は、以下の2つの主要な行を追加・変更しています。

  1. cmap := ast.NewCommentMap(fileSet, p, p.Comments): この行は、リライト処理の開始時に ast.CommentMap を初期化しています。fileSet はファイルのメタデータ(位置情報など)を提供し、p はリライト対象の元のASTファイル、p.Comments は元のファイルに存在するすべてのコメントグループのリストです。この CommentMap は、元のASTノードとコメントの関連付けを記憶するためのものです。

  2. r := apply(f, reflect.ValueOf(p)).Interface().(*ast.File): この行は、元の return 文を分割し、apply 関数によって返された新しいASTを r という変数に格納しています。apply 関数は、定義されたリライトルール (pattern -> replace) を元のAST (p) に適用し、変換されたASTを返します。

  3. r.Comments = cmap.Filter(r).Comments() // recreate comments list: この行が、コメントの正しい処理を保証する最も重要な部分です。

    • cmap.Filter(r): 先ほど作成した CommentMap (cmap) を使用し、リライトによって生成された新しいAST (r) に含まれるノードに関連付けられているコメントのみを抽出します。これにより、リライトによって削除されたコード要素に付随していたコメントは、この段階で自動的にフィルタリングされ、結果のコメントリストから除外されます。
    • .Comments(): フィルタリングされたコメントグループのリストを取得します。
    • r.Comments = ...: フィルタリングされたコメントリストを、新しいAST (r) の Comments フィールドに割り当てます。

この変更により、gofmt はリライト後のコードに不要なコメントが残ることを防ぎ、より正確でクリーンな整形結果を提供できるようになりました。

関連リンク

参考にした情報源リンク