Skip to content

Commit ddcef59

Browse files
committed
go/analysis/passes/printf: warn about verbs that don’t match a type
parameter’s type set Pending the acceptance of golang/go#49038, this CL updates the printf analyzer to warn if any elements of a type parameter's type set do not match the printf verb being considered. Since this may be a source of confusion, it also refactors slightly to allow providing a reason why a match failed. Updates golang/go#48704 Updates golang/go#49038 Change-Id: I92d4d58dd0e9218ae9d522bf2a2999f4c6f9fd84 Reviewed-on: https://go-review.googlesource.com/c/tools/+/351832 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Tim King <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent b8b8e7f commit ddcef59

File tree

5 files changed

+265
-62
lines changed

5 files changed

+265
-62
lines changed

go/analysis/passes/printf/printf.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -879,8 +879,12 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
879879
return
880880
}
881881
arg := call.Args[argNum]
882-
if !matchArgType(pass, argInt, arg) {
883-
pass.ReportRangef(call, "%s format %s uses non-int %s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg))
882+
if reason, ok := matchArgType(pass, argInt, arg); !ok {
883+
details := ""
884+
if reason != "" {
885+
details = " (" + reason + ")"
886+
}
887+
pass.ReportRangef(call, "%s format %s uses non-int %s%s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg), details)
884888
return false
885889
}
886890
}
@@ -897,12 +901,16 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o
897901
pass.ReportRangef(call, "%s format %s arg %s is a func value, not called", state.name, state.format, analysisutil.Format(pass.Fset, arg))
898902
return false
899903
}
900-
if !matchArgType(pass, v.typ, arg) {
904+
if reason, ok := matchArgType(pass, v.typ, arg); !ok {
901905
typeString := ""
902906
if typ := pass.TypesInfo.Types[arg].Type; typ != nil {
903907
typeString = typ.String()
904908
}
905-
pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString)
909+
details := ""
910+
if reason != "" {
911+
details = " (" + reason + ")"
912+
}
913+
pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s%s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString, details)
906914
return false
907915
}
908916
if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) {

go/analysis/passes/printf/printf_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ import (
99

1010
"golang.org/x/tools/go/analysis/analysistest"
1111
"golang.org/x/tools/go/analysis/passes/printf"
12+
"golang.org/x/tools/internal/typeparams"
1213
)
1314

1415
func Test(t *testing.T) {
1516
testdata := analysistest.TestData()
1617
printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
17-
analysistest.Run(t, testdata, printf.Analyzer, "a", "b", "nofmt")
18+
19+
tests := []string{"a", "b", "nofmt"}
20+
if typeparams.Enabled {
21+
tests = append(tests, "typeparams")
22+
}
23+
analysistest.Run(t, testdata, printf.Analyzer, tests...)
1824
}

go/analysis/passes/printf/testdata/src/a/a.go

+2
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,7 @@ type unexportedInterface struct {
698698
type unexportedStringer struct {
699699
t ptrStringer
700700
}
701+
701702
type unexportedStringerOtherFields struct {
702703
s string
703704
t ptrStringer
@@ -708,6 +709,7 @@ type unexportedStringerOtherFields struct {
708709
type unexportedError struct {
709710
e error
710711
}
712+
711713
type unexportedErrorOtherFields struct {
712714
s string
713715
e error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.18
6+
// +build go1.18
7+
8+
package typeparams
9+
10+
import "fmt"
11+
12+
func TestBasicTypeParams[T interface{ ~int }, E error, F fmt.Formatter, S fmt.Stringer, A any](t T, e E, f F, s S, a A) {
13+
fmt.Printf("%d", t)
14+
fmt.Printf("%s", t) // want "wrong type.*contains ~int"
15+
fmt.Printf("%v", t)
16+
fmt.Printf("%d", e)
17+
fmt.Printf("%s", e)
18+
fmt.Errorf("%w", e)
19+
fmt.Printf("%a", f)
20+
fmt.Printf("%d", f)
21+
fmt.Printf("%T", f.Format)
22+
fmt.Printf("%p", f.Format)
23+
fmt.Printf("%s", s)
24+
fmt.Errorf("%w", s) // want "wrong type"
25+
fmt.Printf("%d", a) // want "wrong type"
26+
fmt.Printf("%s", a) // want "wrong type"
27+
fmt.Printf("%v", a)
28+
fmt.Printf("%T", a)
29+
}
30+
31+
func TestNestedTypeParams[T interface{ ~int }, S interface{ ~string }]() {
32+
var x struct {
33+
f int
34+
t T
35+
}
36+
fmt.Printf("%d", x)
37+
fmt.Printf("%s", x) // want "wrong type"
38+
var y struct {
39+
f string
40+
t S
41+
}
42+
fmt.Printf("%d", y) // want "wrong type"
43+
fmt.Printf("%s", y)
44+
var m1 map[T]T
45+
fmt.Printf("%d", m1)
46+
fmt.Printf("%s", m1) // want "wrong type"
47+
var m2 map[S]S
48+
fmt.Printf("%d", m2) // want "wrong type"
49+
fmt.Printf("%s", m2)
50+
}
51+
52+
type R struct {
53+
F []R
54+
}
55+
56+
func TestRecursiveTypeDefinition() {
57+
var r []R
58+
fmt.Printf("%d", r) // No error: avoids infinite recursion.
59+
}
60+
61+
func TestRecursiveTypeParams[T1 ~[]T2, T2 ~[]T1 | string, T3 ~struct{ F T3 }](t1 T1, t2 T2, t3 T3) {
62+
// No error is reported on the following lines to avoid infinite recursion.
63+
fmt.Printf("%s", t1)
64+
fmt.Printf("%s", t2)
65+
fmt.Printf("%s", t3)
66+
}
67+
68+
func TestRecusivePointers[T1 ~*T2, T2 ~*T1](t1 T1, t2 T2) {
69+
// No error: we can't determine if pointer rules apply.
70+
fmt.Printf("%s", t1)
71+
fmt.Printf("%s", t2)
72+
}
73+
74+
func TestEmptyTypeSet[T interface{ int | string; float64 }](t T) {
75+
fmt.Printf("%s", t) // No error: empty type set.
76+
}
77+
78+
func TestPointerRules[T ~*[]int|*[2]int](t T) {
79+
var slicePtr *[]int
80+
var arrayPtr *[2]int
81+
fmt.Printf("%d", slicePtr)
82+
fmt.Printf("%d", arrayPtr)
83+
fmt.Printf("%d", t)
84+
}
85+
86+
func TestInterfacePromotion[E interface {
87+
~int
88+
Error() string
89+
}, S interface {
90+
float64
91+
String() string
92+
}](e E, s S) {
93+
fmt.Printf("%d", e)
94+
fmt.Printf("%s", e)
95+
fmt.Errorf("%w", e)
96+
fmt.Printf("%d", s) // want "wrong type.*contains float64"
97+
fmt.Printf("%s", s)
98+
fmt.Errorf("%w", s) // want "wrong type"
99+
}
100+
101+
type myInt int
102+
103+
func TestTermReduction[T1 interface{ ~int | string }, T2 interface {
104+
~int | string
105+
myInt
106+
}](t1 T1, t2 T2) {
107+
fmt.Printf("%d", t1) // want "wrong type.*contains string"
108+
fmt.Printf("%s", t1) // want "wrong type.*contains ~int"
109+
fmt.Printf("%d", t2)
110+
fmt.Printf("%s", t2) // want "wrong type.*contains typeparams.myInt"
111+
}

0 commit comments

Comments
 (0)