From f0ad886d73e18cf7f17974765435e397ed7b688e Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Mon, 5 Nov 2018 21:29:03 +0000 Subject: [PATCH] reflect/prototype: implement NewGoExtension Implement the constructor for protoreflect.ExtensionType. The constructor is more complicated than NewGoEnum and NewGoMessage because it is responsible for providing the wrappers to present *[]T as a protoreflect.Vector. There are no tests since we need the follow-up logic in internal/impl to actually make use of extensions. A subsequent CL will add that logic and comprehensively test extensions. Change-Id: I2d7893de299fe40be2ccedd8f39a92c40c41e59a Reviewed-on: https://go-review.googlesource.com/c/147578 Reviewed-by: Herbie Ong --- reflect/protoreflect/type.go | 25 +---- reflect/prototype/go_type.go | 204 ++++++++++++++++++++++++++++++++++- 2 files changed, 204 insertions(+), 25 deletions(-) diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go index 5885270e..6e3ff114 100644 --- a/reflect/protoreflect/type.go +++ b/reflect/protoreflect/type.go @@ -438,34 +438,11 @@ type ExtensionType interface { ExtensionDescriptor // GoNew returns a new value for the field. + // For scalars, this returns the default value in native Go form. GoNew() interface{} // GoType returns the Go type of the field value. // - // The type is currently determined automatically (although custom Go types - // may be supported in the future). The type is T for scalars and - // *[]T for vectors. Maps are not valid in extension fields. - // 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 (see EnumDescriptor.GoType). - // The type M is the concrete message type (see MessageDescriptor.GoType). - // // Invariants: // t.GoType() == reflect.TypeOf(t.GoNew()) // t.GoType() == reflect.TypeOf(t.InterfaceOf(t.ValueOf(t.GoNew()))) diff --git a/reflect/prototype/go_type.go b/reflect/prototype/go_type.go index 4db11913..0659b35b 100644 --- a/reflect/prototype/go_type.go +++ b/reflect/prototype/go_type.go @@ -9,6 +9,7 @@ import ( "reflect" "sync" + "github.com/golang/protobuf/v2/internal/value" "github.com/golang/protobuf/v2/reflect/protoreflect" ) @@ -90,4 +91,205 @@ func (p goMessage) GoType() reflect.Type { return p.goType } -// TODO: add extension support. +// GoExtension is a constructor for a protoreflect.ExtensionType. +type GoExtension struct { + protoreflect.ExtensionDescriptor + + // NewEnum returns a concrete proto.Enum value with the given enum number. + // The constructor must be provided if protoreflect.ExtensionDescriptor.Kind + // is protoreflect.EnumKind. + // + // The returned enum must represent an protoreflect.EnumDescriptor + // that matches protoreflect.ExtensionDescriptor.EnumType. + NewEnum func(protoreflect.EnumNumber) protoreflect.ProtoEnum + + // NewMessage returns a new empty proto.Message instance. + // The constructor must be provided if protoreflect.ExtensionDescriptor.Kind + // is protoreflect.MessageKind or protoreflect.GroupKind. + // + // The returned message must represent an protoreflect.MessageDescriptor + // that matches protoreflect.ExtensionDescriptor.MessageType. + NewMessage func() protoreflect.ProtoMessage + + // TODO: Separate NewEnum and NewMessage constructors make it possible for + // users to provide a constructor that returns a Go type does not match + // the corresponding protobuf descriptor in ExtensionDescriptor. + // Checking for correctness is hard since descriptors are not comparable. + // + // An alternative API is for ExtensionDescriptor.{EnumType,MessageType} + // to document that it must implement protoreflect.{EnumType,MessageType}. + + // TODO: Support custom Go types? If so, the user needs to provide their + // own New, ValueOf, and InterfaceOf adapters. + + once sync.Once + new func() interface{} + goType reflect.Type + valueOf func(v interface{}) protoreflect.Value + interfaceOf func(v protoreflect.Value) interface{} +} +type goExtension struct{ *GoExtension } + +// NewGoExtension creates a new protoreflect.ExtensionType. +// +// The Go type is currently determined automatically (although custom Go types +// may be supported in the future). The type is T for scalars and +// *[]T for vectors. Maps are not valid in extension fields. +// 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. +// +// The caller must relinquish full ownership of the input t and must not +// access or mutate any fields. +func NewGoExtension(t *GoExtension) protoreflect.ExtensionType { + if t.ExtendedType() == nil { + panic("field descriptor does not extend a message") + } + switch t.Kind() { + case protoreflect.EnumKind: + if t.NewEnum == nil { + panic("enum constructor not provided for enum kind") + } + if t.NewMessage != nil { + panic("message constructor provided for enum kind") + } + case protoreflect.MessageKind, protoreflect.GroupKind: + if t.NewMessage == nil { + panic("message constructor not provided for message kind") + } + if t.NewEnum != nil { + panic("enum constructor provided for message kind") + } + default: + if t.NewMessage != nil || t.NewEnum != nil { + panic(fmt.Sprintf("enum or message constructor provided for %v kind", t.Kind())) + } + } + return goExtension{t} +} +func (p goExtension) GoNew() interface{} { + p.lazyInit() + v := p.new() + if reflect.TypeOf(v) != p.goType { + panic(fmt.Sprintf("invalid type: got %T, want %v", v, p.goType)) + } + return v +} +func (p goExtension) GoType() reflect.Type { + p.lazyInit() + return p.goType +} +func (p goExtension) ValueOf(v interface{}) protoreflect.Value { + p.lazyInit() + if reflect.TypeOf(v) != p.goType { + panic(fmt.Sprintf("invalid type: got %T, want %v", v, p.goType)) + } + return p.valueOf(v) +} +func (p goExtension) InterfaceOf(pv protoreflect.Value) interface{} { + p.lazyInit() + v := p.interfaceOf(pv) + if reflect.TypeOf(v) != p.goType { + panic(fmt.Sprintf("invalid type: got %T, want %v", v, p.goType)) + } + return v +} +func (p goExtension) lazyInit() { + p.once.Do(func() { + switch p.Cardinality() { + case protoreflect.Optional: + switch p.Kind() { + case protoreflect.EnumKind: + p.goType = reflect.TypeOf(p.NewEnum(0)) + p.new = func() interface{} { return p.NewEnum(p.Default().Enum()) } + p.valueOf = func(v interface{}) protoreflect.Value { + ev := v.(protoreflect.ProtoEnum).ProtoReflect() + return protoreflect.ValueOf(ev.Number()) + } + p.interfaceOf = func(pv protoreflect.Value) interface{} { + return p.NewEnum(pv.Enum()) + } + case protoreflect.MessageKind, protoreflect.GroupKind: + p.goType = reflect.TypeOf(p.NewMessage()) + p.new = func() interface{} { return p.NewMessage() } + p.valueOf = func(v interface{}) protoreflect.Value { + return protoreflect.ValueOf(v) + } + p.interfaceOf = func(pv protoreflect.Value) interface{} { + return pv.Message().Interface() + } + default: + p.goType = goTypeForPBKind[p.Kind()] + p.new = func() interface{} { return p.Default().Interface() } + p.valueOf = func(v interface{}) protoreflect.Value { + return protoreflect.ValueOf(v) + } + p.interfaceOf = func(pv protoreflect.Value) interface{} { + v := pv.Interface() + return v + } + } + case protoreflect.Repeated: + var goType reflect.Type + switch p.Kind() { + case protoreflect.EnumKind: + goType = reflect.TypeOf(p.NewEnum(p.Default().Enum())) + case protoreflect.MessageKind, protoreflect.GroupKind: + goType = reflect.TypeOf(p.NewMessage()) + default: + goType = goTypeForPBKind[p.Kind()] + } + c := value.NewConverter(goType, p.Kind()) + p.goType = reflect.PtrTo(reflect.SliceOf(goType)) + p.new = func() interface{} { return reflect.New(p.goType.Elem()).Interface() } + p.valueOf = func(v interface{}) protoreflect.Value { + return protoreflect.ValueOf(value.VectorOf(v, c)) + } + p.interfaceOf = func(v protoreflect.Value) interface{} { + // TODO: Can we assume that Vector implementations know how + // to unwrap themselves? + // Should this be part of the public API in protoreflect? + return v.Vector().(value.Unwrapper).Unwrap() + } + default: + panic(fmt.Sprintf("invalid cardinality: %v", p.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.Fixed32Kind: reflect.TypeOf(int32(0)), + protoreflect.Int64Kind: reflect.TypeOf(int64(0)), + protoreflect.Sint64Kind: reflect.TypeOf(int64(0)), + protoreflect.Fixed64Kind: reflect.TypeOf(int64(0)), + protoreflect.Uint32Kind: reflect.TypeOf(uint32(0)), + protoreflect.Sfixed32Kind: reflect.TypeOf(uint32(0)), + protoreflect.Uint64Kind: reflect.TypeOf(uint64(0)), + protoreflect.Sfixed64Kind: 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)), +}