]> Cypherpunks repositories - gostls13.git/commitdiff
errors: add Frame and Formatter/Printer interfaces
authorMarcel van Lohuizen <mpvl@golang.org>
Fri, 22 Feb 2019 22:41:38 +0000 (23:41 +0100)
committerMarcel van Lohuizen <mpvl@golang.org>
Wed, 27 Feb 2019 19:07:22 +0000 (19:07 +0000)
errors.New now implements Formatter and includes Frame
information that is reported when detail is requested.

Partly implements proposal Issue #29934.

Change-Id: Id76888d246d7d862595b5e92d517b9c03f23a7a6
Reviewed-on: https://go-review.googlesource.com/c/163557
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
src/errors/errors.go
src/errors/format.go [new file with mode: 0644]
src/errors/frame.go [new file with mode: 0644]
src/errors/frame_test.go [new file with mode: 0644]
src/go/build/deps_test.go

index b8a46921be00e8d872ceccd036e6a2aff4b04acf..f23a96c43eb9e5c9f0ca042efe007a8e68d477f3 100644 (file)
@@ -6,15 +6,25 @@
 package errors
 
 // New returns an error that formats as the given text.
+//
+// The returned error contains a Frame set to the caller's location and
+// implements Formatter to show this information when printed with details.
 func New(text string) error {
-       return &errorString{text}
+       return &errorString{text, Caller(1)}
 }
 
 // errorString is a trivial implementation of error.
 type errorString struct {
-       s string
+       s     string
+       frame Frame
 }
 
 func (e *errorString) Error() string {
        return e.s
 }
+
+func (e *errorString) FormatError(p Printer) (next error) {
+       p.Print(e.s)
+       e.frame.Format(p)
+       return nil
+}
diff --git a/src/errors/format.go b/src/errors/format.go
new file mode 100644 (file)
index 0000000..12deed3
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors
+
+// A Formatter formats error messages.
+type Formatter interface {
+       error
+
+       // FormatError prints the receiver's first error and returns the next error in
+       // the error chain, if any.
+       FormatError(p Printer) (next error)
+}
+
+// A Printer formats error messages.
+//
+// The most common implementation of Printer is the one provided by package fmt
+// during Printf. Localization packages such as golang.org/x/text/message
+// typically provide their own implementations.
+type Printer interface {
+       // Print appends args to the message output.
+       Print(args ...interface{})
+
+       // Printf writes a formatted string.
+       Printf(format string, args ...interface{})
+
+       // Detail reports whether error detail is requested.
+       // After the first call to Detail, all text written to the Printer
+       // is formatted as additional detail, or ignored when
+       // detail has not been requested.
+       // If Detail returns false, the caller can avoid printing the detail at all.
+       Detail() bool
+}
diff --git a/src/errors/frame.go b/src/errors/frame.go
new file mode 100644 (file)
index 0000000..a5369e5
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors
+
+import (
+       "runtime"
+)
+
+// A Frame contains part of a call stack.
+type Frame struct {
+       // Make room for three PCs: the one we were asked for, what it called,
+       // and possibly a PC for skipPleaseUseCallersFrames. See:
+       // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
+       frames [3]uintptr
+}
+
+// Caller returns a Frame that describes a frame on the caller's stack.
+// The argument skip is the number of frames to skip over.
+// Caller(0) returns the frame for the caller of Caller.
+func Caller(skip int) Frame {
+       var s Frame
+       runtime.Callers(skip+1, s.frames[:])
+       return s
+}
+
+// location reports the file, line, and function of a frame.
+//
+// The returned function may be "" even if file and line are not.
+func (f Frame) location() (function, file string, line int) {
+       frames := runtime.CallersFrames(f.frames[:])
+       if _, ok := frames.Next(); !ok {
+               return "", "", 0
+       }
+       fr, ok := frames.Next()
+       if !ok {
+               return "", "", 0
+       }
+       return fr.Function, fr.File, fr.Line
+}
+
+// Format prints the stack as error detail.
+// It should be called from an error's Format implementation,
+// before printing any other error detail.
+func (f Frame) Format(p Printer) {
+       if p.Detail() {
+               function, file, line := f.location()
+               if function != "" {
+                       p.Printf("%s\n    ", function)
+               }
+               if file != "" {
+                       p.Printf("%s:%d\n", file, line)
+               }
+       }
+}
diff --git a/src/errors/frame_test.go b/src/errors/frame_test.go
new file mode 100644 (file)
index 0000000..864a693
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package errors_test
+
+import (
+       "bytes"
+       "errors"
+       "fmt"
+       "math/big"
+       "testing"
+)
+
+type myType struct{}
+
+func (myType) Format(s fmt.State, v rune) {
+       s.Write(bytes.Repeat([]byte("Hi! "), 10))
+}
+
+func BenchmarkErrorf(b *testing.B) {
+       err := errors.New("foo")
+       // pi := big.NewFloat(3.14) // Something expensive.
+       num := big.NewInt(5)
+       args := func(a ...interface{}) []interface{} { return a }
+       benchCases := []struct {
+               name   string
+               format string
+               args   []interface{}
+       }{
+               {"no_format", "msg: %v", args(err)},
+               {"with_format", "failed %d times: %v", args(5, err)},
+               {"method: mytype", "pi: %v", args("myfile.go", myType{}, err)},
+               {"method: number", "pi: %v", args("myfile.go", num, err)},
+       }
+       for _, bc := range benchCases {
+               b.Run(bc.name, func(b *testing.B) {
+                       for i := 0; i < b.N; i++ {
+                               _ = fmt.Errorf(bc.format, bc.args...)
+                       }
+               })
+       }
+}
index 3bf4b7acfa42353d2691fd4e646b1c7419a0fed8..6866abc9b5f09b9b0de8bdba49228c95e0046c02 100644 (file)
@@ -34,7 +34,7 @@ import (
 //
 var pkgDeps = map[string][]string{
        // L0 is the lowest level, core, nearly unavoidable packages.
-       "errors":                  {},
+       "errors":                  {"runtime"},
        "io":                      {"errors", "sync", "sync/atomic"},
        "runtime":                 {"unsafe", "runtime/internal/atomic", "runtime/internal/sys", "runtime/internal/math", "internal/cpu", "internal/bytealg"},
        "runtime/internal/sys":    {},