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

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

このコミットは、Go言語のcmd/apiツールにおける型スクラブ(type scrubbing)の不具合を修正するものです。具体的には、関数型のパラメータ名が適切に削除されていなかった点と、a, b stringのような複数のパラメータ宣言が単一の型として扱われていた点を修正しています。これにより、Go APIの定義ファイルであるgo1.txtの生成がより正確になります。

コミット

commit 93d92d51ddccbbc689f4b9adeded4f310ba0c363
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Jan 22 14:29:38 2013 -0800

    cmd/api: fix type scrubbing
    
    It wasn't removing names from func parameters for func types,
    and it was handling "a, b string" as "string", not "string, string".
    
    Fixes #4688
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7181051

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

https://github.com/golang/go/commit/93d92d51ddccbbc689f4b9adeded4f310ba0c363

元コミット内容

cmd/api: fix type scrubbing

関数型のパラメータ名が関数型から削除されておらず、また、a, b stringのような宣言がstringとして扱われ、string, stringとして扱われていなかった問題を修正します。

Fixes #4688

変更の背景

Go言語の標準ライブラリは、その安定性と互換性が非常に重視されています。go1.txtファイルは、Go 1の互換性保証の対象となる公開APIのリストを定義する重要な役割を担っています。このファイルは、cmd/apiツールによってGoのソースコードから自動生成されます。

このコミット以前のcmd/apiツールには、以下の2つの主要な不具合が存在していました。

  1. 関数型のパラメータ名の不適切な扱い: Goの関数型(例: func(name string) error)において、パラメータ名(この場合はname)はAPIのシグネチャの一部とは見なされません。APIの互換性を保証する上で重要なのは、パラメータの型のみです。しかし、cmd/apiツールがgo1.txtを生成する際に、これらのパラメータ名が適切に「スクラブ」(除去)されずに残ってしまう問題がありました。これにより、go1.txtに余分な情報が含まれ、APIの定義が冗長になったり、将来的にパラメータ名が変更された場合に不必要な差分が生じる可能性がありました。

  2. 複数パラメータの一括宣言の誤った解釈: Goでは、func(a, b string)のように、同じ型の複数のパラメータをカンマ区切りで一括して宣言することができます。この場合、APIのシグネチャとしてはfunc(string, string)と解釈されるべきです。しかし、cmd/apiツールはこれを誤ってfunc(string)のように単一の型として扱っていました。これは、APIのシグネチャを正確に表現できていないことを意味し、go1.txtの正確性を損なう重大な問題でした。

これらの不具合は、GoのAPI定義の正確性に影響を与え、Go 1の互換性保証の基盤となるgo1.txtの信頼性を低下させる可能性がありました。そのため、これらの問題を修正し、cmd/apiツールがより正確なAPI定義を生成できるようにすることが喫緊の課題でした。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とツールに関する知識が必要です。

  1. Go言語のAST (Abstract Syntax Tree): Goコンパイラは、Goのソースコードを解析して抽象構文木(AST)を構築します。ASTは、プログラムの構造を木構造で表現したものです。go/astパッケージは、このASTを操作するための型と関数を提供します。cmd/apiツールは、このASTを走査してAPI情報を抽出します。

  2. go/tokenパッケージ: Goのソースコード内のトークン(キーワード、識別子、演算子など)の位置情報(ファイル名、行番号、列番号)を扱うためのパッケージです。ASTノードは、通常、ソースコード内の対応する位置への参照を含んでいます。

  3. go/typesパッケージ (関連): このコミットでは直接変更されていませんが、Goの型システムをプログラム的に扱うためのパッケージです。cmd/apiは、ASTから抽出した情報に基づいて、Goの型システムにおけるAPIのシグネチャを正確に表現する必要があります。

  4. reflectパッケージ: Goの実行時リフレクション機能を提供するパッケージです。プログラムの実行中に型情報を調べたり、値を操作したりすることができます。go1.txtには、reflect.Valueのようなリフレクション関連の型も含まれており、cmd/apiはこれらの型も適切に処理する必要があります。

  5. cmd/apiツール: Goの標準ライブラリの公開APIを抽出し、go1.txtという形式で出力するためのツールです。このツールは、Goのソースコードを解析し、エクスポートされた型、関数、メソッド、変数などのシグネチャを収集します。go1.txtは、Go 1の互換性保証の対象となるAPIの「スナップショット」として機能します。

  6. 型スクラブ (Type Scrubbing): API定義を生成する際に、APIの互換性には影響しない詳細な情報(例: 関数パラメータ名)を除去するプロセスを指します。これにより、APIの定義が簡潔になり、不必要な変更が検出されるのを防ぎます。

  7. go1.txt: Go 1のリリース以降、Goの標準ライブラリの公開APIの定義を記録しているテキストファイルです。このファイルに記載されているAPIは、Go 1の互換性保証の対象となり、将来のGoのバージョンでも互換性が維持されることが約束されています。cmd/apiツールによって生成・検証されます。

