internal/lsp/source: improve completion of printf operands

We now rank printf operand candidates according to the corresponding
formatting verb. We follow what fmt allows for the most part, but I
omitted some things because they are uncommon and would result in many
false positives, or didn't seem worth it to support:

- We don't prefer fmt.Stringer or error types for "%x" or "%X".
- We don't prefer pointers for any verbs except "%p".
- We don't prefer recursive application of verbs (e.g. we won't prefer
  []string for "%s").

I decided against sharing code with the printf analyzer. It was
tangled somewhat with go/analysis, and I needed only a very small
subset of the format parsing.

I tweaked candidate type evaluation to accommodate the printf hints.
We now skip expected type of "interface{}" when matching candidate
types because it matches everything and would always supersede the
coarser object kind checks.

Fixes golang/go#40485.

Change-Id: I6440702e33d5ec85d701f8be65453044b5dab746
Reviewed-on: https://go-review.googlesource.com/c/tools/+/246699
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 2955af6..639e6a6 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -1545,12 +1545,21 @@
 type objKind int
 
 const (
+	kindAny   objKind = 0
 	kindArray objKind = 1 << iota
 	kindSlice
 	kindChan
 	kindMap
 	kindStruct
 	kindString
+	kindInt
+	kindBool
+	kindBytes
+	kindPtr
+	kindFloat
+	kindComplex
+	kindError
+	kindStringer
 	kindFunc
 )
 
@@ -1749,6 +1758,9 @@
 							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)
 						}
 					}
 				}
@@ -2288,6 +2300,7 @@
 	)
 	if ci.objType != nil {
 		expTypes = append(expTypes, ci.objType)
+
 		if ci.variadic {
 			variadicType = types.NewSlice(ci.objType)
 			expTypes = append(expTypes, variadicType)
@@ -2305,31 +2318,11 @@
 			return true
 		}
 
-		if len(expTypes) == 0 {
-			// If we have no expected type but were able to apply type
-			// modifiers to our candidate type, count that as a match. This
-			// handles cases like:
-			//
-			//   var foo chan int
-			//   <-fo<>
-			//
-			// There is no exected type at "<>", but we were able to apply
-			// the "<-" type modifier to "foo", so it matches.
-			if len(ci.modifiers) > 0 {
-				return true
-			}
-
-			// If we have no expected type, fall back to checking the
-			// expected "kind" of object, if available.
-			if ci.kindMatches(candType) {
-				if ci.objKind == kindFunc {
-					cand.expandFuncCall = true
-				}
-				return true
-			}
-		}
-
 		for _, expType := range expTypes {
+			if isEmptyInterface(expType) {
+				continue
+			}
+
 			matches, untyped := ci.typeMatches(expType, candType)
 			if !matches {
 				continue
@@ -2349,6 +2342,31 @@
 			return true
 		}
 
+		// If we don't have a specific expected type, fall back to coarser
+		// object kind checks.
+		if ci.objType == nil || isEmptyInterface(ci.objType) {
+			// If we were able to apply type modifiers to our candidate type,
+			// count that as a match. For example:
+			//
+			//   var foo chan int
+			//   <-fo<>
+			//
+			// We were able to apply the "<-" type modifier to "foo", so "foo"
+			// matches.
+			if len(ci.modifiers) > 0 {
+				return true
+			}
+
+			// If we didn't have an exact type match, check if our object kind
+			// matches.
+			if ci.kindMatches(candType) {
+				if ci.objKind == kindFunc {
+					cand.expandFuncCall = true
+				}
+				return true
+			}
+		}
+
 		return false
 	})
 }
@@ -2382,7 +2400,7 @@
 // kindMatches reports whether candType's kind matches our expected
 // kind (e.g. slice, map, etc.).
 func (ci *candidateInference) kindMatches(candType types.Type) bool {
-	return ci.objKind&candKind(candType) > 0
+	return ci.objKind > 0 && ci.objKind&candKind(candType) > 0
 }
 
 // assigneesMatch reports whether an invocation of sig matches the
@@ -2508,30 +2526,74 @@
 	return false
 }
 
