go/types: add Scope.Elements iterator
要約
概要
go/typesパッケージのScope型に、スコープ内のオブジェクト(識別子に対応する型情報)を名前順に走査するイテレータメソッドObjects()を追加するproposalです。x/toolsだけでも29箇所以上に存在する定型的なループパターンを簡潔に書けるようにすることが目的です。
ステータス変更
likely_accept → accepted
2026年9月9日のレビュー会議で「likely accept」と判定された後、1週間の異論募集期間を経ても反対意見が出なかったため、9月16日に正式にacceptedとなりました。実装作業を追跡するIssueとして継続し、実際にはすでに実装CL(go.dev/cl/830324)が提案時点で提出されています。
技術的背景
現状の問題点
現在go/types.Scopeからオブジェクト一覧を取得するには、Names()で名前のスライスを取得し、各名前についてLookup()を呼び出すという2段階の処理が必要です。
for _, name := range scope.Names() {
obj := scope.Lookup(name)
// objを使った処理
}
このパターンはx/toolsリポジトリだけでもcmd/godex、cmd/bundle、cmd/deadcode、go/gcexportdataなど29箇所以上で繰り返されており、冗長かつ意図が分かりにくいという課題がありました。
提案された解決策
Go 1.23で導入されたrange-over-func(iter.Seq)を活用し、Scopeにオブジェクトを直接返すイテレータメソッドを追加します。当初提案名はElements()でしたが、レビュー議論の結果、既存ドキュメントの用語や戻り値の型iter.Seq[Object]との一貫性から**Objects()**という名前に決定しました。
package types // "go/types"
// Objects returns an iterator over the objects in s, in name order.
func (s *Scope) Objects() iter.Seq[Object] {
names := s.Names()
return func(yield func(obj Object) bool) {
for _, name := range names {
if !yield(s.Lookup(name)) {
break
}
}
}
}
また、内部実装としてNames()が呼び出すたびに名前一覧をアロケート・ソートしている点がパフォーマンス上の懸念として指摘され、実装CLではScope.Names()のキャッシュ化も併せて行われる予定です。
これによって何ができるようになるか
go/typesを扱う静的解析ツール、リンター、コード生成ツールなどで、スコープ内のシンボルを走査する処理を簡潔に書けるようになります。特にgopls、staticcheck、各種linterのようなx/tools系ツールの開発者にとって、ボイラープレートコードの削減とコードの可読性向上に直結します。
コード例
// Before: Names()とLookup()の2段階呼び出し
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if typeName, ok := obj.(*types.TypeName); ok {
// ...
}
}
// After: Objects()イテレータで直接走査
for obj := range scope.Objects() {
if typeName, ok := obj.(*types.TypeName); ok {
// ...
}
}
議論のハイライト
- 走査中のスコープ変更: @mateusz834から、イテレーション中にスコープが変更された場合の挙動について懸念が示され、提案者の@adonovanは「変更を禁止し、走査終了時に要素数が変わっていればpanicする」という方針を提示しました。
- 戻り値の型設計: レビュー会議で
iter.Seq2[string, Object](名前とオブジェクトのペア)にすべきかが議論されましたが、Objectは自身の名前を保持しているため単純なiter.Seq[Object]で十分と判断されました。 - 順序の保証:
@neildからScope.Children()との順序整合性についての疑問が出ましたが、Child(子スコープ)とNames/Elements(スコープ内オブジェクト)は全く異なる概念であることが確認され、この懸念は取り下げられました。最終的に既存のNames()と同じ名前順(アルファベット順)で返すことが合意されました。 - メソッド名の変更: 当初の
Elements()からObjects()への変更は、go/typesのドキュメント内での用語の一貫性と戻り値型Objectとの対応関係を重視した結果です。 - パフォーマンス: 提案本文のTODOコメントで触れられていた「順序を保証しない実装にすべきか」という論点は、実装側で
Names()の結果をキャッシュすることで解決する方向となり、順序保証を維持したまま性能問題を回避する設計が採用されました。