技術的詳細

このコミットの技術的詳細を掘り下げると、cmd/apiツールがGoのASTをどのように走査し、型情報を「スクラブ」しているかが見えてきます。

問題の核心は、src/cmd/api/goapi.go内のnamelessType関数とnamelessFieldList関数にありました。これらの関数は、APIのシグネチャからパラメータ名などの詳細を除去し、型情報のみを抽出することを目的としています。

  1. namelessType関数の修正: 以前のnamelessType関数は、ast.FuncType(関数型)を処理する際に、そのパラメータリスト(ast.FieldList)を適切に「スクラブ」していませんでした。具体的には、w.nodeString(ts.Type)という呼び出しが、パラメータ名を含む元の型情報をそのまま文字列化していました。 修正後、w.namelessType(ts.Type)が呼び出されるようになりました。これにより、namelessType関数が再帰的に呼び出され、関数型の内部にあるパラメータリストもnamelessFieldListによって処理されるようになります。

  2. namelessFieldList関数の修正: この関数は、ast.FieldList(フィールドリスト、関数パラメータリストなど)からフィールド名を除去することを担当しています。 以前の実装では、a, b stringのような複数のパラメータが一括で宣言されている場合、f.Namesの長さが1より大きいにもかかわらず、w.namelessField(f)が一度しか呼び出されませんでした。これにより、stringが1つだけ出力され、string, stringとなるべきところが誤っていました。 修正後、f.Namesの長さをrepeats変数に格納し、その回数だけw.namelessField(f)をループで呼び出すようになりました。これにより、a, b stringstring, stringとして正しく処理されるようになります。例えば、x, y intという宣言は、int, intとしてAPI定義に反映されます。

これらの変更により、cmd/apiツールは、GoのASTからAPIシグネチャを抽出する際に、関数パラメータ名を除去し、複数パラメータの一括宣言を正しく展開できるようになりました。結果として、go1.txtに生成されるAPI定義がより正確で、互換性保証の目的に合致するようになりました。

api/go1.txtの変更は、これらの修正が実際にAPI定義にどのように影響したかを示しています。例えば、pkg go/ast, type FieldFilter func(name string, value reflect.Value) boolpkg go/ast, type FieldFilter func(string, reflect.Value) boolに変更されているのは、パラメータ名nameが除去された結果です。また、pkg testing, func Main(func(string) (bool, error), []InternalTest, []InternalBenchmark, []InternalExample)のような変更は、関数型のパラメータが正しく展開されたことを示しています。

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

