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..f6148ceb606904 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/reflect/62121.md @@ -0,0 +1,3 @@ +The new [TypeAssert] function permits converting a [Value] directly to a Go value +of the given type. This is like using a type assertion on the result of [Value.Interface], +but avoids unnecessary memory allocations. diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 16c361e53fcc10..e86ee9d322f639 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8681,3 +8681,132 @@ func TestMapOfKeyPanic(t *testing.T) { var slice []int m.MapIndex(ValueOf(slice)) } + +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 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 TestTypeAssertPanic(t *testing.T) { + 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") + }) +} + +func TestTypeAssertAllocs(t *testing.T) { + typeAssertAllocs[[128]int](t, ValueOf([128]int{}), 0) + typeAssertAllocs[any](t, ValueOf([128]int{}), 0) + + val := 123 + typeAssertAllocs[any](t, ValueOf(val), 0) + typeAssertAllocs[any](t, ValueOf(&val).Elem(), 1) // must allocate, so that Set() does not modify the returned inner iface value. + typeAssertAllocs[int](t, ValueOf(val), 0) + typeAssertAllocs[int](t, ValueOf(&val).Elem(), 0) + + typeAssertAllocs[time.Time](t, ValueOf(new(time.Time)).Elem(), 0) + typeAssertAllocs[time.Time](t, ValueOf(*new(time.Time)), 0) +} + +func typeAssertAllocs[T any](t *testing.T, val Value, wantAllocs int) { + t.Helper() + allocs := testing.AllocsPerRun(10, func() { + TypeAssert[T](val) + }) + if allocs != float64(wantAllocs) { + t.Errorf("TypeAssert[%v](%v) unexpected amount of allocations = %v; want = %v", TypeFor[T](), val.Type(), allocs, wantAllocs) + } +} + +func BenchmarkTypeAssert(b *testing.B) { + benchmarkTypeAssert[int](b, ValueOf(int(1))) + benchmarkTypeAssert[byte](b, ValueOf(int(1))) + + benchmarkTypeAssert[fmt.Stringer](b, ValueOf(testTypeWithMethod{})) + benchmarkTypeAssert[fmt.Stringer](b, ValueOf(&testTypeWithMethod{})) + benchmarkTypeAssert[any](b, ValueOf(int(1))) + benchmarkTypeAssert[any](b, ValueOf(testTypeWithMethod{})) + + benchmarkTypeAssert[time.Time](b, ValueOf(*new(time.Time))) + + benchmarkTypeAssert[func() string](b, ValueOf(time.Now()).MethodByName("String")) +} + +func benchmarkTypeAssert[T any](b *testing.B, val Value) { + b.Run(fmt.Sprintf("TypeAssert[%v](%v)", TypeFor[T](), val.Type()), func(b *testing.B) { + for b.Loop() { + TypeAssert[T](val) + } + }) +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 6e062a56d1c7ac..05ecfa1a5b09ab 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1219,15 +1219,7 @@ func (v Value) Elem() Value { k := v.kind() switch k { case Interface: - var eface any - if v.typ().NumMethod() == 0 { - eface = *(*any)(v.ptr) - } else { - eface = (any)(*(*interface { - M() - })(v.ptr)) - } - x := unpackEface(eface) + x := unpackEface(packIfaceValueIntoEmptyIface(v)) if x.flag != 0 { x.flag |= v.flag.ro() } @@ -1500,19 +1492,91 @@ func valueInterface(v Value, safe bool) any { if v.kind() == Interface { // Special case: return the element inside the interface. - // Empty interface has one layout, all interfaces with - // methods have a second layout. - if v.NumMethod() == 0 { - return *(*any)(v.ptr) - } - return *(*interface { - M() - })(v.ptr) + return packIfaceValueIntoEmptyIface(v) } 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 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") + } + + if v.flag&flagMethod != 0 { + v = makeMethodValue("TypeAssert", v) + } + + typ := abi.TypeFor[T]() + if typ != v.typ() { + // We can't just return false here: + // + // var zero T + // return zero, false + // + // since this function should work in the same manner as v.Interface().(T) does. + // Thus we have to handle two cases specially. + + // 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 an interface, but the iface types are different. For example: + // + // var v any = &someError{} + // val := ValueOf(&v).Elem() + // TypeAssert[error](val) == val.Interface().(error) + if v.kind() == Interface { + v, ok := packIfaceValueIntoEmptyIface(v).(T) + return v, ok + } + + // T is an interface, 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() == abi.Interface { + v, ok := packEface(v).(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 +} + +// packIfaceValueIntoEmptyIface converts an interface Value into an empty interface. +// +// Precondition: v.kind() == Interface +func packIfaceValueIntoEmptyIface(v Value) any { + // Empty interface has one layout, all interfaces with + // methods have a second layout. + if v.NumMethod() == 0 { + return *(*any)(v.ptr) + } + return *(*interface { + M() + })(v.ptr) +} + // InterfaceData returns a pair of unspecified uintptr values. // It panics if v's Kind is not Interface. //