reflect/protoreflect: add List.AppendMutable and Map.Mutable
Add methods to add a new, mutable message to a list or map, matching the
existing Message.Mutable.
These methods are purely a convenience, as each can be implemented in
terms of the existing interface.
Change-Id: I889c20fe37ea0f2a566555212e99e6378fb9fe1d
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/220117
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/internal/impl/convert_list.go b/internal/impl/convert_list.go
index 1c86942..fe9384a 100644
--- a/internal/impl/convert_list.go
+++ b/internal/impl/convert_list.go
@@ -119,6 +119,14 @@
func (ls *listReflect) Append(v pref.Value) {
ls.v.Elem().Set(reflect.Append(ls.v.Elem(), ls.conv.GoValueOf(v)))
}
+func (ls *listReflect) AppendMutable() pref.Value {
+ if _, ok := ls.conv.(*messageConverter); !ok {
+ panic("invalid AppendMutable on list with non-message type")
+ }
+ v := ls.NewElement()
+ ls.Append(v)
+ return v
+}
func (ls *listReflect) Truncate(i int) {
ls.v.Elem().Set(ls.v.Elem().Slice(0, i))
}
diff --git a/internal/impl/convert_map.go b/internal/impl/convert_map.go
index fcb1450..3ef36d3 100644
--- a/internal/impl/convert_map.go
+++ b/internal/impl/convert_map.go
@@ -89,6 +89,17 @@
rk := ms.keyConv.GoValueOf(k.Value())
ms.v.SetMapIndex(rk, reflect.Value{})
}
+func (ms *mapReflect) Mutable(k pref.MapKey) pref.Value {
+ if _, ok := ms.valConv.(*messageConverter); !ok {
+ panic("invalid Mutable on map with non-message value type")
+ }
+ v := ms.Get(k)
+ if !v.IsValid() {
+ v = ms.NewValue()
+ ms.Set(k, v)
+ }
+ return v
+}
func (ms *mapReflect) Range(f func(pref.MapKey, pref.Value) bool) {
iter := mapRange(ms.v)
for iter.Next() {
diff --git a/reflect/protoreflect/value.go b/reflect/protoreflect/value.go
index 5ea914d..2149446 100644
--- a/reflect/protoreflect/value.go
+++ b/reflect/protoreflect/value.go
@@ -200,7 +200,10 @@
// Append is a mutating operation and unsafe for concurrent use.
Append(Value)
- // TODO(blocks): Should there be a AppendMutable method?
+ // AppendMutable appends a new, empty, mutable message value to the end
+ // of the list and returns it.
+ // It panics if the list does not contain a message type.
+ AppendMutable() Value
// Truncate truncates the list to a smaller length.
//
@@ -258,7 +261,11 @@
// Set is a mutating operation and unsafe for concurrent use.
Set(MapKey, Value)
- // TODO(blocks): Should there be a Mutable method?
+ // Mutable retrieves a mutable reference to the entry for the given key.
+ // If no entry exists for the key, it creates a new, empty, mutable value
+ // and stores it as the entry for the key.
+ // It panics if the map value is not a message.
+ Mutable(MapKey) Value
// NewValue returns a new value assignable as a map value.
// For enums, this returns the first enum value.
diff --git a/testing/prototest/prototest.go b/testing/prototest/prototest.go
index 050a61d..0c6b700 100644
--- a/testing/prototest/prototest.go
+++ b/testing/prototest/prototest.go
@@ -296,16 +296,37 @@
t.Errorf("non-existent map key in %q: Map.Get(%v).IsValid() = %v, want %v", name, formatValue(missingKey.Value()), got, want)
}
mapv.Clear(missingKey) // noop
+
+ // Mutable.
+ if fd.MapValue().Message() == nil {
+ if !panics(func() {
+ mapv.Mutable(newMapKey(fd, 1))
+ }) {
+ t.Errorf("Mutable on %q succeeds, want panic", name)
+ }
+ } else {
+ k := newMapKey(fd, 1)
+ v := mapv.Mutable(k)
+ if got, want := mapv.Len(), 1; got != want {
+ t.Errorf("after Mutable on %q, Map.Len() = %v, want %v", name, got, want)
+ }
+ populateMessage(v.Message(), 1, nil)
+ if !valueEqual(mapv.Get(k), v) {
+ t.Errorf("after Mutable on %q, changing new mutable value does not change map entry", name)
+ }
+ mapv.Clear(k)
+ }
}
type testMap map[interface{}]pref.Value
-func (m testMap) Get(k pref.MapKey) pref.Value { return m[k.Interface()] }
-func (m testMap) Set(k pref.MapKey, v pref.Value) { m[k.Interface()] = v }
-func (m testMap) Has(k pref.MapKey) bool { return m.Get(k).IsValid() }
-func (m testMap) Clear(k pref.MapKey) { delete(m, k.Interface()) }
-func (m testMap) Len() int { return len(m) }
-func (m testMap) NewValue() pref.Value { panic("unimplemented") }
+func (m testMap) Get(k pref.MapKey) pref.Value { return m[k.Interface()] }
+func (m testMap) Set(k pref.MapKey, v pref.Value) { m[k.Interface()] = v }
+func (m testMap) Has(k pref.MapKey) bool { return m.Get(k).IsValid() }
+func (m testMap) Clear(k pref.MapKey) { delete(m, k.Interface()) }
+func (m testMap) Mutable(k pref.MapKey) pref.Value { panic("unimplemented") }
+func (m testMap) Len() int { return len(m) }
+func (m testMap) NewValue() pref.Value { panic("unimplemented") }
func (m testMap) Range(f func(pref.MapKey, pref.Value) bool) {
for k, v := range m {
if !f(pref.ValueOf(k).MapKey(), v) {
@@ -383,19 +404,39 @@
t.Errorf("after truncating %q to %d:\nMessage.Get(%v) = %v, want %v", name, n, num, formatValue(got), formatValue(want))
}
}
+
+ // AppendMutable.
+ if fd.Message() == nil {
+ if !panics(func() {
+ list.AppendMutable()
+ }) {
+ t.Errorf("AppendMutable on %q succeeds, want panic", name)
+ }
+ } else {
+ v := list.AppendMutable()
+ if got, want := list.Len(), 1; got != want {
+ t.Errorf("after AppendMutable on %q, list.Len() = %v, want %v", name, got, want)
+ }
+ populateMessage(v.Message(), 1, nil)
+ if !valueEqual(list.Get(0), v) {
+ t.Errorf("after AppendMutable on %q, changing new mutable value does not change list item 0", name)
+ }
+ want.Truncate(0)
+ }
}
type testList struct {
a []pref.Value
}
-func (l *testList) Append(v pref.Value) { l.a = append(l.a, v) }
-func (l *testList) Get(n int) pref.Value { return l.a[n] }
-func (l *testList) Len() int { return len(l.a) }
-func (l *testList) Set(n int, v pref.Value) { l.a[n] = v }
-func (l *testList) Truncate(n int) { l.a = l.a[:n] }
-func (l *testList) NewElement() pref.Value { panic("unimplemented") }
-func (l *testList) IsValid() bool { return true }
+func (l *testList) Append(v pref.Value) { l.a = append(l.a, v) }
+func (l *testList) AppendMutable() pref.Value { panic("unimplemented") }
+func (l *testList) Get(n int) pref.Value { return l.a[n] }
+func (l *testList) Len() int { return len(l.a) }
+func (l *testList) Set(n int, v pref.Value) { l.a[n] = v }
+func (l *testList) Truncate(n int) { l.a = l.a[:n] }
+func (l *testList) NewElement() pref.Value { panic("unimplemented") }
+func (l *testList) IsValid() bool { return true }
// testFieldFloat exercises some interesting floating-point scalar field values.
func testFieldFloat(t testing.TB, m pref.Message, fd pref.FieldDescriptor) {
@@ -605,9 +646,6 @@
mapv.Set(newMapKey(fd, n), newMapValue(fd, mapv, newSeed(n, 0), stack))
return pref.ValueOfMap(mapv)
case fd.Message() != nil:
- //if n == 0 {
- // return m.New().Get(fd)
- //}
return populateMessage(m.NewField(fd).Message(), n, stack)
default:
return newScalarValue(fd, n)
diff --git a/types/dynamicpb/dynamic.go b/types/dynamicpb/dynamic.go
index 991624a..f206c18 100644
--- a/types/dynamicpb/dynamic.go
+++ b/types/dynamicpb/dynamic.go
@@ -314,13 +314,14 @@
desc pref.FieldDescriptor
}
-func (x emptyList) Len() int { return 0 }
-func (x emptyList) Get(n int) pref.Value { panic(errors.New("out of range")) }
-func (x emptyList) Set(n int, v pref.Value) { panic(errors.New("modification of immutable list")) }
-func (x emptyList) Append(v pref.Value) { panic(errors.New("modification of immutable list")) }
-func (x emptyList) Truncate(n int) { panic(errors.New("modification of immutable list")) }
-func (x emptyList) NewElement() pref.Value { return newListEntry(x.desc) }
-func (x emptyList) IsValid() bool { return false }
+func (x emptyList) Len() int { return 0 }
+func (x emptyList) Get(n int) pref.Value { panic(errors.New("out of range")) }
+func (x emptyList) Set(n int, v pref.Value) { panic(errors.New("modification of immutable list")) }
+func (x emptyList) Append(v pref.Value) { panic(errors.New("modification of immutable list")) }
+func (x emptyList) AppendMutable() pref.Value { panic(errors.New("modification of immutable list")) }
+func (x emptyList) Truncate(n int) { panic(errors.New("modification of immutable list")) }
+func (x emptyList) NewElement() pref.Value { return newListEntry(x.desc) }
+func (x emptyList) IsValid() bool { return false }
type dynamicList struct {
desc pref.FieldDescriptor
@@ -345,6 +346,15 @@
x.list = append(x.list, v)
}
+func (x *dynamicList) AppendMutable() pref.Value {
+ if x.desc.Message() == nil {
+ panic(errors.New("%v: invalid AppendMutable on list with non-message type", x.desc.FullName()))
+ }
+ v := x.NewElement()
+ x.Append(v)
+ return v
+}
+
func (x *dynamicList) Truncate(n int) {
// Zero truncated elements to avoid keeping data live.
for i := n; i < len(x.list); i++ {
@@ -374,7 +384,18 @@
}
func (x *dynamicMap) Has(k pref.MapKey) bool { return x.Get(k).IsValid() }
func (x *dynamicMap) Clear(k pref.MapKey) { delete(x.mapv, k.Interface()) }
-func (x *dynamicMap) Len() int { return len(x.mapv) }
+func (x *dynamicMap) Mutable(k pref.MapKey) pref.Value {
+ if x.desc.MapValue().Message() == nil {
+ panic(errors.New("%v: invalid Mutable on map with non-message value type", x.desc.FullName()))
+ }
+ v := x.Get(k)
+ if !v.IsValid() {
+ v = x.NewValue()
+ x.Set(k, v)
+ }
+ return v
+}
+func (x *dynamicMap) Len() int { return len(x.mapv) }
func (x *dynamicMap) NewValue() pref.Value {
if md := x.desc.MapValue().Message(); md != nil {
return pref.ValueOfMessage(NewMessage(md).ProtoReflect())