diff --git a/cmd/vet/testdata/print.go b/cmd/vet/testdata/print.go index b35faeb39a..ae3bd33a3b 100644 --- a/cmd/vet/testdata/print.go +++ b/cmd/vet/testdata/print.go @@ -75,6 +75,8 @@ func PrintfTests() { fmt.Printf("%x %x %x %x", 3, i, "hi", s) fmt.Printf("%X %X %X %X", 3, i, "hi", s) fmt.Printf("%.*s %d %g", 3, "hi", 23, 2.3) + fmt.Printf("%s", stringerv) + fmt.Printf("%T", stringerv) fmt.Printf("%*%", 2) // Ridiculous but allowed. // Some bad format/argTypes fmt.Printf("%b", "hi") // ERROR "arg .hi. for printf verb %b of wrong type" @@ -94,6 +96,7 @@ func PrintfTests() { fmt.Printf("%U", x) // ERROR "arg x for printf verb %U of wrong type" fmt.Printf("%x", nil) // ERROR "arg nil for printf verb %x of wrong type" fmt.Printf("%X", 2.3) // ERROR "arg 2.3 for printf verb %X of wrong type" + fmt.Printf("%t", stringerv) // ERROR "arg stringerv for printf verb %t of wrong type" fmt.Printf("%.*s %d %g", 3, "hi", 23, 'x') // ERROR "arg 'x' for printf verb %g of wrong type" fmt.Println() // not an error fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call" @@ -116,7 +119,7 @@ func PrintfTests() { const format = "%s %s\n" Printf(format, "hi", "there") Printf(format, "hi") // ERROR "wrong number of args for format in Printf call" - f := new(File) + f := new(stringer) f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call" f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args for format in Warnf call" f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb" @@ -158,7 +161,28 @@ func Printf(format string, args ...interface{}) { panic("don't call - testing only") } +// printf is used by the test so we must declare it. +func printf(format string, args ...interface{}) { + panic("don't call - testing only") +} + // multi is used by the test. func multi() []interface{} { panic("don't call - testing only") } + +type stringer float64 + +var stringerv stringer + +func (*stringer) String() string { + return "string" +} + +func (*stringer) Warn(int, ...interface{}) string { + return "warn" +} + +func (*stringer) Warnf(int, string, ...interface{}) string { + return "warnf" +} diff --git a/cmd/vet/types.go b/cmd/vet/types.go index 2fa7f0272b..970a9af131 100644 --- a/cmd/vet/types.go +++ b/cmd/vet/types.go @@ -57,11 +57,33 @@ func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) { } func (f *File) matchArgType(t printfArgType, arg ast.Expr) bool { - // TODO: for now, we can only test builtin types and untyped constants. + // TODO: for now, we can only test builtin types, untyped constants, and Stringer/Errors. typ := f.pkg.types[arg] if typ == nil { return true } + // If we can use a string and this is a named type, does it implement the Stringer or Error interface? + // TODO: Simplify when we have the IsAssignableTo predicate in go/types. + if named, ok := typ.(*types.Named); ok && t&argString != 0 { + for i := 0; i < named.NumMethods(); i++ { + method := named.Method(i) + // Method must be either String or Error. + if method.Name() != "String" && method.Name() != "Error" { + continue + } + sig := method.Type().(*types.Signature) + // There must be zero arguments and one return. + if sig.Params().Len() != 0 || sig.Results().Len() != 1 { + continue + } + // Result must be string. + if !isUniverseString(sig.Results().At(0).Type()) { + continue + } + // It's a Stringer and we can print it. + return true + } + } basic, ok := typ.Underlying().(*types.Basic) if !ok { return true @@ -161,9 +183,10 @@ func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { return false } // It must have return type "string" from the universe. - result := sig.Results().At(0).Type() - if types.IsIdentical(result, types.Typ[types.String]) { - return true - } - return false + return isUniverseString(sig.Results().At(0).Type()) +} + +// isUniverseString reports whether the type is the predeclared type "string". +func isUniverseString(typ types.Type) bool { + return types.IsIdentical(typ, types.Typ[types.String]) }