[インデックス 18634] ファイルの概要
このコミットは、Go言語の公式フォーマッタである go/printer
パッケージにおける、ラベル付きステートメント内のコメントの配置(アライメント)に関するバグを修正するものです。具体的には、ラベル付きステートメントに続くコメントが正しくインデントされず、コードの可読性を損なう問題に対処しています。
コミット
commit c738591e7e9b3ed9804c1a1348ccf7c7c596b8c3
Author: Robert Griesemer <gri@golang.org>
Date: Mon Feb 24 19:18:16 2014 -0800
go/printer: fix alignment of comments in labeled statements
Does not change src, misc formatting.
Fixes #5623.
LGTM=r
R=golang-codereviews, r
CC=golang-codereviews
https://golang.org/cl/68400043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c738591e7e9b3ed9804c1a1348ccf7c7c596b8c3
元コミット内容
このコミットは、go/printer
パッケージにおいて、ラベル付きステートメント(L:
のようなラベルが付いた goto
や for
などのステートメント)に付随するコメントのインデントが正しく行われない問題を修正します。この修正は、ソースコードのセマンティクスやその他のフォーマットには影響を与えません。
この変更は、Go issue #5623 で報告された問題を解決します。
変更の背景
Go言語には、コードのフォーマットを自動的に行う gofmt
というツールがあります。このツールは、Goのソースコードを標準的なスタイルに整形し、コードベース全体の一貫性を保つ上で非常に重要な役割を果たしています。gofmt
の内部では、go/printer
パッケージが実際にAST(抽象構文木)を走査し、整形されたコードを生成する役割を担っています。
Go issue #5623 は、go/printer
がラベル付きステートメントの後に続くコメントを適切にインデントできないというバグを報告しています。具体的には、ラベル付きステートメントの本体が複数行にわたる場合、その本体に付随する行コメントやブロックコメントが、ラベルのインデントレベルではなく、ラベルの開始位置に揃ってしまうという問題がありました。これにより、整形後のコードの可読性が損なわれ、開発者が手動で修正する必要が生じていました。
この問題は、go/printer
がステートメントの「複数行性」を判断する際に、ラベル付きステートメント全体を考慮してしまい、ラベル自体が複数行にわたるかのように誤解釈していたことに起因します。本来、コメントのアライメントは、ラベルではなく、そのラベルが指す実際のステートメントのインデントレベルに揃えるべきです。
前提知識の解説
- Go言語のAST (Abstract Syntax Tree): Goコンパイラやツール(
gofmt
など)は、Goのソースコードを解析して、その構造を表現する抽象構文木(AST)を生成します。ASTは、プログラムの構造を階層的に表現したもので、各ノードがコードの要素(変数宣言、関数呼び出し、ステートメントなど)に対応します。go/printer
はこのASTを受け取り、整形されたコードを出力します。 go/printer
パッケージ: Go言語の標準ライブラリの一部であり、GoのASTを整形されたGoのソースコードに変換する機能を提供します。gofmt
ツールはこのパッケージを利用しています。go/printer
は、Goのコードスタイルガイドラインに沿って、インデント、スペース、改行などを自動的に調整します。- ラベル付きステートメント (Labeled Statements): Go言語では、
for
ループやswitch
ステートメントなどの特定のステートメントにラベルを付けることができます。これは主にbreak
やcontinue
ステートメントで特定のループやswitch
から抜け出す際に使用されます。構文はLabelName: Statement
の形式です。 例:Loop: for i := 0; i < 10; i++ { // ... if condition { break Loop // Loopラベルのforループを抜ける } }
- コメントのアライメント: プログラミングにおいて、コードの可読性を高めるために、コメントを特定の列に揃える(アライメントする)ことは一般的な慣習です。
gofmt
のようなフォーマッタは、このアライメントを自動的に維持しようとします。
技術的詳細
この修正は、src/pkg/go/printer/nodes.go
ファイル内の stmtList
メソッドと、新しく追加された unlabeledStmt
ヘルパー関数に焦点を当てています。
stmtList
メソッドは、ステートメントのリスト(例: 関数本体のステートメント、if
や for
のブロック内のステートメント)を整形する役割を担っています。このメソッド内で、各ステートメントが複数行にわたるかどうかを判断するために p.isMultiLine(s)
が呼び出されます。
問題は、ラベル付きステートメント s
が *ast.LabeledStmt
型である場合、p.isMultiLine(s)
がラベル自体も考慮してしまい、ラベル付きステートメント全体が複数行であると誤って判断してしまう点にありました。これにより、ラベル付きステートメントに続くコメントのインデントがずれていました。
この修正では、p.isMultiLine(s)
の呼び出しを p.isMultiLine(unlabeledStmt(s))
に変更しています。
新しく追加された unlabeledStmt
関数は、以下のロジックで動作します。
// unlabeledStmt returns the statement of a labeled statement s;
// otherwise it return s.
func unlabeledStmt(s ast.Stmt) ast.Stmt {
if s, _ := s.(*ast.LabeledStmt); s != nil {
return unlabeledStmt(s.Stmt)
}
return s
}
この関数は、与えられた ast.Stmt
が *ast.LabeledStmt
型であるかどうかをチェックします。もしそうであれば、そのラベル付きステートメントの内部にある実際のステートメント(s.Stmt
)を再帰的に unlabeledStmt
に渡します。これにより、ネストされたラベル付きステートメントであっても、最終的に最も内側のラベルを持たないステートメントが返されます。もし与えられたステートメントがラベル付きステートメントでなければ、そのままそのステートメントを返します。
この変更により、stmtList
メソッドがステートメントの複数行性を判断する際に、ラベル付きステートメントの「ラベル」部分を除外して、純粋な「ステートメント本体」の複数行性のみを考慮するようになります。結果として、ラベル付きステートメントに続くコメントは、ラベルの開始位置ではなく、実際のステートメント本体のインデントレベルに正しく揃えられるようになります。
テストケース src/pkg/go/printer/testdata/comments2.input
と src/pkg/go/printer/testdata/comments2.golden
が追加され、この修正が意図通りに動作することを確認しています。特に issue5623()
関数が追加され、様々なラベル付きステートメントとコメントの組み合わせで、コメントが正しくアライメントされることを検証しています。
コアとなるコードの変更箇所
src/pkg/go/printer/nodes.go
の以下の部分が変更されました。
-
stmtList
メソッド内のコメントの修正:--- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -906,7 +906,7 @@ func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { for _, s := range list { // ignore empty statements (was issue 3466) if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { - // _indent == 0 only for lists of switch/select case clauses; + // nindent == 0 only for lists of switch/select case clauses; // in those cases each clause is a new section if len(p.output) > 0 { // only print line break if we are not at the beginning of the output @@ -914,7 +914,11 @@ func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { } p.stmt(s, nextIsRBrace && i == len(list)-1) - multiLine = p.isMultiLine(s) + // labeled statements put labels on a separate line, but here + // we only care about whether the actual statement w/o label + // is a multi-line statement - remove the label first + // (was issue 5623) + multiLine = p.isMultiLine(unlabeledStmt(s)) i++ } } @@ -923,6 +927,15 @@ func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { } } +// unlabeledStmt returns the statement of a labeled statement s; +// otherwise it return s. +func unlabeledStmt(s ast.Stmt) ast.Stmt { + if s, _ := s.(*ast.LabeledStmt); s != nil { + return unlabeledStmt(s.Stmt) + } + return s +} + // block prints an *ast.BlockStmt; it always spans at least two lines. func (p *printer) block(b *ast.BlockStmt, nindent int) { p.print(b.Lbrace, token.LBRACE)
-
新しいヘルパー関数
unlabeledStmt
の追加。
コアとなるコードの解説
-
src/pkg/go/printer/nodes.go
:stmtList
関数は、GoのASTノードのリストを処理し、整形されたコードを生成する主要な関数の一つです。この関数は、各ステートメントの後に改行を挿入するかどうか、およびそのステートメントが複数行にわたるかどうかを判断します。- 変更前の
multiLine = p.isMultiLine(s)
は、現在のステートメントs
が複数行であるかを直接チェックしていました。しかし、s
が*ast.LabeledStmt
の場合、ラベル自体が改行を伴うため、isMultiLine
がtrue
を返してしまうことが問題でした。 - 変更後の
multiLine = p.isMultiLine(unlabeledStmt(s))
では、unlabeledStmt
関数を介して、ラベル付きステートメントから「ラベル」部分を取り除いた純粋なステートメント本体を取得し、その本体が複数行であるかをチェックするように修正されました。これにより、コメントのアライメントがラベルではなく、実際のコードのインデントに追従するようになります。 unlabeledStmt
関数は、再帰的にラベル付きステートメントを剥がしていくことで、最終的にラベルを持たないステートメントを返します。これは、L1: L2: stmt
のように複数のラベルがネストされている場合にも正しく機能します。
-
src/pkg/go/printer/testdata/comments2.input
およびcomments2.golden
:- これらのファイルは、
go/printer
のテストデータです。.input
ファイルは整形前のコード、.golden
ファイルは整形後の期待されるコードを示します。 func issue5623()
という新しいテストケースが追加されており、様々な長さのラベルと、行コメントおよびブロックコメントの組み合わせで、コメントが正しくアライメントされることを検証しています。これにより、このコミットが修正しようとしている具体的な問題が明確に示され、修正が期待通りに機能していることが確認できます。
- これらのファイルは、
関連リンク
- Go issue #5623: https://github.com/golang/go/issues/5623
- Go CL 68400043: https://golang.org/cl/68400043 (Gerrit Code Review)
参考にした情報源リンク
- Go issue #5623 の内容
- Go CL 68400043 の内容
- Go言語の
go/printer
パッケージのドキュメント (GoDoc) - Go言語のASTに関する一般的な情報
- Go言語のラベル付きステートメントに関する情報
gofmt
ツールの動作原理に関する情報