diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go index af2380a122..089c3736ae 100644 --- a/internal/lsp/source/completion/completion.go +++ b/internal/lsp/source/completion/completion.go @@ -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 { diff --git a/internal/lsp/source/completion/util.go b/internal/lsp/source/completion/util.go index f291691900..24d595c6fc 100644 --- a/internal/lsp/source/completion/util.go +++ b/internal/lsp/source/completion/util.go @@ -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 { diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden index 48639899ed..0d60e8622d 100644 --- a/internal/lsp/testdata/summary_go1.18.txt.golden +++ b/internal/lsp/testdata/summary_go1.18.txt.golden @@ -6,7 +6,7 @@ CompletionSnippetCount = 110 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 1dfb1034a9..34a2a6baa5 100644 --- a/internal/lsp/testdata/typeparams/type_params.go +++ b/internal/lsp/testdata/typeparams/type_params.go @@ -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) }