Skip to content

reflect: add TypeAssert[T] #71639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/next/62121.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pkg reflect, func TypeAssert[$0 interface{}](Value) ($0, bool) #62121
3 changes: 3 additions & 0 deletions doc/next/6-stdlib/99-minor/reflect/62121.md
Original file line number Diff line number Diff line change
@@ -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.
129 changes: 129 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
98 changes: 81 additions & 17 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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.
//
Expand Down