internal/impl: implement Map fields
Generate functions for wrapping map[K]V to implement protoreflect.Map.
This implementation uses Go reflection instead to provide a single implementation
that can handle all Go map types.
Change-Id: Idcb8069ef836614a88e5df12ef7c5044e8aa3dea
Reviewed-on: https://go-review.googlesource.com/c/142778
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/impl/message_field.go b/internal/impl/message_field.go
index aef3993..71fb1de 100644
--- a/internal/impl/message_field.go
+++ b/internal/impl/message_field.go
@@ -36,10 +36,109 @@
}
func fieldInfoForMap(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo {
- // TODO: support map fields.
- panic(fmt.Sprintf("invalid field: %v", fd))
+ ft := fs.Type
+ if ft.Kind() != reflect.Map {
+ panic(fmt.Sprintf("invalid type: got %v, want map kind", ft))
+ }
+ keyConv := matchGoTypePBKind(ft.Key(), fd.MessageType().Fields().ByNumber(1).Kind())
+ valConv := matchGoTypePBKind(ft.Elem(), fd.MessageType().Fields().ByNumber(2).Kind())
+ fieldOffset := offsetOf(fs)
+ // TODO: Implement unsafe fast path?
+ return fieldInfo{
+ has: func(p pointer) bool {
+ rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+ return rv.Len() > 0
+ },
+ get: func(p pointer) pref.Value {
+ rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+ return pref.ValueOf(mapReflect{rv, keyConv, valConv})
+ },
+ set: func(p pointer, v pref.Value) {
+ rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+ rv.Set(v.Map().(mapReflect).v)
+ },
+ clear: func(p pointer) {
+ rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+ rv.Set(reflect.Zero(rv.Type()))
+ },
+ mutable: func(p pointer) pref.Mutable {
+ rv := p.apply(fieldOffset).asType(fs.Type).Elem()
+ return mapReflect{rv, keyConv, valConv}
+ },
+ }
}
+type mapReflect struct {
+ v reflect.Value // addressable map[K]V
+ keyConv converter
+ valConv converter
+}
+
+func (ms mapReflect) List() []pref.MapKey {
+ var ks []pref.MapKey
+ for _, k := range ms.v.MapKeys() {
+ ks = append(ks, ms.keyConv.toPB(k).MapKey())
+ }
+ return ks
+}
+func (ms mapReflect) Len() int {
+ return ms.v.Len()
+}
+func (ms mapReflect) Has(k pref.MapKey) bool {
+ rk := ms.keyConv.toGo(k.Value())
+ rv := ms.v.MapIndex(rk)
+ return rv.IsValid()
+}
+func (ms mapReflect) Get(k pref.MapKey) pref.Value {
+ rk := ms.keyConv.toGo(k.Value())
+ rv := ms.v.MapIndex(rk)
+ if !rv.IsValid() {
+ return pref.Value{}
+ }
+ return ms.valConv.toPB(rv)
+}
+func (ms mapReflect) Set(k pref.MapKey, v pref.Value) {
+ if ms.v.IsNil() {
+ ms.v.Set(reflect.MakeMap(ms.v.Type()))
+ }
+ rk := ms.keyConv.toGo(k.Value())
+ rv := ms.valConv.toGo(v)
+ ms.v.SetMapIndex(rk, rv)
+}
+func (ms mapReflect) Clear(k pref.MapKey) {
+ rk := ms.keyConv.toGo(k.Value())
+ ms.v.SetMapIndex(rk, reflect.Value{})
+}
+func (ms mapReflect) Mutable(k pref.MapKey) pref.Mutable {
+ // Mutable is only valid for messages and panics for other kinds.
+ if ms.v.IsNil() {
+ ms.v.Set(reflect.MakeMap(ms.v.Type()))
+ }
+ rk := ms.keyConv.toGo(k.Value())
+ rv := ms.v.MapIndex(rk)
+ if !rv.IsValid() || rv.IsNil() {
+ pv := pref.ValueOf(ms.valConv.newMessage())
+ rv = ms.valConv.toGo(pv)
+ ms.v.SetMapIndex(rk, rv)
+ }
+ return rv.Interface().(pref.Message)
+}
+func (ms mapReflect) Range(f func(pref.MapKey, pref.Value) bool) {
+ for _, k := range ms.v.MapKeys() {
+ if v := ms.v.MapIndex(k); v.IsValid() {
+ pk := ms.keyConv.toPB(k).MapKey()
+ pv := ms.valConv.toPB(v)
+ if !f(pk, pv) {
+ return
+ }
+ }
+ }
+}
+func (ms mapReflect) Unwrap() interface{} { // TODO: unexport?
+ return ms.v.Interface()
+}
+func (ms mapReflect) ProtoMutable() {}
+
func fieldInfoForVector(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
if ft.Kind() != reflect.Slice {