From b2f66bedb48ca6ff926ba83d8e38a5325511a5cb Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Wed, 22 May 2019 00:42:45 -0400 Subject: [PATCH] reflect/prototype: initial commit Add the prototype package which provides constructors for protoreflect.{Enum,Message,Extension}Type. Switch all usages of the internal/prototype equivalent to the new package. Switch all custom implementions of {Enum,Message}Type to the new package. Change-Id: Ia9dae6fed4f2b90e55c123627044a7faf098c4b1 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/178438 Reviewed-by: Damien Neil --- internal/fileinit/desc_lazy.go | 2 + internal/impl/export.go | 41 ++-- internal/impl/legacy_hook.go | 31 ++- internal/impl/message_field.go | 42 ++-- internal/impl/message_test.go | 65 +++--- internal/legacy/enum.go | 21 +- internal/legacy/export.go | 64 +++++- internal/legacy/extension.go | 40 ++-- internal/legacy/message.go | 11 +- internal/prototype/go_type.go | 305 ------------------------ internal/prototype/go_type_test.go | 143 ------------ internal/value/convert.go | 176 +++----------- internal/value/list.go | 2 +- internal/value/map.go | 2 +- reflect/prototype/type.go | 358 +++++++++++++++++++++++++++++ 15 files changed, 591 insertions(+), 712 deletions(-) delete mode 100644 internal/prototype/go_type.go delete mode 100644 internal/prototype/go_type_test.go create mode 100644 reflect/prototype/type.go diff --git a/internal/fileinit/desc_lazy.go b/internal/fileinit/desc_lazy.go index 018b8e4a..0e26e178 100644 --- a/internal/fileinit/desc_lazy.go +++ b/internal/fileinit/desc_lazy.go @@ -101,6 +101,8 @@ func (file *fileDesc) resolveMessages() { } func (file *fileDesc) resolveExtensions() { + // TODO: Delete this and use reflect/prototype instead. + for i := range file.allExtensions { xd := &file.allExtensions[i] diff --git a/internal/impl/export.go b/internal/impl/export.go index 9ea39006..8c7c1213 100644 --- a/internal/impl/export.go +++ b/internal/impl/export.go @@ -10,6 +10,7 @@ import ( "google.golang.org/protobuf/encoding/prototext" pref "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/prototype" ) // Export is a zero-length named type that exists only to export a set of @@ -31,24 +32,16 @@ func (Export) EnumOf(e enum) pref.Enum { // EnumTypeOf returns the protoreflect.EnumType for e. func (Export) EnumTypeOf(e enum) pref.EnumType { if ev, ok := e.(pref.Enum); ok { - return &enumType{ev.Descriptor(), reflect.TypeOf(e)} + return &prototype.Enum{ + EnumDescriptor: ev.Descriptor(), + NewEnum: func(n pref.EnumNumber) pref.Enum { + return reflect.ValueOf(n).Convert(reflect.TypeOf(e)).Interface().(pref.Enum) + }, + } } return legacyWrapper.EnumTypeOf(e) } -// TODO: This needs to be centralized in a package. -type enumType struct { - // TODO: Remove me as an embedded field. - pref.EnumDescriptor - typ reflect.Type // must implement protoreflect.Enum -} - -func (t *enumType) Descriptor() pref.EnumDescriptor { return t.EnumDescriptor } -func (t *enumType) GoType() reflect.Type { return t.typ } -func (t *enumType) New(n pref.EnumNumber) pref.Enum { - return reflect.ValueOf(n).Convert(t.typ).Interface().(pref.Enum) -} - // EnumDescriptorOf returns the protoreflect.EnumDescriptor for e. func (Export) EnumDescriptorOf(e enum) pref.EnumDescriptor { if ev, ok := e.(pref.Enum); ok { @@ -82,24 +75,16 @@ func (Export) MessageOf(m message) pref.Message { // MessageTypeOf returns the protoreflect.MessageType for m. func (Export) MessageTypeOf(m message) pref.MessageType { if mv, ok := m.(pref.ProtoMessage); ok { - return &messageType{mv.ProtoReflect().Descriptor(), reflect.TypeOf(m)} + return &prototype.Message{ + MessageDescriptor: mv.ProtoReflect().Descriptor(), + NewMessage: func() pref.Message { + return reflect.New(reflect.TypeOf(m).Elem()).Interface().(pref.ProtoMessage).ProtoReflect() + }, + } } return legacyWrapper.MessageTypeOf(m) } -// TODO: This needs to be centralized in a package. -type messageType struct { - // TODO: Remove me as an embedded field. - pref.MessageDescriptor - typ reflect.Type // must implement protoreflect.ProtoMessage -} - -func (t *messageType) Descriptor() pref.MessageDescriptor { return t.MessageDescriptor } -func (t *messageType) GoType() reflect.Type { return t.typ } -func (t *messageType) New() pref.Message { - return reflect.New(t.typ.Elem()).Interface().(pref.ProtoMessage).ProtoReflect() -} - // MessageDescriptorOf returns the protoreflect.MessageDescriptor for m. func (Export) MessageDescriptorOf(m message) pref.MessageDescriptor { if mv, ok := m.(pref.ProtoMessage); ok { diff --git a/internal/impl/legacy_hook.go b/internal/impl/legacy_hook.go index f3ffe636..98eaf2f5 100644 --- a/internal/impl/legacy_hook.go +++ b/internal/impl/legacy_hook.go @@ -4,14 +4,39 @@ package impl -import pvalue "google.golang.org/protobuf/internal/value" +import ( + "reflect" + + pvalue "google.golang.org/protobuf/internal/value" + pref "google.golang.org/protobuf/reflect/protoreflect" + piface "google.golang.org/protobuf/runtime/protoiface" +) // TODO: Add a default LegacyWrapper that panics with a more helpful message? -var legacyWrapper pvalue.LegacyWrapper +var legacyWrapper LegacyWrapper // RegisterLegacyWrapper registers a set of constructor functions that are // called when a legacy enum or message is encountered that does not natively // support the protobuf reflection APIs. -func RegisterLegacyWrapper(w pvalue.LegacyWrapper) { +func RegisterLegacyWrapper(w LegacyWrapper) { legacyWrapper = w } + +// LegacyWrapper is a set of wrapper methods that wraps legacy v1 Go types +// to implement the v2 reflection APIs. +type LegacyWrapper interface { + NewConverter(reflect.Type, pref.Kind) pvalue.Converter + + EnumOf(interface{}) pref.Enum + EnumTypeOf(interface{}) pref.EnumType + EnumDescriptorOf(interface{}) pref.EnumDescriptor + + MessageOf(interface{}) pref.Message + MessageTypeOf(interface{}) pref.MessageType + MessageDescriptorOf(interface{}) pref.MessageDescriptor + + // TODO: Remove these eventually. + // See the TODOs in internal/impl/legacy_extension.go. + ExtensionDescFromType(pref.ExtensionType) *piface.ExtensionDescV1 + ExtensionTypeFromDesc(*piface.ExtensionDescV1) pref.ExtensionType +} diff --git a/internal/impl/message_field.go b/internal/impl/message_field.go index 72f45230..a69d2c8a 100644 --- a/internal/impl/message_field.go +++ b/internal/impl/message_field.go @@ -42,7 +42,7 @@ func fieldInfoForOneof(fd pref.FieldDescriptor, fs reflect.StructField, ot refle if !reflect.PtrTo(ot).Implements(ft) { panic(fmt.Sprintf("invalid type: %v does not implement %v", ot, ft)) } - conv := pvalue.NewLegacyConverter(ot.Field(0).Type, fd.Kind(), legacyWrapper) + conv := newConverter(ot.Field(0).Type, fd.Kind()) fieldOffset := offsetOf(fs) // TODO: Implement unsafe fast path? return fieldInfo{ @@ -86,12 +86,9 @@ func fieldInfoForOneof(fd pref.FieldDescriptor, fs reflect.StructField, ot refle } rv.Set(reflect.Zero(rv.Type())) }, - newMessage: func() pref.Message { - // This is only valid for messages and panics for other kinds. - return conv.MessageType.New() - }, - offset: fieldOffset, - isPointer: true, + newMessage: conv.NewMessage, + offset: fieldOffset, + isPointer: true, } } @@ -100,8 +97,8 @@ func fieldInfoForMap(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo if ft.Kind() != reflect.Map { panic(fmt.Sprintf("invalid type: got %v, want map kind", ft)) } - keyConv := pvalue.NewLegacyConverter(ft.Key(), fd.MapKey().Kind(), legacyWrapper) - valConv := pvalue.NewLegacyConverter(ft.Elem(), fd.MapValue().Kind(), legacyWrapper) + keyConv := newConverter(ft.Key(), fd.MapKey().Kind()) + valConv := newConverter(ft.Elem(), fd.MapValue().Kind()) wiretag := wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()]) fieldOffset := offsetOf(fs) // TODO: Implement unsafe fast path? @@ -142,7 +139,7 @@ func fieldInfoForList(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo if ft.Kind() != reflect.Slice { panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft)) } - conv := pvalue.NewLegacyConverter(ft.Elem(), fd.Kind(), legacyWrapper) + conv := newConverter(ft.Elem(), fd.Kind()) var wiretag uint64 if !fd.IsPacked() { wiretag = wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()]) @@ -197,7 +194,7 @@ func fieldInfoForScalar(fd pref.FieldDescriptor, fs reflect.StructField) fieldIn ft = ft.Elem() } } - conv := pvalue.NewLegacyConverter(ft, fd.Kind(), legacyWrapper) + conv := newConverter(ft, fd.Kind()) fieldOffset := offsetOf(fs) wiretag := wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()]) // TODO: Implement unsafe fast path? @@ -267,7 +264,7 @@ func fieldInfoForScalar(fd pref.FieldDescriptor, fs reflect.StructField) fieldIn func fieldInfoForMessage(fd pref.FieldDescriptor, fs reflect.StructField) fieldInfo { ft := fs.Type - conv := pvalue.NewLegacyConverter(ft, fd.Kind(), legacyWrapper) + conv := newConverter(ft, fd.Kind()) fieldOffset := offsetOf(fs) // TODO: Implement unsafe fast path? wiretag := wire.EncodeTag(fd.Number(), wireTypes[fd.Kind()]) @@ -300,14 +297,12 @@ func fieldInfoForMessage(fd pref.FieldDescriptor, fs reflect.StructField) fieldI rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem() rv.Set(reflect.Zero(rv.Type())) }, - newMessage: func() pref.Message { - return conv.MessageType.New() - }, - funcs: fieldCoder(fd, ft), - offset: fieldOffset, - isPointer: true, - wiretag: wiretag, - tagsize: wire.SizeVarint(wiretag), + newMessage: conv.NewMessage, + funcs: fieldCoder(fd, ft), + offset: fieldOffset, + isPointer: true, + wiretag: wiretag, + tagsize: wire.SizeVarint(wiretag), } } @@ -342,3 +337,10 @@ func makeOneofInfo(od pref.OneofDescriptor, fs reflect.StructField, wrappersByTy }, } } + +func newConverter(t reflect.Type, k pref.Kind) pvalue.Converter { + if legacyWrapper != nil { + return legacyWrapper.NewConverter(t, k) + } + return pvalue.NewConverter(t, k) +} diff --git a/internal/impl/message_test.go b/internal/impl/message_test.go index 26f8db6b..e1fc1ce4 100644 --- a/internal/impl/message_test.go +++ b/internal/impl/message_test.go @@ -19,6 +19,7 @@ import ( scalar "google.golang.org/protobuf/internal/scalar" pvalue "google.golang.org/protobuf/internal/value" pref "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/prototype" proto2_20180125 "google.golang.org/protobuf/internal/testprotos/legacy/proto2.v1.0.0-20180125-92554152" "google.golang.org/protobuf/types/descriptorpb" @@ -189,8 +190,8 @@ type ( MapBytes map[MyString]MyBytes ) -var scalarProto2Type = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ScalarProto2)), PBType: ptype.GoMessage( - mustMakeMessageDesc(ptype.StandaloneMessage{ +var scalarProto2Type = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ScalarProto2)), PBType: &prototype.Message{ + MessageDescriptor: mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto2, FullName: "ScalarProto2", Fields: []ptype.Field{ @@ -219,10 +220,10 @@ var scalarProto2Type = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ScalarProto2 {Name: "f22", Number: 22, Cardinality: pref.Optional, Kind: pref.BytesKind, Default: V([]byte("22"))}, }, }), - func(pref.MessageType) pref.Message { + NewMessage: func() pref.Message { return new(ScalarProto2) }, -)} +}} // TODO: Remove this. func (m *ScalarProto2) Type() pref.MessageType { return scalarProto2Type.PBType } @@ -304,8 +305,8 @@ type ScalarProto3 struct { MyBytesA MyString `protobuf:"22"` } -var scalarProto3Type = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ScalarProto3)), PBType: ptype.GoMessage( - mustMakeMessageDesc(ptype.StandaloneMessage{ +var scalarProto3Type = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ScalarProto3)), PBType: &prototype.Message{ + MessageDescriptor: mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto3, FullName: "ScalarProto3", Fields: []ptype.Field{ @@ -334,10 +335,10 @@ var scalarProto3Type = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ScalarProto3 {Name: "f22", Number: 22, Cardinality: pref.Optional, Kind: pref.BytesKind}, }, }), - func(pref.MessageType) pref.Message { + NewMessage: func() pref.Message { return new(ScalarProto3) }, -)} +}} // TODO: Remove this. func (m *ScalarProto3) Type() pref.MessageType { return scalarProto3Type.PBType } @@ -437,8 +438,8 @@ type ListScalars struct { MyBytes4 ListStrings `protobuf:"19"` } -var listScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ListScalars)), PBType: ptype.GoMessage( - mustMakeMessageDesc(ptype.StandaloneMessage{ +var listScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ListScalars)), PBType: &prototype.Message{ + MessageDescriptor: mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto2, FullName: "ListScalars", Fields: []ptype.Field{ @@ -465,10 +466,10 @@ var listScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(ListScalars)) {Name: "f19", Number: 19, Cardinality: pref.Repeated, Kind: pref.BytesKind}, }, }), - func(pref.MessageType) pref.Message { + NewMessage: func() pref.Message { return new(ListScalars) }, -)} +}} // TODO: Remove this. func (m *ListScalars) Type() pref.MessageType { return listScalarsType.PBType } @@ -628,8 +629,8 @@ func mustMakeMapEntry(n pref.FieldNumber, keyKind, valKind pref.Kind) ptype.Fiel } } -var mapScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(MapScalars)), PBType: ptype.GoMessage( - mustMakeMessageDesc(ptype.StandaloneMessage{ +var mapScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(MapScalars)), PBType: &prototype.Message{ + MessageDescriptor: mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto2, FullName: "MapScalars", Fields: []ptype.Field{ @@ -663,10 +664,10 @@ var mapScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(MapScalars)), mustMakeMapEntry(25, pref.StringKind, pref.BytesKind), }, }), - func(pref.MessageType) pref.Message { + NewMessage: func() pref.Message { return new(MapScalars) }, -)} +}} // TODO: Remove this. func (m *MapScalars) Type() pref.MessageType { return mapScalarsType.PBType } @@ -807,8 +808,8 @@ type OneofScalars struct { Union isOneofScalars_Union `protobuf_oneof:"union"` } -var oneofScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(OneofScalars)), PBType: ptype.GoMessage( - mustMakeMessageDesc(ptype.StandaloneMessage{ +var oneofScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(OneofScalars)), PBType: &prototype.Message{ + MessageDescriptor: mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto2, FullName: "OneofScalars", Fields: []ptype.Field{ @@ -828,10 +829,10 @@ var oneofScalarsType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(OneofScalars }, Oneofs: []ptype.Oneof{{Name: "union"}}, }), - func(pref.MessageType) pref.Message { + NewMessage: func() pref.Message { return new(OneofScalars) }, -)} +}} // TODO: Remove this. func (m *OneofScalars) Type() pref.MessageType { return oneofScalarsType.PBType } @@ -983,16 +984,16 @@ func TestOneofs(t *testing.T) { type EnumProto2 int32 -var enumProto2Type = ptype.GoEnum( - mustMakeEnumDesc(ptype.StandaloneEnum{ +var enumProto2Type = &prototype.Enum{ + EnumDescriptor: mustMakeEnumDesc(ptype.StandaloneEnum{ Syntax: pref.Proto2, FullName: "EnumProto2", Values: []ptype.EnumValue{{Name: "DEAD", Number: 0xdead}, {Name: "BEEF", Number: 0xbeef}}, }), - func(_ pref.EnumType, n pref.EnumNumber) pref.Enum { + NewEnum: func(n pref.EnumNumber) pref.Enum { return EnumProto2(n) }, -) +} // TODO: Remove this. func (e EnumProto2) Type() pref.EnumType { return enumProto2Type } @@ -1002,16 +1003,16 @@ func (e EnumProto2) Number() pref.EnumNumber { return pref.EnumNumber(e) type EnumProto3 int32 -var enumProto3Type = ptype.GoEnum( - mustMakeEnumDesc(ptype.StandaloneEnum{ +var enumProto3Type = &prototype.Enum{ + EnumDescriptor: mustMakeEnumDesc(ptype.StandaloneEnum{ Syntax: pref.Proto3, FullName: "EnumProto3", Values: []ptype.EnumValue{{Name: "ALPHA", Number: 0}, {Name: "BRAVO", Number: 1}}, }), - func(_ pref.EnumType, n pref.EnumNumber) pref.Enum { + NewEnum: func(n pref.EnumNumber) pref.Enum { return EnumProto3(n) }, -) +} // TODO: Remove this. func (e EnumProto3) Type() pref.EnumType { return enumProto3Type } @@ -1031,8 +1032,8 @@ type EnumMessages struct { Union isEnumMessages_Union `protobuf_oneof:"union"` } -var enumMessagesType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(EnumMessages)), PBType: ptype.GoMessage( - mustMakeMessageDesc(ptype.StandaloneMessage{ +var enumMessagesType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(EnumMessages)), PBType: &prototype.Message{ + MessageDescriptor: mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto2, FullName: "EnumMessages", Fields: []ptype.Field{ @@ -1051,10 +1052,10 @@ var enumMessagesType = pimpl.MessageInfo{GoType: reflect.TypeOf(new(EnumMessages }, Oneofs: []ptype.Oneof{{Name: "union"}}, }), - func(pref.MessageType) pref.Message { + NewMessage: func() pref.Message { return new(EnumMessages) }, -)} +}} var enumMapDesc = mustMakeMessageDesc(ptype.StandaloneMessage{ Syntax: pref.Proto2, diff --git a/internal/legacy/enum.go b/internal/legacy/enum.go index d750f1ec..d38aa8cd 100644 --- a/internal/legacy/enum.go +++ b/internal/legacy/enum.go @@ -13,6 +13,7 @@ import ( ptype "google.golang.org/protobuf/internal/prototype" pvalue "google.golang.org/protobuf/internal/value" pref "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/prototype" ) // wrapEnum wraps v as a protoreflect.Enum, @@ -33,16 +34,20 @@ func loadEnumType(t reflect.Type) pref.EnumType { } // Slow-path: derive enum descriptor and initialize EnumType. + var et pref.EnumType var m sync.Map // map[protoreflect.EnumNumber]proto.Enum ed := LoadEnumDesc(t) - et := ptype.GoEnum(ed, func(et pref.EnumType, n pref.EnumNumber) pref.Enum { - if e, ok := m.Load(n); ok { - return e.(pref.Enum) - } - e := &enumWrapper{num: n, pbTyp: et, goTyp: t} - m.Store(n, e) - return e - }) + et = &prototype.Enum{ + EnumDescriptor: ed, + NewEnum: func(n pref.EnumNumber) pref.Enum { + if e, ok := m.Load(n); ok { + return e.(pref.Enum) + } + e := &enumWrapper{num: n, pbTyp: et, goTyp: t} + m.Store(n, e) + return e + }, + } if et, ok := enumTypeCache.LoadOrStore(t, et); ok { return et.(pref.EnumType) } diff --git a/internal/legacy/export.go b/internal/legacy/export.go index ec87a68a..701b578d 100644 --- a/internal/legacy/export.go +++ b/internal/legacy/export.go @@ -5,6 +5,7 @@ package legacy import ( + "fmt" "reflect" pimpl "google.golang.org/protobuf/internal/impl" @@ -17,8 +18,8 @@ import ( // functions that we do not want to appear in godoc. type Export struct{} -func (Export) EnumOf(e interface{}) pvalue.LegacyEnum { - return wrapEnum(reflect.ValueOf(e)).(pvalue.LegacyEnum) +func (Export) EnumOf(e interface{}) pref.Enum { + return wrapEnum(reflect.ValueOf(e)) } func (Export) EnumTypeOf(e interface{}) pref.EnumType { @@ -29,8 +30,8 @@ func (Export) EnumDescriptorOf(e interface{}) pref.EnumDescriptor { return LoadEnumDesc(reflect.TypeOf(e)) } -func (Export) MessageOf(m interface{}) pvalue.LegacyMessage { - return wrapMessage(reflect.ValueOf(m)).ProtoReflect().(pvalue.LegacyMessage) +func (Export) MessageOf(m interface{}) pref.Message { + return wrapMessage(reflect.ValueOf(m)).ProtoReflect() } func (Export) MessageTypeOf(m interface{}) pref.MessageType { @@ -49,6 +50,61 @@ func (Export) ExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionTyp return extensionTypeFromDesc(d) } +var ( + enumIfaceV2 = reflect.TypeOf((*pref.Enum)(nil)).Elem() + messageIfaceV1 = reflect.TypeOf((*piface.MessageV1)(nil)).Elem() + messageIfaceV2 = reflect.TypeOf((*pref.ProtoMessage)(nil)).Elem() +) + +func (Export) NewConverter(t reflect.Type, k pref.Kind) pvalue.Converter { + c, _ := newConverter(t, k) + return c +} + +func newConverter(t reflect.Type, k pref.Kind) (pvalue.Converter, bool) { + switch k { + case pref.EnumKind: + if t.Kind() == reflect.Int32 && !t.Implements(enumIfaceV2) { + return pvalue.Converter{ + PBValueOf: func(v reflect.Value) pref.Value { + if v.Type() != t { + panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t)) + } + return pref.ValueOf(pref.EnumNumber(v.Int())) + }, + GoValueOf: func(v pref.Value) reflect.Value { + return reflect.ValueOf(v.Enum()).Convert(t) + }, + NewEnum: func(n pref.EnumNumber) pref.Enum { + return wrapEnum(reflect.ValueOf(n).Convert(t)) + }, + }, true + } + case pref.MessageKind, pref.GroupKind: + if t.Kind() == reflect.Ptr && t.Implements(messageIfaceV1) && !t.Implements(messageIfaceV2) { + return pvalue.Converter{ + PBValueOf: func(v reflect.Value) pref.Value { + if v.Type() != t { + panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t)) + } + return pref.ValueOf(Export{}.MessageOf(v.Interface())) + }, + GoValueOf: func(v pref.Value) reflect.Value { + rv := reflect.ValueOf(v.Message().(pvalue.Unwrapper).ProtoUnwrap()) + if rv.Type() != t { + panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t)) + } + return rv + }, + NewMessage: func() pref.Message { + return wrapMessage(reflect.New(t.Elem())).ProtoReflect() + }, + }, true + } + } + return pvalue.NewConverter(t, k), false +} + func init() { pimpl.RegisterLegacyWrapper(Export{}) } diff --git a/internal/legacy/extension.go b/internal/legacy/extension.go index 1f53f1b5..c8cb3e98 100644 --- a/internal/legacy/extension.go +++ b/internal/legacy/extension.go @@ -16,6 +16,7 @@ import ( pvalue "google.golang.org/protobuf/internal/value" pref "google.golang.org/protobuf/reflect/protoreflect" preg "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/reflect/prototype" piface "google.golang.org/protobuf/runtime/protoiface" ) @@ -171,12 +172,19 @@ func extensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType { // Construct a v2 ExtensionType. var ed pref.EnumDescriptor var md pref.MessageDescriptor - conv := pvalue.NewLegacyConverter(t, f.Kind, Export{}) - if conv.EnumType != nil { - ed = conv.EnumType.Descriptor() - } - if conv.MessageType != nil { - md = conv.MessageType.Descriptor() + switch f.Kind { + case pref.EnumKind: + if e, ok := reflect.Zero(t).Interface().(pref.Enum); ok { + ed = e.Descriptor() + } else { + ed = LoadEnumDesc(t) + } + case pref.MessageKind, pref.GroupKind: + if m, ok := reflect.Zero(t).Interface().(pref.ProtoMessage); ok { + md = m.ProtoReflect().Descriptor() + } else { + md = LoadMessageDesc(t) + } } xd, err := ptype.NewExtension(&ptype.StandaloneExtension{ FullName: pref.FullName(d.Name), @@ -207,17 +215,21 @@ func extensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType { // // This is exported for testing purposes. func ExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType { - // Extension types for non-enums and non-messages are simple. + var conv pvalue.Converter + var isLegacy bool + xt := &prototype.Extension{ExtensionDescriptor: xd} switch xd.Kind() { - case pref.EnumKind, pref.MessageKind, pref.GroupKind: + case pref.EnumKind: + conv, isLegacy = newConverter(t, xd.Kind()) + xt.NewEnum = conv.NewEnum + case pref.MessageKind, pref.GroupKind: + conv, isLegacy = newConverter(t, xd.Kind()) + xt.NewMessage = conv.NewMessage default: - return ptype.GoExtension(xd, nil, nil) + // Extension types for non-enums and non-messages are simple. + return &prototype.Extension{ExtensionDescriptor: xd} } - - // Create an ExtensionType where GoType is the wrapper type. - conv := pvalue.NewLegacyConverter(t, xd.Kind(), Export{}) - xt := ptype.GoExtension(xd, conv.EnumType, conv.MessageType) - if !conv.IsLegacy { + if !isLegacy { return xt } diff --git a/internal/legacy/message.go b/internal/legacy/message.go index 9919cdb1..b17d705b 100644 --- a/internal/legacy/message.go +++ b/internal/legacy/message.go @@ -15,6 +15,7 @@ import ( pimpl "google.golang.org/protobuf/internal/impl" ptype "google.golang.org/protobuf/internal/prototype" pref "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/prototype" ) // wrapMessage wraps v as a protoreflect.ProtoMessage, @@ -38,10 +39,12 @@ func loadMessageInfo(t reflect.Type) *pimpl.MessageInfo { md := LoadMessageDesc(t) mt := new(pimpl.MessageInfo) mt.GoType = t - mt.PBType = ptype.GoMessage(md, func(pref.MessageType) pref.Message { - p := reflect.New(t.Elem()).Interface() - return mt.MessageOf(p) - }) + mt.PBType = &prototype.Message{ + MessageDescriptor: md, + NewMessage: func() pref.Message { + return mt.MessageOf(reflect.New(t.Elem()).Interface()) + }, + } if mt, ok := messageTypeCache.LoadOrStore(t, mt); ok { return mt.(*pimpl.MessageInfo) } diff --git a/internal/prototype/go_type.go b/internal/prototype/go_type.go deleted file mode 100644 index 16b49dc4..00000000 --- a/internal/prototype/go_type.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package prototype - -import ( - "fmt" - "reflect" - "sync" - - "google.golang.org/protobuf/internal/descfmt" - "google.golang.org/protobuf/internal/value" - "google.golang.org/protobuf/reflect/protoreflect" -) - -// GoEnum creates a new protoreflect.EnumType by combining the provided -// protoreflect.EnumDescriptor with the provided constructor function. -func GoEnum(ed protoreflect.EnumDescriptor, fn func(protoreflect.EnumType, protoreflect.EnumNumber) protoreflect.Enum) protoreflect.EnumType { - if ed.IsPlaceholder() { - panic("enum descriptor must not be a placeholder") - } - return &goEnum{EnumDescriptor: ed, new: fn} -} - -type goEnum struct { - protoreflect.EnumDescriptor - new func(protoreflect.EnumType, protoreflect.EnumNumber) protoreflect.Enum - - once sync.Once - typ reflect.Type -} - -func (t *goEnum) Descriptor() protoreflect.EnumDescriptor { - return t.EnumDescriptor -} -func (t *goEnum) GoType() reflect.Type { - t.New(0) // initialize t.typ - return t.typ -} -func (t *goEnum) New(n protoreflect.EnumNumber) protoreflect.Enum { - e := t.new(t, n) - t.once.Do(func() { t.typ = reflect.TypeOf(e) }) - if t.typ != reflect.TypeOf(e) { - panic(fmt.Sprintf("mismatching types for enum: got %T, want %v", e, t.typ)) - } - return e -} -func (t *goEnum) Format(s fmt.State, r rune) { - descfmt.FormatDesc(s, r, t) -} - -// GoMessage creates a new protoreflect.MessageType by combining the provided -// protoreflect.MessageDescriptor with the provided constructor function. -func GoMessage(md protoreflect.MessageDescriptor, fn func(protoreflect.MessageType) protoreflect.Message) protoreflect.MessageType { - if md.IsPlaceholder() { - panic("message descriptor must not be a placeholder") - } - // NOTE: Avoid calling fn in the constructor since fn itself may depend on - // this function returning (for cyclic message dependencies). - return &goMessage{MessageDescriptor: md, new: fn} -} - -type goMessage struct { - protoreflect.MessageDescriptor - new func(protoreflect.MessageType) protoreflect.Message - - once sync.Once - typ reflect.Type -} - -func (t *goMessage) Descriptor() protoreflect.MessageDescriptor { - return t.MessageDescriptor -} -func (t *goMessage) GoType() reflect.Type { - t.New() // initialize t.typ - return t.typ -} -func (t *goMessage) New() protoreflect.Message { - m := t.new(t) - mi := m.Interface() - t.once.Do(func() { t.typ = reflect.TypeOf(mi) }) - if t.typ != reflect.TypeOf(mi) { - panic(fmt.Sprintf("mismatching types for message: got %T, want %v", mi, t.typ)) - } - return m -} -func (t *goMessage) Format(s fmt.State, r rune) { - descfmt.FormatDesc(s, r, t) -} - -// GoExtension creates a new protoreflect.ExtensionType. -// -// An enum type must be provided for enum extension fields if -// ExtensionDescriptor.EnumType does not implement protoreflect.EnumType, -// in which case it replaces the original enum in ExtensionDescriptor. -// -// Similarly, a message type must be provided for message extension fields if -// ExtensionDescriptor.MessageType does not implement protoreflect.MessageType, -// in which case it replaces the original message in ExtensionDescriptor. -// -// The Go type is currently determined automatically. -// The type is T for scalars and *[]T for lists (maps are not allowed). -// The type T is determined as follows: -// -// +------------+-------------------------------------+ -// | Go type | Protobuf kind | -// +------------+-------------------------------------+ -// | bool | BoolKind | -// | int32 | Int32Kind, Sint32Kind, Sfixed32Kind | -// | int64 | Int64Kind, Sint64Kind, Sfixed64Kind | -// | uint32 | Uint32Kind, Fixed32Kind | -// | uint64 | Uint64Kind, Fixed64Kind | -// | float32 | FloatKind | -// | float64 | DoubleKind | -// | string | StringKind | -// | []byte | BytesKind | -// | E | EnumKind | -// | M | MessageKind, GroupKind | -// +------------+-------------------------------------+ -// -// The type E is the concrete enum type returned by NewEnum, -// which is often, but not required to be, a named int32 type. -// The type M is the concrete message type returned by NewMessage, -// which is often, but not required to be, a pointer to a named struct type. -func GoExtension(xd protoreflect.ExtensionDescriptor, et protoreflect.EnumType, mt protoreflect.MessageType) protoreflect.ExtensionType { - if !xd.IsExtension() { - panic("field descriptor does not extend a message") - } - switch xd.Kind() { - case protoreflect.EnumKind: - if et2, ok := xd.Enum().(protoreflect.EnumType); ok && et == nil { - et = et2 - } - if et == nil { - panic("enum type not provided for enum kind") - } - if mt != nil { - panic("message type provided for enum kind") - } - case protoreflect.MessageKind, protoreflect.GroupKind: - if mt2, ok := xd.Message().(protoreflect.MessageType); ok && mt == nil { - mt = mt2 - } - if et != nil { - panic("enum type provided for message kind") - } - if mt == nil { - panic("message type not provided for message kind") - } - default: - if et != nil || mt != nil { - panic(fmt.Sprintf("enum or message type provided for %v kind", xd.Kind())) - } - } - return &goExtension{ExtensionDescriptor: xd, enumType: et, messageType: mt} -} - -type goExtension struct { - protoreflect.ExtensionDescriptor - enumType protoreflect.EnumType - messageType protoreflect.MessageType - - once sync.Once - typ reflect.Type - new func() protoreflect.Value - valueOf func(v interface{}) protoreflect.Value - interfaceOf func(v protoreflect.Value) interface{} -} - -func (t *goExtension) Descriptor() protoreflect.ExtensionDescriptor { - return t.ExtensionDescriptor -} -func (t *goExtension) EnumType() protoreflect.EnumDescriptor { - if t.enumType == nil { - return nil - } - return t.enumType.Descriptor() -} -func (t *goExtension) MessageType() protoreflect.MessageDescriptor { - if t.messageType == nil { - return nil - } - return t.messageType.Descriptor() -} -func (t *goExtension) GoType() reflect.Type { - t.lazyInit() - return t.typ -} -func (t *goExtension) New() protoreflect.Value { - t.lazyInit() - pv := t.new() - v := t.interfaceOf(pv) - if reflect.TypeOf(v) != t.typ { - panic(fmt.Sprintf("invalid type: got %T, want %v", v, t.typ)) - } - return pv -} -func (t *goExtension) ValueOf(v interface{}) protoreflect.Value { - t.lazyInit() - if reflect.TypeOf(v) != t.typ { - panic(fmt.Sprintf("invalid type: got %T, want %v", v, t.typ)) - } - return t.valueOf(v) -} -func (t *goExtension) InterfaceOf(pv protoreflect.Value) interface{} { - t.lazyInit() - v := t.interfaceOf(pv) - if reflect.TypeOf(v) != t.typ { - panic(fmt.Sprintf("invalid type: got %T, want %v", v, t.typ)) - } - return v -} -func (t *goExtension) Format(s fmt.State, r rune) { - descfmt.FormatDesc(s, r, t) -} -func (t *goExtension) lazyInit() { - t.once.Do(func() { - switch t.Cardinality() { - case protoreflect.Optional: - switch t.Kind() { - case protoreflect.EnumKind: - t.typ = t.enumType.GoType() - t.new = func() protoreflect.Value { - return t.Default() - } - t.valueOf = func(v interface{}) protoreflect.Value { - ev := v.(protoreflect.Enum) - return protoreflect.ValueOf(ev.Number()) - } - t.interfaceOf = func(pv protoreflect.Value) interface{} { - return t.enumType.New(pv.Enum()) - } - case protoreflect.MessageKind, protoreflect.GroupKind: - t.typ = t.messageType.GoType() - t.new = func() protoreflect.Value { - return protoreflect.ValueOf(t.messageType.New()) - } - t.valueOf = func(v interface{}) protoreflect.Value { - mv := v.(protoreflect.ProtoMessage).ProtoReflect() - return protoreflect.ValueOf(mv) - } - t.interfaceOf = func(pv protoreflect.Value) interface{} { - return pv.Message().Interface() - } - default: - t.typ = goTypeForPBKind[t.Kind()] - t.new = func() protoreflect.Value { - return t.Default() - } - t.valueOf = func(v interface{}) protoreflect.Value { - return protoreflect.ValueOf(v) - } - t.interfaceOf = func(pv protoreflect.Value) interface{} { - return pv.Interface() - } - } - case protoreflect.Repeated: - var typ reflect.Type - var c value.Converter - switch t.Kind() { - case protoreflect.EnumKind: - typ = t.enumType.GoType() - c = value.NewEnumConverter(t.enumType) - case protoreflect.MessageKind, protoreflect.GroupKind: - typ = t.messageType.GoType() - c = value.NewMessageConverter(t.messageType) - default: - typ = goTypeForPBKind[t.Kind()] - c = value.NewConverter(typ, t.Kind()) - } - t.typ = reflect.PtrTo(reflect.SliceOf(typ)) - t.new = func() protoreflect.Value { - v := reflect.New(t.typ.Elem()).Interface() - return protoreflect.ValueOf(value.ListOf(v, c)) - } - t.valueOf = func(v interface{}) protoreflect.Value { - return protoreflect.ValueOf(value.ListOf(v, c)) - } - t.interfaceOf = func(pv protoreflect.Value) interface{} { - return pv.List().(value.Unwrapper).ProtoUnwrap() - } - default: - panic(fmt.Sprintf("invalid cardinality: %v", t.Cardinality())) - } - }) -} - -var goTypeForPBKind = map[protoreflect.Kind]reflect.Type{ - protoreflect.BoolKind: reflect.TypeOf(bool(false)), - protoreflect.Int32Kind: reflect.TypeOf(int32(0)), - protoreflect.Sint32Kind: reflect.TypeOf(int32(0)), - protoreflect.Sfixed32Kind: reflect.TypeOf(int32(0)), - protoreflect.Int64Kind: reflect.TypeOf(int64(0)), - protoreflect.Sint64Kind: reflect.TypeOf(int64(0)), - protoreflect.Sfixed64Kind: reflect.TypeOf(int64(0)), - protoreflect.Uint32Kind: reflect.TypeOf(uint32(0)), - protoreflect.Fixed32Kind: reflect.TypeOf(uint32(0)), - protoreflect.Uint64Kind: reflect.TypeOf(uint64(0)), - protoreflect.Fixed64Kind: reflect.TypeOf(uint64(0)), - protoreflect.FloatKind: reflect.TypeOf(float32(0)), - protoreflect.DoubleKind: reflect.TypeOf(float64(0)), - protoreflect.StringKind: reflect.TypeOf(string("")), - protoreflect.BytesKind: reflect.TypeOf([]byte(nil)), -} diff --git a/internal/prototype/go_type_test.go b/internal/prototype/go_type_test.go deleted file mode 100644 index 7200afe7..00000000 --- a/internal/prototype/go_type_test.go +++ /dev/null @@ -1,143 +0,0 @@ -package prototype_test - -import ( - "fmt" - "reflect" - "testing" - - "google.golang.org/protobuf/internal/prototype" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" - - testpb "google.golang.org/protobuf/internal/testprotos/test" -) - -func TestGoEnum(t *testing.T) { - enumDescs := []protoreflect.EnumDescriptor{ - testpb.ForeignEnum(0).Descriptor(), - testpb.TestAllTypes_NestedEnum(0).Descriptor(), - } - for _, ed := range enumDescs { - et := prototype.GoEnum(ed, newEnum) - if gotED := et.Descriptor(); gotED != ed { - fmt.Errorf("GoEnum(ed (%v), newEnum).Descriptor() != ed", ed.FullName()) - } - e := et.New(0) - if gotED := e.Descriptor(); gotED != ed { - fmt.Errorf("GoEnum(ed (%v), newEnum).New(0).Descriptor() != ed", ed.FullName()) - } - if n := e.Number(); n != 0 { - fmt.Errorf("GoEnum(ed (%v), newEnum).New(0).Number() = %v; want 0", ed.FullName(), n) - } - if _, ok := e.(fakeEnum); !ok { - fmt.Errorf("GoEnum(ed (%v), newEnum).New(0) type is %T; want fakeEnum", ed.FullName(), e) - } - } -} - -func TestGoMessage(t *testing.T) { - msgDescs := []protoreflect.MessageDescriptor{ - ((*testpb.TestAllTypes)(nil)).ProtoReflect().Descriptor(), - ((*testpb.TestAllTypes_NestedMessage)(nil)).ProtoReflect().Descriptor(), - } - for _, md := range msgDescs { - mt := prototype.GoMessage(md, newMessage) - if gotMD := mt.Descriptor(); gotMD != md { - fmt.Errorf("GoMessage(md (%v), newMessage).Descriptor() != md", md.FullName()) - } - m := mt.New() - if gotMD := m.Descriptor(); gotMD != md { - fmt.Errorf("GoMessage(md (%v), newMessage).New().Descriptor() != md", md.FullName()) - } - if _, ok := m.(*fakeMessage); !ok { - fmt.Errorf("GoMessage(md (%v), newMessage).New() type is %T; want *fakeMessage", md.FullName(), m) - } - } -} - -func TestGoExtension(t *testing.T) { - testCases := []struct { - extName protoreflect.FullName - wantNewType reflect.Type - }{{ - extName: "goproto.proto.test.optional_int32_extension", - wantNewType: reflect.TypeOf(int32(0)), - }, { - extName: "goproto.proto.test.optional_string_extension", - wantNewType: reflect.TypeOf(""), - }, { - extName: "goproto.proto.test.repeated_int32_extension", - wantNewType: reflect.TypeOf((*[]int32)(nil)), - }, { - extName: "goproto.proto.test.repeated_string_extension", - wantNewType: reflect.TypeOf((*[]string)(nil)), - }, { - extName: "goproto.proto.test.repeated_string_extension", - wantNewType: reflect.TypeOf((*[]string)(nil)), - }, { - extName: "goproto.proto.test.optional_nested_enum_extension", - wantNewType: reflect.TypeOf((*fakeEnum)(nil)).Elem(), - }, { - extName: "goproto.proto.test.optional_nested_message_extension", - wantNewType: reflect.TypeOf((*fakeMessageImpl)(nil)), - }, { - extName: "goproto.proto.test.repeated_nested_enum_extension", - wantNewType: reflect.TypeOf((*[]fakeEnum)(nil)), - }, { - extName: "goproto.proto.test.repeated_nested_message_extension", - wantNewType: reflect.TypeOf((*[]*fakeMessageImpl)(nil)), - }} - for _, tc := range testCases { - xd, err := protoregistry.GlobalFiles.FindExtensionByName(tc.extName) - if err != nil { - t.Errorf("GlobalFiles.FindExtensionByName(%q) = _, %v; want _, ", tc.extName, err) - continue - } - var et protoreflect.EnumType - if ed := xd.Enum(); ed != nil { - et = prototype.GoEnum(ed, newEnum) - } - var mt protoreflect.MessageType - if md := xd.Message(); md != nil { - mt = prototype.GoMessage(md, newMessage) - } - xt := prototype.GoExtension(xd, et, mt) - v := xt.InterfaceOf(xt.New()) - if typ := reflect.TypeOf(v); typ != tc.wantNewType { - t.Errorf("GoExtension(xd (%v), et, mt).New() type unwraps to %v; want %v", tc.extName, typ, tc.wantNewType) - } - } -} - -type fakeMessage struct { - imp *fakeMessageImpl - protoreflect.Message -} - -func (m *fakeMessage) Type() protoreflect.MessageType { return m.imp.typ } -func (m *fakeMessage) Descriptor() protoreflect.MessageDescriptor { return m.imp.typ.Descriptor() } -func (m *fakeMessage) Interface() protoreflect.ProtoMessage { return m.imp } - -type fakeMessageImpl struct{ typ protoreflect.MessageType } - -func (m *fakeMessageImpl) ProtoReflect() protoreflect.Message { return &fakeMessage{imp: m} } - -func newMessage(typ protoreflect.MessageType) protoreflect.Message { - return (&fakeMessageImpl{typ: typ}).ProtoReflect() -} - -type fakeEnum struct { - typ protoreflect.EnumType - num protoreflect.EnumNumber -} - -func (e fakeEnum) Descriptor() protoreflect.EnumDescriptor { return e.typ.Descriptor() } -func (e fakeEnum) Type() protoreflect.EnumType { return e.typ } -func (e fakeEnum) Number() protoreflect.EnumNumber { return e.num } - -func newEnum(typ protoreflect.EnumType, num protoreflect.EnumNumber) protoreflect.Enum { - return fakeEnum{ - typ: typ, - num: num, - } -} diff --git a/internal/value/convert.go b/internal/value/convert.go index 35de822b..eb8d5708 100644 --- a/internal/value/convert.go +++ b/internal/value/convert.go @@ -11,7 +11,6 @@ import ( "reflect" pref "google.golang.org/protobuf/reflect/protoreflect" - piface "google.golang.org/protobuf/runtime/protoiface" ) // Unwrapper unwraps the value to the underlying value. @@ -32,103 +31,60 @@ var ( bytesType = reflect.TypeOf([]byte(nil)) enumIfaceV2 = reflect.TypeOf((*pref.Enum)(nil)).Elem() - messageIfaceV1 = reflect.TypeOf((*piface.MessageV1)(nil)).Elem() messageIfaceV2 = reflect.TypeOf((*pref.ProtoMessage)(nil)).Elem() byteType = reflect.TypeOf(byte(0)) ) // NewConverter matches a Go type with a protobuf kind and returns a Converter -// that converts between the two. NewConverter panics if it unable to provide a -// conversion between the two. The Converter methods also panic when they are -// called on incorrect Go types. +// that converts between the two. Enums must be a named int32 kind that +// implements protoreflect.Enum, and messages must be pointer to a named +// struct type that implements protoreflect.ProtoMessage. // // This matcher deliberately supports a wider range of Go types than what // protoc-gen-go historically generated to be able to automatically wrap some // v1 messages generated by other forks of protoc-gen-go. func NewConverter(t reflect.Type, k pref.Kind) Converter { - return NewLegacyConverter(t, k, nil) -} - -// LegacyWrapper is a set of wrapper methods that wraps legacy v1 Go types -// to implement the v2 reflection APIs. -type ( - LegacyWrapper interface { - EnumOf(interface{}) LegacyEnum - EnumTypeOf(interface{}) pref.EnumType - EnumDescriptorOf(interface{}) pref.EnumDescriptor - - MessageOf(interface{}) LegacyMessage - MessageTypeOf(interface{}) pref.MessageType - MessageDescriptorOf(interface{}) pref.MessageDescriptor - - // TODO: Remove these eventually. - // See the TODOs in internal/impl/legacy_extension.go. - ExtensionDescFromType(pref.ExtensionType) *piface.ExtensionDescV1 - ExtensionTypeFromDesc(*piface.ExtensionDescV1) pref.ExtensionType - } - - LegacyEnum = interface { - pref.Enum - ProtoUnwrap() interface{} - } - - LegacyMessage = interface { - pref.Message - ProtoUnwrap() interface{} - } -) - -// NewLegacyConverter is identical to NewConverter, -// but supports wrapping legacy v1 messages to implement the v2 message API -// using the provided LegacyWrapper. -func NewLegacyConverter(t reflect.Type, k pref.Kind, w LegacyWrapper) Converter { switch k { case pref.BoolKind: if t.Kind() == reflect.Bool { - return makeScalarConverter(t, boolType) + return newScalarConverter(t, boolType) } case pref.Int32Kind, pref.Sint32Kind, pref.Sfixed32Kind: if t.Kind() == reflect.Int32 { - return makeScalarConverter(t, int32Type) + return newScalarConverter(t, int32Type) } case pref.Int64Kind, pref.Sint64Kind, pref.Sfixed64Kind: if t.Kind() == reflect.Int64 { - return makeScalarConverter(t, int64Type) + return newScalarConverter(t, int64Type) } case pref.Uint32Kind, pref.Fixed32Kind: if t.Kind() == reflect.Uint32 { - return makeScalarConverter(t, uint32Type) + return newScalarConverter(t, uint32Type) } case pref.Uint64Kind, pref.Fixed64Kind: if t.Kind() == reflect.Uint64 { - return makeScalarConverter(t, uint64Type) + return newScalarConverter(t, uint64Type) } case pref.FloatKind: if t.Kind() == reflect.Float32 { - return makeScalarConverter(t, float32Type) + return newScalarConverter(t, float32Type) } case pref.DoubleKind: if t.Kind() == reflect.Float64 { - return makeScalarConverter(t, float64Type) + return newScalarConverter(t, float64Type) } case pref.StringKind: if t.Kind() == reflect.String || (t.Kind() == reflect.Slice && t.Elem() == byteType) { - return makeScalarConverter(t, stringType) + return newScalarConverter(t, stringType) } case pref.BytesKind: if t.Kind() == reflect.String || (t.Kind() == reflect.Slice && t.Elem() == byteType) { - return makeScalarConverter(t, bytesType) + return newScalarConverter(t, bytesType) } case pref.EnumKind: // Handle enums, which must be a named int32 type. - if t.PkgPath() != "" && t.Kind() == reflect.Int32 { - var et pref.EnumType - if t.Implements(enumIfaceV2) { - et = &enumType{reflect.Zero(t).Interface().(pref.Enum).Descriptor(), t} - } else { - et = w.EnumTypeOf(reflect.Zero(t).Interface()) - } + if t.Implements(enumIfaceV2) && t.Kind() == reflect.Int32 { return Converter{ PBValueOf: func(v reflect.Value) pref.Value { if v.Type() != t { @@ -139,44 +95,38 @@ func NewLegacyConverter(t reflect.Type, k pref.Kind, w LegacyWrapper) Converter GoValueOf: func(v pref.Value) reflect.Value { return reflect.ValueOf(v.Enum()).Convert(t) }, - EnumType: et, - IsLegacy: !t.Implements(enumIfaceV2), + NewEnum: func(n pref.EnumNumber) pref.Enum { + return reflect.ValueOf(n).Convert(t).Interface().(pref.Enum) + }, } } case pref.MessageKind, pref.GroupKind: // Handle v2 messages, which must satisfy the proto.Message interface. - if t.Kind() == reflect.Ptr && t.Implements(messageIfaceV2) { - md := reflect.Zero(t).Interface().(pref.ProtoMessage).ProtoReflect().Descriptor() - mt := &messageType{md, t} - return NewMessageConverter(mt) - } - - // Handle v1 messages, which we need to wrap as a v2 message. - if w != nil && t.Kind() == reflect.Ptr && t.Implements(messageIfaceV1) { - mt := w.MessageTypeOf(reflect.Zero(t).Interface()) + if t.Implements(messageIfaceV2) && t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { return Converter{ PBValueOf: func(v reflect.Value) pref.Value { if v.Type() != t { panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t)) } - return pref.ValueOf(w.MessageOf(v.Interface())) + return pref.ValueOf(v.Interface().(pref.ProtoMessage).ProtoReflect()) }, GoValueOf: func(v pref.Value) reflect.Value { - rv := reflect.ValueOf(v.Message().(Unwrapper).ProtoUnwrap()) + rv := reflect.ValueOf(v.Message().Interface()) if rv.Type() != t { panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t)) } return rv }, - MessageType: mt, - IsLegacy: true, + NewMessage: func() pref.Message { + return reflect.New(t.Elem()).Interface().(pref.ProtoMessage).ProtoReflect() + }, } } } panic(fmt.Sprintf("invalid Go type %v for protobuf kind %v", t, k)) } -func makeScalarConverter(goType, pbType reflect.Type) Converter { +func newScalarConverter(goType, pbType reflect.Type) Converter { return Converter{ PBValueOf: func(v reflect.Value) pref.Value { if v.Type() != goType { @@ -200,83 +150,11 @@ func makeScalarConverter(goType, pbType reflect.Type) Converter { } } -// NewEnumConverter returns a converter for an EnumType, whose GoType must implement protoreflect.Enum. -func NewEnumConverter(et pref.EnumType) Converter { - t := et.GoType() - if !t.Implements(enumIfaceV2) { - panic(fmt.Sprintf("invalid type: %v does not implement %v", t, enumIfaceV2)) - } - return Converter{ - PBValueOf: func(v reflect.Value) pref.Value { - if v.Type() != t { - panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t)) - } - e := v.Interface().(pref.Enum) - return pref.ValueOf(e.Number()) - }, - GoValueOf: func(v pref.Value) reflect.Value { - return reflect.ValueOf(et.New(v.Enum())) - }, - EnumType: et, - } -} - -// NewMessageConverter returns a converter for a MessageType, whose GoType must implement protoreflect.ProtoMessage. -func NewMessageConverter(mt pref.MessageType) Converter { - t := mt.GoType() - if !t.Implements(messageIfaceV2) { - panic(fmt.Sprintf("invalid type: %v does not implement %v", t, messageIfaceV2)) - } - return Converter{ - PBValueOf: func(v reflect.Value) pref.Value { - if v.Type() != t { - panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), t)) - } - return pref.ValueOf(v.Interface().(pref.ProtoMessage).ProtoReflect()) - }, - GoValueOf: func(v pref.Value) reflect.Value { - rv := reflect.ValueOf(v.Message().Interface()) - if rv.Type() != t { - panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), t)) - } - return rv - }, - MessageType: mt, - } -} - // Converter provides functions for converting to/from Go reflect.Value types // and protobuf protoreflect.Value types. type Converter struct { - PBValueOf func(reflect.Value) pref.Value - GoValueOf func(pref.Value) reflect.Value - EnumType pref.EnumType - MessageType pref.MessageType - IsLegacy bool -} - -// TODO: This needs to be centralized in a package. -type enumType struct { - // TODO: Remove me as an embedded field. - pref.EnumDescriptor - typ reflect.Type // must implement protoreflect.Enum -} - -func (t *enumType) Descriptor() pref.EnumDescriptor { return t.EnumDescriptor } -func (t *enumType) GoType() reflect.Type { return t.typ } -func (t *enumType) New(n pref.EnumNumber) pref.Enum { - return reflect.ValueOf(n).Convert(t.typ).Interface().(pref.Enum) -} - -// TODO: This needs to be centralized in a package. -type messageType struct { - // TODO: Remove me as an embedded field. - pref.MessageDescriptor - typ reflect.Type // must implement protoreflect.ProtoMessage -} - -func (t *messageType) Descriptor() pref.MessageDescriptor { return t.MessageDescriptor } -func (t *messageType) GoType() reflect.Type { return t.typ } -func (t *messageType) New() pref.Message { - return reflect.New(t.typ.Elem()).Interface().(pref.ProtoMessage).ProtoReflect() + PBValueOf func(reflect.Value) pref.Value + GoValueOf func(pref.Value) reflect.Value + NewEnum func(pref.EnumNumber) pref.Enum + NewMessage func() pref.Message } diff --git a/internal/value/list.go b/internal/value/list.go index f40a73f0..5181f4f9 100644 --- a/internal/value/list.go +++ b/internal/value/list.go @@ -43,7 +43,7 @@ func (ls listReflect) Truncate(i int) { ls.v.Elem().Set(ls.v.Elem().Slice(0, i)) } func (ls listReflect) NewMessage() pref.Message { - return ls.conv.MessageType.New() + return ls.conv.NewMessage() } func (ls listReflect) ProtoUnwrap() interface{} { return ls.v.Interface() diff --git a/internal/value/map.go b/internal/value/map.go index 3461d2e5..9fc7358b 100644 --- a/internal/value/map.go +++ b/internal/value/map.go @@ -77,7 +77,7 @@ func (ms mapReflect) Range(f func(pref.MapKey, pref.Value) bool) { } } func (ms mapReflect) NewMessage() pref.Message { - return ms.valConv.MessageType.New() + return ms.valConv.NewMessage() } func (ms mapReflect) ProtoUnwrap() interface{} { return ms.v.Interface() diff --git a/reflect/prototype/type.go b/reflect/prototype/type.go new file mode 100644 index 00000000..0766b4b5 --- /dev/null +++ b/reflect/prototype/type.go @@ -0,0 +1,358 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package prototype provides constructors for protoreflect.EnumType, +// protoreflect.MessageType, and protoreflect.ExtensionType. +package prototype + +import ( + "fmt" + "reflect" + "sync" + + "google.golang.org/protobuf/internal/descfmt" + "google.golang.org/protobuf/internal/value" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// Enum is a protoreflect.EnumType which combines a +// protoreflect.EnumDescriptor with a constructor function. +// +// Both EnumDescriptor and NewEnum must be populated. +// Once constructed, the exported fields must not be modified. +type Enum struct { + protoreflect.EnumDescriptor + + // NewEnum constructs a new protoreflect.Enum representing the provided + // enum number. The returned Go type must be identical for every call. + NewEnum func(protoreflect.EnumNumber) protoreflect.Enum + + once sync.Once + goType reflect.Type +} + +func (t *Enum) New(n protoreflect.EnumNumber) protoreflect.Enum { + e := t.NewEnum(n) + t.once.Do(func() { + t.goType = reflect.TypeOf(e) + if e.Descriptor() != t.Descriptor() { + panic(fmt.Sprintf("mismatching enum descriptor: got %v, want %v", e.Descriptor(), t.Descriptor())) + } + if e.Descriptor().IsPlaceholder() { + panic("enum descriptor must not be a placeholder") + } + }) + if t.goType != reflect.TypeOf(e) { + panic(fmt.Sprintf("mismatching types for enum: got %T, want %v", e, t.goType)) + } + return e +} + +func (t *Enum) GoType() reflect.Type { + t.New(0) // initialize t.typ + return t.goType +} + +func (t *Enum) Descriptor() protoreflect.EnumDescriptor { + return t.EnumDescriptor +} + +func (t *Enum) Format(s fmt.State, r rune) { + descfmt.FormatDesc(s, r, t) +} + +// Message is a protoreflect.MessageType which combines a +// protoreflect.MessageDescriptor with a constructor function. +// +// Both MessageDescriptor and NewMessage must be populated. +// Once constructed, the exported fields must not be modified. +type Message struct { + protoreflect.MessageDescriptor + + // NewMessage constructs an empty, newly allocated protoreflect.Message. + // The returned Go type must be identical for every call. + NewMessage func() protoreflect.Message + + once sync.Once + goType reflect.Type +} + +func (t *Message) New() protoreflect.Message { + m := t.NewMessage() + mi := m.Interface() + t.once.Do(func() { + t.goType = reflect.TypeOf(mi) + if m.Descriptor() != t.Descriptor() { + panic(fmt.Sprintf("mismatching message descriptor: got %v, want %v", m.Descriptor(), t.Descriptor())) + } + if m.Descriptor().IsPlaceholder() { + panic("message descriptor must not be a placeholder") + } + }) + if t.goType != reflect.TypeOf(mi) { + panic(fmt.Sprintf("mismatching types for message: got %T, want %v", mi, t.goType)) + } + return m +} + +func (t *Message) GoType() reflect.Type { + t.New() // initialize t.goType + return t.goType +} + +func (t *Message) Descriptor() protoreflect.MessageDescriptor { + return t.MessageDescriptor +} + +func (t *Message) Format(s fmt.State, r rune) { + descfmt.FormatDesc(s, r, t) +} + +// Extension is a protoreflect.ExtensionType which combines a +// protoreflect.ExtensionDescriptor with a constructor function. +// +// ExtensionDescriptor must be populated, while NewEnum or NewMessage must +// populated depending on the kind of the extension field. +// Once constructed, the exported fields must not be modified. +type Extension struct { + protoreflect.ExtensionDescriptor + + // NewEnum constructs a new enum (see Enum.NewEnum). + // This must be populated if and only if ExtensionDescriptor.Kind + // is a protoreflect.EnumKind. + NewEnum func(protoreflect.EnumNumber) protoreflect.Enum + + // NewMessage constructs a new message (see Enum.NewMessage). + // This must be populated if and only if ExtensionDescriptor.Kind + // is a protoreflect.MessageKind or protoreflect.GroupKind. + NewMessage func() protoreflect.Message + + // TODO: Allow users to manually set new, valueOf, and interfaceOf. + // This allows users to implement custom composite types (e.g., List) or + // custom Go types for primitives (e.g., int32). + + once sync.Once + goType reflect.Type + new func() protoreflect.Value + valueOf func(v interface{}) protoreflect.Value + interfaceOf func(v protoreflect.Value) interface{} +} + +func (t *Extension) New() protoreflect.Value { + t.lazyInit() + pv := t.new() + v := t.interfaceOf(pv) + if reflect.TypeOf(v) != t.goType { + panic(fmt.Sprintf("invalid type: got %T, want %v", v, t.goType)) + } + return pv +} + +func (t *Extension) ValueOf(v interface{}) protoreflect.Value { + t.lazyInit() + if reflect.TypeOf(v) != t.goType { + panic(fmt.Sprintf("invalid type: got %T, want %v", v, t.goType)) + } + return t.valueOf(v) +} + +func (t *Extension) InterfaceOf(v protoreflect.Value) interface{} { + t.lazyInit() + vi := t.interfaceOf(v) + if reflect.TypeOf(vi) != t.goType { + panic(fmt.Sprintf("invalid type: got %T, want %v", vi, t.goType)) + } + return vi +} + +// GoType is the type of the extension field. +// The type is T for scalars and *[]T for lists (maps are not allowed). +// The type T is determined as follows: +// +// +------------+-------------------------------------+ +// | Go type | Protobuf kind | +// +------------+-------------------------------------+ +// | bool | BoolKind | +// | int32 | Int32Kind, Sint32Kind, Sfixed32Kind | +// | int64 | Int64Kind, Sint64Kind, Sfixed64Kind | +// | uint32 | Uint32Kind, Fixed32Kind | +// | uint64 | Uint64Kind, Fixed64Kind | +// | float32 | FloatKind | +// | float64 | DoubleKind | +// | string | StringKind | +// | []byte | BytesKind | +// | E | EnumKind | +// | M | MessageKind, GroupKind | +// +------------+-------------------------------------+ +// +// The type E is the concrete enum type returned by NewEnum, +// which is often, but not required to be, a named int32 type. +// The type M is the concrete message type returned by NewMessage, +// which is often, but not required to be, a pointer to a named struct type. +func (t *Extension) GoType() reflect.Type { + t.lazyInit() + return t.goType +} + +func (t *Extension) Descriptor() protoreflect.ExtensionDescriptor { + return t.ExtensionDescriptor +} + +func (t *Extension) Format(s fmt.State, r rune) { + descfmt.FormatDesc(s, r, t) +} + +func (t *Extension) lazyInit() { + t.once.Do(func() { + switch t.Kind() { + case protoreflect.EnumKind: + if t.NewEnum == nil || t.NewMessage != nil { + panic("NewEnum alone must be set") + } + e := t.NewEnum(0) + if e.Descriptor() != t.Enum() { + panic(fmt.Sprintf("mismatching enum descriptor: got %v, want %v", e.Descriptor(), t.Enum())) + } + t.goType = reflect.TypeOf(e) + case protoreflect.MessageKind, protoreflect.GroupKind: + if t.NewEnum != nil || t.NewMessage == nil { + panic("NewMessage alone must be set") + } + m := t.NewMessage() + if m.Descriptor() != t.Message() { + panic(fmt.Sprintf("mismatching message descriptor: got %v, want %v", m.Descriptor(), t.Message())) + } + t.goType = reflect.TypeOf(m.Interface()) + default: + if t.NewEnum != nil || t.NewMessage != nil { + panic("neither NewEnum nor NewMessage may be set") + } + t.goType = goTypeForPBKind[t.Kind()] + } + + switch t.Cardinality() { + case protoreflect.Optional: + switch t.Kind() { + case protoreflect.EnumKind: + t.new = func() protoreflect.Value { + return t.Default() + } + t.valueOf = func(v interface{}) protoreflect.Value { + ev := v.(protoreflect.Enum) + return protoreflect.ValueOf(ev.Number()) + } + t.interfaceOf = func(v protoreflect.Value) interface{} { + return t.NewEnum(v.Enum()) + } + case protoreflect.MessageKind, protoreflect.GroupKind: + t.new = func() protoreflect.Value { + return protoreflect.ValueOf(t.NewMessage()) + } + t.valueOf = func(v interface{}) protoreflect.Value { + mv := v.(protoreflect.ProtoMessage).ProtoReflect() + return protoreflect.ValueOf(mv) + } + t.interfaceOf = func(v protoreflect.Value) interface{} { + return v.Message().Interface() + } + default: + t.new = func() protoreflect.Value { + v := t.Default() + if t.Kind() == protoreflect.BytesKind { + // Copy default bytes to avoid aliasing the original. + v = protoreflect.ValueOf(append([]byte(nil), v.Bytes()...)) + } + return v + } + t.valueOf = func(v interface{}) protoreflect.Value { + return protoreflect.ValueOf(v) + } + t.interfaceOf = func(v protoreflect.Value) interface{} { + return v.Interface() + } + } + case protoreflect.Repeated: + var conv value.Converter + elemType := t.goType + switch t.Kind() { + case protoreflect.EnumKind: + conv = value.Converter{ + PBValueOf: func(v reflect.Value) protoreflect.Value { + if v.Type() != elemType { + panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), elemType)) + } + e := v.Interface().(protoreflect.Enum) + return protoreflect.ValueOf(e.Number()) + }, + GoValueOf: func(v protoreflect.Value) reflect.Value { + rv := reflect.ValueOf(t.NewEnum(v.Enum())) + if rv.Type() != elemType { + panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), elemType)) + } + return rv + }, + NewEnum: t.NewEnum, + } + case protoreflect.MessageKind, protoreflect.GroupKind: + conv = value.Converter{ + PBValueOf: func(v reflect.Value) protoreflect.Value { + if v.Type() != elemType { + panic(fmt.Sprintf("invalid type: got %v, want %v", v.Type(), elemType)) + } + m := v.Interface().(protoreflect.ProtoMessage).ProtoReflect() + return protoreflect.ValueOf(m) + }, + GoValueOf: func(v protoreflect.Value) reflect.Value { + rv := reflect.ValueOf(v.Message().Interface()) + if rv.Type() != elemType { + panic(fmt.Sprintf("invalid type: got %v, want %v", rv.Type(), elemType)) + } + return rv + }, + NewMessage: t.NewMessage, + } + default: + conv = value.NewConverter(elemType, t.Kind()) + } + + t.goType = reflect.PtrTo(reflect.SliceOf(elemType)) + t.new = func() protoreflect.Value { + v := reflect.New(t.goType.Elem()).Interface() + return protoreflect.ValueOf(value.ListOf(v, conv)) + } + t.valueOf = func(v interface{}) protoreflect.Value { + return protoreflect.ValueOf(value.ListOf(v, conv)) + } + t.interfaceOf = func(v protoreflect.Value) interface{} { + return v.List().(value.Unwrapper).ProtoUnwrap() + } + default: + panic(fmt.Sprintf("invalid cardinality: %v", t.Cardinality())) + } + }) +} + +var goTypeForPBKind = map[protoreflect.Kind]reflect.Type{ + protoreflect.BoolKind: reflect.TypeOf(bool(false)), + protoreflect.Int32Kind: reflect.TypeOf(int32(0)), + protoreflect.Sint32Kind: reflect.TypeOf(int32(0)), + protoreflect.Sfixed32Kind: reflect.TypeOf(int32(0)), + protoreflect.Int64Kind: reflect.TypeOf(int64(0)), + protoreflect.Sint64Kind: reflect.TypeOf(int64(0)), + protoreflect.Sfixed64Kind: reflect.TypeOf(int64(0)), + protoreflect.Uint32Kind: reflect.TypeOf(uint32(0)), + protoreflect.Fixed32Kind: reflect.TypeOf(uint32(0)), + protoreflect.Uint64Kind: reflect.TypeOf(uint64(0)), + protoreflect.Fixed64Kind: reflect.TypeOf(uint64(0)), + protoreflect.FloatKind: reflect.TypeOf(float32(0)), + protoreflect.DoubleKind: reflect.TypeOf(float64(0)), + protoreflect.StringKind: reflect.TypeOf(string("")), + protoreflect.BytesKind: reflect.TypeOf([]byte(nil)), +} + +var ( + _ protoreflect.EnumType = (*Enum)(nil) + _ protoreflect.MessageType = (*Message)(nil) + _ protoreflect.ExtensionType = (*Extension)(nil) +)