github.com/RomiChan/protobuf@v0.1.1-0.20230204044148-2ed269a2e54d/internal/generator/main.go (about) 1 // protocolbuffers/protobuf-go cmd/protoc-gen-go/internal_gengo/main.go 2 // https://github.com/protocolbuffers/protobuf-go/blob/master/cmd/protoc-gen-go/internal_gengo/main.go 3 // 4 // Copyright © 2018 The Go Authors. All rights reserved. 5 // Portions Copyright © 2021 RomiChan 6 // 7 // Redistribution and use in source and binary forms, with or without 8 // modification, are permitted provided that the following conditions are 9 // met: 10 // 11 // * Redistributions of source code must retain the above copyright 12 // notice, this list of conditions and the following disclaimer. 13 // * Redistributions in binary form must reproduce the above 14 // copyright notice, this list of conditions and the following disclaimer 15 // in the documentation and/or other materials provided with the 16 // distribution. 17 // * Neither the name of Google Inc. nor the names of its 18 // contributors may be used to endorse or promote products derived from 19 // this software without specific prior written permission. 20 // 21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33 package generator 34 35 import ( 36 "fmt" 37 "go/ast" 38 "go/parser" 39 "go/token" 40 "strconv" 41 "strings" 42 "unicode" 43 "unicode/utf8" 44 45 "google.golang.org/protobuf/compiler/protogen" 46 "google.golang.org/protobuf/reflect/protoreflect" 47 "google.golang.org/protobuf/types/descriptorpb" 48 ) 49 50 var protoPackage = protogen.GoImportPath("github.com/RomiChan/protobuf/proto") 51 52 // GenerateFile generates the contents of a .pb.go file. 53 func GenerateFile(gen *protogen.Plugin, file *protogen.File) *protogen.GeneratedFile { 54 filename := file.GeneratedFilenamePrefix + ".pb.go" 55 g := gen.NewGeneratedFile(filename, file.GoImportPath) 56 f := newFileInfo(file) 57 58 genGeneratedHeader(gen, g, f) 59 60 packageDoc := "" 61 g.P(packageDoc, "package ", f.GoPackageName) 62 g.P() 63 64 for i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ { 65 genImport(gen, g, f, imps.Get(i)) 66 } 67 for _, enum := range f.allEnums { 68 genEnum(g, f, enum) 69 } 70 for _, message := range f.allMessages { 71 genMessage(g, f, message) 72 } 73 74 return g 75 } 76 77 func genGeneratedHeader(_ *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) { 78 g.P("// Code generated by protoc-gen-golite. DO NOT EDIT.") 79 80 if f.Proto.GetOptions().GetDeprecated() { 81 g.P("// ", f.Desc.Path(), " is a deprecated file.") 82 } else { 83 g.P("// source: ", f.Desc.Path()) 84 } 85 g.P() 86 } 87 88 func genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, imp protoreflect.FileImport) { 89 impFile, ok := gen.FilesByPath[imp.Path()] 90 if !ok { 91 return 92 } 93 if impFile.GoImportPath == f.GoImportPath { 94 // Don't generate imports or aliases for types in the same Go package. 95 return 96 } 97 // Generate imports for all non-weak dependencies, even if they are not 98 // referenced, because other code and tools depend on having the 99 // full transitive closure of protocol buffer types in the binary. 100 if !imp.IsWeak { 101 g.Import(impFile.GoImportPath) 102 } 103 if !imp.IsPublic { 104 return 105 } 106 107 // Generate public imports by generating the imported file, parsing it, 108 // and extracting every symbol that should receive a forwarding declaration. 109 impGen := GenerateFile(gen, impFile) 110 impGen.Skip() 111 b, err := impGen.Content() 112 if err != nil { 113 gen.Error(err) 114 return 115 } 116 fset := token.NewFileSet() 117 astFile, err := parser.ParseFile(fset, "", b, parser.ParseComments) 118 if err != nil { 119 gen.Error(err) 120 return 121 } 122 genForward := func(tok token.Token, name string, expr ast.Expr) { 123 // Don't import unexported symbols. 124 r, _ := utf8.DecodeRuneInString(name) 125 if !unicode.IsUpper(r) { 126 return 127 } 128 // Don't import the FileDescriptor. 129 if name == impFile.GoDescriptorIdent.GoName { 130 return 131 } 132 // Don't import decls referencing a symbol defined in another package. 133 // i.e., don't import decls which are themselves public imports: 134 // 135 // type T = somepackage.T 136 if _, ok := expr.(*ast.SelectorExpr); ok { 137 return 138 } 139 g.P(tok, " ", name, " = ", impFile.GoImportPath.Ident(name)) 140 } 141 g.P("// Symbols defined in public import of ", imp.Path(), ".") 142 g.P() 143 for _, decl := range astFile.Decls { 144 switch decl := decl.(type) { 145 case *ast.GenDecl: 146 for _, spec := range decl.Specs { 147 switch spec := spec.(type) { 148 case *ast.TypeSpec: 149 genForward(decl.Tok, spec.Name.Name, spec.Type) 150 case *ast.ValueSpec: 151 for i, name := range spec.Names { 152 var expr ast.Expr 153 if i < len(spec.Values) { 154 expr = spec.Values[i] 155 } 156 genForward(decl.Tok, name.Name, expr) 157 } 158 case *ast.ImportSpec: 159 default: 160 panic(fmt.Sprintf("can't generate forward for spec type %T", spec)) 161 } 162 } 163 } 164 } 165 g.P() 166 } 167 168 func genEnum(g *protogen.GeneratedFile, _ *fileInfo, e *enumInfo) { 169 // Enum type declaration. 170 g.Annotate(e.GoIdent.GoName, e.Location) 171 leadingComments := appendDeprecationSuffix(e.Comments.Leading, 172 e.Desc.Options().(*descriptorpb.EnumOptions).GetDeprecated()) 173 g.P(leadingComments, 174 "type ", e.GoIdent, "= int32") 175 176 // Enum value constants. 177 g.P("const (") 178 for _, value := range e.Values { 179 g.Annotate(value.GoIdent.GoName, value.Location) 180 leadingComments := appendDeprecationSuffix(value.Comments.Leading, 181 value.Desc.Options().(*descriptorpb.EnumValueOptions).GetDeprecated()) 182 g.P(leadingComments, 183 value.GoIdent, " ", e.GoIdent, " = ", value.Desc.Number(), 184 trailingComment(value.Comments.Trailing)) 185 } 186 g.P(")") 187 g.P() 188 189 // Enum method. 190 // 191 // NOTE: A pointer value is needed to represent presence in proto2. 192 // Since a proto2 message can reference a proto3 enum, it is useful to 193 // always generate this method (even on proto3 enums) to support that case. 194 /* 195 g.P("func (x ", e.GoIdent, ") Enum() *", e.GoIdent, " {") 196 g.P("p := new(", e.GoIdent, ")") 197 g.P("*p = x") 198 g.P("return p") 199 g.P("}") 200 g.P() 201 */ 202 203 // String method. 204 // todo: gen string method 205 } 206 207 func genMessage(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 208 if m.Desc.IsMapEntry() { 209 return 210 } 211 212 // Message type declaration. 213 g.Annotate(m.GoIdent.GoName, m.Location) 214 leadingComments := appendDeprecationSuffix(m.Comments.Leading, 215 m.Desc.Options().(*descriptorpb.MessageOptions).GetDeprecated()) 216 g.P(leadingComments, 217 "type ", m.GoIdent, " struct {") 218 genMessageFields(g, f, m) 219 g.P("}") 220 g.P() 221 222 genMessageMethods(g, f, m) 223 genMessageOneofWrapperTypes(g, f, m) 224 } 225 226 func genMessageFields(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 227 sf := f.allMessageFieldsByPtr[m] 228 f.comparable = true 229 for _, field := range m.Fields { 230 genMessageField(g, f, m, field, sf) 231 } 232 if f.comparable { 233 g.P("_ [0]func()") 234 } 235 } 236 237 func genMessageField(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field, sf *structFields) { 238 if oneof := field.Oneof; oneof != nil && !oneof.Desc.IsSynthetic() { 239 // It would be a bit simpler to iterate over the oneofs below, 240 // but generating the field here keeps the contents of the Go 241 // struct in the same order as the contents of the source 242 // .proto file. 243 if oneof.Fields[0] != field { 244 return // only generate for first appearance 245 } 246 247 tags := structTags{ 248 {"protobuf_oneof", string(oneof.Desc.Name())}, 249 } 250 if m.isTracked { 251 tags = append(tags, gotrackTags...) 252 } 253 254 g.Annotate(m.GoIdent.GoName+"."+oneof.GoName, oneof.Location) 255 leadingComments := oneof.Comments.Leading 256 if leadingComments != "" { 257 leadingComments += "\n" 258 } 259 ss := []string{fmt.Sprintf(" Types that are assignable to %s:\n", oneof.GoName)} 260 for _, field := range oneof.Fields { 261 ss = append(ss, "\t*"+field.GoIdent.GoName+"\n") 262 } 263 leadingComments += protogen.Comments(strings.Join(ss, "")) 264 g.P(leadingComments, 265 oneof.GoName, " ", oneofInterfaceName(oneof), tags) 266 sf.append(oneof.GoName) 267 return 268 } 269 goType, option, comp := fieldGoType(g, f, field) 270 f.comparable = f.comparable && comp 271 if option { 272 goType = g.QualifiedGoIdent(protoPackage.Ident("Option[" + goType + "]")) 273 } 274 tags := structTags{ 275 {"protobuf", fieldProtobufTagValue(field)}, 276 } 277 if field.Desc.IsMap() { 278 key := field.Message.Fields[0] 279 val := field.Message.Fields[1] 280 tags = append(tags, structTags{ 281 {"protobuf_key", fieldProtobufTagValue(key)}, 282 {"protobuf_val", fieldProtobufTagValue(val)}, 283 }...) 284 } 285 if m.isTracked { 286 tags = append(tags, gotrackTags...) 287 } 288 289 name := field.GoName 290 g.Annotate(m.GoIdent.GoName+"."+name, field.Location) 291 leadingComments := appendDeprecationSuffix(field.Comments.Leading, 292 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 293 g.P(leadingComments, 294 name, " ", goType, tags, 295 trailingComment(field.Comments.Trailing)) 296 sf.append(field.GoName) 297 } 298 299 func genMessageMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 300 genMessageGetterMethods(g, f, m) 301 } 302 303 func genMessageGetterMethods(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 304 for _, field := range m.Fields { 305 genNoInterfacePragma(g, m.isTracked) 306 307 // Getter for parent oneof. 308 if oneof := field.Oneof; oneof != nil && oneof.Fields[0] == field && !oneof.Desc.IsSynthetic() { 309 g.Annotate(m.GoIdent.GoName+".Get"+oneof.GoName, oneof.Location) 310 g.P("func (m *", m.GoIdent.GoName, ") Get", oneof.GoName, "() ", oneofInterfaceName(oneof), " {") 311 g.P("if m != nil {") 312 g.P("return m.", oneof.GoName) 313 g.P("}") 314 g.P("return nil") 315 g.P("}") 316 g.P() 317 } 318 319 // Getter for message field. 320 goType, _, _ := fieldGoType(g, f, field) 321 defaultValue := fieldDefaultValue(g, f, m, field) 322 g.Annotate(m.GoIdent.GoName+".Get"+field.GoName, field.Location) 323 leadingComments := appendDeprecationSuffix("", 324 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 325 switch { 326 case field.Oneof != nil && !field.Oneof.Desc.IsSynthetic(): 327 g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", goType, " {") 328 g.P("if x, ok := x.Get", field.Oneof.GoName, "().(*", field.GoIdent, "); ok {") 329 g.P("return x.", field.GoName) 330 g.P("}") 331 g.P("return ", defaultValue) 332 g.P("}") 333 default: 334 /* 335 if !field.Desc.HasPresence() || defaultValue == "nil" { 336 continue 337 } 338 g.P(leadingComments, "func (x *", m.GoIdent, ") Get", field.GoName, "() ", goType, " {") 339 g.P("if x != nil && x.", field.GoName, " != nil {") 340 star := "" 341 if pointer { 342 star = "*" 343 } 344 g.P("return ", star, " x.", field.GoName) 345 g.P("}") 346 g.P("return ", defaultValue) 347 g.P("}")' 348 */ 349 } 350 g.P() 351 } 352 } 353 354 // fieldGoType returns the Go type used for a field. 355 // 356 // If it returns pointer=true, the struct field is a pointer to the type. 357 func fieldGoType(g *protogen.GeneratedFile, f *fileInfo, field *protogen.Field) (goType string, pointer bool, comparable bool) { 358 if field.Desc.IsWeak() { 359 return "struct{}", false, true 360 } 361 362 pointer = field.Desc.HasPresence() 363 comparable = true 364 switch field.Desc.Kind() { 365 case protoreflect.BoolKind: 366 goType = "bool" 367 case protoreflect.EnumKind: 368 goType = g.QualifiedGoIdent(field.Enum.GoIdent) 369 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind: 370 goType = "int32" 371 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind: 372 goType = "uint32" 373 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind: 374 goType = "int64" 375 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind: 376 goType = "uint64" 377 case protoreflect.FloatKind: 378 goType = "float32" 379 case protoreflect.DoubleKind: 380 goType = "float64" 381 case protoreflect.StringKind: 382 goType = "string" 383 case protoreflect.BytesKind: 384 goType = "[]byte" 385 comparable = false 386 pointer = false // rely on nullability of slices for presence 387 case protoreflect.MessageKind, protoreflect.GroupKind: 388 goType = "*" + g.QualifiedGoIdent(field.Message.GoIdent) 389 pointer = false // pointer captured as part of the type 390 } 391 switch { 392 case field.Desc.IsList(): 393 return "[]" + goType, false, false 394 case field.Desc.IsMap(): 395 keyType, _, _ := fieldGoType(g, f, field.Message.Fields[0]) 396 valType, _, _ := fieldGoType(g, f, field.Message.Fields[1]) 397 return fmt.Sprintf("map[%v]%v", keyType, valType), false, false 398 } 399 return goType, pointer, comparable 400 } 401 402 func fieldProtobufTagValue(field *protogen.Field) string { 403 fd := field.Desc 404 var tag []string 405 switch fd.Kind() { 406 case protoreflect.BoolKind, protoreflect.EnumKind, protoreflect.Int32Kind, protoreflect.Uint32Kind, protoreflect.Int64Kind, protoreflect.Uint64Kind: 407 tag = append(tag, "varint") 408 case protoreflect.Sint32Kind: 409 tag = append(tag, "zigzag32") 410 case protoreflect.Sint64Kind: 411 tag = append(tag, "zigzag64") 412 case protoreflect.Sfixed32Kind, protoreflect.Fixed32Kind, protoreflect.FloatKind: 413 tag = append(tag, "fixed32") 414 case protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind, protoreflect.DoubleKind: 415 tag = append(tag, "fixed64") 416 case protoreflect.StringKind, protoreflect.BytesKind, protoreflect.MessageKind: 417 tag = append(tag, "bytes") 418 case protoreflect.GroupKind: 419 tag = append(tag, "group") 420 } 421 tag = append(tag, strconv.Itoa(int(fd.Number()))) 422 switch fd.Cardinality() { 423 case protoreflect.Optional: 424 tag = append(tag, "opt") 425 case protoreflect.Required: 426 tag = append(tag, "req") 427 case protoreflect.Repeated: 428 tag = append(tag, "rep") 429 } 430 return strings.Join(tag, ",") 431 } 432 433 func fieldDefaultValue(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo, field *protogen.Field) string { 434 if field.Desc.IsList() { 435 return "nil" 436 } 437 switch field.Desc.Kind() { 438 case protoreflect.BoolKind: 439 return "false" 440 case protoreflect.StringKind: 441 return `""` 442 case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind: 443 return "nil" 444 case protoreflect.EnumKind: 445 val := field.Enum.Values[0] 446 if val.GoIdent.GoImportPath == f.GoImportPath { 447 return g.QualifiedGoIdent(val.GoIdent) 448 } else { 449 // If the enum value is declared in a different Go package, 450 // reference it by number since the name may not be correct. 451 // See https://github.com/golang/protobuf/issues/513. 452 return g.QualifiedGoIdent(field.Enum.GoIdent) + "(" + strconv.FormatInt(int64(val.Desc.Number()), 10) + ")" 453 } 454 default: 455 return "0" 456 } 457 } 458 459 // genMessageOneofWrapperTypes generates the oneof wrapper types and 460 // associates the types with the parent message type. 461 func genMessageOneofWrapperTypes(g *protogen.GeneratedFile, f *fileInfo, m *messageInfo) { 462 for _, oneof := range m.Oneofs { 463 if oneof.Desc.IsSynthetic() { 464 continue 465 } 466 ifName := oneofInterfaceName(oneof) 467 g.P("type ", ifName, " interface {") 468 g.P(ifName, "()") 469 g.P("}") 470 g.P() 471 for _, field := range oneof.Fields { 472 g.Annotate(field.GoIdent.GoName, field.Location) 473 g.Annotate(field.GoIdent.GoName+"."+field.GoName, field.Location) 474 g.P("type ", field.GoIdent, " struct {") 475 goType, _, _ := fieldGoType(g, f, field) 476 tags := structTags{ 477 {"protobuf", fieldProtobufTagValue(field)}, 478 } 479 if m.isTracked { 480 tags = append(tags, gotrackTags...) 481 } 482 leadingComments := appendDeprecationSuffix(field.Comments.Leading, 483 field.Desc.Options().(*descriptorpb.FieldOptions).GetDeprecated()) 484 g.P(leadingComments, 485 field.GoName, " ", goType, tags, 486 trailingComment(field.Comments.Trailing)) 487 g.P("}") 488 g.P() 489 } 490 for _, field := range oneof.Fields { 491 g.P("func (*", field.GoIdent, ") ", ifName, "() {}") 492 g.P() 493 } 494 } 495 } 496 497 // oneofInterfaceName returns the name of the interface type implemented by 498 // the oneof field value types. 499 func oneofInterfaceName(oneof *protogen.Oneof) string { 500 return "is" + oneof.GoIdent.GoName 501 } 502 503 // genNoInterfacePragma generates a standalone "nointerface" pragma to 504 // decorate methods with field-tracking support. 505 func genNoInterfacePragma(g *protogen.GeneratedFile, tracked bool) { 506 if tracked { 507 g.P("//go:nointerface") 508 g.P() 509 } 510 } 511 512 var gotrackTags = structTags{{"go", "track"}} 513 514 // structTags is a data structure for build idiomatic Go struct tags. 515 // Each [2]string is a key-value pair, where value is the unescaped string. 516 // 517 // Example: structTags{{"key", "value"}}.String() -> `key:"value"` 518 type structTags [][2]string 519 520 func (tags structTags) String() string { 521 if len(tags) == 0 { 522 return "" 523 } 524 var ss []string 525 for _, tag := range tags { 526 // NOTE: When quoting the value, we need to make sure the backtick 527 // character does not appear. Convert all cases to the escaped hex form. 528 key := tag[0] 529 val := strings.Replace(strconv.Quote(tag[1]), "`", `\x60`, -1) 530 ss = append(ss, fmt.Sprintf("%s:%s", key, val)) 531 } 532 return "`" + strings.Join(ss, " ") + "`" 533 } 534 535 // appendDeprecationSuffix optionally appends a deprecation notice as a suffix. 536 func appendDeprecationSuffix(protoreflectix protogen.Comments, deprecated bool) protogen.Comments { 537 if !deprecated { 538 return protoreflectix 539 } 540 if protoreflectix != "" { 541 protoreflectix += "\n" 542 } 543 return protoreflectix + " Deprecated: Do not use.\n" 544 } 545 546 // trailingComment is like protogen.Comments, but lacks a trailing newline. 547 type trailingComment protogen.Comments 548 549 func (c trailingComment) String() string { 550 s := strings.TrimSuffix(protogen.Comments(c).String(), "\n") 551 if strings.Contains(s, "\n") { 552 // We don't support multi-lined trailing comments as it is unclear 553 // how to best render them in the generated code. 554 return "" 555 } 556 return s 557 }