reflect/protoreflect: add {Message,List,Map}.IsValid
Various protoreflect methods can return an "empty, read-only" message,
list, or map value. Provide a method to test if a value is one of these.
Fixes golang/protobuf#966
Change-Id: I793d8426d6e2201755983c06f024412a7e09bc4c
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/209018
Reviewed-by: Joe Tsai <joetsai@google.com>
diff --git a/internal/cmd/generate-types/impl.go b/internal/cmd/generate-types/impl.go
index b16acbf..401eba8 100644
--- a/internal/cmd/generate-types/impl.go
+++ b/internal/cmd/generate-types/impl.go
@@ -740,6 +740,9 @@
m.messageInfo().init()
m.messageInfo().setUnknown(m.pointer(), b)
}
+func (m *{{.}}) IsValid() bool {
+ return m.pointer().IsNil()
+}
{{end}}
`))
diff --git a/internal/impl/convert_list.go b/internal/impl/convert_list.go
index d17676b..bec1d1d 100644
--- a/internal/impl/convert_list.go
+++ b/internal/impl/convert_list.go
@@ -125,6 +125,9 @@
func (ls *listReflect) NewElement() pref.Value {
return ls.conv.New()
}
+func (ls *listReflect) IsValid() bool {
+ return !ls.v.IsNil()
+}
func (ls *listReflect) protoUnwrap() interface{} {
return ls.v.Interface()
}
diff --git a/internal/impl/convert_map.go b/internal/impl/convert_map.go
index 2ddfc78..a05b131 100644
--- a/internal/impl/convert_map.go
+++ b/internal/impl/convert_map.go
@@ -102,6 +102,9 @@
func (ms *mapReflect) NewValue() pref.Value {
return ms.valConv.New()
}
+func (ms *mapReflect) IsValid() bool {
+ return !ms.v.IsNil()
+}
func (ms *mapReflect) protoUnwrap() interface{} {
return ms.v.Interface()
}
diff --git a/internal/impl/legacy_message.go b/internal/impl/legacy_message.go
index 3d0a921..1bded31 100644
--- a/internal/impl/legacy_message.go
+++ b/internal/impl/legacy_message.go
@@ -457,6 +457,12 @@
func (m aberrantMessage) SetUnknown(pref.RawFields) {
// SetUnknown discards its input on messages which don't support unknown field storage.
}
+func (m aberrantMessage) IsValid() bool {
+ // An invalid message is a read-only, empty message. Since we don't know anything
+ // about the alleged contents of this message, we can't say with confidence that
+ // it is invalid in this sense. Therefore, report it as valid.
+ return true
+}
func (m aberrantMessage) ProtoMethods() *piface.Methods {
return legacyProtoMethods
}
diff --git a/internal/impl/message_reflect_gen.go b/internal/impl/message_reflect_gen.go
index 1c56375..e894659 100644
--- a/internal/impl/message_reflect_gen.go
+++ b/internal/impl/message_reflect_gen.go
@@ -124,6 +124,9 @@
m.messageInfo().init()
m.messageInfo().setUnknown(m.pointer(), b)
}
+func (m *messageState) IsValid() bool {
+ return m.pointer().IsNil()
+}
func (m *messageReflectWrapper) Descriptor() protoreflect.MessageDescriptor {
return m.messageInfo().Desc
@@ -241,3 +244,6 @@
m.messageInfo().init()
m.messageInfo().setUnknown(m.pointer(), b)
}
+func (m *messageReflectWrapper) IsValid() bool {
+ return m.pointer().IsNil()
+}
diff --git a/internal/testprotos/irregular/irregular.go b/internal/testprotos/irregular/irregular.go
index 69220ba..4240124 100644
--- a/internal/testprotos/irregular/irregular.go
+++ b/internal/testprotos/irregular/irregular.go
@@ -82,6 +82,10 @@
func (m *message) GetUnknown() pref.RawFields { return nil }
func (m *message) SetUnknown(pref.RawFields) { return }
+func (m *message) IsValid() bool {
+ return m != nil
+}
+
var fileDesc = func() pref.FileDescriptor {
p := &descriptorpb.FileDescriptorProto{}
if err := prototext.Unmarshal([]byte(descriptorText), p); err != nil {
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index 7e59798..31c7d82 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -230,7 +230,7 @@
// New returns a newly allocated empty message.
New() Message
- // Zero returns an immutable empty message.
+ // Zero returns an empty, read-only message.
Zero() Message
// Descriptor returns the message descriptor.
@@ -445,7 +445,7 @@
// Zero returns a new value for the field.
// For scalars, this returns the default value in native Go form.
- // For composite types, this returns an empty, immutable message, list, or map.
+ // For composite types, this returns an empty, read-only message, list, or map.
Zero() Value
// TypeDescriptor returns the extension type descriptor.
diff --git a/reflect/protoreflect/value.go b/reflect/protoreflect/value.go
index e1d6487..e37a233 100644
--- a/reflect/protoreflect/value.go
+++ b/reflect/protoreflect/value.go
@@ -140,6 +140,16 @@
// SetUnknown is a mutating operation and unsafe for concurrent use.
SetUnknown(RawFields)
+ // IsValid reports whether the message is valid.
+ //
+ // An invalid message is an empty, read-only value.
+ //
+ // An invalid message often corresponds to a nil pointer of the concrete
+ // message type, but the details are implementation dependent.
+ // Validity is not part of the protobuf data model, and may not
+ // be preserved in marshaling or other operations.
+ IsValid() bool
+
// TODO: Add method to retrieve ExtensionType by FieldNumber?
}
@@ -200,6 +210,14 @@
// For other scalars, this returns the zero value.
// For messages, this returns a new, empty, mutable value.
NewElement() Value
+
+ // IsValid reports whether the list is valid.
+ //
+ // An invalid list is an empty, read-only value.
+ //
+ // Validity is not part of the protobuf data model, and may not
+ // be preserved in marshaling or other operations.
+ IsValid() bool
}
// Map is an unordered, associative map.
@@ -245,4 +263,14 @@
// For other scalars, this returns the zero value.
// For messages, this returns a new, empty, mutable value.
NewValue() Value
+
+ // IsValid reports whether the map is valid.
+ //
+ // An invalid map is an empty, read-only value.
+ //
+ // An invalid message often corresponds to a nil Go map value,
+ // but the details are implementation dependent.
+ // Validity is not part of the protobuf data model, and may not
+ // be preserved in marshaling or other operations.
+ IsValid() bool
}
diff --git a/testing/prototest/prototest.go b/testing/prototest/prototest.go
index 79c792d..508ccb9 100644
--- a/testing/prototest/prototest.go
+++ b/testing/prototest/prototest.go
@@ -281,6 +281,7 @@
}
}
}
+func (m testMap) IsValid() bool { return true }
// testFieldList exercises set/get/append/truncate of values in a list.
func testFieldList(t testing.TB, m pref.Message, fd pref.FieldDescriptor) {
@@ -346,6 +347,7 @@
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) {
diff --git a/types/dynamicpb/dynamic.go b/types/dynamicpb/dynamic.go
index 6c0bcbe..b21f6da 100644
--- a/types/dynamicpb/dynamic.go
+++ b/types/dynamicpb/dynamic.go
@@ -253,6 +253,12 @@
m.unknown = r
}
+// IsValid reports whether the message is valid.
+// See protoreflect.Message for details.
+func (m *Message) IsValid() bool {
+ return m.known != nil
+}
+
func (m *Message) checkField(fd pref.FieldDescriptor) {
if fd.IsExtension() && fd.ContainingMessage().FullName() == m.Descriptor().FullName() {
if _, ok := fd.(pref.ExtensionTypeDescriptor); !ok {
@@ -295,9 +301,8 @@
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) NewElement() pref.Value { return newListEntry(x.desc) }
+func (x emptyList) IsValid() bool { return false }
type dynamicList struct {
desc pref.FieldDescriptor
@@ -334,6 +339,10 @@
return newListEntry(x.desc)
}
+func (x *dynamicList) IsValid() bool {
+ return true
+}
+
type dynamicMap struct {
desc pref.FieldDescriptor
mapv map[interface{}]pref.Value
@@ -354,6 +363,9 @@
}
return x.desc.MapValue().Default()
}
+func (x *dynamicMap) IsValid() bool {
+ return x.mapv != nil
+}
func (x *dynamicMap) Range(f func(pref.MapKey, pref.Value) bool) {
for k, v := range x.mapv {