[インデックス 10617] ファイルの概要
このコミットは、Go言語のstrconv
パッケージのAPI変更に対応するためのgofix
ツールの修正を導入しています。具体的には、古いstrconv
関数の呼び出しを新しいAPIに自動的に書き換える機能を追加しています。
コミット
commit 4feafeeea0cc0d489557881f9148143c305f2198
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 5 15:52:35 2011 -0500
gofix: fix for strconv API change
R=golang-dev, gri, adg, r
CC=golang-dev
https://golang.org/cl/5434098
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4feafeeea0cc0d489557881f9148143c305f2198
元コミット内容
gofix: fix for strconv API change
R=golang-dev, gri, adg, r
CC=golang-dev
https://golang.org/cl/5434098
変更の背景
このコミットは、Go言語のstrconv
パッケージのAPIが変更されたことに対応するために作成されました。2011年12月頃、Go 1.0の安定版リリース(2012年3月)に向けて、strconv
パッケージは機能の洗練、パフォーマンスの向上、およびインターフェースの標準化を目的とした大規模なAPI変更が行われました。
主な変更点としては、以下のようなものがありました。
strconv.Itoa64
のような特定の関数がstrconv.FormatInt
のようなより汎用的な関数に置き換えられました。- 非常に大きな入力値の処理が改善されました。
- 内部的な最適化(例:テーブルベースの
isPrint
関数の使用、bytes
,unicode
,strings
パッケージへの依存関係の削除)が行われ、効率が向上しました。 strconv.ParseFloat
が非常に長いが有効な入力でパニックを起こす問題が特定され、修正されました。[]byte
引数を受け入れるParsexxx()
関数の追加が議論され、数値のパース時に高コストな文字列変換を減らすことが検討されました。
これらのAPI変更は、既存のGoコードベースに影響を与えるため、開発者が新しいAPIに容易に移行できるよう、gofix
ツールによる自動的なコード修正が必要とされました。このコミットは、そのgofix
ツールにstrconv
パッケージのAPI変更に対応する修正を追加するものです。
前提知識の解説
Go言語のstrconv
パッケージ
strconv
パッケージは、Go言語の標準ライブラリの一部であり、基本的なデータ型(整数、浮動小数点数、真偽値など)と文字列との間の変換機能を提供します。例えば、文字列を整数に変換するAtoi
や、整数を文字列に変換するItoa
などが含まれます。これらの関数は、プログラムが外部からの入力(ユーザー入力、ファイルからの読み込み、ネットワークデータなど)を処理する際や、データを文字列として出力する際に頻繁に利用されます。
gofix
ツール
gofix
は、Go言語のコードベースを、Go言語自体の進化(API変更、言語仕様の変更など)に合わせて自動的に修正するためのコマンドラインツールです。Go言語は初期の段階で活発に開発が進められており、Go 1.0のリリースに向けて多くのAPIが安定化されました。この過程で、既存のコードが新しいAPIや慣習に準拠するように、gofix
が重要な役割を果たしました。gofix
は、AST(抽象構文木)を操作することで、コードの構造を解析し、定義されたルールに基づいて自動的に修正を適用します。これにより、開発者は手動で大量のコードを修正する手間を省き、スムーズに新しいバージョンへ移行することができました。
API変更と後方互換性
API(Application Programming Interface)は、ソフトウェアコンポーネントが互いに通信するためのインターフェースのセットです。APIが変更されると、そのAPIを使用している既存のコードは動作しなくなる可能性があります。これを「後方互換性の破壊」と呼びます。プログラミング言語やライブラリの開発において、後方互換性の維持は重要な課題ですが、言語の成熟や設計の改善のために、時にはAPI変更が必要となることがあります。Go言語では、Go 1.0以降は厳格な後方互換性ポリシーが採用されていますが、それ以前のバージョンでは、より柔軟なAPI変更が行われていました。gofix
のようなツールは、このような過渡期において、開発者の負担を軽減するための重要な手段となります。
技術的詳細
このコミットで追加されたgofix
の修正は、strconv
パッケージの古いAPI呼び出しを検出し、新しいAPI呼び出しに変換することを目的としています。この変換は、Goの抽象構文木(AST)を走査し、特定のパターンに一致する関数呼び出しを見つけて、その関数名や引数を変更することで実現されます。
具体的には、以下の変換が行われます。
- 真偽値変換:
strconv.Atob(s)
→strconv.ParseBool(s)
strconv.Btoa(b)
→strconv.FormatBool(b)
- 浮動小数点数変換:
strconv.Atof32(s)
→strconv.ParseFloat(s, 32)
(戻り値の型変換が必要な旨の警告も出力)strconv.Atof64(s)
→strconv.ParseFloat(s, 64)
strconv.AtofN(s, bitSize)
→strconv.ParseFloat(s, bitSize)
strconv.Ftoa32(f, fmt, prec)
→strconv.FormatFloat(float64(f), fmt, prec, 32)
(引数の型変換も考慮)strconv.Ftoa64(f, fmt, prec)
→strconv.FormatFloat(f, fmt, prec, 64)
strconv.FtoaN(f, fmt, prec, bitSize)
→strconv.FormatFloat(f, fmt, prec, bitSize)
- 整数変換:
strconv.Atoi(s)
は変更なし(ラッパー関数として残存)strconv.Atoi64(s)
→strconv.ParseInt(s, 10, 64)
strconv.Btoi64(s, base)
→strconv.ParseInt(s, base, 64)
strconv.Atoui(s)
→strconv.ParseUint(s, 10, 0)
(戻り値の型変換が必要な旨の警告も出力)strconv.Atoui64(s)
→strconv.ParseUint(s, 10, 64)
strconv.Btoui64(s, base)
→strconv.ParseUint(s, base, 64)
strconv.Itoa(i)
は変更なし(ラッパー関数として残存)strconv.Itoa64(i)
→strconv.FormatInt(i, 10)
strconv.Itob(i, base)
→strconv.FormatInt(int64(i), base)
(引数の型変換も考慮)strconv.Itob64(i, base)
→strconv.FormatInt(i, base)
strconv.Uitoa(u)
→strconv.FormatUint(uint64(u), 10)
(引数の型変換も考慮)strconv.Uitoa64(u)
→strconv.FormatUint(u, 10)
strconv.Uitob(u, base)
→strconv.FormatUint(uint64(u), base)
(引数の型変換も考慮)strconv.Uitob64(u, base)
→strconv.FormatUint(u, base)
これらの変換では、新しいAPIで追加されたbase
(基数)やbitSize
(ビットサイズ)といった引数が、適切なデフォルト値(例:10進数の場合は10
、64ビットの場合は64
)で追加されます。また、一部の変換では、元のコードの型と新しいAPIの期待する型が異なる場合に、明示的な型変換(例:float32
からfloat64
へ、int
からint64
へ)が挿入されます。特に、Atof32
やAtoui
のように、戻り値の型変換が必要なケースでは、gofix
が自動的に型変換を挿入できないため、警告メッセージを出力し、開発者による手動での確認と修正を促しています。
この修正は、go/ast
パッケージを使用してGoのソースコードをASTとして解析し、go/parser
パッケージを使用して文字列からASTを生成するexpr
ヘルパー関数を導入しています。これにより、新しい引数をASTノードとして動的に追加することが可能になっています。
コアとなるコードの変更箇所
このコミットでは、以下の4つのファイルが変更されています。
-
src/cmd/gofix/Makefile
:strconv.go
がgofix
ツールのビルド対象ファイルリストGOFILES
に追加されています。これにより、新しく追加されるstrconv.go
ファイルがgofix
バイナリにコンパイルされるようになります。
-
src/cmd/gofix/fix.go
:go/parser
パッケージがインポートに追加されています。これは、新しいexpr
ヘルパー関数で使用されます。expr(s string) ast.Expr
という新しいヘルパー関数が追加されています。この関数は、与えられた文字列s
をGoの式としてパースし、そのAST表現を返します。パースに失敗した場合はパニックを発生させます。これは、gofix
が新しい引数(例:32
,64
,10
)をASTノードとして動的に生成するために使用されます。
-
src/cmd/gofix/strconv.go
: (新規ファイル)- このファイルは、
strconv
パッケージのAPI変更に対応するgofix
のロジックを実装しています。 strconvFix
というfix
構造体が定義されており、修正の名前、日付、実行関数、説明が含まれています。init()
関数でstrconvFix
がgofix
のレジストリに登録されます。strconvFn(f *ast.File) bool
関数が定義されています。この関数が実際の修正ロジックを含んでいます。- まず、ファイルが
strconv
パッケージをインポートしているかを確認します。インポートしていない場合は修正をスキップします。 walk
関数(gofix
のユーティリティ関数)を使用して、ASTツリーを走査します。- 各ノードが
ast.CallExpr
(関数呼び出し)であり、かつstrconv
パッケージの関数呼び出しであるかをチェックします。 switch sel.Sel.Name
文を使って、古いstrconv
関数の名前(例:Atob
,Atof32
)に基づいて、対応する新しい関数名(例:ParseBool
,ParseFloat
)にsel.Sel.Name
を書き換えます。- 必要に応じて、
add(s string)
ヘルパー関数を使って、新しい引数(例:32
,64
,10
)をcall.Args
に追加します。このadd
関数は、前述のexpr
関数を利用して文字列からASTノードを生成します。 - 特定のケース(例:
Atof32
からParseFloat
への変換)では、戻り値の型変換が必要な旨の警告メッセージをwarn
関数で出力します。 strconvRewrite(t1, t2 string, x ast.Expr) ast.Expr
というヘルパー関数も定義されています。これは、float32(x)
のような型変換の式をfloat64(x)
のように書き換えるか、またはx
が直接型変換されていない場合はt2(x)
という新しい型変換式を挿入するために使用されます。
- まず、ファイルが
fixed
変数をtrue
に設定することで、ファイルが修正されたことを示します。
- このファイルは、
-
src/cmd/gofix/strconv_test.go
: (新規ファイル)- このファイルは、
strconv.go
で実装されたgofix
の修正が正しく機能するかを検証するためのテストケースを含んでいます。 strconvTests
という[]testCase
スライスが定義されており、複数のテストケースが含まれています。- 各テストケースは、
Name
(テスト名)、In
(修正前のGoコードの文字列)、Out
(修正後の期待されるGoコードの文字列)で構成されています。 init()
関数でstrconvTests
がgofix
のテストフレームワークに登録されます。- 提供されているテストケースは、
strconv
パッケージの様々な古い関数呼び出し(Atob
,Btoa
,Atof32
,Ftoa32
,Atoi64
,ParseInt
,Uitoa
,FormatUint
など)が、期待される新しいAPI呼び出しに正しく変換されることを確認しています。特に、引数の追加や型変換が正しく行われるかどうかも検証されています。
- このファイルは、
コアとなるコードの解説
このコミットの核となるのは、src/cmd/gofix/strconv.go
ファイルに実装されたstrconvFn
関数です。
// src/cmd/gofix/strconv.go
func strconvFn(f *ast.File) bool {
if !imports(f, "strconv") { // strconvパッケージをインポートしているか確認
return false
}
fixed := false // 修正が行われたかを示すフラグ
walk(f, func(n interface{}) { // ASTツリーを走査
call, ok := n.(*ast.CallExpr) // 関数呼び出しノードか確認
if !ok || len(call.Args) < 1 {
return
}
sel, ok := call.Fun.(*ast.SelectorExpr) // セレクタ式(例: strconv.Atob)か確認
if !ok || !isTopName(sel.X, "strconv") { // strconvパッケージの関数か確認
return
}
change := func(name string) { // 関数名を変更するヘルパー関数
fixed = true
sel.Sel.Name = name
}
add := func(s string) { // 引数を追加するヘルパー関数
call.Args = append(call.Args, expr(s)) // expr関数で文字列からASTノードを生成
}
switch sel.Sel.Name { // 古い関数名に基づいて処理を分岐
case "Atob":
change("ParseBool")
case "Atof32":
change("ParseFloat")
add("32") // bitSize引数を追加
warn(call.Pos(), "rewrote strconv.Atof32(_) to strconv.ParseFloat(_, 32) but return value must be converted to float32")
// ... 他の多くのケース ...
case "Ftoa32":
change("FormatFloat")
// float32(x)のような型変換をfloat64(x)に書き換えるか、float64(x)を挿入
call.Args[0] = strconvRewrite("float32", "float64", call.Args[0])
add("32") // bitSize引数を追加
// ... 他の多くのケース ...
case "Uitoa":
change("FormatUint")
// uint(x)のような型変換をuint64(x)に書き換えるか、uint64(x)を挿入
call.Args[0] = strconvRewrite("uint", "uint64", call.Args[0])
add("10") // base引数を追加
// ... 他の多くのケース ...
}
})
return fixed // 修正が行われた場合はtrueを返す
}
// src/cmd/gofix/strconv.go
// strconvRewriteは、型t1から型t2への書き換えを行うヘルパー関数
// 式xがt1(y)の形式であれば、t2(y)を使用する。そうでなければt2(x)を使用する。
func strconvRewrite(t1, t2 string, x ast.Expr) ast.Expr {
if call, ok := x.(*ast.CallExpr); ok && isTopName(call.Fun, t1) {
call.Fun.(*ast.Ident).Name = t2 // 型変換の関数名を変更
return x
}
// 新しい型変換式を生成して返す
return &ast.CallExpr{Fun: ast.NewIdent(t2), Args: []ast.Expr{x}}
}
strconvFn
関数は、GoのASTを深く理解し、特定のパターン(strconv.OldFunctionName(...)
)を識別して、それをstrconv.NewFunctionName(..., newArgs...)
という形式に変換します。add
ヘルパー関数は、expr
関数を利用して文字列からASTノードを生成し、新しい引数を動的に追加する柔軟性を提供します。strconvRewrite
関数は、特に型変換が絡むケース(例: float32
からfloat64
への昇格)で、既存の型変換式を適切に修正するか、または新しい型変換式を挿入する役割を担っています。
この修正は、Go言語の進化の過程で、APIの変更がどのように既存のコードベースに影響を与え、それを自動的に移行するためのツールがどのように開発されたかを示す良い例です。
関連リンク
- Go CL 5434098: https://golang.org/cl/5434098
- Go CL 5434095: http://codereview.appspot.com/5434095 (strconv API変更の関連コードレビュー)
- Go CL 5434069: http://codereview.appspot.com/5434069 (strconv API変更の関連コードレビュー)
参考にした情報源リンク
- Go
strconv
package API changes around December 2011: - Go
gofix
tool documentation (general concept):- https://go.dev/blog/go1 (Go 1のリリースと後方互換性に関するブログ記事)
- https://go.dev/doc/go1compat (Go 1の互換性に関するドキュメント)
- https://go.dev/blog/go-tool-fix (Go tool fixに関するブログ記事)