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>
This commit is contained in:
Muir Manders 2022-04-15 17:20:39 -07:00 committed by Robert Findley
parent 5bb9a5ecb1
commit b44fad8412
3 changed files with 56 additions and 3 deletions

View File

@ -186,12 +186,18 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
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 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
}
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 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
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 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
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 @@ func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, m
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(")")

View File

@ -2,7 +2,7 @@
CallHierarchyCount = 2
CodeLensCount = 5
CompletionsCount = 266
CompletionSnippetCount = 113
CompletionSnippetCount = 116
UnimportedCompletionsCount = 5
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8

View File

@ -48,3 +48,13 @@ func _() {
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\\}")
}