diff --git a/api/next/62121.txt b/api/next/62121.txt new file mode 100644 index 00000000000000..bb220a619aaeda --- /dev/null +++ b/api/next/62121.txt @@ -0,0 +1 @@ +pkg reflect, func TypeAssert[$0 interface{}](Value) ($0, bool) #62121 diff --git a/doc/next/6-stdlib/99-minor/reflect/62121.md b/doc/next/6-stdlib/99-minor/reflect/62121.md new file mode 100644 index 00000000000000..4945891c6ebd1a --- /dev/null +++ b/doc/next/6-stdlib/99-minor/reflect/62121.md @@ -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]. diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 3d1e410dac6323..675fa242b45abb 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -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) + } +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 6e062a56d1c7ac..7d30d2ae3df997 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1513,6 +1513,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. //