From 0abb09c987dd0257e6bebb29dbcbe4c7bbb69608 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Tue, 1 Oct 2019 13:36:34 -0700 Subject: [PATCH] internal/lsp: search for deep completions across function calls We now continue deep completion search across function calls. The function must take no arguments and return a single argument. For example, when completing "fo<>" you might get candidates such as "foo.bar().baz()". Previously we would stop searching for deep completions when we hit a function call. For example, we would stop at "foo.bar()", never finding "foo.bar().baz()". At the time I was worried about the search scope growing too large, but now that we dynamically limit the search scope there isn't much left to worry about. Change-Id: I48772c154400662876682503c1f58ef6e3dca688 Reviewed-on: https://go-review.googlesource.com/c/tools/+/201222 Reviewed-by: Rebecca Stambler Run-TryBot: Rebecca Stambler --- internal/lsp/source/deep_completion.go | 27 +++++++++++++++--- internal/lsp/testdata/deep/deep.go | 35 ++++++++++++++++++++++++ internal/lsp/testdata/summary.txt.golden | 2 +- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/internal/lsp/source/deep_completion.go b/internal/lsp/source/deep_completion.go index b7eeacdae0..6dd85e7026 100644 --- a/internal/lsp/source/deep_completion.go +++ b/internal/lsp/source/deep_completion.go @@ -39,10 +39,16 @@ type deepCompletionState struct { candidateCount int } -// push pushes obj onto our search stack. -func (s *deepCompletionState) push(obj types.Object) { +// 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) - s.chainNames = append(s.chainNames, obj.Name()) + + name := obj.Name() + if invoke { + name += "()" + } + s.chainNames = append(s.chainNames, name) } // pop pops the last object off our search stack. @@ -156,8 +162,21 @@ func (c *completer) deepSearch(obj types.Object) { 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(sig.Results().At(0).Type(), false) + c.deepState.pop() + } + } + // Push this object onto our search stack. - c.deepState.push(obj) + c.deepState.push(obj, false) switch obj := obj.(type) { case *types.PkgName: diff --git a/internal/lsp/testdata/deep/deep.go b/internal/lsp/testdata/deep/deep.go index f6bcbd7a7a..df70c14725 100644 --- a/internal/lsp/testdata/deep/deep.go +++ b/internal/lsp/testdata/deep/deep.go @@ -88,3 +88,38 @@ func _() { var i int i = a //@deep(" //", deepAD, deepABC, deepA, deepAB) } + +type foo struct { + b bar +} + +func (f foo) bar() bar { + return f.b +} + +func (f foo) barPtr() *bar { + return &f.b +} + +type bar struct{} + +func (b bar) valueReceiver() int { + return 0 +} + +func (b *bar) ptrReceiver() int { + return 0 +} + +func _() { + var ( + i int + f foo + ) + + f.bar().valueReceiver //@item(deepBarValue, "f.bar().valueReceiver", "func() int", "method") + f.barPtr().valueReceiver //@item(deepBarPtrValue, "f.barPtr().valueReceiver", "func() int", "method") + f.barPtr().ptrReceiver //@item(deepBarPtrPtr, "f.barPtr().ptrReceiver", "func() int", "method") + + i = fb //@fuzzy(" //", deepBarValue, deepBarPtrPtr, deepBarPtrValue) +} diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden index 3b3b10f8fb..e0a2f22177 100644 --- a/internal/lsp/testdata/summary.txt.golden +++ b/internal/lsp/testdata/summary.txt.golden @@ -3,7 +3,7 @@ CompletionsCount = 178 CompletionSnippetCount = 39 UnimportedCompletionsCount = 1 DeepCompletionsCount = 5 -FuzzyCompletionsCount = 6 +FuzzyCompletionsCount = 7 RankedCompletionsCount = 1 CaseSensitiveCompletionsCount = 4 DiagnosticsCount = 21