+var (
+	// "interface { Error() string }" (i.e. error)
+	errorIntf = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
+
+	// "interface { String() string }" (i.e. fmt.Stringer)
+	stringerIntf = types.NewInterfaceType([]*types.Func{
+		types.NewFunc(token.NoPos, nil, "String", types.NewSignature(
+			nil,
+			nil,
+			types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])),
+			false,
+		)),
+	}, nil).Complete()
+
+	byteType = types.Universe.Lookup("byte").Type()
+)
+
 // candKind returns the objKind of candType, if any.
 func candKind(candType types.Type) objKind {
+	var kind objKind
+
 	switch t := candType.Underlying().(type) {
 	case *types.Array:
-		return kindArray
+		kind |= kindArray
+		if t.Elem() == byteType {
+			kind |= kindBytes
+		}
 	case *types.Slice:
-		return kindSlice
+		kind |= kindSlice
+		if t.Elem() == byteType {
+			kind |= kindBytes
+		}
 	case *types.Chan:
-		return kindChan
+		kind |= kindChan
 	case *types.Map:
-		return kindMap
+		kind |= kindMap
 	case *types.Pointer:
+		kind |= kindPtr
+
 		// Some builtins handle array pointers as arrays, so just report a pointer
 		// to an array as an array.
 		if _, isArray := t.Elem().Underlying().(*types.Array); isArray {
-			return kindArray
+			kind |= kindArray
 		}
 	case *types.Basic:
-		if t.Info()&types.IsString > 0 {
-			return kindString
+		switch info := t.Info(); {
+		case info&types.IsString > 0:
+			kind |= kindString
+		case info&types.IsInteger > 0:
+			kind |= kindInt
+		case info&types.IsFloat > 0:
+			kind |= kindFloat
+		case info&types.IsComplex > 0:
+			kind |= kindComplex
+		case info&types.IsBoolean > 0:
+			kind |= kindBool
 		}
 	case *types.Signature:
 		return kindFunc
 	}
 
-	return 0
+	if types.Implements(candType, errorIntf) {
+		kind |= kindError
+	}
+
+	if types.Implements(candType, stringerIntf) {
+		kind |= kindStringer
+	}
+
+	return kind
 }
diff --git a/internal/lsp/source/completion_printf.go b/internal/lsp/source/completion_printf.go
new file mode 100644
index 0000000..e13a938
--- /dev/null
+++ b/internal/lsp/source/completion_printf.go
@@ -0,0 +1,172 @@
+// 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 (
+	"go/ast"
+	"go/constant"
+	"go/types"
+	"strconv"
+	"strings"
+	"unicode/utf8"
+)
+
+// printfArgKind returns the expected objKind when completing a
+// printf-like operand. call is the printf-like function call, and
+// argIdx is the index of call.Args being completed.
+func printfArgKind(info *types.Info, call *ast.CallExpr, argIdx int) objKind {
+	// Printf-like function name must end in "f".
+	fn := exprObj(info, call.Fun)
+	if fn == nil || !strings.HasSuffix(fn.Name(), "f") {
+		return kindAny
+	}
+
+	sig, _ := fn.Type().(*types.Signature)
+	if sig == nil {
+		return kindAny
+	}
+
+	// Must be variadic and take at least two params.
+	numParams := sig.Params().Len()
+	if !sig.Variadic() || numParams < 2 || argIdx < numParams-1 {
+		return kindAny
+	}
+
+	// Param preceding variadic args must be a (format) string.
+	if !types.Identical(sig.Params().At(numParams-2).Type(), types.Typ[types.String]) {
+		return kindAny
+	}
+
+	// Format string must be a constant.
+	strArg := info.Types[call.Args[numParams-2]].Value
+	if strArg == nil || strArg.Kind() != constant.String {
+		return kindAny
+	}
+
+	return formatOperandKind(constant.StringVal(strArg), argIdx-(numParams-1)+1)
+}
+
+// formatOperandKind returns the objKind corresponding to format's
+// operandIdx'th operand.
+func formatOperandKind(format string, operandIdx int) objKind {
+	var (
+		prevOperandIdx int
+		kind           = kindAny
+	)
+	for {
+		i := strings.Index(format, "%")
+		if i == -1 {
+			break
+		}
+
+		var operands []formatOperand
+		format, operands = parsePrintfVerb(format[i+1:], prevOperandIdx)
+
+		// Check if any this verb's operands correspond to our target
+		// operandIdx.
+		for _, v := range operands {
+			if v.idx == operandIdx {
+				if kind == kindAny {
+					kind = v.kind
+				} else if v.kind != kindAny {
+					// If mulitple verbs refer to the same operand, take the
+					// intersection of their kinds.
+					kind &= v.kind
+				}
+			}
+
+			prevOperandIdx = v.idx
+		}
+	}
+	return kind
+}
+
+type formatOperand struct {
+	// idx is the one-based printf operand index.
+	idx int
+	// kind is a mask of expected kinds of objects for this operand.
+	kind objKind
+}
+
+// parsePrintfVerb parses the leading printf verb in f. The opening
+// "%" must already be trimmed from f. prevIdx is the previous
+// operand's index, or zero if this is the first verb. The format
+// string is returned with the leading verb removed. Multiple operands
+// can be returned in the case of dynamic widths such as "%*.*f".
+func parsePrintfVerb(f string, prevIdx int) (string, []formatOperand) {
+	var verbs []formatOperand
+
+	addVerb := func(k objKind) {
+		verbs = append(verbs, formatOperand{
+			idx:  prevIdx + 1,
+			kind: k,
+		})
+		prevIdx++
+	}
+
+	for len(f) > 0 {
+		// Trim first rune off of f so we are guaranteed to make progress.
+		r, l := utf8.DecodeRuneInString(f)
+		f = f[l:]
+
+		// We care about three things:
+		// 1. The verb, which maps directly to object kind.
+		// 2. Explicit operand indices like "%[2]s".
+		// 3. Dynamic widths using "*".
+		switch r {
+		case '%':
+			return f, nil
+		case '*':
+			addVerb(kindInt)
+			continue
+		case '[':
+			// Parse operand index as in "%[2]s".
+			i := strings.Index(f, "]")
+			if i == -1 {
+				return f, nil
+			}
+
+			idx, err := strconv.Atoi(f[:i])
+			f = f[i+1:]
+			if err != nil {
+				return f, nil
+			}
+
+			prevIdx = idx - 1
+			continue
+		case 'v', 'T':
+			addVerb(kindAny)
+		case 't':
+			addVerb(kindBool)
+		case 'c', 'd', 'o', 'O', 'U':
+			addVerb(kindInt)
+		case 'e', 'E', 'f', 'F', 'g', 'G':
+			addVerb(kindFloat | kindComplex)
+		case 'b':
+			addVerb(kindInt | kindFloat | kindComplex | kindBytes)
+		case 'q', 's':
+			addVerb(kindString | kindBytes | kindStringer | kindError)
+		case 'x', 'X':
+			// Omit kindStringer and kindError though technically allowed.
+			addVerb(kindString | kindBytes | kindInt | kindFloat | kindComplex)
+		case 'p':
+			addVerb(kindPtr | kindSlice)
+		case 'w':
+			addVerb(kindError)
+		case '+', '-', '#', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+			// Flag or numeric width/precicision value.
+			continue
+		default:
+			// Assume unrecognized rune is a custom fmt.Formatter verb.
+			addVerb(kindAny)
+		}
+
+		if len(verbs) > 0 {
+			break
+		}
+	}
+
+	return f, verbs
+}
diff --git a/internal/lsp/source/completion_printf_test.go b/internal/lsp/source/completion_printf_test.go
new file mode 100644
index 0000000..ab20f27
--- /dev/null
+++ b/internal/lsp/source/completion_printf_test.go
@@ -0,0 +1,72 @@
+// 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 (
+	"fmt"
+	"testing"
+)
+
+func TestFormatOperandKind(t *testing.T) {
+	cases := []struct {
+		f    string
+		idx  int
+		kind objKind
+	}{
+		{"", 1, kindAny},
+		{"%", 1, kindAny},
+		{"%%%", 1, kindAny},
+		{"%[1", 1, kindAny},
+		{"%[?%s", 2, kindAny},
+		{"%[abc]v", 1, kindAny},
+
+		{"%v", 1, kindAny},
+		{"%T", 1, kindAny},
+		{"%t", 1, kindBool},
+		{"%d", 1, kindInt},
+		{"%c", 1, kindInt},
+		{"%o", 1, kindInt},
+		{"%O", 1, kindInt},
+		{"%U", 1, kindInt},
+		{"%e", 1, kindFloat | kindComplex},
+		{"%E", 1, kindFloat | kindComplex},
+		{"%f", 1, kindFloat | kindComplex},
+		{"%F", 1, kindFloat | kindComplex},
+		{"%g", 1, kindFloat | kindComplex},
+		{"%G", 1, kindFloat | kindComplex},
+		{"%b", 1, kindInt | kindFloat | kindComplex | kindBytes},
+		{"%q", 1, kindString | kindBytes | kindStringer | kindError},
+		{"%s", 1, kindString | kindBytes | kindStringer | kindError},
+		{"%x", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex},
+		{"%X", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex},
+		{"%p", 1, kindPtr | kindSlice},
+		{"%w", 1, kindError},
+
+		{"%1.2f", 1, kindFloat | kindComplex},
+		{"%*f", 1, kindInt},
+		{"%*f", 2, kindFloat | kindComplex},
+		{"%*.*f", 1, kindInt},
+		{"%*.*f", 2, kindInt},
+		{"%*.*f", 3, kindFloat | kindComplex},
+		{"%[3]*.[2]*[1]f", 1, kindFloat | kindComplex},
+		{"%[3]*.[2]*[1]f", 2, kindInt},
+		{"%[3]*.[2]*[1]f", 3, kindInt},
+
+		{"foo %% %d", 1, kindInt},
+		{"%#-12.34f", 1, kindFloat | kindComplex},
+		{"% d", 1, kindInt},
+
+		{"%s %[1]X %d", 1, kindString | kindBytes},
+		{"%s %[1]X %d", 2, kindInt},
+	}
+
+	for _, c := range cases {
+		t.Run(fmt.Sprintf("%q#%d", c.f, c.idx), func(t *testing.T) {
+			if got := formatOperandKind(c.f, c.idx); got != c.kind {
+				t.Errorf("expected %d (%[1]b), got %d (%[2]b)", c.kind, got)
+			}
+		})
+	}
+}
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index d52f90b..a043a17 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -457,11 +457,11 @@
 	return nil
 }
 
