Skip to content
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

reflect: add TypeAssert[T] #71639

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 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,2 @@
The new [TypeAssert] function permits converting a [Value] directly to a Go type.
This is like using a type assertion on the result of [Value.Interface].
123 changes: 123 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8681,3 +8681,126 @@ func TestMapOfKeyPanic(t *testing.T) {
var slice []int
m.MapIndex(ValueOf(slice))
}

func testTypeAssert[T comparable](t *testing.T, val T) {
t.Helper()
v, ok := TypeAssert[T](ValueOf(val))
if v != val || !ok {
t.Errorf("TypeAssert[%T](%v) = (%v, %v); want = (%v, true)", *new(T), val, v, ok, val)
}
}

func testTypeAssertDifferentType[T, T2 comparable](t *testing.T, val T2) {
t.Helper()
if v, ok := TypeAssert[T](ValueOf(val)); ok {
t.Errorf("TypeAssert[%T](%v) = (%v, %v); want = (%v, false)", *new(T), val, v, ok, *new(T))
}
}

func newPtr[T any](t T) *T {
return &t
}

func TestTypeAssertConcreteTypes(t *testing.T) {
testTypeAssert(t, int(1111))
testTypeAssert(t, int(111111111))
testTypeAssert(t, int(-111111111))
testTypeAssert(t, int32(111111111))
testTypeAssert(t, int32(-111111111))
testTypeAssert(t, uint32(111111111))
testTypeAssert(t, [2]int{111111111, 22222222})
testTypeAssert(t, [2]int{-111111111, -22222222})
testTypeAssert(t, newPtr(1111))
testTypeAssert(t, newPtr(111111111))
testTypeAssert(t, newPtr(-111111111))
testTypeAssert(t, newPtr([2]int{-111111111, -22222222}))
testTypeAssert(t, [2]*int{newPtr(-111111111), newPtr(-22222222)})
testTypeAssert(t, newPtr(time.Now()))

testTypeAssertDifferentType[uint](t, int(111111111))
testTypeAssertDifferentType[uint](t, int(-111111111))
}

func TestTypeAssertInterfaceTypes(t *testing.T) {
v, ok := TypeAssert[any](ValueOf(1))
if v != any(1) || !ok {
t.Errorf("TypeAssert[any](1) = (%v, %v); want = (1, true)", v, ok)
}

v, ok = TypeAssert[fmt.Stringer](ValueOf(1))
if v != nil || ok {
t.Errorf("TypeAssert[fmt.Stringer](1) = (%v, %v); want = (1, false)", v, ok)
}

v, ok = TypeAssert[any](ValueOf(testTypeWithMethod{"test"}))
if v != any(testTypeWithMethod{"test"}) || !ok {
t.Errorf(`TypeAssert[any](testTypeWithMethod{"test"}) = (%v, %v); want = (testTypeWithMethod{"test"}, true)`, v, ok)
}

v, ok = TypeAssert[fmt.Stringer](ValueOf(testTypeWithMethod{"test"}))
if v != fmt.Stringer(testTypeWithMethod{"test"}) || !ok {
t.Errorf(`TypeAssert[fmt.Stringer](testTypeWithMethod{"test"}) = (%v, %v); want = (testTypeWithMethod{"test"}, true)`, v, ok)
}

val := &testTypeWithMethod{"test"}
v, ok = TypeAssert[fmt.Stringer](ValueOf(val))
if v != fmt.Stringer(val) || !ok {
t.Errorf(`TypeAssert[fmt.Stringer](&testTypeWithMethod{"test"}) = (%v, %v); want = (&testTypeWithMethod{"test"}, true)`, v, ok)
}

if v, ok := TypeAssert[int](ValueOf(newPtr(any(1))).Elem()); v != 1 || !ok {
t.Errorf(`TypeAssert[int](ValueOf(newPtr(any(1))).Elem()) = (%v, %v); want = (1, true)`, v, ok)
}

if v, ok := TypeAssert[testTypeWithMethod](ValueOf(newPtr(fmt.Stringer(testTypeWithMethod{"test"}))).Elem()); v.val != "test" || !ok {
t.Errorf(`TypeAssert[testTypeWithMethod](newPtr(fmt.Stringer(testTypeWithMethod{"test"}))) = (%v, %v); want = (testTypeWithMethod{"test"}, true)`, v, ok)
}
}

type testTypeWithMethod struct {
val string
}

func (v testTypeWithMethod) String() string { return v.val }

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 TestTypeAssertZeroValPanic(t *testing.T) {
defer func() { recover() }()
TypeAssert[int](Value{})
t.Fatalf("TypeAssert did not panic")
}

func TestTypeAssertReadOnlyPanic(t *testing.T) {
defer func() { recover() }()
TypeAssert[int](ValueOf(&testTypeWithMethod{}).FieldByName("val"))
t.Fatalf("TypeAssert did not panic")
}

func TestTypeAssertAllocs(t *testing.T) {
val := ValueOf(new(time.Time)).Elem()
allocs := testing.AllocsPerRun(100, func() {
TypeAssert[time.Time](val)
})
if allocs != 0 {
t.Errorf("unexpected amount of allocations = %v; want = 0", allocs)
}
}

func BenchmarkTypeAssertTime(b *testing.B) {
val := ValueOf(time.Now())
for b.Loop() {
TypeAssert[time.Time](val)
}
}
52 changes: 52 additions & 0 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,58 @@ func valueInterface(v Value, safe bool) any {
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 Interface,
// 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)
}

if abi.TypeFor[T]() != v.typ() {
// TypeAssert[T] should work the same way as v.Interface().(T), thus we need
// to handle following case properly: TypeAssert[any](ValueOf(1)).
// Note that we will not hit here is such case: TypeAssert[any](ValueOf(new(any)).Elem()).
if abi.TypeFor[T]().Kind() == abi.Interface {
v, ok := packEface(v).(T)
return v, ok
}

// Special case: match the element inside the interface.
// TypeAssert[int](ValueOf(newPtr(any(0))).Elem()
if v.kind() == Interface {
// Empty interface has one layout, all interfaces with
// methods have a second layout.
if v.NumMethod() == 0 {
v, ok := (*(*any)(v.ptr)).(T)
return v, ok
}
v, ok := any(*(*interface {
M()
})(v.ptr)).(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
}

// InterfaceData returns a pair of unspecified uintptr values.
// It panics if v's Kind is not Interface.
//
Expand Down