メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#80449likely_accept

go/types: add Scope.Elements iterator

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

go/typesパッケージのScope型に、スコープ内のオブジェクト(識別子に束縛された変数・関数・型など)を名前順に走査するイテレータメソッドを追加する提案です。Go 1.23で導入されたrange-over-func(イテレータ)の仕組みを活用し、既存のボイラープレートなループを簡潔に書けるようにします。

ステータス変更

activelikely_accept
2026-09-09のproposal review meetingにて、設計上の論点(戻り値の型、順序保証、命名)がおおむね解消されたことから「likely accept(承認見込み)」と判定されました。同時に最終コメント募集期間(Final Comment Period)に入っています。既にメソッド名をObjectsに変更する形で実装CLも提出されており、大きな異論がなければそのまま承認される見通しです。

技術的背景

現状の問題点

go/types.Scopeから要素を取得するには、現状Names()で名前のスライスを取得し、Lookup(name)で個々のオブジェクトを引く、という2段階の手順が必要です。

for _, name := range scope.Names() {
    obj := scope.Lookup(name)
    // objを使った処理
}

提案者(@adonovan)の調査によると、この定型パターンはx/toolsリポジトリだけでも29箇所以上に出現しており、冗長で書き間違えやすいコードになっています。

提案された解決策

Scopeiter.Seq[Object]を返すイテレータメソッドを追加します。当初はElements()という名前で提案されましたが、レビュー中に「Object型自体が名前を保持しているためiter.Seq2[string, Object]にする必要はない」「ドキュメントでObjectsという表現も使われており、戻り値の型Objectとも呼称が一致する」という指摘を受け、最終的に**Objects()**という名前に変更されました。

package types // "go/types"
// Elements returns an iterator over the objects in the s in name order.
func (s *Scope) Objects() iter.Seq[Object] {
    // Implementation note: Rearrange the data structure so we don't
    // have to allocate and sort on every call.
    names := s.Names()
    return func(yield func(obj Object) bool) {
        for _, name := range names {
            if !yield(s.Lookup(name)) {
                break
            }
        }
    }
}

要素の走査順はNames()と同じ名前順(アルファベット順)で確定的であることが合意されました。ただし現状の実装は呼び出しのたびに名前一覧を割り当ててソートしており性能上のボトルネックになり得るため、実装CL(go.dev/cl/830324)ではScope.Names()の結果をキャッシュする最適化も同時に行われています。

これによって何ができるようになるか

go/typesを使った静的解析ツール(vet、gopls、コンパイラのフロントエンド、リンター、コード生成ツールなど)で、スコープ内の宣言済みオブジェクト(変数・定数・関数・型)を1行のfor-rangeループで列挙できるようになります。特にx/toolsやgoplsのようにScopeを頻繁に走査するツール群でコードの見通しが大幅に改善されます。

コード例

// Before: 従来の書き方
for _, name := range scope.Names() {
    obj := scope.Lookup(name)
    if _, ok := obj.(*types.TypeName); ok {
        // 型名のみ処理
    }
}
// After: 新しいイテレータを使った書き方
for obj := range scope.Objects() {
    if _, ok := obj.(*types.TypeName); ok {
        // 型名のみ処理
    }
}

議論のハイライト

  • ループ中の変更の扱い: @mateusz834から「ループ中にスコープが変更された場合の挙動を規定すべきでは」という懸念が出され、提案者の@adonovanは「変更を禁止し、末尾で要素数が変化していればパニックさせる」という方針を示しました。
  • 戻り値の型論争: レビューでiter.Seq2[string, Object](名前とオブジェクトのペア)にすべきかが議論されましたが、Object型自体が名前を保持しているため、単純なiter.Seq[Object]で十分と結論付けられました。
  • 順序保証: Scope.Children()(子スコープを列挙する既存のイテレータ)との順序の整合性について@neildから疑問が出ましたが、Childrenはスコープの階層、Objectsは名前空間という異なる領域を扱うため直接の対応関係はないと@aclementsが整理し、Names()と同じ名前順でよいことで合意しました。
  • 性能面の懸念: 提案本文の時点から、Names()ベースの実装は毎回スライス割り当てとソートが発生するため性能への影響が懸念されており、レビューでも「遅延ソート+キャッシュ」など複数の最適化案が検討されました。最終的に実装CLではScope.Names()の結果をキャッシュする改善も併せて行われています。
  • メソッド名の変更: レビュー終盤で@aclementsが「ElementsObjectsのどちらが適切か」を問い、型Objectとの一貫性から最終的にObjectsへと名称変更されました。

関連リンク