#73395active
go/ast: add FieldList.Fields() iter.Seq2\\[\\*Ident, \\*Field]
新規提案
要約
AIによる要約であり、誤りを含む場合があります。
概要
go/ast の FieldList(関数の引数・戻り値、構造体のフィールド、型パラメータなどを表すノード)に、宣言されている変数を1つずつ列挙するイテレータメソッド Fields() を追加する提案です。名前付き・名前なしのどちらのフィールドも、同じループで同じように扱えるようにすることが狙いです。
ステータス変更
(新規) → active
2026年11月12日、@aclements が proposal review group を代表して active カラムに追加しました。今後は週次の proposal review meeting で審議されます。提案者は @adonovan で、x/tools 内で同じ処理が18箇所書かれていたこと、手書きのループが原因のバグを実際に修正したことを根拠にしています。x/tools の内部パッケージには同じ機能(astutil.FlatFields)がすでに実装されて使われており、それを標準ライブラリに移す提案です。
技術的背景
現状の問題点
ast.FieldList は List []*Field を持ち、各 Field は Names []*Ident と Type を持ちます。ここで次の2点が厄介です。
func(a, b int)では、1つのFieldに2つの名前が入るfunc(int, string)のような名前なしパラメータではNamesが空になる
そのため、「i番目のパラメータ」を求めるコードは名前の有無で場合分けが必要になり、書き間違えやすくなります。実例が CL 862164(x/tools のtestsanalyzer の修正)です。fuzz target のパラメータ位置を数えるループがlen(field.Names)を使っていたため、名前なしパラメータが1つも数えられませんでした。その結果func(*testing.T, complex64)のような関数では該当パラメータが見つからず、診断が関数リテラル全体の位置に出ていました。
提案された解決策
package ast
// Fields returns the sequence of fields declared by a FieldList.
// Each element is represented by a (name, field) pair, where name provides
// the name (if any), and the field provides the type and position.
// The length of the sequence is [FieldList.NumFields].
func (list *FieldList) Fields() iter.Seq2[*Ident, *Field]
- 変数1つにつき (name, field) のペアを1回返します。
- 名前なしフィールドでは
nameがnilになります(x/tools のFlatFieldsと同じ仕様)。 - 返す要素の数は既存の
FieldList.NumFields()と一致します。
これによって何ができるようになるか
- 静的解析ツール(analyzer): 「i番目の引数」を
go/typesのSignature.Params().At(i)と正しく対応づけられます。 - コード生成・リファクタリングツール: 関数シグネチャの引数や戻り値を書き換えるとき、名前付き・名前なしを同じコードで扱えます。
- 構造体フィールドの走査:
a, b intのようにまとめて宣言されたフィールドも、1フィールドずつ取り出せます。 - ジェネリクスの型パラメータ(
[K, V any])の列挙にも使えます。
主な利用者は、go/astを使ってリンターやコード生成ツールなどを書く上級者層です。
コード例
// Before: 名前なしパラメータを数え忘れやすい
i := 0
for _, field := range fn.Type.Params.List {
n := len(field.Names)
if n == 0 {
n = 1 // これを忘れると名前なしパラメータが数えられない
}
for j := 0; j < n; j++ {
var name *ast.Ident
if len(field.Names) > 0 {
name = field.Names[j]
}
use(i, name, field.Type)
i++
}
}
// After: 変数ごとに1回ずつ返ってくる
i := 0
for name, field := range fn.Type.Params.Fields() {
use(i, name, field.Type) // name は名前なしのとき nil
i++
}
議論のハイライト
- 名前の変更: 当初の名前は
Variables()でした。しかし型パラメータ(TypeParams)は変数ではないので名前として合いません。また既存のNumFields()がこの列の長さを返すことから、@adonovan がFields()に変更しました。 - 命名への異論: @willfaught は、
FieldList.Listがすでに[]*Fieldであり、Field自体も複数のIdentを持つため、Fieldsという名前は紛らわしいと指摘しました。代わりにIdents()やFlattenList()を挙げています。名前はこれからのレビューで論点になると考えられます。 - x/tools での実績: x/tools 内部の
astutil.FlatFields(gopls v0.17.0 で追加)と同じものです。提案に合わせて内部版をFieldsに名前変更する CL 862165 と、x/tools 全体の手書きループを置き換える CL 862166 も作成されています(どちらも本稿作成時点でレビュー中)。 - 後方互換性: 新しいメソッドを足すだけなので、既存コードへの影響はありません。