[インデックス 11780] ファイルの概要
このコミットは、Go言語のgo/printerパッケージにSourcePosモードを実装し、整形されたGoコードが元のソースコードの位置情報を保持できるようにするものです。具体的には、整形後のコードを再解析した際に、元のAST(抽象構文木)やソースコードの位置情報が正確に反映されるよう、必要に応じて//lineコメントを挿入する機能が追加されました。これにより、コードの整形後もデバッグやツール連携において重要なソース位置の正確性が保たれます。
コミット
- コミットハッシュ:
f8cf82f6f2de1ea91b525ca70f92b51a3df4d9df - Author: Robert Griesemer gri@golang.org
- Date: Fri Feb 10 13:27:32 2012 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f8cf82f6f2de1ea91b525ca70f92b51a3df4d9df
元コミット内容
commit f8cf82f6f2de1ea91b525ca70f92b51a3df4d9df
Author: Robert Griesemer <gri@golang.org>
Date: Fri Feb 10 13:27:32 2012 -0800
go/printer: implement SourcePos mode
If a printer is configured with the SourcePos mode
set, it will emit //-line comments as necessary to
ensure that the result - if reparsed - reflects the
original source position information.
This change required a bit of reworking of the
output section in printer.go. Specifically:
- Introduced new Config mode 'SourcePos'.
- Introduced new position 'out' which tracks the
position of the generated output if it were read
in again. If there is a discrepancy between out
and the current AST/source position, a //line
comment is emitted to correct for it.
- Lazy emission of indentation so that //line
comments can be placed correctly. As a result,
the trimmer will have to do less work.
- Merged writeItem into writeString.
- Merged writeByteN into writeByte.
- Use a []byte instead of a byte.Buffer both in the
printer and in the trimmer (eliminates dependency).
Also: introduced explicit printer.Mode type (in
sync w/ parser.Mode, scanner.Mode, etc.)
Runs all tests. Applied gofmt to src, misc w/o changes.
Fixes #1047.
Fixes #2697.
R=rsc, rsc
CC=golang-dev
https://golang.org/cl/5643066
変更の背景
Go言語のツールチェインにおいて、ソースコードの整形(pretty-printing)は重要な機能です。しかし、コードを整形すると、元のソースコードの行番号や列番号といった位置情報が失われる可能性があります。これは、デバッガ、コード分析ツール、エラー報告など、ソースコードの位置情報に依存するツールにとって問題となります。
このコミットの背景には、整形後のコードでも元のソース位置情報を保持したいというニーズがありました。特に、go/printerパッケージが生成する出力が、再解析された際に元のAST(抽象構文木)のノードが持つ位置情報と一致するようにすることが求められました。この問題を解決するために、Goのコンパイラやツールが認識する特殊なコメントである//lineコメントを利用して、ソース位置情報を埋め込むアプローチが採用されました。
これにより、整形されたコードを読み込むツールが、あたかも元のソースコードを読んでいるかのように、正確な位置情報を参照できるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語のパッケージと概念に関する知識が必要です。
go/printerパッケージ: GoのAST(抽象構文木)を整形し、Goのソースコードとして出力するためのパッケージです。gofmtツールなどで利用されています。go/astパッケージ: Goのソースコードを解析して生成される抽象構文木(AST)のデータ構造を定義するパッケージです。ASTは、プログラムの構造を木構造で表現したもので、コンパイラや各種ツールがコードを理解・操作するために使用します。go/tokenパッケージ: ソースコード内のトークン(キーワード、識別子、演算子など)や、それらのトークンがソースコードのどこに位置するかを示すPos(位置)情報を扱うパッケージです。token.Positionはファイル名、行番号、列番号、オフセットなどの情報を含みます。//lineコメント: Go言語のコンパイラやツールが特別に解釈するコメントの一種です。//line filename:line_numberの形式で記述され、その行以降のコードのソース位置情報を、指定されたファイル名と行番号に「リセット」する役割があります。これは、コード生成ツールが生成したコードのデバッグ時に、元のソースコードの行番号を参照できるようにするためによく使われます。tabwriterパッケージ: テキストをタブで揃えて整形するためのパッケージです。go/printerは、コードのインデントやアライメントを調整するために内部的にtabwriterを使用しています。tabwriter.Escapeは、tabwriterが特殊文字として解釈しないように、文字列をエスケープするために使用されるバイトです。bytes.Bufferと[]byte: Go言語でバイト列を扱うためのデータ構造です。bytes.Bufferは、可変長のバイトバッファを提供し、Writeメソッドなどでデータを追加できます。動的にサイズが変化するデータに適していますが、内部的にはアロケーションが発生する可能性があります。[]byteは、固定長のバイトスライスです。直接操作することで、bytes.Bufferよりも低レベルで効率的なメモリ管理が可能です。このコミットでは、パフォーマンス向上のため、bytes.Bufferから[]byteへの移行が行われています。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
SourcePosモードの導入:printer.Config構造体に新しいModeフラグSourcePosが追加されました。このフラグが設定されている場合、go/printerは出力時に//lineコメントを挿入するようになります。printer.Modeという明示的な型が導入され、parser.Modeやscanner.Modeなどと同期が取られました。これにより、設定フラグの型安全性が向上しています。
-
out位置の追跡:printer構造体にout token.Positionという新しいフィールドが追加されました。これは、生成された出力が再読み込みされた場合の現在の位置を追跡します。- 既存の
pos token.PositionはAST(ソース)空間での現在の位置を追跡し、outは出力空間での現在の位置を追跡します。 posとoutの間に不一致(例えば、整形によって行番号が変わった場合)がある場合、SourcePosモードが有効であれば、//lineコメントが挿入され、outの位置がposに合うように調整されます。
-
インデントの遅延評価 (Lazy Emission of Indentation):
- 以前は、改行時に即座にインデントが書き込まれていましたが、この変更により、インデントの書き込みが遅延されるようになりました。
- これにより、
//lineコメントが正確な位置に配置できるようになります。//lineコメントは行の先頭に配置される必要があるため、インデントの前に挿入される必要があります。 - この変更は、
trimmer(出力から余分な空白を削除するコンポーネント)の作業量を減らす効果もあります。
-
writeItemのwriteStringへの統合:- 以前は、トークンやリテラルを書き込むための
writeItemメソッドと、一般的な文字列を書き込むためのwriteStringメソッドが別々に存在していました。 - このコミットでは、
writeItemの機能がwriteStringに統合されました。writeStringは、書き込む文字列のソース位置情報を受け取るようになり、より汎用的な書き込み関数となりました。
- 以前は、トークンやリテラルを書き込むための
-
writeByteNのwriteByteへの統合:- 特定のバイトを複数回書き込む
writeByteNメソッドが、単一バイトを書き込むwriteByteメソッドに統合されました。writeByteは、書き込むバイトと回数を受け取るようになりました。
- 特定のバイトを複数回書き込む
-
bytes.Bufferから[]byteへの移行:printerとtrimmerの両方で、出力バッファとしてbytes.Bufferの代わりに[]byteスライスが使用されるようになりました。- これにより、
bytes.Bufferが持つ依存関係が解消され、パフォーマンスが向上します。append関数を使ってバイトスライスに直接データを追加することで、より効率的なメモリ操作が可能になります。
-
CommentedNode型の導入 (doc/go1.html, doc/go1.tmpl):go/printerパッケージにCommentedNodeという新しい型が導入されました。これは任意のast.Nodeに関連付けられたコメントを提供するために使用できます。これまではast.Fileのみがコメント情報を持っていました。
これらの変更により、go/printerはより柔軟かつ正確にソースコードを整形し、元の位置情報を保持できるようになりました。
コアとなるコードの変更箇所
このコミットの主要な変更は、src/pkg/go/printer/printer.goとsrc/pkg/go/printer/printer_test.goに集中しています。
src/pkg/go/printer/printer.go:
printer構造体の変更:outputフィールドがbytes.Bufferから[]byteに変更されました。posとlastに加えて、新しいout token.Positionフィールドが追加されました。
initメソッドの変更:p.posとp.outの初期化が追加されました。
writeByte関数の変更:writeByte(ch byte)がwriteByte(ch byte, n int)に変更され、複数バイトの書き込みに対応しました。- 行の先頭で
atLineBeginを呼び出すロジックが追加されました。 //lineコメントの挿入ロジックがatLineBeginに移動しました。- インデントの書き込みロジックが
atLineBeginに移動しました。
writeString関数の変更:writeString(s string, isLit bool)がwriteString(pos token.Position, s string, isLit bool)に変更され、ソース位置情報を受け取るようになりました。//lineコメントの挿入ロジックがatLineBeginに移動したため、writeStringはよりシンプルになりました。p.output = append(p.output, ...)形式でバイトスライスに直接書き込むようになりました。
writeItem関数の削除:writeItem関数はwriteStringに統合されたため削除されました。
trimmer構造体の変更:spaceフィールドがbytes.Bufferから[]byteに変更されました。resetSpaceメソッドが追加されました。
Mode型の導入:RawFormat,TabIndent,UseSpacesの定数がuintからMode型に変更され、新しいSourcePosモードが追加されました。Config構造体のModeフィールドもuintからMode型に変更されました。
src/pkg/go/printer/printer_test.go:
TestSourcePosテストの追加:SourcePosモードが正しく//lineコメントを生成し、元のソース位置情報を保持していることを検証するための新しいテストが追加されました。- このテストでは、Goのソースコードを整形し、その整形されたコードを再解析して、識別子の位置情報が元のコードと一致するかどうかを確認しています。
その他のファイル:
doc/go1.htmlとdoc/go1.tmplが更新され、go/printerパッケージのSourcePosモードとCommentedNode型に関するドキュメントが追加されました。src/cmd/cgo/godefs.goとsrc/cmd/cgo/out.goでは、printer.Fprintの呼び出しが、新しく導入されたconf変数(SourcePosモードが設定されたprinter.Config)を使用するように変更されました。src/cmd/gofmt/gofmt.goでは、printerMode変数の型がuintからprinter.Modeに変更されました。
コアとなるコードの解説
このコミットの核心は、go/printerがコードを整形する際に、元のソースコードの位置情報を失わないようにするメカニズムにあります。
-
printer構造体のposとout:posは、現在処理しているASTノードが元のソースコードのどこに位置するかを示します。outは、go/printerが現在までに生成した出力の、仮想的な位置(行番号、列番号)を示します。- 通常、
posとoutは同期していますが、コードの整形(例えば、改行の追加や削除、インデントの変更)によって、outがposからずれることがあります。
-
SourcePosモードと//lineコメントの挿入:SourcePosモードが有効な場合、writeByteやwriteStringが呼び出され、新しい行の書き込みが始まる際に、atLineBegin関数が実行されます。atLineBeginでは、p.out.Line != pos.Line || p.out.Filename != pos.Filenameという条件でposとoutの不一致がチェックされます。- 不一致がある場合、
fmt.Sprintf("//line %s:%d\\n", pos.Filename, pos.Line)を使って//lineコメントが生成され、p.output(バイトスライス)に追加されます。これにより、その行以降の出力のソース位置が、元のposに「リセット」されます。 tabwriter.Escapeバイトが//lineコメントの前後に挿入されるのは、tabwriterがこのコメントを通常のテキストとして扱い、整形の影響を受けないようにするためです。
-
インデントの遅延評価:
- 以前は、改行文字(
\n)が書き込まれるとすぐにインデントが追加されていました。 - 新しい実装では、インデントの書き込みは
atLineBegin関数に移動しました。これにより、//lineコメントがインデントの前に挿入されることが保証され、コメントが常に新しい行の先頭に配置されるようになります。
- 以前は、改行文字(
-
bytes.Bufferから[]byteへの移行:p.outputが[]byteになったことで、p.output = append(p.output, ...)という形式で直接バイトスライスにデータを追加できるようになりました。これは、bytes.BufferのWriteByteやWriteStringメソッドを呼び出すよりも、アロケーションが少なく、パフォーマンスが向上します。特に、大量のコードを整形する際にその効果が顕著になります。
これらの変更は、go/printerが生成するコードの品質を向上させ、Goのツールエコシステム全体におけるソース位置情報の正確性を保証するために不可欠なものです。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/f8cf82f6f2de1ea91b525ca70f92b51a3df4d9df
- Go CL (Code Review): https://golang.org/cl/5643066
- 関連するGo Issue:
- Fixes #1047: (Web検索では直接関連するGoの公式リポジトリのIssueは見つかりませんでした。内部的なIssueトラッカーの参照である可能性があります。)
- Fixes #2697: (Web検索では直接関連するGoの公式リポジトリのIssueは見つかりませんでした。内部的なIssueトラッカーの参照である可能性があります。)
参考にした情報源リンク
- 上記のGitHubコミットページ
- Go言語の公式ドキュメント(
go/printer,go/ast,go/tokenパッケージに関する情報) - Go言語の
//lineコメントに関する一般的な情報源 (例: Goのコンパイラやツールのドキュメント)