diff options
author | Ben Krieger <[email protected]> | 2024-10-13 10:23:23 -0400 |
---|---|---|
committer | Damian Gryski <[email protected]> | 2024-11-13 10:28:33 -0700 |
commit | e98fea0bba058b0a46dd1874d9a9f516bcc19304 (patch) | |
tree | 564cf48882c581c93d5431c633c8eec03ef953d3 /src | |
parent | c728e031df92115cea14099b1c672c07f4d14ff3 (diff) | |
download | tinygo-e98fea0bba058b0a46dd1874d9a9f516bcc19304.tar.gz tinygo-e98fea0bba058b0a46dd1874d9a9f516bcc19304.zip |
reflect: add Value.Clear; support anytype->interface{}, Slice->(*)Array in Value.Convert
Diffstat (limited to 'src')
-rw-r--r-- | src/reflect/type.go | 17 | ||||
-rw-r--r-- | src/reflect/value.go | 72 | ||||
-rw-r--r-- | src/reflect/value_test.go | 180 |
3 files changed, 257 insertions, 12 deletions
diff --git a/src/reflect/type.go b/src/reflect/type.go index b18a1a9d4..05915350a 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -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 @@ -439,6 +439,12 @@ type ptrType struct { elem *rawType } +type interfaceType struct { + rawType + ptrTo *rawType + // TODO: methods +} + type arrayType struct { rawType numMethod uint16 @@ -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 } @@ -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. diff --git a/src/reflect/value.go b/src/reflect/value.go index d1f8cb2f7..78453be34 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -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 { @@ -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() } @@ -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 { @@ -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 { @@ -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() { @@ -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 + } } } diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 508b358ad..a32ba4fa9 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -1,6 +1,7 @@ package reflect_test import ( + "bytes" "encoding/base64" . "reflect" "sort" @@ -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) { @@ -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) |