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

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

このコミットは、Go言語のコードフォーマッタであるgofmtと、その基盤となるgo/printerパッケージにおける複数行の要素を検出するロジックのバグを修正するものです。具体的には、ASTノードが複数行にまたがるかどうかの判定基準を修正し、以前の変更(CL 5706055)によって導入された不正確なフォーマットの問題を解決します。これにより、gofmtがコードをより正確に整形できるようになります。

コミット

  • コミットハッシュ: b5021f3fe0163ddad2681cd76402f15fa17cff56
  • Author: Robert Griesemer gri@golang.org
  • Date: Tue Mar 13 16:15:58 2012 -0700

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

https://github.com/golang/go/commit/b5021f3fe0163ddad2681cd76402f15fa17cff56

元コミット内容

go/printer, gofmt: fix multi-line logic

A node spans multiple lines if the line difference
between start and end point is > 0 (rather than > 1).
Fixes some odd cases introduced by CL 5706055;
pointed out by dsymonds.

Added corresponding test case. The other change
in the .golden file reverts to the status before
the CL mentioned above and is correct.

gofmt -w src misc changes godoc.go back to where
it was before the CL mentioned above.

Fixes #3304.

R=dsymonds, rsc
CC=golang-dev
https://golang.org/cl/5820044

変更の背景

この変更は、Go言語の公式フォーマッタであるgofmtが、特定のコード構造、特に複数行にわたる宣言や式を整形する際に、意図しない改行やインデントを生成する問題に対処するために行われました。

問題の根本原因は、go/printerパッケージ内のisMultiLine関数にありました。この関数は、抽象構文木(AST)のノードが複数行にまたがっているかどうかを判定するために使用されます。以前のロジックでは、ノードの開始行と終了行の差が> 1の場合にのみ複数行と判定していました。しかし、これにより、開始行と終了行が異なるが、その差がちょうど1であるようなケース(例えば、1行目で始まり2行目で終わるノード)が「単一行」として誤って扱われ、gofmtによる整形結果が不適切になることがありました。

この問題は、CL 5706055(go/printer: fix multi-line formatting of declarations)という以前の変更によって一部の奇妙なケースが導入されたことで顕在化しました。CL 5706055は、宣言の複数行フォーマットを改善しようとしましたが、isMultiLineの誤ったロジックのために、新たな整形上の問題を引き起こしてしまいました。具体的には、Issue 3304として報告された問題がこれに該当します。このIssueでは、複数行のvar宣言がgofmtによって不適切に整形され、次の行のインデントに影響を与えることが指摘されていました。

このコミットは、isMultiLine関数の判定基準を> 0に修正することで、この問題を解決し、gofmtがより正確な複数行の整形を行えるようにすることを目的としています。また、godoc.goファイルが以前のCLによって不適切に整形されていたのを元に戻す効果もありました。

前提知識の解説

go/printerパッケージ

go/printerパッケージは、Go言語のソースコードを整形(pretty-print)するための機能を提供します。これは、Goの公式フォーマッタであるgofmtの基盤となるパッケージです。AST(抽象構文木)を受け取り、それをGoの標準的なスタイルガイドに従って整形されたソースコード文字列に変換します。インデント、改行、空白の挿入などを制御し、一貫性のあるコードスタイルを保証します。

gofmt

gofmtは、Go言語のソースコードを自動的に整形するツールです。Goの標準ライブラリに含まれるgo/parserでコードをASTにパースし、go/printerで整形して出力します。gofmtは、Goコミュニティ全体でコードの一貫性を保つために広く利用されており、Goのツールチェインの重要な一部です。

抽象構文木 (AST: Abstract Syntax Tree)

ASTは、ソースコードの構造を木構造で表現したものです。コンパイラやリンタ、フォーマッタなどのツールがコードを解析・操作する際に利用します。Go言語では、go/astパッケージがASTの定義を提供しています。各ノードは、変数宣言、関数呼び出し、式などのコードの要素を表し、その位置情報(開始位置と終了位置)も保持しています。

ast.Node, Pos(), End()

ast.Nodeは、GoのASTにおけるすべてのノードが実装するインターフェースです。このインターフェースは、ノードのソースコード上の開始位置と終了位置を返すPos()End()メソッドを定義しています。これらのメソッドは、token.Pos型を返します。token.Posは、ファイル内のバイトオフセットを表す整数値です。

lineFor()

go/printerパッケージ内部で使用されるヘルパー関数で、token.Pos(バイトオフセット)を受け取り、その位置がソースコードの何行目にあたるかを返します。これにより、コードの要素がどの行に存在するか、あるいは複数行にまたがっているかを判断することができます。

コードフォーマットにおける複数行の概念

コードフォーマットにおいて「複数行」であるかどうかの判定は非常に重要です。例えば、複数行にわたる関数呼び出しの引数リストや、構造体のフィールド宣言などは、単一行のそれらとは異なるインデントルールや改行ルールが適用されることが一般的です。この判定が誤っていると、gofmtのようなツールが意図しない整形結果を生成し、コードの可読性を損なう可能性があります。

