all: make handling of zero-value composites more consistent
We occasionally need to work with immutable, empty lists, maps, and
messages. Notably, Message.Get on an empty repeated field will return a
"frozen" empty value.
Move handling of these immutable, zero-length composites into Converter,
to unify the behavior of regular and extension fields.
Add a Zero method to Converter, MessageType, and ExtensionType, to
provide a consistent way to get an empty, frozen value of a composite
type. Adding this method to the public {Message,Extension}Type
interfaces does increase our API surface, but lets us (for example)
cleanly represent an empty map as a nil map rather than a non-nil
one wrapped in a frozenMap type.
Drop the frozen{List,Map,Message} types as no longer necessary.
(These types did have support for creating a read-only view of a
non-empty value, but we are not currently using that feature.)
Change-Id: Ia76f149d591da07b40ce75b7404a7ab8a60cb9d8
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/189339
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/internal/filetype/build.go b/internal/filetype/build.go
index 3b1c97e..f30f98c 100644
--- a/internal/filetype/build.go
+++ b/internal/filetype/build.go
@@ -346,7 +346,8 @@
conv pimpl.Converter
}
-func (t *Extension) New() pref.Value { return t.lazyInit().New() }
+func (t *Extension) New() pref.Value { return t.lazyInit().New() }
+func (t *Extension) Zero() pref.Value { return t.lazyInit().Zero() }
func (t *Extension) ValueOf(v interface{}) pref.Value {
return t.lazyInit().PBValueOf(reflect.ValueOf(v))
}
diff --git a/internal/impl/convert.go b/internal/impl/convert.go
index 86da513..4b280ef 100644
--- a/internal/impl/convert.go
+++ b/internal/impl/convert.go
@@ -19,9 +19,21 @@
// A Converter coverts to/from Go reflect.Value types and protobuf protoreflect.Value types.
type Converter interface {
+ // PBValueOf converts a reflect.Value to a protoreflect.Value.
PBValueOf(reflect.Value) pref.Value
+
+ // GoValueOf converts a protoreflect.Value to a reflect.Value.
GoValueOf(pref.Value) reflect.Value
+
+ // New returns a new field value.
+ // For scalars, it returns the default value of the field.
+ // For composite types, it returns a new mutable value.
New() pref.Value
+
+ // Zero returns a new field value.
+ // For scalars, it returns the default value of the field.
+ // For composite types, it returns an immutable, empty value.
+ Zero() pref.Value
}
// NewConverter matches a Go type with a protobuf field and returns a Converter
@@ -158,6 +170,10 @@
return c.def
}
+func (c *scalarConverter) Zero() pref.Value {
+ return c.New()
+}
+
type enumConverter struct {
goType reflect.Type
def pref.Value
@@ -188,6 +204,10 @@
return c.def
}
+func (c *enumConverter) Zero() pref.Value {
+ return c.def
+}
+
type messageConverter struct {
goType reflect.Type
}
@@ -223,3 +243,7 @@
func (c *messageConverter) New() pref.Value {
return c.PBValueOf(reflect.New(c.goType.Elem()))
}
+
+func (c *messageConverter) Zero() pref.Value {
+ return c.PBValueOf(reflect.Zero(c.goType))
+}
diff --git a/internal/impl/convert_list.go b/internal/impl/convert_list.go
index 26d3e4c..f9001b5 100644
--- a/internal/impl/convert_list.go
+++ b/internal/impl/convert_list.go
@@ -38,6 +38,10 @@
return c.PBValueOf(reflect.New(c.goType.Elem()))
}
+func (c *listConverter) Zero() pref.Value {
+ return c.PBValueOf(reflect.Zero(c.goType))
+}
+
type listReflect struct {
v reflect.Value // *[]T
conv Converter
diff --git a/internal/impl/convert_map.go b/internal/impl/convert_map.go
index 185d408..4182cbe 100644
--- a/internal/impl/convert_map.go
+++ b/internal/impl/convert_map.go
@@ -42,6 +42,10 @@
return c.PBValueOf(reflect.MakeMap(c.goType))
}
+func (c *mapConverter) Zero() pref.Value {
+ return c.PBValueOf(reflect.Zero(c.goType))
+}
+
type mapReflect struct {
v reflect.Value // map[K]V
keyConv Converter
diff --git a/internal/impl/legacy_extension.go b/internal/impl/legacy_extension.go
index e6e86fb..aaf8fcc 100644
--- a/internal/impl/legacy_extension.go
+++ b/internal/impl/legacy_extension.go
@@ -232,6 +232,7 @@
func (x *legacyExtensionType) GoType() reflect.Type { return x.typ }
func (x *legacyExtensionType) New() pref.Value { return x.conv.New() }
+func (x *legacyExtensionType) Zero() pref.Value { return x.conv.Zero() }
func (x *legacyExtensionType) ValueOf(v interface{}) pref.Value {
return x.conv.PBValueOf(reflect.ValueOf(v))
}
diff --git a/internal/impl/legacy_test.go b/internal/impl/legacy_test.go
index 67f6011..70c5603 100644
--- a/internal/impl/legacy_test.go
+++ b/internal/impl/legacy_test.go
@@ -491,8 +491,8 @@
switch name {
case "ParentFile", "Parent":
// Ignore parents to avoid recursive cycle.
- case "New":
- // Ignore New since it a constructor.
+ case "New", "Zero":
+ // Ignore constructors.
case "Options":
// Ignore descriptor options since protos are not cmperable.
case "ContainingOneof", "ContainingMessage", "Enum", "Message":
diff --git a/internal/impl/message_field.go b/internal/impl/message_field.go
index 3dfac23..7eb6199 100644
--- a/internal/impl/message_field.go
+++ b/internal/impl/message_field.go
@@ -42,10 +42,6 @@
}
conv := NewConverter(ot.Field(0).Type, fd)
isMessage := fd.Message() != nil
- var frozenEmpty pref.Value
- if isMessage {
- frozenEmpty = pref.ValueOf(frozenMessage{conv.New().Message()})
- }
// TODO: Implement unsafe fast path?
fieldOffset := offsetOf(fs, x)
@@ -74,17 +70,11 @@
},
get: func(p pointer) pref.Value {
if p.IsNil() {
- if frozenEmpty.IsValid() {
- return frozenEmpty
- }
- return defaultValueOf(fd)
+ return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if rv.IsNil() || rv.Elem().Type().Elem() != ot {
- if frozenEmpty.IsValid() {
- return frozenEmpty
- }
- return defaultValueOf(fd)
+ return conv.Zero()
}
rv = rv.Elem().Elem().Field(0)
return conv.PBValueOf(rv)
@@ -126,7 +116,6 @@
panic(fmt.Sprintf("invalid type: got %v, want map kind", ft))
}
conv := NewConverter(ft, fd)
- frozenEmpty := pref.ValueOf(frozenMap{conv.New().Map()})
// TODO: Implement unsafe fast path?
fieldOffset := offsetOf(fs, x)
@@ -145,12 +134,9 @@
},
get: func(p pointer) pref.Value {
if p.IsNil() {
- return frozenEmpty
+ return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
- if rv.Len() == 0 {
- return frozenEmpty
- }
return conv.PBValueOf(rv)
},
set: func(p pointer, v pref.Value) {
@@ -176,7 +162,6 @@
panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
}
conv := NewConverter(reflect.PtrTo(ft), fd)
- frozenEmpty := pref.ValueOf(frozenList{conv.New().List()})
// TODO: Implement unsafe fast path?
fieldOffset := offsetOf(fs, x)
@@ -195,12 +180,9 @@
},
get: func(p pointer) pref.Value {
if p.IsNil() {
- return frozenEmpty
+ return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type)
- if rv.Elem().Len() == 0 {
- return frozenEmpty
- }
return conv.PBValueOf(rv)
},
set: func(p pointer, v pref.Value) {
@@ -269,12 +251,12 @@
},
get: func(p pointer) pref.Value {
if p.IsNil() {
- return defaultValueOf(fd)
+ return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if nullable {
if rv.IsNil() {
- return defaultValueOf(fd)
+ return conv.Zero()
}
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
@@ -312,7 +294,6 @@
var once sync.Once
var messageType pref.MessageType
- var frozenEmpty pref.Value
lazyInit := func() {
once.Do(func() {
messageName := fd.Message().FullName()
@@ -320,7 +301,6 @@
if messageType == nil {
panic(fmt.Sprintf("weak message %v is not linked in", messageName))
}
- frozenEmpty = pref.ValueOf(frozenMessage{messageType.New()})
})
}
@@ -342,12 +322,12 @@
get: func(p pointer) pref.Value {
lazyInit()
if p.IsNil() {
- return frozenEmpty
+ return pref.ValueOf(messageType.Zero())
}
fs := p.Apply(weakOffset).WeakFields()
m, ok := (*fs)[num]
if !ok {
- return frozenEmpty
+ return pref.ValueOf(messageType.Zero())
}
return pref.ValueOf(m.(pref.ProtoMessage).ProtoReflect())
},
@@ -390,7 +370,6 @@
func fieldInfoForMessage(fd pref.FieldDescriptor, fs reflect.StructField, x exporter) fieldInfo {
ft := fs.Type
conv := NewConverter(ft, fd)
- frozenEmpty := pref.ValueOf(frozenMessage{conv.New().Message()})
// TODO: Implement unsafe fast path?
fieldOffset := offsetOf(fs, x)
@@ -409,12 +388,9 @@
},
get: func(p pointer) pref.Value {
if p.IsNil() {
- return frozenEmpty
+ return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
- if rv.IsNil() {
- return frozenEmpty
- }
return conv.PBValueOf(rv)
},
set: func(p pointer, v pref.Value) {
@@ -461,76 +437,3 @@
},
}
}
-
-// defaultValueOf returns the default value for the field.
-func defaultValueOf(fd pref.FieldDescriptor) pref.Value {
- if fd == nil {
- return pref.Value{}
- }
- pv := fd.Default() // invalid Value for messages and repeated fields
- if fd.Kind() == pref.BytesKind && pv.IsValid() && len(pv.Bytes()) > 0 {
- return pref.ValueOf(append([]byte(nil), pv.Bytes()...)) // copy default bytes for safety
- }
- return pv
-}
-
-// frozenValueOf returns a frozen version of any composite value.
-func frozenValueOf(v pref.Value) pref.Value {
- switch v := v.Interface().(type) {
- case pref.Message:
- if _, ok := v.(frozenMessage); !ok {
- return pref.ValueOf(frozenMessage{v})
- }
- case pref.List:
- if _, ok := v.(frozenList); !ok {
- return pref.ValueOf(frozenList{v})
- }
- case pref.Map:
- if _, ok := v.(frozenMap); !ok {
- return pref.ValueOf(frozenMap{v})
- }
- }
- return v
-}
-
-type frozenMessage struct{ pref.Message }
-
-func (m frozenMessage) ProtoReflect() pref.Message { return m }
-func (m frozenMessage) Interface() pref.ProtoMessage { return m }
-func (m frozenMessage) Range(f func(pref.FieldDescriptor, pref.Value) bool) {
- m.Message.Range(func(fd pref.FieldDescriptor, v pref.Value) bool {
- return f(fd, frozenValueOf(v))
- })
-}
-func (m frozenMessage) Get(fd pref.FieldDescriptor) pref.Value {
- v := m.Message.Get(fd)
- return frozenValueOf(v)
-}
-func (frozenMessage) Clear(pref.FieldDescriptor) { panic("invalid on read-only Message") }
-func (frozenMessage) Set(pref.FieldDescriptor, pref.Value) { panic("invalid on read-only Message") }
-func (frozenMessage) Mutable(pref.FieldDescriptor) pref.Value { panic("invalid on read-only Message") }
-func (frozenMessage) SetUnknown(pref.RawFields) { panic("invalid on read-only Message") }
-
-type frozenList struct{ pref.List }
-
-func (ls frozenList) Get(i int) pref.Value {
- v := ls.List.Get(i)
- return frozenValueOf(v)
-}
-func (frozenList) Set(i int, v pref.Value) { panic("invalid on read-only List") }
-func (frozenList) Append(v pref.Value) { panic("invalid on read-only List") }
-func (frozenList) Truncate(i int) { panic("invalid on read-only List") }
-
-type frozenMap struct{ pref.Map }
-
-func (ms frozenMap) Get(k pref.MapKey) pref.Value {
- v := ms.Map.Get(k)
- return frozenValueOf(v)
-}
-func (ms frozenMap) Range(f func(pref.MapKey, pref.Value) bool) {
- ms.Map.Range(func(k pref.MapKey, v pref.Value) bool {
- return f(k, frozenValueOf(v))
- })
-}
-func (frozenMap) Set(k pref.MapKey, v pref.Value) { panic("invalid n read-only Map") }
-func (frozenMap) Clear(k pref.MapKey) { panic("invalid on read-only Map") }
diff --git a/internal/impl/message_reflect.go b/internal/impl/message_reflect.go
index 46ada8f..699ac2c 100644
--- a/internal/impl/message_reflect.go
+++ b/internal/impl/message_reflect.go
@@ -142,10 +142,7 @@
return xt.ValueOf(x.GetValue())
}
}
- if !isComposite(xt) {
- return defaultValueOf(xt)
- }
- return frozenValueOf(xt.New())
+ return xt.Zero()
}
func (m *extensionMap) Set(xt pref.ExtensionType, v pref.Value) {
if *m == nil {
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index cad02b1..afd4cff 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -234,6 +234,9 @@
// New returns a newly allocated empty message.
New() Message
+ // Zero returns an immutable empty message.
+ Zero() Message
+
// GoType returns the Go type of the allocated message.
//
// Invariant: t.GoType() == reflect.TypeOf(t.New().Interface())
@@ -439,6 +442,11 @@
// For scalars, this returns the default value in native Go form.
New() Value
+ // 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.
+ Zero() Value
+
// GoType returns the Go type of the field value.
//
// Invariants:
diff --git a/reflect/prototype/type.go b/reflect/prototype/type.go
index 7bf61b8..cc36eaa 100644
--- a/reflect/prototype/type.go
+++ b/reflect/prototype/type.go
@@ -95,6 +95,10 @@
return m
}
+func (t *Message) Zero() protoreflect.Message {
+ return t.New() // TODO: return a read-only message instead
+}
+
func (t *Message) GoType() reflect.Type {
t.New() // initialize t.goType
return t.goType