internal/lsp: improve completion support for type conversions

Now when completing in code like:

foo := int64(<>)

we prefer candidates whose type is convertible to int64.

Change-Id: Iadc6cdc7de097ac30d8807d6f5aa21d83f89d756
GitHub-Last-Rev: a86dd72496ba752a1f20877c0594ec6a0ed8160e
GitHub-Pull-Request: golang/tools#127
Reviewed-on: https://go-review.googlesource.com/c/tools/+/183941
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index d06f9c6..e060c2b 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -727,6 +727,9 @@
 
 	// assertableFrom is a type that must be assertable to our candidate type.
 	assertableFrom types.Type
+
+	// convertibleTo is a type our candidate type must be convertible to.
+	convertibleTo types.Type
 }
 
 // expectedType returns information about the expected type for an expression at
@@ -741,8 +744,9 @@
 	}
 
 	var (
-		modifiers []typeModifier
-		typ       types.Type
+		modifiers     []typeModifier
+		typ           types.Type
+		convertibleTo types.Type
 	)
 
 Nodes:
@@ -774,6 +778,13 @@
 		case *ast.CallExpr:
 			// Only consider CallExpr args if position falls between parens.
 			if node.Lparen <= c.pos && c.pos <= node.Rparen {
+				// For type conversions like "int64(foo)" we can only infer our
+				// desired type is convertible to int64.
+				if typ := typeConversion(node, c.info); typ != nil {
+					convertibleTo = typ
+					break Nodes
+				}
+
 				if tv, ok := c.info.Types[node.Fun]; ok {
 					if sig, ok := tv.Type.(*types.Signature); ok {
 						if sig.Params().Len() == 0 {
@@ -860,8 +871,9 @@
 	}
 
 	return typeInference{
-		objType:   typ,
-		modifiers: modifiers,
+		objType:       typ,
+		modifiers:     modifiers,
+		convertibleTo: convertibleTo,
 	}
 }
 
@@ -1045,6 +1057,10 @@
 		}
 	}
 
+	if c.expectedType.convertibleTo != nil {
+		return types.ConvertibleTo(objType, c.expectedType.convertibleTo)
+	}
+
 	return false
 }
 
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index ef7d782..8fe0498 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -139,6 +139,27 @@
 	return ok
 }
 
+// typeConversion returns the type being converted to if call is a type
+// conversion expression.
+func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
+	var ident *ast.Ident
+	switch expr := call.Fun.(type) {
+	case *ast.Ident:
+		ident = expr
+	case *ast.SelectorExpr:
+		ident = expr.Sel
+	default:
+		return nil
+	}
+
+	// Type conversion (e.g. "float64(foo)").
+	if fun, _ := info.ObjectOf(ident).(*types.TypeName); fun != nil {
+		return fun.Type()
+	}
+
+	return nil
+}
+
 func formatParams(tup *types.Tuple, variadic bool, qf types.Qualifier) []string {
 	params := make([]string, 0, tup.Len())
 	for i := 0; i < tup.Len(); i++ {
diff --git a/internal/lsp/testdata/rank/convert_rank.go.in b/internal/lsp/testdata/rank/convert_rank.go.in
new file mode 100644
index 0000000..dc0a3a5
--- /dev/null
+++ b/internal/lsp/testdata/rank/convert_rank.go.in
@@ -0,0 +1,12 @@
+package rank
+
+func _() {
+	type strList []string
+	wantsStrList := func(strList) {}
+
+	var (
+		convA string   //@item(convertA, "convA", "string", "var")
+		convB []string //@item(convertB, "convB", "[]string", "var")
+	)
+	wantsStrList(strList(conv)) //@complete("))", convertB, convertA)
+}