技術的詳細

このコミットの核心は、src/pkg/go/printer/nodes.goファイル内のisMultiLine関数のロジック変更です。

変更前:

func (p *printer) isMultiLine(n ast.Node) bool {
	return p.lineFor(n.End())-p.lineFor(n.Pos()) > 1
}

変更後:

func (p *printer) isMultiLine(n ast.Node) bool {
	return p.lineFor(n.End())-p.lineFor(n.Pos()) > 0
}

この変更は非常に小さいですが、その影響は大きいです。

  • 変更前 (> 1) の問題点:

    • p.lineFor(n.End()) - p.lineFor(n.Pos())は、ノードが占める行数の「差」を計算します。
    • 例えば、ノードが1行目で始まり2行目で終わる場合、lineFor(End())が2、lineFor(Pos())が1となり、差は2 - 1 = 1となります。
    • この場合、1 > 1falseとなるため、ノードは「単一行」であると誤って判定されていました。しかし、実際にはノードは2行にまたがっています。
    • この誤った判定が、gofmtが複数行の宣言などを整形する際に、不適切なインデントや改行を生成する原因となっていました。特に、以前のCL 5706055が導入した変更と組み合わさることで、この問題が顕在化しました。
  • 変更後 (> 0) の修正:

    • p.lineFor(n.End()) - p.lineFor(n.Pos()) > 0という条件は、ノードの開始行と終了行が異なる場合にtrueを返します。
    • 上記の例(1行目で始まり2行目で終わるノード)では、差が1なので1 > 0trueとなり、ノードは正しく「複数行」であると判定されます。
    • これにより、go/printerは複数行にわたるASTノードを正しく認識し、gofmtはそれらのノードに対して適切な整形ルールを適用できるようになります。

この修正は、gofmtの出力の一貫性と正確性を向上させ、特に複数行のvar宣言やtype宣言などの整形に関するIssue 3304で報告された問題を解決します。

また、このコミットでは、src/cmd/godoc/godoc.goファイルに対する変更も含まれていますが、これはgofmt -wコマンドによって自動的に整形が元に戻された結果であり、本質的なコードロジックの変更ではありません。これは、このコミットが修正するgo/printerのバグが、godoc.goの整形にも影響を与えていたことを示しています。

テストケースとしてsrc/pkg/go/printer/testdata/declarations.inputに新しい複数行宣言の例が追加され、それに対応するdeclarations.goldenファイルが更新されています。declarations.goldenの変更は、この修正によってgofmtの出力がどのように改善されたかを示しています。特に、Issue 3304で報告された問題のテストケースが追加され、その整形が正しく行われるようになったことが確認できます。

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

diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go
index 6be3c09382..727d2a3714 100644
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -365,7 +365,7 @@ func (p *printer) setLineComment(text string) {
 }
 
 func (p *printer) isMultiLine(n ast.Node) bool {
-	return p.lineFor(n.End())-p.lineFor(n.Pos()) > 1
+	return p.lineFor(n.End())-p.lineFor(n.Pos()) > 0
 }
 
 func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) {

コアとなるコードの解説

上記のコードスニペットは、go/printerパッケージ内のprinter構造体のメソッドであるisMultiLineの変更を示しています。

  • func (p *printer) isMultiLine(n ast.Node) bool: この関数は、ast.Node型の引数nを受け取り、そのノードがソースコード上で複数行にまたがっているかどうかを真偽値で返します。pprinter構造体のインスタンスであり、整形処理のコンテキスト(例えば、行番号を解決するための情報)を保持しています。

  • p.lineFor(n.End()): ノードnの終了位置(n.End())に対応する行番号を取得します。

  • p.lineFor(n.Pos()): ノードnの開始位置(n.Pos())に対応する行番号を取得します。

  • p.lineFor(n.End()) - p.lineFor(n.Pos()): この計算は、ノードが占める行の「差」を求めます。例えば、ノードが1行目から始まり1行目で終わる場合、差は0です。1行目から始まり2行目で終わる場合、差は1です。

  • 変更前 (> 1): この条件は、「ノードの開始行と終了行の差が1より大きい場合」にのみtrueを返していました。これは、ノードが少なくとも3行以上にまたがる場合にのみ「複数行」と判定されることを意味していました。前述の通り、開始行と終了行が異なるが差が1であるケース(例: 1行目から2行目)を単一行と誤認していました。

  • 変更後 (> 0): この条件は、「ノードの開始行と終了行の差が0より大きい場合」にtrueを返します。これは、ノードの開始行と終了行が異なる(つまり、ノードが少なくとも2行以上にまたがる)場合に「複数行」と正しく判定されることを意味します。これにより、gofmtは複数行の要素に対して適切な整形ルールを適用できるようになり、Issue 3304のような問題が解決されます。

この修正は、Goのコードフォーマットにおける基本的なロジックの正確性を保証し、gofmtの出力品質を向上させる上で非常に重要です。

関連リンク

参考にした情報源リンク