diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go index ea5d2e62..5f559766 100644 --- a/cmd/protoc-gen-go/internal_gengo/main.go +++ b/cmd/protoc-gen-go/internal_gengo/main.go @@ -115,17 +115,14 @@ func GenerateFile(gen *protogen.Plugin, file *protogen.File) *protogen.Generated // genStandaloneComments prints all leading comments for a FileDescriptorProto // location identified by the field number n. func genStandaloneComments(g *protogen.GeneratedFile, f *fileInfo, n int32) { - for _, loc := range f.Proto.GetSourceCodeInfo().GetLocation() { - if len(loc.Path) == 1 && loc.Path[0] == n { - for _, s := range loc.GetLeadingDetachedComments() { - g.P(protogen.Comments(s)) - g.P() - } - if s := loc.GetLeadingComments(); s != "" { - g.P(protogen.Comments(s)) - g.P() - } - } + loc := f.Desc.SourceLocations().ByPath(protoreflect.SourcePath{n}) + for _, s := range loc.LeadingDetachedComments { + g.P(protogen.Comments(s)) + g.P() + } + if s := loc.LeadingComments; s != "" { + g.P(protogen.Comments(s)) + g.P() } } diff --git a/compiler/protogen/protogen.go b/compiler/protogen/protogen.go index 3892d058..c380a03a 100644 --- a/compiler/protogen/protogen.go +++ b/compiler/protogen/protogen.go @@ -13,7 +13,6 @@ package protogen import ( "bufio" "bytes" - "encoding/binary" "fmt" "go/ast" "go/parser" @@ -482,7 +481,7 @@ type File struct { // of "dir/foo". Appending ".pb.go" produces an output file of "dir/foo.pb.go". GeneratedFilenamePrefix string - comments map[pathKey]CommentSet + location Location } func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) { @@ -498,7 +497,7 @@ func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPac Proto: p, GoPackageName: packageName, GoImportPath: importPath, - comments: make(map[pathKey]CommentSet), + location: Location{SourceFile: desc.Path()}, } // Determine the prefix for generated Go files. @@ -526,19 +525,6 @@ func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPac } f.GeneratedFilenamePrefix = prefix - for _, loc := range p.GetSourceCodeInfo().GetLocation() { - // Descriptors declarations are guaranteed to have unique comment sets. - // Other locations may not be unique, but we don't use them. - var leadingDetached []Comments - for _, s := range loc.GetLeadingDetachedComments() { - leadingDetached = append(leadingDetached, Comments(s)) - } - f.comments[newPathKey(loc.Path)] = CommentSet{ - LeadingDetached: leadingDetached, - Leading: Comments(loc.GetLeadingComments()), - Trailing: Comments(loc.GetTrailingComments()), - } - } for i, eds := 0, desc.Enums(); i < eds.Len(); i++ { f.Enums = append(f.Enums, newEnum(gen, f, nil, eds.Get(i))) } @@ -571,13 +557,6 @@ func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPac return f, nil } -func (f *File) location(idxPath ...int32) Location { - return Location{ - SourceFile: f.Desc.Path(), - Path: idxPath, - } -} - // goPackageOption interprets a file's go_package option. // If there is no go_package, it returns ("", ""). // If there's a simple name, it returns (pkg, ""). @@ -625,15 +604,15 @@ type Enum struct { func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum { var loc Location if parent != nil { - loc = parent.Location.appendPath(int32(genid.DescriptorProto_EnumType_field_number), int32(desc.Index())) + loc = parent.Location.appendPath(genid.DescriptorProto_EnumType_field_number, desc.Index()) } else { - loc = f.location(int32(genid.FileDescriptorProto_EnumType_field_number), int32(desc.Index())) + loc = f.location.appendPath(genid.FileDescriptorProto_EnumType_field_number, desc.Index()) } enum := &Enum{ Desc: desc, GoIdent: newGoIdent(f, desc), Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } gen.enumsByName[desc.FullName()] = enum for i, vds := 0, enum.Desc.Values(); i < vds.Len(); i++ { @@ -664,13 +643,13 @@ func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc proto parentIdent = message.GoIdent } name := parentIdent.GoName + "_" + string(desc.Name()) - loc := enum.Location.appendPath(int32(genid.EnumDescriptorProto_Value_field_number), int32(desc.Index())) + loc := enum.Location.appendPath(genid.EnumDescriptorProto_Value_field_number, desc.Index()) return &EnumValue{ Desc: desc, GoIdent: f.GoImportPath.Ident(name), Parent: enum, Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } } @@ -694,15 +673,15 @@ type Message struct { func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message { var loc Location if parent != nil { - loc = parent.Location.appendPath(int32(genid.DescriptorProto_NestedType_field_number), int32(desc.Index())) + loc = parent.Location.appendPath(genid.DescriptorProto_NestedType_field_number, desc.Index()) } else { - loc = f.location(int32(genid.FileDescriptorProto_MessageType_field_number), int32(desc.Index())) + loc = f.location.appendPath(genid.FileDescriptorProto_MessageType_field_number, desc.Index()) } message := &Message{ Desc: desc, GoIdent: newGoIdent(f, desc), Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } gen.messagesByName[desc.FullName()] = message for i, eds := 0, desc.Enums(); i < eds.Len(); i++ { @@ -852,11 +831,11 @@ func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDes var loc Location switch { case desc.IsExtension() && message == nil: - loc = f.location(int32(genid.FileDescriptorProto_Extension_field_number), int32(desc.Index())) + loc = f.location.appendPath(genid.FileDescriptorProto_Extension_field_number, desc.Index()) case desc.IsExtension() && message != nil: - loc = message.Location.appendPath(int32(genid.DescriptorProto_Extension_field_number), int32(desc.Index())) + loc = message.Location.appendPath(genid.DescriptorProto_Extension_field_number, desc.Index()) default: - loc = message.Location.appendPath(int32(genid.DescriptorProto_Field_field_number), int32(desc.Index())) + loc = message.Location.appendPath(genid.DescriptorProto_Field_field_number, desc.Index()) } camelCased := strs.GoCamelCase(string(desc.Name())) var parentPrefix string @@ -872,7 +851,7 @@ func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDes }, Parent: message, Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } return field } @@ -927,7 +906,7 @@ type Oneof struct { } func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof { - loc := message.Location.appendPath(int32(genid.DescriptorProto_OneofDecl_field_number), int32(desc.Index())) + loc := message.Location.appendPath(genid.DescriptorProto_OneofDecl_field_number, desc.Index()) camelCased := strs.GoCamelCase(string(desc.Name())) parentPrefix := message.GoIdent.GoName + "_" return &Oneof{ @@ -939,7 +918,7 @@ func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDes GoName: parentPrefix + camelCased, }, Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } } @@ -959,12 +938,12 @@ type Service struct { } func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service { - loc := f.location(int32(genid.FileDescriptorProto_Service_field_number), int32(desc.Index())) + loc := f.location.appendPath(genid.FileDescriptorProto_Service_field_number, desc.Index()) service := &Service{ Desc: desc, GoName: strs.GoCamelCase(string(desc.Name())), Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } for i, mds := 0, desc.Methods(); i < mds.Len(); i++ { service.Methods = append(service.Methods, newMethod(gen, f, service, mds.Get(i))) @@ -988,13 +967,13 @@ type Method struct { } func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method { - loc := service.Location.appendPath(int32(genid.ServiceDescriptorProto_Method_field_number), int32(desc.Index())) + loc := service.Location.appendPath(genid.ServiceDescriptorProto_Method_field_number, desc.Index()) method := &Method{ Desc: desc, GoName: strs.GoCamelCase(string(desc.Name())), Parent: service, Location: loc, - Comments: f.comments[newPathKey(loc.Path)], + Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), } return method } @@ -1359,28 +1338,10 @@ type Location struct { } // appendPath add elements to a Location's path, returning a new Location. -func (loc Location) appendPath(a ...int32) Location { - var n protoreflect.SourcePath - n = append(n, loc.Path...) - n = append(n, a...) - return Location{ - SourceFile: loc.SourceFile, - Path: n, - } -} - -// A pathKey is a representation of a location path suitable for use as a map key. -type pathKey struct { - s string -} - -// newPathKey converts a location path to a pathKey. -func newPathKey(idxPath []int32) pathKey { - buf := make([]byte, 4*len(idxPath)) - for i, x := range idxPath { - binary.LittleEndian.PutUint32(buf[i*4:], uint32(x)) - } - return pathKey{string(buf)} +func (loc Location) appendPath(num protoreflect.FieldNumber, idx int) Location { + loc.Path = append(protoreflect.SourcePath(nil), loc.Path...) // make copy + loc.Path = append(loc.Path, int32(num), int32(idx)) + return loc } // CommentSet is a set of leading and trailing comments associated @@ -1391,6 +1352,18 @@ type CommentSet struct { Trailing Comments } +func makeCommentSet(loc protoreflect.SourceLocation) CommentSet { + var leadingDetached []Comments + for _, s := range loc.LeadingDetachedComments { + leadingDetached = append(leadingDetached, Comments(s)) + } + return CommentSet{ + LeadingDetached: leadingDetached, + Leading: Comments(loc.LeadingComments), + Trailing: Comments(loc.TrailingComments), + } +} + // Comments is a comments string as provided by protoc. type Comments string diff --git a/internal/cmd/generate-protos/main.go b/internal/cmd/generate-protos/main.go index 48dda637..f8778d71 100644 --- a/internal/cmd/generate-protos/main.go +++ b/internal/cmd/generate-protos/main.go @@ -93,6 +93,7 @@ func init() { gengo.GenerateVersionMarkers = false gengo.GenerateFile(gen, file) generateIdentifiers(gen, file) + generateSouceContextStringer(gen, file) } } gen.SupportedFeatures = gengo.SupportedFeatures @@ -361,6 +362,66 @@ func generateIdentifiers(gen *protogen.Plugin, file *protogen.File) { processMessages(file.Messages) } +// generateSouceContextStringer generates the implementation for the +// protoreflect.SourcePath.String method by using information present +// in the descriptor.proto. +func generateSouceContextStringer(gen *protogen.Plugin, file *protogen.File) { + if file.Desc.Path() != "google/protobuf/descriptor.proto" { + return + } + + importPath := modulePath + "/reflect/protoreflect" + g := gen.NewGeneratedFile(importPath+"/source_gen.go", protogen.GoImportPath(importPath)) + for _, s := range generatedPreamble { + g.P(s) + } + g.P("package ", path.Base(importPath)) + g.P() + + var messages []*protogen.Message + for _, message := range file.Messages { + if message.Desc.Name() == "FileDescriptorProto" { + messages = append(messages, message) + } + } + seen := make(map[*protogen.Message]bool) + + for len(messages) > 0 { + m := messages[0] + messages = messages[1:] + if seen[m] { + continue + } + seen[m] = true + + g.P("func (p *SourcePath) append", m.GoIdent.GoName, "(b []byte) []byte {") + g.P("if len(*p) == 0 { return b }") + g.P("switch (*p)[0] {") + for _, f := range m.Fields { + g.P("case ", f.Desc.Number(), ":") + var cardinality string + switch { + case f.Desc.IsMap(): + panic("maps are not supported") + case f.Desc.IsList(): + cardinality = "Repeated" + default: + cardinality = "Singular" + } + nextAppender := "nil" + if f.Message != nil { + nextAppender = "(*SourcePath).append" + f.Message.GoIdent.GoName + messages = append(messages, f.Message) + } + g.P("b = p.append", cardinality, "Field(b, ", strconv.Quote(string(f.Desc.Name())), ", ", nextAppender, ")") + } + g.P("}") + g.P("return b") + g.P("}") + g.P() + } +} + func syncOutput(dstDir, srcDir string) { filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error { if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") { diff --git a/internal/filedesc/build.go b/internal/filedesc/build.go index d02d770c..b293b694 100644 --- a/internal/filedesc/build.go +++ b/internal/filedesc/build.go @@ -3,6 +3,9 @@ // license that can be found in the LICENSE file. // Package filedesc provides functionality for constructing descriptors. +// +// The types in this package implement interfaces in the protoreflect package +// related to protobuf descripriptors. package filedesc import ( diff --git a/internal/filedesc/desc_list.go b/internal/filedesc/desc_list.go index 4187d66d..aa294fff 100644 --- a/internal/filedesc/desc_list.go +++ b/internal/filedesc/desc_list.go @@ -6,9 +6,12 @@ package filedesc import ( "fmt" + "math" "sort" "sync" + "google.golang.org/protobuf/internal/genid" + "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/internal/descfmt" "google.golang.org/protobuf/internal/errors" @@ -278,9 +281,170 @@ func (p *OneofFields) lazyInit() *OneofFields { } type SourceLocations struct { + // List is a list of SourceLocations. + // The SourceLocation.Next field does not need to be populated + // as it will be lazily populated upon first need. List []pref.SourceLocation + + // File is the parent file descriptor that these locations are relative to. + // If non-nil, ByDescriptor verifies that the provided descriptor + // is a child of this file descriptor. + File pref.FileDescriptor + + once sync.Once + byPath map[pathKey]int } -func (p *SourceLocations) Len() int { return len(p.List) } -func (p *SourceLocations) Get(i int) pref.SourceLocation { return p.List[i] } +func (p *SourceLocations) Len() int { return len(p.List) } +func (p *SourceLocations) Get(i int) pref.SourceLocation { return p.lazyInit().List[i] } +func (p *SourceLocations) byKey(k pathKey) pref.SourceLocation { + if i, ok := p.lazyInit().byPath[k]; ok { + return p.List[i] + } + return pref.SourceLocation{} +} +func (p *SourceLocations) ByPath(path pref.SourcePath) pref.SourceLocation { + return p.byKey(newPathKey(path)) +} +func (p *SourceLocations) ByDescriptor(desc pref.Descriptor) pref.SourceLocation { + if p.File != nil && desc != nil && p.File != desc.ParentFile() { + return pref.SourceLocation{} // mismatching parent files + } + var pathArr [16]int32 + path := pathArr[:0] + for { + switch desc.(type) { + case pref.FileDescriptor: + // Reverse the path since it was constructed in reverse. + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + return p.byKey(newPathKey(path)) + case pref.MessageDescriptor: + path = append(path, int32(desc.Index())) + desc = desc.Parent() + switch desc.(type) { + case pref.FileDescriptor: + path = append(path, int32(genid.FileDescriptorProto_MessageType_field_number)) + case pref.MessageDescriptor: + path = append(path, int32(genid.DescriptorProto_NestedType_field_number)) + default: + return pref.SourceLocation{} + } + case pref.FieldDescriptor: + isExtension := desc.(pref.FieldDescriptor).IsExtension() + path = append(path, int32(desc.Index())) + desc = desc.Parent() + if isExtension { + switch desc.(type) { + case pref.FileDescriptor: + path = append(path, int32(genid.FileDescriptorProto_Extension_field_number)) + case pref.MessageDescriptor: + path = append(path, int32(genid.DescriptorProto_Extension_field_number)) + default: + return pref.SourceLocation{} + } + } else { + switch desc.(type) { + case pref.MessageDescriptor: + path = append(path, int32(genid.DescriptorProto_Field_field_number)) + default: + return pref.SourceLocation{} + } + } + case pref.OneofDescriptor: + path = append(path, int32(desc.Index())) + desc = desc.Parent() + switch desc.(type) { + case pref.MessageDescriptor: + path = append(path, int32(genid.DescriptorProto_OneofDecl_field_number)) + default: + return pref.SourceLocation{} + } + case pref.EnumDescriptor: + path = append(path, int32(desc.Index())) + desc = desc.Parent() + switch desc.(type) { + case pref.FileDescriptor: + path = append(path, int32(genid.FileDescriptorProto_EnumType_field_number)) + case pref.MessageDescriptor: + path = append(path, int32(genid.DescriptorProto_EnumType_field_number)) + default: + return pref.SourceLocation{} + } + case pref.EnumValueDescriptor: + path = append(path, int32(desc.Index())) + desc = desc.Parent() + switch desc.(type) { + case pref.EnumDescriptor: + path = append(path, int32(genid.EnumDescriptorProto_Value_field_number)) + default: + return pref.SourceLocation{} + } + case pref.ServiceDescriptor: + path = append(path, int32(desc.Index())) + desc = desc.Parent() + switch desc.(type) { + case pref.FileDescriptor: + path = append(path, int32(genid.FileDescriptorProto_Service_field_number)) + default: + return pref.SourceLocation{} + } + case pref.MethodDescriptor: + path = append(path, int32(desc.Index())) + desc = desc.Parent() + switch desc.(type) { + case pref.ServiceDescriptor: + path = append(path, int32(genid.ServiceDescriptorProto_Method_field_number)) + default: + return pref.SourceLocation{} + } + default: + return pref.SourceLocation{} + } + } +} +func (p *SourceLocations) lazyInit() *SourceLocations { + p.once.Do(func() { + if len(p.List) > 0 { + // Collect all the indexes for a given path. + pathIdxs := make(map[pathKey][]int, len(p.List)) + for i, l := range p.List { + k := newPathKey(l.Path) + pathIdxs[k] = append(pathIdxs[k], i) + } + + // Update the next index for all locations. + p.byPath = make(map[pathKey]int, len(p.List)) + for k, idxs := range pathIdxs { + for i := 0; i < len(idxs)-1; i++ { + p.List[idxs[i]].Next = idxs[i+1] + } + p.List[idxs[len(idxs)-1]].Next = 0 + p.byPath[k] = idxs[0] // record the first location for this path + } + } + }) + return p +} func (p *SourceLocations) ProtoInternal(pragma.DoNotImplement) {} + +// pathKey is a comparable representation of protoreflect.SourcePath. +type pathKey struct { + arr [16]uint8 // first n-1 path segments; last element is the length + str string // used if the path does not fit in arr +} + +func newPathKey(p pref.SourcePath) (k pathKey) { + if len(p) < len(k.arr) { + for i, ps := range p { + if ps < 0 || math.MaxUint8 <= ps { + return pathKey{str: p.String()} + } + k.arr[i] = uint8(ps) + } + k.arr[len(k.arr)-1] = uint8(len(p)) + return k + } + return pathKey{str: p.String()} +} diff --git a/reflect/protodesc/desc.go b/reflect/protodesc/desc.go index 37f254d4..e4dfb120 100644 --- a/reflect/protodesc/desc.go +++ b/reflect/protodesc/desc.go @@ -144,6 +144,7 @@ func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (prot } // Handle source locations. + f.L2.Locations.File = f for _, loc := range fd.GetSourceCodeInfo().GetLocation() { var l protoreflect.SourceLocation // TODO: Validate that the path points to an actual declaration? diff --git a/reflect/protodesc/file_test.go b/reflect/protodesc/file_test.go index 9b7e8dd4..50a0ed95 100644 --- a/reflect/protodesc/file_test.go +++ b/reflect/protodesc/file_test.go @@ -994,3 +994,241 @@ func TestNewFilesImportCycle(t *testing.T) { t.Fatal("NewFiles with import cycle: success, want error") } } + +func TestSourceLocations(t *testing.T) { + fd := mustParseFile(` + name: "comments.proto" + message_type: [{ + name: "Message1" + field: [ + {name:"field1" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, + {name:"field2" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}, + {name:"field3" number:3 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}, + {name:"field4" number:4 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0}, + {name:"field5" number:5 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:1}, + {name:"field6" number:6 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:1} + ] + extension: [ + {name:"extension1" number:100 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"}, + {name:"extension2" number:101 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"} + ] + nested_type: [{name:"Message1"}, {name:"Message2"}] + extension_range: {start:100 end:536870912} + oneof_decl: [{name:"oneof1"}, {name:"oneof2"}] + }, { + name: "Message2" + enum_type: { + name: "Enum1" + value: [ + {name: "FOO", number: 0}, + {name: "BAR", number: 1} + ] + } + }] + enum_type: { + name: "Enum1" + value: [ + {name: "FOO", number: 0}, + {name: "BAR", number: 1} + ] + } + service: { + name: "Service1" + method: [ + {name:"Method1" input_type:".Message1" output_type:".Message1"}, + {name:"Method2" input_type:".Message2" output_type:".Message2"} + ] + } + extension: [ + {name:"extension1" number:102 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"}, + {name:"extension2" number:103 label:LABEL_OPTIONAL type:TYPE_STRING extendee:".Message1"} + ] + source_code_info: { + location: [ + {span:[0,0,69,1]}, + {path:[12] span:[0,0,18]}, + {path:[5,0] span:[3,0,8,1] leading_comments:" Enum1\r\n"}, + {path:[5,0,1] span:[3,5,10]}, + {path:[5,0,2,0] span:[5,2,10] leading_comments:" FOO\r\n"}, + {path:[5,0,2,0,1] span:[5,2,5]}, + {path:[5,0,2,0,2] span:[5,8,9]}, + {path:[5,0,2,1] span:[7,2,10] leading_comments:" BAR\r\n"}, + {path:[5,0,2,1,1] span:[7,2,5]}, + {path:[5,0,2,1,2] span:[7,8,9]}, + {path:[4,0] span:[11,0,43,1] leading_comments:" Message1\r\n"}, + {path:[4,0,1] span:[11,8,16]}, + {path:[4,0,3,0] span:[13,2,21] leading_comments:" Message1.Message1\r\n"}, + {path:[4,0,3,0,1] span:[13,10,18]}, + {path:[4,0,3,1] span:[15,2,21] leading_comments:" Message1.Message2\r\n"}, + {path:[4,0,3,1,1] span:[15,10,18]}, + {path:[4,0,2,0] span:[18,2,29] leading_comments:" Message1.field1\r\n"}, + {path:[4,0,2,0,4] span:[18,2,10]}, + {path:[4,0,2,0,5] span:[18,11,17]}, + {path:[4,0,2,0,1] span:[18,18,24]}, + {path:[4,0,2,0,3] span:[18,27,28]}, + {path:[4,0,2,1] span:[20,2,29] leading_comments:" Message1.field2\r\n"}, + {path:[4,0,2,1,4] span:[20,2,10]}, + {path:[4,0,2,1,5] span:[20,11,17]}, + {path:[4,0,2,1,1] span:[20,18,24]}, + {path:[4,0,2,1,3] span:[20,27,28]}, + {path:[4,0,8,0] span:[22,2,27,3] leading_comments:" Message1.oneof1\r\n"}, + {path:[4,0,8,0,1] span:[22,8,14]}, + {path:[4,0,2,2] span:[24,4,22] leading_comments:" Message1.field3\r\n"}, + {path:[4,0,2,2,5] span:[24,4,10]}, + {path:[4,0,2,2,1] span:[24,11,17]}, + {path:[4,0,2,2,3] span:[24,20,21]}, + {path:[4,0,2,3] span:[26,4,22] leading_comments:" Message1.field4\r\n"}, + {path:[4,0,2,3,5] span:[26,4,10]}, + {path:[4,0,2,3,1] span:[26,11,17]}, + {path:[4,0,2,3,3] span:[26,20,21]}, + {path:[4,0,8,1] span:[29,2,34,3] leading_comments:" Message1.oneof2\r\n"}, + {path:[4,0,8,1,1] span:[29,8,14]}, + {path:[4,0,2,4] span:[31,4,22] leading_comments:" Message1.field5\r\n"}, + {path:[4,0,2,4,5] span:[31,4,10]}, + {path:[4,0,2,4,1] span:[31,11,17]}, + {path:[4,0,2,4,3] span:[31,20,21]}, + {path:[4,0,2,5] span:[33,4,22] leading_comments:" Message1.field6\r\n"}, + {path:[4,0,2,5,5] span:[33,4,10]}, + {path:[4,0,2,5,1] span:[33,11,17]}, + {path:[4,0,2,5,3] span:[33,20,21]}, + {path:[4,0,5] span:[36,2,24]}, + {path:[4,0,5,0] span:[36,13,23]}, + {path:[4,0,5,0,1] span:[36,13,16]}, + {path:[4,0,5,0,2] span:[36,20,23]}, + {path:[4,0,6] span:[37,2,42,3]}, + {path:[4,0,6,0] span:[39,4,37] leading_comments:" Message1.extension1\r\n"}, + {path:[4,0,6,0,2] span:[37,9,18]}, + {path:[4,0,6,0,4] span:[39,4,12]}, + {path:[4,0,6,0,5] span:[39,13,19]}, + {path:[4,0,6,0,1] span:[39,20,30]}, + {path:[4,0,6,0,3] span:[39,33,36]}, + {path:[4,0,6,1] span:[41,4,37] leading_comments:" Message1.extension2\r\n"}, + {path:[4,0,6,1,2] span:[37,9,18]}, + {path:[4,0,6,1,4] span:[41,4,12]}, + {path:[4,0,6,1,5] span:[41,13,19]}, + {path:[4,0,6,1,1] span:[41,20,30]}, + {path:[4,0,6,1,3] span:[41,33,36]}, + {path:[7] span:[45,0,50,1]}, + {path:[7,0] span:[47,2,35] leading_comments:" extension1\r\n"}, + {path:[7,0,2] span:[45,7,15]}, + {path:[7,0,4] span:[47,2,10]}, + {path:[7,0,5] span:[47,11,17]}, + {path:[7,0,1] span:[47,18,28]}, + {path:[7,0,3] span:[47,31,34]}, + {path:[7,1] span:[49,2,35] leading_comments:" extension2\r\n"}, + {path:[7,1,2] span:[45,7,15]}, + {path:[7,1,4] span:[49,2,10]}, + {path:[7,1,5] span:[49,11,17]}, + {path:[7,1,1] span:[49,18,28]}, + {path:[7,1,3] span:[49,31,34]}, + {path:[4,1] span:[53,0,61,1] leading_comments:" Message2\r\n"}, + {path:[4,1,1] span:[53,8,16]}, + {path:[4,1,4,0] span:[55,2,60,3] leading_comments:" Message2.Enum1\r\n"}, + {path:[4,1,4,0,1] span:[55,7,12]}, + {path:[4,1,4,0,2,0] span:[57,4,12] leading_comments:" Message2.FOO\r\n"}, + {path:[4,1,4,0,2,0,1] span:[57,4,7]}, + {path:[4,1,4,0,2,0,2] span:[57,10,11]}, + {path:[4,1,4,0,2,1] span:[59,4,12] leading_comments:" Message2.BAR\r\n"}, + {path:[4,1,4,0,2,1,1] span:[59,4,7]}, + {path:[4,1,4,0,2,1,2] span:[59,10,11]}, + {path:[6,0] span:[64,0,69,1] leading_comments:" Service1\r\n"}, + {path:[6,0,1] span:[64,8,16]}, + {path:[6,0,2,0] span:[66,2,43] leading_comments:" Service1.Method1\r\n"}, + {path:[6,0,2,0,1] span:[66,6,13]}, + {path:[6,0,2,0,2] span:[66,14,22]}, + {path:[6,0,2,0,3] span:[66,33,41]}, + {path:[6,0,2,1] span:[68,2,43] leading_comments:" Service1.Method2\r\n"}, + {path:[6,0,2,1,1] span:[68,6,13]}, + {path:[6,0,2,1,2] span:[68,14,22]}, + {path:[6,0,2,1,3] span:[68,33,41]} + ] + } + `) + fileDesc, err := NewFile(fd, nil) + if err != nil { + t.Fatalf("NewFile error: %v", err) + } + + var walkDescs func(protoreflect.Descriptor, func(protoreflect.Descriptor)) + walkDescs = func(d protoreflect.Descriptor, f func(protoreflect.Descriptor)) { + f(d) + if d, ok := d.(interface { + Enums() protoreflect.EnumDescriptors + }); ok { + eds := d.Enums() + for i := 0; i < eds.Len(); i++ { + walkDescs(eds.Get(i), f) + } + } + if d, ok := d.(interface { + Values() protoreflect.EnumValueDescriptors + }); ok { + vds := d.Values() + for i := 0; i < vds.Len(); i++ { + walkDescs(vds.Get(i), f) + } + } + if d, ok := d.(interface { + Messages() protoreflect.MessageDescriptors + }); ok { + mds := d.Messages() + for i := 0; i < mds.Len(); i++ { + walkDescs(mds.Get(i), f) + } + } + if d, ok := d.(interface { + Fields() protoreflect.FieldDescriptors + }); ok { + fds := d.Fields() + for i := 0; i < fds.Len(); i++ { + walkDescs(fds.Get(i), f) + } + } + if d, ok := d.(interface { + Oneofs() protoreflect.OneofDescriptors + }); ok { + ods := d.Oneofs() + for i := 0; i < ods.Len(); i++ { + walkDescs(ods.Get(i), f) + } + } + if d, ok := d.(interface { + Extensions() protoreflect.ExtensionDescriptors + }); ok { + xds := d.Extensions() + for i := 0; i < xds.Len(); i++ { + walkDescs(xds.Get(i), f) + } + } + if d, ok := d.(interface { + Services() protoreflect.ServiceDescriptors + }); ok { + sds := d.Services() + for i := 0; i < sds.Len(); i++ { + walkDescs(sds.Get(i), f) + } + } + if d, ok := d.(interface { + Methods() protoreflect.MethodDescriptors + }); ok { + mds := d.Methods() + for i := 0; i < mds.Len(); i++ { + walkDescs(mds.Get(i), f) + } + } + } + + var numDescs int + walkDescs(fileDesc, func(d protoreflect.Descriptor) { + // The comment for every descriptor should be the full name itself. + got := strings.TrimSpace(fileDesc.SourceLocations().ByDescriptor(d).LeadingComments) + want := string(d.FullName()) + if got != want { + t.Errorf("comment mismatch: got %v, want %v", got, want) + } + numDescs++ + }) + if numDescs != 30 { + t.Errorf("visited %d descriptor, expected 30", numDescs) + } +} diff --git a/reflect/protoreflect/source.go b/reflect/protoreflect/source.go index 32ea3d98..121ba3a0 100644 --- a/reflect/protoreflect/source.go +++ b/reflect/protoreflect/source.go @@ -4,6 +4,10 @@ package protoreflect +import ( + "strconv" +) + // SourceLocations is a list of source locations. type SourceLocations interface { // Len reports the number of source locations in the proto file. @@ -11,9 +15,20 @@ type SourceLocations interface { // Get returns the ith SourceLocation. It panics if out of bounds. Get(int) SourceLocation - doNotImplement + // ByPath returns the SourceLocation for the given path, + // returning the first location if multiple exist for the same path. + // If multiple locations exist for the same path, + // then SourceLocation.Next index can be used to identify the + // index of the next SourceLocation. + // If no location exists for this path, it returns the zero value. + ByPath(path SourcePath) SourceLocation - // TODO: Add ByPath and ByDescriptor helper methods. + // ByDescriptor returns the SourceLocation for the given descriptor, + // returning the first location if multiple exist for the same path. + // If no location exists for this descriptor, it returns the zero value. + ByDescriptor(desc Descriptor) SourceLocation + + doNotImplement } // SourceLocation describes a source location and @@ -39,6 +54,10 @@ type SourceLocation struct { LeadingComments string // TrailingComments is the trailing attached comment for the declaration. TrailingComments string + + // Next is an index into SourceLocations for the next source location that + // has the same Path. It is zero if there is no next location. + Next int } // SourcePath identifies part of a file descriptor for a source location. @@ -48,5 +67,62 @@ type SourceLocation struct { // See google.protobuf.SourceCodeInfo.Location.path. type SourcePath []int32 -// TODO: Add SourcePath.String method to pretty-print the path. For example: -// ".message_type[6].nested_type[15].field[3]" +// Equal reports whether p1 equals p2. +func (p1 SourcePath) Equal(p2 SourcePath) bool { + if len(p1) != len(p2) { + return false + } + for i := range p1 { + if p1[i] != p2[i] { + return false + } + } + return true +} + +// String formats the path in a humanly readable manner. +// The output is guaranteed to be deterministic, +// making it suitable for use as a key into a Go map. +// It is not guaranteed to be stable as the exact output could change +// in a future version of this module. +// +// Example output: +// .message_type[6].nested_type[15].field[3] +func (p SourcePath) String() string { + b := p.appendFileDescriptorProto(nil) + for _, i := range p { + b = append(b, '.') + b = strconv.AppendInt(b, int64(i), 10) + } + return string(b) +} + +type appendFunc func(*SourcePath, []byte) []byte + +func (p *SourcePath) appendSingularField(b []byte, name string, f appendFunc) []byte { + if len(*p) == 0 { + return b + } + b = append(b, '.') + b = append(b, name...) + *p = (*p)[1:] + if f != nil { + b = f(p, b) + } + return b +} + +func (p *SourcePath) appendRepeatedField(b []byte, name string, f appendFunc) []byte { + b = p.appendSingularField(b, name, nil) + if len(*p) == 0 || (*p)[0] < 0 { + return b + } + b = append(b, '[') + b = strconv.AppendUint(b, uint64((*p)[0]), 10) + b = append(b, ']') + *p = (*p)[1:] + if f != nil { + b = f(p, b) + } + return b +} diff --git a/reflect/protoreflect/source_gen.go b/reflect/protoreflect/source_gen.go new file mode 100644 index 00000000..b03c1223 --- /dev/null +++ b/reflect/protoreflect/source_gen.go @@ -0,0 +1,461 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by generate-protos. DO NOT EDIT. + +package protoreflect + +func (p *SourcePath) appendFileDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendSingularField(b, "package", nil) + case 3: + b = p.appendRepeatedField(b, "dependency", nil) + case 10: + b = p.appendRepeatedField(b, "public_dependency", nil) + case 11: + b = p.appendRepeatedField(b, "weak_dependency", nil) + case 4: + b = p.appendRepeatedField(b, "message_type", (*SourcePath).appendDescriptorProto) + case 5: + b = p.appendRepeatedField(b, "enum_type", (*SourcePath).appendEnumDescriptorProto) + case 6: + b = p.appendRepeatedField(b, "service", (*SourcePath).appendServiceDescriptorProto) + case 7: + b = p.appendRepeatedField(b, "extension", (*SourcePath).appendFieldDescriptorProto) + case 8: + b = p.appendSingularField(b, "options", (*SourcePath).appendFileOptions) + case 9: + b = p.appendSingularField(b, "source_code_info", (*SourcePath).appendSourceCodeInfo) + case 12: + b = p.appendSingularField(b, "syntax", nil) + } + return b +} + +func (p *SourcePath) appendDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendRepeatedField(b, "field", (*SourcePath).appendFieldDescriptorProto) + case 6: + b = p.appendRepeatedField(b, "extension", (*SourcePath).appendFieldDescriptorProto) + case 3: + b = p.appendRepeatedField(b, "nested_type", (*SourcePath).appendDescriptorProto) + case 4: + b = p.appendRepeatedField(b, "enum_type", (*SourcePath).appendEnumDescriptorProto) + case 5: + b = p.appendRepeatedField(b, "extension_range", (*SourcePath).appendDescriptorProto_ExtensionRange) + case 8: + b = p.appendRepeatedField(b, "oneof_decl", (*SourcePath).appendOneofDescriptorProto) + case 7: + b = p.appendSingularField(b, "options", (*SourcePath).appendMessageOptions) + case 9: + b = p.appendRepeatedField(b, "reserved_range", (*SourcePath).appendDescriptorProto_ReservedRange) + case 10: + b = p.appendRepeatedField(b, "reserved_name", nil) + } + return b +} + +func (p *SourcePath) appendEnumDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendRepeatedField(b, "value", (*SourcePath).appendEnumValueDescriptorProto) + case 3: + b = p.appendSingularField(b, "options", (*SourcePath).appendEnumOptions) + case 4: + b = p.appendRepeatedField(b, "reserved_range", (*SourcePath).appendEnumDescriptorProto_EnumReservedRange) + case 5: + b = p.appendRepeatedField(b, "reserved_name", nil) + } + return b +} + +func (p *SourcePath) appendServiceDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendRepeatedField(b, "method", (*SourcePath).appendMethodDescriptorProto) + case 3: + b = p.appendSingularField(b, "options", (*SourcePath).appendServiceOptions) + } + return b +} + +func (p *SourcePath) appendFieldDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 3: + b = p.appendSingularField(b, "number", nil) + case 4: + b = p.appendSingularField(b, "label", nil) + case 5: + b = p.appendSingularField(b, "type", nil) + case 6: + b = p.appendSingularField(b, "type_name", nil) + case 2: + b = p.appendSingularField(b, "extendee", nil) + case 7: + b = p.appendSingularField(b, "default_value", nil) + case 9: + b = p.appendSingularField(b, "oneof_index", nil) + case 10: + b = p.appendSingularField(b, "json_name", nil) + case 8: + b = p.appendSingularField(b, "options", (*SourcePath).appendFieldOptions) + case 17: + b = p.appendSingularField(b, "proto3_optional", nil) + } + return b +} + +func (p *SourcePath) appendFileOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "java_package", nil) + case 8: + b = p.appendSingularField(b, "java_outer_classname", nil) + case 10: + b = p.appendSingularField(b, "java_multiple_files", nil) + case 20: + b = p.appendSingularField(b, "java_generate_equals_and_hash", nil) + case 27: + b = p.appendSingularField(b, "java_string_check_utf8", nil) + case 9: + b = p.appendSingularField(b, "optimize_for", nil) + case 11: + b = p.appendSingularField(b, "go_package", nil) + case 16: + b = p.appendSingularField(b, "cc_generic_services", nil) + case 17: + b = p.appendSingularField(b, "java_generic_services", nil) + case 18: + b = p.appendSingularField(b, "py_generic_services", nil) + case 42: + b = p.appendSingularField(b, "php_generic_services", nil) + case 23: + b = p.appendSingularField(b, "deprecated", nil) + case 31: + b = p.appendSingularField(b, "cc_enable_arenas", nil) + case 36: + b = p.appendSingularField(b, "objc_class_prefix", nil) + case 37: + b = p.appendSingularField(b, "csharp_namespace", nil) + case 39: + b = p.appendSingularField(b, "swift_prefix", nil) + case 40: + b = p.appendSingularField(b, "php_class_prefix", nil) + case 41: + b = p.appendSingularField(b, "php_namespace", nil) + case 44: + b = p.appendSingularField(b, "php_metadata_namespace", nil) + case 45: + b = p.appendSingularField(b, "ruby_package", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendSourceCodeInfo(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendRepeatedField(b, "location", (*SourcePath).appendSourceCodeInfo_Location) + } + return b +} + +func (p *SourcePath) appendDescriptorProto_ExtensionRange(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "start", nil) + case 2: + b = p.appendSingularField(b, "end", nil) + case 3: + b = p.appendSingularField(b, "options", (*SourcePath).appendExtensionRangeOptions) + } + return b +} + +func (p *SourcePath) appendOneofDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendSingularField(b, "options", (*SourcePath).appendOneofOptions) + } + return b +} + +func (p *SourcePath) appendMessageOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "message_set_wire_format", nil) + case 2: + b = p.appendSingularField(b, "no_standard_descriptor_accessor", nil) + case 3: + b = p.appendSingularField(b, "deprecated", nil) + case 7: + b = p.appendSingularField(b, "map_entry", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendDescriptorProto_ReservedRange(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "start", nil) + case 2: + b = p.appendSingularField(b, "end", nil) + } + return b +} + +func (p *SourcePath) appendEnumValueDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendSingularField(b, "number", nil) + case 3: + b = p.appendSingularField(b, "options", (*SourcePath).appendEnumValueOptions) + } + return b +} + +func (p *SourcePath) appendEnumOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 2: + b = p.appendSingularField(b, "allow_alias", nil) + case 3: + b = p.appendSingularField(b, "deprecated", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendEnumDescriptorProto_EnumReservedRange(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "start", nil) + case 2: + b = p.appendSingularField(b, "end", nil) + } + return b +} + +func (p *SourcePath) appendMethodDescriptorProto(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name", nil) + case 2: + b = p.appendSingularField(b, "input_type", nil) + case 3: + b = p.appendSingularField(b, "output_type", nil) + case 4: + b = p.appendSingularField(b, "options", (*SourcePath).appendMethodOptions) + case 5: + b = p.appendSingularField(b, "client_streaming", nil) + case 6: + b = p.appendSingularField(b, "server_streaming", nil) + } + return b +} + +func (p *SourcePath) appendServiceOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 33: + b = p.appendSingularField(b, "deprecated", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendFieldOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "ctype", nil) + case 2: + b = p.appendSingularField(b, "packed", nil) + case 6: + b = p.appendSingularField(b, "jstype", nil) + case 5: + b = p.appendSingularField(b, "lazy", nil) + case 3: + b = p.appendSingularField(b, "deprecated", nil) + case 10: + b = p.appendSingularField(b, "weak", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendUninterpretedOption(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 2: + b = p.appendRepeatedField(b, "name", (*SourcePath).appendUninterpretedOption_NamePart) + case 3: + b = p.appendSingularField(b, "identifier_value", nil) + case 4: + b = p.appendSingularField(b, "positive_int_value", nil) + case 5: + b = p.appendSingularField(b, "negative_int_value", nil) + case 6: + b = p.appendSingularField(b, "double_value", nil) + case 7: + b = p.appendSingularField(b, "string_value", nil) + case 8: + b = p.appendSingularField(b, "aggregate_value", nil) + } + return b +} + +func (p *SourcePath) appendSourceCodeInfo_Location(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendRepeatedField(b, "path", nil) + case 2: + b = p.appendRepeatedField(b, "span", nil) + case 3: + b = p.appendSingularField(b, "leading_comments", nil) + case 4: + b = p.appendSingularField(b, "trailing_comments", nil) + case 6: + b = p.appendRepeatedField(b, "leading_detached_comments", nil) + } + return b +} + +func (p *SourcePath) appendExtensionRangeOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendOneofOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendEnumValueOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "deprecated", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendMethodOptions(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 33: + b = p.appendSingularField(b, "deprecated", nil) + case 34: + b = p.appendSingularField(b, "idempotency_level", nil) + case 999: + b = p.appendRepeatedField(b, "uninterpreted_option", (*SourcePath).appendUninterpretedOption) + } + return b +} + +func (p *SourcePath) appendUninterpretedOption_NamePart(b []byte) []byte { + if len(*p) == 0 { + return b + } + switch (*p)[0] { + case 1: + b = p.appendSingularField(b, "name_part", nil) + case 2: + b = p.appendSingularField(b, "is_extension", nil) + } + return b +} diff --git a/reflect/protoreflect/source_test.go b/reflect/protoreflect/source_test.go new file mode 100644 index 00000000..877ede59 --- /dev/null +++ b/reflect/protoreflect/source_test.go @@ -0,0 +1,35 @@ +// Copyright 2020 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 protoreflect + +import "testing" + +func TestSourcePathString(t *testing.T) { + tests := []struct { + in SourcePath + want string + }{ + {nil, ""}, + {SourcePath{}, ""}, + {SourcePath{0}, ".0"}, + {SourcePath{1}, ".name"}, + {SourcePath{1, 1}, ".name.1"}, + {SourcePath{1, 1, -2, 3}, ".name.1.-2.3"}, + {SourcePath{3}, ".dependency"}, + {SourcePath{3, 0}, ".dependency[0]"}, + {SourcePath{3, -1}, ".dependency.-1"}, + {SourcePath{3, 1, 2}, ".dependency[1].2"}, + {SourcePath{4}, ".message_type"}, + {SourcePath{4, 0}, ".message_type[0]"}, + {SourcePath{4, -1}, ".message_type.-1"}, + {SourcePath{4, 1, 0}, ".message_type[1].0"}, + {SourcePath{4, 1, 1}, ".message_type[1].name"}, + } + for _, tt := range tests { + if got := tt.in.String(); got != tt.want { + t.Errorf("SourcePath(%d).String() = %v, want %v", tt.in, got, tt.want) + } + } +}