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>
This commit is contained in:
Muir Manders 2022-03-19 16:21:01 -07:00 committed by Robert Findley
parent d567bc1c22
commit fa7afc95f2
4 changed files with 58 additions and 42 deletions

View File

@ -2002,46 +2002,8 @@ Nodes:
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 @@ Nodes:
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 {

View File

@ -12,6 +12,7 @@ import (
"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 isFunc(obj types.Object) bool {
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 {

View File

@ -6,7 +6,7 @@ CompletionSnippetCount = 110
UnimportedCompletionsCount = 5
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8
RankedCompletionsCount = 169
RankedCompletionsCount = 170
CaseSensitiveCompletionsCount = 4
DiagnosticsCount = 37
FoldingRangesCount = 2

View File

@ -30,4 +30,8 @@ func returnTP[A int | float64](a A) A { //@item(returnTP, "returnTP", "something
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)
}