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

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

このコミットは、Go言語のgo/printerパッケージとgofmtツールにおける、複数行にわたる関数シグネチャのパラメータリストの整形に関するバグ修正です。具体的には、パラメータの型が複数行にまたがる場合に、不要な末尾のカンマが挿入されてしまう問題を解決しています。

コミット

commit 8323cef77cb4bc9c905aca7cd8b66655c8e3b3a2
Author: Robert Griesemer <gri@golang.org>
Date:   Tue Mar 12 13:07:15 2013 -0700

    go/printer, gofmt: avoid extra final comma in multi-line signatures
    
    The parameter list layout function was incorrectly computing the
    end of the previous line in cases where a parameter type spanned
    multiple lines. As a result, an extra (valid, but not needed)
    comma was introduced before the paremeter list's closing parenthesis.
    
    Fixes #4533.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/7674044

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

https://github.com/golang/go/commit/8323cef77cb4bc9c905aca7cd8b66655c8e3b3a2

元コミット内容

go/printer, gofmt: avoid extra final comma in multi-line signatures

パラメータリストのレイアウト関数が、パラメータの型が複数行にわたる場合に、前の行の末尾を誤って計算していました。その結果、パラメータリストの閉じ括弧の前に、余分な(有効ではあるが不要な)カンマが挿入されていました。

Issue #4533 を修正します。

変更の背景

