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

[インデックス 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へ)が挿入されます。特に、Atof32Atouiのように、戻り値の型変換が必要なケースでは、gofixが自動的に型変換を挿入できないため、警告メッセージを出力し、開発者による手動での確認と修正を促しています。

この修正は、go/astパッケージを使用してGoのソースコードをASTとして解析し、go/parserパッケージを使用して文字列からASTを生成するexprヘルパー関数を導入しています。これにより、新しい引数をASTノードとして動的に追加することが可能になっています。

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

このコミットでは、以下の4つのファイルが変更されています。

  1. src/cmd/gofix/Makefile:

    • strconv.gogofixツールのビルド対象ファイルリストGOFILESに追加されています。これにより、新しく追加されるstrconv.goファイルがgofixバイナリにコンパイルされるようになります。
  2. src/cmd/gofix/fix.go:

    • go/parserパッケージがインポートに追加されています。これは、新しいexprヘルパー関数で使用されます。
    • expr(s string) ast.Exprという新しいヘルパー関数が追加されています。この関数は、与えられた文字列sをGoの式としてパースし、そのAST表現を返します。パースに失敗した場合はパニックを発生させます。これは、gofixが新しい引数(例: 32, 64, 10)をASTノードとして動的に生成するために使用されます。
  3. src/cmd/gofix/strconv.go: (新規ファイル)

    • このファイルは、strconvパッケージのAPI変更に対応するgofixのロジックを実装しています。
    • strconvFixというfix構造体が定義されており、修正の名前、日付、実行関数、説明が含まれています。
    • init()関数でstrconvFixgofixのレジストリに登録されます。
    • 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に設定することで、ファイルが修正されたことを示します。
  4. src/cmd/gofix/strconv_test.go: (新規ファイル)

    • このファイルは、strconv.goで実装されたgofixの修正が正しく機能するかを検証するためのテストケースを含んでいます。
    • strconvTestsという[]testCaseスライスが定義されており、複数のテストケースが含まれています。
    • 各テストケースは、Name(テスト名)、In(修正前のGoコードの文字列)、Out(修正後の期待されるGoコードの文字列)で構成されています。
    • init()関数でstrconvTestsgofixのテストフレームワークに登録されます。
    • 提供されているテストケースは、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の変更がどのように既存のコードベースに影響を与え、それを自動的に移行するためのツールがどのように開発されたかを示す良い例です。

関連リンク

参考にした情報源リンク