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
|
var slice []int
|
||||||
m.MapIndex(ValueOf(slice))
|
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()
|
k := v.kind()
|
||||||
switch k {
|
switch k {
|
||||||
case Interface:
|
case Interface:
|
||||||
var eface any
|
x := unpackEface(packIfaceValueIntoEmptyIface(v))
|
||||||
if v.typ().NumMethod() == 0 {
|
|
||||||
eface = *(*any)(v.ptr)
|
|
||||||
} else {
|
|
||||||
eface = (any)(*(*interface {
|
|
||||||
M()
|
|
||||||
})(v.ptr))
|
|
||||||
}
|
|
||||||
x := unpackEface(eface)
|
|
||||||
if x.flag != 0 {
|
if x.flag != 0 {
|
||||||
x.flag |= v.flag.ro()
|
x.flag |= v.flag.ro()
|
||||||
}
|
}
|
||||||
|
|
@ -1500,19 +1492,91 @@ func valueInterface(v Value, safe bool) any {
|
||||||
|
|
||||||
if v.kind() == Interface {
|
if v.kind() == Interface {
|
||||||
// Special case: return the element inside the interface.
|
// Special case: return the element inside the interface.
|
||||||
// Empty interface has one layout, all interfaces with
|
return packIfaceValueIntoEmptyIface(v)
|
||||||
// methods have a second layout.
|
|
||||||
if v.NumMethod() == 0 {
|
|
||||||
return *(*any)(v.ptr)
|
|
||||||
}
|
|
||||||
return *(*interface {
|
|
||||||
M()
|
|
||||||
})(v.ptr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return packEface(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.
|
// InterfaceData returns a pair of unspecified uintptr values.
|
||||||
// It panics if v's Kind is not Interface.
|
// It panics if v's Kind is not Interface.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue