diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 76e37ba7be..82324a25bd 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -324,7 +324,7 @@ func (c *completer) found(cand candidate) { return } - if c.matchingCandidate(&cand) { + if c.matchingCandidate(&cand, nil) { cand.score *= highScore } else if isTypeName(obj) { // If obj is a *types.TypeName that didn't otherwise match, check @@ -1737,8 +1737,9 @@ func (c *completer) fakeObj(T types.Type) *types.Var { } // matchingCandidate reports whether a candidate matches our type -// inferences. -func (c *completer) matchingCandidate(cand *candidate) bool { +// inferences. seen is used to detect recursive types in certain cases +// and should be set to nil when calling matchingCandidate. +func (c *completer) matchingCandidate(cand *candidate, seen map[types.Type]struct{}) bool { if isTypeName(cand.obj) { return c.matchingTypeName(cand) } else if c.wantTypeName() { @@ -1788,7 +1789,20 @@ func (c *completer) matchingCandidate(cand *candidate) bool { // Check if dereferencing cand would match our type inference. if ptr, ok := cand.obj.Type().Underlying().(*types.Pointer); ok { - if c.matchingCandidate(&candidate{obj: c.fakeObj(ptr.Elem())}) { + // Notice if we have already encountered this pointer type before. + _, saw := seen[cand.obj.Type()] + + if _, named := cand.obj.Type().(*types.Named); named { + // Lazily allocate "seen" since it isn't used normally. + if seen == nil { + seen = make(map[types.Type]struct{}) + } + + // Track named pointer types we have seen to detect cycles. + seen[cand.obj.Type()] = struct{}{} + } + + if !saw && c.matchingCandidate(&candidate{obj: c.fakeObj(ptr.Elem())}, seen) { // Mark the candidate so we know to prepend "*" when formatting. cand.dereference = true return true @@ -1796,7 +1810,7 @@ func (c *completer) matchingCandidate(cand *candidate) bool { } // Check if cand is addressable and a pointer to cand matches our type inference. - if cand.addressable && c.matchingCandidate(&candidate{obj: c.fakeObj(types.NewPointer(candType))}) { + if cand.addressable && c.matchingCandidate(&candidate{obj: c.fakeObj(types.NewPointer(candType))}, seen) { // Mark the candidate so we know to prepend "&" when formatting. cand.takeAddress = true return true diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go index 0a3a4427e0..b95974e984 100644 --- a/internal/lsp/source/completion_literal.go +++ b/internal/lsp/source/completion_literal.go @@ -75,7 +75,7 @@ func (c *completer) literal(literalType types.Type, imp *importInfo) { cand.addressable = true } - if !c.matchingCandidate(&cand) { + if !c.matchingCandidate(&cand, nil) { return } diff --git a/internal/lsp/testdata/lsp/primarymod/address/address.go b/internal/lsp/testdata/lsp/primarymod/address/address.go index 83cc235341..f1c95280ae 100644 --- a/internal/lsp/testdata/lsp/primarymod/address/address.go +++ b/internal/lsp/testdata/lsp/primarymod/address/address.go @@ -36,6 +36,17 @@ func _() { var _ int = _ //@rank("_ //", addrCPtr, addrA),snippet("_ //", addrCPtr, "*c", "*c") wantsVariadic() //@rank(")", addrCPtr, addrA),snippet(")", addrCPtr, "*c", "*c") + + type namedPtr *int + var np namedPtr + *np //@item(addrNamedPtr, "*np", "namedPtr", "var") + var _ int = _ //@rank("_ //", addrNamedPtr, addrA) + + // don't get tripped up by recursive pointer type + type dontMessUp *dontMessUp + var dmu *dontMessUp //@item(addrDMU, "dmu", "*dontMessUp", "var") + + var _ int = dmu //@complete(" //", addrDMU) } func (f foo) ptr() *foo { return &f } diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden index c52a6426af..519a643566 100644 --- a/internal/lsp/testdata/lsp/summary.txt.golden +++ b/internal/lsp/testdata/lsp/summary.txt.golden @@ -1,10 +1,10 @@ -- summary -- -CompletionsCount = 226 +CompletionsCount = 227 CompletionSnippetCount = 66 UnimportedCompletionsCount = 9 DeepCompletionsCount = 5 FuzzyCompletionsCount = 8 -RankedCompletionsCount = 83 +RankedCompletionsCount = 84 CaseSensitiveCompletionsCount = 4 DiagnosticsCount = 38 FoldingRangesCount = 2