lsp/completion: improve generic func arg ranking
In cases like:
func foo[A int|string](a A) {}
foo[_](<>)
We now prefer ints and strings at <> by matching against the type
constraint. Note that even if "_" is replaced with "int", we still
prefer strings since the type checker doesn't seem to want to
instantiate foo unless the params check out.
Change-Id: I0e7acfef0775752a96fcfe23e7e2e3d939820eee
Reviewed-on: https://go-review.googlesource.com/c/tools/+/394017
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: Suzy Mueller <suzmue@golang.org>
Auto-Submit: Peter Weinberger <pjw@google.com>
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index af2380a..089c373 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -2002,46 +2002,8 @@
break Nodes
}
- if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
- if sig, ok := tv.Type.(*types.Signature); ok {
- numParams := sig.Params().Len()
- if numParams == 0 {
- return inf
- }
-
- exprIdx := exprAtPos(c.pos, node.Args)
-
- // If we have one or zero arg expressions, we may be
- // completing to a function call that returns multiple
- // values, in turn getting passed in to the surrounding
- // call. Record the assignees so we can favor function
- // calls that return matching values.
- if len(node.Args) <= 1 && exprIdx == 0 {
- for i := 0; i < sig.Params().Len(); i++ {
- inf.assignees = append(inf.assignees, sig.Params().At(i).Type())
- }
-
- // Record that we may be completing into variadic parameters.
- inf.variadicAssignees = sig.Variadic()
- }
-
- // Make sure not to run past the end of expected parameters.
- if exprIdx >= numParams {
- inf.objType = sig.Params().At(numParams - 1).Type()
- } else {
- inf.objType = sig.Params().At(exprIdx).Type()
- }
-
- if sig.Variadic() && exprIdx >= (numParams-1) {
- // If we are completing a variadic param, deslice the variadic type.
- inf.objType = deslice(inf.objType)
- // Record whether we are completing the initial variadic param.
- inf.variadic = exprIdx == numParams-1 && len(node.Args) <= numParams
-
- // Check if we can infer object kind from printf verb.
- inf.objKind |= printfArgKind(c.pkg.GetTypesInfo(), node, exprIdx)
- }
- }
+ if sig, _ := c.pkg.GetTypesInfo().Types[node.Fun].Type.(*types.Signature); sig != nil {
+ inf = c.expectedCallParamType(inf, node, sig)
}
if funIdent, ok := node.Fun.(*ast.Ident); ok {
@@ -2179,6 +2141,55 @@
return inf
}
+func (c *completer) expectedCallParamType(inf candidateInference, node *ast.CallExpr, sig *types.Signature) candidateInference {
+ numParams := sig.Params().Len()
+ if numParams == 0 {
+ return inf
+ }
+
+ exprIdx := exprAtPos(c.pos, node.Args)
+
+ // If we have one or zero arg expressions, we may be
+ // completing to a function call that returns multiple
+ // values, in turn getting passed in to the surrounding
+ // call. Record the assignees so we can favor function
+ // calls that return matching values.
+ if len(node.Args) <= 1 && exprIdx == 0 {
+ for i := 0; i < sig.Params().Len(); i++ {
+ inf.assignees = append(inf.assignees, sig.Params().At(i).Type())
+ }
+
+ // Record that we may be completing into variadic parameters.
+ inf.variadicAssignees = sig.Variadic()
+ }
+
+ // Make sure not to run past the end of expected parameters.
+ if exprIdx >= numParams {
+ inf.objType = sig.Params().At(numParams - 1).Type()
+ } else {
+ inf.objType = sig.Params().At(exprIdx).Type()
+ }
+
+ if sig.Variadic() && exprIdx >= (numParams-1) {
+ // If we are completing a variadic param, deslice the variadic type.
+ inf.objType = deslice(inf.objType)
+ // Record whether we are completing the initial variadic param.
+ inf.variadic = exprIdx == numParams-1 && len(node.Args) <= numParams
+
+ // Check if we can infer object kind from printf verb.
+ inf.objKind |= printfArgKind(c.pkg.GetTypesInfo(), node, exprIdx)
+ }
+
+ // If our expected type is an uninstantiated generic type param,
+ // swap to the constraint which will do a decent job filtering
+ // candidates.
+ if tp, _ := inf.objType.(*typeparams.TypeParam); tp != nil {
+ inf.objType = tp.Constraint()
+ }
+
+ return inf
+}
+
func expectedConstraint(t types.Type, idx int) types.Type {
var tp *typeparams.TypeParamList
if named, _ := t.(*types.Named); named != nil {
diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go
index f291691..24d595c 100644
--- a/internal/lsp/source/completion/util.go
+++ b/internal/lsp/source/completion/util.go
@@ -12,6 +12,7 @@
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
+ "golang.org/x/tools/internal/typeparams"
)
// exprAtPos returns the index of the expression containing pos.
@@ -150,7 +151,7 @@
func isEmptyInterface(T types.Type) bool {
intf, _ := T.(*types.Interface)
- return intf != nil && intf.NumMethods() == 0
+ return intf != nil && intf.NumMethods() == 0 && typeparams.IsMethodSet(intf)
}
func isUntyped(T types.Type) bool {
diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden
index 4863989..0d60e86 100644
--- a/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -6,7 +6,7 @@
UnimportedCompletionsCount = 5
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8
-RankedCompletionsCount = 169
+RankedCompletionsCount = 170
CaseSensitiveCompletionsCount = 4
DiagnosticsCount = 37
FoldingRangesCount = 2
diff --git a/internal/lsp/testdata/typeparams/type_params.go b/internal/lsp/testdata/typeparams/type_params.go
index 1dfb103..34a2a6b 100644
--- a/internal/lsp/testdata/typeparams/type_params.go
+++ b/internal/lsp/testdata/typeparams/type_params.go
@@ -30,4 +30,8 @@
func _() {
var _ int = returnTP //@snippet(" //", returnTP, "returnTP[${1:}](${2:})", "returnTP[${1:A int|float64}](${2:a A})")
+
+ var aa int //@item(tpInt, "aa", "int", "var")
+ var ab string //@item(tpString, "ab", "string", "var")
+ returnTP[int](a) //@rank(")", tpInt, tpString)
}