...

Source file src/google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo/main.go

Documentation: google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo

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

View as plain text