このコミットにおけるコアとなるコードの変更は、主にsrc/cmd/api/goapi.goファイルに集中しています。

  1. src/cmd/api/goapi.goの変更:

    • func (w *Walker) walkTypeSpec(ts *ast.TypeSpec): ts.Typeast.InterfaceTypeではない場合の処理で、emitFeatureに渡す文字列の生成方法が変更されました。 変更前: w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type))) 変更後: w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(w.namelessType(ts.Type)))) これにより、型定義の文字列化の際に、namelessType関数を通してパラメータ名が除去されるようになりました。

    • func (w *Walker) namelessFieldList(fl *ast.FieldList): フィールドリスト(関数のパラメータリストなど)を処理するロジックが変更されました。 変更前:

      if fl != nil {
          for _, f := range fl.List {
              fl2.List = append(fl2.List, w.namelessField(f))
          }
      }
      

      変更後:

      if fl != nil {
          for _, f := range fl.List {
              repeats := 1
              if len(f.Names) > 1 {
                  repeats = len(f.Names)
              }
              for i := 0; i < repeats; i++ {
                  fl2.List = append(fl2.List, w.namelessField(f))
              }
          }
      }
      

      この変更により、x, y intのように複数の名前が同じ型を持つフィールドとして宣言されている場合に、その名前の数だけ型が繰り返されるように修正されました。

  2. api/go1.txtの変更: cmd/apiツールの修正によって、go1.txtの内容が更新されました。これは、修正が正しく機能していることを示すものです。 例:

    • pkg go/ast, type FieldFilter func(name string, value reflect.Value) boolpkg go/ast, type FieldFilter func(string, reflect.Value) bool
    • pkg go/build, type Context struct, HasSubdir func(string) (string, bool)pkg go/build, type Context struct, HasSubdir func(string, string) (string, bool)
    • pkg testing, func Main(func(string) (bool, error), []InternalTest, []InternalBenchmark, []InternalExample)pkg testing, func Main(func(string, string) (bool, error), []InternalTest, []InternalBenchmark, []InternalExample)
  3. src/cmd/api/testdata/src/pkg/p1/golden.txtsrc/cmd/api/testdata/src/pkg/p1/p1.goの変更: テストデータが更新され、新しいFuncTypePlainFuncが追加されました。これは、修正が意図通りに動作することを確認するためのテストケースの追加です。golden.txtは、cmd/apiが生成する期待される出力であり、p1.goはテスト対象のソースコードです。

コアとなるコードの解説

このコミットの核心は、src/cmd/api/goapi.go内のnamelessFieldList関数のロジック変更にあります。

func (w *Walker) namelessFieldList(fl *ast.FieldList) *ast.FieldList {
	fl2 := &ast.FieldList{}
	if fl != nil {
		for _, f := range fl.List {
			repeats := 1
			if len(f.Names) > 1 {
				repeats = len(f.Names)
			}
			for i := 0; i < repeats; i++ {
				fl2.List = append(fl2.List, w.namelessField(f))
			}
		}
	}
	return fl2
}

この関数は、ast.FieldList(GoのASTにおけるフィールドのリスト、例えば関数の引数や構造体のフィールド)を受け取り、その中のフィールド名を除去した新しいast.FieldListを返します。

変更前のコードでは、f.Names(フィールド名を表す*ast.Identのスライス)の長さが1より大きい場合でも、w.namelessField(f)が一度しか呼び出されませんでした。例えば、x, y intという宣言があった場合、f.Namesにはxyの2つの識別子が含まれますが、namelessFieldは一度しか実行されず、結果としてintが1つだけ出力されていました。

修正後のコードでは、repeatsという変数が導入されました。

  • もしf.Namesの長さが1より大きければ(つまり、x, y intのように複数の名前が宣言されていれば)、repeatsはその名前の数(この場合は2)に設定されます。
  • そうでなければ、repeatsは1のままです。

そして、for i := 0; i < repeats; i++というループが追加されました。このループは、repeatsの回数だけw.namelessField(f)を呼び出し、その結果をfl2.Listに追加します。

これにより、x, y intという宣言は、namelessFieldが2回呼び出され、結果としてint, intという正しい型リストが生成されるようになりました。これは、GoのAPI定義において、複数のパラメータが同じ型を持つ場合に、それぞれのパラメータの型が明示的に列挙されるべきという仕様に合致するものです。

また、walkTypeSpec関数におけるw.nodeString(w.namelessType(ts.Type))への変更は、型定義全体を文字列化する際に、このnamelessFieldListによって処理された「名前のない」型情報が確実に反映されるようにするためのものです。これにより、関数型のパラメータ名が最終的なgo1.txtの出力から除去されることが保証されます。

これらの変更は、GoのAPI定義の正確性を高め、go1.txtがGo 1の互換性保証の目的をより適切に果たすための重要な修正です。

関連リンク

参考にした情報源リンク