lsp/completion: fix func literals with type params
Give placeholders for type params in func literal completions. For
example:
func foo[T any](func(T) T) {}
foo(<>)
Will now give "func(<T>) <T> {}" where <> denotes a placeholder.
Change-Id: Iadde73ed6b88e1410c28dfa33a20ab6a51235c93
Reviewed-on: https://go-review.googlesource.com/c/tools/+/400616
Run-TryBot: Muir Manders <muir@mnd.rs>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
diff --git a/internal/lsp/source/completion/literal.go b/internal/lsp/source/completion/literal.go
index 03c4401..139ec17 100644
--- a/internal/lsp/source/completion/literal.go
+++ b/internal/lsp/source/completion/literal.go
@@ -186,12 +186,18 @@
var (
paramNames = make([]string, sig.Params().Len())
paramNameCount = make(map[string]int)
+ hasTypeParams bool
)
for i := 0; i < sig.Params().Len(); i++ {
var (
p = sig.Params().At(i)
name = p.Name()
)
+
+ if tp, _ := p.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ hasTypeParams = true
+ }
+
if name == "" {
// If the param has no name in the signature, guess a name based
// on the type. Use an empty qualifier to ignore the package.
@@ -219,6 +225,14 @@
}
for i := 0; i < sig.Params().Len(); i++ {
+ if hasTypeParams && !c.opts.placeholders {
+ // If there are type params in the args then the user must
+ // choose the concrete types. If placeholders are disabled just
+ // drop them between the parens and let them fill things in.
+ snip.WritePlaceholder(nil)
+ break
+ }
+
if i > 0 {
snip.WriteText(", ")
}
@@ -254,7 +268,14 @@
if sig.Variadic() && i == sig.Params().Len()-1 {
typeStr = strings.Replace(typeStr, "[]", "...", 1)
}
- snip.WriteText(typeStr)
+
+ if tp, _ := p.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ snip.WritePlaceholder(func(snip *snippet.Builder) {
+ snip.WriteText(typeStr)
+ })
+ } else {
+ snip.WriteText(typeStr)
+ }
}
}
snip.WriteText(")")
@@ -267,10 +288,24 @@
resultsNeedParens := results.Len() > 1 ||
results.Len() == 1 && results.At(0).Name() != ""
+ var resultHasTypeParams bool
+ for i := 0; i < results.Len(); i++ {
+ if tp, _ := results.At(i).Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ resultHasTypeParams = true
+ }
+ }
+
if resultsNeedParens {
snip.WriteText("(")
}
for i := 0; i < results.Len(); i++ {
+ if resultHasTypeParams && !c.opts.placeholders {
+ // Leave an empty tabstop if placeholders are disabled and there
+ // are type args that need specificying.
+ snip.WritePlaceholder(nil)
+ break
+ }
+
if i > 0 {
snip.WriteText(", ")
}
@@ -278,7 +313,15 @@
if name := r.Name(); name != "" {
snip.WriteText(name + " ")
}
- snip.WriteText(source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf))
+
+ text := source.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf)
+ if tp, _ := r.Type().(*typeparams.TypeParam); tp != nil && !c.typeParamInScope(tp) {
+ snip.WritePlaceholder(func(snip *snippet.Builder) {
+ snip.WriteText(text)
+ })
+ } else {
+ snip.WriteText(text)
+ }
}
if resultsNeedParens {
snip.WriteText(")")
diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden
index e33bcec..f2f0eac 100644
--- a/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -2,7 +2,7 @@
CallHierarchyCount = 2
CodeLensCount = 5
CompletionsCount = 266
-CompletionSnippetCount = 113
+CompletionSnippetCount = 116
UnimportedCompletionsCount = 5
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8
diff --git a/internal/lsp/testdata/typeparams/type_params.go b/internal/lsp/testdata/typeparams/type_params.go
index 48b428e..715726b 100644
--- a/internal/lsp/testdata/typeparams/type_params.go
+++ b/internal/lsp/testdata/typeparams/type_params.go
@@ -48,3 +48,13 @@
var ab float64 //@item(tpFloat, "ab", "float64", "var")
returnTP[int](a) //@rank(")", tpInt, tpFloat)
}
+
+func takesFunc[T any](func(T) T) {
+ var _ func(t T) T = f //@snippet(" //", tpLitFunc, "func(t T) T {$0\\}", "func(t T) T {$0\\}")
+}
+
+func _() {
+ _ = "func(...) {}" //@item(tpLitFunc, "func(...) {}", "", "var")
+ takesFunc() //@snippet(")", tpLitFunc, "func(${1:}) ${2:} {$0\\}", "func(${1:t} ${2:T}) ${3:T} {$0\\}")
+ takesFunc[int]() //@snippet(")", tpLitFunc, "func(i int) int {$0\\}", "func(${1:i} int) int {$0\\}")
+}