Skip to content

Commit 0a69cba

Browse files
committed
reflect: add TypeAssert
1 parent cfd74c2 commit 0a69cba

3 files changed

Lines changed: 133 additions & 0 deletions

File tree

‎src/internal/reflectlite/value.go‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,64 @@ func (v Value) Interface() interface{} {
8686
return valueInterfaceUnsafe(v)
8787
}
8888

89+
func TypeAssert[T any](v Value) (T, bool) {
90+
if v.typecode == nil {
91+
panic("reflect.TypeAssert: zero Value")
92+
}
93+
if !v.isExported() {
94+
// Do not allow access to unexported values via TypeAssert,
95+
// because they might be pointers that should not be
96+
// writable or methods or function that should not be callable.
97+
panic("reflect.TypeAssert: cannot return value obtained from unexported field or method")
98+
}
99+
100+
typ := TypeFor[T]()
101+
102+
// If v is an interface, return the element inside the interface.
103+
//
104+
// T is a concrete type and v is an interface. For example:
105+
//
106+
// var v any = int(1)
107+
// val := ValueOf(&v).Elem()
108+
// TypeAssert[int](val) == val.Interface().(int)
109+
//
110+
// T is a interface and v is a non-nil interface value. For example:
111+
//
112+
// var v any = &someError{}
113+
// val := ValueOf(&v).Elem()
114+
// TypeAssert[error](val) == val.Interface().(error)
115+
//
116+
// T is a interface and v is a nil interface value. For example:
117+
//
118+
// var v error = nil
119+
// val := ValueOf(&v).Elem()
120+
// TypeAssert[error](val) == val.Interface().(error)
121+
if v.Kind() == Interface {
122+
val, ok := valueInterfaceUnsafe(v).(T)
123+
return val, ok
124+
}
125+
126+
// If T is an interface and v is a concrete type. For example:
127+
//
128+
// TypeAssert[any](ValueOf(1)) == ValueOf(1).Interface().(any)
129+
// TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error)
130+
if typ.Kind() == Interface {
131+
val, ok := valueInterfaceUnsafe(v).(T)
132+
return val, ok
133+
}
134+
135+
// Both v and T must be concrete types.
136+
// The only way for an type-assertion to match is if the types are equal.
137+
if typ != v.typecode {
138+
var zero T
139+
return zero, false
140+
}
141+
if !v.isIndirect() {
142+
return *(*T)(unsafe.Pointer(&v.value)), true
143+
}
144+
return *(*T)(v.value), true
145+
}
146+
89147
// valueInterfaceUnsafe is used by the runtime to hash map keys. It should not
90148
// be subject to the isExported check.
91149
func valueInterfaceUnsafe(v Value) interface{} {

‎src/reflect/value.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ func ValueOf(i interface{}) Value {
1717
return Value{reflectlite.ValueOf(i)}
1818
}
1919

20+
func TypeAssert[T any](v Value) (T, bool) {
21+
return reflectlite.TypeAssert[T](v.Value)
22+
}
23+
2024
func (v Value) Type() Type {
2125
return toType(v.Value.Type())
2226
}

‎src/reflect/value_test.go‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package reflect_test
33
import (
44
"bytes"
55
"encoding/base64"
6+
"fmt"
67
. "reflect"
78
"slices"
89
"sort"
@@ -869,3 +870,73 @@ func equal[T comparable](a, b []T) bool {
869870
}
870871
return true
871872
}
873+
874+
func TestTypeAssert(t *testing.T) {
875+
testTypeAssert(t, int(123456789), int(123456789), true)
876+
testTypeAssert(t, int(-123456789), int(-123456789), true)
877+
testTypeAssert(t, int32(123456789), int32(123456789), true)
878+
testTypeAssert(t, int8(-123), int8(-123), true)
879+
testTypeAssert(t, [2]int{1234, -5678}, [2]int{1234, -5678}, true)
880+
testTypeAssert(t, "test value", "test value", true)
881+
testTypeAssert(t, any("test value"), any("test value"), true)
882+
883+
v := 123456789
884+
testTypeAssert(t, &v, &v, true)
885+
886+
testTypeAssert(t, int(123), uint(0), false)
887+
888+
testTypeAssert[any](t, 1, 1, true)
889+
testTypeAssert[fmt.Stringer](t, 1, nil, false)
890+
891+
vv := testTypeWithMethod{"test"}
892+
testTypeAssert[any](t, vv, vv, true)
893+
testTypeAssert[any](t, &vv, &vv, true)
894+
testTypeAssert[fmt.Stringer](t, vv, vv, true)
895+
testTypeAssert[fmt.Stringer](t, &vv, &vv, true)
896+
testTypeAssert[interface{ A() }](t, vv, nil, false)
897+
testTypeAssert[interface{ A() }](t, &vv, nil, false)
898+
testTypeAssert(t, any(vv), any(vv), true)
899+
testTypeAssert(t, fmt.Stringer(vv), fmt.Stringer(vv), true)
900+
901+
testTypeAssert(t, fmt.Stringer(vv), any(vv), true)
902+
testTypeAssert(t, any(vv), fmt.Stringer(vv), true)
903+
testTypeAssert(t, fmt.Stringer(vv), interface{ M() }(vv), true)
904+
testTypeAssert(t, interface{ M() }(vv), fmt.Stringer(vv), true)
905+
906+
testTypeAssert(t, any(int(1)), int(1), true)
907+
testTypeAssert(t, any(int(1)), byte(0), false)
908+
testTypeAssert(t, fmt.Stringer(vv), vv, true)
909+
}
910+
911+
func testTypeAssert[T comparable, V any](t *testing.T, val V, wantVal T, wantOk bool) {
912+
t.Helper()
913+
914+
v, ok := TypeAssert[T](ValueOf(&val).Elem())
915+
if v != wantVal || ok != wantOk {
916+
t.Errorf("TypeAssert[%v](%#v) = (%#v, %v); want = (%#v, %v)", TypeFor[T](), val, v, ok, wantVal, wantOk)
917+
}
918+
919+
// Additionally make sure that TypeAssert[T](v) behaves in the same way as v.Interface().(T).
920+
v2, ok2 := ValueOf(&val).Elem().Interface().(T)
921+
if v != v2 || ok != ok2 {
922+
t.Errorf("reflect.ValueOf(%#v).Interface().(%v) = (%#v, %v); want = (%#v, %v)", val, TypeFor[T](), v2, ok2, v, ok)
923+
}
924+
}
925+
926+
type testTypeWithMethod struct{ val string }
927+
928+
func (v testTypeWithMethod) String() string { return v.val }
929+
func (v testTypeWithMethod) M() {}
930+
931+
func TestTypeAssertPanic(t *testing.T) {
932+
t.Run("zero val", func(t *testing.T) {
933+
defer func() { recover() }()
934+
TypeAssert[int](Value{})
935+
t.Fatalf("TypeAssert did not panic")
936+
})
937+
t.Run("read only", func(t *testing.T) {
938+
defer func() { recover() }()
939+
TypeAssert[int](ValueOf(&testTypeWithMethod{}).FieldByName("val"))
940+
t.Fatalf("TypeAssert did not panic")
941+
})
942+
}

0 commit comments

Comments
 (0)