Skip to content

Commit

Permalink
reflect: add Value.Clear; support anytype->interface{}, Slice->(*)Arr…
Browse files Browse the repository at this point in the history
…ay in Value.Convert
  • Loading branch information
ben-krieger committed Nov 4, 2024
1 parent 5862b48 commit d00abd5
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 12 deletions.
17 changes: 15 additions & 2 deletions src/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ type rawType struct {
meta uint8 // metadata byte, contains kind and flags (see constants above)
}

// All types that have an element type: named, chan, slice, array, map (but not
// pointer because it doesn't have ptrTo).
// All types that have an element type: named, chan, slice, array, map, interface
// (but not pointer because it doesn't have ptrTo).
type elemType struct {
rawType
numMethod uint16
Expand All @@ -439,6 +439,12 @@ type ptrType struct {
elem *rawType
}

type interfaceType struct {
rawType
ptrTo *rawType
// TODO: methods
}

type arrayType struct {
rawType
numMethod uint16
Expand Down Expand Up @@ -995,6 +1001,10 @@ func (t *rawType) AssignableTo(u Type) bool {
return true
}

if t.underlying() == u.(*rawType).underlying() && (!t.isNamed() || !u.(*rawType).isNamed()) {
return true
}

if u.Kind() == Interface && u.NumMethod() == 0 {
return true
}
Expand Down Expand Up @@ -1060,6 +1070,9 @@ func (t *rawType) NumMethod() int {
return int((*ptrType)(unsafe.Pointer(t)).numMethod)
case Struct:
return int((*structType)(unsafe.Pointer(t)).numMethod)
case Interface:
//FIXME: Use len(methods)
return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod()
}

// Other types have no methods attached. Note we don't panic here.
Expand Down
72 changes: 62 additions & 10 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,25 @@ func (v Value) Cap() int {
}
}

//go:linkname mapclear runtime.hashmapClear
func mapclear(p unsafe.Pointer)

// Clear clears the contents of a map or zeros the contents of a slice
//
// It panics if v's Kind is not Map or Slice.
func (v Value) Clear() {
switch v.typecode.Kind() {
case Map:
mapclear(v.pointer())
case Slice:
hdr := (*sliceHeader)(v.value)
elemSize := v.typecode.underlying().elem().Size()
memzero(hdr.data, elemSize*hdr.len)
default:
panic(&ValueError{Method: "Clear", Kind: v.Kind()})
}
}

// NumField returns the number of fields of this struct. It panics for other
// value types.
func (v Value) NumField() int {
Expand Down Expand Up @@ -888,6 +907,9 @@ func (v Value) Index(i int) Value {
}

func (v Value) NumMethod() int {
if v.typecode == nil {
panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid})
}
return v.typecode.NumMethod()
}

Expand Down Expand Up @@ -1062,7 +1084,7 @@ func (v Value) Set(x Value) {
v.checkAddressable()
v.checkRO()
if !x.typecode.AssignableTo(v.typecode) {
panic("reflect: cannot set")
panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String())
}

