1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:14:46 -07:00
go/internal/lsp/source/completion_builtin.go
Muir Manders 90abf76919 internal/lsp/source: fix a couple issues completing append() args
In this example:

     p := &[]int{}
     append([]int{}, *<>)

At <> we completed to "**p" instead of "*p...". There were two fixes:

1. builtinArgType() wasn't propagating the "modifiers", so we were
   forgetting about the preceding "*" pointer indirection and
   inserting it again with the completion. Fix by propagating
   modifiers.
2. The candidate formatting responsible for adding "..." had over
   simplified logic to determine if we are completing the variadic
   param. Now instead the candidate evaluation code marks the
   candidate as "variadic" so the formatting doesn't have to think at
   all.

Change-Id: Ib71ee8ecfafb915df331f1d2e55b76f76a530243
Reviewed-on: https://go-review.googlesource.com/c/tools/+/248018
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-08-15 16:56:00 +00:00

136 lines
3.4 KiB
Go

// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"go/ast"
"go/types"
)
// builtinArgKind determines the expected object kind for a builtin
// argument. It attempts to use the AST hints from builtin.go where
// possible.
func (c *completer) builtinArgKind(ctx context.Context, obj types.Object, call *ast.CallExpr) objKind {
builtin, err := c.snapshot.BuiltinPackage(ctx)
if err != nil {
return 0
}
exprIdx := exprAtPos(c.pos, call.Args)
builtinObj := builtin.Package.Scope.Lookup(obj.Name())
if builtinObj == nil {
return 0
}
decl, ok := builtinObj.Decl.(*ast.FuncDecl)
if !ok || exprIdx >= len(decl.Type.Params.List) {
return 0
}
switch ptyp := decl.Type.Params.List[exprIdx].Type.(type) {
case *ast.ChanType:
return kindChan
case *ast.ArrayType:
return kindSlice
case *ast.MapType:
return kindMap
case *ast.Ident:
switch ptyp.Name {
case "Type":
switch obj.Name() {
case "make":
return kindChan | kindSlice | kindMap
case "len":
return kindSlice | kindMap | kindArray | kindString | kindChan
case "cap":
return kindSlice | kindArray | kindChan
}
}
}
return 0
}
// builtinArgType infers the type of an argument to a builtin
// function. parentInf is the inferred type info for the builtin
// call's parent node.
func (c *completer) builtinArgType(obj types.Object, call *ast.CallExpr, parentInf candidateInference) candidateInference {
var (
exprIdx = exprAtPos(c.pos, call.Args)
// Propagate certain properties from our parent's inference.
inf = candidateInference{
typeName: parentInf.typeName,
modifiers: parentInf.modifiers,
}
)
switch obj.Name() {
case "append":
if parentInf.objType == nil {
break
}
inf.objType = parentInf.objType
if exprIdx <= 0 {
break
}
inf.objType = deslice(inf.objType)
// Check if we are completing the variadic append() param.
inf.variadic = exprIdx == 1 && len(call.Args) <= 2
// Penalize the first append() argument as a candidate. You
// don't normally append a slice to itself.
if sliceChain := objChain(c.pkg.GetTypesInfo(), call.Args[0]); len(sliceChain) > 0 {
inf.penalized = append(inf.penalized, penalizedObj{objChain: sliceChain, penalty: 0.9})
}
case "delete":
if exprIdx > 0 && len(call.Args) > 0 {
// Try to fill in expected type of map key.
firstArgType := c.pkg.GetTypesInfo().TypeOf(call.Args[0])
if firstArgType != nil {
if mt, ok := firstArgType.Underlying().(*types.Map); ok {
inf.objType = mt.Key()
}
}
}
case "copy":
var t1, t2 types.Type
if len(call.Args) > 0 {
t1 = c.pkg.GetTypesInfo().TypeOf(call.Args[0])
if len(call.Args) > 1 {
t2 = c.pkg.GetTypesInfo().TypeOf(call.Args[1])
}
}
// Fill in expected type of either arg if the other is already present.
if exprIdx == 1 && t1 != nil {
inf.objType = t1
} else if exprIdx == 0 && t2 != nil {
inf.objType = t2
}
case "new":
inf.typeName.wantTypeName = true
if parentInf.objType != nil {
// Expected type for "new" is the de-pointered parent type.
if ptr, ok := parentInf.objType.Underlying().(*types.Pointer); ok {
inf.objType = ptr.Elem()
}
}
case "make":
if exprIdx == 0 {
inf.typeName.wantTypeName = true
inf.objType = parentInf.objType
} else {
inf.objType = types.Typ[types.Int]
}
}
return inf
}