[インデックス 10826] ファイルの概要
このコミットは、Go言語のコードフォーマッタである gofmt
およびその基盤となる go/printer
パッケージにおける、余分な改行の出力に関するバグを修正するものです。具体的には、ソースコードの断片間に最大許容数(1行)を超える空行が生成される稀なケースを修正しています。主な変更は src/pkg/go/printer/printer.go
に集中しており、改行の数を制御するロジックが改善されました。
コミット
commit 9f65e99ad4bdc85e979f12fb5e4d7f4e4b8a7693
Author: Robert Griesemer <gri@golang.org>
Date: Thu Dec 15 13:51:47 2011 -0800
go/printer, gofmt: don't write too many newlines
In some rare cases, gofmt would accept more than the maximum
number of empty lines (1) between source code snippets.
The actual change is in printer.go, lines 773-775; the rest
is some minor restructuring.
Applied gofmt -w src misc .
Fixes #2387.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5496047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9f65e99ad4bdc85e979f12fb5e4d7f4e4b8a7693
元コミット内容
go/printer, gofmt: don't write too many newlines
In some rare cases, gofmt would accept more than the maximum
number of empty lines (1) between source code snippets.
The actual change is in printer.go, lines 773-775; the rest
is some minor restructuring.
Applied gofmt -w src misc .
Fixes #2387.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5496047
変更の背景
この変更は、gofmt
がGoのソースコードをフォーマットする際に、意図しない過剰な空行を挿入してしまうバグを修正するために行われました。具体的には、ソースコードの異なるセクション間(例えば、パッケージ宣言とimport文の間、または関数定義と次の関数定義の間など)で、許容される空行の最大数(通常は1行)を超えてしまう問題がありました。これは、gofmt
の出力の一貫性を損ない、開発者にとって不便なものでした。この問題は Issue #2387 として報告されており、このコミットによって修正されました。
前提知識の解説
gofmt
: Go言語の公式なコードフォーマッタです。Goのソースコードを標準的なスタイルに自動的に整形し、コードの一貫性を保ち、可読性を向上させます。gofmt
は、Goのツールチェインの一部として提供されています。go/printer
パッケージ:gofmt
の中核をなすパッケージで、Goの抽象構文木(AST)を読み取り、それを整形されたGoのソースコードとして出力する役割を担っています。このパッケージは、コメントの扱い、改行の挿入、インデントの調整など、コードのレイアウトに関する詳細なルールを実装しています。- 抽象構文木 (AST): プログラムのソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやリンタ、フォーマッタなどのツールは、ソースコードを直接操作するのではなく、一度ASTに変換してから処理を行います。
go/printer
はこのASTを受け取り、整形されたコードを生成します。 - 改行 (
\n
) とフォームフィード (\f
):- 改行 (
\n
): 一般的な行の区切り文字です。 - フォームフィード (
\f
): プリンタ制御文字の一種で、新しいページの先頭に移動するよう指示します。go/printer
の文脈では、異なるファイルからのコメントなど、より強い区切りが必要な場合に内部的に使用されることがあります。これは、通常の改行よりも大きな視覚的な区切りを意味します。
- 改行 (
token.Position
: Goのソースコード内の位置(ファイル名、行番号、列番号、オフセット)を表す構造体です。go/printer
はこれを用いて、コードの各要素の元の位置を追跡し、整形後の出力に反映させます。
技術的詳細
このコミットの主要な変更点は、go/printer
パッケージ内の改行処理ロジックの改善です。
-
maxNewlines
定数の導入:src/pkg/go/printer/printer.go
にmaxNewlines = 2
という定数が導入されました。これは、ソーステキスト間に許容される最大改行数を定義します。以前はnlines
関数内でconst max = 2
としてハードコードされていましたが、これをグローバル定数として明確に定義することで、コードの意図がより明確になりました。コミットメッセージでは「最大空行数 (1)」とありますが、コード上ではmaxNewlines = 2
となっており、これは「空行が2つまで許容される(つまり、3行分のスペース)」という意味合いで使われている可能性があります。 -
nlines
関数の削除とnlimit
関数の導入: 以前存在したnlines
関数(min <= result <= max
の範囲で改行数を調整する)が削除され、代わりにnlimit
関数が導入されました。nlimit(n int) int
: この新しい関数は、入力された改行数n
がmaxNewlines
を超える場合に、maxNewlines
に制限するシンプルなロジックを提供します。これにより、改行数の上限がより直接的に制御されるようになりました。
-
writeByteN
関数の導入:writeNewlines
関数が削除され、writeByteN(ch byte, n int)
関数が導入されました。writeByteN
: 指定された文字ch
をn
回出力する汎用的な関数です。これにより、改行 (\n
) やフォームフィード (\f
) を複数回出力する処理がより簡潔になりました。
-
writeCommentSuffix
およびintersperseComments
の戻り値の変更: これらの関数は、以前はdroppedFF bool
(フォームフィードが破棄されたかどうか) のみを返していましたが、wroteNewline, droppedFF bool
(改行が書き込まれたかどうか、およびフォームフィードが破棄されたかどうか) を返すように変更されました。これにより、呼び出し元が改行が実際に行われたかどうかを正確に把握できるようになり、その後の改行処理のロジックに影響を与えます。 -
printer.print
メソッド内の改行処理の修正:src/pkg/go/printer/printer.go
のprint
メソッド内の改行処理が最も重要な変更点です。- 以前は
p.flush
の結果に基づいてnl
(改行文字) を決定し、p.writeNewlines
を呼び出していましたが、新しいロジックではp.flush
からwroteNewline
とdroppedFF
の両方を受け取ります。 n := nlimit(next.Line - p.pos.Line)
: 次のトークンまでの行差を計算し、nlimit
で最大改行数に制限します。if wroteNewline && n == maxNewlines { n = maxNewlines - 1 }
: ここが重要な修正点です。もしflush
処理ですでに改行が書き込まれており、かつ計算された改行数がmaxNewlines
と同じ場合、n
をmaxNewlines - 1
に減らします。これにより、maxNewlines
を超える余分な改行が書き込まれるのを防ぎます。p.writeByteN(ch, n)
: 最終的にwriteByteN
を使用して、調整された数の改行(またはフォームフィード)を書き込みます。
- 以前は
-
テストデータの修正:
src/pkg/go/printer/testdata/statements.golden
ファイルから余分な空行が削除されました。これは、このコミットが修正しようとしているバグの具体的な例であり、修正が正しく適用されたことを示すものです。
コアとなるコードの変更箇所
src/pkg/go/printer/printer.go
:maxNewlines
定数の追加。nlines
関数の削除。writeByteN
関数の追加。writeNewlines
関数の削除。writeCommentSuffix
の戻り値の変更と内部ロジックの調整。intersperseComments
の戻り値の変更。nlimit
関数の追加。print
メソッド内の改行処理ロジックの修正(特にif wroteNewline && n == maxNewlines { n = maxNewlines - 1 }
の部分)。flush
メソッドの戻り値の変更。
src/pkg/go/printer/nodes.go
:linebreak
関数内でnlines
の代わりにnlimit
を使用するように変更。
src/cmd/cgo/godefs.go
およびsrc/pkg/bytes/buffer_test.go
:- ファイルの先頭にある余分な空行の削除。これは
gofmt
の適用結果であり、このコミットの修正がこれらのファイルにも影響を与えたことを示します。
- ファイルの先頭にある余分な空行の削除。これは
src/pkg/go/printer/testdata/statements.golden
:- テストデータ内の余分な空行の削除。
コアとなるコードの解説
このコミットの核心は、go/printer
が出力する改行の数を厳密に制御することにあります。
以前の go/printer
は、特定の状況下で、ソースコードの要素間に許容される以上の空行を生成してしまうことがありました。これは、特にコメントの処理や、異なるコードブロック間の区切りにおいて顕著でした。
新しい実装では、以下の点が改善されました。
-
maxNewlines
とnlimit
による上限設定:maxNewlines
定数とnlimit
関数を導入することで、出力される改行の数が常にこの上限を超えないように保証されます。これにより、gofmt
が生成するコードの空行の数が予測可能かつ一貫性のあるものになります。 -
print
メソッド内の改行調整ロジック:print
メソッド内のif wroteNewline && n == maxNewlines { n = maxNewlines - 1 }
という条件文が、過剰な改行を防ぐための重要な役割を果たします。flush
処理(コメントの挿入や空白の書き出し)によってすでに改行が書き込まれている場合 (wroteNewline
がtrue
)、そして、次のトークンまでの行差から計算された改行数n
がmaxNewlines
と同じである場合、これはmaxNewlines
を超える改行が書き込まれる可能性があることを意味します。- この場合、
n
をmaxNewlines - 1
に減らすことで、合計の改行数がmaxNewlines
を超えないように調整されます。例えば、maxNewlines
が2(空行が2つまで許容)で、flush
で1行書き込まれ、さらに2行の改行が計算された場合、合計3行になってしまいます。これを防ぐために、計算された2行を1行に減らし、合計2行に収めます。
-
writeByteN
の導入:writeByteN
は、改行やフォームフィードを複数回書き込む処理を抽象化し、コードの重複を減らし、可読性を向上させます。
これらの変更により、go/printer
はより正確にGoのフォーマットルールに従い、gofmt
が生成するコードの品質と一貫性が向上しました。
関連リンク
- Go CL (Code Review) 5496047: https://golang.org/cl/5496047