diff --git a/src/fmt/errors.go b/src/fmt/errors.go index 1ac83404bc..a0ce7ada34 100644 --- a/src/fmt/errors.go +++ b/src/fmt/errors.go @@ -6,6 +6,7 @@ package fmt import ( "errors" + "internal/stringslite" "slices" ) @@ -19,7 +20,22 @@ import ( // order they appear in the arguments. // It is invalid to supply the %w verb with an operand that does not implement // the error interface. The %w verb is otherwise a synonym for %v. -func Errorf(format string, a ...any) error { +func Errorf(format string, a ...any) (err error) { + // This function has been split in a somewhat unnatural way + // so that both it and the errors.New call can be inlined. + if err = errorf(format, a...); err != nil { + return err + } + // No formatting was needed. We can avoid some allocations and other work. + // See https://go.dev/cl/708836 for details. + return errors.New(format) +} + +// errorf formats and returns an error value, or nil if no formatting is required. +func errorf(format string, a ...any) error { + if len(a) == 0 && stringslite.IndexByte(format, '%') == -1 { + return nil + } p := newPrinter() p.wrapErrs = true p.doPrintf(format, a) diff --git a/src/fmt/errors_test.go b/src/fmt/errors_test.go index 4eb55faffe..52bf42d0a6 100644 --- a/src/fmt/errors_test.go +++ b/src/fmt/errors_test.go @@ -54,6 +54,15 @@ func TestErrorf(t *testing.T) { }, { err: noVetErrorf("%w is not an error", "not-an-error"), wantText: "%!w(string=not-an-error) is not an error", + }, { + err: fmt.Errorf("no verbs"), + wantText: "no verbs", + }, { + err: noVetErrorf("no verbs with extra arg", "extra"), + wantText: "no verbs with extra arg%!(EXTRA string=extra)", + }, { + err: noVetErrorf("too many verbs: %w %v"), + wantText: "too many verbs: %!w(MISSING) %!v(MISSING)", }, { err: noVetErrorf("wrapped two errors: %w %w", errString("1"), errString("2")), wantText: "wrapped two errors: 1 2", diff --git a/src/fmt/fmt_test.go b/src/fmt/fmt_test.go index 86e458ae64..c07da5683c 100644 --- a/src/fmt/fmt_test.go +++ b/src/fmt/fmt_test.go @@ -1480,6 +1480,7 @@ func BenchmarkFprintIntNoAlloc(b *testing.B) { var mallocBuf bytes.Buffer var mallocPointer *int // A pointer so we know the interface value won't allocate. +var sink any var mallocTest = []struct { count int @@ -1510,6 +1511,10 @@ var mallocTest = []struct { mallocBuf.Reset() Fprintf(&mallocBuf, "%x %x %x", mallocPointer, mallocPointer, mallocPointer) }}, + {0, `Errorf("hello")`, func() { _ = Errorf("hello") }}, + {2, `Errorf("hello: %x")`, func() { _ = Errorf("hello: %x", mallocPointer) }}, + {1, `sink = Errorf("hello")`, func() { sink = Errorf("hello") }}, + {2, `sink = Errorf("hello: %x")`, func() { sink = Errorf("hello: %x", mallocPointer) }}, } var _ bytes.Buffer