1// Copyright 2018 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package protogen provides support for writing protoc plugins. 6// 7// Plugins for protoc, the Protocol Buffer compiler, 8// are programs which read a CodeGeneratorRequest message from standard input 9// and write a CodeGeneratorResponse message to standard output. 10// This package provides support for writing plugins which generate Go code. 11package protogen 12 13import ( 14 "bufio" 15 "bytes" 16 "fmt" 17 "go/ast" 18 "go/parser" 19 "go/printer" 20 "go/token" 21 "go/types" 22 "io/ioutil" 23 "os" 24 "path" 25 "path/filepath" 26 "sort" 27 "strconv" 28 "strings" 29 30 "google.golang.org/protobuf/encoding/prototext" 31 "google.golang.org/protobuf/internal/genid" 32 "google.golang.org/protobuf/internal/strs" 33 "google.golang.org/protobuf/proto" 34 "google.golang.org/protobuf/reflect/protodesc" 35 "google.golang.org/protobuf/reflect/protoreflect" 36 "google.golang.org/protobuf/reflect/protoregistry" 37 38 "google.golang.org/protobuf/types/descriptorpb" 39 "google.golang.org/protobuf/types/dynamicpb" 40 "google.golang.org/protobuf/types/pluginpb" 41) 42 43const goPackageDocURL = "https://protobuf.dev/reference/go/go-generated#package" 44 45// Run executes a function as a protoc plugin. 46// 47// It reads a CodeGeneratorRequest message from os.Stdin, invokes the plugin 48// function, and writes a CodeGeneratorResponse message to os.Stdout. 49// 50// If a failure occurs while reading or writing, Run prints an error to 51// os.Stderr and calls os.Exit(1). 52func (opts Options) Run(f func(*Plugin) error) { 53 if err := run(opts, f); err != nil { 54 fmt.Fprintf(os.Stderr, "%s: %v\n", filepath.Base(os.Args[0]), err) 55 os.Exit(1) 56 } 57} 58 59func run(opts Options, f func(*Plugin) error) error { 60 if len(os.Args) > 1 { 61 return fmt.Errorf("unknown argument %q (this program should be run by protoc, not directly)", os.Args[1]) 62 } 63 in, err := ioutil.ReadAll(os.Stdin) 64 if err != nil { 65 return err 66 } 67 req := &pluginpb.CodeGeneratorRequest{} 68 if err := proto.Unmarshal(in, req); err != nil { 69 return err 70 } 71 gen, err := opts.New(req) 72 if err != nil { 73 return err 74 } 75 if err := f(gen); err != nil { 76 // Errors from the plugin function are reported by setting the 77 // error field in the CodeGeneratorResponse. 78 // 79 // In contrast, errors that indicate a problem in protoc 80 // itself (unparsable input, I/O errors, etc.) are reported 81 // to stderr. 82 gen.Error(err) 83 } 84 resp := gen.Response() 85 out, err := proto.Marshal(resp) 86 if err != nil { 87 return err 88 } 89 if _, err := os.Stdout.Write(out); err != nil { 90 return err 91 } 92 return nil 93} 94 95// A Plugin is a protoc plugin invocation. 96type Plugin struct { 97 // Request is the CodeGeneratorRequest provided by protoc. 98 Request *pluginpb.CodeGeneratorRequest 99 100 // Files is the set of files to generate and everything they import. 101 // Files appear in topological order, so each file appears before any 102 // file that imports it. 103 Files []*File 104 FilesByPath map[string]*File 105 106 // SupportedFeatures is the set of protobuf language features supported by 107 // this generator plugin. See the documentation for 108 // google.protobuf.CodeGeneratorResponse.supported_features for details. 109 SupportedFeatures uint64 110 111 fileReg *protoregistry.Files 112 enumsByName map[protoreflect.FullName]*Enum 113 messagesByName map[protoreflect.FullName]*Message 114 annotateCode bool 115 pathType pathType 116 module string 117 genFiles []*GeneratedFile 118 opts Options 119 err error 120} 121 122type Options struct { 123 // If ParamFunc is non-nil, it will be called with each unknown 124 // generator parameter. 125 // 126 // Plugins for protoc can accept parameters from the command line, 127 // passed in the --<lang>_out protoc, separated from the output 128 // directory with a colon; e.g., 129 // 130 // --go_out=<param1>=<value1>,<param2>=<value2>:<output_directory> 131 // 132 // Parameters passed in this fashion as a comma-separated list of 133 // key=value pairs will be passed to the ParamFunc. 134 // 135 // The (flag.FlagSet).Set method matches this function signature, 136 // so parameters can be converted into flags as in the following: 137 // 138 // var flags flag.FlagSet 139 // value := flags.Bool("param", false, "") 140 // opts := &protogen.Options{ 141 // ParamFunc: flags.Set, 142 // } 143 // protogen.Run(opts, func(p *protogen.Plugin) error { 144 // if *value { ... } 145 // }) 146 ParamFunc func(name, value string) error 147 148 // ImportRewriteFunc is called with the import path of each package 149 // imported by a generated file. It returns the import path to use 150 // for this package. 151 ImportRewriteFunc func(GoImportPath) GoImportPath 152} 153 154// New returns a new Plugin. 155func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) { 156 gen := &Plugin{ 157 Request: req, 158 FilesByPath: make(map[string]*File), 159 fileReg: new(protoregistry.Files), 160 enumsByName: make(map[protoreflect.FullName]*Enum), 161 messagesByName: make(map[protoreflect.FullName]*Message), 162 opts: opts, 163 } 164 165 packageNames := make(map[string]GoPackageName) // filename -> package name 166 importPaths := make(map[string]GoImportPath) // filename -> import path 167 for _, param := range strings.Split(req.GetParameter(), ",") { 168 var value string 169 if i := strings.Index(param, "="); i >= 0 { 170 value = param[i+1:] 171 param = param[0:i] 172 } 173 switch param { 174 case "": 175 // Ignore. 176 case "module": 177 gen.module = value 178 case "paths": 179 switch value { 180 case "import": 181 gen.pathType = pathTypeImport 182 case "source_relative": 183 gen.pathType = pathTypeSourceRelative 184 default: 185 return nil, fmt.Errorf(`unknown path type %q: want "import" or "source_relative"`, value) 186 } 187 case "annotate_code": 188 switch value { 189 case "true", "": 190 gen.annotateCode = true 191 case "false": 192 default: 193 return nil, fmt.Errorf(`bad value for parameter %q: want "true" or "false"`, param) 194 } 195 default: 196 if param[0] == 'M' { 197 impPath, pkgName := splitImportPathAndPackageName(value) 198 if pkgName != "" { 199 packageNames[param[1:]] = pkgName 200 } 201 if impPath != "" { 202 importPaths[param[1:]] = impPath 203 } 204 continue 205 } 206 if opts.ParamFunc != nil { 207 if err := opts.ParamFunc(param, value); err != nil { 208 return nil, err 209 } 210 } 211 } 212 } 213 214 // When the module= option is provided, we strip the module name 215 // prefix from generated files. This only makes sense if generated 216 // filenames are based on the import path. 217 if gen.module != "" && gen.pathType == pathTypeSourceRelative { 218 return nil, fmt.Errorf("cannot use module= with paths=source_relative") 219 } 220 221 // Figure out the import path and package name for each file. 222 // 223 // The rules here are complicated and have grown organically over time. 224 // Interactions between different ways of specifying package information 225 // may be surprising. 226 // 227 // The recommended approach is to include a go_package option in every 228 // .proto source file specifying the full import path of the Go package 229 // associated with this file. 230 // 231 // option go_package = "google.golang.org/protobuf/types/known/anypb"; 232 // 233 // Alternatively, build systems which want to exert full control over 234 // import paths may specify M<filename>=<import_path> flags. 235 for _, fdesc := range gen.Request.ProtoFile { 236 // The "M" command-line flags take precedence over 237 // the "go_package" option in the .proto source file. 238 filename := fdesc.GetName() 239 impPath, pkgName := splitImportPathAndPackageName(fdesc.GetOptions().GetGoPackage()) 240 if importPaths[filename] == "" && impPath != "" { 241 importPaths[filename] = impPath 242 } 243 if packageNames[filename] == "" && pkgName != "" { 244 packageNames[filename] = pkgName 245 } 246 switch { 247 case importPaths[filename] == "": 248 // The import path must be specified one way or another. 249 return nil, fmt.Errorf( 250 "unable to determine Go import path for %q\n\n"+ 251 "Please specify either:\n"+ 252 "\t• a \"go_package\" option in the .proto source file, or\n"+ 253 "\t• a \"M\" argument on the command line.\n\n"+ 254 "See %v for more information.\n", 255 fdesc.GetName(), goPackageDocURL) 256 case !strings.Contains(string(importPaths[filename]), ".") && 257 !strings.Contains(string(importPaths[filename]), "/"): 258 // Check that import paths contain at least a dot or slash to avoid 259 // a common mistake where import path is confused with package name. 260 return nil, fmt.Errorf( 261 "invalid Go import path %q for %q\n\n"+ 262 "The import path must contain at least one period ('.') or forward slash ('/') character.\n\n"+ 263 "See %v for more information.\n", 264 string(importPaths[filename]), fdesc.GetName(), goPackageDocURL) 265 case packageNames[filename] == "": 266 // If the package name is not explicitly specified, 267 // then derive a reasonable package name from the import path. 268 // 269 // NOTE: The package name is derived first from the import path in 270 // the "go_package" option (if present) before trying the "M" flag. 271 // The inverted order for this is because the primary use of the "M" 272 // flag is by build systems that have full control over the 273 // import paths all packages, where it is generally expected that 274 // the Go package name still be identical for the Go toolchain and 275 // for custom build systems like Bazel. 276 if impPath == "" { 277 impPath = importPaths[filename] 278 } 279 packageNames[filename] = cleanPackageName(path.Base(string(impPath))) 280 } 281 } 282 283 // Consistency check: Every file with the same Go import path should have 284 // the same Go package name. 285 packageFiles := make(map[GoImportPath][]string) 286 for filename, importPath := range importPaths { 287 if _, ok := packageNames[filename]; !ok { 288 // Skip files mentioned in a M<file>=<import_path> parameter 289 // but which do not appear in the CodeGeneratorRequest. 290 continue 291 } 292 packageFiles[importPath] = append(packageFiles[importPath], filename) 293 } 294 for importPath, filenames := range packageFiles { 295 for i := 1; i < len(filenames); i++ { 296 if a, b := packageNames[filenames[0]], packageNames[filenames[i]]; a != b { 297 return nil, fmt.Errorf("Go package %v has inconsistent names %v (%v) and %v (%v)", 298 importPath, a, filenames[0], b, filenames[i]) 299 } 300 } 301 } 302 303 // The extracted types from the full import set 304 typeRegistry := newExtensionRegistry() 305 for _, fdesc := range gen.Request.ProtoFile { 306 filename := fdesc.GetName() 307 if gen.FilesByPath[filename] != nil { 308 return nil, fmt.Errorf("duplicate file name: %q", filename) 309 } 310 f, err := newFile(gen, fdesc, packageNames[filename], importPaths[filename]) 311 if err != nil { 312 return nil, err 313 } 314 gen.Files = append(gen.Files, f) 315 gen.FilesByPath[filename] = f 316 if err = typeRegistry.registerAllExtensionsFromFile(f.Desc); err != nil { 317 return nil, err 318 } 319 } 320 for _, filename := range gen.Request.FileToGenerate { 321 f, ok := gen.FilesByPath[filename] 322 if !ok { 323 return nil, fmt.Errorf("no descriptor for generated file: %v", filename) 324 } 325 f.Generate = true 326 } 327 328 // Create fully-linked descriptors if new extensions were found 329 if typeRegistry.hasNovelExtensions() { 330 for _, f := range gen.Files { 331 b, err := proto.Marshal(f.Proto.ProtoReflect().Interface()) 332 if err != nil { 333 return nil, err 334 } 335 err = proto.UnmarshalOptions{Resolver: typeRegistry}.Unmarshal(b, f.Proto) 336 if err != nil { 337 return nil, err 338 } 339 } 340 } 341 return gen, nil 342} 343 344// Error records an error in code generation. The generator will report the 345// error back to protoc and will not produce output. 346func (gen *Plugin) Error(err error) { 347 if gen.err == nil { 348 gen.err = err 349 } 350} 351 352// Response returns the generator output. 353func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse { 354 resp := &pluginpb.CodeGeneratorResponse{} 355 if gen.err != nil { 356 resp.Error = proto.String(gen.err.Error()) 357 return resp 358 } 359 for _, g := range gen.genFiles { 360 if g.skip { 361 continue 362 } 363 content, err := g.Content() 364 if err != nil { 365 return &pluginpb.CodeGeneratorResponse{ 366 Error: proto.String(err.Error()), 367 } 368 } 369 filename := g.filename 370 if gen.module != "" { 371 trim := gen.module + "/" 372 if !strings.HasPrefix(filename, trim) { 373 return &pluginpb.CodeGeneratorResponse{ 374 Error: proto.String(fmt.Sprintf("%v: generated file does not match prefix %q", filename, gen.module)), 375 } 376 } 377 filename = strings.TrimPrefix(filename, trim) 378 } 379 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{ 380 Name: proto.String(filename), 381 Content: proto.String(string(content)), 382 }) 383 if gen.annotateCode && strings.HasSuffix(g.filename, ".go") { 384 meta, err := g.metaFile(content) 385 if err != nil { 386 return &pluginpb.CodeGeneratorResponse{ 387 Error: proto.String(err.Error()), 388 } 389 } 390 resp.File = append(resp.File, &pluginpb.CodeGeneratorResponse_File{ 391 Name: proto.String(filename + ".meta"), 392 Content: proto.String(meta), 393 }) 394 } 395 } 396 if gen.SupportedFeatures > 0 { 397 resp.SupportedFeatures = proto.Uint64(gen.SupportedFeatures) 398 } 399 return resp 400} 401 402// A File describes a .proto source file. 403type File struct { 404 Desc protoreflect.FileDescriptor 405 Proto *descriptorpb.FileDescriptorProto 406 407 GoDescriptorIdent GoIdent // name of Go variable for the file descriptor 408 GoPackageName GoPackageName // name of this file's Go package 409 GoImportPath GoImportPath // import path of this file's Go package 410 411 Enums []*Enum // top-level enum declarations 412 Messages []*Message // top-level message declarations 413 Extensions []*Extension // top-level extension declarations 414 Services []*Service // top-level service declarations 415 416 Generate bool // true if we should generate code for this file 417 418 // GeneratedFilenamePrefix is used to construct filenames for generated 419 // files associated with this source file. 420 // 421 // For example, the source file "dir/foo.proto" might have a filename prefix 422 // of "dir/foo". Appending ".pb.go" produces an output file of "dir/foo.pb.go". 423 GeneratedFilenamePrefix string 424 425 location Location 426} 427 428func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPackageName, importPath GoImportPath) (*File, error) { 429 desc, err := protodesc.NewFile(p, gen.fileReg) 430 if err != nil { 431 return nil, fmt.Errorf("invalid FileDescriptorProto %q: %v", p.GetName(), err) 432 } 433 if err := gen.fileReg.RegisterFile(desc); err != nil { 434 return nil, fmt.Errorf("cannot register descriptor %q: %v", p.GetName(), err) 435 } 436 f := &File{ 437 Desc: desc, 438 Proto: p, 439 GoPackageName: packageName, 440 GoImportPath: importPath, 441 location: Location{SourceFile: desc.Path()}, 442 } 443 444 // Determine the prefix for generated Go files. 445 prefix := p.GetName() 446 if ext := path.Ext(prefix); ext == ".proto" || ext == ".protodevel" { 447 prefix = prefix[:len(prefix)-len(ext)] 448 } 449 switch gen.pathType { 450 case pathTypeImport: 451 // If paths=import, the output filename is derived from the Go import path. 452 prefix = path.Join(string(f.GoImportPath), path.Base(prefix)) 453 case pathTypeSourceRelative: 454 // If paths=source_relative, the output filename is derived from 455 // the input filename. 456 } 457 f.GoDescriptorIdent = GoIdent{ 458 GoName: "File_" + strs.GoSanitized(p.GetName()), 459 GoImportPath: f.GoImportPath, 460 } 461 f.GeneratedFilenamePrefix = prefix 462 463 for i, eds := 0, desc.Enums(); i < eds.Len(); i++ { 464 f.Enums = append(f.Enums, newEnum(gen, f, nil, eds.Get(i))) 465 } 466 for i, mds := 0, desc.Messages(); i < mds.Len(); i++ { 467 f.Messages = append(f.Messages, newMessage(gen, f, nil, mds.Get(i))) 468 } 469 for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ { 470 f.Extensions = append(f.Extensions, newField(gen, f, nil, xds.Get(i))) 471 } 472 for i, sds := 0, desc.Services(); i < sds.Len(); i++ { 473 f.Services = append(f.Services, newService(gen, f, sds.Get(i))) 474 } 475 for _, message := range f.Messages { 476 if err := message.resolveDependencies(gen); err != nil { 477 return nil, err 478 } 479 } 480 for _, extension := range f.Extensions { 481 if err := extension.resolveDependencies(gen); err != nil { 482 return nil, err 483 } 484 } 485 for _, service := range f.Services { 486 for _, method := range service.Methods { 487 if err := method.resolveDependencies(gen); err != nil { 488 return nil, err 489 } 490 } 491 } 492 return f, nil 493} 494 495// splitImportPathAndPackageName splits off the optional Go package name 496// from the Go import path when separated by a ';' delimiter. 497func splitImportPathAndPackageName(s string) (GoImportPath, GoPackageName) { 498 if i := strings.Index(s, ";"); i >= 0 { 499 return GoImportPath(s[:i]), GoPackageName(s[i+1:]) 500 } 501 return GoImportPath(s), "" 502} 503 504// An Enum describes an enum. 505type Enum struct { 506 Desc protoreflect.EnumDescriptor 507 508 GoIdent GoIdent // name of the generated Go type 509 510 Values []*EnumValue // enum value declarations 511 512 Location Location // location of this enum 513 Comments CommentSet // comments associated with this enum 514} 515 516func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum { 517 var loc Location 518 if parent != nil { 519 loc = parent.Location.appendPath(genid.DescriptorProto_EnumType_field_number, desc.Index()) 520 } else { 521 loc = f.location.appendPath(genid.FileDescriptorProto_EnumType_field_number, desc.Index()) 522 } 523 enum := &Enum{ 524 Desc: desc, 525 GoIdent: newGoIdent(f, desc), 526 Location: loc, 527 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 528 } 529 gen.enumsByName[desc.FullName()] = enum 530 for i, vds := 0, enum.Desc.Values(); i < vds.Len(); i++ { 531 enum.Values = append(enum.Values, newEnumValue(gen, f, parent, enum, vds.Get(i))) 532 } 533 return enum 534} 535 536// An EnumValue describes an enum value. 537type EnumValue struct { 538 Desc protoreflect.EnumValueDescriptor 539 540 GoIdent GoIdent // name of the generated Go declaration 541 542 Parent *Enum // enum in which this value is declared 543 544 Location Location // location of this enum value 545 Comments CommentSet // comments associated with this enum value 546} 547 548func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue { 549 // A top-level enum value's name is: EnumName_ValueName 550 // An enum value contained in a message is: MessageName_ValueName 551 // 552 // For historical reasons, enum value names are not camel-cased. 553 parentIdent := enum.GoIdent 554 if message != nil { 555 parentIdent = message.GoIdent 556 } 557 name := parentIdent.GoName + "_" + string(desc.Name()) 558 loc := enum.Location.appendPath(genid.EnumDescriptorProto_Value_field_number, desc.Index()) 559 return &EnumValue{ 560 Desc: desc, 561 GoIdent: f.GoImportPath.Ident(name), 562 Parent: enum, 563 Location: loc, 564 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 565 } 566} 567 568// A Message describes a message. 569type Message struct { 570 Desc protoreflect.MessageDescriptor 571 572 GoIdent GoIdent // name of the generated Go type 573 574 Fields []*Field // message field declarations 575 Oneofs []*Oneof // message oneof declarations 576 577 Enums []*Enum // nested enum declarations 578 Messages []*Message // nested message declarations 579 Extensions []*Extension // nested extension declarations 580 581 Location Location // location of this message 582 Comments CommentSet // comments associated with this message 583} 584 585func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message { 586 var loc Location 587 if parent != nil { 588 loc = parent.Location.appendPath(genid.DescriptorProto_NestedType_field_number, desc.Index()) 589 } else { 590 loc = f.location.appendPath(genid.FileDescriptorProto_MessageType_field_number, desc.Index()) 591 } 592 message := &Message{ 593 Desc: desc, 594 GoIdent: newGoIdent(f, desc), 595 Location: loc, 596 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 597 } 598 gen.messagesByName[desc.FullName()] = message 599 for i, eds := 0, desc.Enums(); i < eds.Len(); i++ { 600 message.Enums = append(message.Enums, newEnum(gen, f, message, eds.Get(i))) 601 } 602 for i, mds := 0, desc.Messages(); i < mds.Len(); i++ { 603 message.Messages = append(message.Messages, newMessage(gen, f, message, mds.Get(i))) 604 } 605 for i, fds := 0, desc.Fields(); i < fds.Len(); i++ { 606 message.Fields = append(message.Fields, newField(gen, f, message, fds.Get(i))) 607 } 608 for i, ods := 0, desc.Oneofs(); i < ods.Len(); i++ { 609 message.Oneofs = append(message.Oneofs, newOneof(gen, f, message, ods.Get(i))) 610 } 611 for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ { 612 message.Extensions = append(message.Extensions, newField(gen, f, message, xds.Get(i))) 613 } 614 615 // Resolve local references between fields and oneofs. 616 for _, field := range message.Fields { 617 if od := field.Desc.ContainingOneof(); od != nil { 618 oneof := message.Oneofs[od.Index()] 619 field.Oneof = oneof 620 oneof.Fields = append(oneof.Fields, field) 621 } 622 } 623 624 // Field name conflict resolution. 625 // 626 // We assume well-known method names that may be attached to a generated 627 // message type, as well as a 'Get*' method for each field. For each 628 // field in turn, we add _s to its name until there are no conflicts. 629 // 630 // Any change to the following set of method names is a potential 631 // incompatible API change because it may change generated field names. 632 // 633 // TODO: If we ever support a 'go_name' option to set the Go name of a 634 // field, we should consider dropping this entirely. The conflict 635 // resolution algorithm is subtle and surprising (changing the order 636 // in which fields appear in the .proto source file can change the 637 // names of fields in generated code), and does not adapt well to 638 // adding new per-field methods such as setters. 639 usedNames := map[string]bool{ 640 "Reset": true, 641 "String": true, 642 "ProtoMessage": true, 643 "Marshal": true, 644 "Unmarshal": true, 645 "ExtensionRangeArray": true, 646 "ExtensionMap": true, 647 "Descriptor": true, 648 } 649 makeNameUnique := func(name string, hasGetter bool) string { 650 for usedNames[name] || (hasGetter && usedNames["Get"+name]) { 651 name += "_" 652 } 653 usedNames[name] = true 654 usedNames["Get"+name] = hasGetter 655 return name 656 } 657 for _, field := range message.Fields { 658 field.GoName = makeNameUnique(field.GoName, true) 659 field.GoIdent.GoName = message.GoIdent.GoName + "_" + field.GoName 660 if field.Oneof != nil && field.Oneof.Fields[0] == field { 661 // Make the name for a oneof unique as well. For historical reasons, 662 // this assumes that a getter method is not generated for oneofs. 663 // This is incorrect, but fixing it breaks existing code. 664 field.Oneof.GoName = makeNameUnique(field.Oneof.GoName, false) 665 field.Oneof.GoIdent.GoName = message.GoIdent.GoName + "_" + field.Oneof.GoName 666 } 667 } 668 669 // Oneof field name conflict resolution. 670 // 671 // This conflict resolution is incomplete as it does not consider collisions 672 // with other oneof field types, but fixing it breaks existing code. 673 for _, field := range message.Fields { 674 if field.Oneof != nil { 675 Loop: 676 for { 677 for _, nestedMessage := range message.Messages { 678 if nestedMessage.GoIdent == field.GoIdent { 679 field.GoIdent.GoName += "_" 680 continue Loop 681 } 682 } 683 for _, nestedEnum := range message.Enums { 684 if nestedEnum.GoIdent == field.GoIdent { 685 field.GoIdent.GoName += "_" 686 continue Loop 687 } 688 } 689 break Loop 690 } 691 } 692 } 693 694 return message 695} 696 697func (message *Message) resolveDependencies(gen *Plugin) error { 698 for _, field := range message.Fields { 699 if err := field.resolveDependencies(gen); err != nil { 700 return err 701 } 702 } 703 for _, message := range message.Messages { 704 if err := message.resolveDependencies(gen); err != nil { 705 return err 706 } 707 } 708 for _, extension := range message.Extensions { 709 if err := extension.resolveDependencies(gen); err != nil { 710 return err 711 } 712 } 713 return nil 714} 715 716// A Field describes a message field. 717type Field struct { 718 Desc protoreflect.FieldDescriptor 719 720 // GoName is the base name of this field's Go field and methods. 721 // For code generated by protoc-gen-go, this means a field named 722 // '{{GoName}}' and a getter method named 'Get{{GoName}}'. 723 GoName string // e.g., "FieldName" 724 725 // GoIdent is the base name of a top-level declaration for this field. 726 // For code generated by protoc-gen-go, this means a wrapper type named 727 // '{{GoIdent}}' for members fields of a oneof, and a variable named 728 // 'E_{{GoIdent}}' for extension fields. 729 GoIdent GoIdent // e.g., "MessageName_FieldName" 730 731 Parent *Message // message in which this field is declared; nil if top-level extension 732 Oneof *Oneof // containing oneof; nil if not part of a oneof 733 Extendee *Message // extended message for extension fields; nil otherwise 734 735 Enum *Enum // type for enum fields; nil otherwise 736 Message *Message // type for message or group fields; nil otherwise 737 738 Location Location // location of this field 739 Comments CommentSet // comments associated with this field 740} 741 742func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field { 743 var loc Location 744 switch { 745 case desc.IsExtension() && message == nil: 746 loc = f.location.appendPath(genid.FileDescriptorProto_Extension_field_number, desc.Index()) 747 case desc.IsExtension() && message != nil: 748 loc = message.Location.appendPath(genid.DescriptorProto_Extension_field_number, desc.Index()) 749 default: 750 loc = message.Location.appendPath(genid.DescriptorProto_Field_field_number, desc.Index()) 751 } 752 camelCased := strs.GoCamelCase(string(desc.Name())) 753 var parentPrefix string 754 if message != nil { 755 parentPrefix = message.GoIdent.GoName + "_" 756 } 757 field := &Field{ 758 Desc: desc, 759 GoName: camelCased, 760 GoIdent: GoIdent{ 761 GoImportPath: f.GoImportPath, 762 GoName: parentPrefix + camelCased, 763 }, 764 Parent: message, 765 Location: loc, 766 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 767 } 768 return field 769} 770 771func (field *Field) resolveDependencies(gen *Plugin) error { 772 desc := field.Desc 773 switch desc.Kind() { 774 case protoreflect.EnumKind: 775 name := field.Desc.Enum().FullName() 776 enum, ok := gen.enumsByName[name] 777 if !ok { 778 return fmt.Errorf("field %v: no descriptor for enum %v", desc.FullName(), name) 779 } 780 field.Enum = enum 781 case protoreflect.MessageKind, protoreflect.GroupKind: 782 name := desc.Message().FullName() 783 message, ok := gen.messagesByName[name] 784 if !ok { 785 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), name) 786 } 787 field.Message = message 788 } 789 if desc.IsExtension() { 790 name := desc.ContainingMessage().FullName() 791 message, ok := gen.messagesByName[name] 792 if !ok { 793 return fmt.Errorf("field %v: no descriptor for type %v", desc.FullName(), name) 794 } 795 field.Extendee = message 796 } 797 return nil 798} 799 800// A Oneof describes a message oneof. 801type Oneof struct { 802 Desc protoreflect.OneofDescriptor 803 804 // GoName is the base name of this oneof's Go field and methods. 805 // For code generated by protoc-gen-go, this means a field named 806 // '{{GoName}}' and a getter method named 'Get{{GoName}}'. 807 GoName string // e.g., "OneofName" 808 809 // GoIdent is the base name of a top-level declaration for this oneof. 810 GoIdent GoIdent // e.g., "MessageName_OneofName" 811 812 Parent *Message // message in which this oneof is declared 813 814 Fields []*Field // fields that are part of this oneof 815 816 Location Location // location of this oneof 817 Comments CommentSet // comments associated with this oneof 818} 819 820func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDescriptor) *Oneof { 821 loc := message.Location.appendPath(genid.DescriptorProto_OneofDecl_field_number, desc.Index()) 822 camelCased := strs.GoCamelCase(string(desc.Name())) 823 parentPrefix := message.GoIdent.GoName + "_" 824 return &Oneof{ 825 Desc: desc, 826 Parent: message, 827 GoName: camelCased, 828 GoIdent: GoIdent{ 829 GoImportPath: f.GoImportPath, 830 GoName: parentPrefix + camelCased, 831 }, 832 Location: loc, 833 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 834 } 835} 836 837// Extension is an alias of Field for documentation. 838type Extension = Field 839 840// A Service describes a service. 841type Service struct { 842 Desc protoreflect.ServiceDescriptor 843 844 GoName string 845 846 Methods []*Method // service method declarations 847 848 Location Location // location of this service 849 Comments CommentSet // comments associated with this service 850} 851 852func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service { 853 loc := f.location.appendPath(genid.FileDescriptorProto_Service_field_number, desc.Index()) 854 service := &Service{ 855 Desc: desc, 856 GoName: strs.GoCamelCase(string(desc.Name())), 857 Location: loc, 858 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 859 } 860 for i, mds := 0, desc.Methods(); i < mds.Len(); i++ { 861 service.Methods = append(service.Methods, newMethod(gen, f, service, mds.Get(i))) 862 } 863 return service 864} 865 866// A Method describes a method in a service. 867type Method struct { 868 Desc protoreflect.MethodDescriptor 869 870 GoName string 871 872 Parent *Service // service in which this method is declared 873 874 Input *Message 875 Output *Message 876 877 Location Location // location of this method 878 Comments CommentSet // comments associated with this method 879} 880 881func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method { 882 loc := service.Location.appendPath(genid.ServiceDescriptorProto_Method_field_number, desc.Index()) 883 method := &Method{ 884 Desc: desc, 885 GoName: strs.GoCamelCase(string(desc.Name())), 886 Parent: service, 887 Location: loc, 888 Comments: makeCommentSet(f.Desc.SourceLocations().ByDescriptor(desc)), 889 } 890 return method 891} 892 893func (method *Method) resolveDependencies(gen *Plugin) error { 894 desc := method.Desc 895 896 inName := desc.Input().FullName() 897 in, ok := gen.messagesByName[inName] 898 if !ok { 899 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), inName) 900 } 901 method.Input = in 902 903 outName := desc.Output().FullName() 904 out, ok := gen.messagesByName[outName] 905 if !ok { 906 return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), outName) 907 } 908 method.Output = out 909 910 return nil 911} 912 913// A GeneratedFile is a generated file. 914type GeneratedFile struct { 915 gen *Plugin 916 skip bool 917 filename string 918 goImportPath GoImportPath 919 buf bytes.Buffer 920 packageNames map[GoImportPath]GoPackageName 921 usedPackageNames map[GoPackageName]bool 922 manualImports map[GoImportPath]bool 923 annotations map[string][]Location 924} 925 926// NewGeneratedFile creates a new generated file with the given filename 927// and import path. 928func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath) *GeneratedFile { 929 g := &GeneratedFile{ 930 gen: gen, 931 filename: filename, 932 goImportPath: goImportPath, 933 packageNames: make(map[GoImportPath]GoPackageName), 934 usedPackageNames: make(map[GoPackageName]bool), 935 manualImports: make(map[GoImportPath]bool), 936 annotations: make(map[string][]Location), 937 } 938 939 // All predeclared identifiers in Go are already used. 940 for _, s := range types.Universe.Names() { 941 g.usedPackageNames[GoPackageName(s)] = true 942 } 943 944 gen.genFiles = append(gen.genFiles, g) 945 return g 946} 947 948// P prints a line to the generated output. It converts each parameter to a 949// string following the same rules as fmt.Print. It never inserts spaces 950// between parameters. 951func (g *GeneratedFile) P(v ...interface{}) { 952 for _, x := range v { 953 switch x := x.(type) { 954 case GoIdent: 955 fmt.Fprint(&g.buf, g.QualifiedGoIdent(x)) 956 default: 957 fmt.Fprint(&g.buf, x) 958 } 959 } 960 fmt.Fprintln(&g.buf) 961} 962 963// QualifiedGoIdent returns the string to use for a Go identifier. 964// 965// If the identifier is from a different Go package than the generated file, 966// the returned name will be qualified (package.name) and an import statement 967// for the identifier's package will be included in the file. 968func (g *GeneratedFile) QualifiedGoIdent(ident GoIdent) string { 969 if ident.GoImportPath == g.goImportPath { 970 return ident.GoName 971 } 972 if packageName, ok := g.packageNames[ident.GoImportPath]; ok { 973 return string(packageName) + "." + ident.GoName 974 } 975 packageName := cleanPackageName(path.Base(string(ident.GoImportPath))) 976 for i, orig := 1, packageName; g.usedPackageNames[packageName]; i++ { 977 packageName = orig + GoPackageName(strconv.Itoa(i)) 978 } 979 g.packageNames[ident.GoImportPath] = packageName 980 g.usedPackageNames[packageName] = true 981 return string(packageName) + "." + ident.GoName 982} 983 984// Import ensures a package is imported by the generated file. 985// 986// Packages referenced by QualifiedGoIdent are automatically imported. 987// Explicitly importing a package with Import is generally only necessary 988// when the import will be blank (import _ "package"). 989func (g *GeneratedFile) Import(importPath GoImportPath) { 990 g.manualImports[importPath] = true 991} 992 993// Write implements io.Writer. 994func (g *GeneratedFile) Write(p []byte) (n int, err error) { 995 return g.buf.Write(p) 996} 997 998// Skip removes the generated file from the plugin output. 999func (g *GeneratedFile) Skip() { 1000 g.skip = true 1001} 1002 1003// Unskip reverts a previous call to Skip, re-including the generated file in 1004// the plugin output. 1005func (g *GeneratedFile) Unskip() { 1006 g.skip = false 1007} 1008 1009// Annotate associates a symbol in a generated Go file with a location in a 1010// source .proto file. 1011// 1012// The symbol may refer to a type, constant, variable, function, method, or 1013// struct field. The "T.sel" syntax is used to identify the method or field 1014// 'sel' on type 'T'. 1015func (g *GeneratedFile) Annotate(symbol string, loc Location) { 1016 g.annotations[symbol] = append(g.annotations[symbol], loc) 1017} 1018 1019// Content returns the contents of the generated file. 1020func (g *GeneratedFile) Content() ([]byte, error) { 1021 if !strings.HasSuffix(g.filename, ".go") { 1022 return g.buf.Bytes(), nil 1023 } 1024 1025 // Reformat generated code. 1026 original := g.buf.Bytes() 1027 fset := token.NewFileSet() 1028 file, err := parser.ParseFile(fset, "", original, parser.ParseComments) 1029 if err != nil { 1030 // Print out the bad code with line numbers. 1031 // This should never happen in practice, but it can while changing generated code 1032 // so consider this a debugging aid. 1033 var src bytes.Buffer 1034 s := bufio.NewScanner(bytes.NewReader(original)) 1035 for line := 1; s.Scan(); line++ { 1036 fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes()) 1037 } 1038 return nil, fmt.Errorf("%v: unparsable Go source: %v\n%v", g.filename, err, src.String()) 1039 } 1040 1041 // Collect a sorted list of all imports. 1042 var importPaths [][2]string 1043 rewriteImport := func(importPath string) string { 1044 if f := g.gen.opts.ImportRewriteFunc; f != nil { 1045 return string(f(GoImportPath(importPath))) 1046 } 1047 return importPath 1048 } 1049 for importPath := range g.packageNames { 1050 pkgName := string(g.packageNames[GoImportPath(importPath)]) 1051 pkgPath := rewriteImport(string(importPath)) 1052 importPaths = append(importPaths, [2]string{pkgName, pkgPath}) 1053 } 1054 for importPath := range g.manualImports { 1055 if _, ok := g.packageNames[importPath]; !ok { 1056 pkgPath := rewriteImport(string(importPath)) 1057 importPaths = append(importPaths, [2]string{"_", pkgPath}) 1058 } 1059 } 1060 sort.Slice(importPaths, func(i, j int) bool { 1061 return importPaths[i][1] < importPaths[j][1] 1062 }) 1063 1064 // Modify the AST to include a new import block. 1065 if len(importPaths) > 0 { 1066 // Insert block after package statement or 1067 // possible comment attached to the end of the package statement. 1068 pos := file.Package 1069 tokFile := fset.File(file.Package) 1070 pkgLine := tokFile.Line(file.Package) 1071 for _, c := range file.Comments { 1072 if tokFile.Line(c.Pos()) > pkgLine { 1073 break 1074 } 1075 pos = c.End() 1076 } 1077 1078 // Construct the import block. 1079 impDecl := &ast.GenDecl{ 1080 Tok: token.IMPORT, 1081 TokPos: pos, 1082 Lparen: pos, 1083 Rparen: pos, 1084 } 1085 for _, importPath := range importPaths { 1086 impDecl.Specs = append(impDecl.Specs, &ast.ImportSpec{ 1087 Name: &ast.Ident{ 1088 Name: importPath[0], 1089 NamePos: pos, 1090 }, 1091 Path: &ast.BasicLit{ 1092 Kind: token.STRING, 1093 Value: strconv.Quote(importPath[1]), 1094 ValuePos: pos, 1095 }, 1096 EndPos: pos, 1097 }) 1098 } 1099 file.Decls = append([]ast.Decl{impDecl}, file.Decls...) 1100 } 1101 1102 var out bytes.Buffer 1103 if err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(&out, fset, file); err != nil { 1104 return nil, fmt.Errorf("%v: can not reformat Go source: %v", g.filename, err) 1105 } 1106 return out.Bytes(), nil 1107} 1108 1109// metaFile returns the contents of the file's metadata file, which is a 1110// text formatted string of the google.protobuf.GeneratedCodeInfo. 1111func (g *GeneratedFile) metaFile(content []byte) (string, error) { 1112 fset := token.NewFileSet() 1113 astFile, err := parser.ParseFile(fset, "", content, 0) 1114 if err != nil { 1115 return "", err 1116 } 1117 info := &descriptorpb.GeneratedCodeInfo{} 1118 1119 seenAnnotations := make(map[string]bool) 1120 annotate := func(s string, ident *ast.Ident) { 1121 seenAnnotations[s] = true 1122 for _, loc := range g.annotations[s] { 1123 info.Annotation = append(info.Annotation, &descriptorpb.GeneratedCodeInfo_Annotation{ 1124 SourceFile: proto.String(loc.SourceFile), 1125 Path: loc.Path, 1126 Begin: proto.Int32(int32(fset.Position(ident.Pos()).Offset)), 1127 End: proto.Int32(int32(fset.Position(ident.End()).Offset)), 1128 }) 1129 } 1130 } 1131 for _, decl := range astFile.Decls { 1132 switch decl := decl.(type) { 1133 case *ast.GenDecl: 1134 for _, spec := range decl.Specs { 1135 switch spec := spec.(type) { 1136 case *ast.TypeSpec: 1137 annotate(spec.Name.Name, spec.Name) 1138 switch st := spec.Type.(type) { 1139 case *ast.StructType: 1140 for _, field := range st.Fields.List { 1141 for _, name := range field.Names { 1142 annotate(spec.Name.Name+"."+name.Name, name) 1143 } 1144 } 1145 case *ast.InterfaceType: 1146 for _, field := range st.Methods.List { 1147 for _, name := range field.Names { 1148 annotate(spec.Name.Name+"."+name.Name, name) 1149 } 1150 } 1151 } 1152 case *ast.ValueSpec: 1153 for _, name := range spec.Names { 1154 annotate(name.Name, name) 1155 } 1156 } 1157 } 1158 case *ast.FuncDecl: 1159 if decl.Recv == nil { 1160 annotate(decl.Name.Name, decl.Name) 1161 } else { 1162 recv := decl.Recv.List[0].Type 1163 if s, ok := recv.(*ast.StarExpr); ok { 1164 recv = s.X 1165 } 1166 if id, ok := recv.(*ast.Ident); ok { 1167 annotate(id.Name+"."+decl.Name.Name, decl.Name) 1168 } 1169 } 1170 } 1171 } 1172 for a := range g.annotations { 1173 if !seenAnnotations[a] { 1174 return "", fmt.Errorf("%v: no symbol matching annotation %q", g.filename, a) 1175 } 1176 } 1177 1178 b, err := prototext.Marshal(info) 1179 if err != nil { 1180 return "", err 1181 } 1182 return string(b), nil 1183} 1184 1185// A GoIdent is a Go identifier, consisting of a name and import path. 1186// The name is a single identifier and may not be a dot-qualified selector. 1187type GoIdent struct { 1188 GoName string 1189 GoImportPath GoImportPath 1190} 1191 1192func (id GoIdent) String() string { return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName) } 1193 1194// newGoIdent returns the Go identifier for a descriptor. 1195func newGoIdent(f *File, d protoreflect.Descriptor) GoIdent { 1196 name := strings.TrimPrefix(string(d.FullName()), string(f.Desc.Package())+".") 1197 return GoIdent{ 1198 GoName: strs.GoCamelCase(name), 1199 GoImportPath: f.GoImportPath, 1200 } 1201} 1202 1203// A GoImportPath is the import path of a Go package. 1204// For example: "google.golang.org/protobuf/compiler/protogen" 1205type GoImportPath string 1206 1207func (p GoImportPath) String() string { return strconv.Quote(string(p)) } 1208 1209// Ident returns a GoIdent with s as the GoName and p as the GoImportPath. 1210func (p GoImportPath) Ident(s string) GoIdent { 1211 return GoIdent{GoName: s, GoImportPath: p} 1212} 1213 1214// A GoPackageName is the name of a Go package. e.g., "protobuf". 1215type GoPackageName string 1216 1217// cleanPackageName converts a string to a valid Go package name. 1218func cleanPackageName(name string) GoPackageName { 1219 return GoPackageName(strs.GoSanitized(name)) 1220} 1221 1222type pathType int 1223 1224const ( 1225 pathTypeImport pathType = iota 1226 pathTypeSourceRelative 1227) 1228 1229// A Location is a location in a .proto source file. 1230// 1231// See the google.protobuf.SourceCodeInfo documentation in descriptor.proto 1232// for details. 1233type Location struct { 1234 SourceFile string 1235 Path protoreflect.SourcePath 1236} 1237 1238// appendPath add elements to a Location's path, returning a new Location. 1239func (loc Location) appendPath(num protoreflect.FieldNumber, idx int) Location { 1240 loc.Path = append(protoreflect.SourcePath(nil), loc.Path...) // make copy 1241 loc.Path = append(loc.Path, int32(num), int32(idx)) 1242 return loc 1243} 1244 1245// CommentSet is a set of leading and trailing comments associated 1246// with a .proto descriptor declaration. 1247type CommentSet struct { 1248 LeadingDetached []Comments 1249 Leading Comments 1250 Trailing Comments 1251} 1252 1253func makeCommentSet(loc protoreflect.SourceLocation) CommentSet { 1254 var leadingDetached []Comments 1255 for _, s := range loc.LeadingDetachedComments { 1256 leadingDetached = append(leadingDetached, Comments(s)) 1257 } 1258 return CommentSet{ 1259 LeadingDetached: leadingDetached, 1260 Leading: Comments(loc.LeadingComments), 1261 Trailing: Comments(loc.TrailingComments), 1262 } 1263} 1264 1265// Comments is a comments string as provided by protoc. 1266type Comments string 1267 1268// String formats the comments by inserting // to the start of each line, 1269// ensuring that there is a trailing newline. 1270// An empty comment is formatted as an empty string. 1271func (c Comments) String() string { 1272 if c == "" { 1273 return "" 1274 } 1275 var b []byte 1276 for _, line := range strings.Split(strings.TrimSuffix(string(c), "\n"), "\n") { 1277 b = append(b, "//"...) 1278 b = append(b, line...) 1279 b = append(b, "\n"...) 1280 } 1281 return string(b) 1282} 1283 1284// extensionRegistry allows registration of new extensions defined in the .proto 1285// file for which we are generating bindings. 1286// 1287// Lookups consult the local type registry first and fall back to the base type 1288// registry which defaults to protoregistry.GlobalTypes 1289type extensionRegistry struct { 1290 base *protoregistry.Types 1291 local *protoregistry.Types 1292} 1293 1294func newExtensionRegistry() *extensionRegistry { 1295 return &extensionRegistry{ 1296 base: protoregistry.GlobalTypes, 1297 local: &protoregistry.Types{}, 1298 } 1299} 1300 1301// FindExtensionByName implements proto.UnmarshalOptions.FindExtensionByName 1302func (e *extensionRegistry) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) { 1303 if xt, err := e.local.FindExtensionByName(field); err == nil { 1304 return xt, nil 1305 } 1306 1307 return e.base.FindExtensionByName(field) 1308} 1309 1310// FindExtensionByNumber implements proto.UnmarshalOptions.FindExtensionByNumber 1311func (e *extensionRegistry) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { 1312 if xt, err := e.local.FindExtensionByNumber(message, field); err == nil { 1313 return xt, nil 1314 } 1315 1316 return e.base.FindExtensionByNumber(message, field) 1317} 1318 1319func (e *extensionRegistry) hasNovelExtensions() bool { 1320 return e.local.NumExtensions() > 0 1321} 1322 1323func (e *extensionRegistry) registerAllExtensionsFromFile(f protoreflect.FileDescriptor) error { 1324 if err := e.registerAllExtensions(f.Extensions()); err != nil { 1325 return err 1326 } 1327 return nil 1328} 1329 1330func (e *extensionRegistry) registerAllExtensionsFromMessage(ms protoreflect.MessageDescriptors) error { 1331 for i := 0; i < ms.Len(); i++ { 1332 m := ms.Get(i) 1333 if err := e.registerAllExtensions(m.Extensions()); err != nil { 1334 return err 1335 } 1336 } 1337 return nil 1338} 1339 1340func (e *extensionRegistry) registerAllExtensions(exts protoreflect.ExtensionDescriptors) error { 1341 for i := 0; i < exts.Len(); i++ { 1342 if err := e.registerExtension(exts.Get(i)); err != nil { 1343 return err 1344 } 1345 } 1346 return nil 1347} 1348 1349// registerExtension adds the given extension to the type registry if an 1350// extension with that full name does not exist yet. 1351func (e *extensionRegistry) registerExtension(xd protoreflect.ExtensionDescriptor) error { 1352 if _, err := e.FindExtensionByName(xd.FullName()); err != protoregistry.NotFound { 1353 // Either the extension already exists or there was an error, either way we're done. 1354 return err 1355 } 1356 return e.local.RegisterExtension(dynamicpb.NewExtensionType(xd)) 1357} 1358