[インデックス 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のコンパイラやツールのドキュメント)