-// typeConversion returns the type being converted to if call is a type
-// conversion expression.
-func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
+// exprObj returns the types.Object associated with the *ast.Ident or
+// *ast.SelectorExpr e.
+func exprObj(info *types.Info, e ast.Expr) types.Object {
 	var ident *ast.Ident
-	switch expr := call.Fun.(type) {
+	switch expr := e.(type) {
 	case *ast.Ident:
 		ident = expr
 	case *ast.SelectorExpr:
@@ -470,8 +470,14 @@
 		return nil
 	}
 
+	return info.ObjectOf(ident)
+}
+
+// typeConversion returns the type being converted to if call is a type
+// conversion expression.
+func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
 	// Type conversion (e.g. "float64(foo)").
-	if fun, _ := info.ObjectOf(ident).(*types.TypeName); fun != nil {
+	if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil {
 		return fun.Type()
 	}
 
diff --git a/internal/lsp/testdata/lsp/primarymod/printf/printf.go b/internal/lsp/testdata/lsp/primarymod/printf/printf.go
new file mode 100644
index 0000000..6e56549
--- /dev/null
+++ b/internal/lsp/testdata/lsp/primarymod/printf/printf.go
@@ -0,0 +1,33 @@
+package printf
+
+import "fmt"
+
+func myPrintf(string, ...interface{}) {}
+
+func _() {
+	var (
+		aInt      int          //@item(printfInt, "aInt", "int", "var")
+		aFloat    float64      //@item(printfFloat, "aFloat", "float64", "var")
+		aString   string       //@item(printfString, "aString", "string", "var")
+		aBytes    []byte       //@item(printfBytes, "aBytes", "[]byte", "var")
+		aStringer fmt.Stringer //@item(printfStringer, "aStringer", "fmt.Stringer", "var")
+		aError    error        //@item(printfError, "aError", "error", "var")
+		aBool     bool         //@item(printfBool, "aBool", "bool", "var")
+	)
+
+	myPrintf("%d", a)       //@rank(")", printfInt, printfFloat)
+	myPrintf("%s", a)       //@rank(")", printfString, printfInt),rank(")", printfBytes, printfInt),rank(")", printfStringer, printfInt),rank(")", printfError, printfInt)
+	myPrintf("%w", a)       //@rank(")", printfError, printfInt)
+	myPrintf("%x %[1]b", a) //@rank(")", printfInt, printfString)
+
+	fmt.Printf("%t", a) //@rank(")", printfBool, printfInt)
+
+	fmt.Fprintf(nil, "%f", a) //@rank(")", printfFloat, printfInt)
+
+	fmt.Sprintf("%[2]q %[1]*.[3]*[4]f",
+		a, //@rank(",", printfInt, printfFloat)
+		a, //@rank(",", printfString, printfFloat)
+		a, //@rank(",", printfInt, printfFloat)
+		a, //@rank(",", printfFloat, printfInt)
+	)
+}
diff --git a/internal/lsp/testdata/lsp/summary.txt.golden b/internal/lsp/testdata/lsp/summary.txt.golden
index 654d04c..8ff02af 100644
--- a/internal/lsp/testdata/lsp/summary.txt.golden
+++ b/internal/lsp/testdata/lsp/summary.txt.golden
@@ -6,7 +6,7 @@
 UnimportedCompletionsCount = 6
 DeepCompletionsCount = 5
 FuzzyCompletionsCount = 8
-RankedCompletionsCount = 139
+RankedCompletionsCount = 152
 CaseSensitiveCompletionsCount = 4
 DiagnosticsCount = 44
 FoldingRangesCount = 2