From cb41764b34d4af78ed85b3d8146480a22f9b2759 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 10 Feb 2025 15:38:15 +0100 Subject: [PATCH 1/4] reflect: add TypeAssert[T] --- api/next/62121.txt | 1 + doc/next/6-stdlib/99-minor/reflect/62121.md | 2 + src/reflect/all_test.go | 80 +++++++++++++++++++++ src/reflect/value.go | 31 ++++++++ 4 files changed, 114 insertions(+) create mode 100644 api/next/62121.txt create mode 100644 doc/next/6-stdlib/99-minor/reflect/62121.md 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..d97da8bbbce413 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8681,3 +8681,83 @@ 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 TestTypeAssert(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, newPtr(time.Now())) + + testTypeAssertDifferentType[uint](t, int(111111111)) + testTypeAssertDifferentType[uint](t, int(-111111111)) +} + +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) + } +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 6e062a56d1c7ac..d6c0dd71ea36a6 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1513,6 +1513,37 @@ 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_ { + var zero T + return zero, false + } + + if v.typ_.IsDirectIface() { + 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. // From d50547d1038bbb0dc3af32fcc9a8c812d26ff646 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 11 Feb 2025 12:30:55 +0100 Subject: [PATCH 2/4] Make sure that it is equivalent to v.Interface().(T) --- src/reflect/all_test.go | 32 ++++++++++++++++++++++++++++++-- src/reflect/value.go | 12 +++++++++--- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index d97da8bbbce413..239f247ee96e75 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8701,7 +8701,7 @@ func newPtr[T any](t T) *T { return &t } -func TestTypeAssert(t *testing.T) { +func TestTypeAssertConcreteTypes(t *testing.T) { testTypeAssert(t, int(1111)) testTypeAssert(t, int(111111111)) testTypeAssert(t, int(-111111111)) @@ -8714,13 +8714,41 @@ func TestTypeAssert(t *testing.T) { 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) + } +} + type testTypeWithMethod struct { val string } diff --git a/src/reflect/value.go b/src/reflect/value.go index d6c0dd71ea36a6..9d2b37a2ae6813 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1520,7 +1520,6 @@ 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 @@ -1532,12 +1531,19 @@ func TypeAssert[T any](v Value) (T, bool) { v = makeMethodValue("TypeAssert", v) } - if abi.TypeFor[T]() != v.typ_ { + 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(any(1))). + if abi.TypeFor[T]().Kind() == abi.Interface { + v, ok := packEface(v).(T) + return v, ok + } var zero T return zero, false } - if v.typ_.IsDirectIface() { + if v.typ().IsDirectIface() { return *(*T)(unsafe.Pointer(&v.ptr)), true } From 31a7a1878f38a11aadce402a03c2829d28dffbfb Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 11 Feb 2025 13:15:21 +0100 Subject: [PATCH 3/4] fix typo --- src/reflect/value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 9d2b37a2ae6813..6ad74c0d4ea7e4 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1534,7 +1534,7 @@ func TypeAssert[T any](v Value) (T, bool) { 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(any(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 From 83c22e189275a12f7379df9164c356596f6b08cd Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 11 Feb 2025 17:57:33 +0100 Subject: [PATCH 4/4] update Change-Id: Id8d5f247884fbe189a4c778203ea7cf575b1a93a --- src/reflect/all_test.go | 15 +++++++++++++++ src/reflect/value.go | 19 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 239f247ee96e75..675fa242b45abb 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8747,6 +8747,14 @@ func TestTypeAssertInterfaceTypes(t *testing.T) { 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 { @@ -8789,3 +8797,10 @@ func TestTypeAssertAllocs(t *testing.T) { 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 6ad74c0d4ea7e4..7d30d2ae3df997 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1539,14 +1539,29 @@ func TypeAssert[T any](v Value) (T, bool) { 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.typ().IsDirectIface() { + if v.flag&flagIndir == 0 { return *(*T)(unsafe.Pointer(&v.ptr)), true } - return *(*T)(v.ptr), true }