From 204f1c0ad82a6ef005fef693cc5401dfb66c792e Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 23 Oct 2018 15:03:38 -0700 Subject: [PATCH] reflect/protoreflect: add Descriptor.Options method Add a method to fetch descriptor options. Since options are proto messages (e.g., google.protobuf.FieldOptions), and proto message packages depend on the protoreflect package, returning the actual option type would cause a dependency cycle. Instead, we return an interface value which can be type asserted to the appropriate concrete type. Add options support to the prototype package. Some of the prototype constructors included fields (such as Field.IsPacked) which represent information from the options (such as google.protobuf.FieldOptions.packed). To avoid confusion about the canonical source of information, drop these fields in favor of the options. Drop the unimplemented Descriptor.DescriptorOptionsProto. Change-Id: I66579b6a7d10d99eb6977402a247306a78913e74 Reviewed-on: https://go-review.googlesource.com/c/144277 Reviewed-by: Joe Tsai --- .../internal_gengogrpc/grpc.go | 11 +- .../internal_gengogrpc/options.go | 116 ------ cmd/protoc-gen-go/internal_gengo/main.go | 16 +- cmd/protoc-gen-go/internal_gengo/options.go | 122 ------- internal/cmd/pbdump/pbdump.go | 4 +- internal/cmd/pbdump/pbdump_test.go | 3 +- internal/encoding/pack/pack_test.go | 25 +- internal/impl/legacy_message.go | 15 +- internal/impl/legacy_test.go | 2 + internal/impl/message_test.go | 4 +- reflect/protoreflect/type.go | 71 ++-- reflect/prototype/descriptor.go | 29 -- reflect/prototype/placeholder_type.go | 21 +- reflect/prototype/protofile.go | 40 ++- reflect/prototype/protofile_desc.go | 57 +-- reflect/prototype/protofile_type.go | 333 ++++++++++-------- reflect/prototype/standalone.go | 8 +- reflect/prototype/standalone_type.go | 110 +++--- reflect/prototype/type_test.go | 125 +++++-- reflect/prototype/validate.go | 3 + 20 files changed, 466 insertions(+), 649 deletions(-) delete mode 100644 cmd/protoc-gen-go-grpc/internal_gengogrpc/options.go delete mode 100644 cmd/protoc-gen-go/internal_gengo/options.go delete mode 100644 reflect/prototype/descriptor.go diff --git a/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go b/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go index e35f7c37..a8283e1b 100644 --- a/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go +++ b/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" + descpb "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/protogen" ) @@ -56,7 +57,7 @@ func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.Generated g.P("// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.") // Client interface. - if serviceOptions(gen, service).GetDeprecated() { + if service.Desc.Options().(*descpb.ServiceOptions).GetDeprecated() { g.P("//") g.P(deprecationComment) } @@ -77,7 +78,7 @@ func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.Generated g.P() // NewClient factory. - if serviceOptions(gen, service).GetDeprecated() { + if service.Desc.Options().(*descpb.ServiceOptions).GetDeprecated() { g.P(deprecationComment) } g.P("func New", clientName, " (cc *", ident("grpc.ClientConn"), ") ", clientName, " {") @@ -102,7 +103,7 @@ func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.Generated // Server interface. serverType := service.GoName + "Server" g.P("// ", serverType, " is the server API for ", service.GoName, " service.") - if serviceOptions(gen, service).GetDeprecated() { + if service.Desc.Options().(*descpb.ServiceOptions).GetDeprecated() { g.P("//") g.P(deprecationComment) } @@ -117,7 +118,7 @@ func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.Generated g.P() // Server registration. - if serviceOptions(gen, service).GetDeprecated() { + if service.Desc.Options().(*descpb.ServiceOptions).GetDeprecated() { g.P(deprecationComment) } serviceDescVar := "_" + service.GoName + "_serviceDesc" @@ -189,7 +190,7 @@ func genClientMethod(gen *protogen.Plugin, file *protogen.File, g *protogen.Gene service := method.ParentService sname := fmt.Sprintf("/%s/%s", service.Desc.FullName(), method.Desc.Name()) - if methodOptions(gen, method).GetDeprecated() { + if method.Desc.Options().(*descpb.MethodOptions).GetDeprecated() { g.P(deprecationComment) } g.P("func (c *", unexport(service.GoName), "Client) ", clientSignature(g, method), "{") diff --git a/cmd/protoc-gen-go-grpc/internal_gengogrpc/options.go b/cmd/protoc-gen-go-grpc/internal_gengogrpc/options.go deleted file mode 100644 index fad4aa02..00000000 --- a/cmd/protoc-gen-go-grpc/internal_gengogrpc/options.go +++ /dev/null @@ -1,116 +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. - -// This file contains functions for fetching the options for a protoreflect descriptor. -// -// TODO: Replace this with the appropriate protoreflect API, once it exists. - -package internal_gengogrpc - -import ( - "github.com/golang/protobuf/proto" - descpb "github.com/golang/protobuf/protoc-gen-go/descriptor" - "github.com/golang/protobuf/v2/protogen" - "github.com/golang/protobuf/v2/reflect/protoreflect" -) - -// serviceOptions returns the options for a service. -func serviceOptions(gen *protogen.Plugin, service *protogen.Service) *descpb.ServiceOptions { - d := getDescriptorProto(gen, service.Desc, service.Location.Path) - if d == nil { - return nil - } - return d.(*descpb.ServiceDescriptorProto).GetOptions() -} - -// methodOptions returns the options for a method. -func methodOptions(gen *protogen.Plugin, method *protogen.Method) *descpb.MethodOptions { - d := getDescriptorProto(gen, method.Desc, method.Location.Path) - if d == nil { - return nil - } - return d.(*descpb.MethodDescriptorProto).GetOptions() -} - -func getDescriptorProto(gen *protogen.Plugin, desc protoreflect.Descriptor, path []int32) proto.Message { - var p proto.Message - // Look up the FileDescriptorProto. - for { - if fdesc, ok := desc.(protoreflect.FileDescriptor); ok { - file, ok := gen.FileByName(fdesc.Path()) - if !ok { - return nil - } - p = file.Proto - break - } - var ok bool - desc, ok = desc.Parent() - if !ok { - return nil - } - } - const ( - // field numbers in FileDescriptorProto - filePackageField = 2 // package - fileMessageField = 4 // message_type - fileEnumField = 5 // enum_type - fileServiceField = 6 // service - fileExtensionField = 7 // extension - // field numbers in DescriptorProto - messageFieldField = 2 // field - messageMessageField = 3 // nested_type - messageEnumField = 4 // enum_type - messageExtensionField = 6 // extension - messageOneofField = 8 // oneof_decl - // field numbers in EnumDescriptorProto - enumValueField = 2 // value - // field numbers in ServiceDescriptorProto - serviceMethodField = 2 // method - ) - for len(path) > 1 { - switch d := p.(type) { - case *descpb.FileDescriptorProto: - switch path[0] { - case fileMessageField: - p = d.MessageType[path[1]] - case fileEnumField: - p = d.EnumType[path[1]] - case fileServiceField: - p = d.Service[path[1]] - default: - return nil - } - case *descpb.DescriptorProto: - switch path[0] { - case messageFieldField: - p = d.Field[path[1]] - case messageMessageField: - p = d.NestedType[path[1]] - case messageEnumField: - p = d.EnumType[path[1]] - default: - return nil - } - case *descpb.EnumDescriptorProto: - switch path[0] { - case enumValueField: - p = d.Value[path[1]] - default: - return nil - } - case *descpb.ServiceDescriptorProto: - switch path[0] { - case serviceMethodField: - p = d.Method[path[1]] - default: - return nil - } - default: - return nil - } - path = path[2:] - } - return p -} diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go index 154e95a2..b5b0f5a6 100644 --- a/cmd/protoc-gen-go/internal_gengo/main.go +++ b/cmd/protoc-gen-go/internal_gengo/main.go @@ -237,13 +237,13 @@ func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum g.PrintLeadingComments(enum.Location) g.Annotate(enum.GoIdent.GoName, enum.Location) g.P("type ", enum.GoIdent, " int32", - deprecationComment(enumOptions(gen, enum).GetDeprecated())) + deprecationComment(enum.Desc.Options().(*descpb.EnumOptions).GetDeprecated())) g.P("const (") for _, value := range enum.Values { g.PrintLeadingComments(value.Location) g.Annotate(value.GoIdent.GoName, value.Location) g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number(), - deprecationComment(enumValueOptions(gen, value).GetDeprecated())) + deprecationComment(value.Desc.Options().(*descpb.EnumValueOptions).GetDeprecated())) } g.P(")") g.P() @@ -333,7 +333,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me } hasComment := g.PrintLeadingComments(message.Location) - if messageOptions(gen, message).GetDeprecated() { + if message.Desc.Options().(*descpb.MessageOptions).GetDeprecated() { if hasComment { g.P("//") } @@ -371,13 +371,13 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me } g.Annotate(message.GoIdent.GoName+"."+field.GoName, field.Location) g.P(field.GoName, " ", goType, " `", strings.Join(tags, " "), "`", - deprecationComment(fieldOptions(gen, field).GetDeprecated())) + deprecationComment(field.Desc.Options().(*descpb.FieldOptions).GetDeprecated())) } g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`") if message.Desc.ExtensionRanges().Len() > 0 { var tags []string - if messageOptions(gen, message).GetMessageSetWireFormat() { + if message.Desc.Options().(*descpb.MessageOptions).GetMessageSetWireFormat() { tags = append(tags, `protobuf_messageset:"1"`) } tags = append(tags, `json:"-"`) @@ -413,7 +413,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me // ExtensionRangeArray if extranges := message.Desc.ExtensionRanges(); extranges.Len() > 0 { - if messageOptions(gen, message).GetMessageSetWireFormat() { + if message.Desc.Options().(*descpb.MessageOptions).GetMessageSetWireFormat() { g.P("func (m *", message.GoIdent, ") MarshalJSON() ([]byte, error) {") g.P("return ", protogen.GoIdent{ GoImportPath: protoPackage, @@ -545,7 +545,7 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, me } goType, pointer := fieldGoType(g, field) defaultValue := fieldDefaultValue(g, message, field) - if fieldOptions(gen, field).GetDeprecated() { + if field.Desc.Options().(*descpb.FieldOptions).GetDeprecated() { g.P(deprecationComment(true)) } g.Annotate(message.GoIdent.GoName+".Get"+field.GoName, field.Location) @@ -836,7 +836,7 @@ func genExtension(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, func isExtensionMessageSetElement(gen *protogen.Plugin, extension *protogen.Extension) bool { return extension.ParentMessage != nil && - messageOptions(gen, extension.ExtendedType).GetMessageSetWireFormat() && + extension.ExtendedType.Desc.Options().(*descpb.MessageOptions).GetMessageSetWireFormat() && extension.Desc.Name() == "message_set_extension" } diff --git a/cmd/protoc-gen-go/internal_gengo/options.go b/cmd/protoc-gen-go/internal_gengo/options.go deleted file mode 100644 index 427fe401..00000000 --- a/cmd/protoc-gen-go/internal_gengo/options.go +++ /dev/null @@ -1,122 +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. - -// This file contains functions for fetching the options for a protoreflect descriptor. -// -// TODO: Replace this with the appropriate protoreflect API, once it exists. - -package internal_gengo - -import ( - "github.com/golang/protobuf/proto" - descpb "github.com/golang/protobuf/protoc-gen-go/descriptor" - "github.com/golang/protobuf/v2/protogen" - "github.com/golang/protobuf/v2/reflect/protoreflect" -) - -// messageOptions returns the MessageOptions for a message. -func messageOptions(gen *protogen.Plugin, message *protogen.Message) *descpb.MessageOptions { - d := getDescriptorProto(gen, message.Desc, message.Location.Path) - if d == nil { - return nil - } - return d.(*descpb.DescriptorProto).GetOptions() -} - -// fieldOptions returns the FieldOptions for a message. -func fieldOptions(gen *protogen.Plugin, field *protogen.Field) *descpb.FieldOptions { - d := getDescriptorProto(gen, field.Desc, field.Location.Path) - if d == nil { - return nil - } - return d.(*descpb.FieldDescriptorProto).GetOptions() -} - -// enumOptions returns the EnumOptions for an enum -func enumOptions(gen *protogen.Plugin, enum *protogen.Enum) *descpb.EnumOptions { - d := getDescriptorProto(gen, enum.Desc, enum.Location.Path) - if d == nil { - return nil - } - return d.(*descpb.EnumDescriptorProto).GetOptions() -} - -// enumValueOptions returns the EnumValueOptions for an enum value -func enumValueOptions(gen *protogen.Plugin, value *protogen.EnumValue) *descpb.EnumValueOptions { - d := getDescriptorProto(gen, value.Desc, value.Location.Path) - if d == nil { - return nil - } - return d.(*descpb.EnumValueDescriptorProto).GetOptions() -} - -func getDescriptorProto(gen *protogen.Plugin, desc protoreflect.Descriptor, path []int32) proto.Message { - var p proto.Message - // Look up the FileDescriptorProto. - for { - if fdesc, ok := desc.(protoreflect.FileDescriptor); ok { - file, ok := gen.FileByName(fdesc.Path()) - if !ok { - return nil - } - p = file.Proto - break - } - var ok bool - desc, ok = desc.Parent() - if !ok { - return nil - } - } - const ( - // field numbers in FileDescriptorProto - filePackageField = 2 // package - fileMessageField = 4 // message_type - fileEnumField = 5 // enum_type - fileExtensionField = 7 // extension - // field numbers in DescriptorProto - messageFieldField = 2 // field - messageMessageField = 3 // nested_type - messageEnumField = 4 // enum_type - messageExtensionField = 6 // extension - messageOneofField = 8 // oneof_decl - // field numbers in EnumDescriptorProto - enumValueField = 2 // value - ) - for len(path) > 1 { - switch d := p.(type) { - case *descpb.FileDescriptorProto: - switch path[0] { - case fileMessageField: - p = d.MessageType[path[1]] - case fileEnumField: - p = d.EnumType[path[1]] - default: - return nil - } - case *descpb.DescriptorProto: - switch path[0] { - case messageFieldField: - p = d.Field[path[1]] - case messageMessageField: - p = d.NestedType[path[1]] - case messageEnumField: - p = d.EnumType[path[1]] - default: - return nil - } - case *descpb.EnumDescriptorProto: - switch path[0] { - case enumValueField: - p = d.Value[path[1]] - default: - return nil - } - default: - return nil - } - path = path[2:] - } - return p -} diff --git a/internal/cmd/pbdump/pbdump.go b/internal/cmd/pbdump/pbdump.go index 1a4b0c1f..ec65a550 100644 --- a/internal/cmd/pbdump/pbdump.go +++ b/internal/cmd/pbdump/pbdump.go @@ -17,6 +17,8 @@ import ( "strconv" "strings" + protoV1 "github.com/golang/protobuf/proto" + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/internal/encoding/pack" "github.com/golang/protobuf/v2/internal/encoding/wire" "github.com/golang/protobuf/v2/reflect/protoreflect" @@ -227,7 +229,7 @@ func (fs fields) messageDescriptor(name protoreflect.FullName) prototype.Message protoreflect.Sfixed32Kind, protoreflect.Fixed32Kind, protoreflect.FloatKind, protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind, protoreflect.DoubleKind: f.Cardinality = protoreflect.Repeated - f.IsPacked = true + f.Options = &descriptorV1.FieldOptions{Packed: protoV1.Bool(true)} case protoreflect.MessageKind, protoreflect.GroupKind: s := name.Append(protoreflect.Name(fmt.Sprintf("M%d", n))) f.MessageType = prototype.PlaceholderMessage(s) diff --git a/internal/cmd/pbdump/pbdump_test.go b/internal/cmd/pbdump/pbdump_test.go index 0d03c26b..ddc20a1f 100644 --- a/internal/cmd/pbdump/pbdump_test.go +++ b/internal/cmd/pbdump/pbdump_test.go @@ -67,7 +67,7 @@ func TestFields(t *testing.T) { Name: "M20", Fields: []ptype.Field{ {Name: "f30", Number: 30, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: ptype.PlaceholderMessage("M.M10.M20.M30")}, - {Name: "f31", Number: 31, Cardinality: pref.Repeated, IsPacked: true, Kind: pref.Int32Kind}, + {Name: "f31", Number: 31, Cardinality: pref.Repeated, Kind: pref.Int32Kind}, }, Messages: []ptype.Message{{ Name: "M30", @@ -87,6 +87,7 @@ func TestFields(t *testing.T) { return x.FullName() == y.FullName() }), cmpopts.IgnoreFields(ptype.Field{}, "Default"), + cmpopts.IgnoreFields(ptype.Field{}, "Options"), cmpopts.IgnoreUnexported(ptype.Message{}, ptype.Field{}), } for _, tt := range tests { diff --git a/internal/encoding/pack/pack_test.go b/internal/encoding/pack/pack_test.go index 293dcdbb..dcaf3289 100644 --- a/internal/encoding/pack/pack_test.go +++ b/internal/encoding/pack/pack_test.go @@ -11,6 +11,7 @@ import ( "math" "testing" + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" pref "github.com/golang/protobuf/v2/reflect/protoreflect" ptype "github.com/golang/protobuf/v2/reflect/prototype" "github.com/google/go-cmp/cmp" @@ -21,16 +22,16 @@ var msgDesc = func() pref.MessageDescriptor { Syntax: pref.Proto2, FullName: "Message", Fields: []ptype.Field{ - {Name: "F1", Number: 1, Cardinality: pref.Repeated, Kind: pref.BoolKind, IsPacked: true}, - {Name: "F2", Number: 2, Cardinality: pref.Repeated, Kind: pref.Int64Kind, IsPacked: true}, - {Name: "F3", Number: 3, Cardinality: pref.Repeated, Kind: pref.Sint64Kind, IsPacked: true}, - {Name: "F4", Number: 4, Cardinality: pref.Repeated, Kind: pref.Uint64Kind, IsPacked: true}, - {Name: "F5", Number: 5, Cardinality: pref.Repeated, Kind: pref.Fixed32Kind, IsPacked: true}, - {Name: "F6", Number: 6, Cardinality: pref.Repeated, Kind: pref.Sfixed32Kind, IsPacked: true}, - {Name: "F7", Number: 7, Cardinality: pref.Repeated, Kind: pref.FloatKind, IsPacked: true}, - {Name: "F8", Number: 8, Cardinality: pref.Repeated, Kind: pref.Fixed64Kind, IsPacked: true}, - {Name: "F9", Number: 9, Cardinality: pref.Repeated, Kind: pref.Sfixed64Kind, IsPacked: true}, - {Name: "F10", Number: 10, Cardinality: pref.Repeated, Kind: pref.DoubleKind, IsPacked: true}, + {Name: "F1", Number: 1, Cardinality: pref.Repeated, Kind: pref.BoolKind, Options: packedOpt(true)}, + {Name: "F2", Number: 2, Cardinality: pref.Repeated, Kind: pref.Int64Kind, Options: packedOpt(true)}, + {Name: "F3", Number: 3, Cardinality: pref.Repeated, Kind: pref.Sint64Kind, Options: packedOpt(true)}, + {Name: "F4", Number: 4, Cardinality: pref.Repeated, Kind: pref.Uint64Kind, Options: packedOpt(true)}, + {Name: "F5", Number: 5, Cardinality: pref.Repeated, Kind: pref.Fixed32Kind, Options: packedOpt(true)}, + {Name: "F6", Number: 6, Cardinality: pref.Repeated, Kind: pref.Sfixed32Kind, Options: packedOpt(true)}, + {Name: "F7", Number: 7, Cardinality: pref.Repeated, Kind: pref.FloatKind, Options: packedOpt(true)}, + {Name: "F8", Number: 8, Cardinality: pref.Repeated, Kind: pref.Fixed64Kind, Options: packedOpt(true)}, + {Name: "F9", Number: 9, Cardinality: pref.Repeated, Kind: pref.Sfixed64Kind, Options: packedOpt(true)}, + {Name: "F10", Number: 10, Cardinality: pref.Repeated, Kind: pref.DoubleKind, Options: packedOpt(true)}, {Name: "F11", Number: 11, Cardinality: pref.Optional, Kind: pref.StringKind}, {Name: "F12", Number: 12, Cardinality: pref.Optional, Kind: pref.BytesKind}, {Name: "F13", Number: 13, Cardinality: pref.Optional, Kind: pref.MessageKind, MessageType: ptype.PlaceholderMessage("Message")}, @@ -42,6 +43,10 @@ var msgDesc = func() pref.MessageDescriptor { return mtyp }() +func packedOpt(b bool) *descriptorV1.FieldOptions { + return &descriptorV1.FieldOptions{Packed: &b} +} + // dhex decodes a hex-string and returns the bytes and panics if s is invalid. func dhex(s string) []byte { b, err := hex.DecodeString(s) diff --git a/internal/impl/legacy_message.go b/internal/impl/legacy_message.go index b30c667c..12c1832e 100644 --- a/internal/impl/legacy_message.go +++ b/internal/impl/legacy_message.go @@ -13,6 +13,8 @@ import ( "sync" "unicode" + protoV1 "github.com/golang/protobuf/proto" + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/internal/encoding/text" pref "github.com/golang/protobuf/v2/reflect/protoreflect" ptype "github.com/golang/protobuf/v2/reflect/prototype" @@ -179,6 +181,9 @@ func (ms *messageDescSet) parseField(tag, tagKey, tagVal string, t reflect.Type, t = t.Elem() } + f.Options = &descriptorV1.FieldOptions{ + Packed: protoV1.Bool(false), + } for len(tag) > 0 { i := strings.IndexByte(tag, ',') if i < 0 { @@ -251,9 +256,9 @@ func (ms *messageDescSet) parseField(tag, tagKey, tagVal string, t reflect.Type, case strings.HasPrefix(s, "json="): f.JSONName = s[len("json="):] case s == "packed": - f.IsPacked = true + *f.Options.Packed = true case strings.HasPrefix(s, "weak="): - f.IsWeak = true + f.Options.Weak = protoV1.Bool(true) f.MessageType = ptype.PlaceholderMessage(pref.FullName(s[len("weak="):])) case strings.HasPrefix(s, "def="): // The default tag is special in that everything afterwards is the @@ -333,9 +338,9 @@ func (ms *messageDescSet) parseField(tag, tagKey, tagVal string, t reflect.Type, f.MessageType = mv.ProtoReflect().Type() } else if t.Kind() == reflect.Map { m := &ptype.StandaloneMessage{ - Syntax: parent.Syntax, - FullName: parent.FullName.Append(mapEntryName(f.Name)), - IsMapEntry: true, + Syntax: parent.Syntax, + FullName: parent.FullName.Append(mapEntryName(f.Name)), + Options: &descriptorV1.MessageOptions{MapEntry: protoV1.Bool(true)}, Fields: []ptype.Field{ ms.parseField(tagKey, "", "", t.Key(), nil), ms.parseField(tagVal, "", "", t.Elem(), nil), diff --git a/internal/impl/legacy_test.go b/internal/impl/legacy_test.go index e182d17e..fc3a2eef 100644 --- a/internal/impl/legacy_test.go +++ b/internal/impl/legacy_test.go @@ -99,6 +99,8 @@ func TestLegacy(t *testing.T) { switch name { case "Index": // Ignore index since legacy descriptors have no parent. + case "Options": + // Ignore descriptor options since protos are not cmperable. case "Messages", "Enums": // Ignore nested message and enum declarations since // legacy descriptors are all created standalone. diff --git a/internal/impl/message_test.go b/internal/impl/message_test.go index 2fe53edc..ce8bc703 100644 --- a/internal/impl/message_test.go +++ b/internal/impl/message_test.go @@ -14,6 +14,8 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/golang/protobuf/proto" + protoV1 "github.com/golang/protobuf/proto" + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" pref "github.com/golang/protobuf/v2/reflect/protoreflect" ptype "github.com/golang/protobuf/v2/reflect/prototype" ) @@ -461,7 +463,7 @@ func TestMapScalars(t *testing.T) { {Name: "key", Number: 1, Cardinality: pref.Optional, Kind: keyKind}, {Name: "value", Number: 2, Cardinality: pref.Optional, Kind: valKind}, }, - IsMapEntry: true, + Options: &descriptorV1.MessageOptions{MapEntry: protoV1.Bool(true)}, }), } } diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go index a729c3b7..6c9e69ff 100644 --- a/reflect/protoreflect/type.go +++ b/reflect/protoreflect/type.go @@ -104,57 +104,30 @@ type Descriptor interface { // Support for this functionality is optional and may return (nil, false). DescriptorProto() (Message, bool) - // DescriptorOptions is a helper for accessing the proto options specified - // on any of the descriptor types. It is functionally equivalent to - // accessing the "options" field in the descriptor and providing a set of - // efficient lookup methods. + // Options returns the descriptor options. The caller must not modify + // the returned value. // - // Support for this functionality is optional and may return (nil, false). - DescriptorOptions() (DescriptorOptions, bool) - - doNotImplement -} - -// DescriptorOptions is a wrapper around proto options. -// -// The proto message type for each descriptor type is as follows: -// +---------------------+----------------------------------+ -// | Go type | Proto message type | -// +---------------------+----------------------------------+ -// | FileDescriptor | google.protobuf.FileOptions | -// | MessageDescriptor | google.protobuf.MessageOptions | -// | FieldDescriptor | google.protobuf.FieldOptions | -// | OneofDescriptor | google.protobuf.OneofOptions | -// | EnumDescriptor | google.protobuf.EnumOptions | -// | EnumValueDescriptor | google.protobuf.EnumValueOptions | -// | ServiceDescriptor | google.protobuf.ServiceOptions | -// | MethodDescriptor | google.protobuf.MethodOptions | -// +---------------------+----------------------------------+ -// -// The values returned by Get, ByName, and ByNumber are considered frozen and -// mutating operations must not be performed on values returned by them. -type DescriptorOptions interface { - // Len reports the total number of option fields, - // including both fields declared in the options proto and extensions, and - // including fields that are declared but not set in the proto file. - Len() int - - // Get returns the ith field. It panics if out of bounds. - // The FieldDescriptor is guaranteed to be non-nil, while the Value - // may be invalid if the proto file did not specify this option. - Get(i int) (FieldDescriptor, Value) - - // ByName looks a field up by full name and - // returns (nil, Value{}) if not found. + // To avoid a dependency cycle, this function returns an interface value. + // The proto message type returned for each descriptor type is as follows: + // +---------------------+------------------------------------------+ + // | Go type | Proto message type | + // +---------------------+------------------------------------------+ + // | FileDescriptor | google.protobuf.FileOptions | + // | MessageDescriptor | google.protobuf.MessageOptions | + // | FieldDescriptor | google.protobuf.FieldOptions | + // | OneofDescriptor | google.protobuf.OneofOptions | + // | EnumDescriptor | google.protobuf.EnumOptions | + // | EnumValueDescriptor | google.protobuf.EnumValueOptions | + // | ServiceDescriptor | google.protobuf.ServiceOptions | + // | MethodDescriptor | google.protobuf.MethodOptions | + // +---------------------+------------------------------------------+ // - // As a special-case, non-extension fields in the options type can be - // directly accessed by the field name alone (e.g., "map_entry" may be used - // instead of "google.protobuf.MessageOptions.map_entry"). - ByName(s FullName) (FieldDescriptor, Value) - - // ByNumber looks a field up by the field number and - // returns (nil, Value{}) if not found. - ByNumber(n FieldNumber) (FieldDescriptor, Value) + // This method will never return a nil interface value, although the + // concrete value contained in the interface may be nil (e.g., + // (*descpb.FileOptions)(nil)). + // + // TODO: Return ProtoMessage instead of interface{}. + Options() interface{} doNotImplement } diff --git a/reflect/prototype/descriptor.go b/reflect/prototype/descriptor.go deleted file mode 100644 index 031c51ef..00000000 --- a/reflect/prototype/descriptor.go +++ /dev/null @@ -1,29 +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 ( - pref "github.com/golang/protobuf/v2/reflect/protoreflect" -) - -// TODO: This cannot be implemented without proto.Unmarshal. - -type descriptorFileMeta struct{} - -func (p *descriptorFileMeta) lazyInit(t fileDesc) (pref.Message, bool) { - return nil, false -} - -type descriptorSubMeta struct{} - -func (p *descriptorSubMeta) lazyInit(t pref.Descriptor) (pref.Message, bool) { - return nil, false -} - -type descriptorOptionsMeta struct{} - -func (p *descriptorOptionsMeta) lazyInit(t pref.Descriptor) (pref.DescriptorOptions, bool) { - return nil, false -} diff --git a/reflect/prototype/placeholder_type.go b/reflect/prototype/placeholder_type.go index 2035111e..559dbf60 100644 --- a/reflect/prototype/placeholder_type.go +++ b/reflect/prototype/placeholder_type.go @@ -7,6 +7,7 @@ package prototype import ( "fmt" + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/internal/pragma" pref "github.com/golang/protobuf/v2/reflect/protoreflect" ) @@ -26,21 +27,21 @@ var ( type placeholderName pref.FullName -func (t placeholderName) Parent() (pref.Descriptor, bool) { return nil, false } -func (t placeholderName) Index() int { return 0 } -func (t placeholderName) Syntax() pref.Syntax { return 0 } -func (t placeholderName) Name() pref.Name { return pref.FullName(t).Name() } -func (t placeholderName) FullName() pref.FullName { return pref.FullName(t) } -func (t placeholderName) IsPlaceholder() bool { return true } -func (t placeholderName) DescriptorProto() (pref.Message, bool) { return nil, false } -func (t placeholderName) DescriptorOptions() (pref.DescriptorOptions, bool) { return nil, false } -func (t placeholderName) ProtoInternal(pragma.DoNotImplement) {} +func (t placeholderName) Parent() (pref.Descriptor, bool) { return nil, false } +func (t placeholderName) Index() int { return 0 } +func (t placeholderName) Syntax() pref.Syntax { return 0 } +func (t placeholderName) Name() pref.Name { return pref.FullName(t).Name() } +func (t placeholderName) FullName() pref.FullName { return pref.FullName(t) } +func (t placeholderName) IsPlaceholder() bool { return true } +func (t placeholderName) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t placeholderName) ProtoInternal(pragma.DoNotImplement) {} type placeholderFile struct { path string placeholderName } +func (t placeholderFile) Options() interface{} { return (*descriptorV1.FileOptions)(nil) } func (t placeholderFile) Path() string { return t.path } func (t placeholderFile) Package() pref.FullName { return t.FullName() } func (t placeholderFile) Imports() pref.FileImports { return &emptyFiles } @@ -56,6 +57,7 @@ type placeholderMessage struct { placeholderName } +func (t placeholderMessage) Options() interface{} { return (*descriptorV1.MessageOptions)(nil) } func (t placeholderMessage) IsMapEntry() bool { return false } func (t placeholderMessage) Fields() pref.FieldDescriptors { return &emptyFields } func (t placeholderMessage) Oneofs() pref.OneofDescriptors { return &emptyOneofs } @@ -71,6 +73,7 @@ type placeholderEnum struct { placeholderName } +func (t placeholderEnum) Options() interface{} { return (*descriptorV1.EnumOptions)(nil) } func (t placeholderEnum) Values() pref.EnumValueDescriptors { return &emptyEnumValues } func (t placeholderEnum) Format(s fmt.State, r rune) { formatDesc(s, r, t) } func (t placeholderEnum) ProtoType(pref.EnumDescriptor) {} diff --git a/reflect/prototype/protofile.go b/reflect/prototype/protofile.go index 21ba412d..a765ccd8 100644 --- a/reflect/prototype/protofile.go +++ b/reflect/prototype/protofile.go @@ -14,6 +14,7 @@ package prototype import ( + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/reflect/protoreflect" ) @@ -35,15 +36,15 @@ import ( // File is a constructor for protoreflect.FileDescriptor. type File struct { - Syntax protoreflect.Syntax - Path string - Package protoreflect.FullName - Imports []protoreflect.FileImport - + Syntax protoreflect.Syntax + Path string + Package protoreflect.FullName + Imports []protoreflect.FileImport Messages []Message Enums []Enum Extensions []Extension Services []Service + Options *descriptorV1.FileOptions *fileMeta } @@ -72,14 +73,13 @@ func NewFile(t *File) (protoreflect.FileDescriptor, error) { // Message is a constructor for protoreflect.MessageDescriptor. type Message struct { Name protoreflect.Name - IsMapEntry bool Fields []Field Oneofs []Oneof ExtensionRanges [][2]protoreflect.FieldNumber - - Messages []Message - Enums []Enum - Extensions []Extension + Messages []Message + Enums []Enum + Extensions []Extension + Options *descriptorV1.MessageOptions *messageMeta } @@ -91,19 +91,19 @@ type Field struct { Cardinality protoreflect.Cardinality Kind protoreflect.Kind JSONName string - IsPacked bool - IsWeak bool Default protoreflect.Value OneofName protoreflect.Name MessageType protoreflect.MessageDescriptor EnumType protoreflect.EnumDescriptor + Options *descriptorV1.FieldOptions *fieldMeta } // Oneof is a constructor for protoreflect.OneofDescriptor. type Oneof struct { - Name protoreflect.Name + Name protoreflect.Name + Options *descriptorV1.OneofOptions *oneofMeta } @@ -114,27 +114,29 @@ type Extension struct { Number protoreflect.FieldNumber Cardinality protoreflect.Cardinality Kind protoreflect.Kind - IsPacked bool Default protoreflect.Value MessageType protoreflect.MessageDescriptor EnumType protoreflect.EnumDescriptor ExtendedType protoreflect.MessageDescriptor + Options *descriptorV1.FieldOptions *extensionMeta } // Enum is a constructor for protoreflect.EnumDescriptor. type Enum struct { - Name protoreflect.Name - Values []EnumValue + Name protoreflect.Name + Values []EnumValue + Options *descriptorV1.EnumOptions *enumMeta } // EnumValue is a constructor for protoreflect.EnumValueDescriptor. type EnumValue struct { - Name protoreflect.Name - Number protoreflect.EnumNumber + Name protoreflect.Name + Number protoreflect.EnumNumber + Options *descriptorV1.EnumValueOptions *enumValueMeta } @@ -143,6 +145,7 @@ type EnumValue struct { type Service struct { Name protoreflect.Name Methods []Method + Options *descriptorV1.ServiceOptions *serviceMeta } @@ -154,6 +157,7 @@ type Method struct { OutputType protoreflect.MessageDescriptor IsStreamingClient bool IsStreamingServer bool + Options *descriptorV1.MethodOptions *methodMeta } diff --git a/reflect/prototype/protofile_desc.go b/reflect/prototype/protofile_desc.go index cc942fc1..b4ea5c39 100644 --- a/reflect/prototype/protofile_desc.go +++ b/reflect/prototype/protofile_desc.go @@ -64,6 +64,7 @@ func NewFileFromDescriptorProto(fd *descriptorV1.FileDescriptorProto, r *protore } f.Path = fd.GetName() f.Package = protoreflect.FullName(fd.GetPackage()) + f.Options = fd.GetOptions() f.Imports = make([]protoreflect.FileImport, len(fd.GetDependency())) for _, i := range fd.GetPublicDependency() { @@ -119,25 +120,15 @@ func messagesFromDescriptorProto(mds []*descriptorV1.DescriptorProto, syntax pro for _, md := range mds { var m Message m.Name = protoreflect.Name(md.GetName()) - m.IsMapEntry = md.GetOptions().GetMapEntry() + m.Options = md.GetOptions() for _, fd := range md.GetField() { var f Field f.Name = protoreflect.Name(fd.GetName()) f.Number = protoreflect.FieldNumber(fd.GetNumber()) f.Cardinality = protoreflect.Cardinality(fd.GetLabel()) f.Kind = protoreflect.Kind(fd.GetType()) + f.Options = fd.GetOptions() f.JSONName = fd.GetJsonName() - if opts := fd.GetOptions(); opts != nil && opts.Packed != nil { - f.IsPacked = *opts.Packed - } else { - // https://developers.google.com/protocol-buffers/docs/proto3: - // "In proto3, repeated fields of scalar numeric types use packed - // encoding by default." - f.IsPacked = (syntax == protoreflect.Proto3 && - f.Cardinality == protoreflect.Repeated && - isScalarNumeric[f.Kind]) - } - f.IsWeak = fd.GetOptions().GetWeak() if fd.DefaultValue != nil { f.Default, err = parseDefault(fd.GetDefaultValue(), f.Kind) if err != nil { @@ -157,7 +148,7 @@ func messagesFromDescriptorProto(mds []*descriptorV1.DescriptorProto, syntax pro if err != nil { return nil, err } - if f.IsWeak && !f.EnumType.IsPlaceholder() { + if f.Options.GetWeak() && !f.EnumType.IsPlaceholder() { f.EnumType = PlaceholderEnum(f.EnumType.FullName()) } case protoreflect.MessageKind, protoreflect.GroupKind: @@ -165,16 +156,20 @@ func messagesFromDescriptorProto(mds []*descriptorV1.DescriptorProto, syntax pro if err != nil { return nil, err } - if f.IsWeak && !f.MessageType.IsPlaceholder() { + if f.Options.GetWeak() && !f.MessageType.IsPlaceholder() { f.MessageType = PlaceholderMessage(f.MessageType.FullName()) } } m.Fields = append(m.Fields, f) } for _, od := range md.GetOneofDecl() { - m.Oneofs = append(m.Oneofs, Oneof{Name: protoreflect.Name(od.GetName())}) + m.Oneofs = append(m.Oneofs, Oneof{ + Name: protoreflect.Name(od.GetName()), + Options: od.Options, + }) } for _, xr := range md.GetExtensionRange() { + // TODO: Extension range options. m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{ protoreflect.FieldNumber(xr.GetStart()), protoreflect.FieldNumber(xr.GetEnd()), @@ -199,31 +194,16 @@ func messagesFromDescriptorProto(mds []*descriptorV1.DescriptorProto, syntax pro return ms, nil } -var isScalarNumeric = map[protoreflect.Kind]bool{ - protoreflect.BoolKind: true, - protoreflect.EnumKind: true, - protoreflect.Int32Kind: true, - protoreflect.Sint32Kind: true, - protoreflect.Uint32Kind: true, - protoreflect.Int64Kind: true, - protoreflect.Sint64Kind: true, - protoreflect.Uint64Kind: true, - protoreflect.Sfixed32Kind: true, - protoreflect.Fixed32Kind: true, - protoreflect.FloatKind: true, - protoreflect.Sfixed64Kind: true, - protoreflect.Fixed64Kind: true, - protoreflect.DoubleKind: true, -} - func enumsFromDescriptorProto(eds []*descriptorV1.EnumDescriptorProto, r *protoregistry.Files) (es []Enum, err error) { for _, ed := range eds { var e Enum e.Name = protoreflect.Name(ed.GetName()) + e.Options = ed.GetOptions() for _, vd := range ed.GetValue() { e.Values = append(e.Values, EnumValue{ - Name: protoreflect.Name(vd.GetName()), - Number: protoreflect.EnumNumber(vd.GetNumber()), + Name: protoreflect.Name(vd.GetName()), + Number: protoreflect.EnumNumber(vd.GetNumber()), + Options: vd.Options, }) } es = append(es, e) @@ -238,12 +218,7 @@ func extensionsFromDescriptorProto(xds []*descriptorV1.FieldDescriptorProto, r * x.Number = protoreflect.FieldNumber(xd.GetNumber()) x.Cardinality = protoreflect.Cardinality(xd.GetLabel()) x.Kind = protoreflect.Kind(xd.GetType()) - // TODO: When a proto3 file extends a proto2 message (permitted only for - // extending descriptor options), does the extension have proto2 or proto3 - // semantics? If the latter, repeated, scalar, numeric, proto3 extension - // fields should default to packed. If the former, perhaps the extension syntax - // should be protoreflect.Proto2. - x.IsPacked = xd.GetOptions().GetPacked() + x.Options = xd.GetOptions() if xd.DefaultValue != nil { x.Default, err = parseDefault(xd.GetDefaultValue(), x.Kind) if err != nil { @@ -275,9 +250,11 @@ func servicesFromDescriptorProto(sds []*descriptorV1.ServiceDescriptorProto, r * for _, sd := range sds { var s Service s.Name = protoreflect.Name(sd.GetName()) + s.Options = sd.GetOptions() for _, md := range sd.GetMethod() { var m Method m.Name = protoreflect.Name(md.GetName()) + m.Options = md.GetOptions() m.InputType, err = findMessageDescriptor(md.GetInputType(), r) if err != nil { return nil, err diff --git a/reflect/prototype/protofile_type.go b/reflect/prototype/protofile_type.go index 66e8a622..83901b87 100644 --- a/reflect/prototype/protofile_type.go +++ b/reflect/prototype/protofile_type.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/internal/pragma" pref "github.com/golang/protobuf/v2/reflect/protoreflect" ) @@ -20,9 +21,6 @@ type inheritedMeta struct { index int syntax pref.Syntax fullName pref.FullName - - desc descriptorSubMeta - opts descriptorOptionsMeta } func (m *inheritedMeta) init(nb *nameBuilder, parent pref.Descriptor, index int, name pref.Name, child bool) { @@ -41,9 +39,6 @@ func (m *inheritedMeta) init(nb *nameBuilder, parent pref.Descriptor, index int, } type fileMeta struct { - desc descriptorFileMeta - opts descriptorOptionsMeta - ms messagesMeta es enumsMeta xs extensionsMeta @@ -59,25 +54,25 @@ func newFile(f *File) fileDesc { f.fileMeta = new(fileMeta) return fileDesc{f} } -func (t fileDesc) Parent() (pref.Descriptor, bool) { return nil, false } -func (t fileDesc) Index() int { return 0 } -func (t fileDesc) Syntax() pref.Syntax { return t.f.Syntax } -func (t fileDesc) Name() pref.Name { return t.f.Package.Name() } -func (t fileDesc) FullName() pref.FullName { return t.f.Package } -func (t fileDesc) IsPlaceholder() bool { return false } -func (t fileDesc) DescriptorProto() (pref.Message, bool) { return t.f.desc.lazyInit(t) } -func (t fileDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.f.opts.lazyInit(t) } -func (t fileDesc) Path() string { return t.f.Path } -func (t fileDesc) Package() pref.FullName { return t.f.Package } -func (t fileDesc) Imports() pref.FileImports { return (*fileImports)(&t.f.Imports) } -func (t fileDesc) Messages() pref.MessageDescriptors { return t.f.ms.lazyInit(t, t.f.Messages) } -func (t fileDesc) Enums() pref.EnumDescriptors { return t.f.es.lazyInit(t, t.f.Enums) } -func (t fileDesc) Extensions() pref.ExtensionDescriptors { return t.f.xs.lazyInit(t, t.f.Extensions) } -func (t fileDesc) Services() pref.ServiceDescriptors { return t.f.ss.lazyInit(t, t.f.Services) } -func (t fileDesc) DescriptorByName(s pref.FullName) pref.Descriptor { return t.f.ds.lookup(t, s) } -func (t fileDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t fileDesc) ProtoType(pref.FileDescriptor) {} -func (t fileDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t fileDesc) Parent() (pref.Descriptor, bool) { return nil, false } +func (t fileDesc) Index() int { return 0 } +func (t fileDesc) Syntax() pref.Syntax { return t.f.Syntax } +func (t fileDesc) Name() pref.Name { return t.f.Package.Name() } +func (t fileDesc) FullName() pref.FullName { return t.f.Package } +func (t fileDesc) IsPlaceholder() bool { return false } +func (t fileDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t fileDesc) Options() interface{} { return t.f.Options } +func (t fileDesc) Path() string { return t.f.Path } +func (t fileDesc) Package() pref.FullName { return t.f.Package } +func (t fileDesc) Imports() pref.FileImports { return (*fileImports)(&t.f.Imports) } +func (t fileDesc) Messages() pref.MessageDescriptors { return t.f.ms.lazyInit(t, t.f.Messages) } +func (t fileDesc) Enums() pref.EnumDescriptors { return t.f.es.lazyInit(t, t.f.Enums) } +func (t fileDesc) Extensions() pref.ExtensionDescriptors { return t.f.xs.lazyInit(t, t.f.Extensions) } +func (t fileDesc) Services() pref.ServiceDescriptors { return t.f.ss.lazyInit(t, t.f.Services) } +func (t fileDesc) DescriptorByName(s pref.FullName) pref.Descriptor { return t.f.ds.lookup(t, s) } +func (t fileDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t fileDesc) ProtoType(pref.FileDescriptor) {} +func (t fileDesc) ProtoInternal(pragma.DoNotImplement) {} // descriptorsMeta is a lazily initialized map of all descriptors declared in // the file by full name. @@ -166,25 +161,25 @@ type messageMeta struct { } type messageDesc struct{ m *Message } -func (t messageDesc) Parent() (pref.Descriptor, bool) { return t.m.parent, true } -func (t messageDesc) Index() int { return t.m.index } -func (t messageDesc) Syntax() pref.Syntax { return t.m.syntax } -func (t messageDesc) Name() pref.Name { return t.m.Name } -func (t messageDesc) FullName() pref.FullName { return t.m.fullName } -func (t messageDesc) IsPlaceholder() bool { return false } -func (t messageDesc) DescriptorProto() (pref.Message, bool) { return t.m.desc.lazyInit(t) } -func (t messageDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.m.opts.lazyInit(t) } -func (t messageDesc) IsMapEntry() bool { return t.m.IsMapEntry } -func (t messageDesc) Fields() pref.FieldDescriptors { return t.m.fs.lazyInit(t, t.m.Fields) } -func (t messageDesc) Oneofs() pref.OneofDescriptors { return t.m.os.lazyInit(t, t.m.Oneofs) } -func (t messageDesc) RequiredNumbers() pref.FieldNumbers { return t.m.ns.lazyInit(t.m.Fields) } -func (t messageDesc) ExtensionRanges() pref.FieldRanges { return (*ranges)(&t.m.ExtensionRanges) } -func (t messageDesc) Messages() pref.MessageDescriptors { return t.m.ms.lazyInit(t, t.m.Messages) } -func (t messageDesc) Enums() pref.EnumDescriptors { return t.m.es.lazyInit(t, t.m.Enums) } -func (t messageDesc) Extensions() pref.ExtensionDescriptors { return t.m.xs.lazyInit(t, t.m.Extensions) } -func (t messageDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t messageDesc) ProtoType(pref.MessageDescriptor) {} -func (t messageDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t messageDesc) Parent() (pref.Descriptor, bool) { return t.m.parent, true } +func (t messageDesc) Index() int { return t.m.index } +func (t messageDesc) Syntax() pref.Syntax { return t.m.syntax } +func (t messageDesc) Name() pref.Name { return t.m.Name } +func (t messageDesc) FullName() pref.FullName { return t.m.fullName } +func (t messageDesc) IsPlaceholder() bool { return false } +func (t messageDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t messageDesc) Options() interface{} { return t.m.Options } +func (t messageDesc) IsMapEntry() bool { return t.m.Options.GetMapEntry() } +func (t messageDesc) Fields() pref.FieldDescriptors { return t.m.fs.lazyInit(t, t.m.Fields) } +func (t messageDesc) Oneofs() pref.OneofDescriptors { return t.m.os.lazyInit(t, t.m.Oneofs) } +func (t messageDesc) RequiredNumbers() pref.FieldNumbers { return t.m.ns.lazyInit(t.m.Fields) } +func (t messageDesc) ExtensionRanges() pref.FieldRanges { return (*ranges)(&t.m.ExtensionRanges) } +func (t messageDesc) Messages() pref.MessageDescriptors { return t.m.ms.lazyInit(t, t.m.Messages) } +func (t messageDesc) Enums() pref.EnumDescriptors { return t.m.es.lazyInit(t, t.m.Enums) } +func (t messageDesc) Extensions() pref.ExtensionDescriptors { return t.m.xs.lazyInit(t, t.m.Extensions) } +func (t messageDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t messageDesc) ProtoType(pref.MessageDescriptor) {} +func (t messageDesc) ProtoInternal(pragma.DoNotImplement) {} type fieldMeta struct { inheritedMeta @@ -197,35 +192,64 @@ type fieldMeta struct { } type fieldDesc struct{ f *Field } -func (t fieldDesc) Parent() (pref.Descriptor, bool) { return t.f.parent, true } -func (t fieldDesc) Index() int { return t.f.index } -func (t fieldDesc) Syntax() pref.Syntax { return t.f.syntax } -func (t fieldDesc) Name() pref.Name { return t.f.Name } -func (t fieldDesc) FullName() pref.FullName { return t.f.fullName } -func (t fieldDesc) IsPlaceholder() bool { return false } -func (t fieldDesc) DescriptorProto() (pref.Message, bool) { return t.f.desc.lazyInit(t) } -func (t fieldDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.f.opts.lazyInit(t) } -func (t fieldDesc) Number() pref.FieldNumber { return t.f.Number } -func (t fieldDesc) Cardinality() pref.Cardinality { return t.f.Cardinality } -func (t fieldDesc) Kind() pref.Kind { return t.f.Kind } -func (t fieldDesc) JSONName() string { return t.f.js.lazyInit(t.f) } -func (t fieldDesc) IsPacked() bool { return t.f.IsPacked } -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.IsValid() } -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) } -func (t fieldDesc) EnumType() pref.EnumDescriptor { return t.f.et.lazyInit(t, &t.f.EnumType) } -func (t fieldDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t fieldDesc) ProtoType(pref.FieldDescriptor) {} -func (t fieldDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t fieldDesc) Parent() (pref.Descriptor, bool) { return t.f.parent, true } +func (t fieldDesc) Index() int { return t.f.index } +func (t fieldDesc) Syntax() pref.Syntax { return t.f.syntax } +func (t fieldDesc) Name() pref.Name { return t.f.Name } +func (t fieldDesc) FullName() pref.FullName { return t.f.fullName } +func (t fieldDesc) IsPlaceholder() bool { return false } +func (t fieldDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t fieldDesc) Options() interface{} { return t.f.Options } +func (t fieldDesc) Number() pref.FieldNumber { return t.f.Number } +func (t fieldDesc) Cardinality() pref.Cardinality { return t.f.Cardinality } +func (t fieldDesc) Kind() pref.Kind { return t.f.Kind } +func (t fieldDesc) JSONName() string { return t.f.js.lazyInit(t.f) } +func (t fieldDesc) IsPacked() bool { return fieldIsPacked(t) } +func (t fieldDesc) IsMap() bool { return isMap(t) } +func (t fieldDesc) IsWeak() bool { return t.f.Options.GetWeak() } +func (t fieldDesc) Default() pref.Value { return t.f.dv.lazyInit(t, t.f.Default) } +func (t fieldDesc) HasDefault() bool { return t.f.Default.IsValid() } +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) } +func (t fieldDesc) EnumType() pref.EnumDescriptor { return t.f.et.lazyInit(t, &t.f.EnumType) } +func (t fieldDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t fieldDesc) ProtoType(pref.FieldDescriptor) {} +func (t fieldDesc) ProtoInternal(pragma.DoNotImplement) {} + +func fieldIsPacked(t fieldDesc) bool { + if t.f.Options != nil && t.f.Options.Packed != nil { + return *t.f.Options.Packed + } + // https://developers.google.com/protocol-buffers/docs/proto3: + // "In proto3, repeated fields of scalar numeric types use packed + // encoding by default." + return (t.f.syntax == pref.Proto3 && + t.f.Cardinality == pref.Repeated && + isScalarNumeric[t.f.Kind]) +} + +var isScalarNumeric = map[pref.Kind]bool{ + pref.BoolKind: true, + pref.EnumKind: true, + pref.Int32Kind: true, + pref.Sint32Kind: true, + pref.Uint32Kind: true, + pref.Int64Kind: true, + pref.Sint64Kind: true, + pref.Uint64Kind: true, + pref.Sfixed32Kind: true, + pref.Fixed32Kind: true, + pref.FloatKind: true, + pref.Sfixed64Kind: true, + pref.Fixed64Kind: true, + pref.DoubleKind: true, +} func isMap(t pref.FieldDescriptor) bool { if t.Cardinality() == pref.Repeated && t.Kind() == pref.MessageKind { if mt := t.MessageType(); mt != nil { - return mt.IsMapEntry() + return mt.Options().(*descriptorV1.MessageOptions).GetMapEntry() } } return false @@ -283,18 +307,18 @@ type oneofMeta struct { } type oneofDesc struct{ o *Oneof } -func (t oneofDesc) Parent() (pref.Descriptor, bool) { return t.o.parent, true } -func (t oneofDesc) Index() int { return t.o.index } -func (t oneofDesc) Syntax() pref.Syntax { return t.o.syntax } -func (t oneofDesc) Name() pref.Name { return t.o.Name } -func (t oneofDesc) FullName() pref.FullName { return t.o.fullName } -func (t oneofDesc) IsPlaceholder() bool { return false } -func (t oneofDesc) DescriptorProto() (pref.Message, bool) { return t.o.desc.lazyInit(t) } -func (t oneofDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.o.opts.lazyInit(t) } -func (t oneofDesc) Fields() pref.FieldDescriptors { return t.o.fs.lazyInit(t) } -func (t oneofDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t oneofDesc) ProtoType(pref.OneofDescriptor) {} -func (t oneofDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t oneofDesc) Parent() (pref.Descriptor, bool) { return t.o.parent, true } +func (t oneofDesc) Index() int { return t.o.index } +func (t oneofDesc) Syntax() pref.Syntax { return t.o.syntax } +func (t oneofDesc) Name() pref.Name { return t.o.Name } +func (t oneofDesc) FullName() pref.FullName { return t.o.fullName } +func (t oneofDesc) IsPlaceholder() bool { return false } +func (t oneofDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t oneofDesc) Options() interface{} { return t.o.Options } +func (t oneofDesc) Fields() pref.FieldDescriptors { return t.o.fs.lazyInit(t) } +func (t oneofDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t oneofDesc) ProtoType(pref.OneofDescriptor) {} +func (t oneofDesc) ProtoInternal(pragma.DoNotImplement) {} type extensionMeta struct { inheritedMeta @@ -306,24 +330,24 @@ type extensionMeta struct { } type extensionDesc struct{ x *Extension } -func (t extensionDesc) Parent() (pref.Descriptor, bool) { return t.x.parent, true } -func (t extensionDesc) Syntax() pref.Syntax { return t.x.syntax } -func (t extensionDesc) Index() int { return t.x.index } -func (t extensionDesc) Name() pref.Name { return t.x.Name } -func (t extensionDesc) FullName() pref.FullName { return t.x.fullName } -func (t extensionDesc) IsPlaceholder() bool { return false } -func (t extensionDesc) DescriptorProto() (pref.Message, bool) { return t.x.desc.lazyInit(t) } -func (t extensionDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.x.opts.lazyInit(t) } -func (t extensionDesc) Number() pref.FieldNumber { return t.x.Number } -func (t extensionDesc) Cardinality() pref.Cardinality { return t.x.Cardinality } -func (t extensionDesc) Kind() pref.Kind { return t.x.Kind } -func (t extensionDesc) JSONName() string { return "" } -func (t extensionDesc) IsPacked() bool { return t.x.IsPacked } -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.IsValid() } -func (t extensionDesc) OneofType() pref.OneofDescriptor { return nil } +func (t extensionDesc) Parent() (pref.Descriptor, bool) { return t.x.parent, true } +func (t extensionDesc) Syntax() pref.Syntax { return t.x.syntax } +func (t extensionDesc) Index() int { return t.x.index } +func (t extensionDesc) Name() pref.Name { return t.x.Name } +func (t extensionDesc) FullName() pref.FullName { return t.x.fullName } +func (t extensionDesc) IsPlaceholder() bool { return false } +func (t extensionDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t extensionDesc) Options() interface{} { return t.x.Options } +func (t extensionDesc) Number() pref.FieldNumber { return t.x.Number } +func (t extensionDesc) Cardinality() pref.Cardinality { return t.x.Cardinality } +func (t extensionDesc) Kind() pref.Kind { return t.x.Kind } +func (t extensionDesc) JSONName() string { return "" } +func (t extensionDesc) IsPacked() bool { return extIsPacked(t) } +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.IsValid() } +func (t extensionDesc) OneofType() pref.OneofDescriptor { return nil } func (t extensionDesc) ExtendedType() pref.MessageDescriptor { return t.x.xt.lazyInit(t, &t.x.ExtendedType) } @@ -335,6 +359,15 @@ func (t extensionDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t func (t extensionDesc) ProtoType(pref.FieldDescriptor) {} func (t extensionDesc) ProtoInternal(pragma.DoNotImplement) {} +func extIsPacked(t extensionDesc) bool { + // TODO: When a proto3 file extends a proto2 message (permitted only for + // extending descriptor options), does the extension have proto2 or proto3 + // semantics? If the latter, repeated, scalar, numeric, proto3 extension + // fields should default to packed. If the former, perhaps the extension syntax + // should be protoreflect.Proto2. + return t.x.Options.GetPacked() +} + type enumMeta struct { inheritedMeta @@ -342,36 +375,36 @@ type enumMeta struct { } type enumDesc struct{ e *Enum } -func (t enumDesc) Parent() (pref.Descriptor, bool) { return t.e.parent, true } -func (t enumDesc) Index() int { return t.e.index } -func (t enumDesc) Syntax() pref.Syntax { return t.e.syntax } -func (t enumDesc) Name() pref.Name { return t.e.Name } -func (t enumDesc) FullName() pref.FullName { return t.e.fullName } -func (t enumDesc) IsPlaceholder() bool { return false } -func (t enumDesc) DescriptorProto() (pref.Message, bool) { return t.e.desc.lazyInit(t) } -func (t enumDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.e.opts.lazyInit(t) } -func (t enumDesc) Values() pref.EnumValueDescriptors { return t.e.vs.lazyInit(t, t.e.Values) } -func (t enumDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t enumDesc) ProtoType(pref.EnumDescriptor) {} -func (t enumDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t enumDesc) Parent() (pref.Descriptor, bool) { return t.e.parent, true } +func (t enumDesc) Index() int { return t.e.index } +func (t enumDesc) Syntax() pref.Syntax { return t.e.syntax } +func (t enumDesc) Name() pref.Name { return t.e.Name } +func (t enumDesc) FullName() pref.FullName { return t.e.fullName } +func (t enumDesc) IsPlaceholder() bool { return false } +func (t enumDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t enumDesc) Options() interface{} { return t.e.Options } +func (t enumDesc) Values() pref.EnumValueDescriptors { return t.e.vs.lazyInit(t, t.e.Values) } +func (t enumDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t enumDesc) ProtoType(pref.EnumDescriptor) {} +func (t enumDesc) ProtoInternal(pragma.DoNotImplement) {} type enumValueMeta struct { inheritedMeta } type enumValueDesc struct{ v *EnumValue } -func (t enumValueDesc) Parent() (pref.Descriptor, bool) { return t.v.parent, true } -func (t enumValueDesc) Index() int { return t.v.index } -func (t enumValueDesc) Syntax() pref.Syntax { return t.v.syntax } -func (t enumValueDesc) Name() pref.Name { return t.v.Name } -func (t enumValueDesc) FullName() pref.FullName { return t.v.fullName } -func (t enumValueDesc) IsPlaceholder() bool { return false } -func (t enumValueDesc) DescriptorProto() (pref.Message, bool) { return t.v.desc.lazyInit(t) } -func (t enumValueDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.v.opts.lazyInit(t) } -func (t enumValueDesc) Number() pref.EnumNumber { return t.v.Number } -func (t enumValueDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t enumValueDesc) ProtoType(pref.EnumValueDescriptor) {} -func (t enumValueDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t enumValueDesc) Parent() (pref.Descriptor, bool) { return t.v.parent, true } +func (t enumValueDesc) Index() int { return t.v.index } +func (t enumValueDesc) Syntax() pref.Syntax { return t.v.syntax } +func (t enumValueDesc) Name() pref.Name { return t.v.Name } +func (t enumValueDesc) FullName() pref.FullName { return t.v.fullName } +func (t enumValueDesc) IsPlaceholder() bool { return false } +func (t enumValueDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t enumValueDesc) Options() interface{} { return t.v.Options } +func (t enumValueDesc) Number() pref.EnumNumber { return t.v.Number } +func (t enumValueDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t enumValueDesc) ProtoType(pref.EnumValueDescriptor) {} +func (t enumValueDesc) ProtoInternal(pragma.DoNotImplement) {} type serviceMeta struct { inheritedMeta @@ -380,18 +413,18 @@ type serviceMeta struct { } type serviceDesc struct{ s *Service } -func (t serviceDesc) Parent() (pref.Descriptor, bool) { return t.s.parent, true } -func (t serviceDesc) Index() int { return t.s.index } -func (t serviceDesc) Syntax() pref.Syntax { return t.s.syntax } -func (t serviceDesc) Name() pref.Name { return t.s.Name } -func (t serviceDesc) FullName() pref.FullName { return t.s.fullName } -func (t serviceDesc) IsPlaceholder() bool { return false } -func (t serviceDesc) DescriptorProto() (pref.Message, bool) { return t.s.desc.lazyInit(t) } -func (t serviceDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.s.opts.lazyInit(t) } -func (t serviceDesc) Methods() pref.MethodDescriptors { return t.s.ms.lazyInit(t, t.s.Methods) } -func (t serviceDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t serviceDesc) ProtoType(pref.ServiceDescriptor) {} -func (t serviceDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t serviceDesc) Parent() (pref.Descriptor, bool) { return t.s.parent, true } +func (t serviceDesc) Index() int { return t.s.index } +func (t serviceDesc) Syntax() pref.Syntax { return t.s.syntax } +func (t serviceDesc) Name() pref.Name { return t.s.Name } +func (t serviceDesc) FullName() pref.FullName { return t.s.fullName } +func (t serviceDesc) IsPlaceholder() bool { return false } +func (t serviceDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t serviceDesc) Options() interface{} { return t.s.Options } +func (t serviceDesc) Methods() pref.MethodDescriptors { return t.s.ms.lazyInit(t, t.s.Methods) } +func (t serviceDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t serviceDesc) ProtoType(pref.ServiceDescriptor) {} +func (t serviceDesc) ProtoInternal(pragma.DoNotImplement) {} type methodMeta struct { inheritedMeta @@ -401,21 +434,21 @@ type methodMeta struct { } type methodDesc struct{ m *Method } -func (t methodDesc) Parent() (pref.Descriptor, bool) { return t.m.parent, true } -func (t methodDesc) Index() int { return t.m.index } -func (t methodDesc) Syntax() pref.Syntax { return t.m.syntax } -func (t methodDesc) Name() pref.Name { return t.m.Name } -func (t methodDesc) FullName() pref.FullName { return t.m.fullName } -func (t methodDesc) IsPlaceholder() bool { return false } -func (t methodDesc) DescriptorProto() (pref.Message, bool) { return t.m.desc.lazyInit(t) } -func (t methodDesc) DescriptorOptions() (pref.DescriptorOptions, bool) { return t.m.opts.lazyInit(t) } -func (t methodDesc) InputType() pref.MessageDescriptor { return t.m.mit.lazyInit(t, &t.m.InputType) } -func (t methodDesc) OutputType() pref.MessageDescriptor { return t.m.mot.lazyInit(t, &t.m.OutputType) } -func (t methodDesc) IsStreamingClient() bool { return t.m.IsStreamingClient } -func (t methodDesc) IsStreamingServer() bool { return t.m.IsStreamingServer } -func (t methodDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t methodDesc) ProtoType(pref.MethodDescriptor) {} -func (t methodDesc) ProtoInternal(pragma.DoNotImplement) {} +func (t methodDesc) Parent() (pref.Descriptor, bool) { return t.m.parent, true } +func (t methodDesc) Index() int { return t.m.index } +func (t methodDesc) Syntax() pref.Syntax { return t.m.syntax } +func (t methodDesc) Name() pref.Name { return t.m.Name } +func (t methodDesc) FullName() pref.FullName { return t.m.fullName } +func (t methodDesc) IsPlaceholder() bool { return false } +func (t methodDesc) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t methodDesc) Options() interface{} { return t.m.Options } +func (t methodDesc) InputType() pref.MessageDescriptor { return t.m.mit.lazyInit(t, &t.m.InputType) } +func (t methodDesc) OutputType() pref.MessageDescriptor { return t.m.mot.lazyInit(t, &t.m.OutputType) } +func (t methodDesc) IsStreamingClient() bool { return t.m.IsStreamingClient } +func (t methodDesc) IsStreamingServer() bool { return t.m.IsStreamingServer } +func (t methodDesc) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t methodDesc) ProtoType(pref.MethodDescriptor) {} +func (t methodDesc) ProtoInternal(pragma.DoNotImplement) {} type defaultValue struct { once sync.Once diff --git a/reflect/prototype/standalone.go b/reflect/prototype/standalone.go index 74893856..bd420ed1 100644 --- a/reflect/prototype/standalone.go +++ b/reflect/prototype/standalone.go @@ -5,6 +5,7 @@ package prototype import ( + descriptorV1 "github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/v2/internal/errors" "github.com/golang/protobuf/v2/reflect/protoreflect" ) @@ -17,10 +18,10 @@ import ( type StandaloneMessage struct { Syntax protoreflect.Syntax FullName protoreflect.FullName - IsMapEntry bool Fields []Field Oneofs []Oneof ExtensionRanges [][2]protoreflect.FieldNumber + Options *descriptorV1.MessageOptions fields fieldsMeta oneofs oneofsMeta @@ -63,7 +64,7 @@ func NewMessages(ts []*StandaloneMessage) ([]protoreflect.MessageDescriptor, err for i, f := range t.Fields { // Resolve placeholder messages with a concrete standalone message. // If this fails, validateMessage will complain about it later. - if !f.IsWeak && f.MessageType != nil && f.MessageType.IsPlaceholder() { + if !f.Options.GetWeak() && f.MessageType != nil && f.MessageType.IsPlaceholder() { if m, ok := ms[f.MessageType.FullName()]; ok { t.Fields[i].MessageType = m } @@ -84,6 +85,7 @@ type StandaloneEnum struct { Syntax protoreflect.Syntax FullName protoreflect.FullName Values []EnumValue + Options *descriptorV1.EnumOptions vals enumValuesMeta } @@ -107,11 +109,11 @@ type StandaloneExtension struct { Number protoreflect.FieldNumber Cardinality protoreflect.Cardinality Kind protoreflect.Kind - IsPacked bool Default protoreflect.Value MessageType protoreflect.MessageDescriptor EnumType protoreflect.EnumDescriptor ExtendedType protoreflect.MessageDescriptor + Options *descriptorV1.FieldOptions dv defaultValue } diff --git a/reflect/prototype/standalone_type.go b/reflect/prototype/standalone_type.go index 1c91e33f..90609aa5 100644 --- a/reflect/prototype/standalone_type.go +++ b/reflect/prototype/standalone_type.go @@ -13,64 +13,64 @@ import ( type standaloneMessage struct{ m *StandaloneMessage } -func (t standaloneMessage) Parent() (pref.Descriptor, bool) { return nil, false } -func (t standaloneMessage) Index() int { return 0 } -func (t standaloneMessage) Syntax() pref.Syntax { return t.m.Syntax } -func (t standaloneMessage) Name() pref.Name { return t.m.FullName.Name() } -func (t standaloneMessage) FullName() pref.FullName { return t.m.FullName } -func (t standaloneMessage) IsPlaceholder() bool { return false } -func (t standaloneMessage) DescriptorProto() (pref.Message, bool) { return nil, false } -func (t standaloneMessage) DescriptorOptions() (pref.DescriptorOptions, bool) { return nil, false } -func (t standaloneMessage) IsMapEntry() bool { return t.m.IsMapEntry } -func (t standaloneMessage) Fields() pref.FieldDescriptors { return t.m.fields.lazyInit(t, t.m.Fields) } -func (t standaloneMessage) Oneofs() pref.OneofDescriptors { return t.m.oneofs.lazyInit(t, t.m.Oneofs) } -func (t standaloneMessage) RequiredNumbers() pref.FieldNumbers { return t.m.nums.lazyInit(t.m.Fields) } -func (t standaloneMessage) ExtensionRanges() pref.FieldRanges { return (*ranges)(&t.m.ExtensionRanges) } -func (t standaloneMessage) Messages() pref.MessageDescriptors { return &emptyMessages } -func (t standaloneMessage) Enums() pref.EnumDescriptors { return &emptyEnums } -func (t standaloneMessage) Extensions() pref.ExtensionDescriptors { return &emptyExtensions } -func (t standaloneMessage) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t standaloneMessage) ProtoType(pref.MessageDescriptor) {} -func (t standaloneMessage) ProtoInternal(pragma.DoNotImplement) {} +func (t standaloneMessage) Parent() (pref.Descriptor, bool) { return nil, false } +func (t standaloneMessage) Index() int { return 0 } +func (t standaloneMessage) Syntax() pref.Syntax { return t.m.Syntax } +func (t standaloneMessage) Name() pref.Name { return t.m.FullName.Name() } +func (t standaloneMessage) FullName() pref.FullName { return t.m.FullName } +func (t standaloneMessage) IsPlaceholder() bool { return false } +func (t standaloneMessage) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t standaloneMessage) Options() interface{} { return t.m.Options } +func (t standaloneMessage) IsMapEntry() bool { return t.m.Options.GetMapEntry() } +func (t standaloneMessage) Fields() pref.FieldDescriptors { return t.m.fields.lazyInit(t, t.m.Fields) } +func (t standaloneMessage) Oneofs() pref.OneofDescriptors { return t.m.oneofs.lazyInit(t, t.m.Oneofs) } +func (t standaloneMessage) RequiredNumbers() pref.FieldNumbers { return t.m.nums.lazyInit(t.m.Fields) } +func (t standaloneMessage) ExtensionRanges() pref.FieldRanges { return (*ranges)(&t.m.ExtensionRanges) } +func (t standaloneMessage) Messages() pref.MessageDescriptors { return &emptyMessages } +func (t standaloneMessage) Enums() pref.EnumDescriptors { return &emptyEnums } +func (t standaloneMessage) Extensions() pref.ExtensionDescriptors { return &emptyExtensions } +func (t standaloneMessage) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t standaloneMessage) ProtoType(pref.MessageDescriptor) {} +func (t standaloneMessage) ProtoInternal(pragma.DoNotImplement) {} type standaloneEnum struct{ e *StandaloneEnum } -func (t standaloneEnum) Parent() (pref.Descriptor, bool) { return nil, false } -func (t standaloneEnum) Index() int { return 0 } -func (t standaloneEnum) Syntax() pref.Syntax { return t.e.Syntax } -func (t standaloneEnum) Name() pref.Name { return t.e.FullName.Name() } -func (t standaloneEnum) FullName() pref.FullName { return t.e.FullName } -func (t standaloneEnum) IsPlaceholder() bool { return false } -func (t standaloneEnum) DescriptorProto() (pref.Message, bool) { return nil, false } -func (t standaloneEnum) DescriptorOptions() (pref.DescriptorOptions, bool) { return nil, false } -func (t standaloneEnum) Values() pref.EnumValueDescriptors { return t.e.vals.lazyInit(t, t.e.Values) } -func (t standaloneEnum) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t standaloneEnum) ProtoType(pref.EnumDescriptor) {} -func (t standaloneEnum) ProtoInternal(pragma.DoNotImplement) {} +func (t standaloneEnum) Parent() (pref.Descriptor, bool) { return nil, false } +func (t standaloneEnum) Index() int { return 0 } +func (t standaloneEnum) Syntax() pref.Syntax { return t.e.Syntax } +func (t standaloneEnum) Name() pref.Name { return t.e.FullName.Name() } +func (t standaloneEnum) FullName() pref.FullName { return t.e.FullName } +func (t standaloneEnum) IsPlaceholder() bool { return false } +func (t standaloneEnum) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t standaloneEnum) Options() interface{} { return t.e.Options } +func (t standaloneEnum) Values() pref.EnumValueDescriptors { return t.e.vals.lazyInit(t, t.e.Values) } +func (t standaloneEnum) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t standaloneEnum) ProtoType(pref.EnumDescriptor) {} +func (t standaloneEnum) ProtoInternal(pragma.DoNotImplement) {} type standaloneExtension struct{ x *StandaloneExtension } -func (t standaloneExtension) Parent() (pref.Descriptor, bool) { return nil, false } -func (t standaloneExtension) Index() int { return 0 } -func (t standaloneExtension) Syntax() pref.Syntax { return t.x.Syntax } -func (t standaloneExtension) Name() pref.Name { return t.x.FullName.Name() } -func (t standaloneExtension) FullName() pref.FullName { return t.x.FullName } -func (t standaloneExtension) IsPlaceholder() bool { return false } -func (t standaloneExtension) DescriptorProto() (pref.Message, bool) { return nil, false } -func (t standaloneExtension) DescriptorOptions() (pref.DescriptorOptions, bool) { return nil, false } -func (t standaloneExtension) Number() pref.FieldNumber { return t.x.Number } -func (t standaloneExtension) Cardinality() pref.Cardinality { return t.x.Cardinality } -func (t standaloneExtension) Kind() pref.Kind { return t.x.Kind } -func (t standaloneExtension) JSONName() string { return "" } -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.dv.lazyInit(t, t.x.Default) } -func (t standaloneExtension) HasDefault() bool { return t.x.Default.IsValid() } -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 } -func (t standaloneExtension) ExtendedType() pref.MessageDescriptor { return t.x.ExtendedType } -func (t standaloneExtension) Format(s fmt.State, r rune) { formatDesc(s, r, t) } -func (t standaloneExtension) ProtoType(pref.FieldDescriptor) {} -func (t standaloneExtension) ProtoInternal(pragma.DoNotImplement) {} +func (t standaloneExtension) Parent() (pref.Descriptor, bool) { return nil, false } +func (t standaloneExtension) Index() int { return 0 } +func (t standaloneExtension) Syntax() pref.Syntax { return t.x.Syntax } +func (t standaloneExtension) Name() pref.Name { return t.x.FullName.Name() } +func (t standaloneExtension) FullName() pref.FullName { return t.x.FullName } +func (t standaloneExtension) IsPlaceholder() bool { return false } +func (t standaloneExtension) DescriptorProto() (pref.Message, bool) { return nil, false } +func (t standaloneExtension) Options() interface{} { return t.x.Options } +func (t standaloneExtension) Number() pref.FieldNumber { return t.x.Number } +func (t standaloneExtension) Cardinality() pref.Cardinality { return t.x.Cardinality } +func (t standaloneExtension) Kind() pref.Kind { return t.x.Kind } +func (t standaloneExtension) JSONName() string { return "" } +func (t standaloneExtension) IsPacked() bool { return t.x.Options.GetPacked() } +func (t standaloneExtension) IsMap() bool { return false } +func (t standaloneExtension) IsWeak() bool { return false } +func (t standaloneExtension) Default() pref.Value { return t.x.dv.lazyInit(t, t.x.Default) } +func (t standaloneExtension) HasDefault() bool { return t.x.Default.IsValid() } +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 } +func (t standaloneExtension) ExtendedType() pref.MessageDescriptor { return t.x.ExtendedType } +func (t standaloneExtension) Format(s fmt.State, r rune) { formatDesc(s, r, t) } +func (t standaloneExtension) ProtoType(pref.FieldDescriptor) {} +func (t standaloneExtension) ProtoInternal(pragma.DoNotImplement) {} diff --git a/reflect/prototype/type_test.go b/reflect/prototype/type_test.go index c5fe70fb..cf961f15 100644 --- a/reflect/prototype/type_test.go +++ b/reflect/prototype/type_test.go @@ -109,12 +109,17 @@ func TestFile(t *testing.T) { Syntax: pref.Proto2, Path: "path/to/file.proto", Package: "test", + Options: &descriptorV1.FileOptions{Deprecated: protoV1.Bool(true)}, Messages: []Message{{ - Name: "A", // "test.A" - IsMapEntry: true, + Name: "A", // "test.A" + Options: &descriptorV1.MessageOptions{ + MapEntry: protoV1.Bool(true), + Deprecated: protoV1.Bool(true), + }, Fields: []Field{{ Name: "key", // "test.A.key" Number: 1, + Options: &descriptorV1.FieldOptions{Deprecated: protoV1.Bool(true)}, Cardinality: pref.Optional, Kind: pref.StringKind, }, { @@ -161,7 +166,7 @@ func TestFile(t *testing.T) { Number: 5, Cardinality: pref.Repeated, Kind: pref.Int32Kind, - IsPacked: true, + Options: &descriptorV1.FieldOptions{Packed: protoV1.Bool(true)}, }, { Name: "field_six", // "test.B.field_six" Number: 6, @@ -169,7 +174,14 @@ func TestFile(t *testing.T) { Kind: pref.BytesKind, }}, Oneofs: []Oneof{ - {Name: "O1"}, // "test.B.O1" + { + Name: "O1", // "test.B.O1" + Options: &descriptorV1.OneofOptions{ + UninterpretedOption: []*descriptorV1.UninterpretedOption{ + {StringValue: []byte("option")}, + }, + }, + }, {Name: "O2"}, // "test.B.O2" }, ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}, {3000, 3001}}, @@ -188,32 +200,42 @@ func TestFile(t *testing.T) { Number: 1000, Cardinality: pref.Repeated, Kind: pref.MessageKind, - IsPacked: false, + Options: &descriptorV1.FieldOptions{Packed: protoV1.Bool(false)}, MessageType: PlaceholderMessage("test.C"), ExtendedType: PlaceholderMessage("test.B"), }}, }}, Enums: []Enum{{ - Name: "E1", // "test.E1" - Values: []EnumValue{{Name: "FOO", Number: 0}, {Name: "BAR", Number: 1}}, + Name: "E1", // "test.E1" + Options: &descriptorV1.EnumOptions{Deprecated: protoV1.Bool(true)}, + Values: []EnumValue{ + { + Name: "FOO", + Number: 0, + Options: &descriptorV1.EnumValueOptions{Deprecated: protoV1.Bool(true)}, + }, + {Name: "BAR", Number: 1}, + }, }}, Extensions: []Extension{{ Name: "X", // "test.X" Number: 1000, Cardinality: pref.Repeated, Kind: pref.MessageKind, - IsPacked: true, + Options: &descriptorV1.FieldOptions{Packed: protoV1.Bool(true)}, MessageType: PlaceholderMessage("test.C"), ExtendedType: PlaceholderMessage("test.B"), }}, Services: []Service{{ - Name: "S", // "test.S" + Name: "S", // "test.S" + Options: &descriptorV1.ServiceOptions{Deprecated: protoV1.Bool(true)}, Methods: []Method{{ Name: "M", // "test.S.M" InputType: PlaceholderMessage("test.A"), OutputType: PlaceholderMessage("test.C.A"), IsStreamingClient: true, IsStreamingServer: true, + Options: &descriptorV1.MethodOptions{Deprecated: protoV1.Bool(true)}, }}, }}, } @@ -226,14 +248,19 @@ func TestFile(t *testing.T) { Syntax: protoV1.String("proto2"), Name: protoV1.String("path/to/file.proto"), Package: protoV1.String("test"), + Options: &descriptorV1.FileOptions{Deprecated: protoV1.Bool(true)}, MessageType: []*descriptorV1.DescriptorProto{{ - Name: protoV1.String("A"), - Options: &descriptorV1.MessageOptions{MapEntry: protoV1.Bool(true)}, + Name: protoV1.String("A"), + Options: &descriptorV1.MessageOptions{ + MapEntry: protoV1.Bool(true), + Deprecated: protoV1.Bool(true), + }, Field: []*descriptorV1.FieldDescriptorProto{{ - Name: protoV1.String("key"), - Number: protoV1.Int32(1), - Label: descriptorV1.FieldDescriptorProto_Label(pref.Optional).Enum(), - Type: descriptorV1.FieldDescriptorProto_Type(pref.StringKind).Enum(), + Name: protoV1.String("key"), + Number: protoV1.Int32(1), + Options: &descriptorV1.FieldOptions{Deprecated: protoV1.Bool(true)}, + Label: descriptorV1.FieldDescriptorProto_Label(pref.Optional).Enum(), + Type: descriptorV1.FieldDescriptorProto_Type(pref.StringKind).Enum(), }, { Name: protoV1.String("value"), Number: protoV1.Int32(2), @@ -286,7 +313,14 @@ func TestFile(t *testing.T) { Type: descriptorV1.FieldDescriptorProto_Type(pref.BytesKind).Enum(), }}, OneofDecl: []*descriptorV1.OneofDescriptorProto{ - {Name: protoV1.String("O1")}, + { + Name: protoV1.String("O1"), + Options: &descriptorV1.OneofOptions{ + UninterpretedOption: []*descriptorV1.UninterpretedOption{ + {StringValue: []byte("option")}, + }, + }, + }, {Name: protoV1.String("O2")}, }, ExtensionRange: []*descriptorV1.DescriptorProto_ExtensionRange{ @@ -322,9 +356,14 @@ func TestFile(t *testing.T) { }}, }}, EnumType: []*descriptorV1.EnumDescriptorProto{{ - Name: protoV1.String("E1"), + Name: protoV1.String("E1"), + Options: &descriptorV1.EnumOptions{Deprecated: protoV1.Bool(true)}, Value: []*descriptorV1.EnumValueDescriptorProto{ - {Name: protoV1.String("FOO"), Number: protoV1.Int32(0)}, + { + Name: protoV1.String("FOO"), + Number: protoV1.Int32(0), + Options: &descriptorV1.EnumValueOptions{Deprecated: protoV1.Bool(true)}, + }, {Name: protoV1.String("BAR"), Number: protoV1.Int32(1)}, }, }}, @@ -338,13 +377,15 @@ func TestFile(t *testing.T) { Extendee: protoV1.String(".test.B"), }}, Service: []*descriptorV1.ServiceDescriptorProto{{ - Name: protoV1.String("S"), + Name: protoV1.String("S"), + Options: &descriptorV1.ServiceOptions{Deprecated: protoV1.Bool(true)}, Method: []*descriptorV1.MethodDescriptorProto{{ Name: protoV1.String("M"), InputType: protoV1.String(".test.A"), OutputType: protoV1.String(".test.C.A"), ClientStreaming: protoV1.Bool(true), ServerStreaming: protoV1.Bool(true), + Options: &descriptorV1.MethodOptions{Deprecated: protoV1.Bool(true)}, }}, }}, } @@ -385,6 +426,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "Path": "path/to/file.proto", "Package": pref.FullName("test"), "IsPlaceholder": false, + "Options": &descriptorV1.FileOptions{Deprecated: protoV1.Bool(true)}, "Messages": M{ "Len": 3, "Get:0": M{ @@ -395,6 +437,10 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "FullName": pref.FullName("test.A"), "IsPlaceholder": false, "IsMapEntry": true, + "Options": &descriptorV1.MessageOptions{ + MapEntry: protoV1.Bool(true), + Deprecated: protoV1.Bool(true), + }, "Fields": M{ "Len": 2, "ByNumber:1": M{ @@ -405,6 +451,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "Number": pref.FieldNumber(1), "Cardinality": pref.Optional, "Kind": pref.StringKind, + "Options": &descriptorV1.FieldOptions{Deprecated: protoV1.Bool(true)}, "JSONName": "key", "IsPacked": false, "IsMap": false, @@ -494,6 +541,11 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "ByName:O1": M{ "FullName": pref.FullName("test.B.O1"), "Index": 0, + "Options": &descriptorV1.OneofOptions{ + UninterpretedOption: []*descriptorV1.UninterpretedOption{ + {StringValue: []byte("option")}, + }, + }, "Fields": M{ "Len": 1, "Get:0": M{"FullName": pref.FullName("test.B.field_one")}, @@ -546,11 +598,15 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "Enums": M{ "Len": 1, "Get:0": M{ - "Name": pref.Name("E1"), + "Name": pref.Name("E1"), + "Options": &descriptorV1.EnumOptions{Deprecated: protoV1.Bool(true)}, "Values": M{ "Len": 2, "ByName:Foo": nil, - "ByName:FOO": M{"FullName": pref.FullName("test.FOO")}, + "ByName:FOO": M{ + "FullName": pref.FullName("test.FOO"), + "Options": &descriptorV1.EnumValueOptions{Deprecated: protoV1.Bool(true)}, + }, "ByNumber:2": nil, "ByNumber:1": M{"FullName": pref.FullName("test.BAR")}, }, @@ -566,6 +622,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "IsPacked": true, "MessageType": M{"FullName": pref.FullName("test.C"), "IsPlaceholder": false}, "ExtendedType": M{"FullName": pref.FullName("test.B"), "IsPlaceholder": false}, + "Options": &descriptorV1.FieldOptions{Packed: protoV1.Bool(true)}, }, }, "Services": M{ @@ -575,6 +632,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "Parent": M{"FullName": pref.FullName("test")}, "Name": pref.Name("S"), "FullName": pref.FullName("test.S"), + "Options": &descriptorV1.ServiceOptions{Deprecated: protoV1.Bool(true)}, "Methods": M{ "Len": 1, "Get:0": M{ @@ -585,6 +643,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) { "OutputType": M{"FullName": pref.FullName("test.C.A"), "IsPlaceholder": false}, "IsStreamingClient": true, "IsStreamingServer": true, + "Options": &descriptorV1.MethodOptions{Deprecated: protoV1.Bool(true)}, }, }, }, @@ -659,14 +718,26 @@ func checkAccessors(t *testing.T, p string, rv reflect.Value, want map[string]in // Check that the accessor output matches. if want, ok := v.(map[string]interface{}); ok { checkAccessors(t, p, rets[0], want) - } else { - got := rets[0].Interface() - if pv, ok := got.(pref.Value); ok { - got = pv.Interface() - } - if want := v; !reflect.DeepEqual(got, want) { + continue + } + + got := rets[0].Interface() + if pv, ok := got.(pref.Value); ok { + got = pv.Interface() + } + + // Compare with proto.Equal if possible. + gotMsg, gotMsgOK := got.(protoV1.Message) + wantMsg, wantMsgOK := v.(protoV1.Message) + if gotMsgOK && wantMsgOK { + if !protoV1.Equal(gotMsg, wantMsg) { t.Errorf("%v = %v, want %v", p, got, want) } + continue + } + + if want := v; !reflect.DeepEqual(got, want) { + t.Errorf("%v = %v, want %v", p, got, want) } } } diff --git a/reflect/prototype/validate.go b/reflect/prototype/validate.go index c336f177..be1090b7 100644 --- a/reflect/prototype/validate.go +++ b/reflect/prototype/validate.go @@ -26,6 +26,9 @@ import ( // * Placeholder messages and types may only be for weak fields. // * Placeholder full names must be valid. // * The name of each descriptor must be valid. +// * Options are consistent with constructor fields: +// Message.IsMapEntry and Message.Options.MapEntry +// Field.IsPacked and Field.Options.Packed func validateFile(t pref.FileDescriptor) error { return nil