reflect: add TypeAssert

This commit is contained in:
Jake Bailey
2026-01-06 21:23:48 -08:00
committed by Ron Evans
parent 42fed60e61
commit 068d7c1b44
3 changed files with 139 additions and 0 deletions
+58
View File
@@ -86,6 +86,64 @@ func (v Value) Interface() interface{} {
return valueInterfaceUnsafe(v)
}
func TypeAssert[T any](v Value) (T, bool) {
if v.typecode == nil {
panic("reflect.TypeAssert: zero Value")
}
if !v.isExported() {
// 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")
}
typ := TypeFor[T]()
// If v is an interface, 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 a non-nil interface value. For example:
//
// var v any = &someError{}
// val := ValueOf(&v).Elem()
// TypeAssert[error](val) == val.Interface().(error)
//
// T is a interface and v is a nil interface value. For example:
//
// var v error = nil
// val := ValueOf(&v).Elem()
// TypeAssert[error](val) == val.Interface().(error)
if v.Kind() == Interface {
val, ok := valueInterfaceUnsafe(v).(T)
return val, ok
}
// If T is an interface and 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() == Interface {
val, ok := valueInterfaceUnsafe(v).(T)
return val, ok
}
// Both v and T must be concrete types.
// The only way for an type-assertion to match is if the types are equal.
if typ != v.typecode {
var zero T
return zero, false
}
if !v.isIndirect() {
return *(*T)(unsafe.Pointer(&v.value)), true
}
return *(*T)(v.value), true
}
// valueInterfaceUnsafe is used by the runtime to hash map keys. It should not
// be subject to the isExported check.
func valueInterfaceUnsafe(v Value) interface{} {
+4
View File
@@ -17,6 +17,10 @@ func ValueOf(i interface{}) Value {
return Value{reflectlite.ValueOf(i)}
}
func TypeAssert[T any](v Value) (T, bool) {
return reflectlite.TypeAssert[T](v.Value)
}
func (v Value) Type() Type {
return toType(v.Value.Type())
}
+77
View File
@@ -3,7 +3,9 @@ package reflect_test
import (
"bytes"
"encoding/base64"
"fmt"
. "reflect"
"runtime"
"slices"
"sort"
"strings"
@@ -869,3 +871,78 @@ func equal[T comparable](a, b []T) bool {
}
return true
}
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 TestTypeAssertPanic(t *testing.T) {
if runtime.GOARCH == "wasm" {
t.Log("recover not supported")
return
}
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")
})
}