Skip to content

proposal: go/types: add Scope.Elements iterator #80449

Description

@adonovan

Background: Within x/tools alone, the pattern of a loop over the objects in a types.Scope appears at least 29 times:

tools$ ag -A1 'for _, name := range .*[Ss]cope.*Names\(\) {'
cmd/godex/print.go
88:	for _, name := range scope.Names() {
89-		obj := scope.Lookup(name)

cmd/bundle/main.go
224:	for _, name := range scope.Names() {
225-		rename(scope.Lookup(name))

cmd/deadcode/deadcode.go
186:		for _, name := range scope.Names() {
187-			if typeName, ok := scope.Lookup(name).(*types.TypeName); ok &&

go/gcexportdata/main.go
84:	for _, name := range scope.Names() {
85-		obj := scope.Lookup(name)

...24 more...

With an iterator, this would be simplified to:

for obj := range scope.Elements() { ... }

Proposal: We propose to add the Elements method to Scope to provide an iterator over the objects:

package types // "go/types"

// Elements returns an iterator over the objects in the s in name order.
//
// Example: for obj := range scope.Elements() { ... }
func (s *Scope) Elements() iter.Seq[Object] {
	// TODO(adonovan): opt: names allocates and sorts,
	// which has been a performance problem for clients
	// in the past. Perhaps this iterator should leave
	// the order unspecified?
	names := s.Names()
	return func(yield func(obj Object) bool) {
		for _, name := range names {
			if !yield(s.Lookup(name)) {
				break
			}
		}
	}
}

I think the likely answer to the question in the TODO is "no", but performance is a concern. Perhaps a better approach would be for Scope to amortize the construction of the sorted Names array by retaining it after first use (using a sync.Once or similar).

@griesemer @mrkfrmn

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status
    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions