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 internal_gengo is internal to the protobuf module. 6package internal_gengo 7 8import ( 9 "fmt" 10 "go/ast" 11 "go/parser" 12 "go/token" 13 "math" 14 "strconv" 15 "strings" 16 "unicode" 17 "unicode/utf8" 18 19 "google.golang.org/protobuf/compiler/protogen" 20 "google.golang.org/protobuf/internal/encoding/tag" 21 "google.golang.org/protobuf/internal/genid" 22 "google.golang.org/protobuf/internal/version" 23 "google.golang.org/protobuf/reflect/protoreflect" 24 "google.golang.org/protobuf/runtime/protoimpl" 25 26 "google.golang.org/protobuf/types/descriptorpb" 27 "google.golang.org/protobuf/types/pluginpb" 28) 29 30// SupportedFeatures reports the set of supported protobuf language features. 31var SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) 32 33// GenerateVersionMarkers specifies whether to generate version markers. 34var GenerateVersionMarkers = true 35 36// Standard library dependencies. 37const ( 38 base64Package = protogen.GoImportPath("encoding/base64") 39 mathPackage = protogen.GoImportPath("math") 40 reflectPackage = protogen.GoImportPath("reflect") 41 sortPackage = protogen.GoImportPath("sort") 42 stringsPackage = protogen.GoImportPath("strings") 43 syncPackage = protogen.GoImportPath("sync") 44 timePackage = protogen.GoImportPath("time") 45 utf8Package = protogen.GoImportPath("unicode/utf8") 46) 47 48// Protobuf library dependencies. 49// 50// These are declared as an interface type so that they can be more easily 51// patched to support unique build environments that impose restrictions 52// on the dependencies of generated source code. 53var ( 54 protoPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/proto") 55 protoifacePackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoiface") 56 protoimplPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/runtime/protoimpl") 57 protojsonPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/encoding/protojson") 58 protoreflectPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoreflect") 59 protoregistryPackage goImportPath = protogen.GoImportPath("google.golang.org/protobuf/reflect/protoregistry") 60) 61 62type goImportPath interface { 63 String() string 64 Ident(string) protogen.GoIdent 65} 66 67// GenerateFile generates the contents of a .pb.go file. 68func GenerateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile { 69 filename := file.GeneratedFilenamePrefix + ".pb.go" 70 g := gen.NewGeneratedFile(filename, file.GoImportPath) 71 f := newFileInfo(file) 72 73 genStandaloneComments(g, f, int32(genid.FileDescriptorProto_Syntax_field_number)) 74 genGeneratedHeader(gen, g, f) 75 genStandaloneComments(g, f, int32(genid.FileDescriptorProto_Package_field_number)) 76 77 packageDoc := genPackageKnownComment(f) 78 g.P(packageDoc, "package ", f.GoPackageName) 79 g.P() 80 81 // Emit a static check that enforces a minimum version of the proto package. 82 if GenerateVersionMarkers { 83 g.P("const (") 84 g.P("// Verify that this generated code is sufficiently up-to-date.") 85 g.P("_ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimpl.GenVersion, " - ", protoimplPackage.Ident("MinVersion"), ")") 86 g.P("// Verify that runtime/protoimpl is sufficiently up-to-date.") 87 g.P("_ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("MaxVersion"), " - ", protoimpl.GenVersion, ")") 88 g.P(")") 89 g.P() 90 } 91 92 for i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ { 93 genImport(gen, g, f, imps.Get(i)) 94 } 95 for _, enum := range f.allEnums { 96 genEnum(g, f, enum) 97 } 98 for _, message := range f.allMessages { 99 genMessage(g, f, message) 100 } 101 genExtensions(g, f) 102 103 genReflectFileDescriptor(gen, g, f) 104 105 return g 106} 107 108// genStandaloneComments prints all leading comments for a FileDescriptorProto 109// location identified by the field number n. 110func genStandaloneComments(g *protogen.GeneratedFile, f *fileInfo, n int32) { 111 loc := f.Desc.SourceLocations().ByPath(protoreflect.SourcePath{n}) 112 for _, s := range loc.LeadingDetachedComments { 113 g.P(protogen.Comments(s)) 114 g.P() 115 } 116 if s := loc.LeadingComments; s != "" { 117 g.P(protogen.Comments(s)) 118 g.P() 119 } 120} 121 122func genGeneratedHeader(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) { 123 g.P("// Code generated by protoc-gen-go. DO NOT EDIT.") 124 125 if GenerateVersionMarkers { 126 g.P("// versions:") 127 protocGenGoVersion := version.String() 128 protocVersion := "(unknown)" 129 if v := gen.Request.GetCompilerVersion(); v != nil { 130 protocVersion = fmt.Sprintf("v%v.%v.%v", v.GetMajor(), v.GetMinor(), v.GetPatch()) 131 } 132 g.P("// \tprotoc-gen-go ", protocGenGoVersion) 133 g.P("// \tprotoc ", protocVersion) 134 } 135 136 if f.Proto.GetOptions().GetDeprecated() { 137 g.P("// ", f.Desc.Path(), " is a deprecated file.") 138 } else { 139 g.P("// source: ", f.Desc.Path()) 140 } 141 g.P() 142} 143 144func genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, imp protoreflect.FileImport) { 145 impFile, ok := gen.FilesByPath[imp.Path()] 146 if !ok { 147 return 148 } 149 if impFile.GoImportPath == f.GoImportPath { 150 // Don't generate imports or aliases for types in the same Go package. 151 return 152 } 153 // Generate imports for all non-weak dependencies, even if they are not 154 // referenced, because other code and tools depend on having the 155 // full transitive closure of protocol buffer types in the binary. 156 if !imp.IsWeak { 157 g.Import(impFile.GoImportPath) 158 } 159 if !imp.IsPublic { 160 return 161 } 162 163 // Generate public imports by generating the imported file, parsing it, 164 // and extracting every symbol that should receive a forwarding declaration. 165 impGen := GenerateFile(gen, impFile) 166 impGen.Skip() 167 b, err := impGen.Content() 168 if err != nil { 169 gen.Error(err) 170 return 171 } 172 fset := token.NewFileSet() 173 astFile, err := parser.ParseFile(fset, "", b, parser.ParseComments) 174 if err != nil { 175 gen.Error(err) 176 return 177 } 178 genForward := func(tok token.Token, name string, expr ast.Expr) { 179 // Don't import unexported symbols. 180 r, _ := utf8.DecodeRuneInString(name) 181 if !unicode.IsUpper(r) { 182 return 183 } 184 // Don't import the FileDescriptor. 185 if name == impFile.GoDescriptorIdent.GoName { 186 return 187 } 188 // Don't import decls referencing a symbol defined in another package. 189 // i.e., don't import decls which are themselves public imports: 190 // 191 // type T = somepackage.T 192 if _, ok := expr.(*ast.SelectorExpr); ok { 193 return 194 } 195 g.P(tok, " ", name, " = ", impFile.GoImportPath.Ident(name)) 196 } 197 g.P("// Symbols defined in public import of ", imp.Path(), ".") 198 g.P() 199 for _, decl := range astFile.Decls { 200 switch decl := decl.(type) { 201 case *ast.GenDecl: 202 for _, spec := range decl.Specs { 203 switch spec := spec.(type) { 204 case *ast.TypeSpec: 205 genForward(decl.Tok, spec.Name.Name, spec.Type) 206 case *ast.ValueSpec: 207 for i, name := range spec.Names { 208 var expr ast.Expr 209 if i < len(spec.Values) { 210 expr = spec.Values[i] 211 } 212 genForward(decl.Tok, name.Name, expr) 213 } 214 case *ast.ImportSpec: 215 default: 216 panic(fmt.Sprintf("can't generate forward for spec type %T", spec)) 217 } 218 } 219 } 220 } 221 g.P() 222} 223 224func genEnum(g *protogen.GeneratedFile, f *fileInfo, e *enumInfo) { 225 // Enum type declaration. 226 g.Annotate(e.GoIdent.GoName, e.Location) 227 leadingComments := appendDeprecationSuffix(e.Comments.Leading, 228 e.Desc.Options().(*descriptorpb.EnumOptions).GetDeprecated()) 229 g.P(leadingComments, 230 "type ", e.GoIdent, " int32") 231 232 // Enum value constants. 233 g.P("const (") 234 for _, value := range e.Values { 235 g.Annotate(value.GoIdent.GoName, value.Location) 236 leadingComments := appendDeprecationSuffix(value.Comments.Leading, 237 value.Desc.Options().(*descriptorpb.EnumValueOptions).GetDeprecated()) 238 g.P(leadingComments, 239 value.GoIdent, " ", e.GoIdent, " = ", value.Desc.Number(), 240 trailingComment(value.Comments.Trailing)) 241 } 242 g.P(")") 243 g.P() 244 245 // Enum value maps. 246 g.P("// Enum value maps for ", e.GoIdent, ".") 247 g.P("var (") 248 g.P(e.GoIdent.GoName+"_name", " = map[int32]string{") 249 for _, value := range e.Values { 250 duplicate := "" 251 if value.Desc != e.Desc.Values().ByNumber(value.Desc.Number()) { 252 duplicate = "// Duplicate value: " 253 } 254 g.P(duplicate, value.Desc.Number(), ": ", strconv.Quote(string(value.Desc.Name())), ",") 255 } 256 g.P("}") 257 g.P(e.GoIdent.GoName+"_value", " = map[string]int32{") 258 for _, value := range e.Values { 259 g.P(strconv.Quote(string(value.Desc.Name())), ": ", value.Desc.Number(), ",") 260 } 261 g.P("}") 262 g.P(")") 263 g.P() 264 265 // Enum method. 266 // 267 // NOTE: A pointer value is needed to represent presence in proto2. 268 // Since a proto2 message can reference a proto3 enum, it is useful to 269 // always generate this method (even on proto3 enums) to support that case. 270 g.P("func (x ", e.GoIdent, ") Enum() *", e.GoIdent, " {") 271 g.P("p := new(", e.GoIdent, ")") 272 g.P("*p = x") 273 g.P("return p") 274 g.P("}") 275 g.P() 276 277 // String method. 278 g.P("func (x ", e.GoIdent, ") String() string {") 279 g.P("return ", protoimplPackage.Ident("X"), ".EnumStringOf(x.Descriptor(), ", protoreflectPackage.Ident("EnumNumber"), "(x))") 280 g.P("}") 281 g.P() 282 283 genEnumReflectMethods(g, f, e) 284 285 // UnmarshalJSON method. 286 if e.genJSONMethod && e.Desc.Syntax() == protoreflect.Proto2 { 287 g.P("// Deprecated: Do not use.") 288 g.P("func (x *", e.GoIdent, ") UnmarshalJSON(b []byte) error {") 289 g.P("num, err := ", protoimplPackage.Ident("X"), ".UnmarshalJSONEnum(x.Descriptor(), b)") 290 g.P("if err != nil {") 291 g.P("return err") 292 g.P("}") 293 g.P("*x = ", e.GoIdent, "(num)") 294 g.P("return nil") 295 g.P("}") 296 g.P() 297 } 298 299 // EnumDescriptor method. 300 if e.genRawDescMethod { 301 var indexes []string 302 for i := 1; i < len(e.Location.Path); i += 2 { 303 indexes = append(indexes, strconv.Itoa(int(e.Location.Path[i]))) 304 } 305 g.P("// Deprecated: Use ", e.GoIdent, ".Descriptor instead.") 306 g.P("func (", e.GoIdent, ") EnumDescriptor() ([]byte, []int) {") 307 g.P("return ", rawDescVarName(f), "GZIP(), []int{", strings.Join(indexes, ","), "}") 308 g.P("}") 309 g.P() 310 f.needRawDesc = true 311 } 312} 313 314func genMessage(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 315 if m.Desc.IsMapEntry() { 316 return 317 } 318 319 // Message type declaration. 320 g.Annotate(m.GoIdent.GoName, m.Location) 321 leadingComments := appendDeprecationSuffix(m.Comments.Leading, 322 m.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated()) 323 g.P(leadingComments, 324 "type ", m.GoIdent, " struct {") 325 genMessageFields(g, f, m) 326 g.P("}") 327 g.P() 328 329 genMessageKnownFunctions(g, f, m) 330 genMessageDefaultDecls(g, f, m) 331 genMessageMethods(g, f, m) 332 genMessageOneofWrapperTypes(g, f, m) 333} 334 335func genMessageFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 336 sf := f.allMessageFieldsByPtr[m] 337 genMessageInternalFields(g, f, m, sf) 338 for _, field := range m.Fields { 339 genMessageField(g, f, m, field, sf) 340 } 341} 342 343func genMessageInternalFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, sf *structFields) { 344 g.P(genid.State_goname, " ", protoimplPackage.Ident("MessageState")) 345 sf.append(genid.State_goname) 346 g.P(genid.SizeCache_goname, " ", protoimplPackage.Ident("SizeCache")) 347 sf.append(genid.SizeCache_goname) 348 if m.hasWeak { 349 g.P(genid.WeakFields_goname, " ", protoimplPackage.Ident("WeakFields")) 350 sf.append(genid.WeakFields_goname) 351 } 352 g.P(genid.UnknownFields_goname, " ", protoimplPackage.Ident("UnknownFields")) 353 sf.append(genid.UnknownFields_goname) 354 if m.Desc.ExtensionRanges().Len() > 0 { 355 g.P(genid.ExtensionFields_goname, " ", protoimplPackage.Ident("ExtensionFields")) 356 sf.append(genid.ExtensionFields_goname) 357 } 358 if sf.count > 0 { 359 g.P() 360 } 361} 362 363func genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field, sf *structFields) { 364 if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() { 365 // It would be a bit simpler to iterate over the oneofs below, 366 // but generating the field here keeps the contents of the Go 367 // struct in the same order as the contents of the source 368 // .proto file. 369 if oneof.Fields[0] != field { 370 return // only generate for first appearance 371 } 372 373 tags := structTags{ 374 {"protobuf_oneof", string(oneof.Desc.Name())}, 375 } 376 if m.isTracked { 377 tags = append(tags, gotrackTags...) 378 } 379 380 g.Annotate(m.GoIdent.GoName+"."+oneof.GoName, oneof.Location) 381 leadingComments := oneof.Comments.Leading 382 if leadingComments != "" { 383 leadingComments += "\n" 384 } 385 ss := []string{fmt.Sprintf(" Types that are assignable to %s:\n", oneof.GoName)} 386 for _, field := range oneof.Fields { 387 ss = append(ss, "\t*"+field.GoIdent.GoName+"\n") 388 } 389 leadingComments += protogen.Comments(strings.Join(ss, "")) 390 g.P(leadingComments, 391 oneof.GoName, " ", oneofInterfaceName(oneof), tags) 392 sf.append(oneof.GoName) 393 return 394 } 395 goType, pointer := fieldGoType(g, f, field) 396 if pointer { 397 goType = "*" + goType 398 } 399 tags := structTags{ 400 {"protobuf", fieldProtobufTagValue(field)}, 401 {"json", fieldJSONTagValue(field)}, 402 } 403 if field.Desc.IsMap() { 404 key := field.Message.Fields[0] 405 val := field.Message.Fields[1] 406 tags = append(tags, structTags{ 407 {"protobuf_key", fieldProtobufTagValue(key)}, 408 {"protobuf_val", fieldProtobufTagValue(val)}, 409 }...) 410 } 411 if m.isTracked { 412 tags = append(tags, gotrackTags...) 413 } 414 415 name := field.GoName 416 if field.Desc.IsWeak() { 417 name = genid.WeakFieldPrefix_goname + name 418 } 419 g.Annotate(m.GoIdent.GoName+"."+name, field.Location) 420 leadingComments := appendDeprecationSuffix(field.Comments.Leading, 421 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 422 g.P(leadingComments, 423 name, " ", goType, tags, 424 trailingComment(field.Comments.Trailing)) 425 sf.append(field.GoName) 426} 427 428// genMessageDefaultDecls generates consts and vars holding the default 429// values of fields. 430func genMessageDefaultDecls(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 431 var consts, vars []string 432 for _, field := range m.Fields { 433 if !field.Desc.HasDefault() { 434 continue 435 } 436 name := "Default_" + m.GoIdent.GoName + "_" + field.GoName 437 goType, _ := fieldGoType(g, f, field) 438 defVal := field.Desc.Default() 439 switch field.Desc.Kind() { 440 case protoreflect.StringKind: 441 consts = append(consts, fmt.Sprintf("%s = %s(%q)", name, goType, defVal.String())) 442 case protoreflect.BytesKind: 443 vars = append(vars, fmt.Sprintf("%s = %s(%q)", name, goType, defVal.Bytes())) 444 case protoreflect.EnumKind: 445 idx := field.Desc.DefaultEnumValue().Index() 446 val := field.Enum.Values[idx] 447 consts = append(consts, fmt.Sprintf("%s = %s", name, g.QualifiedGoIdent(val.GoIdent))) 448 case protoreflect.FloatKind, protoreflect.DoubleKind: 449 if f := defVal.Float(); math.IsNaN(f) || math.IsInf(f, 0) { 450 var fn, arg string 451 switch f := defVal.Float(); { 452 case math.IsInf(f, -1): 453 fn, arg = g.QualifiedGoIdent(mathPackage.Ident("Inf")), "-1" 454 case math.IsInf(f, +1): 455 fn, arg = g.QualifiedGoIdent(mathPackage.Ident("Inf")), "+1" 456 case math.IsNaN(f): 457 fn, arg = g.QualifiedGoIdent(mathPackage.Ident("NaN")), "" 458 } 459 vars = append(vars, fmt.Sprintf("%s = %s(%s(%s))", name, goType, fn, arg)) 460 } else { 461 consts = append(consts, fmt.Sprintf("%s = %s(%v)", name, goType, f)) 462 } 463 default: 464 consts = append(consts, fmt.Sprintf("%s = %s(%v)", name, goType, defVal.Interface())) 465 } 466 } 467 if len(consts) > 0 { 468 g.P("// Default values for ", m.GoIdent, " fields.") 469 g.P("const (") 470 for _, s := range consts { 471 g.P(s) 472 } 473 g.P(")") 474 } 475 if len(vars) > 0 { 476 g.P("// Default values for ", m.GoIdent, " fields.") 477 g.P("var (") 478 for _, s := range vars { 479 g.P(s) 480 } 481 g.P(")") 482 } 483 g.P() 484} 485 486func genMessageMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 487 genMessageBaseMethods(g, f, m) 488 genMessageGetterMethods(g, f, m) 489 genMessageSetterMethods(g, f, m) 490} 491 492func genMessageBaseMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 493 // Reset method. 494 g.P("func (x *", m.GoIdent, ") Reset() {") 495 g.P("*x = ", m.GoIdent, "{}") 496 g.P("if ", protoimplPackage.Ident("UnsafeEnabled"), " {") 497 g.P("mi := &", messageTypesVarName(f), "[", f.allMessagesByPtr[m], "]") 498 g.P("ms := ", protoimplPackage.Ident("X"), ".MessageStateOf(", protoimplPackage.Ident("Pointer"), "(x))") 499 g.P("ms.StoreMessageInfo(mi)") 500 g.P("}") 501 g.P("}") 502 g.P() 503 504 // String method. 505 g.P("func (x *", m.GoIdent, ") String() string {") 506 g.P("return ", protoimplPackage.Ident("X"), ".MessageStringOf(x)") 507 g.P("}") 508 g.P() 509 510 // ProtoMessage method. 511 g.P("func (*", m.GoIdent, ") ProtoMessage() {}") 512 g.P() 513 514 // ProtoReflect method. 515 genMessageReflectMethods(g, f, m) 516 517 // Descriptor method. 518 if m.genRawDescMethod { 519 var indexes []string 520 for i := 1; i < len(m.Location.Path); i += 2 { 521 indexes = append(indexes, strconv.Itoa(int(m.Location.Path[i]))) 522 } 523 g.P("// Deprecated: Use ", m.GoIdent, ".ProtoReflect.Descriptor instead.") 524 g.P("func (*", m.GoIdent, ") Descriptor() ([]byte, []int) {") 525 g.P("return ", rawDescVarName(f), "GZIP(), []int{", strings.Join(indexes, ","), "}") 526 g.P("}") 527 g.P() 528 f.needRawDesc = true 529 } 530 531 // ExtensionRangeArray method. 532 extRanges := m.Desc.ExtensionRanges() 533 if m.genExtRangeMethod && extRanges.Len() > 0 { 534 protoExtRange := protoifacePackage.Ident("ExtensionRangeV1") 535 extRangeVar := "extRange_" + m.GoIdent.GoName 536 g.P("var ", extRangeVar, " = []", protoExtRange, " {") 537 for i := 0; i < extRanges.Len(); i++ { 538 r := extRanges.Get(i) 539 g.P("{Start:", r[0], ", End:", r[1]-1 /* inclusive */, "},") 540 } 541 g.P("}") 542 g.P() 543 g.P("// Deprecated: Use ", m.GoIdent, ".ProtoReflect.Descriptor.ExtensionRanges instead.") 544 g.P("func (*", m.GoIdent, ") ExtensionRangeArray() []", protoExtRange, " {") 545 g.P("return ", extRangeVar) 546 g.P("}") 547 g.P() 548 } 549} 550 551func genMessageGetterMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 552 for _, field := range m.Fields { 553 genNoInterfacePragma(g, m.isTracked) 554 555 // Getter for parent oneof. 556 if oneof := field.Oneof; oneof != nil && oneof.Fields[0] == field && !oneof.Desc.IsSynthetic() { 557 g.Annotate(m.GoIdent.GoName+".Get"+oneof.GoName, oneof.Location) 558 g.P("func (m *", m.GoIdent.GoName, ") Get", oneof.GoName, "() ", oneofInterfaceName(oneof), " {") 559 g.P("if m != nil {") 560 g.P("return m.", oneof.GoName) 561 g.P("}") 562 g.P("return nil") 563 g.P("}") 564 g.P() 565 } 566 567 // Getter for message field. 568 goType, pointer := fieldGoType(g, f, field) 569 defaultValue := fieldDefaultValue(g, m, field) 570 g.Annotate(m.GoIdent.GoName+".Get"+field.GoName, field.Location) 571 leadingComments := appendDeprecationSuffix("", 572 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 573 switch { 574 case field.Desc.IsWeak(): 575 g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", protoPackage.Ident("Message"), "{") 576 g.P("var w ", protoimplPackage.Ident("WeakFields")) 577 g.P("if x != nil {") 578 g.P("w = x.", genid.WeakFields_goname) 579 if m.isTracked { 580 g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName) 581 } 582 g.P("}") 583 g.P("return ", protoimplPackage.Ident("X"), ".GetWeak(w, ", field.Desc.Number(), ", ", strconv.Quote(string(field.Message.Desc.FullName())), ")") 584 g.P("}") 585 case field.Oneof != nil && !field.Oneof.Desc.IsSynthetic(): 586 g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", goType, " {") 587 g.P("if x, ok := x.Get", field.Oneof.GoName, "().(*", field.GoIdent, "); ok {") 588 g.P("return x.", field.GoName) 589 g.P("}") 590 g.P("return ", defaultValue) 591 g.P("}") 592 default: 593 g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", goType, " {") 594 if !field.Desc.HasPresence() || defaultValue == "nil" { 595 g.P("if x != nil {") 596 } else { 597 g.P("if x != nil && x.", field.GoName, " != nil {") 598 } 599 star := "" 600 if pointer { 601 star = "*" 602 } 603 g.P("return ", star, " x.", field.GoName) 604 g.P("}") 605 g.P("return ", defaultValue) 606 g.P("}") 607 } 608 g.P() 609 } 610} 611 612func genMessageSetterMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 613 for _, field := range m.Fields { 614 if !field.Desc.IsWeak() { 615 continue 616 } 617 618 genNoInterfacePragma(g, m.isTracked) 619 620 g.Annotate(m.GoIdent.GoName+".Set"+field.GoName, field.Location) 621 leadingComments := appendDeprecationSuffix("", 622 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 623 g.P(leadingComments, "func (x *", m.GoIdent, ") Set", field.GoName, "(v ", protoPackage.Ident("Message"), ") {") 624 g.P("var w *", protoimplPackage.Ident("WeakFields")) 625 g.P("if x != nil {") 626 g.P("w = &x.", genid.WeakFields_goname) 627 if m.isTracked { 628 g.P("_ = x.", genid.WeakFieldPrefix_goname+field.GoName) 629 } 630 g.P("}") 631 g.P(protoimplPackage.Ident("X"), ".SetWeak(w, ", field.Desc.Number(), ", ", strconv.Quote(string(field.Message.Desc.FullName())), ", v)") 632 g.P("}") 633 g.P() 634 } 635} 636 637// fieldGoType returns the Go type used for a field. 638// 639// If it returns pointer=true, the struct field is a pointer to the type. 640func fieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field) (goType string, pointer bool) { 641 if field.Desc.IsWeak() { 642 return "struct{}", false 643 } 644 645 pointer = field.Desc.HasPresence() 646 switch field.Desc.Kind() { 647 case protoreflect.BoolKind: 648 goType = "bool" 649 case protoreflect.EnumKind: 650 goType = g.QualifiedGoIdent(field.Enum.GoIdent) 651 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: 652 goType = "int32" 653 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 654 goType = "uint32" 655 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: 656 goType = "int64" 657 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 658 goType = "uint64" 659 case protoreflect.FloatKind: 660 goType = "float32" 661 case protoreflect.DoubleKind: 662 goType = "float64" 663 case protoreflect.StringKind: 664 goType = "string" 665 case protoreflect.BytesKind: 666 goType = "[]byte" 667 pointer = false // rely on nullability of slices for presence 668 case protoreflect.MessageKind, protoreflect.GroupKind: 669 goType = "*" + g.QualifiedGoIdent(field.Message.GoIdent) 670 pointer = false // pointer captured as part of the type 671 } 672 switch { 673 case field.Desc.IsList(): 674 return "[]" + goType, false 675 case field.Desc.IsMap(): 676 keyType, _ := fieldGoType(g, f, field.Message.Fields[0]) 677 valType, _ := fieldGoType(g, f, field.Message.Fields[1]) 678 return fmt.Sprintf("map[%v]%v", keyType, valType), false 679 } 680 return goType, pointer 681} 682 683func fieldProtobufTagValue(field *protogen.Field) string { 684 var enumName string 685 if field.Desc.Kind() == protoreflect.EnumKind { 686 enumName = protoimpl.X.LegacyEnumName(field.Enum.Desc) 687 } 688 return tag.Marshal(field.Desc, enumName) 689} 690 691func fieldDefaultValue(g *protogen.GeneratedFile, m *messageInfo, field *protogen.Field) string { 692 if field.Desc.IsList() { 693 return "nil" 694 } 695 if field.Desc.HasDefault() { 696 defVarName := "Default_" + m.GoIdent.GoName + "_" + field.GoName 697 if field.Desc.Kind() == protoreflect.BytesKind { 698 return "append([]byte(nil), " + defVarName + "...)" 699 } 700 return defVarName 701 } 702 switch field.Desc.Kind() { 703 case protoreflect.BoolKind: 704 return "false" 705 case protoreflect.StringKind: 706 return `""` 707 case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind: 708 return "nil" 709 case protoreflect.EnumKind: 710 return g.QualifiedGoIdent(field.Enum.Values[0].GoIdent) 711 default: 712 return "0" 713 } 714} 715 716func fieldJSONTagValue(field *protogen.Field) string { 717 return string(field.Desc.Name()) + ",omitempty" 718} 719 720func genExtensions(g *protogen.GeneratedFile, f *fileInfo) { 721 if len(f.allExtensions) == 0 { 722 return 723 } 724 725 g.P("var ", extensionTypesVarName(f), " = []", protoimplPackage.Ident("ExtensionInfo"), "{") 726 for _, x := range f.allExtensions { 727 g.P("{") 728 g.P("ExtendedType: (*", x.Extendee.GoIdent, ")(nil),") 729 goType, pointer := fieldGoType(g, f, x.Extension) 730 if pointer { 731 goType = "*" + goType 732 } 733 g.P("ExtensionType: (", goType, ")(nil),") 734 g.P("Field: ", x.Desc.Number(), ",") 735 g.P("Name: ", strconv.Quote(string(x.Desc.FullName())), ",") 736 g.P("Tag: ", strconv.Quote(fieldProtobufTagValue(x.Extension)), ",") 737 g.P("Filename: ", strconv.Quote(f.Desc.Path()), ",") 738 g.P("},") 739 } 740 g.P("}") 741 g.P() 742 743 // Group extensions by the target message. 744 var orderedTargets []protogen.GoIdent 745 allExtensionsByTarget := make(map[protogen.GoIdent][]*extensionInfo) 746 allExtensionsByPtr := make(map[*extensionInfo]int) 747 for i, x := range f.allExtensions { 748 target := x.Extendee.GoIdent 749 if len(allExtensionsByTarget[target]) == 0 { 750 orderedTargets = append(orderedTargets, target) 751 } 752 allExtensionsByTarget[target] = append(allExtensionsByTarget[target], x) 753 allExtensionsByPtr[x] = i 754 } 755 for _, target := range orderedTargets { 756 g.P("// Extension fields to ", target, ".") 757 g.P("var (") 758 for _, x := range allExtensionsByTarget[target] { 759 xd := x.Desc 760 typeName := xd.Kind().String() 761 switch xd.Kind() { 762 case protoreflect.EnumKind: 763 typeName = string(xd.Enum().FullName()) 764 case protoreflect.MessageKind, protoreflect.GroupKind: 765 typeName = string(xd.Message().FullName()) 766 } 767 fieldName := string(xd.Name()) 768 769 leadingComments := x.Comments.Leading 770 if leadingComments != "" { 771 leadingComments += "\n" 772 } 773 leadingComments += protogen.Comments(fmt.Sprintf(" %v %v %v = %v;\n", 774 xd.Cardinality(), typeName, fieldName, xd.Number())) 775 leadingComments = appendDeprecationSuffix(leadingComments, 776 x.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 777 g.P(leadingComments, 778 "E_", x.GoIdent, " = &", extensionTypesVarName(f), "[", allExtensionsByPtr[x], "]", 779 trailingComment(x.Comments.Trailing)) 780 } 781 g.P(")") 782 g.P() 783 } 784} 785 786// genMessageOneofWrapperTypes generates the oneof wrapper types and 787// associates the types with the parent message type. 788func genMessageOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 789 for _, oneof := range m.Oneofs { 790 if oneof.Desc.IsSynthetic() { 791 continue 792 } 793 ifName := oneofInterfaceName(oneof) 794 g.P("type ", ifName, " interface {") 795 g.P(ifName, "()") 796 g.P("}") 797 g.P() 798 for _, field := range oneof.Fields { 799 g.Annotate(field.GoIdent.GoName, field.Location) 800 g.Annotate(field.GoIdent.GoName+"."+field.GoName, field.Location) 801 g.P("type ", field.GoIdent, " struct {") 802 goType, _ := fieldGoType(g, f, field) 803 tags := structTags{ 804 {"protobuf", fieldProtobufTagValue(field)}, 805 } 806 if m.isTracked { 807 tags = append(tags, gotrackTags...) 808 } 809 leadingComments := appendDeprecationSuffix(field.Comments.Leading, 810 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 811 g.P(leadingComments, 812 field.GoName, " ", goType, tags, 813 trailingComment(field.Comments.Trailing)) 814 g.P("}") 815 g.P() 816 } 817 for _, field := range oneof.Fields { 818 g.P("func (*", field.GoIdent, ") ", ifName, "() {}") 819 g.P() 820 } 821 } 822} 823 824// oneofInterfaceName returns the name of the interface type implemented by 825// the oneof field value types. 826func oneofInterfaceName(oneof *protogen.Oneof) string { 827 return "is" + oneof.GoIdent.GoName 828} 829 830// genNoInterfacePragma generates a standalone "nointerface" pragma to 831// decorate methods with field-tracking support. 832func genNoInterfacePragma(g *protogen.GeneratedFile, tracked bool) { 833 if tracked { 834 g.P("//go:nointerface") 835 g.P() 836 } 837} 838 839var gotrackTags = structTags{{"go", "track"}} 840 841// structTags is a data structure for build idiomatic Go struct tags. 842// Each [2]string is a key-value pair, where value is the unescaped string. 843// 844// Example: structTags{{"key", "value"}}.String() -> `key:"value"` 845type structTags [][2]string 846 847func (tags structTags) String() string { 848 if len(tags) == 0 { 849 return "" 850 } 851 var ss []string 852 for _, tag := range tags { 853 // NOTE: When quoting the value, we need to make sure the backtick 854 // character does not appear. Convert all cases to the escaped hex form. 855 key := tag[0] 856 val := strings.Replace(strconv.Quote(tag[1]), "`", `\x60`, -1) 857 ss = append(ss, fmt.Sprintf("%s:%s", key, val)) 858 } 859 return "`" + strings.Join(ss, " ") + "`" 860} 861 862// appendDeprecationSuffix optionally appends a deprecation notice as a suffix. 863func appendDeprecationSuffix(prefix protogen.Comments, deprecated bool) protogen.Comments { 864 if !deprecated { 865 return prefix 866 } 867 if prefix != "" { 868 prefix += "\n" 869 } 870 return prefix + " Deprecated: Do not use.\n" 871} 872 873// trailingComment is like protogen.Comments, but lacks a trailing newline. 874type trailingComment protogen.Comments 875 876func (c trailingComment) String() string { 877 s := strings.TrimSuffix(protogen.Comments(c).String(), "\n") 878 if strings.Contains(s, "\n") { 879 // We don't support multi-lined trailing comments as it is unclear 880 // how to best render them in the generated code. 881 return "" 882 } 883 return s 884} 885