internal/lsp: add snippet completion for function type parameters

Fixes golang/go#51544

Change-Id: I29cf2a0fe878eed263b406ff3ebf1eaeffe10e4b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/390854
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Peter Weinberger <pjw@google.com>
This commit is contained in:
Robert Findley 2022-03-08 16:12:22 -05:00
parent 94322c4f17
commit 03d333aa5e
5 changed files with 66 additions and 13 deletions

View File

@ -130,7 +130,7 @@ Suffixes:
case invoke:
if sig, ok := funcType.Underlying().(*types.Signature); ok {
s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf)
c.functionCallSnippet("", s.Params(), &snip)
c.functionCallSnippet("", s.TypeParams(), s.Params(), &snip)
if sig.Results().Len() == 1 {
funcType = sig.Results().At(0).Type()
}
@ -307,7 +307,7 @@ func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (Completi
}
item.Detail = "func" + sig.Format()
item.snippet = &snippet.Builder{}
c.functionCallSnippet(obj.Name(), sig.Params(), item.snippet)
c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet)
case *types.TypeName:
if types.IsInterface(obj.Type()) {
item.Kind = protocol.InterfaceCompletion

View File

@ -49,7 +49,7 @@ func (c *completer) structFieldSnippet(cand candidate, detail string, snip *snip
}
// functionCallSnippets calculates the snippet for function calls.
func (c *completer) functionCallSnippet(name string, params []string, snip *snippet.Builder) {
func (c *completer) functionCallSnippet(name string, tparams, params []string, snip *snippet.Builder) {
// If there is no suffix then we need to reuse existing call parens
// "()" if present. If there is an identifier suffix then we always
// need to include "()" since we don't overwrite the suffix.
@ -73,7 +73,26 @@ func (c *completer) functionCallSnippet(name string, params []string, snip *snip
}
}
snip.WriteText(name + "(")
snip.WriteText(name)
if len(tparams) > 0 {
snip.WriteText("[")
if c.opts.placeholders {
for i, tp := range tparams {
if i > 0 {
snip.WriteText(", ")
}
snip.WritePlaceholder(func(b *snippet.Builder) {
b.WriteText(tp)
})
}
} else {
snip.WritePlaceholder(nil)
}
snip.WriteText("]")
}
snip.WriteText("(")
if c.opts.placeholders {
// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".

View File

@ -39,10 +39,10 @@ func FormatType(typ types.Type, qf types.Qualifier) (detail string, kind protoco
}
type signature struct {
name, doc string
params, results []string
variadic bool
needResultParens bool
name, doc string
typeParams, params, results []string
variadic bool
needResultParens bool
}
func (s *signature) Format() string {
@ -75,6 +75,10 @@ func (s *signature) Format() string {
return b.String()
}
func (s *signature) TypeParams() []string {
return s.typeParams
}
func (s *signature) Params() []string {
return s.params
}
@ -171,17 +175,17 @@ func formatFieldList(ctx context.Context, snapshot Snapshot, list *ast.FieldList
// FormatTypeParams turns TypeParamList into its Go representation, such as:
// [T, Y]. Note that it does not print constraints as this is mainly used for
// formatting type params in method receivers.
func FormatTypeParams(tp *typeparams.TypeParamList) string {
if tp == nil || tp.Len() == 0 {
func FormatTypeParams(tparams *typeparams.TypeParamList) string {
if tparams == nil || tparams.Len() == 0 {
return ""
}
var buf bytes.Buffer
buf.WriteByte('[')
for i := 0; i < tp.Len(); i++ {
for i := 0; i < tparams.Len(); i++ {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(tp.At(i).Obj().Name())
buf.WriteString(tparams.At(i).Obj().Name())
}
buf.WriteByte(']')
return buf.String()
@ -189,6 +193,15 @@ func FormatTypeParams(tp *typeparams.TypeParamList) string {
// NewSignature returns formatted signature for a types.Signature struct.
func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) *signature {
var tparams []string
tpList := typeparams.ForSignature(sig)
for i := 0; i < tpList.Len(); i++ {
tparam := tpList.At(i)
// TODO: is it possible to reuse the logic from FormatVarType here?
s := tparam.Obj().Name() + " " + tparam.Constraint().String()
tparams = append(tparams, s)
}
params := make([]string, 0, sig.Params().Len())
for i := 0; i < sig.Params().Len(); i++ {
el := sig.Params().At(i)
@ -199,6 +212,7 @@ func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signa
}
params = append(params, p)
}
var needResultParens bool
results := make([]string, 0, sig.Results().Len())
for i := 0; i < sig.Results().Len(); i++ {
@ -228,6 +242,7 @@ func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signa
}
return &signature{
doc: d,
typeParams: tparams,
params: params,
results: results,
variadic: sig.Variadic(),

View File

@ -0,0 +1,19 @@
// +build go1.18
//go:build go1.18
package snippets
type SyncMap[K comparable, V any] struct{}
func NewSyncMap[K comparable, V any]() (result *SyncMap[K, V]) { //@item(NewSyncMap, "NewSyncMap", "", "")
return
}
func Identity[P ~int](p P) P { //@item(Identity, "Identity", "", "")
return p
}
func _() {
_ = NewSyncM //@snippet(" //", NewSyncMap, "NewSyncMap[${1:}]()", "NewSyncMap[${1:K comparable}, ${2:V any}]()")
_ = Identi //@snippet(" //", Identity, "Identity[${1:}](${2:})", "Identity[${1:P ~int}](${2:p P})")
}

View File

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