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

[インデックス 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 パッケージ内の改行処理ロジックの改善です。

  1. maxNewlines 定数の導入: src/pkg/go/printer/printer.gomaxNewlines = 2 という定数が導入されました。これは、ソーステキスト間に許容される最大改行数を定義します。以前は nlines 関数内で const max = 2 としてハードコードされていましたが、これをグローバル定数として明確に定義することで、コードの意図がより明確になりました。コミットメッセージでは「最大空行数 (1)」とありますが、コード上では maxNewlines = 2 となっており、これは「空行が2つまで許容される(つまり、3行分のスペース)」という意味合いで使われている可能性があります。

  2. nlines 関数の削除と nlimit 関数の導入: 以前存在した nlines 関数(min <= result <= max の範囲で改行数を調整する)が削除され、代わりに nlimit 関数が導入されました。

    • nlimit(n int) int: この新しい関数は、入力された改行数 nmaxNewlines を超える場合に、maxNewlines に制限するシンプルなロジックを提供します。これにより、改行数の上限がより直接的に制御されるようになりました。
  3. writeByteN 関数の導入: writeNewlines 関数が削除され、writeByteN(ch byte, n int) 関数が導入されました。

    • writeByteN: 指定された文字 chn 回出力する汎用的な関数です。これにより、改行 (\n) やフォームフィード (\f) を複数回出力する処理がより簡潔になりました。
  4. writeCommentSuffix および intersperseComments の戻り値の変更: これらの関数は、以前は droppedFF bool (フォームフィードが破棄されたかどうか) のみを返していましたが、wroteNewline, droppedFF bool (改行が書き込まれたかどうか、およびフォームフィードが破棄されたかどうか) を返すように変更されました。これにより、呼び出し元が改行が実際に行われたかどうかを正確に把握できるようになり、その後の改行処理のロジックに影響を与えます。

  5. printer.print メソッド内の改行処理の修正: src/pkg/go/printer/printer.goprint メソッド内の改行処理が最も重要な変更点です。

    • 以前は p.flush の結果に基づいて nl (改行文字) を決定し、p.writeNewlines を呼び出していましたが、新しいロジックでは p.flush から wroteNewlinedroppedFF の両方を受け取ります。
    • n := nlimit(next.Line - p.pos.Line): 次のトークンまでの行差を計算し、nlimit で最大改行数に制限します。
    • if wroteNewline && n == maxNewlines { n = maxNewlines - 1 }: ここが重要な修正点です。もし flush 処理ですでに改行が書き込まれており、かつ計算された改行数が maxNewlines と同じ場合、nmaxNewlines - 1 に減らします。これにより、maxNewlines を超える余分な改行が書き込まれるのを防ぎます。
    • p.writeByteN(ch, n): 最終的に writeByteN を使用して、調整された数の改行(またはフォームフィード)を書き込みます。
  6. テストデータの修正: 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 は、特定の状況下で、ソースコードの要素間に許容される以上の空行を生成してしまうことがありました。これは、特にコメントの処理や、異なるコードブロック間の区切りにおいて顕著でした。

新しい実装では、以下の点が改善されました。

  1. maxNewlinesnlimit による上限設定: maxNewlines 定数と nlimit 関数を導入することで、出力される改行の数が常にこの上限を超えないように保証されます。これにより、gofmt が生成するコードの空行の数が予測可能かつ一貫性のあるものになります。

  2. print メソッド内の改行調整ロジック: print メソッド内の if wroteNewline && n == maxNewlines { n = maxNewlines - 1 } という条件文が、過剰な改行を防ぐための重要な役割を果たします。

    • flush 処理(コメントの挿入や空白の書き出し)によってすでに改行が書き込まれている場合 (wroteNewlinetrue)、そして、次のトークンまでの行差から計算された改行数 nmaxNewlines と同じである場合、これは maxNewlines を超える改行が書き込まれる可能性があることを意味します。
    • この場合、nmaxNewlines - 1 に減らすことで、合計の改行数が maxNewlines を超えないように調整されます。例えば、maxNewlines が2(空行が2つまで許容)で、flush で1行書き込まれ、さらに2行の改行が計算された場合、合計3行になってしまいます。これを防ぐために、計算された2行を1行に減らし、合計2行に収めます。
  3. writeByteN の導入: writeByteN は、改行やフォームフィードを複数回書き込む処理を抽象化し、コードの重複を減らし、可読性を向上させます。

これらの変更により、go/printer はより正確にGoのフォーマットルールに従い、gofmt が生成するコードの品質と一貫性が向上しました。

関連リンク

参考にした情報源リンク