Updates #17057.
Change-Id: I54c838d3a44007d4023754e42971e91bfb5e8612
Reviewed-on: https://go-review.googlesource.com/32851
Run-TryBot: Rob Pike <r@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
f.pkg.types[d.Type.Results.List[0].Type].Type == types.Typ[types.String]
}
+// isFormatter reports whether t satisfies fmt.Formatter.
+// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt.
+func (f *File) isFormatter(t types.Type) bool {
+ return formatterType != nil && types.Implements(t, formatterType)
+}
+
// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
// It is constructed by parsePrintfVerb.
type formatState struct {
)
// printVerbs identifies which flags are known to printf for each verb.
-// TODO: A type that implements Formatter may do what it wants, and vet
-// will complain incorrectly.
var printVerbs = []printVerb{
// '-' is a width modifier, always valid.
// '.' is a precision for float, max width for strings.
break
}
}
- if !found {
+
+ // Does current arg implement fmt.Formatter?
+ formatter := false
+ if state.argNum < len(call.Args) {
+ if tv, ok := f.pkg.types[call.Args[state.argNum]]; ok {
+ formatter = f.isFormatter(tv.Type)
+ }
+ }
+
+ if !found && !formatter {
f.Badf(call.Pos(), "unrecognized printf verb %q", state.verb)
return false
}
return false
}
}
- if state.verb == '%' {
+ if state.verb == '%' || formatter {
return true
}
argNum := state.argNums[len(state.argNums)-1]
fmt.Printf("%t", stringerarrayv) // ERROR "arg stringerarrayv for printf verb %t of wrong type"
fmt.Printf("%t", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %t of wrong type"
fmt.Printf("%q", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %q of wrong type"
- fmt.Printf("%d", Formatter(true)) // correct (the type is responsible for formatting)
- fmt.Printf("%s", nonemptyinterface) // correct (the dynamic type of nonemptyinterface may be a stringer)
+ fmt.Printf("%d", Formatter(true)) // ERROR "arg Formatter\(true\) for printf verb %d of wrong type: testdata.Formatter"
+ fmt.Printf("%z", FormatterVal(true)) // correct (the type is responsible for formatting)
+ fmt.Printf("%d", FormatterVal(true)) // correct (the type is responsible for formatting)
+ fmt.Printf("%s", nonemptyinterface) // correct (the type is responsible for formatting)
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"
func (*Formatter) Format(fmt.State, rune) {
}
+// Formatter with value receiver
+type FormatterVal bool
+
+func (FormatterVal) Format(fmt.State, rune) {
+}
+
type RecursiveSlice []RecursiveSlice
var recursiveSliceV = &RecursiveSlice{}
}
}
// If the type implements fmt.Formatter, we have nothing to check.
- // formatterTyp may be nil - be conservative and check for Format method in that case.
- if formatterType != nil && types.Implements(typ, formatterType) || f.hasMethod(typ, "Format") {
+ if f.isFormatter(typ) {
return true
}
// If we can use a string, might arg (dynamically) implement the Stringer or Error interface?