mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
errors: add AsType
Fixes #51945
Change-Id: Icda169782e796578eba728938134a85b5827d3b6
GitHub-Last-Rev: c6ff335ee1
GitHub-Pull-Request: golang/go#75621
Reviewed-on: https://go-review.googlesource.com/c/go/+/707235
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Sean Liao <sean@liao.dev>
This commit is contained in:
parent
7c8166d02d
commit
a846bb0aa5
6 changed files with 207 additions and 5 deletions
1
api/next/51945.txt
Normal file
1
api/next/51945.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
pkg errors, func AsType[$0 error](error) ($0, bool) #51945
|
||||
2
doc/next/6-stdlib/99-minor/errors/51945.md
Normal file
2
doc/next/6-stdlib/99-minor/errors/51945.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
The new [AsType] function is a generic version of [As]. It is type-safe, faster,
|
||||
and, in most cases, easier to use.
|
||||
|
|
@ -41,12 +41,12 @@
|
|||
//
|
||||
// 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)
|
||||
// }
|
||||
//
|
||||
|
|
|
|||
|
|
@ -102,6 +102,18 @@ func ExampleAs() {
|
|||
// 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)
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ func is(err, target error, targetComparable bool) bool {
|
|||
// 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.
|
||||
|
|
@ -145,3 +149,60 @@ func as(err error, target any, targetVal reflectlite.Value, targetType reflectli
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,6 +239,123 @@ func TestAsValidation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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"}}}
|
||||
|
|
@ -260,6 +377,15 @@ func BenchmarkAs(b *testing.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}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue