--- /dev/null
+pkg errors, func AsType[$0 error](error) ($0, bool) #51945
--- /dev/null
+The new [AsType] function is a generic version of [As]. It is type-safe, faster,
+and, in most cases, easier to use.
//
// because the former will succeed if err wraps [io/fs.ErrExist].
//
-// [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
+// [AsType] examines the tree of its argument looking for an error whose
+// type matches its type argument. If it succeeds, it returns the
+// corresponding value of that type and true. Otherwise, it returns the
+// zero value of that type and false. The form
//
-// var perr *fs.PathError
-// if errors.As(err, &perr) {
+// if perr, ok := errors.AsType[*fs.PathError](err); ok {
// fmt.Println(perr.Path)
// }
//
// Failed at path: non-existing
}
+func ExampleAsType() {
+ if _, err := os.Open("non-existing"); err != nil {
+ if pathError, ok := errors.AsType[*fs.PathError](err); ok {
+ fmt.Println("Failed at path:", pathError.Path)
+ } else {
+ fmt.Println(err)
+ }
+ }
+ // Output:
+ // Failed at path: non-existing
+}
+
func ExampleUnwrap() {
err1 := errors.New("error1")
err2 := fmt.Errorf("error2: [%w]", err1)
// 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.
//
+// For most uses, prefer [AsType]. As is equivalent to [AsType] but sets its target
+// argument rather than returning the matching error and doesn't require its target
+// argument to implement error.
+//
// The tree consists of err itself, followed by the errors obtained by repeatedly
// calling its Unwrap() error or Unwrap() []error method. When err wraps multiple
// errors, As examines err followed by a depth-first traversal of its children.
}
var errorType = reflectlite.TypeOf((*error)(nil)).Elem()
+
+// AsType finds the first error in err's tree that matches the type E, and
+// if one is found, returns that error value and true. Otherwise, it
+// returns the zero value of E and false.
+//
+// The tree consists of err itself, followed by the errors obtained by
+// repeatedly calling its Unwrap() error or Unwrap() []error method. When
+// err wraps multiple errors, AsType examines err followed by a
+// depth-first traversal of its children.
+//
+// An error err matches the type E if the type assertion err.(E) holds,
+// or if the error has a method As(any) bool such that err.As(target)
+// returns true when target is a non-nil *E. In the latter case, the As
+// method is responsible for setting target.
+func AsType[E error](err error) (E, bool) {
+ if err == nil {
+ var zero E
+ return zero, false
+ }
+ var pe *E // lazily initialized
+ return asType(err, &pe)
+}
+
+func asType[E error](err error, ppe **E) (_ E, _ bool) {
+ for {
+ if e, ok := err.(E); ok {
+ return e, true
+ }
+ if x, ok := err.(interface{ As(any) bool }); ok {
+ if *ppe == nil {
+ *ppe = new(E)
+ }
+ if x.As(*ppe) {
+ return **ppe, true
+ }
+ }
+ switch x := err.(type) {
+ case interface{ Unwrap() error }:
+ err = x.Unwrap()
+ if err == nil {
+ return
+ }
+ case interface{ Unwrap() []error }:
+ for _, err := range x.Unwrap() {
+ if err == nil {
+ continue
+ }
+ if x, ok := asType(err, ppe); ok {
+ return x, true
+ }
+ }
+ return
+ default:
+ return
+ }
+ }
+}
}
}
+func TestAsType(t *testing.T) {
+ var errT errorT
+ var errP *fs.PathError
+ type timeout interface {
+ Timeout() bool
+ error
+ }
+ _, errF := os.Open("non-existing")
+ poserErr := &poser{"oh no", nil}
+
+ testAsType(t,
+ nil,
+ errP,
+ false,
+ )
+ testAsType(t,
+ wrapped{"pitied the fool", errorT{"T"}},
+ errorT{"T"},
+ true,
+ )
+ testAsType(t,
+ errF,
+ errF,
+ true,
+ )
+ testAsType(t,
+ errT,
+ errP,
+ false,
+ )
+ testAsType(t,
+ wrapped{"wrapped", nil},
+ errT,
+ false,
+ )
+ testAsType(t,
+ &poser{"error", nil},
+ errorT{"poser"},
+ true,
+ )
+ testAsType(t,
+ &poser{"path", nil},
+ poserPathErr,
+ true,
+ )
+ testAsType(t,
+ poserErr,
+ poserErr,
+ true,
+ )
+ testAsType(t,
+ errors.New("err"),
+ timeout(nil),
+ false,
+ )
+ testAsType(t,
+ errF,
+ errF.(timeout),
+ true)
+ testAsType(t,
+ wrapped{"path error", errF},
+ errF.(timeout),
+ true,
+ )
+ testAsType(t,
+ multiErr{},
+ errT,
+ false,
+ )
+ testAsType(t,
+ multiErr{errors.New("a"), errorT{"T"}},
+ errorT{"T"},
+ true,
+ )
+ testAsType(t,
+ multiErr{errorT{"T"}, errors.New("a")},
+ errorT{"T"},
+ true,
+ )
+ testAsType(t,
+ multiErr{errorT{"a"}, errorT{"b"}},
+ errorT{"a"},
+ true,
+ )
+ testAsType(t,
+ multiErr{multiErr{errors.New("a"), errorT{"a"}}, errorT{"b"}},
+ errorT{"a"},
+ true,
+ )
+ testAsType(t,
+ multiErr{wrapped{"path error", errF}},
+ errF.(timeout),
+ true,
+ )
+ testAsType(t,
+ multiErr{nil},
+ errT,
+ false,
+ )
+}
+
+type compError interface {
+ comparable
+ error
+}
+
+func testAsType[E compError](t *testing.T, err error, want E, wantOK bool) {
+ t.Helper()
+ name := fmt.Sprintf("AsType[%T](Errorf(..., %v))", want, err)
+ t.Run(name, func(t *testing.T) {
+ got, gotOK := errors.AsType[E](err)
+ if gotOK != wantOK || got != want {
+ t.Fatalf("got %v, %t; want %v, %t", got, gotOK, want, wantOK)
+ }
+ })
+}
+
func BenchmarkIs(b *testing.B) {
err1 := errors.New("1")
err2 := multiErr{multiErr{multiErr{err1, errorT{"a"}}, errorT{"b"}}}
}
}
+func BenchmarkAsType(b *testing.B) {
+ err := multiErr{multiErr{multiErr{errors.New("a"), errorT{"a"}}, errorT{"b"}}}
+ for range b.N {
+ if _, ok := errors.AsType[errorT](err); !ok {
+ b.Fatal("AsType failed")
+ }
+ }
+}
+
func TestUnwrap(t *testing.T) {
err1 := errors.New("1")
erra := wrapped{"wrap 2", err1}