reflect/protoregistry: initial commit
Package protoregistry provides data structures to register and lookup
protobuf descriptor types.
High-level API:
var GlobalFiles = new(Files)
var NotFound = errors.New("not found")
type Files struct{ ... }
func NewFiles(...pref.FileDescriptor) *Files
func (*Files) Register(...pref.FileDescriptor) error
func (*Files) FindDescriptorByName(pref.FullName) (pref.Descriptor, error)
func (*Files) RangeFiles(func(pref.FileDescriptor) bool)
func (*Files) RangeFilesByPackage(pref.FullName, func(pref.FileDescriptor) bool)
func (*Files) RangeFilesByPath(string, func(pref.FileDescriptor) bool)
To support the FindDescriptorByName method, we add a DescriptorByName to
protoreflect.FileDescriptor and associated logic to prototype.
Change-Id: I14d65f74d2bd9f4f48641da9dfa70190310e5878
Reviewed-on: https://go-review.googlesource.com/129499
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/reflect/protoreflect/type.go b/reflect/protoreflect/type.go
index d46f402..427aea9 100644
--- a/reflect/protoreflect/type.go
+++ b/reflect/protoreflect/type.go
@@ -178,6 +178,9 @@
Extensions() ExtensionDescriptors
// Services is a list of the top-level service declarations.
Services() ServiceDescriptors
+ // DescriptorByName looks up any descriptor declared within this file
+ // by full name. It returns nil if not found.
+ DescriptorByName(FullName) Descriptor
isFileDescriptor
}
diff --git a/reflect/protoregistry/registry.go b/reflect/protoregistry/registry.go
new file mode 100644
index 0000000..5e21043
--- /dev/null
+++ b/reflect/protoregistry/registry.go
@@ -0,0 +1,291 @@
+// 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 protoregistry provides data structures to register and lookup
+// protobuf descriptor types.
+package protoregistry
+
+import (
+ "sort"
+ "strings"
+
+ "google.golang.org/proto/internal/errors"
+ "google.golang.org/proto/reflect/protoreflect"
+)
+
+// TODO: Perhaps Register should record the frame of where the function was
+// called and surface that in the error? That would help users debug duplicate
+// registration issues. This presumes that we provide a way to disable automatic
+// registration in generated code.
+
+// TODO: Add a type registry:
+/*
+var GlobalTypes = new(Types)
+
+type Type interface {
+ protoreflect.Descriptor
+ GoType() reflect.Type
+}
+type Types struct {
+ Parent *Types
+ Resolver func(url string) (Type, error)
+}
+func NewTypes(typs ...Type) *Types
+func (*Types) Register(typs ...Type) error
+func (*Types) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error)
+func (*Types) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error)
+func (*Types) FindMessageByURL(url string) (protoreflect.MessageType, error)
+func (*Types) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
+func (*Types) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
+func (*Types) RangeEnums(f func(protoreflect.EnumType) bool)
+func (*Types) RangeMessages(f func(protoreflect.MessageType) bool)
+func (*Types) RangeExtensions(f func(protoreflect.ExtensionType) bool)
+func (*Types) RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
+*/
+
+// GlobalFiles is a global registry of file descriptors.
+var GlobalFiles = new(Files)
+
+// NotFound is a sentinel error value to indicate that the type was not found.
+var NotFound = errors.New("not found")
+
+// Files is a registry for looking up or iterating over files and the
+// descriptors contained within them.
+// The Find and Range methods are safe for concurrent use.
+type Files struct {
+ filesByPackage filesByPackage
+ filesByPath filesByPath
+}
+
+type (
+ filesByPackage struct {
+ // files is a list of files all in the same package.
+ files []protoreflect.FileDescriptor
+ // subs is a tree of files all in a sub-package scope.
+ // It also maps all top-level identifiers declared in files
+ // as the notProtoPackage sentinel value.
+ subs map[protoreflect.Name]*filesByPackage // invariant: len(Name) > 0
+ }
+ filesByPath map[string][]protoreflect.FileDescriptor
+)
+
+// notProtoPackage is a sentinel value to indicate that some identifier maps
+// to an actual protobuf declaration and is not a sub-package.
+var notProtoPackage = new(filesByPackage)
+
+// NewFiles returns a registry initialized with the provided set of files.
+// If there are duplicates, the first one takes precedence.
+func NewFiles(files ...protoreflect.FileDescriptor) *Files {
+ // TODO: Should last take precedence? This allows a user to intentionally
+ // overwrite an existing registration.
+ //
+ // The use case is for implementing the existing v1 proto.RegisterFile
+ // function where the behavior is last wins. However, it could be argued
+ // that the v1 behavior is broken, and we can switch to first wins
+ // without violating compatibility.
+ r := new(Files)
+ r.Register(files...) // ignore errors; first takes precedence
+ return r
+}
+
+// Register registers the provided list of file descriptors.
+// Placeholder files are ignored.
+//
+// If any descriptor within a file conflicts with the descriptor of any
+// previously registered file (e.g., two enums with the same full name),
+// then that file is not registered and an error is returned.
+//
+// It is permitted for multiple files to have the same file path.
+func (r *Files) Register(files ...protoreflect.FileDescriptor) error {
+ var firstErr error
+fileLoop:
+ for _, file := range files {
+ if file.IsPlaceholder() {
+ continue // TODO: Should this be an error instead?
+ }
+
+ // Register the file into the filesByPackage tree.
+ //
+ // The prototype package validates that a FileDescriptor is internally
+ // consistent such it does not have conflicts within itself.
+ // However, we need to ensure that the inserted file does not conflict
+ // with other previously inserted files.
+ pkg := file.Package()
+ root := &r.filesByPackage
+ for len(pkg) > 0 {
+ var prefix protoreflect.Name
+ prefix, pkg = splitPrefix(pkg)
+
+ // Add a new sub-package segment.
+ switch nextRoot := root.subs[prefix]; nextRoot {
+ case nil:
+ nextRoot = new(filesByPackage)
+ if root.subs == nil {
+ root.subs = make(map[protoreflect.Name]*filesByPackage)
+ }
+ root.subs[prefix] = nextRoot
+ root = nextRoot
+ case notProtoPackage:
+ if firstErr == nil {
+ name := strings.TrimSuffix(strings.TrimSuffix(string(file.Package()), string(pkg)), ".")
+ firstErr = errors.New("file %q has a name conflict over %v", file.Path(), name)
+ }
+ continue fileLoop
+ default:
+ root = nextRoot
+ }
+ }
+ // Check for top-level conflicts within the same package.
+ // The current file cannot add any top-level declaration that conflict
+ // with another top-level declaration or sub-package name.
+ var conflicts []protoreflect.Name
+ rangeTopLevelDeclarations(file, func(s protoreflect.Name) {
+ if root.subs[s] == nil {
+ if root.subs == nil {
+ root.subs = make(map[protoreflect.Name]*filesByPackage)
+ }
+ root.subs[s] = notProtoPackage
+ } else {
+ conflicts = append(conflicts, s)
+ }
+ })
+ if len(conflicts) > 0 {
+ // Remove inserted identifiers to make registration failure atomic.
+ sort.Slice(conflicts, func(i, j int) bool { return conflicts[i] < conflicts[j] })
+ rangeTopLevelDeclarations(file, func(s protoreflect.Name) {
+ i := sort.Search(len(conflicts), func(i int) bool { return conflicts[i] >= s })
+ if has := i < len(conflicts) && conflicts[i] == s; !has {
+ delete(root.subs, s) // remove everything not in conflicts
+ }
+ })
+
+ if firstErr == nil {
+ name := file.Package().Append(conflicts[0])
+ firstErr = errors.New("file %q has a name conflict over %v", file.Path(), name)
+ }
+ continue fileLoop
+ }
+ root.files = append(root.files, file)
+
+ // Register the file into the filesByPath map.
+ //
+ // There is no check for conflicts in file path since the path is
+ // heavily dependent on how protoc is invoked. When protoc is being
+ // invoked by different parties in a distributed manner, it is
+ // unreasonable to assume nor ensure that the path is unique.
+ if r.filesByPath == nil {
+ r.filesByPath = make(filesByPath)
+ }
+ r.filesByPath[file.Path()] = append(r.filesByPath[file.Path()], file)
+ }
+ return firstErr
+}
+
+// FindDescriptorByName looks up any descriptor (except files) by its full name.
+// Files are not handled since multiple file descriptors may belong in
+// the same package and have the same full name (see RangeFilesByPackage).
+//
+// This return (nil, NotFound) if not found.
+func (r *Files) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
+ pkg := name
+ root := &r.filesByPackage
+ for len(pkg) > 0 {
+ var prefix protoreflect.Name
+ prefix, pkg = splitPrefix(pkg)
+ switch nextRoot := root.subs[prefix]; nextRoot {
+ case nil:
+ return nil, NotFound
+ case notProtoPackage:
+ // Search current root's package for the descriptor.
+ for _, fd := range root.files {
+ if d := fd.DescriptorByName(name); d != nil {
+ return d, nil
+ }
+ }
+ return nil, NotFound
+ default:
+ root = nextRoot
+ }
+ }
+ return nil, NotFound
+}
+
+// RangeFiles iterates over all registered files.
+// The iteration order is undefined.
+func (r *Files) RangeFiles(f func(protoreflect.FileDescriptor) bool) {
+ r.RangeFilesByPackage("", f) // empty package is a prefix for all packages
+}
+
+// RangeFilesByPackage iterates over all registered files filtered by
+// the given proto package prefix. It iterates over files with an exact package
+// match before iterating over files with general prefix match.
+// The iteration order is undefined within exact matches or prefix matches.
+func (r *Files) RangeFilesByPackage(pkg protoreflect.FullName, f func(protoreflect.FileDescriptor) bool) {
+ if strings.HasSuffix(string(pkg), ".") {
+ return // avoid edge case where splitPrefix allows trailing dot
+ }
+ root := &r.filesByPackage
+ for len(pkg) > 0 && root != nil {
+ var prefix protoreflect.Name
+ prefix, pkg = splitPrefix(pkg)
+ root = root.subs[prefix]
+ }
+ rangeFiles(root, f)
+}
+func rangeFiles(fs *filesByPackage, f func(protoreflect.FileDescriptor) bool) bool {
+ if fs == nil {
+ return true
+ }
+ // Iterate over exact matches.
+ for _, fd := range fs.files { // TODO: iterate non-deterministically
+ if !f(fd) {
+ return false
+ }
+ }
+ // Iterate over prefix matches.
+ for _, fs := range fs.subs {
+ if !rangeFiles(fs, f) {
+ return false
+ }
+ }
+ return true
+}
+
+// RangeFilesByPath iterates over all registered files filtered by
+// the given proto path. The iteration order is undefined.
+func (r *Files) RangeFilesByPath(path string, f func(protoreflect.FileDescriptor) bool) {
+ for _, fd := range r.filesByPath[path] { // TODO: iterate non-deterministically
+ if !f(fd) {
+ return
+ }
+ }
+}
+
+func splitPrefix(name protoreflect.FullName) (protoreflect.Name, protoreflect.FullName) {
+ if i := strings.IndexByte(string(name), '.'); i >= 0 {
+ return protoreflect.Name(name[:i]), name[i+len("."):]
+ }
+ return protoreflect.Name(name), ""
+}
+
+// rangeTopLevelDeclarations iterates over the name of all top-level
+// declarations in the proto file.
+func rangeTopLevelDeclarations(fd protoreflect.FileDescriptor, f func(protoreflect.Name)) {
+ for i := 0; i < fd.Enums().Len(); i++ {
+ e := fd.Enums().Get(i)
+ f(e.Name())
+ for i := 0; i < e.Values().Len(); i++ {
+ f(e.Values().Get(i).Name())
+ }
+ }
+ for i := 0; i < fd.Messages().Len(); i++ {
+ f(fd.Messages().Get(i).Name())
+ }
+ for i := 0; i < fd.Extensions().Len(); i++ {
+ f(fd.Extensions().Get(i).Name())
+ }
+ for i := 0; i < fd.Services().Len(); i++ {
+ f(fd.Services().Get(i).Name())
+ }
+}
diff --git a/reflect/protoregistry/registry_test.go b/reflect/protoregistry/registry_test.go
new file mode 100644
index 0000000..d05f8ed
--- /dev/null
+++ b/reflect/protoregistry/registry_test.go
@@ -0,0 +1,313 @@
+// 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 protoregistry
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+
+ pref "google.golang.org/proto/reflect/protoreflect"
+ ptype "google.golang.org/proto/reflect/prototype"
+)
+
+func TestFiles(t *testing.T) {
+ type (
+ file struct {
+ Path string
+ Pkg pref.FullName
+ }
+ testFile struct {
+ inFile *ptype.File
+ wantErr string
+ }
+ testFindDesc struct {
+ inName pref.FullName
+ wantOk bool
+ }
+ testRangePkg struct {
+ inPkg pref.FullName
+ wantFiles []file
+ }
+ testRangePath struct {
+ inPath string
+ wantFiles []file
+ }
+ )
+
+ tests := []struct {
+ files []testFile
+ findDescs []testFindDesc
+ rangePkgs []testRangePkg
+ rangePaths []testRangePath
+ }{{
+ // Test that overlapping packages and files are permitted.
+ files: []testFile{
+ {inFile: &ptype.File{Syntax: pref.Proto2, Package: "foo.bar"}},
+ {inFile: &ptype.File{Syntax: pref.Proto2, Path: "foo/bar/test.proto", Package: "my.test"}},
+ {inFile: &ptype.File{Syntax: pref.Proto2, Path: "foo/bar/test.proto", Package: "foo.bar.baz"}},
+ {inFile: &ptype.File{Syntax: pref.Proto2, Package: "my.test.package"}},
+ {inFile: &ptype.File{Syntax: pref.Proto2, Package: "foo.bar"}},
+ {inFile: &ptype.File{Syntax: pref.Proto2, Path: "foo/bar/baz/../test.proto", Package: "my.test"}},
+ },
+
+ rangePkgs: []testRangePkg{{
+ inPkg: "nothing",
+ }, {
+ inPkg: "",
+ wantFiles: []file{
+ {"", "foo.bar"},
+ {"", "foo.bar"},
+ {"foo/bar/test.proto", "foo.bar.baz"},
+ {"foo/bar/test.proto", "my.test"},
+ {"", "my.test.package"},
+ {"foo/bar/baz/../test.proto", "my.test"},
+ },
+ }, {
+ inPkg: ".",
+ }, {
+ inPkg: "foo",
+ wantFiles: []file{
+ {"", "foo.bar"},
+ {"", "foo.bar"},
+ {"foo/bar/test.proto", "foo.bar.baz"},
+ },
+ }, {
+ inPkg: "foo.",
+ }, {
+ inPkg: "foo..",
+ }, {
+ inPkg: "foo.bar.baz",
+ wantFiles: []file{
+ {"foo/bar/test.proto", "foo.bar.baz"},
+ },
+ }, {
+ inPkg: "fo",
+ }},
+
+ rangePaths: []testRangePath{{
+ inPath: "nothing",
+ }, {
+ inPath: "",
+ wantFiles: []file{
+ {"", "foo.bar"},
+ {"", "foo.bar"},
+ {"", "my.test.package"},
+ },
+ }, {
+ inPath: "foo/bar/test.proto",
+ wantFiles: []file{
+ {"foo/bar/test.proto", "foo.bar.baz"},
+ {"foo/bar/test.proto", "my.test"},
+ },
+ }},
+ }, {
+ // Test when new enum conflicts with existing package.
+ files: []testFile{{
+ inFile: &ptype.File{Syntax: pref.Proto2, Path: "test1a.proto", Package: "foo.bar.baz"},
+ }, {
+ inFile: &ptype.File{Syntax: pref.Proto2, Path: "test1b.proto", Enums: []ptype.Enum{{Name: "foo"}}},
+ wantErr: `file "test1b.proto" has a name conflict over foo`,
+ }},
+ }, {
+ // Test when new package conflicts with existing enum.
+ files: []testFile{{
+ inFile: &ptype.File{Syntax: pref.Proto2, Path: "test2a.proto", Enums: []ptype.Enum{{Name: "foo"}}},
+ }, {
+ inFile: &ptype.File{Syntax: pref.Proto2, Path: "test2b.proto", Package: "foo.bar.baz"},
+ wantErr: `file "test2b.proto" has a name conflict over foo`,
+ }},
+ }, {
+ // Test when new enum conflicts with existing enum in same package.
+ files: []testFile{{
+ inFile: &ptype.File{Syntax: pref.Proto2, Path: "test3a.proto", Package: "foo", Enums: []ptype.Enum{{Name: "BAR"}}},
+ }, {
+ inFile: &ptype.File{Syntax: pref.Proto2, Path: "test3b.proto", Package: "foo", Enums: []ptype.Enum{{Name: "BAR"}}},
+ wantErr: `file "test3b.proto" has a name conflict over foo.BAR`,
+ }},
+ }, {
+ files: []testFile{{
+ inFile: &ptype.File{
+ Syntax: pref.Proto2,
+ Package: "fizz.buzz",
+ Messages: []ptype.Message{{
+ Name: "Message",
+ Fields: []ptype.Field{{
+ Name: "Field",
+ Number: 1,
+ Cardinality: pref.Optional,
+ Kind: pref.StringKind,
+ OneofName: "Oneof",
+ }},
+ Oneofs: []ptype.Oneof{{Name: "Oneof"}},
+ ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}},
+ }},
+ Enums: []ptype.Enum{{
+ Name: "Enum",
+ Values: []ptype.EnumValue{{Name: "EnumValue", Number: 0}},
+ }},
+ Extensions: []ptype.Extension{{
+ Name: "Extension",
+ Number: 1000,
+ Cardinality: pref.Optional,
+ Kind: pref.StringKind,
+ ExtendedType: ptype.PlaceholderMessage("fizz.buzz.Message"),
+ }},
+ Services: []ptype.Service{{
+ Name: "Service",
+ Methods: []ptype.Method{{
+ Name: "Method",
+ InputType: ptype.PlaceholderMessage("fizz.buzz.Message"),
+ OutputType: ptype.PlaceholderMessage("fizz.buzz.Message"),
+ IsStreamingClient: true,
+ IsStreamingServer: true,
+ }},
+ }},
+ },
+ }, {
+ inFile: &ptype.File{
+ Syntax: pref.Proto2,
+ Package: "fizz.buzz.gazz",
+ Enums: []ptype.Enum{{
+ Name: "Enum",
+ Values: []ptype.EnumValue{{Name: "EnumValue", Number: 0}},
+ }},
+ },
+ }, {
+ // Conflict over a single declaration.
+ inFile: &ptype.File{
+ Syntax: pref.Proto2,
+ Package: "fizz.buzz",
+ Enums: []ptype.Enum{{
+ Name: "Enum1",
+ Values: []ptype.EnumValue{{Name: "EnumValue1", Number: 0}},
+ }, {
+ Name: "Enum2",
+ Values: []ptype.EnumValue{{Name: "EnumValue2", Number: 0}},
+ }, {
+ Name: "Enum3",
+ Values: []ptype.EnumValue{{Name: "Enum", Number: 0}}, // conflict
+ }},
+ },
+ wantErr: "name conflict over fizz.buzz.Enum",
+ }, {
+ // Previously failed registration should not pollute the namespace.
+ inFile: &ptype.File{
+ Syntax: pref.Proto2,
+ Package: "fizz.buzz",
+ Enums: []ptype.Enum{{
+ Name: "Enum1",
+ Values: []ptype.EnumValue{{Name: "EnumValue1", Number: 0}},
+ }, {
+ Name: "Enum2",
+ Values: []ptype.EnumValue{{Name: "EnumValue2", Number: 0}},
+ }},
+ },
+ }, {
+ // Make sure we can register without package name.
+ inFile: &ptype.File{
+ Syntax: pref.Proto2,
+ Messages: []ptype.Message{{
+ Name: "Message",
+ Messages: []ptype.Message{{
+ Name: "Message",
+ Messages: []ptype.Message{{
+ Name: "Message",
+ }},
+ }},
+ }},
+ },
+ }},
+
+ findDescs: []testFindDesc{
+ {"", false},
+ {"Enum", false},
+ {"Message", true},
+ {"Message.", false},
+ {"Message.Message", true},
+ {"Message.Message.Message", true},
+ {"Message.Message.Message.Message", false},
+ {"fizz.buzz", false},
+ {"fizz.buzz.Enum", true},
+ {"fizz.buzz.Enum1", true},
+ {"fizz.buzz.Enum1.EnumValue", false},
+ {"fizz.buzz.EnumValue", true},
+ {"fizz.buzz.Message", true},
+ {"fizz.buzz.Message.Field", true},
+ {"fizz.buzz.Message.Oneof", true},
+ {"fizz.buzz.Extension", true},
+ {"fizz.buzz.Service", true},
+ {"fizz.buzz.Service.Method", true},
+ {"fizz.buzz.Method", false},
+ },
+ }}
+
+ sortFiles := cmpopts.SortSlices(func(x, y file) bool {
+ return x.Path < y.Path || (x.Path == y.Path && x.Pkg < y.Pkg)
+ })
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ var files Files
+ for i, tc := range tt.files {
+ fd, err := ptype.NewFile(tc.inFile)
+ if err != nil {
+ t.Fatalf("file %d, prototype.NewFile() error: %v", i, err)
+ }
+ gotErr := files.Register(fd)
+ if (gotErr == nil && tc.wantErr != "") || !strings.Contains(fmt.Sprint(gotErr), tc.wantErr) {
+ t.Errorf("file %d, Register() = %v, want %v", i, gotErr, tc.wantErr)
+ }
+ }
+
+ for _, tc := range tt.findDescs {
+ got, err := files.FindDescriptorByName(tc.inName)
+ if (got == nil) == (err == nil) {
+ if tc.wantOk {
+ t.Errorf("FindDescriptorByName(%v) = (%v, %v), want (non-nil, nil)", tc.inName, got, err)
+ } else {
+ t.Errorf("FindDescriptorByName(%v) = (%v, %v), want (nil, NotFound)", tc.inName, got, err)
+ }
+ }
+
+ gotName := pref.FullName("<nil>")
+ if got != nil {
+ gotName = got.FullName()
+ }
+ wantName := pref.FullName("<nil>")
+ if tc.wantOk {
+ wantName = tc.inName
+ }
+ if gotName != wantName {
+ t.Errorf("FindDescriptorByName(%v) = %v, want %v", tc.inName, gotName, wantName)
+ }
+ }
+
+ for _, tc := range tt.rangePkgs {
+ var gotFiles []file
+ files.RangeFilesByPackage(tc.inPkg, func(fd pref.FileDescriptor) bool {
+ gotFiles = append(gotFiles, file{fd.Path(), fd.Package()})
+ return true
+ })
+ if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" {
+ t.Errorf("RangeFilesByPackage(%v) mismatch (-want +got):\n%v", tc.inPkg, diff)
+ }
+ }
+
+ for _, tc := range tt.rangePaths {
+ var gotFiles []file
+ files.RangeFilesByPath(tc.inPath, func(fd pref.FileDescriptor) bool {
+ gotFiles = append(gotFiles, file{fd.Path(), fd.Package()})
+ return true
+ })
+ if diff := cmp.Diff(tc.wantFiles, gotFiles, sortFiles); diff != "" {
+ t.Errorf("RangeFilesByPath(%v) mismatch (-want +got):\n%v", tc.inPath, diff)
+ }
+ }
+ })
+ }
+}
diff --git a/reflect/prototype/placeholder_type.go b/reflect/prototype/placeholder_type.go
index 1aed8de..387d75b 100644
--- a/reflect/prototype/placeholder_type.go
+++ b/reflect/prototype/placeholder_type.go
@@ -40,15 +40,16 @@
placeholderName
}
-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 }
-func (t placeholderFile) Messages() pref.MessageDescriptors { return &emptyMessages }
-func (t placeholderFile) Enums() pref.EnumDescriptors { return &emptyEnums }
-func (t placeholderFile) Extensions() pref.ExtensionDescriptors { return &emptyExtensions }
-func (t placeholderFile) Services() pref.ServiceDescriptors { return &emptyServices }
-func (t placeholderFile) Format(s fmt.State, r rune) { formatDesc(s, r, t) }
-func (t placeholderFile) ProtoType(pref.FileDescriptor) {}
+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 }
+func (t placeholderFile) Messages() pref.MessageDescriptors { return &emptyMessages }
+func (t placeholderFile) Enums() pref.EnumDescriptors { return &emptyEnums }
+func (t placeholderFile) Extensions() pref.ExtensionDescriptors { return &emptyExtensions }
+func (t placeholderFile) Services() pref.ServiceDescriptors { return &emptyServices }
+func (t placeholderFile) DescriptorByName(pref.FullName) pref.Descriptor { return nil }
+func (t placeholderFile) Format(s fmt.State, r rune) { formatDesc(s, r, t) }
+func (t placeholderFile) ProtoType(pref.FileDescriptor) {}
type placeholderMessage struct {
placeholderName
diff --git a/reflect/prototype/protofile_type.go b/reflect/prototype/protofile_type.go
index 2929232..e82de98 100644
--- a/reflect/prototype/protofile_type.go
+++ b/reflect/prototype/protofile_type.go
@@ -46,6 +46,7 @@
es enumsMeta
xs extensionsMeta
ss servicesMeta
+ ds descriptorsMeta
}
type fileDesc struct{ f *File }
@@ -70,10 +71,86 @@
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.
+type descriptorsMeta struct {
+ once sync.Once
+ m map[pref.FullName]pref.Descriptor
+}
+
+func (m *descriptorsMeta) lookup(fd pref.FileDescriptor, s pref.FullName) pref.Descriptor {
+ m.once.Do(func() {
+ m.m = make(map[pref.FullName]pref.Descriptor)
+ m.initMap(fd)
+ delete(m.m, fd.Package()) // avoid registering the file descriptor itself
+ })
+ return m.m[s]
+}
+func (m *descriptorsMeta) initMap(d pref.Descriptor) {
+ m.m[d.FullName()] = d
+ if ds, ok := d.(interface {
+ Enums() pref.EnumDescriptors
+ }); ok {
+ for i := 0; i < ds.Enums().Len(); i++ {
+ m.initMap(ds.Enums().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Values() pref.EnumValueDescriptors
+ }); ok {
+ for i := 0; i < ds.Values().Len(); i++ {
+ m.initMap(ds.Values().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Messages() pref.MessageDescriptors
+ }); ok {
+ for i := 0; i < ds.Messages().Len(); i++ {
+ m.initMap(ds.Messages().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Fields() pref.FieldDescriptors
+ }); ok {
+ for i := 0; i < ds.Fields().Len(); i++ {
+ m.initMap(ds.Fields().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Oneofs() pref.OneofDescriptors
+ }); ok {
+ for i := 0; i < ds.Oneofs().Len(); i++ {
+ m.initMap(ds.Oneofs().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Extensions() pref.ExtensionDescriptors
+ }); ok {
+ for i := 0; i < ds.Extensions().Len(); i++ {
+ m.initMap(ds.Extensions().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Services() pref.ServiceDescriptors
+ }); ok {
+ for i := 0; i < ds.Services().Len(); i++ {
+ m.initMap(ds.Services().Get(i))
+ }
+ }
+ if ds, ok := d.(interface {
+ Methods() pref.MethodDescriptors
+ }); ok {
+ for i := 0; i < ds.Methods().Len(); i++ {
+ m.initMap(ds.Methods().Get(i))
+ }
+ }
+}
+
type messageMeta struct {
inheritedMeta
diff --git a/reflect/prototype/type_test.go b/reflect/prototype/type_test.go
index 5ecfc1a..a5ef61d 100644
--- a/reflect/prototype/type_test.go
+++ b/reflect/prototype/type_test.go
@@ -84,14 +84,14 @@
}, {
Name: "B", // "test.B"
Fields: []Field{{
- Name: "field_one",
+ Name: "field_one", // "test.B.field_one"
Number: 1,
Cardinality: pref.Optional,
Kind: pref.StringKind,
Default: pref.ValueOf("hello"),
OneofName: "O1",
}, {
- Name: "field_two",
+ Name: "field_two", // "test.B.field_two"
JSONName: "Field2",
Number: 2,
Cardinality: pref.Optional,
@@ -100,32 +100,35 @@
EnumType: PlaceholderEnum("test.E1"),
OneofName: "O2",
}, {
- Name: "field_three",
+ Name: "field_three", // "test.B.field_three"
Number: 3,
Cardinality: pref.Optional,
Kind: pref.MessageKind,
MessageType: PlaceholderMessage("test.C"),
OneofName: "O2",
}, {
- Name: "field_four",
+ Name: "field_four", // "test.B.field_four"
JSONName: "Field4",
Number: 4,
Cardinality: pref.Repeated,
Kind: pref.MessageKind,
MessageType: PlaceholderMessage("test.A"),
}, {
- Name: "field_five",
+ Name: "field_five", // "test.B.field_five"
Number: 5,
Cardinality: pref.Repeated,
Kind: pref.Int32Kind,
IsPacked: true,
}, {
- Name: "field_six",
+ Name: "field_six", // "test.B.field_six"
Number: 6,
Cardinality: pref.Required,
Kind: pref.StringKind,
}},
- Oneofs: []Oneof{{Name: "O1"}, {Name: "O2"}},
+ Oneofs: []Oneof{
+ {Name: "O1"}, // "test.B.O1"
+ {Name: "O2"}, // "test.B.O2"
+ },
ExtensionRanges: [][2]pref.FieldNumber{{1000, 2000}},
}, {
Name: "C", // "test.C"
@@ -382,6 +385,33 @@
},
},
},
+ "DescriptorByName:": nil,
+ "DescriptorByName:A": nil,
+ "DescriptorByName:test": nil,
+ "DescriptorByName:test.": nil,
+ "DescriptorByName:test.A": M{"FullName": pref.FullName("test.A")},
+ "DescriptorByName:test.A.key": M{"FullName": pref.FullName("test.A.key")},
+ "DescriptorByName:test.A.A": nil,
+ "DescriptorByName:test.A.field_one": nil,
+ "DescriptorByName:test.B.field_one": M{"FullName": pref.FullName("test.B.field_one")},
+ "DescriptorByName:test.B.O1": M{"FullName": pref.FullName("test.B.O1")},
+ "DescriptorByName:test.B.O3": nil,
+ "DescriptorByName:test.C.E1": M{"FullName": pref.FullName("test.C.E1")},
+ "DescriptorByName:test.C.E1.FOO": nil,
+ "DescriptorByName:test.C.FOO": M{"FullName": pref.FullName("test.C.FOO")},
+ "DescriptorByName:test.C.Foo": nil,
+ "DescriptorByName:test.C.BAZ": nil,
+ "DescriptorByName:test.E1": M{"FullName": pref.FullName("test.E1")},
+ "DescriptorByName:test.E1.FOO": nil,
+ "DescriptorByName:test.FOO": M{"FullName": pref.FullName("test.FOO")},
+ "DescriptorByName:test.Foo": nil,
+ "DescriptorByName:test.BAZ": nil,
+ "DescriptorByName:test.C.X": M{"FullName": pref.FullName("test.C.X")},
+ "DescriptorByName:test.X": M{"FullName": pref.FullName("test.X")},
+ "DescriptorByName:test.X.": nil,
+ "DescriptorByName:test.S": M{"FullName": pref.FullName("test.S")},
+ "DescriptorByName:test.S.M": M{"FullName": pref.FullName("test.S.M")},
+ "DescriptorByName:test.M": nil,
}
// Concurrently explore the file tree to induce races.