diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index d883be663b..e103e26018 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -869,14 +869,15 @@ func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *impo } // Add fields of T. - for _, f := range fieldSelections(typ) { + eachField(typ, func(v *types.Var) { c.found(candidate{ - obj: f, + obj: v, score: stdScore - 0.01, imp: imp, addressable: addressable || isPointer(typ), }) - } + }) + return nil } diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index b691f7c384..09f3569757 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -272,34 +272,39 @@ func exprAtPos(pos token.Pos, args []ast.Expr) int { return len(args) } -// fieldSelections returns the set of fields that can -// be selected from a value of type T. -func fieldSelections(T types.Type) (fields []*types.Var) { +// eachField invokes fn for each field that can be selected from a +// value of type T. +func eachField(T types.Type, fn func(*types.Var)) { // TODO(adonovan): this algorithm doesn't exclude ambiguous // selections that match more than one field/method. // types.NewSelectionSet should do that for us. - seen := make(map[*types.Var]bool) // for termination on recursive types + // for termination on recursive types + var seen map[*types.Struct]bool var visit func(T types.Type) visit = func(T types.Type) { if T, ok := deref(T).Underlying().(*types.Struct); ok { + if seen[T] { + return + } + for i := 0; i < T.NumFields(); i++ { f := T.Field(i) - if seen[f] { - continue - } - seen[f] = true - fields = append(fields, f) + fn(f) if f.Anonymous() { + if seen == nil { + // Lazily create "seen" since it is only needed for + // embedded structs. + seen = make(map[*types.Struct]bool) + } + seen[T] = true visit(f.Type()) } } } } visit(T) - - return fields } // typeIsValid reports whether typ doesn't contain any Invalid types.