diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go index fc34802fac..3bfa588015 100644 --- a/internal/lsp/source/completion/completion.go +++ b/internal/lsp/source/completion/completion.go @@ -89,7 +89,6 @@ type CompletionItem struct { // completionOptions holds completion specific configuration. type completionOptions struct { - deepCompletion bool unimported bool documentation bool fullDocumentation bool @@ -318,114 +317,6 @@ func (c *completer) getSurrounding() *Selection { return c.surrounding } -// found adds a candidate completion. We will also search through the object's -// members for more candidates. -func (c *completer) found(ctx context.Context, cand candidate) { - obj := cand.obj - - if obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() { - // obj is not accessible because it lives in another package and is not - // exported. Don't treat it as a completion candidate. - return - } - - if c.inDeepCompletion() { - // When searching deep, just make sure we don't have a cycle in our chain. - // We don't dedupe by object because we want to allow both "foo.Baz" and - // "bar.Baz" even though "Baz" is represented the same types.Object in both. - for _, seenObj := range c.deepState.chain { - if seenObj == obj { - return - } - } - } else { - // At the top level, dedupe by object. - if c.seen[obj] { - return - } - c.seen[obj] = true - } - - // If we are running out of budgeted time we must limit our search for deep - // completion candidates. - if c.shouldPrune() { - return - } - - // If we know we want a type name, don't offer non-type name - // candidates. However, do offer package names since they can - // contain type names, and do offer any candidate without a type - // since we aren't sure if it is a type name or not (i.e. unimported - // candidate). - if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) { - return - } - - if c.matchingCandidate(&cand) { - cand.score *= highScore - - if p := c.penalty(&cand); p > 0 { - cand.score *= (1 - p) - } - } else if isTypeName(obj) { - // If obj is a *types.TypeName that didn't otherwise match, check - // if a literal object of this type makes a good candidate. - - // We only care about named types (i.e. don't want builtin types). - if _, isNamed := obj.Type().(*types.Named); isNamed { - c.literal(ctx, obj.Type(), cand.imp) - } - } - - // Lower score of method calls so we prefer fields and vars over calls. - if cand.expandFuncCall { - if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil { - cand.score *= 0.9 - } - } - - // Prefer private objects over public ones. - if !obj.Exported() && obj.Parent() != types.Universe { - cand.score *= 1.1 - } - - // Favor shallow matches by lowering score according to depth. - cand.score -= cand.score * c.deepState.scorePenalty() - - if cand.score < 0 { - cand.score = 0 - } - - cand.name = c.deepState.chainString(obj.Name()) - matchScore := c.matcher.Score(cand.name) - if matchScore > 0 { - cand.score *= float64(matchScore) - - // Avoid calling c.item() for deep candidates that wouldn't be in the top - // MaxDeepCompletions anyway. - if !c.inDeepCompletion() || c.deepState.isHighScore(cand.score) { - if item, err := c.item(ctx, cand); err == nil { - c.items = append(c.items, item) - } - } - } - - c.deepSearch(ctx, cand) -} - -// penalty reports a score penalty for cand in the range (0, 1). -// For example, a candidate is penalized if it has already been used -// in another switch case statement. -func (c *completer) penalty(cand *candidate) float64 { - for _, p := range c.inference.penalized { - if c.objChainMatches(cand.obj, p.objChain) { - return p.penalty - } - } - - return 0 -} - // candidate represents a completion candidate. type candidate struct { // obj is the types.Object to complete to. @@ -486,7 +377,7 @@ func (e ErrIsDefinition) Error() string { // the client to score the quality of the completion. For instance, some clients // may tolerate imperfect matches as valid completion results, since users may make typos. func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, protoPos protocol.Position, triggerCharacter string) ([]CompletionItem, *Selection, error) { - ctx, done := event.Start(ctx, "source.Completion") + ctx, done := event.Start(ctx, "completion.Completion") defer done() startTime := time.Now() @@ -571,9 +462,12 @@ func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHan seen: make(map[types.Object]bool), enclosingFunc: enclosingFunction(path, pkg.GetTypesInfo()), enclosingCompositeLiteral: enclosingCompositeLiteral(path, rng.Start, pkg.GetTypesInfo()), + deepState: deepCompletionState{ + enabled: opts.DeepCompletion, + curPath: &searchPath{}, + }, opts: &completionOptions{ matcher: opts.Matcher, - deepCompletion: opts.DeepCompletion, unimported: opts.CompleteUnimported, documentation: opts.CompletionDocumentation, fullDocumentation: opts.HoverKind == source.FullDocumentation, @@ -588,11 +482,6 @@ func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHan startTime: startTime, } - if c.opts.deepCompletion { - // Initialize max search depth to unlimited. - c.deepState.maxDepth = -1 - } - var cancel context.CancelFunc if c.opts.budget == 0 { ctx, cancel = context.WithCancel(ctx) @@ -623,9 +512,9 @@ func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHan // Inside comments, offer completions for the name of the relevant symbol. for _, comment := range pgf.File.Comments { if comment.Pos() < rng.Start && rng.Start <= comment.End() { - // deep completion doesn't work properly in comments since we don't - // have a type object to complete further - c.deepState.maxDepth = 0 + // Deep completion doesn't work properly in comments since we don't + // have a type object to complete further. + c.deepState.enabled = false c.populateCommentCompletions(ctx, comment) return c.items, c.getSurrounding(), nil } @@ -633,6 +522,11 @@ func Completion(ctx context.Context, snapshot source.Snapshot, fh source.FileHan // Struct literals are handled entirely separately. if c.wantStructFieldCompletions() { + // If we are definitely completing a struct field name, deep completions + // don't make sense. + if c.enclosingCompositeLiteral.inKey { + c.deepState.enabled = false + } if err := c.structLiteralFieldName(ctx); err != nil { return nil, nil, err } @@ -1104,7 +998,10 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error { // Is sel a qualified identifier? if id, ok := sel.X.(*ast.Ident); ok { if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok { - c.packageMembers(ctx, pkgName.Imported(), stdScore, nil) + candidates := c.packageMembers(ctx, pkgName.Imported(), stdScore, nil) + for _, cand := range candidates { + c.found(ctx, cand) + } return nil } } @@ -1112,7 +1009,11 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error { // Invariant: sel is a true selector. tv, ok := c.pkg.GetTypesInfo().Types[sel.X] if ok { - return c.methodsAndFields(ctx, tv.Type, tv.Addressable(), nil) + candidates := c.methodsAndFields(ctx, tv.Type, tv.Addressable(), nil) + for _, cand := range candidates { + c.found(ctx, cand) + } + return nil } // Try unimported packages. @@ -1165,7 +1066,10 @@ func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error if imports.ImportPathToAssumedName(path) != pkg.GetTypes().Name() { imp.name = pkg.GetTypes().Name() } - c.packageMembers(ctx, pkg.GetTypes(), unimportedScore(relevances[path]), imp) + candidates := c.packageMembers(ctx, pkg.GetTypes(), unimportedScore(relevances[path]), imp) + for _, cand := range candidates { + c.found(ctx, cand) + } if len(c.items) >= unimportedMemberTarget { return nil } @@ -1210,20 +1114,22 @@ func unimportedScore(relevance int) float64 { return (stdScore + .1*float64(relevance)) / 2 } -func (c *completer) packageMembers(ctx context.Context, pkg *types.Package, score float64, imp *importInfo) { +func (c *completer) packageMembers(ctx context.Context, pkg *types.Package, score float64, imp *importInfo) []candidate { + var candidates []candidate scope := pkg.Scope() for _, name := range scope.Names() { obj := scope.Lookup(name) - c.found(ctx, candidate{ + candidates = append(candidates, candidate{ obj: obj, score: score, imp: imp, addressable: isVar(obj), }) } + return candidates } -func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addressable bool, imp *importInfo) error { +func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addressable bool, imp *importInfo) []candidate { mset := c.methodSetCache[methodSetKey{typ, addressable}] if mset == nil { if addressable && !types.IsInterface(typ) && !isPointer(typ) { @@ -1236,8 +1142,9 @@ func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addres c.methodSetCache[methodSetKey{typ, addressable}] = mset } + var candidates []candidate for i := 0; i < mset.Len(); i++ { - c.found(ctx, candidate{ + candidates = append(candidates, candidate{ obj: mset.At(i).Obj(), score: stdScore, imp: imp, @@ -1247,7 +1154,7 @@ func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addres // Add fields of T. eachField(typ, func(v *types.Var) { - c.found(ctx, candidate{ + candidates = append(candidates, candidate{ obj: v, score: stdScore - 0.01, imp: imp, @@ -1255,7 +1162,7 @@ func (c *completer) methodsAndFields(ctx context.Context, typ types.Type, addres }) }) - return nil + return candidates } // lexical finds completions in the lexical environment. @@ -2474,40 +2381,6 @@ func (c *completer) matchingCandidate(cand *candidate) bool { return false } -// objChainMatches reports whether cand combined with the surrounding -// object prefix matches chain. -func (c *completer) objChainMatches(cand types.Object, chain []types.Object) bool { - // For example, when completing: - // - // foo.ba<> - // - // If we are considering the deep candidate "bar.baz", cand is baz, - // objChain is [foo] and deepChain is [bar]. We would match the - // chain [foo, bar, baz]. - - if len(chain) != len(c.inference.objChain)+len(c.deepState.chain)+1 { - return false - } - - if chain[len(chain)-1] != cand { - return false - } - - for i, o := range c.inference.objChain { - if chain[i] != o { - return false - } - } - - for i, o := range c.deepState.chain { - if chain[i+len(c.inference.objChain)] != o { - return false - } - } - - return true -} - // candTypeMatches reports whether cand makes a good completion // candidate given the candidate inference. cand's score may be // mutated to downrank the candidate in certain situations. diff --git a/internal/lsp/source/completion/completion_format.go b/internal/lsp/source/completion/completion_format.go index 793f1ec4dd..35d5f5a227 100644 --- a/internal/lsp/source/completion/completion_format.go +++ b/internal/lsp/source/completion/completion_format.go @@ -160,7 +160,7 @@ func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, e Detail: detail, Kind: kind, Score: cand.score, - Depth: len(c.deepState.chain), + Depth: len(c.deepState.curPath.path), snippet: snip, obj: obj, } diff --git a/internal/lsp/source/completion/completion_snippet.go b/internal/lsp/source/completion/completion_snippet.go index c254c6567c..a88be01baf 100644 --- a/internal/lsp/source/completion/completion_snippet.go +++ b/internal/lsp/source/completion/completion_snippet.go @@ -19,7 +19,7 @@ func (c *completer) structFieldSnippet(label, detail string) *snippet.Builder { // If we are in a deep completion then we can't be completing a field // name (e.g. "Foo{f<>}" completing to "Foo{f.Bar}" should not generate // a snippet). - if c.inDeepCompletion() { + if c.deepState.inDeepCompletion() { return nil } diff --git a/internal/lsp/source/completion/deep_completion.go b/internal/lsp/source/completion/deep_completion.go index 3f06d13b82..c7754ac51f 100644 --- a/internal/lsp/source/completion/deep_completion.go +++ b/internal/lsp/source/completion/deep_completion.go @@ -11,6 +11,19 @@ import ( "time" ) +// searchItem represents a candidate in deep completion search queue. +type searchItem struct { + *searchPath + cand candidate +} + +// searchPath holds the path from search root (excluding the item itself) for +// a searchItem. +type searchPath struct { + path []types.Object + names []string +} + // MaxDeepCompletions limits deep completion results because in most cases // there are too many to be useful. const MaxDeepCompletions = 3 @@ -19,17 +32,19 @@ const MaxDeepCompletions = 3 // "deep completion" refers to searching into objects' fields and methods to // find more completion candidates. type deepCompletionState struct { - // maxDepth limits the deep completion search depth. 0 means - // disabled and -1 means unlimited. - maxDepth int + // enabled indicates wether deep completion is permitted. It should be + // reset to original value if manually disabled for an individual case. + enabled bool - // chain holds the traversal path as we do a depth-first search through - // objects' members looking for exact type matches. - chain []types.Object + // queueClosed is used to disable adding new items to search queue once + // we're running out of our time budget. + queueClosed bool - // chainNames holds the names of the chain objects. This allows us to - // save allocations as we build many deep completion items. - chainNames []string + // searchQueue holds the current breadth first search queue. + searchQueue []searchItem + + // curPath tracks the current deep completion search path. + curPath *searchPath // highScores tracks the highest deep candidate scores we have found // so far. This is used to avoid work for low scoring deep candidates. @@ -40,35 +55,45 @@ type deepCompletionState struct { candidateCount int } -// push pushes obj onto our search stack. If invoke is true then -// invocation parens "()" will be appended to the object name. -func (s *deepCompletionState) push(obj types.Object, invoke bool) { - s.chain = append(s.chain, obj) - - name := obj.Name() - if invoke { - name += "()" +// enqueue adds candidates to the search queue. +func (s *deepCompletionState) enqueue(path *searchPath, candidates ...candidate) { + for _, cand := range candidates { + s.searchQueue = append(s.searchQueue, searchItem{path, cand}) } - s.chainNames = append(s.chainNames, name) } -// pop pops the last object off our search stack. -func (s *deepCompletionState) pop() { - s.chain = s.chain[:len(s.chain)-1] - s.chainNames = s.chainNames[:len(s.chainNames)-1] +// dequeue removes and returns the leftmost element from the search queue. +func (s *deepCompletionState) dequeue() *searchItem { + var item *searchItem + item, s.searchQueue = &s.searchQueue[0], s.searchQueue[1:] + return item } -// chainString joins the chain of objects' names together on ".". -func (s *deepCompletionState) chainString(finalName string) string { - s.chainNames = append(s.chainNames, finalName) - chainStr := strings.Join(s.chainNames, ".") - s.chainNames = s.chainNames[:len(s.chainNames)-1] - return chainStr +// scorePenalty computes a deep candidate score penalty. A candidate is +// penalized based on depth to favor shallower candidates. We also give a +// slight bonus to unexported objects and a slight additional penalty to +// function objects. +func (s *deepCompletionState) scorePenalty() float64 { + var deepPenalty float64 + for _, dc := range s.curPath.path { + deepPenalty++ + + if !dc.Exported() { + deepPenalty -= 0.1 + } + + if _, isSig := dc.Type().Underlying().(*types.Signature); isSig { + deepPenalty += 0.1 + } + } + + // Normalize penalty to a max depth of 10. + return deepPenalty / 10 } -// isHighScore returns whether score is among the top MaxDeepCompletions -// deep candidate scores encountered so far. If so, it adds score to -// highScores, possibly displacing an existing high score. +// isHighScore returns whether score is among the top MaxDeepCompletions deep +// candidate scores encountered so far. If so, it adds score to highScores, +// possibly displacing an existing high score. func (s *deepCompletionState) isHighScore(score float64) bool { // Invariant: s.highScores is sorted with highest score first. Unclaimed // positions are trailing zeros. @@ -91,126 +116,240 @@ func (s *deepCompletionState) isHighScore(score float64) bool { return false } -// scorePenalty computes a deep candidate score penalty. A candidate -// is penalized based on depth to favor shallower candidates. We also -// give a slight bonus to unexported objects and a slight additional -// penalty to function objects. -func (s *deepCompletionState) scorePenalty() float64 { - var deepPenalty float64 - for _, dc := range s.chain { - deepPenalty += 1 +// inDeepCompletion returns if we're currently searching an object's members. +func (s *deepCompletionState) inDeepCompletion() bool { + return len(s.curPath.path) > 0 +} - if !dc.Exported() { - deepPenalty -= 0.1 +// reset resets deepCompletionState since found might be called multiple times. +// We don't reset high scores since multiple calls to found still respect the +// same MaxDeepCompletions count. +func (s *deepCompletionState) reset() { + s.searchQueue = nil + s.curPath = &searchPath{} +} + +// appendToSearchPath appends an object to a given searchPath. +func appendToSearchPath(oldPath searchPath, obj types.Object, invoke bool) *searchPath { + name := obj.Name() + if invoke { + name += "()" + } + + // copy the slice since we don't want to overwrite the original slice. + path := append([]types.Object{}, oldPath.path...) + names := append([]string{}, oldPath.names...) + + return &searchPath{ + path: append(path, obj), + names: append(names, name), + } +} + +// found adds a candidate to completion items if it's a valid suggestion and +// searches the candidate's subordinate objects for more completion items if +// deep completion is enabled. +func (c *completer) found(ctx context.Context, cand candidate) { + // reset state at the end so current state doesn't affect completions done + // outside c.found. + defer c.deepState.reset() + + // At the top level, dedupe by object. + if c.seen[cand.obj] { + return + } + c.seen[cand.obj] = true + + c.deepState.enqueue(&searchPath{}, cand) +outer: + for len(c.deepState.searchQueue) > 0 { + item := c.deepState.dequeue() + curCand := item.cand + obj := curCand.obj + + // If obj is not accessible because it lives in another package and is + // not exported, don't treat it as a completion candidate. + if obj.Pkg() != nil && obj.Pkg() != c.pkg.GetTypes() && !obj.Exported() { + continue } - if _, isSig := dc.Type().Underlying().(*types.Signature); isSig { - deepPenalty += 0.1 + // If we want a type name, don't offer non-type name candidates. + // However, do offer package names since they can contain type names, + // and do offer any candidate without a type since we aren't sure if it + // is a type name or not (i.e. unimported candidate). + if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) { + continue + } + + // When searching deep, make sure we don't have a cycle in our chain. + // We don't dedupe by object because we want to allow both "foo.Baz" + // and "bar.Baz" even though "Baz" is represented the same types.Object + // in both. + for _, seenObj := range item.path { + if seenObj == obj { + continue outer + } + } + + // update tracked current path since other functions might check it. + c.deepState.curPath = item.searchPath + + c.addCandidate(ctx, curCand) + + c.deepState.candidateCount++ + if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 { + spent := float64(time.Since(c.startTime)) / float64(c.opts.budget) + if spent > 1.0 { + return + } + // If we are almost out of budgeted time, no further elements + // should be added to the queue. This ensures remaining time is + // used for processing current queue. + if !c.deepState.queueClosed && spent >= 0.85 { + c.deepState.queueClosed = true + } + } + + // if deep search is disabled, don't add any more candidates. + if !c.deepState.enabled || c.deepState.queueClosed { + continue + } + + // Searching members for a type name doesn't make sense. + if isTypeName(obj) { + continue + } + if obj.Type() == nil { + continue + } + + // Don't search embedded fields because they were already included in their + // parent's fields. + if v, ok := obj.(*types.Var); ok && v.Embedded() { + continue + } + + if sig, ok := obj.Type().Underlying().(*types.Signature); ok { + // If obj is a function that takes no arguments and returns one + // value, keep searching across the function call. + if sig.Params().Len() == 0 && sig.Results().Len() == 1 { + newSearchPath := appendToSearchPath(*item.searchPath, obj, true) + // The result of a function call is not addressable. + candidates := c.methodsAndFields(ctx, sig.Results().At(0).Type(), false, curCand.imp) + c.deepState.enqueue(newSearchPath, candidates...) + } + } + + newSearchPath := appendToSearchPath(*item.searchPath, obj, false) + switch obj := obj.(type) { + case *types.PkgName: + candidates := c.packageMembers(ctx, obj.Imported(), stdScore, curCand.imp) + c.deepState.enqueue(newSearchPath, candidates...) + default: + candidates := c.methodsAndFields(ctx, obj.Type(), curCand.addressable, curCand.imp) + c.deepState.enqueue(newSearchPath, candidates...) + } + } +} + +// addCandidate adds a completion candidate to suggestions, without searching +// its members for more candidates. +func (c *completer) addCandidate(ctx context.Context, cand candidate) { + obj := cand.obj + if c.matchingCandidate(&cand) { + cand.score *= highScore + + if p := c.penalty(&cand); p > 0 { + cand.score *= (1 - p) + } + } else if isTypeName(obj) { + // If obj is a *types.TypeName that didn't otherwise match, check + // if a literal object of this type makes a good candidate. + + // We only care about named types (i.e. don't want builtin types). + if _, isNamed := obj.Type().(*types.Named); isNamed { + c.literal(ctx, obj.Type(), cand.imp) + } + } + + // Lower score of method calls so we prefer fields and vars over calls. + if cand.expandFuncCall { + if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil { + cand.score *= 0.9 + } + } + + // Prefer private objects over public ones. + if !obj.Exported() && obj.Parent() != types.Universe { + cand.score *= 1.1 + } + + // Favor shallow matches by lowering score according to depth. + cand.score -= cand.score * c.deepState.scorePenalty() + + if cand.score < 0 { + cand.score = 0 + } + + cand.name = strings.Join(append(c.deepState.curPath.names, cand.obj.Name()), ".") + matchScore := c.matcher.Score(cand.name) + if matchScore > 0 { + cand.score *= float64(matchScore) + + // Avoid calling c.item() for deep candidates that wouldn't be in the top + // MaxDeepCompletions anyway. + if !c.deepState.inDeepCompletion() || c.deepState.isHighScore(cand.score) { + if item, err := c.item(ctx, cand); err == nil { + c.items = append(c.items, item) + } } } - // Normalize penalty to a max depth of 10. - return deepPenalty / 10 } -func (c *completer) inDeepCompletion() bool { - return len(c.deepState.chain) > 0 +// penalty reports a score penalty for cand in the range (0, 1). +// For example, a candidate is penalized if it has already been used +// in another switch case statement. +func (c *completer) penalty(cand *candidate) float64 { + for _, p := range c.inference.penalized { + if c.objChainMatches(cand.obj, p.objChain) { + return p.penalty + } + } + + return 0 } -// shouldPrune returns whether we should prune the current deep -// candidate search to reduce the overall search scope. The -// maximum search depth is reduced gradually as we use up our -// completionBudget. -func (c *completer) shouldPrune() bool { - if !c.inDeepCompletion() { +// objChainMatches reports whether cand combined with the surrounding +// object prefix matches chain. +func (c *completer) objChainMatches(cand types.Object, chain []types.Object) bool { + // For example, when completing: + // + // foo.ba<> + // + // If we are considering the deep candidate "bar.baz", cand is baz, + // objChain is [foo] and deepChain is [bar]. We would match the + // chain [foo, bar, baz]. + + if len(chain) != len(c.inference.objChain)+len(c.deepState.curPath.path)+1 { return false } - // Check our remaining budget every 100 candidates. - if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 { - spent := float64(time.Since(c.startTime)) / float64(c.opts.budget) + if chain[len(chain)-1] != cand { + return false + } - switch { - case spent >= 0.90: - // We are close to exhausting our budget. Disable deep completions. - c.deepState.maxDepth = 0 - case spent >= 0.75: - // We are running out of budget, reduce max depth again. - c.deepState.maxDepth = 2 - case spent >= 0.5: - // We have used half our budget, reduce max depth again. - c.deepState.maxDepth = 3 - case spent >= 0.25: - // We have used a good chunk of our budget, so start limiting our search. - // By default the search depth is unlimited, so this limit, while still - // generous, is normally a huge reduction in search scope that will result - // in our search completing very soon. - c.deepState.maxDepth = 4 + for i, o := range c.inference.objChain { + if chain[i] != o { + return false } } - c.deepState.candidateCount++ - - if c.deepState.maxDepth >= 0 { - return len(c.deepState.chain) >= c.deepState.maxDepth - } - - return false -} - -// deepSearch searches through cand's subordinate objects for more -// completion items. -func (c *completer) deepSearch(ctx context.Context, cand candidate) { - if c.deepState.maxDepth == 0 { - return - } - - obj := cand.obj - - // If we are definitely completing a struct field name, deep completions - // don't make sense. - if c.wantStructFieldCompletions() && c.enclosingCompositeLiteral.inKey { - return - } - - // Don't search into type names. - if isTypeName(obj) { - return - } - - if obj.Type() == nil { - return - } - - // Don't search embedded fields because they were already included in their - // parent's fields. - if v, ok := obj.(*types.Var); ok && v.Embedded() { - return - } - - if sig, ok := obj.Type().Underlying().(*types.Signature); ok { - // If obj is a function that takes no arguments and returns one - // value, keep searching across the function call. - if sig.Params().Len() == 0 && sig.Results().Len() == 1 { - // Pass invoke=true since the function needs to be invoked in - // the deep chain. - c.deepState.push(obj, true) - // The result of a function call is not addressable. - c.methodsAndFields(ctx, sig.Results().At(0).Type(), false, cand.imp) - c.deepState.pop() + for i, o := range c.deepState.curPath.path { + if chain[i+len(c.inference.objChain)] != o { + return false } } - // Push this object onto our search stack. - c.deepState.push(obj, false) - - switch obj := obj.(type) { - case *types.PkgName: - c.packageMembers(ctx, obj.Imported(), stdScore, cand.imp) - default: - c.methodsAndFields(ctx, obj.Type(), cand.addressable, cand.imp) - } - - // Pop the object off our search stack. - c.deepState.pop() + return true }