メインコンテンツへスキップ
#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 の tests analyzer の修正)です。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 も作成されています(どちらも本稿作成時点でレビュー中)。
  • 後方互換性: 新しいメソッドを足すだけなので、既存コードへの影響はありません。

関連リンク