icmd/vet: improved checking for variadic Println-like functions

- Automatically determine the first argument to check.
- Skip checking matching non-variadic functions.
- Skip checking matching functions accepting non-interface{}
  variadic arguments.
- Removed fragile 'magic' code for special cases such as math.Log
  and error interface.

Fixes #15067
Fixes #15099

Change-Id: Ib313557f18b12b36daa493f4b02c598b9503b55b
Reviewed-on: https://go-review.googlesource.com/21513
Run-TryBot: Rob Pike <r@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/src/cmd/vet/print.go b/src/cmd/vet/print.go
index 4e3252f..07499e6 100644
--- a/src/cmd/vet/print.go
+++ b/src/cmd/vet/print.go
@@ -35,20 +35,18 @@
 		if len(name) == 0 {
 			flag.Usage()
 		}
-		skip := 0
+
+		// Backwards compatibility: skip optional first argument
+		// index after the colon.
 		if colon := strings.LastIndex(name, ":"); colon > 0 {
-			var err error
-			skip, err = strconv.Atoi(name[colon+1:])
-			if err != nil {
-				errorf(`illegal format for "Func:N" argument %q; %s`, name, err)
-			}
 			name = name[:colon]
 		}
+
 		name = strings.ToLower(name)
 		if name[len(name)-1] == 'f' {
 			isFormattedPrint[name] = true
 		} else {
-			printList[name] = skip
+			isPrint[name] = true
 		}
 	}
 }
@@ -65,17 +63,20 @@
 	"sprintf": true,
 }
 
-// printList records the unformatted-print functions. The value is the location
-// of the first parameter to be printed. Names are lower-cased so the lookup is
-// case insensitive.
-var printList = map[string]int{
-	"error":  0,
-	"fatal":  0,
-	"fprint": 1, "fprintln": 1,
-	"log":   0,
-	"panic": 0, "panicln": 0,
-	"print": 0, "println": 0,
-	"sprint": 0, "sprintln": 0,
+// isPrint records the unformatted-print functions. Names are lower-cased
+// so the lookup is case insensitive.
+var isPrint = map[string]bool{
+	"error":    true,
+	"fatal":    true,
+	"fprint":   true,
+	"fprintln": true,
+	"log":      true,
+	"panic":    true,
+	"panicln":  true,
+	"print":    true,
+	"println":  true,
+	"sprint":   true,
+	"sprintln": true,
 }
 
 // formatString returns the format string argument and its index within
@@ -171,8 +172,8 @@
 		f.checkPrintf(call, Name)
 		return
 	}
-	if skip, ok := printList[name]; ok {
-		f.checkPrint(call, Name, skip)
+	if _, ok := isPrint[name]; ok {
+		f.checkPrint(call, Name)
 		return
 	}
 }
@@ -583,25 +584,36 @@
 }
 
 // checkPrint checks a call to an unformatted print routine such as Println.
-// call.Args[firstArg] is the first argument to be printed.
-func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) {
-	isLn := strings.HasSuffix(name, "ln")
-	isF := strings.HasPrefix(name, "F")
-	args := call.Args
-	if name == "Log" && len(args) > 0 {
-		// Special case: Don't complain about math.Log or cmplx.Log.
-		// Not strictly necessary because the only complaint likely is for Log("%d")
-		// but it feels wrong to check that math.Log is a good print function.
-		if sel, ok := args[0].(*ast.SelectorExpr); ok {
-			if x, ok := sel.X.(*ast.Ident); ok {
-				if x.Name == "math" || x.Name == "cmplx" {
-					return
-				}
+func (f *File) checkPrint(call *ast.CallExpr, name string) {
+	firstArg := 0
+	typ := f.pkg.types[call.Fun].Type
+	if typ != nil {
+		if sig, ok := typ.(*types.Signature); ok {
+			if !sig.Variadic() {
+				// Skip checking non-variadic functions.
+				return
+			}
+			params := sig.Params()
+			firstArg = params.Len() - 1
+
+			typ := params.At(firstArg).Type()
+			typ = typ.(*types.Slice).Elem()
+			it, ok := typ.(*types.Interface)
+			if !ok || !it.Empty() {
+				// Skip variadic functions accepting non-interface{} args.
+				return
 			}
 		}
 	}
+	args := call.Args
+	if len(args) <= firstArg {
+		// Skip calls without variadic args.
+		return
+	}
+	args = args[firstArg:]
+
 	// check for Println(os.Stderr, ...)
-	if firstArg == 0 && !isF && len(args) > 0 {
+	if firstArg == 0 {
 		if sel, ok := args[0].(*ast.SelectorExpr); ok {
 			if x, ok := sel.X.(*ast.Ident); ok {
 				if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
@@ -610,31 +622,15 @@
 			}
 		}
 	}
-	if len(args) <= firstArg {
-		// If we have a call to a method called Error that satisfies the Error interface,
-		// then it's ok. Otherwise it's something like (*T).Error from the testing package
-		// and we need to check it.
-		if name == "Error" && f.isErrorMethodCall(call) {
-			return
-		}
-		// If it's an Error call now, it's probably for printing errors.
-		if !isLn {
-			// Check the signature to be sure: there are niladic functions called "error".
-			if firstArg != 0 || f.numArgsInSignature(call) != firstArg {
-				f.Badf(call.Pos(), "no args in %s call", name)
-			}
-		}
-		return
-	}
-	arg := args[firstArg]
+	arg := args[0]
 	if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
 		if strings.Contains(lit.Value, "%") {
 			f.Badf(call.Pos(), "possible formatting directive in %s call", name)
 		}
 	}
-	if isLn {
+	if strings.HasSuffix(name, "ln") {
 		// The last item, if a string, should not have a newline.
-		arg = args[len(call.Args)-1]
+		arg = args[len(args)-1]
 		if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING {
 			if strings.HasSuffix(lit.Value, `\n"`) {
 				f.Badf(call.Pos(), "%s call ends with newline", name)