diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go index cea5aafcca..e67456911f 100644 --- a/internal/lsp/source/completion/format.go +++ b/internal/lsp/source/completion/format.go @@ -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 diff --git a/internal/lsp/source/completion/snippet.go b/internal/lsp/source/completion/snippet.go index 3649314536..72c351f946 100644 --- a/internal/lsp/source/completion/snippet.go +++ b/internal/lsp/source/completion/snippet.go @@ -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*)". diff --git a/internal/lsp/source/types_format.go b/internal/lsp/source/types_format.go index 069268dec3..34d5f28b52 100644 --- a/internal/lsp/source/types_format.go +++ b/internal/lsp/source/types_format.go @@ -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(), diff --git a/internal/lsp/testdata/snippets/func_snippets118.go.in b/internal/lsp/testdata/snippets/func_snippets118.go.in new file mode 100644 index 0000000000..d4933689d6 --- /dev/null +++ b/internal/lsp/testdata/snippets/func_snippets118.go.in @@ -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})") +} diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden index e927324cf3..6bb0671396 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 = 107 +CompletionSnippetCount = 109 UnimportedCompletionsCount = 5 DeepCompletionsCount = 5 FuzzyCompletionsCount = 8