[インデックス 13870] ファイルの概要
このコミットは、Go言語のcmd/api
ツールにおけるバグ修正です。具体的には、func(x, y, z int)
のように、複数の引数や戻り値が同じ型を持つ場合に、それらの関数シグネチャの出力が正しく行われない問題を修正しています。この修正により、cmd/api
が生成するAPIシグネチャが、Goの言語仕様に沿って正確に表現されるようになりました。
コミット
commit 1ad5f87635ee35b8b0053a1a4d2a05d0e892e4d0
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Wed Sep 19 07:04:12 2012 +0900
cmd/api: fix signatures like func(x, y, z int)
Fixes writing of function parameter, result lists which
consist of multiple named or unnamed items with same type.
Fixes #4011.
R=golang-dev, bsiegert, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/6475062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1ad5f87635ee35b8b0053a1a4d2a05d0e892e4d0
元コミット内容
cmd/api: fix signatures like func(x, y, z int)
Fixes writing of function parameter, result lists which
consist of multiple named or unnamed items with same type.
Fixes #4011.
R=golang-dev, bsiegert, bradfitz, rsc
CC=golang-dev
https://golang.org/cl/6475062
変更の背景
この変更は、Go言語のcmd/api
ツールが、特定の形式の関数シグネチャを正しく出力できないというバグ(Issue 4011)を修正するために行われました。具体的には、func(x, y, z int)
のように、複数の引数や戻り値が同じ型を持つ場合に、cmd/api
が生成するAPIシグネチャの文字列表現が不正確になる問題がありました。
Go言語では、関数パラメータや戻り値のリストにおいて、同じ型の複数の変数名をまとめて宣言することができます。例えば、func(x, y int)
はfunc(x int, y int)
と同じ意味です。cmd/api
ツールは、Goの標準ライブラリのAPIシグネチャを抽出・比較するために使用される重要なツールであり、その出力の正確性はGoエコシステム全体の整合性を保つ上で不可欠です。このバグは、APIの変更を正確に追跡する能力に影響を与える可能性があったため、修正が必要とされました。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連ツールの概念を理解しておく必要があります。
-
Go言語の
ast
(Abstract Syntax Tree) パッケージ:go/ast
パッケージは、Goのソースコードを抽象構文木(AST)として表現するためのデータ構造と関数を提供します。コンパイラ、リンタ、コード分析ツールなど、Goのコードをプログラム的に操作する多くのツールで利用されます。ast.FuncDecl
: 関数宣言(func
キーワードで始まる)を表すASTノードです。ast.FuncType
: 関数の型(パラメータと戻り値のリストを含む)を表すASTノードです。ast.Field
: 関数パラメータや構造体フィールドなど、名前と型を持つ単一の項目を表すASTノードです。Names
フィールドは、その項目が持つ名前のリスト(例:x, y, z int
の場合、x
,y
,z
)を含みます。Type
フィールドはその型を表します。
-
Go言語の関数シグネチャ: Goの関数シグネチャは、関数のパラメータと戻り値の型を定義します。
- パラメータリスト:
func(param1 type1, param2 type2)
のように記述されます。同じ型の複数のパラメータはfunc(x, y int)
のようにまとめて宣言できます。 - 戻り値リスト:
func() (result1 type1, result2 type2)
のように記述されます。戻り値もパラメータと同様に、func() (x, y int)
のようにまとめて宣言できます。戻り値に名前を付けない場合は、型のみを記述します(例:func() (int, error)
)。
- パラメータリスト:
-
cmd/api
ツール: Goのソースコードリポジトリに含まれる内部ツールの一つで、Goの標準ライブラリのAPIシグネチャを抽出・比較するために使用されます。これは、Goの互換性保証(Go 1互換性保証など)を維持するために非常に重要です。このツールは、GoのASTを解析し、APIの公開された部分のシグネチャをテキスト形式で出力します。この出力は、APIの変更を検出するために使用されます。
技術的詳細
このバグは、cmd/api
ツール内のfuncSigString
関数に存在していました。この関数は、ast.FuncType
構造体を受け取り、その関数シグネチャを文字列として整形して出力する役割を担っています。
問題の核心は、ast.Field
構造体のNames
フィールドの扱いにありました。GoのASTでは、func(x, y, z int)
のような宣言は、ast.Field
のリストとして表現され、各ast.Field
はNames
フィールドに複数の識別子(x
, y
, z
)を持ち、Type
フィールドに共通の型(int
)を持ちます。
しかし、funcSigString
関数は、このNames
フィールドの複数の識別子を適切に展開せず、単一のast.Field
を単一の型としてしか扱っていませんでした。その結果、func(x, y, z int)
のようなシグネチャが、func(int)
のように誤って出力されてしまうことがありました。同様に、戻り値のリストでもこの問題が発生していました。
修正は、以下の2つの主要な変更によって行われました。
-
writeField
ヘルパー関数の導入: この新しいヘルパー関数は、ast.Field
を受け取り、そのNames
フィールドに複数の識別子が含まれている場合に、それぞれの識別子に対して型を繰り返し出力するように修正されました。例えば、f.Names
が[x, y, z]
でf.Type
がint
の場合、int, int, int
と出力されるようになります。これにより、func(x, y, z int)
がfunc(int, int, int)
として正しく表現されるようになります。 -
戻り値の数の正確な計算: 戻り値のリストの処理において、
len(ft.Results.List)
だけでは、func() (x, y int)
のような場合に実際の戻り値の数(この場合は2つ)を正確に把握できませんでした。これは、ft.Results.List
の各ast.Field
が複数の名前を持つ可能性があるためです。修正では、nr
変数を導入し、各ast.Field
のNames
の数を合計することで、実際の戻り値の総数を正確に計算するように変更されました。これにより、戻り値が複数ある場合に括弧で囲むべきかどうかの判断が正しく行われるようになりました。
これらの変更により、cmd/api
はGoの関数シグネチャを、名前付き・名前なし、単一型・複数型にかかわらず、正確に文字列として表現できるようになりました。
コアとなるコードの変更箇所
変更は主にsrc/cmd/api/goapi.go
ファイル内のfuncSigString
関数に集中しています。
--- a/src/cmd/api/goapi.go
+++ b/src/cmd/api/goapi.go
@@ -1017,18 +1017,38 @@ func (w *Walker) walkFuncDecl(f *ast.FuncDecl) {
func (w *Walker) funcSigString(ft *ast.FuncType) string {
var b bytes.Buffer
+ writeField := func(b *bytes.Buffer, f *ast.Field) {
+ if n := len(f.Names); n > 1 {
+ for i := 0; i < n; i++ {
+ if i > 0 {
+ b.WriteString(", ")
+ }
+ b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ }
+ } else {
+ b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ }
+ }
b.WriteByte('(')
if ft.Params != nil {
for i, f := range ft.Params.List {
if i > 0 {
b.WriteString(", ")
}
- b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ writeField(&b, f)
}
}
b.WriteByte(')')
if ft.Results != nil {
- if nr := len(ft.Results.List); nr > 0 {
+ nr := 0
+ for _, f := range ft.Results.List {
+ if n := len(f.Names); n > 1 {
+ nr += n
+ } else {
+ nr++
+ }
+ }
+ if nr > 0 {
b.WriteByte(' ')
if nr > 1 {
b.WriteByte('(')
@@ -1037,7 +1057,7 @@ func (w *Walker) funcSigString(ft *ast.FuncType) string {
if i > 0 {
b.WriteString(", ")
}
- b.WriteString(w.nodeString(w.namelessType(f.Type)))
+ writeField(&b, f)
}
if nr > 1 {
b.WriteByte(')')
また、この修正を検証するための新しいテストケースが追加されています。
src/cmd/api/testdata/src/pkg/p3/golden.txt
src/cmd/api/testdata/src/pkg/p3/p3.go
p3.go
には、以下のような関数が含まれており、これらが正しくシグネチャとして抽出されることをgolden.txt
で確認しています。
package p3
type ThirdBase struct{}
func (tb *ThirdBase) GoodPlayer() (i, j, k int)
func BadHop(i, j, k int) (l, m bool, n, o *ThirdBase, err error)
コアとなるコードの解説
-
writeField
ヘルパー関数:funcSigString
関数内にwriteField
というローカル関数が定義されました。- この関数は
*ast.Field
を受け取ります。 if n := len(f.Names); n > 1
の条件で、フィールドに複数の名前が定義されているか(例:x, y, z int
)をチェックします。- もし複数の名前がある場合、
for
ループを使って、それぞれの名前に対してフィールドの型(w.nodeString(w.namelessType(f.Type))
)をカンマで区切ってバッファに書き込みます。これにより、x, y, z int
がint, int, int
として出力されます。 - 名前が1つまたはない場合(通常のパラメータや戻り値)、単にその型をバッファに書き込みます。
- この関数は
-
パラメータリストの処理:
ft.Params.List
をループする箇所で、以前は直接w.nodeString(w.namelessType(f.Type))
を呼び出していましたが、これがwriteField(&b, f)
に置き換えられました。これにより、パラメータリストの出力がwriteField
のロジックに従って正確に行われるようになりました。 -
戻り値の数の正確な計算:
ft.Results != nil
のブロック内で、nr
変数の計算方法が変更されました。- 以前は
nr := len(ft.Results.List)
としていましたが、これはast.Field
のリストの長さであり、実際の戻り値の総数ではありませんでした。 - 新しいコードでは、
for _, f := range ft.Results.List
で各ast.Field
をループし、len(f.Names)
(そのフィールドが持つ名前の数)をnr
に加算しています。これにより、func() (l, m bool, n, o *ThirdBase, err error)
のようなシグネチャの場合、l, m
で2つ、n, o
で2つ、err
で1つ、合計5つの戻り値があることを正確にnr
が示すようになります。 - この正確な
nr
の値に基づいて、戻り値が複数ある場合に()
で囲むべきかどうかの判断(if nr > 1
)が正しく行われます。
- 以前は
-
戻り値リストの処理: 戻り値リストの各フィールドをループする箇所でも、パラメータリストと同様に
writeField(&b, f)
が使用されるようになりました。これにより、戻り値の出力も正確に行われます。
これらの変更により、cmd/api
はGoの関数シグネチャを、Goの言語仕様に厳密に従って、より正確に文字列として表現できるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/1ad5f87635ee35b8b0053a1a4d2a05d0e892e4d0
- Go CL (Code Review): https://golang.org/cl/6475062
- 関連するIssue: https://golang.org/issue/4011
参考にした情報源リンク
- Go言語の
go/ast
パッケージのドキュメント - Go言語の関数シグネチャに関する公式ドキュメントまたはチュートリアル
- Go言語のIssueトラッカー (Issue 4011)
- Go言語のコードレビューシステム (CL 6475062)