mirror of https://github.com/golang/go.git
reflect: add TypeAssert[T]
This implementation is zero-alloc when T is a concrete type,
allocates when val contains a method or when T is a interface
and Value was obtained for example through Elem(), in which case
it has to be allocated to avoid sharing the same memory.
goos: linux
goarch: amd64
pkg: reflect
cpu: AMD Ryzen 5 4600G with Radeon Graphics
│ /tmp/bench2 │
│ sec/op │
TypeAssert/TypeAssert[int](int)-12 2.725n ± 1%
TypeAssert/TypeAssert[uint8](int)-12 2.599n ± 1%
TypeAssert/TypeAssert[fmt.Stringer](reflect_test.testTypeWithMethod)-12 8.470n ± 0%
TypeAssert/TypeAssert[fmt.Stringer](*reflect_test.testTypeWithMethod)-12 8.460n ± 1%
TypeAssert/TypeAssert[interface_{}](int)-12 4.181n ± 1%
TypeAssert/TypeAssert[interface_{}](reflect_test.testTypeWithMethod)-12 4.178n ± 1%
TypeAssert/TypeAssert[time.Time](time.Time)-12 2.839n ± 0%
TypeAssert/TypeAssert[func()_string](func()_string)-12 151.1n ± 1%
geomean 6.645n
│ /tmp/bench2 │
│ B/op │
TypeAssert/TypeAssert[int](int)-12 0.000 ± 0%
TypeAssert/TypeAssert[uint8](int)-12 0.000 ± 0%
TypeAssert/TypeAssert[fmt.Stringer](reflect_test.testTypeWithMethod)-12 0.000 ± 0%
TypeAssert/TypeAssert[fmt.Stringer](*reflect_test.testTypeWithMethod)-12 0.000 ± 0%
TypeAssert/TypeAssert[interface_{}](int)-12 0.000 ± 0%
TypeAssert/TypeAssert[interface_{}](reflect_test.testTypeWithMethod)-12 0.000 ± 0%
TypeAssert/TypeAssert[time.Time](time.Time)-12 0.000 ± 0%
TypeAssert/TypeAssert[func()_string](func()_string)-12 72.00 ± 0%
geomean ¹
Fixes #62121
Change-Id: I0911c70c5966672c930d387438643f94a40441c4
GitHub-Last-Rev: ce89a53097
GitHub-Pull-Request: golang/go#71639
Reviewed-on: https://go-review.googlesource.com/c/go/+/648056
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
parent
d596bc0e81
commit
2541a68a70
|
|
@ -0,0 +1 @@
|
|||
pkg reflect, func TypeAssert[$0 interface{}](Value) ($0, bool) #62121
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
The new [TypeAssert] function permits converting a [Value] directly to a Go value
|
||||
of the given type. This is like using a type assertion on the result of [Value.Interface],
|
||||
but avoids unnecessary memory allocations.
|
||||
|
|
@ -8681,3 +8681,132 @@ func TestMapOfKeyPanic(t *testing.T) {
|
|||
var slice []int
|
||||
m.MapIndex(ValueOf(slice))
|
||||
}
|
||||
|
||||
func TestTypeAssert(t *testing.T) {
|
||||
testTypeAssert(t, int(123456789), int(123456789), true)
|
||||
testTypeAssert(t, int(-123456789), int(-123456789), true)
|
||||
testTypeAssert(t, int32(123456789), int32(123456789), true)
|
||||
testTypeAssert(t, int8(-123), int8(-123), true)
|
||||
testTypeAssert(t, [2]int{1234, -5678}, [2]int{1234, -5678}, true)
|
||||
testTypeAssert(t, "test value", "test value", true)
|
||||
testTypeAssert(t, any("test value"), any("test value"), true)
|
||||
|
||||
v := 123456789
|
||||
testTypeAssert(t, &v, &v, true)
|
||||
|
||||
testTypeAssert(t, int(123), uint(0), false)
|
||||
|
||||
testTypeAssert[any](t, 1, 1, true)
|
||||
testTypeAssert[fmt.Stringer](t, 1, nil, false)
|
||||
|
||||
vv := testTypeWithMethod{"test"}
|
||||
testTypeAssert[any](t, vv, vv, true)
|
||||
testTypeAssert[any](t, &vv, &vv, true)
|
||||
testTypeAssert[fmt.Stringer](t, vv, vv, true)
|
||||
testTypeAssert[fmt.Stringer](t, &vv, &vv, true)
|
||||
testTypeAssert[interface{ A() }](t, vv, nil, false)
|
||||
testTypeAssert[interface{ A() }](t, &vv, nil, false)
|
||||
testTypeAssert(t, any(vv), any(vv), true)
|
||||
testTypeAssert(t, fmt.Stringer(vv), fmt.Stringer(vv), true)
|
||||
|
||||
testTypeAssert(t, fmt.Stringer(vv), any(vv), true)
|
||||
testTypeAssert(t, any(vv), fmt.Stringer(vv), true)
|
||||
testTypeAssert(t, fmt.Stringer(vv), interface{ M() }(vv), true)
|
||||
testTypeAssert(t, interface{ M() }(vv), fmt.Stringer(vv), true)
|
||||
|
||||
testTypeAssert(t, any(int(1)), int(1), true)
|
||||
testTypeAssert(t, any(int(1)), byte(0), false)
|
||||
testTypeAssert(t, fmt.Stringer(vv), vv, true)
|
||||
}
|
||||
|
||||
func testTypeAssert[T comparable, V any](t *testing.T, val V, wantVal T, wantOk bool) {
|
||||
t.Helper()
|
||||
|
||||
v, ok := TypeAssert[T](ValueOf(&val).Elem())
|
||||
if v != wantVal || ok != wantOk {
|
||||
t.Errorf("TypeAssert[%v](%#v) = (%#v, %v); want = (%#v, %v)", TypeFor[T](), val, v, ok, wantVal, wantOk)
|
||||
}
|
||||
|
||||
// Additionally make sure that TypeAssert[T](v) behaves in the same way as v.Interface().(T).
|
||||
v2, ok2 := ValueOf(&val).Elem().Interface().(T)
|
||||
if v != v2 || ok != ok2 {
|
||||
t.Errorf("reflect.ValueOf(%#v).Interface().(%v) = (%#v, %v); want = (%#v, %v)", val, TypeFor[T](), v2, ok2, v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
type testTypeWithMethod struct{ val string }
|
||||
|
||||
func (v testTypeWithMethod) String() string { return v.val }
|
||||
func (v testTypeWithMethod) M() {}
|
||||
|
||||
func TestTypeAssertMethod(t *testing.T) {
|
||||
method := ValueOf(&testTypeWithMethod{val: "test value"}).MethodByName("String")
|
||||
f, ok := TypeAssert[func() string](method)
|
||||
if !ok {
|
||||
t.Fatalf(`TypeAssert[func() string](method) = (,false); want = (,true)`)
|
||||
}
|
||||
|
||||
out := f()
|
||||
if out != "test value" {
|
||||
t.Fatalf(`TypeAssert[func() string](method)() = %q; want "test value"`, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeAssertPanic(t *testing.T) {
|
||||
t.Run("zero val", func(t *testing.T) {
|
||||
defer func() { recover() }()
|
||||
TypeAssert[int](Value{})
|
||||
t.Fatalf("TypeAssert did not panic")
|
||||
})
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
defer func() { recover() }()
|
||||
TypeAssert[int](ValueOf(&testTypeWithMethod{}).FieldByName("val"))
|
||||
t.Fatalf("TypeAssert did not panic")
|
||||
})
|
||||
}
|
||||
|
||||
func TestTypeAssertAllocs(t *testing.T) {
|
||||
typeAssertAllocs[[128]int](t, ValueOf([128]int{}), 0)
|
||||
typeAssertAllocs[any](t, ValueOf([128]int{}), 0)
|
||||
|
||||
val := 123
|
||||
typeAssertAllocs[any](t, ValueOf(val), 0)
|
||||
typeAssertAllocs[any](t, ValueOf(&val).Elem(), 1) // must allocate, so that Set() does not modify the returned inner iface value.
|
||||
typeAssertAllocs[int](t, ValueOf(val), 0)
|
||||
typeAssertAllocs[int](t, ValueOf(&val).Elem(), 0)
|
||||
|
||||
typeAssertAllocs[time.Time](t, ValueOf(new(time.Time)).Elem(), 0)
|
||||
typeAssertAllocs[time.Time](t, ValueOf(*new(time.Time)), 0)
|
||||
}
|
||||
|
||||
func typeAssertAllocs[T any](t *testing.T, val Value, wantAllocs int) {
|
||||
t.Helper()
|
||||
allocs := testing.AllocsPerRun(10, func() {
|
||||
TypeAssert[T](val)
|
||||
})
|
||||
if allocs != float64(wantAllocs) {
|
||||
t.Errorf("TypeAssert[%v](%v) unexpected amount of allocations = %v; want = %v", TypeFor[T](), val.Type(), allocs, wantAllocs)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTypeAssert(b *testing.B) {
|
||||
benchmarkTypeAssert[int](b, ValueOf(int(1)))
|
||||
benchmarkTypeAssert[byte](b, ValueOf(int(1)))
|
||||
|
||||
benchmarkTypeAssert[fmt.Stringer](b, ValueOf(testTypeWithMethod{}))
|
||||
benchmarkTypeAssert[fmt.Stringer](b, ValueOf(&testTypeWithMethod{}))
|
||||
benchmarkTypeAssert[any](b, ValueOf(int(1)))
|
||||
benchmarkTypeAssert[any](b, ValueOf(testTypeWithMethod{}))
|
||||
|
||||
benchmarkTypeAssert[time.Time](b, ValueOf(*new(time.Time)))
|
||||
|
||||
benchmarkTypeAssert[func() string](b, ValueOf(time.Now()).MethodByName("String"))
|
||||
}
|
||||
|
||||
func benchmarkTypeAssert[T any](b *testing.B, val Value) {
|
||||
b.Run(fmt.Sprintf("TypeAssert[%v](%v)", TypeFor[T](), val.Type()), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
TypeAssert[T](val)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1219,15 +1219,7 @@ func (v Value) Elem() Value {
|
|||
k := v.kind()
|
||||
switch k {
|
||||
case Interface:
|
||||
var eface any
|
||||
if v.typ().NumMethod() == 0 {
|
||||
eface = *(*any)(v.ptr)
|
||||
} else {
|
||||
eface = (any)(*(*interface {
|
||||
M()
|
||||
})(v.ptr))
|
||||
}
|
||||
x := unpackEface(eface)
|
||||
x := unpackEface(packIfaceValueIntoEmptyIface(v))
|
||||
if x.flag != 0 {
|
||||
x.flag |= v.flag.ro()
|
||||
}
|
||||
|
|
@ -1500,19 +1492,91 @@ func valueInterface(v Value, safe bool) any {
|
|||
|
||||
if v.kind() == Interface {
|
||||
// Special case: return the element inside the interface.
|
||||
// Empty interface has one layout, all interfaces with
|
||||
// methods have a second layout.
|
||||
if v.NumMethod() == 0 {
|
||||
return *(*any)(v.ptr)
|
||||
}
|
||||
return *(*interface {
|
||||
M()
|
||||
})(v.ptr)
|
||||
return packIfaceValueIntoEmptyIface(v)
|
||||
}
|
||||
|
||||
return packEface(v)
|
||||
}
|
||||
|
||||
// TypeAssert is semantically equivalent to:
|
||||
//
|
||||
// v2, ok := v.Interface().(T)
|
||||
func TypeAssert[T any](v Value) (T, bool) {
|
||||
if v.flag == 0 {
|
||||
panic(&ValueError{"reflect.TypeAssert", Invalid})
|
||||
}
|
||||
if v.flag&flagRO != 0 {
|
||||
// Do not allow access to unexported values via TypeAssert,
|
||||
// because they might be pointers that should not be
|
||||
// writable or methods or function that should not be callable.
|
||||
panic("reflect.TypeAssert: cannot return value obtained from unexported field or method")
|
||||
}
|
||||
|
||||
if v.flag&flagMethod != 0 {
|
||||
v = makeMethodValue("TypeAssert", v)
|
||||
}
|
||||
|
||||
typ := abi.TypeFor[T]()
|
||||
if typ != v.typ() {
|
||||
// We can't just return false here:
|
||||
//
|
||||
// var zero T
|
||||
// return zero, false
|
||||
//
|
||||
// since this function should work in the same manner as v.Interface().(T) does.
|
||||
// Thus we have to handle two cases specially.
|
||||
|
||||
// Return the element inside the interface.
|
||||
//
|
||||
// T is a concrete type and v is an interface. For example:
|
||||
//
|
||||
// var v any = int(1)
|
||||
// val := ValueOf(&v).Elem()
|
||||
// TypeAssert[int](val) == val.Interface().(int)
|
||||
//
|
||||
// T is a interface and v is an interface, but the iface types are different. For example:
|
||||
//
|
||||
// var v any = &someError{}
|
||||
// val := ValueOf(&v).Elem()
|
||||
// TypeAssert[error](val) == val.Interface().(error)
|
||||
if v.kind() == Interface {
|
||||
v, ok := packIfaceValueIntoEmptyIface(v).(T)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// T is an interface, v is a concrete type. For example:
|
||||
//
|
||||
// TypeAssert[any](ValueOf(1)) == ValueOf(1).Interface().(any)
|
||||
// TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error)
|
||||
if typ.Kind() == abi.Interface {
|
||||
v, ok := packEface(v).(T)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
|
||||
if v.flag&flagIndir == 0 {
|
||||
return *(*T)(unsafe.Pointer(&v.ptr)), true
|
||||
}
|
||||
return *(*T)(v.ptr), true
|
||||
}
|
||||
|
||||
// packIfaceValueIntoEmptyIface converts an interface Value into an empty interface.
|
||||
//
|
||||
// Precondition: v.kind() == Interface
|
||||
func packIfaceValueIntoEmptyIface(v Value) any {
|
||||
// Empty interface has one layout, all interfaces with
|
||||
// methods have a second layout.
|
||||
if v.NumMethod() == 0 {
|
||||
return *(*any)(v.ptr)
|
||||
}
|
||||
return *(*interface {
|
||||
M()
|
||||
})(v.ptr)
|
||||
}
|
||||
|
||||
// InterfaceData returns a pair of unspecified uintptr values.
|
||||
// It panics if v's Kind is not Interface.
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in New Issue