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

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

このコミットは、Go言語のgofixツールにtime+fileinfoという新しい修正ルールを追加するものです。この修正は、Go言語の標準ライブラリであるtimeパッケージとos.FileInfoインターフェースのAPI変更に対応するために導入されました。具体的には、古いAPIの利用箇所を新しいAPIに自動的に書き換えることを目的としていますが、機械的な変更に限定され、手動での追加修正が必要となる場合があることが明記されています。

コミット

commit c52b7db470880681d8467e87830dc24a1196be63
Author: Russ Cox <rsc@golang.org>
Date:   Thu Dec 1 13:59:57 2011 -0500

    gofix: add time+fileinfo fix
    
    R=adg, rogpeppe, r, cw
    CC=golang-dev
    https://golang.org/cl/5450050

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

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

元コミット内容

このコミットは、gofixツールにtime+fileinfoという新しい修正を追加します。この修正は、timeパッケージとos.FileInfoインターフェースの新しいAPIに対応するためのコードの書き換えを行います。

変更の背景

Go言語は初期の段階で活発な開発が行われており、APIの改善や整理が頻繁に行われていました。このコミットが行われた2011年頃は、Go 1.0のリリースに向けて標準ライブラリの安定化が進められていた時期です。

特に、timeパッケージとos.FileInfoインターフェースは、より直感的で一貫性のあるAPIを提供するために大幅な変更が加えられました。これらの変更は、既存のGoプログラムに破壊的な影響を与える可能性があったため、開発者が新しいAPIに容易に移行できるよう、gofixツールによる自動修正が提供されることになりました。

この修正の背景には、以下の具体的なAPI変更があります。

  • timeパッケージの変更:
    • 時刻の表現が、秒やナノ秒の整数値から、より抽象的なtime.Time型に移行しました。
    • 時刻の取得方法がtime.Seconds()time.LocalTime()からtime.Now()に統一されました。
    • 時刻の変換関数(例: time.SecondsToLocalTime)がtime.Unixなどのより汎用的な関数に置き換えられました。
    • time.UTCが関数呼び出しから変数参照に変わりました。
    • time.Time間の差分計算が、直接の減算からSubメソッドの使用に変わりました。
  • os.FileInfoインターフェースの変更:
    • ファイル情報のフィールド(例: Name, Size, Mode, Mtime_ns)が、メソッド(例: Name(), Size(), Mode(), ModTime())に変更されました。これにより、インターフェースとしての柔軟性が向上しました。
    • IsDirectory()IsRegular()といったブール値を返すメソッドが、IsDir()という単一のメソッドに統合され、正規ファイルかどうかの判定は!IsDir()で行うようになりました。
    • *os.FileInfoのようなポインタ型ではなく、os.FileInfoという値型でインターフェースを扱うことが推奨されるようになりました。

これらの変更は、Go言語の設計思想である「シンプルさ」と「一貫性」を追求した結果であり、より堅牢で使いやすいAPIを提供するためのものでした。

前提知識の解説

Go言語のgofixツール

gofixは、Go言語のソースコードを自動的に書き換えて、新しいAPIや言語仕様の変更に対応させるためのコマンドラインツールです。Go言語の進化に伴い、過去のコードが新しいバージョンでコンパイルできなくなるような破壊的変更が発生した場合に、開発者が手動でコードを修正する手間を省くために開発されました。

gofixは、Goの抽象構文木(AST: Abstract Syntax Tree)を解析し、定義されたルールに基づいてコードを変換します。各修正は「fix」として実装され、特定のAPI変更や言語機能の変更に対応します。開発者はgofixを実行するだけで、多くの機械的なコード修正を自動化できます。

Go言語のtimeパッケージ

timeパッケージは、時刻の表現、操作、フォーマット、およびタイマー機能を提供します。このコミットの時点では、APIが進化の途中にあり、特に時刻の取得や変換に関する関数が変更されました。

  • time.Time: 特定の時点を表す構造体。新しいAPIでは、この型が時刻操作の中心となります。
  • time.Now(): 現在のローカル時刻をtime.Time型で返します。
  • time.Unix(sec int64, nsec int64): Unixエポック(1970年1月1日UTC)からの秒数とナノ秒数に基づいてtime.Timeを生成します。
  • t.Unix(): time.Time型のtが表す時刻のUnixエポックからの秒数を返します。
  • t.UnixNano(): time.Time型のtが表す時刻のUnixエポックからのナノ秒数を返します。
  • t.Sub(u time.Time): time.Time型のtuの差分をtime.Duration型で返します。
  • t.UTC(): time.Time型のtをUTC(協定世界時)に変換したtime.Timeを返します。

Go言語のos.FileInfoインターフェース

