@@ -1407,6 +1407,31 @@ func isStringer(T types.Type, msCache *gotypeutil.MethodSetCache) bool {
1407
1407
return true
1408
1408
}
1409
1409
1410
+ func isFormatter (T types.Type , msCache * gotypeutil.MethodSetCache ) bool {
1411
+ // TODO(dh): this function also exists in staticcheck/lint.go – deduplicate.
1412
+
1413
+ ms := msCache .MethodSet (T )
1414
+ sel := ms .Lookup (nil , "Format" )
1415
+ if sel == nil {
1416
+ return false
1417
+ }
1418
+ fn , ok := sel .Obj ().(* types.Func )
1419
+ if ! ok {
1420
+ // should be unreachable
1421
+ return false
1422
+ }
1423
+ sig := fn .Type ().(* types.Signature )
1424
+ if sig .Params ().Len () != 2 {
1425
+ return false
1426
+ }
1427
+ // TODO(dh): check the types of the arguments for more
1428
+ // precision
1429
+ if sig .Results ().Len () != 0 {
1430
+ return false
1431
+ }
1432
+ return true
1433
+ }
1434
+
1410
1435
var checkRedundantSprintfQ = pattern .MustParse (`(CallExpr (Function "fmt.Sprintf") [format arg])` )
1411
1436
1412
1437
func CheckRedundantSprintf (pass * analysis.Pass ) (interface {}, error ) {
@@ -1425,9 +1450,20 @@ func CheckRedundantSprintf(pass *analysis.Pass) (interface{}, error) {
1425
1450
return
1426
1451
}
1427
1452
typ := pass .TypesInfo .TypeOf (arg )
1428
-
1429
1453
irpkg := pass .ResultOf [buildir .Analyzer ].(* buildir.IR ).Pkg
1430
- if types .TypeString (typ , nil ) != "reflect.Value" && isStringer (typ , & irpkg .Prog .MethodSets ) {
1454
+
1455
+ if types .TypeString (typ , nil ) == "reflect.Value" {
1456
+ // printing with %s produces output different from using
1457
+ // the String method
1458
+ return
1459
+ }
1460
+
1461
+ if isFormatter (typ , & irpkg .Prog .MethodSets ) {
1462
+ // the type may choose to handle %s in arbitrary ways
1463
+ return
1464
+ }
1465
+
1466
+ if isStringer (typ , & irpkg .Prog .MethodSets ) {
1431
1467
replacement := & ast.CallExpr {
1432
1468
Fun : & ast.SelectorExpr {
1433
1469
X : arg ,
@@ -1436,20 +1472,18 @@ func CheckRedundantSprintf(pass *analysis.Pass) (interface{}, error) {
1436
1472
}
1437
1473
report .Report (pass , node , "should use String() instead of fmt.Sprintf" ,
1438
1474
report .Fixes (edit .Fix ("replace with call to String method" , edit .ReplaceWithNode (pass .Fset , node , replacement ))))
1475
+ } else if typ == types .Universe .Lookup ("string" ).Type () {
1476
+ report .Report (pass , node , "the argument is already a string, there's no need to use fmt.Sprintf" ,
1477
+ report .FilterGenerated (),
1478
+ report .Fixes (edit .Fix ("remove unnecessary call to fmt.Sprintf" , edit .ReplaceWithNode (pass .Fset , node , arg ))))
1439
1479
} else if typ .Underlying () == types .Universe .Lookup ("string" ).Type () {
1440
- if typ == types .Universe .Lookup ("string" ).Type () {
1441
- report .Report (pass , node , "the argument is already a string, there's no need to use fmt.Sprintf" ,
1442
- report .FilterGenerated (),
1443
- report .Fixes (edit .Fix ("remove unnecessary call to fmt.Sprintf" , edit .ReplaceWithNode (pass .Fset , node , arg ))))
1444
- } else {
1445
- replacement := & ast.CallExpr {
1446
- Fun : & ast.Ident {Name : "string" },
1447
- Args : []ast.Expr {arg },
1448
- }
1449
- report .Report (pass , node , "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf" ,
1450
- report .FilterGenerated (),
1451
- report .Fixes (edit .Fix ("replace with conversion to string" , edit .ReplaceWithNode (pass .Fset , node , replacement ))))
1480
+ replacement := & ast.CallExpr {
1481
+ Fun : & ast.Ident {Name : "string" },
1482
+ Args : []ast.Expr {arg },
1452
1483
}
1484
+ report .Report (pass , node , "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf" ,
1485
+ report .FilterGenerated (),
1486
+ report .Fixes (edit .Fix ("replace with conversion to string" , edit .ReplaceWithNode (pass .Fset , node , replacement ))))
1453
1487
} else if slice , ok := typ .Underlying ().(* types.Slice ); ok && slice .Elem () == types .Universe .Lookup ("byte" ).Type () {
1454
1488
// Note that we check slice.Elem(), not slice.Elem().Underlying, because of https://github.com/golang/go/issues/23536
1455
1489
replacement := & ast.CallExpr {
0 commit comments