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