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

[インデックス 11993] ファイルの概要

このコミットは、Go言語の公式ツールであるgo fixコマンドにおけるバグ修正を目的としています。具体的には、抽象構文木(AST)を走査する際に、ast.Ellipsis(可変長引数などを表す...)の内部要素が正しく処理されない問題を解決しています。これにより、go fixos.Errorから組み込みのerrorインターフェースへの移行を処理する際に、可変長引数を持つ関数定義などでos.Errorが使用されている場合に、その型が正しくerrorに修正されるようになります。

コミット

commit a52027a491c27a057ed7413607393f5f0a256c8d
Author: Nigel Tao <nigeltao@golang.org>
Date:   Fri Feb 17 14:39:50 2012 +1100

    fix: walk ast.Ellipsis values.
    
    Fixes #2583.
    
    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/5671078

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a52027a491c27a057ed7413607393f5f0a256c8d

元コミット内容

--- a/src/cmd/fix/error_test.go
+++ b/src/cmd/fix/error_test.go
@@ -42,6 +42,10 @@ func g() {
 	error := 1
 	_ = error
 }
+
+func h(os.Error) {}
+
+func i(...os.Error) {}
 `,
 		Out: `package main
 
@@ -59,6 +63,10 @@ func g() {
 	error := 1
 	_ = error
 }
