Skip to content

Commit 05a7636

Browse files
committed
completions/inference: resolve CL feedback and add tests
Implements cleanups from CL feedback. Fixes unintuitive behavior with partially instantiated calls. Fixes reverse inference to take lower precedence than generic type constraints. Fixes no inference completions for out of bounds parameters. chore: better comment small format change
1 parent 152e295 commit 05a7636

File tree

5 files changed

+314
-147
lines changed

5 files changed

+314
-147
lines changed

gopls/internal/golang/completion/completion.go

Lines changed: 95 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -134,25 +134,28 @@ func (i *CompletionItem) Snippet() string {
134134
return i.InsertText
135135
}
136136

137-
func (i *CompletionItem) addPrefixSuffix(c *completer, prefix string, suffix string) error {
138-
if prefix != "" {
137+
// addConversion wraps the existing completionItem in a conversion expression.
138+
// Only affects the receiver's InsertText and snippet fields, not the Label.
139+
// An empty conv argument has no effect.
140+
func (i *CompletionItem) addConversion(c *completer, conv conversionEdits) error {
141+
if conv.prefix != "" {
139142
// If we are in a selector, add an edit to place prefix before selector.
140143
if sel := enclosingSelector(c.path, c.pos); sel != nil {
141-
edits, err := c.editText(sel.Pos(), sel.Pos(), prefix)
144+
edits, err := c.editText(sel.Pos(), sel.Pos(), conv.prefix)
142145
if err != nil {
143146
return err
144147
}
145148
i.AdditionalTextEdits = append(i.AdditionalTextEdits, edits...)
146149
} else {
147150
// If there is no selector, just stick the prefix at the start.
148-
i.InsertText = prefix + i.InsertText
149-
i.snippet.PrependText(prefix)
151+
i.InsertText = conv.prefix + i.InsertText
152+
i.snippet.PrependText(conv.prefix)
150153
}
151154
}
152155

153-
if suffix != "" {
154-
i.InsertText += suffix
155-
i.snippet.WriteText(suffix)
156+
if conv.suffix != "" {
157+
i.InsertText += conv.suffix
158+
i.snippet.WriteText(conv.suffix)
156159
}
157160

158161
return nil
@@ -2189,6 +2192,10 @@ type candidateInference struct {
21892192
// convertibleTo is a type our candidate type must be convertible to.
21902193
convertibleTo types.Type
21912194

2195+
// needsExactType is true if the candidate type must be exactly the type of
2196+
// the objType, e.g. an interface rather than it's implementors.
2197+
needsExactType bool
2198+
21922199
// typeName holds information about the expected type name at
21932200
// position, if any.
21942201
typeName typeNameInference
@@ -2324,26 +2331,32 @@ Nodes:
23242331
break Nodes
23252332
}
23262333

2327-
sig, _ := c.pkg.TypesInfo().Types[node.Fun].Type.(*types.Signature)
2334+
if sig, ok := c.pkg.TypesInfo().Types[node.Fun].Type.(*types.Signature); ok {
2335+
// Out of bounds arguments get no inference completion.
2336+
if !sig.Variadic() && exprAtPos(c.pos, node.Args) >= sig.Params().Len() {
2337+
return inf
2338+
}
23282339

2329-
if sig != nil && sig.TypeParams().Len() > 0 && len(c.path) > i+1 {
2330-
// infer an instantiation for the CallExpr from it's context
2331-
switch c.path[i+1].(type) {
2332-
case *ast.AssignStmt, *ast.ReturnStmt, *ast.SendStmt, *ast.ValueSpec:
2333-
// Defer call to reverseInferExpectedCallParamType so we can provide it the
2334-
// inferences about its parent node.
2335-
defer func(sig *types.Signature) {
2340+
// Call has uninstantiated type parameters.
2341+
// Skips inference for partially instantiated calls until partially
2342+
// instantiating signatures has stronger support.
2343+
if sig.TypeParams().Len() > 0 && c.numTypeArgs(node) == 0 && len(c.path) > i+1 {
2344+
// Use the CallExpr's surroundings to infer an instantiation from its return types.
2345+
switch c.path[i+1].(type) {
2346+
case *ast.AssignStmt, *ast.ReturnStmt, *ast.SendStmt, *ast.ValueSpec:
2347+
// Defer call to reverseInferExpectedCallParamType so we can provide it the
2348+
// inferences about its parent node.
2349+
defer func(sig *types.Signature) {
2350+
inf = c.reverseInferExpectedCallParam(inf, node, sig)
2351+
}(sig)
2352+
continue Nodes
2353+
case *ast.KeyValueExpr:
2354+
c.enclosingCompositeLiteral = enclosingCompositeLiteral(c.path[i:], node.Pos(), c.pkg.TypesInfo())
23362355
inf = c.reverseInferExpectedCallParam(inf, node, sig)
2337-
}(sig)
2338-
continue Nodes
2339-
case *ast.KeyValueExpr:
2340-
c.enclosingCompositeLiteral = enclosingCompositeLiteral(c.path[i:], node.Pos(), c.pkg.TypesInfo())
2341-
inf = c.reverseInferExpectedCallParam(inf, node, sig)
2342-
break Nodes
2356+
break Nodes
2357+
}
23432358
}
2344-
}
23452359

2346-
if sig != nil {
23472360
inf = c.expectedCallParamType(inf, node, sig)
23482361
}
23492362

@@ -2531,79 +2544,82 @@ Nodes:
25312544
return inf
25322545
}
25332546

2534-
func reverseInferSignature(sig *types.Signature, targetType []types.Type) []types.Type {
2535-
if sig.Results().Len() != len(targetType) {
2536-
return nil
2547+
func (c *completer) numTypeArgs(callExpr *ast.CallExpr) int {
2548+
switch fun := callExpr.Fun.(type) {
2549+
case *ast.IndexListExpr:
2550+
return len(fun.Indices)
2551+
case *ast.IndexExpr:
2552+
if typ, ok := c.pkg.TypesInfo().Types[fun.Index]; ok && typeIsValid(typ.Type) {
2553+
return 1
2554+
}
2555+
}
2556+
return 0
2557+
}
2558+
2559+
// reverseInferSignature instantiates the call site of a generic function
2560+
// based on the expected return types. Returns false if inference fails or is invalid.
2561+
func reverseInferSignature(sig *types.Signature, targetType []types.Type) ([]types.Type, bool) {
2562+
if sig.Results().Len() != len(targetType) || len(targetType) == 0 {
2563+
return nil, false
25372564
}
25382565

2539-
tparams := []*types.TypeParam{}
2540-
targs := []types.Type{}
2566+
tparams := make([]*types.TypeParam, sig.TypeParams().Len())
25412567
for i := range sig.TypeParams().Len() {
2542-
tparams = append(tparams, sig.TypeParams().At(i))
2543-
targs = append(targs, nil)
2568+
tparams[i] = sig.TypeParams().At(i)
25442569
}
25452570

2546-
u := newUnifier(tparams, targs)
2571+
u := newUnifier(tparams, make([]types.Type, sig.TypeParams().Len()))
25472572
for i, assignee := range targetType {
2548-
// reverseInferSignature instantiates the call site of a generic function
2549-
// based on the expected return types. Returns nil if inference fails or is invalid.
2550-
//
2551-
// targetType is the expected return types of the function after instantiation.
2573+
// This unification does not check the constraints of the type parameters.
25522574
if !u.unify(sig.Results().At(i).Type(), assignee, unifyMode(unifyModeExact)) {
2553-
return nil
2575+
return nil, false
25542576
}
25552577
}
25562578

2557-
substs := []types.Type{}
2579+
substs := make([]types.Type, sig.TypeParams().Len())
25582580
for i := 0; i < sig.TypeParams().Len(); i++ {
2559-
if v := u.handles[sig.TypeParams().At(i)]; v != nil && *v != nil {
2560-
substs = append(substs, *v)
2561-
} else {
2562-
substs = append(substs, nil)
2581+
if sub := u.handles[sig.TypeParams().At(i)]; sub != nil && *sub != nil {
2582+
// Ensure the inferred subst is assignable to the type parameter's constraint.
2583+
if !assignableTo(*sub, sig.TypeParams().At(i).Constraint()) {
2584+
return nil, false
2585+
}
2586+
substs[i] = *sub
25632587
}
25642588
}
25652589

2566-
return substs
2590+
return substs, true
25672591
}
25682592

2569-
func (c *completer) reverseInferredSubstitions(inf candidateInference, sig *types.Signature) []types.Type {
2570-
targetType := []types.Type{}
2593+
// resetExpectedType resets the inference and returns the previous
2594+
// expected inference type.
2595+
func (c *completer) resetExpectedType(inf *candidateInference) []types.Type {
2596+
var targetType []types.Type
25712597
if inf.assignees != nil {
25722598
targetType = inf.assignees
25732599
inf.assignees = nil
25742600
} else if c.enclosingCompositeLiteral != nil && !c.wantStructFieldCompletions() {
25752601
targetType = append(targetType, c.expectedCompositeLiteralType())
25762602
} else if t := inf.objType; t != nil {
2577-
inf.objType = nil
25782603
targetType = append(targetType, t)
2579-
} else {
2580-
return nil
2604+
inf.objType = nil
25812605
}
2582-
return reverseInferSignature(sig, targetType)
2606+
return targetType
25832607
}
25842608

25852609
// reverseInferExpectedTypeParam uses inferences and completion parameters from the parent scope
25862610
// to instantiate the generalized signature of the call node.
25872611
//
25882612
// inf is expected to contain inferences based on the parent of the CallExpr node.
25892613
func (c *completer) reverseInferExpectedTypeParam(inf candidateInference, expectedConstraint types.Type, typeParamIdx int, sig *types.Signature) candidateInference {
2590-
if typeParamIdx >= sig.TypeParams().Len() {
2591-
inf.objType = nil
2592-
inf.assignees = nil
2593-
return inf
2594-
}
2595-
2596-
substs := c.reverseInferredSubstitions(inf, sig)
2597-
if substs != nil && len(substs) > 0 {
2598-
if substs[typeParamIdx] != nil {
2599-
inf.objType = substs[typeParamIdx]
2600-
} else {
2601-
// default to the constraint if no viable substition
2602-
inf.objType = expectedConstraint
2603-
}
2604-
inf.typeName.wantTypeName = true
2605-
inf.typeName.isTypeParam = true
2614+
substs, ok := reverseInferSignature(sig, c.resetExpectedType(&inf))
2615+
if ok && len(substs) > 0 && substs[typeParamIdx] != nil {
2616+
inf.objType = substs[typeParamIdx]
2617+
} else {
2618+
// Default to the constraint if no viable substition.
2619+
inf.objType = expectedConstraint
26062620
}
2621+
inf.typeName.wantTypeName = true
2622+
inf.typeName.isTypeParam = true
26072623
return inf
26082624
}
26092625

@@ -2612,9 +2628,9 @@ func (c *completer) reverseInferExpectedTypeParam(inf candidateInference, expect
26122628
//
26132629
// inf is expected to contain inferences based on the parent of the CallExpr node.
26142630
func (c *completer) reverseInferExpectedCallParam(inf candidateInference, node *ast.CallExpr, sig *types.Signature) candidateInference {
2615-
substs := c.reverseInferredSubstitions(inf, sig)
2616-
if substs == nil {
2617-
return inf
2631+
substs, ok := reverseInferSignature(sig, c.resetExpectedType(&inf))
2632+
if !ok {
2633+
return c.expectedCallParamType(inf, node, sig)
26182634
}
26192635

26202636
for i := range substs {
@@ -2627,17 +2643,15 @@ func (c *completer) reverseInferExpectedCallParam(inf candidateInference, node *
26272643
if inst, ok := inst.(*types.Signature); ok {
26282644
inf = c.expectedCallParamType(inf, node, inst)
26292645

2630-
// Interface type variants shouldn't be candidates as arguments if the caller isn't
2631-
// explicitly instantiated
2646+
// Interface variants can't be passed if the caller isn't
2647+
// explicitly instantiated. Completions must be exactly the interface type.
26322648
//
26332649
// func generic[T any](x T) T { return x }
26342650
// var x someInterface = generic(someImplementor{})
26352651
// ^^ wanted generic[someInterface] but got generic[someImplementor]
26362652
// When offering completions, add a conversion if necessary.
26372653
// generic(someInterface(someImplementor{}))
2638-
if types.IsInterface(inf.objType) {
2639-
inf.convertibleTo = inf.objType
2640-
}
2654+
inf.needsExactType = true
26412655
}
26422656
}
26432657
return inf
@@ -3124,12 +3138,6 @@ func (ci *candidateInference) candTypeMatches(cand *candidate) bool {
31243138
}
31253139

31263140
if ci.convertibleTo != nil && convertibleTo(candType, ci.convertibleTo) {
3127-
// Candidate implements an interface, but needs explicit conversion to the interface
3128-
// type. This happens when passing arguments to a generic function.
3129-
if ci.objType != nil && types.IsInterface(ci.objType) && !types.Identical(candType, ci.convertibleTo) {
3130-
cand.score *= 0.95 // should rank barely lower if it needs a conversion, even though it's perfectly valid
3131-
cand.convertTo = ci.objType
3132-
}
31333141
return true
31343142
}
31353143

@@ -3164,6 +3172,14 @@ func (ci *candidateInference) candTypeMatches(cand *candidate) bool {
31643172
cand.mods = append(cand.mods, takeDotDotDot)
31653173
}
31663174

3175+
// Candidate matches, but isn't exactly identical to the expected type.
3176+
// Apply a conversion to allow it to match.
3177+
if ci.needsExactType && !types.Identical(candType, expType) {
3178+
cand.convertTo = expType
3179+
// Ranks barely lower if it needs a conversion, even though it's perfectly valid.
3180+
cand.score *= 0.95
3181+
}
3182+
31673183
// Lower candidate score for untyped conversions. This avoids
31683184
// ranking untyped constants above candidates with an exact type
31693185
// match. Don't lower score of builtin constants, e.g. "true".

gopls/internal/golang/completion/format.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,9 @@ Suffixes:
196196
}
197197

198198
if cand.convertTo != nil {
199-
p, s := c.formatConvertTo(cand.convertTo)
200-
prefix = p + prefix
201-
suffix = s
199+
conv := c.formatConversion(cand.convertTo)
200+
prefix = conv.prefix + prefix
201+
suffix = conv.suffix
202202
}
203203

204204
if prefix != "" {
@@ -273,7 +273,21 @@ Suffixes:
273273
return item, nil
274274
}
275275

276-
func (c *completer) formatConvertTo(convertTo types.Type) (prefix string, suffix string) {
276+
// conversionEdits represents the string edits needed to make a type conversion
277+
// of an expression.
278+
type conversionEdits struct {
279+
prefix, suffix string
280+
}
281+
282+
// formatConversion returns the edits needed to make a type conversion
283+
// expression, including parentheses if necessary.
284+
//
285+
// Returns empty conversionEdits if convertTo is nil.
286+
func (c *completer) formatConversion(convertTo types.Type) conversionEdits {
287+
if convertTo == nil {
288+
return conversionEdits{}
289+
}
290+
277291
typeName := types.TypeString(convertTo, c.qf)
278292
switch t := convertTo.(type) {
279293
// We need extra parens when casting to these types. For example,
@@ -288,7 +302,7 @@ func (c *completer) formatConvertTo(convertTo types.Type) (prefix string, suffix
288302
typeName = types.TypeString(types.Default(convertTo), c.qf)
289303
}
290304
}
291-
return typeName + "(", ")"
305+
return conversionEdits{prefix: typeName + "(", suffix: ")"}
292306
}
293307

294308
// importEdits produces the text edits necessary to add the given import to the current file.

0 commit comments

Comments
 (0)