mirror of
https://github.com/golang/go
synced 2024-11-19 18:54:41 -07:00
cmd/vet: unexported interface{} fields on %s are ok
For example, the following program is valid: type T struct { f interface{} } func main() { fmt.Printf("%s", T{"foo"}) // prints {foo} } Since the field is of type interface{}, we might have any value in it. For example, if we had T{3}, fmt would complain. However, not knowing what the type under the interface is, we must be conservative. However, as shown in #17798, we should issue an error if the field's type is statically known to implement the error or fmt.Stringer interfaces. In those cases, the user likely wanted the %s format to call those methods. Keep the vet error in those cases. While at it, add more field type test cases, such as custom error types, and interfaces that extend the error interface. Fixes #23563. Change-Id: I063885955555917c59da000391b603f0d6dce432 Reviewed-on: https://go-review.googlesource.com/90516 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
a0222ec518
commit
f54f780d2f
34
src/cmd/vet/testdata/print.go
vendored
34
src/cmd/vet/testdata/print.go
vendored
@ -484,6 +484,10 @@ type RecursiveStruct2 struct {
|
||||
|
||||
var recursiveStruct1V = &RecursiveStruct1{}
|
||||
|
||||
type unexportedInterface struct {
|
||||
f interface{}
|
||||
}
|
||||
|
||||
// Issue 17798: unexported ptrStringer cannot be formatted.
|
||||
type unexportedStringer struct {
|
||||
t ptrStringer
|
||||
@ -508,7 +512,23 @@ type errorer struct{}
|
||||
|
||||
func (e errorer) Error() string { return "errorer" }
|
||||
|
||||
type unexportedCustomError struct {
|
||||
e errorer
|
||||
}
|
||||
|
||||
type errorInterface interface {
|
||||
error
|
||||
ExtraMethod()
|
||||
}
|
||||
|
||||
type unexportedErrorInterface struct {
|
||||
e errorInterface
|
||||
}
|
||||
|
||||
func UnexportedStringerOrError() {
|
||||
fmt.Printf("%s", unexportedInterface{"foo"}) // ok; prints {foo}
|
||||
fmt.Printf("%s", unexportedInterface{3}) // ok; we can't see the problem
|
||||
|
||||
us := unexportedStringer{}
|
||||
fmt.Printf("%s", us) // ERROR "Printf format %s has arg us of wrong type testdata.unexportedStringer"
|
||||
fmt.Printf("%s", &us) // ERROR "Printf format %s has arg &us of wrong type [*]testdata.unexportedStringer"
|
||||
@ -534,10 +554,18 @@ func UnexportedStringerOrError() {
|
||||
fmt.Printf("%s", uef) // ERROR "Printf format %s has arg uef of wrong type testdata.unexportedErrorOtherFields"
|
||||
fmt.Printf("%s", &uef) // ERROR "Printf format %s has arg &uef of wrong type [*]testdata.unexportedErrorOtherFields"
|
||||
|
||||
uce := unexportedCustomError{
|
||||
e: errorer{},
|
||||
}
|
||||
fmt.Printf("%s", uce) // ERROR "Printf format %s has arg uce of wrong type testdata.unexportedCustomError"
|
||||
|
||||
uei := unexportedErrorInterface{}
|
||||
fmt.Printf("%s", uei) // ERROR "Printf format %s has arg uei of wrong type testdata.unexportedErrorInterface"
|
||||
fmt.Println("foo\n", "bar") // not an error
|
||||
fmt.Println("foo\n") // ERROR "Println arg list ends with redundant newline"
|
||||
fmt.Println("foo\\n") // not an error
|
||||
fmt.Println(`foo\n`) // not an error
|
||||
|
||||
fmt.Println("foo\n") // ERROR "Println arg list ends with redundant newline"
|
||||
fmt.Println("foo\\n") // not an error
|
||||
fmt.Println(`foo\n`) // not an error
|
||||
|
||||
intSlice := []int{3, 4}
|
||||
fmt.Printf("%s", intSlice) // ERROR "Printf format %s has arg intSlice of wrong type \[\]int"
|
||||
|
@ -269,7 +269,19 @@ func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Exp
|
||||
}
|
||||
|
||||
func isConvertibleToString(typ types.Type) bool {
|
||||
return types.AssertableTo(errorType, typ) || stringerType != nil && types.AssertableTo(stringerType, typ)
|
||||
if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
|
||||
// We explicitly don't want untyped nil, which is
|
||||
// convertible to both of the interfaces below, as it
|
||||
// would just panic anyway.
|
||||
return false
|
||||
}
|
||||
if types.ConvertibleTo(typ, errorType) {
|
||||
return true // via .Error()
|
||||
}
|
||||
if stringerType != nil && types.ConvertibleTo(typ, stringerType) {
|
||||
return true // via .String()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasBasicType reports whether x's type is a types.Basic with the given kind.
|
||||
|
Loading…
Reference in New Issue
Block a user