Skip to content

Commit

Permalink
reflect: add TypeAssert[T]
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusz834 committed Feb 10, 2025
1 parent ff27d27 commit 10ff4ad
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
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].
81 changes: 81 additions & 0 deletions src/reflect/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"math/rand"
"net"
"os"
"reflect"
. "reflect"
"reflect/internal/example1"
"reflect/internal/example2"
Expand Down Expand Up @@ -8681,3 +8682,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](reflect.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)
}
}
31 changes: 31 additions & 0 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,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.
//
Expand Down

0 comments on commit 10ff4ad

Please sign in to comment.