if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface {
Expand Down Expand Up @@ -1240,7 +1262,9 @@ func (v Value) OverflowUint(x uint64) bool {
}

func (v Value) CanConvert(t Type) bool {
panic("unimplemented: (reflect.Value).CanConvert()")
// TODO: Optimize this to not actually perform a conversion
_, ok := convertOp(v, t)
return ok
}

func (v Value) Convert(t Type) Value {
Expand All @@ -1262,6 +1286,15 @@ func convertOp(src Value, typ Type) (Value, bool) {
}, true
}

if rtype := typ.(*rawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 {
iface := composeInterface(unsafe.Pointer(src.typecode), src.value)
return Value{
typecode: rtype,
value: unsafe.Pointer(&iface),
flags: valueFlagExported,
}, true
}

switch src.Kind() {
case Int, Int8, Int16, Int32, Int64:
switch rtype := typ.(*rawType); rtype.Kind() {
Expand Down Expand Up @@ -1302,14 +1335,33 @@ func convertOp(src Value, typ Type) (Value, bool) {
*/

case Slice:
if typ.Kind() == String && !src.typecode.elem().isNamed() {
rtype := typ.(*rawType)

switch src.Type().Elem().Kind() {
case Uint8:
return cvtBytesString(src, rtype), true
case Int32:
return cvtRunesString(src, rtype), true
switch rtype := typ.(*rawType); rtype.Kind() {
case Array:
if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() {
return Value{
typecode: rtype,
value: (*sliceHeader)(src.value).data,
flags: src.flags | valueFlagIndirect,
}, true
}
case Pointer:
if rtype.Elem().Kind() == Array {
if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() {
return Value{
typecode: rtype,
value: (*sliceHeader)(src.value).data,
flags: src.flags & (valueFlagExported | valueFlagRO),
}, true
}
}
case String:
if !src.typecode.elem().isNamed() {
switch src.Type().Elem().Kind() {
case Uint8:
return cvtBytesString(src, rtype), true
case Int32:
return cvtRunesString(src, rtype), true
}
}
}

Expand Down
180 changes: 180 additions & 0 deletions src/reflect/value_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package reflect_test

import (
"bytes"
"encoding/base64"
. "reflect"
"sort"
Expand Down Expand Up @@ -599,6 +600,29 @@ func TestAssignableTo(t *testing.T) {
if got, want := refa.Interface().(int), 4; got != want {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}

b := []byte{0x01, 0x02}
refb := ValueOf(&b).Elem()
refb.Set(ValueOf([]byte{0x02, 0x03}))
if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}

type bstr []byte

c := bstr{0x01, 0x02}
refc := ValueOf(&c).Elem()
refc.Set(ValueOf([]byte{0x02, 0x03}))
if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}

d := []byte{0x01, 0x02}
refd := ValueOf(&d).Elem()
refd.Set(ValueOf(bstr{0x02, 0x03}))
if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}
}

func TestConvert(t *testing.T) {
Expand All @@ -624,6 +648,162 @@ func TestConvert(t *testing.T) {
}
}

func TestConvertSliceToArrayOrArrayPointer(t *testing.T) {
s := make([]byte, 2, 4)
// a0 := [0]byte(s)
// a1 := [1]byte(s[1:]) // a1[0] == s[1]
// a2 := [2]byte(s) // a2[0] == s[0]
// a4 := [4]byte(s) // panics: len([4]byte) > len(s)

v := ValueOf(s).Convert(TypeFor[[0]byte]())
if v.Kind() != Array || v.Type().Len() != 0 {
t.Error("Convert([]byte -> [0]byte)")
}
v = ValueOf(s[1:]).Convert(TypeFor[[1]byte]())
if v.Kind() != Array || v.Type().Len() != 1 {
t.Error("Convert([]byte -> [1]byte)")
}
v = ValueOf(s).Convert(TypeFor[[2]byte]())
if v.Kind() != Array || v.Type().Len() != 2 {
t.Error("Convert([]byte -> [2]byte)")
}
if ValueOf(s).CanConvert(TypeFor[[4]byte]()) {
t.Error("Converting a slice with len smaller than array to array should fail")
}

// s0 := (*[0]byte)(s) // s0 != nil
// s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1]
// s2 := (*[2]byte)(s) // &s2[0] == &s[0]
// s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s)
v = ValueOf(s).Convert(TypeFor[*[0]byte]())
if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 0 {
t.Error("Convert([]byte -> *[0]byte)")
}
v = ValueOf(s[1:]).Convert(TypeFor[*[1]byte]())
if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 1 {
t.Error("Convert([]byte -> *[1]byte)")
}
v = ValueOf(s).Convert(TypeFor[*[2]byte]())
if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 2 {
t.Error("Convert([]byte -> *[2]byte)")
}
if ValueOf(s).CanConvert(TypeFor[*[4]byte]()) {
t.Error("Converting a slice with len smaller than array to array pointer should fail")
}

// Test converting slices with backing arrays <= and >64bits
slice64 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
array64 := ValueOf(slice64).Convert(TypeFor[[8]byte]()).Interface().([8]byte)
if !bytes.Equal(slice64, array64[:]) {
t.Errorf("converted array %x does not match backing array of slice %x", array64, slice64)
}

slice72 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}
array72 := ValueOf(slice72).Convert(TypeFor[[9]byte]()).Interface().([9]byte)
if !bytes.Equal(slice72, array72[:]) {
t.Errorf("converted array %x does not match backing array of slice %x", array72, slice72)
}
}

func TestConvertToEmptyInterface(t *testing.T) {
anyType := TypeFor[interface{}]()

v := ValueOf(false).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(bool -> interface{})")
}
_ = v.Interface().(interface{}).(bool)

v = ValueOf(int64(3)).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(int64 -> interface{})")
}
_ = v.Interface().(interface{}).(int64)

v = ValueOf(struct{}{}).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(struct -> interface{})")
}
_ = v.Interface().(interface{}).(struct{})

v = ValueOf([]struct{}{}).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(slice -> interface{})")
}
_ = v.Interface().(interface{}).([]struct{})

v = ValueOf(map[string]string{"A": "B"}).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(map -> interface{})")
}
_ = v.Interface().(interface{}).(map[string]string)
}

func TestClearSlice(t *testing.T) {
type stringSlice []string
for _, test := range []struct {
slice any
expect any
}{
{
slice: []bool{true, false, true},
expect: []bool{false, false, false},
},
{
slice: []byte{0x00, 0x01, 0x02, 0x03},
expect: []byte{0x00, 0x00, 0x00, 0x00},
},
{
slice: [][]int{[]int{2, 1}, []int{3}, []int{}},
expect: [][]int{nil, nil, nil},
},
{
slice: []stringSlice{stringSlice{"hello", "world"}, stringSlice{}, stringSlice{"goodbye"}},
expect: []stringSlice{nil, nil, nil},
},
} {
v := ValueOf(test.slice)
expectLen, expectCap := v.Len(), v.Cap()

v.Clear()
if len := v.Len(); len != expectLen {
t.Errorf("Clear(slice) altered len, got %d, expected %d", len, expectLen)
}
if cap := v.Cap(); cap != expectCap {
t.Errorf("Clear(slice) altered cap, got %d, expected %d", cap, expectCap)
}
if !DeepEqual(test.slice, test.expect) {
t.Errorf("Clear(slice) got %v, expected %v", test.slice, test.expect)
}
}
}

func TestClearMap(t *testing.T) {
for _, test := range []struct {
m any
expect any
}{
{
m: map[int]bool{1: true, 2: false, 3: true},
expect: map[int]bool{},
},
{
m: map[string][]byte{"hello": []byte("world")},
expect: map[string][]byte{},
},
} {
v := ValueOf(test.m)

v.Clear()
if len := v.Len(); len != 0 {
t.Errorf("Clear(map) should set len to 0, got %d", len)
}
if !DeepEqual(test.m, test.expect) {
t.Errorf("Clear(map) got %v, expected %v", test.m, test.expect)
}
}
}

func TestIssue4040(t *testing.T) {
var value interface{} = uint16(0)

Expand Down

0 comments on commit d00abd5

Please sign in to comment.