Skip to content

Commit f54f780

Browse files
mvdanrsc
authored andcommitted
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>
1 parent a0222ec commit f54f780

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

src/cmd/vet/testdata/print.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,10 @@ type RecursiveStruct2 struct {
484484

485485
var recursiveStruct1V = &RecursiveStruct1{}
486486

487+
type unexportedInterface struct {
488+
f interface{}
489+
}
490+
487491
// Issue 17798: unexported ptrStringer cannot be formatted.
488492
type unexportedStringer struct {
489493
t ptrStringer
@@ -508,7 +512,23 @@ type errorer struct{}
508512

509513
func (e errorer) Error() string { return "errorer" }
510514

515+
type unexportedCustomError struct {
516+
e errorer
517+
}
518+
519+
type errorInterface interface {
520+
error
521+
ExtraMethod()
522+
}
523+
524+
type unexportedErrorInterface struct {
525+
e errorInterface
526+
}
527+
511528
func UnexportedStringerOrError() {
529+
fmt.Printf("%s", unexportedInterface{"foo"}) // ok; prints {foo}
530+
fmt.Printf("%s", unexportedInterface{3}) // ok; we can't see the problem
531+
512532
us := unexportedStringer{}
513533
fmt.Printf("%s", us) // ERROR "Printf format %s has arg us of wrong type testdata.unexportedStringer"
514534
fmt.Printf("%s", &us) // ERROR "Printf format %s has arg &us of wrong type [*]testdata.unexportedStringer"
@@ -534,10 +554,18 @@ func UnexportedStringerOrError() {
534554
fmt.Printf("%s", uef) // ERROR "Printf format %s has arg uef of wrong type testdata.unexportedErrorOtherFields"
535555
fmt.Printf("%s", &uef) // ERROR "Printf format %s has arg &uef of wrong type [*]testdata.unexportedErrorOtherFields"
536556

557+
uce := unexportedCustomError{
558+
e: errorer{},
559+
}
560+
fmt.Printf("%s", uce) // ERROR "Printf format %s has arg uce of wrong type testdata.unexportedCustomError"
561+
562+
uei := unexportedErrorInterface{}
563+
fmt.Printf("%s", uei) // ERROR "Printf format %s has arg uei of wrong type testdata.unexportedErrorInterface"
537564
fmt.Println("foo\n", "bar") // not an error
538-
fmt.Println("foo\n") // ERROR "Println arg list ends with redundant newline"
539-
fmt.Println("foo\\n") // not an error
540-
fmt.Println(`foo\n`) // not an error
565+
566+
fmt.Println("foo\n") // ERROR "Println arg list ends with redundant newline"
567+
fmt.Println("foo\\n") // not an error
568+
fmt.Println(`foo\n`) // not an error
541569

542570
intSlice := []int{3, 4}
543571
fmt.Printf("%s", intSlice) // ERROR "Printf format %s has arg intSlice of wrong type \[\]int"

src/cmd/vet/types.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,19 @@ func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Exp
269269
}
270270

271271
func isConvertibleToString(typ types.Type) bool {
272-
return types.AssertableTo(errorType, typ) || stringerType != nil && types.AssertableTo(stringerType, typ)
272+
if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil {
273+
// We explicitly don't want untyped nil, which is
274+
// convertible to both of the interfaces below, as it
275+
// would just panic anyway.
276+
return false
277+
}
278+
if types.ConvertibleTo(typ, errorType) {
279+
return true // via .Error()
280+
}
281+
if stringerType != nil && types.ConvertibleTo(typ, stringerType) {
282+
return true // via .String()
283+
}
284+
return false
273285
}
274286

275287
// hasBasicType reports whether x's type is a types.Basic with the given kind.

0 commit comments

Comments
 (0)