reflect/prototype: add HasDefault to FieldDescriptor

Provide a way to distinguish between a field with a zero-value
default and one with no default.

Change-Id: I4b1231de2d1bb141099cb1a415b35184dd198f93
Reviewed-on: https://go-review.googlesource.com/135255
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index 9b8dd34..223532d 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -352,6 +352,9 @@
 	// The Value type is determined by the Kind.
 	Default() Value
 
+	// HasDefault reports whether this field has a default value.
+	HasDefault() bool
+
 	// OneofType is the containing oneof that this field belongs to,
 	// and is nil if this field is not part of a oneof.
 	OneofType() OneofDescriptor
diff --git a/reflect/prototype/protofile_type.go b/reflect/prototype/protofile_type.go
index 9a98082..f058403 100644
--- a/reflect/prototype/protofile_type.go
+++ b/reflect/prototype/protofile_type.go
@@ -213,6 +213,7 @@
 func (t fieldDesc) IsMap() bool                                       { return isMap(t) }
 func (t fieldDesc) IsWeak() bool                                      { return t.f.IsWeak }
 func (t fieldDesc) Default() pref.Value                               { return t.f.dv.lazyInit(t, t.f.Default) }
+func (t fieldDesc) HasDefault() bool                                  { return !t.f.Default.IsNull() }
 func (t fieldDesc) OneofType() pref.OneofDescriptor                   { return t.f.ot.lazyInit(t, t.f.OneofName) }
 func (t fieldDesc) ExtendedType() pref.MessageDescriptor              { return nil }
 func (t fieldDesc) MessageType() pref.MessageDescriptor               { return t.f.mt.lazyInit(t, &t.f.MessageType) }
@@ -321,6 +322,7 @@
 func (t extensionDesc) IsMap() bool                                       { return false }
 func (t extensionDesc) IsWeak() bool                                      { return false }
 func (t extensionDesc) Default() pref.Value                               { return t.x.dv.lazyInit(t, t.x.Default) }
+func (t extensionDesc) HasDefault() bool                                  { return !t.x.Default.IsNull() }
 func (t extensionDesc) OneofType() pref.OneofDescriptor                   { return nil }
 func (t extensionDesc) ExtendedType() pref.MessageDescriptor {
 	return t.x.xt.lazyInit(t, &t.x.ExtendedType)
diff --git a/reflect/prototype/standalone.go b/reflect/prototype/standalone.go
index 84fdac1..18d77f4 100644
--- a/reflect/prototype/standalone.go
+++ b/reflect/prototype/standalone.go
@@ -71,6 +71,8 @@
 	MessageType  protoreflect.MessageDescriptor
 	EnumType     protoreflect.EnumDescriptor
 	ExtendedType protoreflect.MessageDescriptor
+
+	dv defaultValue
 }
 
 // NewExtension creates a new protoreflect.ExtensionDescriptor.
diff --git a/reflect/prototype/standalone_type.go b/reflect/prototype/standalone_type.go
index 56dcc3c..0bcee4a 100644
--- a/reflect/prototype/standalone_type.go
+++ b/reflect/prototype/standalone_type.go
@@ -65,7 +65,8 @@
 func (t standaloneExtension) IsPacked() bool                                    { return t.x.IsPacked }
 func (t standaloneExtension) IsMap() bool                                       { return false }
 func (t standaloneExtension) IsWeak() bool                                      { return false }
-func (t standaloneExtension) Default() pref.Value                               { return t.x.Default }
+func (t standaloneExtension) Default() pref.Value                               { return t.x.dv.lazyInit(t, t.x.Default) }
+func (t standaloneExtension) HasDefault() bool                                  { return !t.x.Default.IsNull() }
 func (t standaloneExtension) OneofType() pref.OneofDescriptor                   { return nil }
 func (t standaloneExtension) MessageType() pref.MessageDescriptor               { return t.x.MessageType }
 func (t standaloneExtension) EnumType() pref.EnumDescriptor                     { return t.x.EnumType }
