diff --git a/go/analysis/passes/printf/printf.go b/go/analysis/passes/printf/printf.go index c0265aaf..b73ff5e0 100644 --- a/go/analysis/passes/printf/printf.go +++ b/go/analysis/passes/printf/printf.go @@ -453,15 +453,23 @@ func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, } // isFormatter reports whether t satisfies fmt.Formatter. -// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt. -func isFormatter(pass *analysis.Pass, t types.Type) bool { - for _, imp := range pass.Pkg.Imports() { - if imp.Path() == "fmt" { - formatter := imp.Scope().Lookup("Formatter").Type().Underlying().(*types.Interface) - return types.Implements(t, formatter) - } +// The only interface method to look for is "Format(State, rune)". +func isFormatter(typ types.Type) bool { + obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format") + fn, ok := obj.(*types.Func) + if !ok { + return false } - return false + sig := fn.Type().(*types.Signature) + return sig.Params().Len() == 2 && + sig.Results().Len() == 0 && + isNamed(sig.Params().At(0).Type(), "fmt", "State") && + types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune]) +} + +func isNamed(T types.Type, pkgpath, name string) bool { + named, ok := T.(*types.Named) + return ok && named.Obj().Pkg().Path() == pkgpath && named.Obj().Name() == name } // formatState holds the parsed representation of a printf directive such as "%3.*[4]d". @@ -753,7 +761,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (o formatter := false if state.argNum < len(call.Args) { if tv, ok := pass.TypesInfo.Types[call.Args[state.argNum]]; ok { - formatter = isFormatter(pass, tv.Type) + formatter = isFormatter(tv.Type) } } @@ -831,7 +839,7 @@ func recursiveStringer(pass *analysis.Pass, e ast.Expr) bool { typ := pass.TypesInfo.Types[e].Type // It's unlikely to be a recursive stringer if it has a Format method. - if isFormatter(pass, typ) { + if isFormatter(typ) { return false } diff --git a/go/analysis/passes/printf/printf_test.go b/go/analysis/passes/printf/printf_test.go index 937bbe7a..68453269 100644 --- a/go/analysis/passes/printf/printf_test.go +++ b/go/analysis/passes/printf/printf_test.go @@ -10,5 +10,5 @@ import ( func Test(t *testing.T) { testdata := analysistest.TestData() printf.Analyzer.Flags.Set("funcs", "Warn,Warnf") - analysistest.Run(t, testdata, printf.Analyzer, "a", "b") + analysistest.Run(t, testdata, printf.Analyzer, "a", "b", "nofmt") } diff --git a/go/analysis/passes/printf/testdata/src/nofmt/nofmt.go b/go/analysis/passes/printf/testdata/src/nofmt/nofmt.go new file mode 100644 index 00000000..c323796f --- /dev/null +++ b/go/analysis/passes/printf/testdata/src/nofmt/nofmt.go @@ -0,0 +1,10 @@ +package b + +import ( + "math/big" + "testing" +) + +func formatBigInt(t *testing.T) { + t.Logf("%d\n", big.NewInt(4)) +} diff --git a/go/analysis/passes/printf/types.go b/go/analysis/passes/printf/types.go index 2eb6dc74..12286fd5 100644 --- a/go/analysis/passes/printf/types.go +++ b/go/analysis/passes/printf/types.go @@ -38,7 +38,7 @@ func matchArgTypeInternal(pass *analysis.Pass, t printfArgType, typ types.Type, } } // If the type implements fmt.Formatter, we have nothing to check. - if isFormatter(pass, typ) { + if isFormatter(typ) { return true } // If we can use a string, might arg (dynamically) implement the Stringer or Error interface?