os.FileInfoインターフェースは、ファイルシステム上のファイルやディレクトリに関する情報(名前、サイズ、パーミッション、更新時刻など)を提供します。

  • os.Stat(name string): 指定されたパスのファイル情報をos.FileInfoインターフェースとして返します。
  • fi.Name(): ファイルまたはディレクトリの名前を返します。
  • fi.Size(): ファイルのサイズをバイト単位で返します。
  • fi.Mode(): ファイルのパーミッションとモードビットをos.FileMode型で返します。
  • fi.ModTime(): ファイルの最終更新時刻をtime.Time型で返します。
  • fi.IsDir(): ファイルがディレクトリである場合にtrueを返します。

抽象構文木(AST)

Go言語のコンパイラは、ソースコードを解析して抽象構文木(AST)を生成します。ASTは、プログラムの構造を木構造で表現したもので、各ノードがコードの要素(変数、関数呼び出し、式など)に対応します。gofixのようなツールは、このASTを操作することで、コードの意味を変えずに構造的な変更を加えることができます。

技術的詳細

このコミットで追加されたtime+fileinfo修正は、gofixフレームワーク内で動作するGoプログラムです。その主要なロジックはsrc/cmd/gofix/timefileinfo.goに実装されています。

  1. 修正の登録: init()関数内でregister(timefileinfoFix)が呼び出され、この修正がgofixツールに認識されるように登録されます。timefileinfoFix構造体は、修正の名前(time+fileinfo)、適用日、および修正ロジックを実装した関数(timefileinfo)を定義しています。
  2. 古いコードの検出 (timefileinfoIsOld): timefileinfoIsOld関数は、与えられたGoソースファイル(*ast.File)が古いAPIを使用しているかどうかを判断します。これは、修正を適用すべきかどうかを決定するための重要なステップです。以下のパターンを検出します。
    • *os.FileInfoまたは*time.Timeのようなポインタ型の使用。
    • timeパッケージの古い関数名(例: LocalTime, SecondsToLocalTime, Secondsなど)の参照。
    • os.FileInfoの古いフィールド名(例: Mtime_ns, IsDirectory, IsRegular, Name, Size, Modeがフィールドとして参照されている場合)の参照。
    • time.UTC()のような関数呼び出し(新しいAPIではtime.UTCは変数)。 この検出ロジックは、ASTを走査し、特定のノードパターンをチェックすることで実現されます。
  3. コードの書き換え (timefileinfo): timefileinfo関数は、実際にコードの書き換えを行います。この関数もASTを走査し、検出された古いAPIの使用箇所を新しいAPIに変換します。
    • ポインタの削除: *os.FileInfo*time.Timeのような型宣言を、それぞれos.FileInfotime.Timeに書き換えます。
    • timeパッケージの関数呼び出しの変換:
      • time.Seconds(), time.Nanoseconds(), time.LocalTime()time.Now()に変換されます。
      • time.UTC()time.Now().UTC()に変換されます。
      • time.SecondsToLocalTime(sec)time.SecondsToUTC(sec)time.Unix(sec, 0)に変換され、必要に応じて.UTC()が追加されます。
      • time.NanosecondsToLocalTime(nsec)time.NanosecondsToUTC(nsec)time.Unix(0, nsec)に変換され、必要に応じて.UTC()が追加されます。
    • time.Timeメソッドの変換:
      • t.Seconds()t.Unix()に変換されます。
      • t.Nanoseconds()t.UnixNano()に変換されます。
    • os.FileInfoのフィールド/メソッドの変換:
      • st.IsDirectory()st.IsDir()に変換されます。
      • st.IsRegular()!st.IsDir()に変換されます。
      • st.Name, st.Size, st.Modeといったフィールド参照は、それぞれst.Name(), st.Size(), st.Mode()といったメソッド呼び出しに変換されます。
      • st.Mtime_nsst.ModTime()に変換されます。
    • 時刻の減算: t2 - t1のような時刻の直接減算は、t2.Sub(t1)のようなメソッド呼び出しに変換されます。

この修正は、ASTのノードを直接操作することで行われます。例えば、*ast.StarExpr(ポインタ型を表すASTノード)をその基底型(star.X)に置き換えたり、ast.SelectorExprobject.fieldpackage.functionのような選択式)のSel.Nameを変更したり、ast.CallExpr(関数呼び出し)のFunArgsを変更したりします。

timefileinfoTypeConfigは、gofixの型チェックメカニズムで使用される設定で、古いAPIのシグネチャを定義しています。これにより、gofixはコード内の式がどの型を持つかを推論し、適切な修正を適用できます。

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