diff --git a/reflect/prototype/stringer.go b/reflect/prototype/stringer.go
index 20805fa..d97e030 100644
--- a/reflect/prototype/stringer.go
+++ b/reflect/prototype/stringer.go
@@ -86,7 +86,7 @@
 var descriptorAccessors = map[reflect.Type][]string{
 	reflect.TypeOf((*pref.FileDescriptor)(nil)).Elem():      {"Path", "Package", "Imports", "Messages", "Enums", "Extensions", "Services"},
 	reflect.TypeOf((*pref.MessageDescriptor)(nil)).Elem():   {"IsMapEntry", "Fields", "Oneofs", "RequiredNumbers", "ExtensionRanges", "Messages", "Enums", "Extensions"},
-	reflect.TypeOf((*pref.FieldDescriptor)(nil)).Elem():     {"Number", "Cardinality", "Kind", "JSONName", "IsPacked", "IsMap", "IsWeak", "Default", "OneofType", "ExtendedType", "MessageType", "EnumType"},
+	reflect.TypeOf((*pref.FieldDescriptor)(nil)).Elem():     {"Number", "Cardinality", "Kind", "JSONName", "IsPacked", "IsMap", "IsWeak", "HasDefault", "Default", "OneofType", "ExtendedType", "MessageType", "EnumType"},
 	reflect.TypeOf((*pref.OneofDescriptor)(nil)).Elem():     {"Fields"}, // not directly used; must keep in sync with formatDescOpt
 	reflect.TypeOf((*pref.EnumDescriptor)(nil)).Elem():      {"Values"},
 	reflect.TypeOf((*pref.EnumValueDescriptor)(nil)).Elem(): {"Number"},
diff --git a/reflect/prototype/type_test.go b/reflect/prototype/type_test.go
index 00404ff..fc04da9 100644
--- a/reflect/prototype/type_test.go
+++ b/reflect/prototype/type_test.go
@@ -672,7 +672,7 @@
 }
 
 func testFileFormatCompact(t *testing.T, fd pref.FileDescriptor) {
-	const want = `FileDescriptor{Syntax: proto2, Path: "path/to/file.proto", Package: test, Messages: [{Name: A, IsMapEntry: true, Fields: [{Name: key, Number: 1, Cardinality: optional, Kind: string, JSONName: "key"}, {Name: value, Number: 2, Cardinality: optional, Kind: message, JSONName: "value", MessageType: test.B}]}, {Name: B, Fields: [{Name: field_one, Number: 1, Cardinality: optional, Kind: string, JSONName: "fieldOne", Default: "hello", OneofType: O1}, {Name: field_two, Number: 2, Cardinality: optional, Kind: enum, JSONName: "Field2", Default: 1, OneofType: O2, EnumType: test.E1}, {Name: field_three, Number: 3, Cardinality: optional, Kind: message, JSONName: "fieldThree", OneofType: O2, MessageType: test.C}, {Name: field_four, Number: 4, Cardinality: repeated, Kind: message, JSONName: "Field4", IsMap: true, MessageType: test.A}, {Name: field_five, Number: 5, Cardinality: repeated, Kind: int32, JSONName: "fieldFive", IsPacked: true}, {Name: field_six, Number: 6, Cardinality: required, Kind: bytes, JSONName: "fieldSix"}], Oneofs: [{Name: O1, Fields: [field_one]}, {Name: O2, Fields: [field_two, field_three]}], RequiredNumbers: [6], ExtensionRanges: [1000:2000, 3000]}, {Name: C, Messages: [{Name: A, Fields: [{Name: F, Number: 1, Cardinality: required, Kind: bytes, JSONName: "F", Default: "dead\xbe\xef"}], RequiredNumbers: [1]}], Enums: [{Name: E1, Values: [{Name: FOO}, {Name: BAR, Number: 1}]}], Extensions: [{Name: X, Number: 1000, Cardinality: repeated, Kind: message, ExtendedType: test.B, MessageType: test.C}]}], Enums: [{Name: E1, Values: [{Name: FOO}, {Name: BAR, Number: 1}]}], Extensions: [{Name: X, Number: 1000, Cardinality: repeated, Kind: message, IsPacked: true, ExtendedType: test.B, MessageType: test.C}], Services: [{Name: S, Methods: [{Name: M, InputType: test.A, OutputType: test.C.A, IsStreamingClient: true, IsStreamingServer: true}]}]}`
+	const want = `FileDescriptor{Syntax: proto2, Path: "path/to/file.proto", Package: test, Messages: [{Name: A, IsMapEntry: true, Fields: [{Name: key, Number: 1, Cardinality: optional, Kind: string, JSONName: "key"}, {Name: value, Number: 2, Cardinality: optional, Kind: message, JSONName: "value", MessageType: test.B}]}, {Name: B, Fields: [{Name: field_one, Number: 1, Cardinality: optional, Kind: string, JSONName: "fieldOne", HasDefault: true, Default: "hello", OneofType: O1}, {Name: field_two, Number: 2, Cardinality: optional, Kind: enum, JSONName: "Field2", HasDefault: true, Default: 1, OneofType: O2, EnumType: test.E1}, {Name: field_three, Number: 3, Cardinality: optional, Kind: message, JSONName: "fieldThree", OneofType: O2, MessageType: test.C}, {Name: field_four, Number: 4, Cardinality: repeated, Kind: message, JSONName: "Field4", IsMap: true, MessageType: test.A}, {Name: field_five, Number: 5, Cardinality: repeated, Kind: int32, JSONName: "fieldFive", IsPacked: true}, {Name: field_six, Number: 6, Cardinality: required, Kind: bytes, JSONName: "fieldSix"}], Oneofs: [{Name: O1, Fields: [field_one]}, {Name: O2, Fields: [field_two, field_three]}], RequiredNumbers: [6], ExtensionRanges: [1000:2000, 3000]}, {Name: C, Messages: [{Name: A, Fields: [{Name: F, Number: 1, Cardinality: required, Kind: bytes, JSONName: "F", HasDefault: true, Default: "dead\xbe\xef"}], RequiredNumbers: [1]}], Enums: [{Name: E1, Values: [{Name: FOO}, {Name: BAR, Number: 1}]}], Extensions: [{Name: X, Number: 1000, Cardinality: repeated, Kind: message, ExtendedType: test.B, MessageType: test.C}]}], Enums: [{Name: E1, Values: [{Name: FOO}, {Name: BAR, Number: 1}]}], Extensions: [{Name: X, Number: 1000, Cardinality: repeated, Kind: message, IsPacked: true, ExtendedType: test.B, MessageType: test.C}], Services: [{Name: S, Methods: [{Name: M, InputType: test.A, OutputType: test.C.A, IsStreamingClient: true, IsStreamingServer: true}]}]}`
 	got := fmt.Sprintf("%v", fd)
 	got = strings.Replace(got, "FileDescriptor ", "FileDescriptor", 1) // cleanup randomizer
 	if got != want {
@@ -710,6 +710,7 @@
 			Cardinality: optional
 			Kind:        string
 			JSONName:    "fieldOne"
+			HasDefault:  true
 			Default:     "hello"
 			OneofType:   O1
 		}, {
@@ -718,6 +719,7 @@
 			Cardinality: optional
 			Kind:        enum
 			JSONName:    "Field2"
+			HasDefault:  true
 			Default:     1
 			OneofType:   O2
 			EnumType:    test.E1
@@ -770,6 +772,7 @@
 				Cardinality: required
 				Kind:        bytes
 				JSONName:    "F"
+				HasDefault:  true
 				Default:     "dead\xbe\xef"
 			}]
 			RequiredNumbers: [1]