[インデックス 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つの主要な不具合が存在していました。
-
関数型のパラメータ名の不適切な扱い: Goの関数型(例:
func(name string) error
)において、パラメータ名(この場合はname
)はAPIのシグネチャの一部とは見なされません。APIの互換性を保証する上で重要なのは、パラメータの型のみです。しかし、cmd/api
ツールがgo1.txt
を生成する際に、これらのパラメータ名が適切に「スクラブ」(除去)されずに残ってしまう問題がありました。これにより、go1.txt
に余分な情報が含まれ、APIの定義が冗長になったり、将来的にパラメータ名が変更された場合に不必要な差分が生じる可能性がありました。 -
複数パラメータの一括宣言の誤った解釈: 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言語の概念とツールに関する知識が必要です。
-
Go言語のAST (Abstract Syntax Tree): Goコンパイラは、Goのソースコードを解析して抽象構文木(AST)を構築します。ASTは、プログラムの構造を木構造で表現したものです。
go/ast
パッケージは、このASTを操作するための型と関数を提供します。cmd/api
ツールは、このASTを走査してAPI情報を抽出します。 -
go/token
パッケージ: Goのソースコード内のトークン(キーワード、識別子、演算子など)の位置情報(ファイル名、行番号、列番号)を扱うためのパッケージです。ASTノードは、通常、ソースコード内の対応する位置への参照を含んでいます。 -
go/types
パッケージ (関連): このコミットでは直接変更されていませんが、Goの型システムをプログラム的に扱うためのパッケージです。cmd/api
は、ASTから抽出した情報に基づいて、Goの型システムにおけるAPIのシグネチャを正確に表現する必要があります。 -
reflect
パッケージ: Goの実行時リフレクション機能を提供するパッケージです。プログラムの実行中に型情報を調べたり、値を操作したりすることができます。go1.txt
には、reflect.Value
のようなリフレクション関連の型も含まれており、cmd/api
はこれらの型も適切に処理する必要があります。 -
cmd/api
ツール: Goの標準ライブラリの公開APIを抽出し、go1.txt
という形式で出力するためのツールです。このツールは、Goのソースコードを解析し、エクスポートされた型、関数、メソッド、変数などのシグネチャを収集します。go1.txt
は、Go 1の互換性保証の対象となるAPIの「スナップショット」として機能します。 -
型スクラブ (Type Scrubbing): API定義を生成する際に、APIの互換性には影響しない詳細な情報(例: 関数パラメータ名)を除去するプロセスを指します。これにより、APIの定義が簡潔になり、不必要な変更が検出されるのを防ぎます。
-
go1.txt
: Go 1のリリース以降、Goの標準ライブラリの公開APIの定義を記録しているテキストファイルです。このファイルに記載されているAPIは、Go 1の互換性保証の対象となり、将来のGoのバージョンでも互換性が維持されることが約束されています。cmd/api
ツールによって生成・検証されます。
技術的詳細
このコミットの技術的詳細を掘り下げると、cmd/api
ツールがGoのASTをどのように走査し、型情報を「スクラブ」しているかが見えてきます。
問題の核心は、src/cmd/api/goapi.go
内のnamelessType
関数とnamelessFieldList
関数にありました。これらの関数は、APIのシグネチャからパラメータ名などの詳細を除去し、型情報のみを抽出することを目的としています。
-
namelessType
関数の修正: 以前のnamelessType
関数は、ast.FuncType
(関数型)を処理する際に、そのパラメータリスト(ast.FieldList
)を適切に「スクラブ」していませんでした。具体的には、w.nodeString(ts.Type)
という呼び出しが、パラメータ名を含む元の型情報をそのまま文字列化していました。 修正後、w.namelessType(ts.Type)
が呼び出されるようになりました。これにより、namelessType
関数が再帰的に呼び出され、関数型の内部にあるパラメータリストもnamelessFieldList
によって処理されるようになります。 -
namelessFieldList
関数の修正: この関数は、ast.FieldList
(フィールドリスト、関数パラメータリストなど)からフィールド名を除去することを担当しています。 以前の実装では、a, b string
のような複数のパラメータが一括で宣言されている場合、f.Names
の長さが1より大きいにもかかわらず、w.namelessField(f)
が一度しか呼び出されませんでした。これにより、string
が1つだけ出力され、string, string
となるべきところが誤っていました。 修正後、f.Names
の長さをrepeats
変数に格納し、その回数だけw.namelessField(f)
をループで呼び出すようになりました。これにより、a, b string
はstring, 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) bool
がpkg 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
ファイルに集中しています。
-
src/cmd/api/goapi.go
の変更:-
func (w *Walker) walkTypeSpec(ts *ast.TypeSpec)
内:ts.Type
がast.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
のように複数の名前が同じ型を持つフィールドとして宣言されている場合に、その名前の数だけ型が繰り返されるように修正されました。
-
-
api/go1.txt
の変更:cmd/api
ツールの修正によって、go1.txt
の内容が更新されました。これは、修正が正しく機能していることを示すものです。 例:pkg go/ast, type FieldFilter func(name string, value reflect.Value) bool
↓pkg 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)
-
src/cmd/api/testdata/src/pkg/p1/golden.txt
とsrc/cmd/api/testdata/src/pkg/p1/p1.go
の変更: テストデータが更新され、新しいFuncType
とPlainFunc
が追加されました。これは、修正が意図通りに動作することを確認するためのテストケースの追加です。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
にはx
とy
の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の互換性保証の目的をより適切に果たすための重要な修正です。
関連リンク
- Go Issue #4688:
cmd/api: fix type scrubbing
- https://github.com/golang/go/issues/4688 - Gerrit Change-Id:
I7181051
- https://golang.org/cl/7181051 (このコミットの元のコードレビューページ)
参考にした情報源リンク
- Go言語の公式ドキュメント (AST, reflect, go/tokenパッケージなど)
- Go言語のソースコード (
src/cmd/api/
,api/go1.txt
など) - Go言語のIssueトラッカー (GitHub Issues)
- Go言語のコードレビューシステム (Gerrit)
- Go 1 Compatibility Guarantee: https://go.dev/doc/go1compat
- Go ASTパッケージのドキュメント: https://pkg.go.dev/go/ast
- Go reflectパッケージのドキュメント: https://pkg.go.dev/reflect
- Go tokenパッケージのドキュメント: https://pkg.go.dev/go/token