このコミットの主要な変更は、以下の2つの新しいファイルの追加と、既存のMakefileの更新です。

  1. src/cmd/gofix/Makefile:

    --- a/src/cmd/gofix/Makefile
    +++ b/src/cmd/gofix/Makefile
    @@ -33,6 +33,7 @@ GOFILES=\
     	sortslice.go\
     	stringssplit.go\
     	template.go\
    +\ttimefileinfo.go\
     	typecheck.go\
     	url.go\
    

    この変更は、新しく追加されるtimefileinfo.gogofixツールのビルドプロセスに含まれるようにします。

  2. src/cmd/gofix/timefileinfo.go: このファイルは、time+fileinfo修正の主要なロジックを含んでいます。

    • init()関数での修正の登録。
    • timefileinfoFix構造体での修正のメタデータ定義。
    • timefileinfoTypeConfig変数での古いAPIの型情報定義。
    • timefileinfoIsOld関数での古いAPI使用箇所の検出ロジック。
    • timefileinfo関数でのASTを操作してコードを書き換える主要なロジック。
  3. src/cmd/gofix/timefileinfo_test.go: このファイルは、time+fileinfo修正のテストケースを含んでいます。

    • timefileinfoTests変数に、入力コード(In)と期待される出力コード(Out)のペアが定義されています。これにより、修正が正しく適用されることを検証します。
    • 例えば、os.Statの結果のst.Namest.Name()に、st.IsDirectory()st.IsDir()に、time.Seconds()time.Now()に、t2 - t1t2.Sub(t1)に変換される様子が示されています。

コアとなるコードの解説

src/cmd/gofix/timefileinfo.go

このファイルは、GoのAST操作の典型的な例を示しています。

  • timefileinfoIsOld関数: この関数は、walkBeforeAfterというヘルパー関数を使ってASTを深さ優先で走査します。beforeクロージャ内で、各ノードが古いAPIのパターンに一致するかどうかをチェックします。 例えば、*ast.StarExpr(ポインタ型)の場合、os.FileInfotime.Timeへのポインタであればold = trueを設定します。 ast.SelectorExprobj.Sel形式の式)の場合、timeパッケージの古い関数名やos.FileInfoの古いフィールド名(Mtime_ns, IsDirectoryなど)をチェックします。 ast.CallExpr(関数呼び出し)の場合、time.UTC()のような古い関数呼び出しを検出します。 typeofマップは、gofixの型チェックメカニズムによって提供され、各式の型情報を含んでいます。これにより、os.FileInfoのインスタンスに対する操作かどうかを判断できます。

  • timefileinfo関数: この関数もASTを走査しますが、こちらはノードを書き換えることを目的としています。 walk関数は、ASTの各ノードに対して匿名関数を実行します。この匿名関数内で、*p = newExprのようにポインタを介してASTノードを直接書き換えることで、コードの変換を行います。 例えば、*os.FileInfoos.FileInfoに変換する部分は、*p = star.Xというシンプルな代入で行われます。これは、star*os.FileInfoを表すast.StarExprであり、star.Xが基底型であるos.FileInfoを表すためです。 time.UTC()からtime.Now().UTC()への変換は、新しいast.CallExprast.SelectorExprを構築して*pに代入することで実現されます。これは、単なる名前の変更だけでなく、式の構造自体を変更する複雑な変換の例です。 t2 - t1からt2.Sub(t1)への変換も同様に、ast.BinaryExprast.CallExprに置き換えることで行われます。

これらのAST操作は、Go言語のコンパイラが内部的に使用するのと同じメカニズムであり、非常に強力で柔軟なコード変換を可能にします。

src/cmd/gofix/timefileinfo_test.go

このテストファイルは、gofixの修正が意図通りに動作するかを確認するためのものです。testCase構造体は、修正前のコード(In)と修正後の期待されるコード(Out)を定義しています。addTestCases関数は、これらのテストケースをgofixのテストフレームワークに登録します。

テストケースは、os.FileInfoのフィールドがメソッドに変わる例、timeパッケージの関数がtime.Now()time.Unix()に変わる例、時刻の減算がSubメソッドに変わる例など、多岐にわたります。これらのテストは、修正の正確性と網羅性を保証するために不可欠です。

関連リンク

  • Go言語の公式ドキュメント: Go言語のtimeパッケージやosパッケージの最新のドキュメントは、現在のAPIを理解する上で役立ちます。
  • gofixツールの概要: gofixの目的と使い方に関する情報は、Goの公式ブログやドキュメントで確認できます。

参考にした情報源リンク

  • 元のコードレビュー:
    • http://codereview.appspot.com/5392041 (Go: time: new Time API)
    • http://codereview.appspot.com/5416060 (Go: os: new FileInfo API)
    • https://golang.org/cl/5450050 (gofix: add time+fileinfo fix) これらのコードレビューは、API変更の具体的な議論と、それに対応するgofixの修正の背景を理解する上で非常に重要です。特に、timeパッケージとos.FileInfoインターフェースの変更に関する議論は、なぜこれらの変更が必要とされたのか、どのような設計上の考慮があったのかを深く理解するのに役立ちます。
  • Go言語のリリースノート: Go 1.0のリリースノートや、それ以前のGoのバージョンに関する変更履歴は、APIの進化のタイムラインを理解するのに役立ちます。
  • Go言語のASTパッケージ: go/astパッケージのドキュメントは、ASTの構造と操作方法を理解する上で不可欠です。
  • Go言語のトークンパッケージ: go/tokenパッケージのドキュメントは、Go言語の構文要素を表すトークンについて理解するのに役立ちます。
  • Go言語のブログ記事: 過去のGo言語のブログ記事には、API変更やツールの開発に関する詳細な情報が含まれている場合があります。