[インデックス 12143] ファイルの概要
このコミットは、Go言語の公式フォーマッタであるgofmt
と、その基盤となるgo/printer
パッケージにおけるカンマの配置ロジックの改善に関するものです。特に、コメントが隣接する場合のカンマの扱いが修正され、より自然で読みやすいコードフォーマットが実現されています。
コミット
commit 8b7cdb7f25ff1e97150ee4648ff4f7764454ccd5
Author: Robert Griesemer <gri@golang.org>
Date: Wed Feb 22 11:27:45 2012 -0800
go/printer, gofmt: improved comma placement
Not a Go 1 issue, but appeared to be fairly easy to fix.
- Note that a few existing test cases look slightly worse but
those cases were not representative for real code. All real
code looks better now.
- Manual move of the comment in go/scanner/example_test.go
before applying gofmt.
- gofmt -w $GOROOT/src $GOROOT/misc
Fixes #3062.
R=rsc
CC=golang-dev
https://golang.org/cl/5674093
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8b7cdb7f25ff1e97150ee4648ff4f7764454ccd5
元コミット内容
go/printer, gofmt: improved comma placement
このコミットは、Go言語のコードフォーマッタであるgofmt
と、その内部で使用されるgo/printer
パッケージにおけるカンマの配置を改善するものです。Go 1のリリースには直接関係ない問題でしたが、比較的簡単に修正できると判断されました。
変更によって、既存のいくつかのテストケースでは見た目がわずかに悪くなるものがありましたが、それらは実際のコードを代表するものではなく、実際のコードではすべて改善が見られるとのことです。
また、go/scanner/example_test.go
内のコメントは、gofmt
適用前に手動で移動されました。そして、$GOROOT/src
と$GOROOT/misc
に対してgofmt -w
が実行されました。
この変更は、Issue #3062を修正するものです。
変更の背景
Go言語では、コードの可読性と一貫性を保つために、gofmt
という自動フォーマッタが提供されています。gofmt
は、Goのソースコードを標準的なスタイルに整形し、開発者がスタイルガイドラインについて議論する時間を削減することを目的としています。
しかし、特定の状況、特にコード内にコメントが挿入されている場合において、gofmt
によるカンマの配置が最適ではないという問題がありました。例えば、カンマの直後にコメントがある場合、カンマがコメントの前に移動してしまうことで、コードの意図が分かりにくくなったり、視覚的な整合性が損なわれたりすることがありました。
このコミットは、このようなgofmt
の既存の振る舞いを改善し、コメントとカンマの間の関係をより適切に処理することで、生成されるコードの可読性と美しさを向上させることを目的としています。特に、カンマがリスト内の各要素に属するように、その要素の直後に配置されるべきという原則を強化しています。
前提知識の解説
gofmt
gofmt
は、Go言語のソースコードを自動的に整形するツールです。Go言語のツールチェインに標準で含まれており、Goコミュニティ全体でコードスタイルの一貫性を保つために広く利用されています。gofmt
は、インデント、スペース、改行、カンマなどの配置をGoの公式スタイルガイドラインに従って自動的に調整します。これにより、開発者はコードのスタイルについて悩むことなく、本質的なロジックの実装に集中できます。
go/printer
パッケージ
go/printer
パッケージは、Goの抽象構文木(AST: Abstract Syntax Tree)をGoのソースコードとして出力するためのパッケージです。gofmt
は、このgo/printer
パッケージを内部的に利用して、ASTを整形されたコードに変換しています。つまり、gofmt
の出力スタイルは、主にgo/printer
パッケージのロジックによって決定されます。このパッケージは、コメントの扱い、改行の挿入、カンマの配置など、コードの視覚的な表現に関する詳細なルールを実装しています。
Go言語のコードフォーマットにおけるカンマの役割
Go言語では、リスト、引数、構造体リテラル、マップリテラルなどの要素を区切るためにカンマが使用されます。一般的に、カンマは区切られる要素の直後に配置され、その後にスペースが続きます。このコミットの背景にある問題は、この「要素の直後にカンマ」という原則が、コメントが介在する場合に適切に適用されないケースがあったことです。
token.Pos
とコメントの関連性
Goのパーサーは、ソースコード内の各トークン(キーワード、識別子、演算子など)とコメントに、その位置情報(token.Pos
)を割り当てます。go/printer
は、この位置情報とASTを基にコードを整形します。コメントはコードの論理的な構造の一部ではないため、go/printer
がコメントをどこに配置するかは、そのコメントが関連するコード要素のtoken.Pos
と、go/printer
が持つ整形ルールに依存します。このコミットでは、カンマとコメントの相対的な位置関係をより適切に処理するために、token.Pos
の利用方法が調整されています。
技術的詳細
このコミットの主要な変更点は、go/printer
パッケージにおけるカンマの配置ロジック、特にコメントが隣接する場合の挙動の改善です。
従来のgo/printer
では、カンマの後にコメントが続く場合、カンマがコメントの前に移動してしまうことがありました。これは、カンマが区切る要素の直後に配置されるべきというGoの一般的なスタイルガイドラインに反するものでした。
このコミットでは、以下の主要な変更が導入されています。
-
カンマの位置情報の調整:
exprList
関数(式リストの整形)とparameters
関数(関数パラメータの整形)において、カンマをプリントする際に、そのカンマが区切る次の要素のtoken.Pos()
を使用するように変更されました。- 具体的には、
p.print(token.COMMA)
の代わりにp.print(x.Pos(), token.COMMA)
(x
は次の要素)やp.print(par.Pos(), token.COMMA)
(par
は次のパラメータ)が使用されています。 - これにより、カンマは次の要素の開始位置に「紐付けられる」ようになり、その要素に続くコメントがあっても、カンマがコメントの前に適切に配置されるようになります。
- ただし、次の要素が新しい行にある場合は、この位置情報の調整は行われません(
!needsLinebreak
の条件)。これは、改行によってカンマと次の要素の関連性が薄れるため、コメントの配置に影響を与えないようにするためです。
-
printer.intersperseComments
関数のロファクタリング:- この関数は、コードとコメントの間に適切なスペースや改行を挿入する役割を担っています。
- 変更前は、
/*-style
のコメントが次の要素と同じ行にある場合、無条件にスペースを追加していました。 - 変更後は、次のトークンがカンマ (
token.COMMA
)、右括弧 (token.RPAREN
)、右角括弧 (token.RBRACK
)、または右波括弧 (token.RBRACE
) でない場合にのみスペースを追加するように条件が追加されました。 - これにより、カンマや閉じ括弧の直後にコメントがある場合に、不要なスペースが挿入されるのを防ぎ、よりコンパクトで自然なフォーマットを実現しています。
-
テストケースの更新:
src/pkg/go/printer/testdata/comments.golden
とsrc/pkg/go/printer/testdata/comments.input
が更新され、新しいカンマ配置ロジックの挙動を反映しています。特に、カンマの直後にコメントがある場合の整形結果が改善されていることが示されています。
これらの変更により、gofmt
は、特にコメントがコード内に散りばめられている場合に、より一貫性があり、視覚的に優れたコードを生成できるようになりました。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に以下のファイルに集中しています。
src/pkg/go/printer/nodes.go
: 式リスト、関数パラメータ、for-range
文におけるカンマの配置ロジックが変更されています。src/pkg/go/printer/printer.go
: コメントと次のトークンの間にスペースを挿入するロジックが変更されています。src/pkg/go/printer/testdata/comments.golden
: 期待される整形結果のテストデータが更新されています。src/pkg/go/printer/testdata/comments.input
: 整形前の入力テストデータが更新されています。
具体的な変更行は以下の通りです。
src/pkg/go/printer/nodes.go
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -132,7 +132,9 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
for i, x := range list {
if i > 0 {
if mode&commaSep != 0 {
- p.print(token.COMMA)
+ // use position of expression following the comma as
+ // comma position for correct comment placement
+ p.print(x.Pos(), token.COMMA)
}
p.print(blank)
}
@@ -212,11 +214,18 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
}
if i > 0 {
+ needsLinebreak := prevLine < line && prevLine > 0 && line > 0
if mode&commaSep != 0 {
+ // use position of expression following the comma as
+ // comma position for correct comment placement, but
+ // only if the expression is on the same line
+ if !needsLinebreak {
+ p.print(x.Pos())
+ }
p.print(token.COMMA)
}
needsBlank := true
- if prevLine < line && prevLine > 0 && line > 0 {
+ if needsLinebreak {
// lines are broken using newlines so comments remain aligned
// unless forceFF is set or there are multiple expressions on
// the same line in which case formfeed is used
@@ -283,11 +292,18 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
parLineBeg = parLineEnd
}
// separating "," if needed
+ needsLinebreak := 0 < prevLine && prevLine < parLineBeg
if i > 0 {
+ // use position of parameter following the comma as
+ // comma position for correct comma placement, but
+ // only if the next parameter is on the same line
+ if !needsLinebreak {
+ p.print(par.Pos())
+ }
p.print(token.COMMA)
}
// separator if needed (linebreak or blank)
- if 0 < prevLine && prevLine < parLineBeg && p.linebreak(parLineBeg, 0, ws, true) {
+ if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) {
// break line if the opening "(" or previous parameter ended on a different line
// or if there are multiple expressions on the same line in which case formfeed is used
ws = ignore
@@ -312,7 +328,7 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
// if the closing ")" is on a separate line from the last parameter,
// print an additional "," and line break
if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing {
- p.print(",")
+ p.print(token.COMMA)
p.linebreak(closing, 0, ignore, true)
}
// unindent if we indented
@@ -393,6 +409,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
f := list[0]
for i, x := range f.Names {
if i > 0 {
+ // no comments so no need for comma position
p.print(token.COMMA, blank)
}
p.expr(x, ignoreMultiLine)
@@ -1125,7 +1142,9 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
p.print(token.FOR, blank)
p.expr(s.Key, multiLine)
if s.Value != nil {
- p.print(token.COMMA, blank)
+ // use position of value following the comma as
+ // comma position for correct comment placement
+ p.print(s.Value.Pos(), token.COMMA, blank)
p.expr(s.Value, multiLine)
}
p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank
src/pkg/go/printer/printer.go
--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -686,9 +686,11 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro
}
if last != nil {
- if last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line {
- // the last comment is a /*-style comment and the next item
- // follows on the same line: separate with an extra blank
+ // if the last comment is a /*-style comment and the next item
+ // follows on the same line but is not a comma or a "closing"
+ // token, add an extra blank for separation
+ if last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line && tok != token.COMMA &&
+ tok != token.RPAREN && tok != token.RBRACK && tok != token.RBRACE {
p.writeByte(' ', 1)
}
// ensure that there is a line break after a //-style comment,
コアとなるコードの解説
src/pkg/go/printer/nodes.go
の変更
このファイルでは、GoのASTノードを整形する際のカンマの配置ロジックが修正されています。
-
exprList
関数とparameters
関数におけるp.print(x.Pos(), token.COMMA)
:- 以前は単に
p.print(token.COMMA)
としてカンマを出力していました。これは、カンマ自体の位置情報を持たず、プリンタが内部的に決定する位置に配置されることを意味します。 - 変更後は、
x.Pos()
(またはpar.Pos()
)を引数として追加しています。x
はリスト内の次の要素(式やパラメータ)を表します。 - この変更の意図は、カンマを次の要素の開始位置に「紐付ける」ことです。これにより、カンマの直後にコメントがある場合でも、カンマがコメントの前に適切に配置されるようになります。
go/printer
は、与えられた位置情報に基づいて、コメントを考慮した上で最適なカンマの配置を決定します。 needsLinebreak
の導入により、次の要素が新しい行にある場合は、この位置情報の調整を行わないようにしています。これは、改行がある場合はカンマと次の要素の視覚的な関連性が薄れるため、コメントの配置に影響を与えないようにするためです。
- 以前は単に
-
fieldList
関数におけるコメントの追加:// no comments so no need for comma position
というコメントが追加されています。これは、構造体のフィールドリストなど、カンマの直後にコメントが来る可能性が低い箇所では、カンマの位置情報を特別に指定する必要がないことを示しています。
-
stmt
関数(for-range
文)におけるp.print(s.Value.Pos(), token.COMMA, blank)
:for-range
文のキーと値のペアを区切るカンマについても、同様に値のtoken.Pos()
を使用してカンマの位置情報を指定するように変更されています。これにより、for a /* comment */, b := range x
のようなケースで、カンマがa
の直後に適切に配置されるようになります。
src/pkg/go/printer/printer.go
の変更
このファイルでは、コメントと次のトークンの間にスペースを挿入するロジックが修正されています。
intersperseComments
関数における条件の追加:/*-style
のコメントが次のトークンと同じ行にある場合にスペースを追加する既存のロジックに、新しい条件が追加されました。- 追加された条件は、次のトークンが
token.COMMA
(カンマ)、token.RPAREN
(右括弧)、token.RBRACK
(右角括弧)、またはtoken.RBRACE
(右波括弧)ではない場合にのみスペースを追加するというものです。 - この変更の目的は、カンマや閉じ括弧の直後にコメントがある場合に、
gofmt
が不要なスペースを挿入するのを防ぐことです。例えば、f( /* no args */)
のようなコードで、(
と/*
の間にスペースが挿入されないようにします。これにより、よりコンパクトで自然なコードフォーマットが実現されます。
これらの変更は、go/printer
がGoのコードを整形する際の「賢さ」を向上させ、特にコメントが絡む複雑なケースにおいて、より直感的でGoのスタイルガイドラインに沿った出力を生成することを可能にしています。
関連リンク
- Go言語公式ウェブサイト: https://golang.org/
gofmt
に関する公式ドキュメント: https://go.dev/blog/gofmt- Go言語のIssueトラッカー (GitHub): https://github.com/golang/go/issues
参考にした情報源リンク
- コミットハッシュ:
8b7cdb7f25ff1e97150ee4648ff4f7764454ccd5
- GitHub上のコミットページ: https://github.com/golang/go/commit/8b7cdb7f25ff1e97150ee4648ff4f7764454ccd5
- Go Change List (CL) 5674093 (コミットメッセージに記載されているが、今回のコミット内容とは異なる可能性が高い): https://golang.org/cl/5674093
- Go Issue #3062 (コミットメッセージに記載されているが、今回のコミット内容と直接関連する詳細な情報は見つからなかった): https://github.com/golang/go/issues/3062