[インデックス 14659] ファイルの概要
このコミットは、Go言語のツールチェインの一部である cmd/fix に新しい修正 (printerconfig fix) を追加するものです。この修正は、go/printer パッケージの Config 構造体の複合リテラルにおいて、要素に明示的なキーを追加することを目的としています。これにより、コードの可読性と将来の互換性が向上します。
コミット
commit c00371eafd6d53217354e61c97afba57e455c8cc
Author: Christopher Cahoon <chris.cahoon@gmail.com>
Date: Sun Dec 16 19:31:59 2012 -0500
cmd/fix: Add keys to printer.Config composite literals.
Fixes #4499.
R=rsc
CC=golang-dev
https://golang.org/cl/6931046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c00371eafd6d53217354e61c97afba57e455c8cc
元コミット内容
cmd/fix: Add keys to printer.Config composite literals.
Fixes #4499.
このコミットは、go/printer パッケージの Config 構造体の複合リテラルにキーを追加します。これは、Go言語のIssue #4499を修正するものです。
変更の背景
Go言語では、構造体の複合リテラルを初期化する際に、フィールド名を明示的に指定せずに値のリストで初期化することができます(例: MyStruct{value1, value2})。これは「位置による初期化」と呼ばれます。しかし、構造体のフィールドが追加されたり、順序が変更されたりすると、位置による初期化を使用しているコードはコンパイルエラーになったり、意図しない動作を引き起こしたりする可能性があります。
go/printer.Config 構造体も同様の問題を抱えていました。この構造体は、Goコードのフォーマットや出力に関する設定を保持しており、Mode と Tabwidth という2つのフィールドを持っていました。これらのフィールドが位置によって初期化されている場合、将来的に Config 構造体に新しいフィールドが追加されたり、既存のフィールドの順序が変更されたりすると、既存のコードが壊れる可能性がありました。
このコミットは、go fix ツールを使用して、このような位置による初期化を、フィールド名を明示的に指定する「キーによる初期化」に自動的に変換することで、将来の互換性の問題を回避することを目的としています。具体的には、printer.Config{0, 8} のような記述を printer.Config{Mode: 0, Tabwidth: 8} のように変換します。
関連するIssue #4499は、この問題提起と、go fix ツールによる自動修正の必要性について議論しています。
前提知識の解説
Go言語の複合リテラル (Composite Literals)
Go言語において、構造体、配列、スライス、マップなどの複合型を初期化するための構文です。
- 位置による初期化 (Positional Initialization): フィールド名を指定せず、宣言されたフィールドの順序に従って値を並べる方法。例:
Point{10, 20}(Point struct {X, Y int}) - キーによる初期化 (Keyed Initialization): フィールド名を明示的に指定して値を割り当てる方法。例:
Point{X: 10, Y: 20}
キーによる初期化は、フィールドの順序変更や追加に対して堅牢であり、コードの可読性も向上します。
go/printer パッケージ
Go言語の標準ライブラリの一部で、Goの抽象構文木 (AST) をGoのソースコードとして出力(プリント)するための機能を提供します。コードのフォーマットや整形、特定のスタイルでの出力などに利用されます。
printer.Config:go/printerパッケージがコードを整形する際の挙動を制御するための設定構造体です。通常、Mode(出力モード、例:printer.RawFormat,printer.SourcePos) とTabwidth(タブの幅) などのフィールドを持ちます。
go/ast パッケージ
Go言語の標準ライブラリの一部で、Goのソースコードを抽象構文木 (AST) として表現するための型と関数を提供します。Goのコードを解析、変換、生成するツール(例: go vet, go fmt, go fix)で広く利用されます。
ast.CompositeLit: 複合リテラルを表すASTノード。ast.KeyValueExpr: キーと値のペアを表すASTノード。構造体のキーによる初期化やマップのリテラルなどで使用されます。ast.SelectorExpr: セレクタ式(例:pkg.Name)を表すASTノード。
cmd/fix ツール
go fix は、Go言語のソースコードを新しいGoのバージョンやAPIの変更に合わせて自動的に修正するためのコマンドラインツールです。Go言語の進化に伴い、古いAPIの使用や非推奨の構文を新しいものに変換する際に非常に役立ちます。各修正は「fix」として実装され、go fix コマンドを実行すると、適用可能なすべてのfixがコードに適用されます。
技術的詳細
このコミットで追加された printerconfig fix は、以下のロジックで動作します。
- 対象ファイルの特定:
go/printerパッケージをインポートしているGoのソースファイルが対象となります。 - ASTの走査: 抽象構文木 (AST) を走査し、
ast.CompositeLitノード(複合リテラル)を探します。 printer.Configの識別: 見つかった複合リテラルがprinter.Config型のものであるかを識別します。これは、複合リテラルの型がprinter.Configであることをast.SelectorExprを使って確認することで行われます。- キーの追加:
- 複合リテラルの要素が既に
ast.KeyValueExpr(キーと値のペア)である場合は、既にキーが追加されているためスキップします。 - 要素がキーを持たない場合、その要素のインデックスに基づいて適切なキー (
ModeまたはTabwidth) を割り当て、ast.KeyValueExprに変換します。- 最初の要素 (インデックス 0) には
Modeキーを割り当てます。 - 2番目の要素 (インデックス 1) には
Tabwidthキーを割り当てます。
- 最初の要素 (インデックス 0) には
- 複合リテラルの要素が既に
- 変更の適用: ASTに変更が加えられた場合、
fixedフラグをtrueに設定し、go fixツールが変更をファイルに書き戻すようにします。
この修正は、go/ast パッケージの機能(ASTの走査、ノードの識別、新しいノードの構築)をフル活用して、Goコードの構造をプログラム的に変更する典型的な例です。
コアとなるコードの変更箇所
このコミットでは、主に以下の2つのファイルが新規追加されています。
-
src/cmd/fix/printerconfig.go:printerconfigFixというfix構造体を定義し、この修正の名前、適用日、説明を登録しています。printerconfig関数がこの修正の主要なロジックを含んでいます。この関数は*ast.Fileを引数に取り、ASTを走査してprinter.Configの複合リテラルを探し、キーを追加する処理を行います。
-
src/cmd/fix/printerconfig_test.go:printerconfigTestsというテストケースのスライスを定義しています。addTestCases関数を使って、printerconfigfix のテストケースを登録しています。- テストケースは、入力コード (
In) と期待される出力コード (Out) を含んでおり、go fixが正しく変換を行うことを検証します。
src/cmd/fix/printerconfig.go の主要なコードスニペット
package main
import "go/ast"
func init() {
register(printerconfigFix)
}
var printerconfigFix = fix{
"printerconfig",
"2012-12-11",
printerconfig,
`Add element keys to Config composite literals.`,
}
func printerconfig(f *ast.File) bool {
if !imports(f, "go/printer") {
return false
}
fixed := false
walk(f, func(n interface{}) {
cl, ok := n.(*ast.CompositeLit)
if !ok {
return
}
se, ok := cl.Type.(*ast.SelectorExpr)
if !ok {
return
}
if !isTopName(se.X, "printer") || se.Sel == nil {
return
}
if ss := se.Sel.String(); ss == "Config" {
for i, e := range cl.Elts {
if _, ok := e.(*ast.KeyValueExpr); ok {
break // Already has keys.
}
switch i {
case 0:
cl.Elts[i] = &ast.KeyValueExpr{
Key: ast.NewIdent("Mode"),
Value: e,
}
case 1:
cl.Elts[i] = &ast.KeyValueExpr{
Key: ast.NewIdent("Tabwidth"),
Value: e,
}
}
fixed = true
}
}
})
return fixed
}
コアとなるコードの解説
printerconfig 関数がこの修正の核となる部分です。
-
if !imports(f, "go/printer") { return false }:- この行は、現在のファイルが
go/printerパッケージをインポートしているかどうかをチェックします。インポートしていない場合、この修正は適用する必要がないため、falseを返して処理を終了します。これは、不要なAST走査を避けるための最適化です。
- この行は、現在のファイルが
-
walk(f, func(n interface{}) { ... }):walk関数は、GoのASTを深さ優先で走査するためのヘルパー関数です。匿名関数は、走査中に見つかった各ASTノードnに対して実行されます。
-
cl, ok := n.(*ast.CompositeLit):- 現在のノード
nがast.CompositeLit型(複合リテラル)であるかを型アサーションで確認します。複合リテラルでなければ、このノードは対象外なので処理をスキップします。
- 現在のノード
-
se, ok := cl.Type.(*ast.SelectorExpr):- 複合リテラルの型 (
cl.Type) がast.SelectorExpr型(例:printer.Configのようなパッケージ名.型名形式)であるかをチェックします。
- 複合リテラルの型 (
-
if !isTopName(se.X, "printer") || se.Sel == nil { return }:- セレクタ式のパッケージ部分 (
se.X) が "printer" であるか、およびセレクタの選択部分 (se.Sel) が存在するかを確認します。これにより、printer.Configのような形式であることを保証します。
- セレクタ式のパッケージ部分 (
-
if ss := se.Sel.String(); ss == "Config" { ... }:- セレクタの選択部分 (
se.Sel) が "Config" であるかをチェックします。これにより、対象がprinter.Config型の複合リテラルであることを最終的に特定します。
- セレクタの選択部分 (
-
for i, e := range cl.Elts { ... }:printer.Configの複合リテラルであることが確認されたら、その要素 (cl.Elts) をループで処理します。
-
if _, ok := e.(*ast.KeyValueExpr); ok { break }:- 現在の要素
eが既にast.KeyValueExpr(キーと値のペア)である場合、その複合リテラルは既にキーによる初期化が行われているため、これ以上修正する必要はありません。breakでループを抜けます。
- 現在の要素
-
switch i { case 0: ... case 1: ... }:- 要素がキーを持たない場合、そのインデックス
iに応じて適切なキーを割り当てます。i == 0: 最初の要素にはModeキーを割り当てます。i == 1: 2番目の要素にはTabwidthキーを割り当てます。
- 新しい
ast.KeyValueExprを作成し、元の要素eをそのValueとして設定します。そして、元の要素を新しいast.KeyValueExprで置き換えます (cl.Elts[i] = ...)。
- 要素がキーを持たない場合、そのインデックス
-
fixed = true:- いずれかの要素が修正された場合、
fixedフラグをtrueに設定します。このフラグは、printerconfig関数が最終的に返す値となり、ファイルに変更が加えられたかどうかをgo fixツールに伝えます。
- いずれかの要素が修正された場合、
このコードは、GoのAST操作の典型的なパターンを示しており、特定の構文パターンを識別し、それをプログラム的に変換する能力を持っています。
関連リンク
- Go Issue #4499: https://github.com/golang/go/issues/4499
- Go CL 6931046: https://golang.org/cl/6931046 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント (
go/printer,go/ast,cmd/fix): - Go言語の複合リテラルに関する情報:
- Go言語のASTに関する一般的な解説記事 (例: "Go AST: The Absolute Beginner's Guide"):
- (具体的なURLは検索結果によるが、
go ast tutorialなどで検索可能)
- (具体的なURLは検索結果によるが、
- Go言語の
go fixツールに関する解説記事:- (具体的なURLは検索結果によるが、
go fix toolなどで検索可能)
- (具体的なURLは検索結果によるが、
- GitHubのGoリポジトリのコミット履歴:
- Go言語のIssueトラッカー:
- Gerrit Code Review (Goプロジェクトのコードレビューシステム):
- Christopher CahoonのGitHubプロフィール (コミット著者):
- (検索結果によるが、
Christopher Cahoon githubなどで検索可能)
- (検索結果によるが、
- rsc (Robert Griesemer, Go言語の共同設計者の一人):
- (検索結果によるが、
Robert Griesemer Goなどで検索可能)
- (検索結果によるが、
- golang-devメーリングリスト: