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 {