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].
|
// 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
|
// [AsType] examines the tree of its argument looking for an error whose
|
||||||
// assigned to its second argument, which must be a pointer. If it succeeds, it
|
// type matches its type argument. If it succeeds, it returns the
|
||||||
// performs the assignment and returns true. Otherwise, it returns false. The form
|
// 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 perr, ok := errors.AsType[*fs.PathError](err); ok {
|
||||||
// if errors.As(err, &perr) {
|
|
||||||
// fmt.Println(perr.Path)
|
// fmt.Println(perr.Path)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,18 @@ func ExampleAs() {
|
||||||
// Failed at path: non-existing
|
// 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() {
|
func ExampleUnwrap() {
|
||||||
err1 := errors.New("error1")
|
err1 := errors.New("error1")
|
||||||
err2 := fmt.Errorf("error2: [%w]", err1)
|
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
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// 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()
|
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) {
|
func BenchmarkIs(b *testing.B) {
|
||||||
err1 := errors.New("1")
|
err1 := errors.New("1")
|
||||||
err2 := multiErr{multiErr{multiErr{err1, errorT{"a"}}, errorT{"b"}}}
|
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) {
|
func TestUnwrap(t *testing.T) {
|
||||||
err1 := errors.New("1")
|
err1 := errors.New("1")
|
||||||
erra := wrapped{"wrap 2", err1}
|
erra := wrapped{"wrap 2", err1}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue