[インデックス 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.input
とsrc/pkg/go/printer/testdata/declarations.golden
に追加されています。これらのテストケースは、複数行にわたるinterface{}
やstruct{}
をパラメータとして持つ関数シグネチャが、正しく整形され、不要なカンマが挿入されないことを確認しています。
コアとなるコードの解説
変更されたnodes.go
のparameters
関数は、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を型の終了位置の行で再計算
parLineEnd = p.lineFor(par.Type.Pos())
の行がコメントアウト(実質削除)されました。これは、parLineEnd
の初期化が不正確であったためです。else
ブロック内のparLineBeg = parLineEnd
がparLineBeg = p.lineFor(par.Type.Pos())
に変更されました。これにより、パラメータ名がない場合のparLineBeg
は、常に型の開始位置の行を正確に指すようになります。- 最も重要な変更は、
var parLineEnd = p.lineFor(par.Type.End())
が追加されたことです。この行は、パラメータの型の終了位置の行を正確に取得し、parLineEnd
に設定します。これにより、go/printer
は、パラメータの型が複数行にわたる場合でも、その型の実際の範囲を正確に把握できるようになりました。
この修正により、needsLinebreak
の計算(0 < prevLine && prevLine < parLineBeg
)が、最後のパラメータと閉じ括弧の位置関係を正しく評価できるようになり、不要なカンマの挿入が防止されます。
関連リンク
- Go Issue #4533: go/printer, gofmt: avoid extra final comma in multi-line signatures
- Gerrit Change-Id: 7674044
参考にした情報源リンク
- Go Issue #4533
- Gerrit Change 7674044
- Go言語の公式ドキュメント(
go/printer
パッケージ、gofmt
ツールに関する情報) - Go言語のASTに関する一般的な知識