reflect/protoreflect: improve source information usability

Added API:
  SourceLocations.ByPath
  SourceLocations.ByDescriptor
  SourceLocation.Next
  SourcePath.String
  SourcePath.Equal

We modify compiler/protogen to use SourceLocations.ByDescriptor.
In retrospect, if this had existed during the development of protogen,
we would not have exposed protogen.Location and related fields.

Change-Id: I58f17e59f90b9ba16f0982c4b71c2542e4ff6e75
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/238000
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2020-06-16 08:48:38 -07:00 committed by Joe Tsai
parent 1a290e9a0e
commit 42cc4c592f
10 changed files with 1089 additions and 80 deletions

View File

@ -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()
}
}

View File

@ -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

View File

@ -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") {

View File

@ -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 (

View File

@ -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()}
}

View File

@ -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?

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}
}