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

[インデックス 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コードのフォーマットや出力に関する設定を保持しており、ModeTabwidth という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 は、以下のロジックで動作します。

  1. 対象ファイルの特定: go/printer パッケージをインポートしているGoのソースファイルが対象となります。
  2. ASTの走査: 抽象構文木 (AST) を走査し、ast.CompositeLit ノード(複合リテラル)を探します。
  3. printer.Config の識別: 見つかった複合リテラルが printer.Config 型のものであるかを識別します。これは、複合リテラルの型が printer.Config であることを ast.SelectorExpr を使って確認することで行われます。
  4. キーの追加:
    • 複合リテラルの要素が既に ast.KeyValueExpr(キーと値のペア)である場合は、既にキーが追加されているためスキップします。
    • 要素がキーを持たない場合、その要素のインデックスに基づいて適切なキー (Mode または Tabwidth) を割り当て、ast.KeyValueExpr に変換します。
      • 最初の要素 (インデックス 0) には Mode キーを割り当てます。
      • 2番目の要素 (インデックス 1) には Tabwidth キーを割り当てます。
  5. 変更の適用: ASTに変更が加えられた場合、fixed フラグを true に設定し、go fix ツールが変更をファイルに書き戻すようにします。

この修正は、go/ast パッケージの機能(ASTの走査、ノードの識別、新しいノードの構築)をフル活用して、Goコードの構造をプログラム的に変更する典型的な例です。

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

このコミットでは、主に以下の2つのファイルが新規追加されています。

  1. src/cmd/fix/printerconfig.go:

    • printerconfigFix という fix 構造体を定義し、この修正の名前、適用日、説明を登録しています。
    • printerconfig 関数がこの修正の主要なロジックを含んでいます。この関数は *ast.File を引数に取り、ASTを走査して printer.Config の複合リテラルを探し、キーを追加する処理を行います。
  2. src/cmd/fix/printerconfig_test.go:

    • printerconfigTests というテストケースのスライスを定義しています。
    • addTestCases 関数を使って、printerconfig fix のテストケースを登録しています。
    • テストケースは、入力コード (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 関数がこの修正の核となる部分です。

  1. if !imports(f, "go/printer") { return false }:

    • この行は、現在のファイルが go/printer パッケージをインポートしているかどうかをチェックします。インポートしていない場合、この修正は適用する必要がないため、false を返して処理を終了します。これは、不要なAST走査を避けるための最適化です。
  2. walk(f, func(n interface{}) { ... }):

    • walk 関数は、GoのASTを深さ優先で走査するためのヘルパー関数です。匿名関数は、走査中に見つかった各ASTノード n に対して実行されます。
  3. cl, ok := n.(*ast.CompositeLit):

    • 現在のノード nast.CompositeLit 型(複合リテラル)であるかを型アサーションで確認します。複合リテラルでなければ、このノードは対象外なので処理をスキップします。
  4. se, ok := cl.Type.(*ast.SelectorExpr):

    • 複合リテラルの型 (cl.Type) が ast.SelectorExpr 型(例: printer.Config のような パッケージ名.型名 形式)であるかをチェックします。
  5. if !isTopName(se.X, "printer") || se.Sel == nil { return }:

    • セレクタ式のパッケージ部分 (se.X) が "printer" であるか、およびセレクタの選択部分 (se.Sel) が存在するかを確認します。これにより、printer.Config のような形式であることを保証します。
  6. if ss := se.Sel.String(); ss == "Config" { ... }:

    • セレクタの選択部分 (se.Sel) が "Config" であるかをチェックします。これにより、対象が printer.Config 型の複合リテラルであることを最終的に特定します。
  7. for i, e := range cl.Elts { ... }:

    • printer.Config の複合リテラルであることが確認されたら、その要素 (cl.Elts) をループで処理します。
  8. if _, ok := e.(*ast.KeyValueExpr); ok { break }:

    • 現在の要素 e が既に ast.KeyValueExpr(キーと値のペア)である場合、その複合リテラルは既にキーによる初期化が行われているため、これ以上修正する必要はありません。break でループを抜けます。
  9. switch i { case 0: ... case 1: ... }:

    • 要素がキーを持たない場合、そのインデックス i に応じて適切なキーを割り当てます。
      • i == 0: 最初の要素には Mode キーを割り当てます。
      • i == 1: 2番目の要素には Tabwidth キーを割り当てます。
    • 新しい ast.KeyValueExpr を作成し、元の要素 e をその Value として設定します。そして、元の要素を新しい ast.KeyValueExpr で置き換えます (cl.Elts[i] = ...)。
  10. fixed = true:

    • いずれかの要素が修正された場合、fixed フラグを true に設定します。このフラグは、printerconfig 関数が最終的に返す値となり、ファイルに変更が加えられたかどうかを go fix ツールに伝えます。

このコードは、GoのAST操作の典型的なパターンを示しており、特定の構文パターンを識別し、それをプログラム的に変換する能力を持っています。

関連リンク

参考にした情報源リンク