この変更は、Go言語の公式フォーマッタであるgofmtが、特定の複数行にわたる関数シグネチャにおいて、コードの整形時に不要なカンマを挿入してしまうというバグ(Issue #4533)に対応するものです。

gofmtは、Go言語のコードを標準的なスタイルに自動的に整形するツールであり、Go開発者にとって非常に重要な役割を担っています。コードの可読性と一貫性を保つために、gofmtは厳密な整形ルールに従います。しかし、このバグにより、本来不要なカンマが挿入されることで、コードの見た目が意図しないものとなり、開発者の体験を損ねていました。

特に、インターフェース型や構造体型がパラメータとして複数行にわたって定義されるような複雑なシグネチャでこの問題が発生していました。gofmtは通常、最後のパラメータの後にカンマを付けないのが慣例ですが、このバグはその慣例を破るものでした。

前提知識の解説

  • go/printerパッケージ: Go言語のソースコードを整形(pretty-print)するためのパッケージです。gofmtツールはこのパッケージを利用してコードの整形を行っています。AST(Abstract Syntax Tree)を走査し、Goの構文ルールと整形規則に基づいてコードを再構築します。
  • gofmt: Go言語のソースコードを自動的に整形するコマンドラインツールです。Goの標準的なコーディングスタイルを強制し、コードの可読性と一貫性を高めることを目的としています。gofmtは、GoのビルドシステムやIDEと密接に統合されており、Go開発のワークフローに不可欠なツールです。
  • AST (Abstract Syntax Tree): ソースコードの抽象的な構文構造を木構造で表現したものです。Goコンパイラやgo/printerのようなツールは、ソースコードをASTにパースし、そのASTを操作することで、コードの解析、変換、整形などを行います。
  • ast.FieldList: GoのASTにおいて、関数や構造体のパラメータリスト、または構造体のフィールドリストを表す構造体です。各フィールド(パラメータ)はast.Fieldとして表現されます。
  • ast.Node.Pos()ast.Node.End(): GoのASTノードがソースコード内で占める位置(開始位置と終了位置)を返します。これらの位置情報は、コードの整形やエラー報告において非常に重要です。go/printerはこれらの位置情報を用いて、改行やカンマの挿入位置を決定します。
  • カンマの扱い: Go言語の関数シグネチャや複合リテラルでは、要素が複数行にわたる場合、最後の要素の後にカンマを置くことが許容されています(trailing comma)。これは、要素の追加や削除の際にGitの差分を最小限に抑えるための慣習ですが、gofmtは通常、単一行のリストや、最後の要素が閉じ括弧と同じ行にある場合には、末尾のカンマを挿入しません。

技術的詳細

このバグは、go/printerパッケージ内のパラメータリストのレイアウトを計算するロジックに起因していました。具体的には、printer.parameters関数内で、パラメータの型が複数行にわたる場合に、そのパラメータの「前の行の末尾」を正しく計算できていなかったことが問題でした。

go/printerは、整形時に各要素(パラメータ名、型など)の開始行と終了行を追跡し、それに基づいて改行やカンマを挿入するかどうかを決定します。このコミット以前のコードでは、parLineEnd(パラメータの型の終了行)をp.lineFor(par.Type.Pos())で初期化していました。これは、型の開始位置の行を取得するものであり、型が複数行にわたる場合に、その型の実際の終了行を反映していませんでした。

その結果、parLineEndが実際よりも前の行を指してしまうことがあり、prevLine(前のパラメータの終了行)との比較において、最後のパラメータの閉じ括弧が同じ行にあるにもかかわらず、gofmtが「改行が必要」と誤判断し、不要なカンマを挿入してしまっていました。

修正は、parLineEndの計算をp.lineFor(par.Type.End())に変更することで、パラメータの型の実際の終了行を正確に取得するようにしました。これにより、gofmtは最後のパラメータと閉じ括弧の位置関係を正しく判断できるようになり、不要なカンマの挿入が回避されるようになりました。

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

変更はsrc/pkg/go/printer/nodes.goファイルに集中しています。

--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -271,12 +271,12 @@ func (p *printer) parameters(fields *ast.FieldList) {
 			// if there are multiple parameter names for this par
 			// or the type is on a separate line)
 			var parLineBeg int
-			var parLineEnd = p.lineFor(par.Type.Pos())
+			// var parLineEnd = p.lineFor(par.Type.Pos()) // 削除された行
 			if len(par.Names) > 0 {
 				parLineBeg = p.lineFor(par.Names[0].Pos())
 			} else {
-				parLineBeg = parLineEnd
+				parLineBeg = p.lineFor(par.Type.Pos()) // 変更された行
 			}
+			var parLineEnd = p.lineFor(par.Type.End()) // 追加された行
 			// separating "," if needed
 			needsLinebreak := 0 < prevLine && prevLine < parLineBeg
 			if i > 0 {

また、この修正を検証するためのテストケースがsrc/pkg/go/printer/testdata/declarations.inputsrc/pkg/go/printer/testdata/declarations.goldenに追加されています。これらのテストケースは、複数行にわたるinterface{}struct{}をパラメータとして持つ関数シグネチャが、正しく整形され、不要なカンマが挿入されないことを確認しています。

コアとなるコードの解説

変更されたnodes.goparameters関数は、ast.FieldList(関数のパラメータリストなど)を整形する役割を担っています。

  • 変更前:

    var parLineEnd = p.lineFor(par.Type.Pos())
    // ...
    } else {
        parLineBeg = parLineEnd
    }
    

    このコードでは、parLineEndがパラメータの型の開始位置の行で初期化されていました。そして、パラメータ名がない場合(例えば、型のみのパラメータの場合)、parLineBegもこのparLineEndと同じ値に設定されていました。型が複数行にわたる場合、parLineEndが型の実際の終了行を反映していないため、後続の改行判定ロジックが誤動作する可能性がありました。

  • 変更後:

    // var parLineEnd = p.lineFor(par.Type.Pos()) // この行は削除
    // ...
    } else {
        parLineBeg = p.lineFor(par.Type.Pos()) // parLineBegの計算を修正
    }
    var parLineEnd = p.lineFor(par.Type.End()) // parLineEndを型の終了位置の行で再計算
    
    1. parLineEnd = p.lineFor(par.Type.Pos()) の行がコメントアウト(実質削除)されました。これは、parLineEndの初期化が不正確であったためです。
    2. elseブロック内の parLineBeg = parLineEndparLineBeg = p.lineFor(par.Type.Pos()) に変更されました。これにより、パラメータ名がない場合のparLineBegは、常に型の開始位置の行を正確に指すようになります。
    3. 最も重要な変更は、var parLineEnd = p.lineFor(par.Type.End()) が追加されたことです。この行は、パラメータの型の終了位置の行を正確に取得し、parLineEndに設定します。これにより、go/printerは、パラメータの型が複数行にわたる場合でも、その型の実際の範囲を正確に把握できるようになりました。

この修正により、needsLinebreakの計算(0 < prevLine && prevLine < parLineBeg)が、最後のパラメータと閉じ括弧の位置関係を正しく評価できるようになり、不要なカンマの挿入が防止されます。

関連リンク

参考にした情報源リンク

  • Go Issue #4533
  • Gerrit Change 7674044
  • Go言語の公式ドキュメント(go/printerパッケージ、gofmtツールに関する情報)
  • Go言語のASTに関する一般的な知識