--- /dev/null
+pkg errors, func Join(...error) error #53435
//
// The New function creates errors whose only content is a text message.
//
-// The Unwrap, Is and As functions work on errors that may wrap other errors.
-// An error wraps another error if its type has the method
+// An error e wraps another error if e's type has one of the methods
//
// Unwrap() error
+// Unwrap() []error
//
-// If e.Unwrap() returns a non-nil error w, then we say that e wraps w.
+// If e.Unwrap() returns a non-nil error w or a slice containing w,
+// then we say that e wraps w. A nil error returned from e.Unwrap()
+// indicates that e does not wrap any error. It is invalid for an
+// Unwrap method to return an []error containing a nil error value.
//
-// Unwrap unpacks wrapped errors. If its argument's type has an
-// Unwrap method, it calls the method once. Otherwise, it returns nil.
+// An easy way to create wrapped errors is to call fmt.Errorf and apply
+// the %w verb to the error argument:
//
-// A simple way to create wrapped errors is to call fmt.Errorf and apply the %w verb
-// to the error argument:
+// wrapsErr := fmt.Errorf("... %w ...", ..., err, ...)
//
-// errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...))
+// Successive unwrapping of an error creates a tree. The Is and As
+// functions inspect an error's tree by examining first the error
+// itself followed by the tree of each of its children in turn
+// (pre-order, depth-first traversal).
//
-// returns err.
-//
-// Is unwraps its first argument sequentially looking for an error that matches the
-// second. It reports whether it finds a match. It should be used in preference to
-// simple equality checks:
+// Is examines the tree of its first argument looking for an error that
+// matches the second. It reports whether it finds a match. It should be
+// used in preference to simple equality checks:
//
// if errors.Is(err, fs.ErrExist)
//
//
// because the former will succeed if err wraps fs.ErrExist.
//
-// As unwraps its first argument sequentially looking for an error that can be
+// As examines the tree of its first argument looking for an error that can be
// assigned to its second argument, which must be a pointer. If it succeeds, it
// performs the assignment and returns true. Otherwise, it returns false. The form
//
}
// Output: user "bimmler" (id 17) not found
}
+
+func ExampleJoin() {
+ err1 := errors.New("err1")
+ err2 := errors.New("err2")
+ err := errors.Join(err1, err2)
+ fmt.Println(err)
+ if errors.Is(err, err1) {
+ fmt.Println("err is err1")
+ }
+ if errors.Is(err, err2) {
+ fmt.Println("err is err2")
+ }
+ // Output:
+ // err1
+ // err2
+ // err is err1
+ // err is err2
+}
--- /dev/null
+// Copyright 2022 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
+
+// Join returns an error that wraps the given errors.
+// Any nil error values are discarded.
+// Join returns nil if errs contains no non-nil values.
+// The error formats as the concatenation of the strings obtained
+// by calling the Error method of each element of errs, with a newline
+// between each string.
+func Join(errs ...error) error {
+ n := 0
+ for _, err := range errs {
+ if err != nil {
+ n++
+ }
+ }
+ if n == 0 {
+ return nil
+ }
+ e := &joinError{
+ errs: make([]error, 0, n),
+ }
+ for _, err := range errs {
+ if err != nil {
+ e.errs = append(e.errs, err)
+ }
+ }
+ return e
+}
+
+type joinError struct {
+ errs []error
+}
+
+func (e *joinError) Error() string {
+ var b []byte
+ for i, err := range e.errs {
+ if i > 0 {
+ b = append(b, '\n')
+ }
+ b = append(b, err.Error()...)
+ }
+ return string(b)
+}
+
+func (e *joinError) Unwrap() []error {
+ return e.errs
+}
--- /dev/null
+// Copyright 2022 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 (
+ "errors"
+ "reflect"
+ "testing"
+)
+
+func TestJoinReturnsNil(t *testing.T) {
+ if err := errors.Join(); err != nil {
+ t.Errorf("errors.Join() = %v, want nil", err)
+ }
+ if err := errors.Join(nil); err != nil {
+ t.Errorf("errors.Join(nil) = %v, want nil", err)
+ }
+ if err := errors.Join(nil, nil); err != nil {
+ t.Errorf("errors.Join(nil, nil) = %v, want nil", err)
+ }
+}
+
+func TestJoin(t *testing.T) {
+ err1 := errors.New("err1")
+ err2 := errors.New("err2")
+ for _, test := range []struct {
+ errs []error
+ want []error
+ }{{
+ errs: []error{err1},
+ want: []error{err1},
+ }, {
+ errs: []error{err1, err2},
+ want: []error{err1, err2},
+ }, {
+ errs: []error{err1, nil, err2},
+ want: []error{err1, err2},
+ }} {
+ got := errors.Join(test.errs...).(interface{ Unwrap() []error }).Unwrap()
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("Join(%v) = %v; want %v", test.errs, got, test.want)
+ }
+ if len(got) != cap(got) {
+ t.Errorf("Join(%v) returns errors with len=%v, cap=%v; want len==cap", test.errs, len(got), cap(got))
+ }
+ }
+}
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
+//
+// Unwrap returns nil if the Unwrap method returns []error.
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
return u.Unwrap()
}
-// Is reports whether any error in err's chain matches target.
+// Is reports whether any error in err's tree matches target.
//
-// The chain consists of err itself followed by the sequence of errors obtained by
-// repeatedly calling Unwrap.
+// The tree consists of err itself, followed by the errors obtained by repeatedly
+// calling Unwrap. When err wraps multiple errors, Is examines err followed by a
+// depth-first traversal of its children.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
- // TODO: consider supporting target.Is(err). This would allow
- // user-definable predicates, but also may allow for coping with sloppy
- // APIs, thereby making it easier to get away with them.
- if err = Unwrap(err); err == nil {
+ switch x := err.(type) {
+ case interface{ Unwrap() error }:
+ err = x.Unwrap()
+ if err == nil {
+ return false
+ }
+ case interface{ Unwrap() []error }:
+ for _, err := range x.Unwrap() {
+ if Is(err, target) {
+ return true
+ }
+ }
+ return false
+ default:
return false
}
}
}
-// As finds the first error in err's chain that matches target, and if one is found, sets
+// As finds the first error in err's tree that matches target, and if one is found, sets
// target to that error value and returns true. Otherwise, it returns false.
//
-// The chain consists of err itself followed by the sequence of errors obtained by
-// repeatedly calling Unwrap.
+// The tree consists of err itself, followed by the errors obtained by repeatedly
+// calling Unwrap. When err wraps multiple errors, As examines err followed by a
+// depth-first traversal of its children.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
func As(err error, target any) bool {
+ if err == nil {
+ return false
+ }
if target == nil {
panic("errors: target cannot be nil")
}
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
- for err != nil {
+ for {
if reflectlite.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflectlite.ValueOf(err))
return true
if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
return true
}
- err = Unwrap(err)
+ switch x := err.(type) {
+ case interface{ Unwrap() error }:
+ err = x.Unwrap()
+ if err == nil {
+ return false
+ }
+ case interface{ Unwrap() []error }:
+ for _, err := range x.Unwrap() {
+ if As(err, target) {
+ return true
+ }
+ }
+ return false
+ default:
+ return false
+ }
}
- return false
}
var errorType = reflectlite.TypeOf((*error)(nil)).Elem()
{&errorUncomparable{}, &errorUncomparable{}, false},
{errorUncomparable{}, err1, false},
{&errorUncomparable{}, err1, false},
+ {multiErr{}, err1, false},
+ {multiErr{err1, err3}, err1, true},
+ {multiErr{err3, err1}, err1, true},
+ {multiErr{err1, err3}, errors.New("x"), false},
+ {multiErr{err3, errb}, errb, true},
+ {multiErr{err3, errb}, erra, true},
+ {multiErr{err3, errb}, err1, true},
+ {multiErr{errb, err3}, err1, true},
+ {multiErr{poser}, err1, true},
+ {multiErr{poser}, err3, true},
+ {multiErr{nil}, nil, false},
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
&timeout,
true,
errF,
+ }, {
+ multiErr{},
+ &errT,
+ false,
+ nil,
+ }, {
+ multiErr{errors.New("a"), errorT{"T"}},
+ &errT,
+ true,
+ errorT{"T"},
+ }, {
+ multiErr{errorT{"T"}, errors.New("a")},
+ &errT,
+ true,
+ errorT{"T"},
+ }, {
+ multiErr{errorT{"a"}, errorT{"b"}},
+ &errT,
+ true,
+ errorT{"a"},
+ }, {
+ multiErr{multiErr{errors.New("a"), errorT{"a"}}, errorT{"b"}},
+ &errT,
+ true,
+ errorT{"a"},
+ }, {
+ multiErr{wrapped{"path error", errF}},
+ &timeout,
+ true,
+ errF,
+ }, {
+ multiErr{nil},
+ &errT,
+ false,
+ nil,
}}
for i, tc := range testCases {
name := fmt.Sprintf("%d:As(Errorf(..., %v), %v)", i, tc.err, tc.target)
}
func (e wrapped) Error() string { return e.msg }
-
func (e wrapped) Unwrap() error { return e.err }
+type multiErr []error
+
+func (m multiErr) Error() string { return "multiError" }
+func (m multiErr) Unwrap() []error { return []error(m) }
+
type errorUncomparable struct {
f []string
}
package fmt
-import "errors"
+import (
+ "errors"
+ "sort"
+)
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
-// the returned error will implement an Unwrap method returning the operand. It is
-// invalid to include more than one %w verb or to supply it with an operand
-// that does not implement the error interface. The %w verb is otherwise
-// a synonym for %v.
+// the returned error will implement an Unwrap method returning the operand.
+// If there is more than one %w verb, the returned error will implement an
+// Unwrap method returning a []error containing all the %w operands in the
+// 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 {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
- if p.wrappedErr == nil {
+ switch len(p.wrappedErrs) {
+ case 0:
err = errors.New(s)
- } else {
- err = &wrapError{s, p.wrappedErr}
+ case 1:
+ w := &wrapError{msg: s}
+ w.err, _ = a[p.wrappedErrs[0]].(error)
+ err = w
+ default:
+ if p.reordered {
+ sort.Ints(p.wrappedErrs)
+ }
+ var errs []error
+ for i, argNum := range p.wrappedErrs {
+ if i > 0 && p.wrappedErrs[i-1] == argNum {
+ continue
+ }
+ if e, ok := a[argNum].(error); ok {
+ errs = append(errs, e)
+ }
+ }
+ err = &wrapErrors{s, errs}
}
p.free()
return err
func (e *wrapError) Unwrap() error {
return e.err
}
+
+type wrapErrors struct {
+ msg string
+ errs []error
+}
+
+func (e *wrapErrors) Error() string {
+ return e.msg
+}
+
+func (e *wrapErrors) Unwrap() []error {
+ return e.errs
+}
import (
"errors"
"fmt"
+ "reflect"
"testing"
)
err error
wantText string
wantUnwrap error
+ wantSplit []error
}{{
err: fmt.Errorf("%w", wrapped),
wantText: "inner error",
err: noVetErrorf("%w is not an error", "not-an-error"),
wantText: "%!w(string=not-an-error) is not an error",
}, {
- err: noVetErrorf("wrapped two errors: %w %w", errString("1"), errString("2")),
- wantText: "wrapped two errors: 1 %!w(fmt_test.errString=2)",
+ err: noVetErrorf("wrapped two errors: %w %w", errString("1"), errString("2")),
+ wantText: "wrapped two errors: 1 2",
+ wantSplit: []error{errString("1"), errString("2")},
}, {
- err: noVetErrorf("wrapped three errors: %w %w %w", errString("1"), errString("2"), errString("3")),
- wantText: "wrapped three errors: 1 %!w(fmt_test.errString=2) %!w(fmt_test.errString=3)",
+ err: noVetErrorf("wrapped three errors: %w %w %w", errString("1"), errString("2"), errString("3")),
+ wantText: "wrapped three errors: 1 2 3",
+ wantSplit: []error{errString("1"), errString("2"), errString("3")},
+ }, {
+ err: noVetErrorf("wrapped nil error: %w %w %w", errString("1"), nil, errString("2")),
+ wantText: "wrapped nil error: 1 %!w(<nil>) 2",
+ wantSplit: []error{errString("1"), errString("2")},
+ }, {
+ err: noVetErrorf("wrapped one non-error: %w %w %w", errString("1"), "not-an-error", errString("3")),
+ wantText: "wrapped one non-error: 1 %!w(string=not-an-error) 3",
+ wantSplit: []error{errString("1"), errString("3")},
+ }, {
+ err: fmt.Errorf("wrapped errors out of order: %[3]w %[2]w %[1]w", errString("1"), errString("2"), errString("3")),
+ wantText: "wrapped errors out of order: 3 2 1",
+ wantSplit: []error{errString("1"), errString("2"), errString("3")},
+ }, {
+ err: fmt.Errorf("wrapped several times: %[1]w %[1]w %[2]w %[1]w", errString("1"), errString("2")),
+ wantText: "wrapped several times: 1 1 2 1",
+ wantSplit: []error{errString("1"), errString("2")},
}, {
err: fmt.Errorf("%w", nil),
wantText: "%!w(<nil>)",
if got, want := errors.Unwrap(test.err), test.wantUnwrap; got != want {
t.Errorf("Formatted error: %v\nerrors.Unwrap() = %v, want %v", test.err, got, want)
}
+ if got, want := splitErr(test.err), test.wantSplit; !reflect.DeepEqual(got, want) {
+ t.Errorf("Formatted error: %v\nUnwrap() []error = %v, want %v", test.err, got, want)
+ }
if got, want := test.err.Error(), test.wantText; got != want {
t.Errorf("err.Error() = %q, want %q", got, want)
}
}
}
+func splitErr(err error) []error {
+ if e, ok := err.(interface{ Unwrap() []error }); ok {
+ return e.Unwrap()
+ }
+ return nil
+}
+
type errString string
func (e errString) Error() string { return string(e) }
erroring bool
// wrapErrs is set when the format string may contain a %w verb.
wrapErrs bool
- // wrappedErr records the target of the %w verb.
- wrappedErr error
+ // wrappedErrs records the targets of the %w verb.
+ wrappedErrs []int
}
var ppFree = sync.Pool{
} else {
p.buf = p.buf[:0]
}
+ if cap(p.wrappedErrs) > 8 {
+ p.wrappedErrs = nil
+ }
p.arg = nil
p.value = reflect.Value{}
- p.wrappedErr = nil
+ p.wrappedErrs = p.wrappedErrs[:0]
ppFree.Put(p)
}
return
}
if verb == 'w' {
- // It is invalid to use %w other than with Errorf, more than once,
- // or with a non-error arg.
- err, ok := p.arg.(error)
- if !ok || !p.wrapErrs || p.wrappedErr != nil {
- p.wrappedErr = nil
- p.wrapErrs = false
+ // It is invalid to use %w other than with Errorf or with a non-error arg.
+ _, ok := p.arg.(error)
+ if !ok || !p.wrapErrs {
p.badVerb(verb)
return true
}
- p.wrappedErr = err
// If the arg is a Formatter, pass 'v' as the verb to it.
verb = 'v'
}
// Fast path for common case of ascii lower case simple verbs
// without precision or width or argument indices.
if 'a' <= c && c <= 'z' && argNum < len(a) {
- if c == 'v' {
+ switch c {
+ case 'w':
+ p.wrappedErrs = append(p.wrappedErrs, argNum)
+ fallthrough
+ case 'v':
// Go syntax
p.fmt.sharpV = p.fmt.sharp
p.fmt.sharp = false
p.badArgNum(verb)
case argNum >= len(a): // No argument left over to print for the current verb.
p.missingArg(verb)
+ case verb == 'w':
+ p.wrappedErrs = append(p.wrappedErrs, argNum)
+ fallthrough
case verb == 'v':
// Go syntax
p.fmt.sharpV = p.fmt.sharp