+
+func h(error) {}
+
+func i(...error) {}
 `,
 	},
 	{
diff --git a/src/cmd/fix/fix.go b/src/cmd/fix/fix.go
index d2067cb51e..a100be794e 100644
--- a/src/cmd/fix/fix.go
+++ b/src/cmd/fix/fix.go
@@ -113,6 +113,7 @@ func walkBeforeAfter(x interface{}, before, after func(interface{})) {
 	case *ast.BadExpr:
 	case *ast.Ident:
 	case *ast.Ellipsis:
+		walkBeforeAfter(&n.Elt, before, after)
 	case *ast.BasicLit:
 	case *ast.FuncLit:
 		walkBeforeAfter(&n.Type, before, after)

変更の背景

このコミットは、Go言語の進化に伴う重要な型変更、特にos.Errorから組み込みのerrorインターフェースへの移行に関連するものです。Go 1のリリースに向けて、標準ライブラリ全体でエラーハンドリングの統一が図られ、それまでosパッケージで定義されていたos.Errorインターフェースが、言語組み込みのerrorインターフェースに置き換えられました。

この変更は、既存のGoプログラムに影響を与えるため、Goチームはgo fixというツールを提供しました。go fixは、古いGoのコードを新しいGoのバージョンで動作するように自動的に書き換えるためのコマンドラインツールです。しかし、このツールには、可変長引数(...)を持つ関数定義内でos.Errorが使用されている場合に、そのos.Errorが正しくerrorに修正されないというバグが存在しました。

具体的には、go fixがコードの抽象構文木(AST)を走査する際、ast.Ellipsisノード(可変長引数を表すASTノード)の内部にある要素(この場合はos.Error型)に対して再帰的な走査が行われていなかったため、その部分の型が修正されずに残ってしまう問題がありました。このコミットは、この見落としを修正し、go fixがより堅牢に型変換を行えるようにすることを目的としています。

前提知識の解説

Go言語のgo fixコマンド

go fixは、Go言語の標準ツールチェーンに含まれるコマンドラインユーティリティです。その主な目的は、Go言語のバージョンアップに伴うAPIの変更や言語仕様の変更に対応するため、古いGoのソースコードを自動的に新しいバージョンに書き換えることです。これにより、開発者は手動で大量のコードを修正する手間を省き、スムーズに新しいGoのバージョンへ移行できます。例えば、このコミットが関連するos.Errorからerrorへの移行のように、大規模なAPI変更があった際に非常に役立ちます。

Go言語の抽象構文木(AST)

Goコンパイラやツールは、Goのソースコードを直接テキストとして扱うのではなく、その構造を表現する「抽象構文木(Abstract Syntax Tree, AST)」に変換して処理します。ASTは、プログラムの構造を木構造で表現したもので、各ノードがプログラムの要素(変数、関数、式、型など)に対応します。

Go言語には、go/astパッケージが提供されており、GoのソースコードをパースしてASTを構築したり、ASTを走査したりするための機能が提供されています。go fixのようなツールは、このASTを操作することで、コードの構造を理解し、必要な変更を適用します。

ast.Ellipsis

ast.Ellipsisは、Go言語のASTにおける特定のノードタイプです。これは、Goのコードにおける...(エリプシス)トークンを表します。このトークンは主に以下の2つの文脈で使用されます。

  1. 可変長引数(Variadic Functions): 関数定義において、最後のパラメータの型の前に...を付けることで、その関数が任意の数の引数を受け取れることを示します。例: func foo(args ...int)
  2. 配列リテラル: 配列の長さをコンパイラに推論させるために使用されます。例: var a = [...]int{1, 2, 3}

このコミットでは、特に可変長引数の文脈でast.Ellipsisが問題となっていました。...os.Errorのような記述があった場合、ast.Ellipsisノード自体は...を表しますが、その内部にあるos.Errorという型情報も正しく処理される必要がありました。

os.Errorからerrorインターフェースへの移行

Go言語の初期のバージョンでは、エラーを表すためにos.Errorというインターフェースがosパッケージ内に定義されていました。しかし、Go 1のリリースに向けて、エラーハンドリングの統一と簡素化のため、os.Errorは言語組み込みのerrorインターフェースに置き換えられました。

  • os.Error (旧): type Error interface { String() string }
  • error (新): type error interface { Error() string }

この変更は、Go言語のエラーハンドリングの標準化に大きく貢献しましたが、既存のコードベースを新しいGoバージョンに移行する際には、os.Errorを使用している箇所をすべてerrorに書き換える必要がありました。go fixはこの自動変換を支援する役割を担っていました。

技術的詳細

このコミットの技術的な核心は、go fixツールがGoソースコードのASTを走査する際のロジックの改善にあります。go fixは、コード内の特定のパターン(この場合はos.Errorの使用)を見つけて、それを新しいパターン(error)に置き換えるために、ASTを再帰的に走査するメカニズムを持っています。

問題は、src/cmd/fix/fix.go内のwalkBeforeAfter関数にありました。この関数は、ASTノードを走査し、各ノードに対して前処理(before)と後処理(after)を適用するための汎用的なウォーカーです。しかし、ast.Ellipsisノードを処理するcase文において、ast.Ellipsisが持つ内部要素(Eltフィールド)に対する再帰的なwalkBeforeAfterの呼び出しが欠落していました。

ast.Ellipsis構造体は、可変長引数や配列リテラルの要素の型を表すEltフィールドを持っています。例えば、...os.Errorというコードがあった場合、ast.EllipsisノードのEltフィールドはos.Errorを表すASTノードを指します。go fixos.Errorerrorに修正するためには、このEltフィールドが指すノードに対してもウォーカーが適用される必要がありました。

このコミットでは、ast.EllipsiscasewalkBeforeAfter(&n.Elt, before, after)という行を追加することで、この問題を解決しています。これにより、go fix...os.Errorのような構造に出くわした際に、os.Errorの部分も正しく走査し、必要な型変換を適用できるようになりました。

src/cmd/fix/error_test.goの変更は、この修正が正しく機能することを確認するためのテストケースの追加です。特に、func i(...os.Error) {}という可変長引数を持つ関数定義が、go fixによってfunc i(...error) {}に正しく変換されることを検証しています。

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

src/cmd/fix/error_test.go

このファイルはgo fixコマンドのテストケースを定義しています。追加されたテストケースは、os.Errorからerrorへの変換が、可変長引数を持つ関数定義においても正しく行われることを確認するためのものです。

// 追加されたテスト入力
func h(os.Error) {}
func i(...os.Error) {}

// 期待される出力
func h(error) {}
func i(...error) {}

func i(...os.Error) {}という行が追加され、そのOut(期待される出力)としてfunc i(...error) {}が指定されています。これは、go fixast.Ellipsisの内部にあるos.Errorを正しくerrorに修正できるようになったことをテストしています。

src/cmd/fix/fix.go

このファイルはgo fixコマンドの主要なロジックを含んでいます。特に、ASTを走査するためのwalkBeforeAfter関数が修正されています。

// 修正前
// case *ast.Ellipsis:
//     // 何も処理がなかった

// 修正後
case *ast.Ellipsis:
    walkBeforeAfter(&n.Elt, before, after) // n.Elt (Ellipsisの要素)を再帰的に走査する行が追加された

ast.Ellipsisノードを処理するcase文に、walkBeforeAfter(&n.Elt, before, after)という行が追加されました。ここで、nは現在のast.Ellipsisノードを指し、n.Eltはそのエリプシスが適用される要素(例えば、...os.Errorにおけるos.Error)を表します。この追加により、go fixast.Ellipsisの内部にある型情報も正しく走査し、必要に応じて修正を適用できるようになりました。

コアとなるコードの解説

このコミットの核心は、go fixがGoのASTを走査する際の「深さ」を改善した点にあります。以前は、ast.Ellipsisノードに到達しても、そのノードが表す...記号自体は認識しても、その...が適用される具体的な型(n.Elt)に対しては、さらに深く走査する処理が欠けていました。

例えば、func i(...os.Error)というコードがあった場合、go fixfunc i(...)の部分は認識しますが、os.Errorという型が...の「中」にあるため、その型を修正するためのウォーカーが適用されませんでした。

追加されたwalkBeforeAfter(&n.Elt, before, after)という一行は、この問題を解決します。これは、現在のast.EllipsisノードnEltフィールド(つまり、...の対象となる要素)を、再度walkBeforeAfter関数に渡して再帰的に走査するように指示しています。これにより、go fixos.Errorのような型がast.Ellipsisの内部にネストされていても、それを発見し、適切な修正(この場合はos.Errorerrorに変換)を適用できるようになりました。

この修正は、go fixツールの堅牢性を高め、Go言語のバージョンアップに伴うコードの自動変換をより正確に行うために不可欠なものでした。特に、Go 1への移行期において、既存のコードベースが新しい言語仕様にスムーズに適合できるよう支援する上で重要な役割を果たしました。

関連リンク

参考にした情報源リンク