blob: a337c5ffd674d1a4da4cc74bcca78966da972ee8 [file] [log] [blame]
Damien Neil220c2022018-08-15 11:24:18 -07001// 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
Damien Neil1adaec92018-09-24 13:43:03 -07005// Package internal_gengo is internal to the protobuf module.
6package internal_gengo
Damien Neil220c2022018-08-15 11:24:18 -07007
8import (
Damien Neil7779e052018-09-07 14:14:06 -07009 "bytes"
10 "compress/gzip"
11 "crypto/sha256"
12 "encoding/hex"
Damien Neil2dc67182018-09-21 15:03:34 -070013 "errors"
Damien Neil3cf6e622018-09-11 13:53:14 -070014 "flag"
Damien Neil7779e052018-09-07 14:14:06 -070015 "fmt"
Damien Neilebc699d2018-09-13 08:50:13 -070016 "math"
Damien Neilce36f8d2018-09-13 15:19:08 -070017 "sort"
Damien Neil7779e052018-09-07 14:14:06 -070018 "strconv"
Damien Neilcab8dfe2018-09-06 14:51:28 -070019 "strings"
Damien Neil7779e052018-09-07 14:14:06 -070020
21 "github.com/golang/protobuf/proto"
22 descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
Joe Tsai01ab2962018-09-21 17:44:00 -070023 "github.com/golang/protobuf/v2/protogen"
24 "github.com/golang/protobuf/v2/reflect/protoreflect"
Damien Neil220c2022018-08-15 11:24:18 -070025)
26
Damien Neild4127922018-09-12 11:13:49 -070027// generatedCodeVersion indicates a version of the generated code.
28// It is incremented whenever an incompatibility between the generated code and
29// proto package is introduced; the generated code references
30// a constant, proto.ProtoPackageIsVersionN (where N is generatedCodeVersion).
31const generatedCodeVersion = 2
32
Damien Neil46abb572018-09-07 12:45:37 -070033const protoPackage = "github.com/golang/protobuf/proto"
34
Damien Neil1adaec92018-09-24 13:43:03 -070035func Main() {
Damien Neil3cf6e622018-09-11 13:53:14 -070036 var flags flag.FlagSet
Damien Neil2dc67182018-09-21 15:03:34 -070037 plugins := flags.String("plugins", "", "deprecated option")
Damien Neil3cf6e622018-09-11 13:53:14 -070038 opts := &protogen.Options{
39 ParamFunc: flags.Set,
40 }
41 protogen.Run(opts, func(gen *protogen.Plugin) error {
Damien Neil2dc67182018-09-21 15:03:34 -070042 if *plugins != "" {
43 return errors.New("protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC")
44 }
Damien Neil220c2022018-08-15 11:24:18 -070045 for _, f := range gen.Files {
46 if !f.Generate {
47 continue
48 }
49 genFile(gen, f)
50 }
51 return nil
52 })
53}
54
Damien Neild39efc82018-09-24 12:38:10 -070055type fileInfo struct {
Damien Neilcab8dfe2018-09-06 14:51:28 -070056 *protogen.File
Damien Neil46abb572018-09-07 12:45:37 -070057 locationMap map[string][]*descpb.SourceCodeInfo_Location
58 descriptorVar string // var containing the gzipped FileDescriptorProto
Damien Neilce36f8d2018-09-13 15:19:08 -070059 allEnums []*protogen.Enum
60 allMessages []*protogen.Message
Damien Neil993c04d2018-09-14 15:41:11 -070061 allExtensions []*protogen.Extension
Damien Neilcab8dfe2018-09-06 14:51:28 -070062}
63
64func genFile(gen *protogen.Plugin, file *protogen.File) {
Damien Neild39efc82018-09-24 12:38:10 -070065 f := &fileInfo{
Damien Neilcab8dfe2018-09-06 14:51:28 -070066 File: file,
67 locationMap: make(map[string][]*descpb.SourceCodeInfo_Location),
68 }
69 for _, loc := range file.Proto.GetSourceCodeInfo().GetLocation() {
70 key := pathKey(loc.Path)
71 f.locationMap[key] = append(f.locationMap[key], loc)
72 }
73
Damien Neil993c04d2018-09-14 15:41:11 -070074 // The different order for enums and extensions is to match the output
75 // of the previous implementation.
76 //
77 // TODO: Eventually make this consistent.
Damien Neilce36f8d2018-09-13 15:19:08 -070078 f.allEnums = append(f.allEnums, f.File.Enums...)
Damien Neil73ac8852018-09-17 15:11:24 -070079 walkMessages(f.Messages, func(message *protogen.Message) {
80 f.allMessages = append(f.allMessages, message)
81 f.allEnums = append(f.allEnums, message.Enums...)
82 f.allExtensions = append(f.allExtensions, message.Extensions...)
83 })
Damien Neil993c04d2018-09-14 15:41:11 -070084 f.allExtensions = append(f.allExtensions, f.File.Extensions...)
Damien Neilce36f8d2018-09-13 15:19:08 -070085
Damien Neil46abb572018-09-07 12:45:37 -070086 // Determine the name of the var holding the file descriptor:
87 //
88 // fileDescriptor_<hash of filename>
89 filenameHash := sha256.Sum256([]byte(f.Desc.Path()))
90 f.descriptorVar = fmt.Sprintf("fileDescriptor_%s", hex.EncodeToString(filenameHash[:8]))
91
Damien Neil082ce922018-09-06 10:23:53 -070092 g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath)
Damien Neil220c2022018-08-15 11:24:18 -070093 g.P("// Code generated by protoc-gen-go. DO NOT EDIT.")
Damien Neil55fe1c02018-09-17 15:11:24 -070094 if f.Proto.GetOptions().GetDeprecated() {
95 g.P("// ", f.Desc.Path(), " is a deprecated file.")
96 } else {
97 g.P("// source: ", f.Desc.Path())
98 }
Damien Neil220c2022018-08-15 11:24:18 -070099 g.P()
Damien Neilcab8dfe2018-09-06 14:51:28 -0700100 const filePackageField = 2 // FileDescriptorProto.package
101 genComment(g, f, []int32{filePackageField})
102 g.P()
Damien Neil082ce922018-09-06 10:23:53 -0700103 g.P("package ", f.GoPackageName)
Damien Neilc7d07d92018-08-22 13:46:02 -0700104 g.P()
Damien Neil1ec33152018-09-13 13:12:36 -0700105
106 // These references are not necessary, since we automatically add
107 // all necessary imports before formatting the generated file.
108 //
109 // This section exists to generate output more consistent with
110 // the previous version of protoc-gen-go, to make it easier to
111 // detect unintended variations.
112 //
113 // TODO: Eventually remove this.
114 g.P("// Reference imports to suppress errors if they are not otherwise used.")
115 g.P("var _ = ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "Marshal"})
116 g.P("var _ = ", protogen.GoIdent{GoImportPath: "fmt", GoName: "Errorf"})
117 g.P("var _ = ", protogen.GoIdent{GoImportPath: "math", GoName: "Inf"})
118 g.P()
119
Damien Neild4127922018-09-12 11:13:49 -0700120 g.P("// This is a compile-time assertion to ensure that this generated file")
121 g.P("// is compatible with the proto package it is being compiled against.")
122 g.P("// A compilation error at this line likely means your copy of the")
123 g.P("// proto package needs to be updated.")
124 g.P("const _ = ", protogen.GoIdent{
125 GoImportPath: protoPackage,
126 GoName: fmt.Sprintf("ProtoPackageIsVersion%d", generatedCodeVersion),
127 }, "// please upgrade the proto package")
128 g.P()
Damien Neilc7d07d92018-08-22 13:46:02 -0700129
Damien Neil73ac8852018-09-17 15:11:24 -0700130 for i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ {
131 genImport(gen, g, f, imps.Get(i))
132 }
Damien Neilce36f8d2018-09-13 15:19:08 -0700133 for _, enum := range f.allEnums {
Damien Neil46abb572018-09-07 12:45:37 -0700134 genEnum(gen, g, f, enum)
135 }
Damien Neilce36f8d2018-09-13 15:19:08 -0700136 for _, message := range f.allMessages {
Damien Neilcab8dfe2018-09-06 14:51:28 -0700137 genMessage(gen, g, f, message)
Damien Neilc7d07d92018-08-22 13:46:02 -0700138 }
Damien Neil993c04d2018-09-14 15:41:11 -0700139 for _, extension := range f.Extensions {
140 genExtension(gen, g, f, extension)
141 }
Damien Neil220c2022018-08-15 11:24:18 -0700142
Damien Neilce36f8d2018-09-13 15:19:08 -0700143 genInitFunction(gen, g, f)
Damien Neil7779e052018-09-07 14:14:06 -0700144 genFileDescriptor(gen, g, f)
145}
146
Damien Neil73ac8852018-09-17 15:11:24 -0700147// walkMessages calls f on each message and all of its descendants.
148func walkMessages(messages []*protogen.Message, f func(*protogen.Message)) {
149 for _, m := range messages {
150 f(m)
151 walkMessages(m.Messages, f)
152 }
153}
154
Damien Neild39efc82018-09-24 12:38:10 -0700155func genImport(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, imp protoreflect.FileImport) {
Damien Neil73ac8852018-09-17 15:11:24 -0700156 impFile, ok := gen.FileByName(imp.Path())
157 if !ok {
158 return
159 }
160 if impFile.GoImportPath == f.GoImportPath {
Damien Neil2e0c3da2018-09-19 12:51:36 -0700161 // Don't generate imports or aliases for types in the same Go package.
162 return
163 }
164 // Generate imports for all dependencies, even if they are not
165 // referenced, because other code and tools depend on having the
166 // full transitive closure of protocol buffer types in the binary.
167 g.Import(impFile.GoImportPath)
168 if !imp.IsPublic {
Damien Neil73ac8852018-09-17 15:11:24 -0700169 return
170 }
171 var enums []*protogen.Enum
172 enums = append(enums, impFile.Enums...)
173 walkMessages(impFile.Messages, func(message *protogen.Message) {
174 enums = append(enums, message.Enums...)
175 g.P("// ", message.GoIdent.GoName, " from public import ", imp.Path())
176 g.P("type ", message.GoIdent.GoName, " = ", message.GoIdent)
177 for _, oneof := range message.Oneofs {
178 for _, field := range oneof.Fields {
179 typ := fieldOneofType(field)
180 g.P("type ", typ.GoName, " = ", typ)
181 }
182 }
183 g.P()
184 })
185 for _, enum := range enums {
186 g.P("// ", enum.GoIdent.GoName, " from public import ", imp.Path())
187 g.P("type ", enum.GoIdent.GoName, " = ", enum.GoIdent)
188 g.P("var ", enum.GoIdent.GoName, "_name = ", enum.GoIdent, "_name")
189 g.P("var ", enum.GoIdent.GoName, "_value = ", enum.GoIdent, "_value")
190 g.P()
191 for _, value := range enum.Values {
192 g.P("const ", value.GoIdent.GoName, " = ", enum.GoIdent.GoName, "(", value.GoIdent, ")")
193 }
Damien Neilce36f8d2018-09-13 15:19:08 -0700194 }
195}
196
Damien Neild39efc82018-09-24 12:38:10 -0700197func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
Damien Neil7779e052018-09-07 14:14:06 -0700198 // Trim the source_code_info from the descriptor.
199 // Marshal and gzip it.
200 descProto := proto.Clone(f.Proto).(*descpb.FileDescriptorProto)
201 descProto.SourceCodeInfo = nil
202 b, err := proto.Marshal(descProto)
203 if err != nil {
204 gen.Error(err)
205 return
206 }
207 var buf bytes.Buffer
208 w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
209 w.Write(b)
210 w.Close()
211 b = buf.Bytes()
212
Damien Neil46abb572018-09-07 12:45:37 -0700213 g.P("func init() { proto.RegisterFile(", strconv.Quote(f.Desc.Path()), ", ", f.descriptorVar, ") }")
Damien Neil7779e052018-09-07 14:14:06 -0700214 g.P()
Damien Neil46abb572018-09-07 12:45:37 -0700215 g.P("var ", f.descriptorVar, " = []byte{")
Damien Neil7779e052018-09-07 14:14:06 -0700216 g.P("// ", len(b), " bytes of a gzipped FileDescriptorProto")
217 for len(b) > 0 {
218 n := 16
219 if n > len(b) {
220 n = len(b)
221 }
222
223 s := ""
224 for _, c := range b[:n] {
225 s += fmt.Sprintf("0x%02x,", c)
226 }
227 g.P(s)
228
229 b = b[n:]
230 }
231 g.P("}")
232 g.P()
Damien Neil220c2022018-08-15 11:24:18 -0700233}
Damien Neilc7d07d92018-08-22 13:46:02 -0700234
Damien Neild39efc82018-09-24 12:38:10 -0700235func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
Damien Neil46abb572018-09-07 12:45:37 -0700236 genComment(g, f, enum.Path)
Damien Neil55fe1c02018-09-17 15:11:24 -0700237 g.P("type ", enum.GoIdent, " int32",
238 deprecationComment(enumOptions(gen, enum).GetDeprecated()))
Damien Neil46abb572018-09-07 12:45:37 -0700239 g.P("const (")
240 for _, value := range enum.Values {
241 genComment(g, f, value.Path)
Damien Neil55fe1c02018-09-17 15:11:24 -0700242 g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number(),
243 deprecationComment(enumValueOptions(gen, value).GetDeprecated()))
Damien Neil46abb572018-09-07 12:45:37 -0700244 }
245 g.P(")")
246 g.P()
247 nameMap := enum.GoIdent.GoName + "_name"
248 g.P("var ", nameMap, " = map[int32]string{")
249 generated := make(map[protoreflect.EnumNumber]bool)
250 for _, value := range enum.Values {
251 duplicate := ""
252 if _, present := generated[value.Desc.Number()]; present {
253 duplicate = "// Duplicate value: "
254 }
255 g.P(duplicate, value.Desc.Number(), ": ", strconv.Quote(string(value.Desc.Name())), ",")
256 generated[value.Desc.Number()] = true
257 }
258 g.P("}")
259 g.P()
260 valueMap := enum.GoIdent.GoName + "_value"
261 g.P("var ", valueMap, " = map[string]int32{")
262 for _, value := range enum.Values {
263 g.P(strconv.Quote(string(value.Desc.Name())), ": ", value.Desc.Number(), ",")
264 }
265 g.P("}")
266 g.P()
267 if enum.Desc.Syntax() != protoreflect.Proto3 {
268 g.P("func (x ", enum.GoIdent, ") Enum() *", enum.GoIdent, " {")
269 g.P("p := new(", enum.GoIdent, ")")
270 g.P("*p = x")
271 g.P("return p")
272 g.P("}")
273 g.P()
274 }
275 g.P("func (x ", enum.GoIdent, ") String() string {")
276 g.P("return ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "EnumName"}, "(", enum.GoIdent, "_name, int32(x))")
277 g.P("}")
278 g.P()
279
280 if enum.Desc.Syntax() != protoreflect.Proto3 {
281 g.P("func (x *", enum.GoIdent, ") UnmarshalJSON(data []byte) error {")
282 g.P("value, err := ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "UnmarshalJSONEnum"}, "(", enum.GoIdent, `_value, data, "`, enum.GoIdent, `")`)
283 g.P("if err != nil {")
284 g.P("return err")
285 g.P("}")
286 g.P("*x = ", enum.GoIdent, "(value)")
287 g.P("return nil")
288 g.P("}")
289 g.P()
290 }
291
292 var indexes []string
293 for i := 1; i < len(enum.Path); i += 2 {
294 indexes = append(indexes, strconv.Itoa(int(enum.Path[i])))
295 }
296 g.P("func (", enum.GoIdent, ") EnumDescriptor() ([]byte, []int) {")
297 g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
298 g.P("}")
299 g.P()
300
301 genWellKnownType(g, enum.GoIdent, enum.Desc)
Damien Neil46abb572018-09-07 12:45:37 -0700302}
303
Damien Neil658051b2018-09-10 12:26:21 -0700304// enumRegistryName returns the name used to register an enum with the proto
305// package registry.
306//
307// Confusingly, this is <proto_package>.<go_ident>. This probably should have
308// been the full name of the proto enum type instead, but changing it at this
309// point would require thought.
310func enumRegistryName(enum *protogen.Enum) string {
311 // Find the FileDescriptor for this enum.
312 var desc protoreflect.Descriptor = enum.Desc
313 for {
314 p, ok := desc.Parent()
315 if !ok {
316 break
317 }
318 desc = p
319 }
320 fdesc := desc.(protoreflect.FileDescriptor)
321 return string(fdesc.Package()) + "." + enum.GoIdent.GoName
322}
323
Damien Neild39efc82018-09-24 12:38:10 -0700324func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
Damien Neil0bd5a382018-09-13 15:07:10 -0700325 if message.Desc.IsMapEntry() {
326 return
327 }
328
Damien Neil55fe1c02018-09-17 15:11:24 -0700329 hasComment := genComment(g, f, message.Path)
330 if messageOptions(gen, message).GetDeprecated() {
331 if hasComment {
332 g.P("//")
333 }
334 g.P(deprecationComment(true))
335 }
Damien Neilcab8dfe2018-09-06 14:51:28 -0700336 g.P("type ", message.GoIdent, " struct {")
Damien Neil658051b2018-09-10 12:26:21 -0700337 for _, field := range message.Fields {
Damien Neil1fa78d82018-09-13 13:12:36 -0700338 if field.OneofType != nil {
339 // It would be a bit simpler to iterate over the oneofs below,
340 // but generating the field here keeps the contents of the Go
341 // struct in the same order as the contents of the source
342 // .proto file.
343 if field == field.OneofType.Fields[0] {
344 genOneofField(gen, g, f, message, field.OneofType)
345 }
Damien Neil658051b2018-09-10 12:26:21 -0700346 continue
347 }
348 genComment(g, f, field.Path)
Damien Neil77f82fe2018-09-13 10:59:17 -0700349 goType, pointer := fieldGoType(g, field)
350 if pointer {
351 goType = "*" + goType
352 }
Damien Neil0bd5a382018-09-13 15:07:10 -0700353 tags := []string{
354 fmt.Sprintf("protobuf:%q", fieldProtobufTag(field)),
355 fmt.Sprintf("json:%q", fieldJSONTag(field)),
356 }
357 if field.Desc.IsMap() {
358 key := field.MessageType.Fields[0]
359 val := field.MessageType.Fields[1]
360 tags = append(tags,
361 fmt.Sprintf("protobuf_key:%q", fieldProtobufTag(key)),
362 fmt.Sprintf("protobuf_val:%q", fieldProtobufTag(val)),
363 )
364 }
Damien Neil55fe1c02018-09-17 15:11:24 -0700365 g.P(field.GoName, " ", goType, " `", strings.Join(tags, " "), "`",
366 deprecationComment(fieldOptions(gen, field).GetDeprecated()))
Damien Neil658051b2018-09-10 12:26:21 -0700367 }
368 g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
Damien Neil993c04d2018-09-14 15:41:11 -0700369
370 if message.Desc.ExtensionRanges().Len() > 0 {
371 var tags []string
372 if messageOptions(gen, message).GetMessageSetWireFormat() {
373 tags = append(tags, `protobuf_messageset:"1"`)
374 }
375 tags = append(tags, `json:"-"`)
376 g.P(protogen.GoIdent{
377 GoImportPath: protoPackage,
378 GoName: "XXX_InternalExtensions",
379 }, " `", strings.Join(tags, " "), "`")
380 }
Damien Neil658051b2018-09-10 12:26:21 -0700381 // TODO XXX_InternalExtensions
382 g.P("XXX_unrecognized []byte `json:\"-\"`")
383 g.P("XXX_sizecache int32 `json:\"-\"`")
Damien Neilc7d07d92018-08-22 13:46:02 -0700384 g.P("}")
385 g.P()
386
Damien Neila1c6abc2018-09-12 13:36:34 -0700387 // Reset
388 g.P("func (m *", message.GoIdent, ") Reset() { *m = ", message.GoIdent, "{} }")
389 // String
390 g.P("func (m *", message.GoIdent, ") String() string { return ", protogen.GoIdent{
391 GoImportPath: protoPackage,
392 GoName: "CompactTextString",
393 }, "(m) }")
394 // ProtoMessage
395 g.P("func (*", message.GoIdent, ") ProtoMessage() {}")
396 // Descriptor
397 var indexes []string
398 for i := 1; i < len(message.Path); i += 2 {
399 indexes = append(indexes, strconv.Itoa(int(message.Path[i])))
400 }
401 g.P("func (*", message.GoIdent, ") Descriptor() ([]byte, []int) {")
402 g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
403 g.P("}")
Damien Neil993c04d2018-09-14 15:41:11 -0700404 g.P()
405
406 // ExtensionRangeArray
407 if extranges := message.Desc.ExtensionRanges(); extranges.Len() > 0 {
408 if messageOptions(gen, message).GetMessageSetWireFormat() {
409 g.P("func (m *", message.GoIdent, ") MarshalJSON() ([]byte, error) {")
410 g.P("return ", protogen.GoIdent{
411 GoImportPath: protoPackage,
412 GoName: "MarshalMessageSetJSON",
413 }, "(&m.XXX_InternalExtensions)")
414 g.P("}")
415 g.P("func (m *", message.GoIdent, ") UnmarshalJSON(buf []byte) error {")
416 g.P("return ", protogen.GoIdent{
417 GoImportPath: protoPackage,
418 GoName: "UnmarshalMessageSetJSON",
419 }, "(buf, &m.XXX_InternalExtensions)")
420 g.P("}")
421 g.P()
422 }
423
424 protoExtRange := protogen.GoIdent{
425 GoImportPath: protoPackage,
426 GoName: "ExtensionRange",
427 }
428 extRangeVar := "extRange_" + message.GoIdent.GoName
429 g.P("var ", extRangeVar, " = []", protoExtRange, " {")
430 for i := 0; i < extranges.Len(); i++ {
431 r := extranges.Get(i)
432 g.P("{Start:", r[0], ", End:", r[1]-1 /* inclusive */, "},")
433 }
434 g.P("}")
435 g.P()
436 g.P("func (*", message.GoIdent, ") ExtensionRangeArray() []", protoExtRange, " {")
437 g.P("return ", extRangeVar)
438 g.P("}")
439 g.P()
440 }
Damien Neila1c6abc2018-09-12 13:36:34 -0700441
442 // Table-driven proto support.
443 //
444 // TODO: It does not scale to keep adding another method for every
445 // operation on protos that we want to switch over to using the
446 // table-driven approach. Instead, we should only add a single method
447 // that allows getting access to the *InternalMessageInfo struct and then
448 // calling Unmarshal, Marshal, Merge, Size, and Discard directly on that.
449 messageInfoVar := "xxx_messageInfo_" + message.GoIdent.GoName
450 // XXX_Unmarshal
451 g.P("func (m *", message.GoIdent, ") XXX_Unmarshal(b []byte) error {")
452 g.P("return ", messageInfoVar, ".Unmarshal(m, b)")
453 g.P("}")
454 // XXX_Marshal
455 g.P("func (m *", message.GoIdent, ") XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {")
456 g.P("return ", messageInfoVar, ".Marshal(b, m, deterministic)")
457 g.P("}")
458 // XXX_Merge
459 g.P("func (m *", message.GoIdent, ") XXX_Merge(src proto.Message) {")
460 g.P(messageInfoVar, ".Merge(m, src)")
461 g.P("}")
462 // XXX_Size
463 g.P("func (m *", message.GoIdent, ") XXX_Size() int {")
464 g.P("return ", messageInfoVar, ".Size(m)")
465 g.P("}")
466 // XXX_DiscardUnknown
467 g.P("func (m *", message.GoIdent, ") XXX_DiscardUnknown() {")
468 g.P(messageInfoVar, ".DiscardUnknown(m)")
469 g.P("}")
470 g.P()
471 g.P("var ", messageInfoVar, " ", protogen.GoIdent{
472 GoImportPath: protoPackage,
473 GoName: "InternalMessageInfo",
474 })
475 g.P()
476
Damien Neilebc699d2018-09-13 08:50:13 -0700477 // Constants and vars holding the default values of fields.
478 for _, field := range message.Fields {
479 if !field.Desc.HasDefault() {
480 continue
481 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700482 defVarName := "Default_" + message.GoIdent.GoName + "_" + field.GoName
Damien Neilebc699d2018-09-13 08:50:13 -0700483 def := field.Desc.Default()
484 switch field.Desc.Kind() {
485 case protoreflect.StringKind:
486 g.P("const ", defVarName, " string = ", strconv.Quote(def.String()))
487 case protoreflect.BytesKind:
488 g.P("var ", defVarName, " []byte = []byte(", strconv.Quote(string(def.Bytes())), ")")
489 case protoreflect.EnumKind:
490 enum := field.EnumType
491 evalue := enum.Values[enum.Desc.Values().ByNumber(def.Enum()).Index()]
492 g.P("const ", defVarName, " ", field.EnumType.GoIdent, " = ", evalue.GoIdent)
493 case protoreflect.FloatKind, protoreflect.DoubleKind:
494 // Floating point numbers need extra handling for -Inf/Inf/NaN.
495 f := field.Desc.Default().Float()
496 goType := "float64"
497 if field.Desc.Kind() == protoreflect.FloatKind {
498 goType = "float32"
499 }
500 // funcCall returns a call to a function in the math package,
501 // possibly converting the result to float32.
502 funcCall := func(fn, param string) string {
503 s := g.QualifiedGoIdent(protogen.GoIdent{
504 GoImportPath: "math",
505 GoName: fn,
506 }) + param
507 if goType != "float64" {
508 s = goType + "(" + s + ")"
509 }
510 return s
511 }
512 switch {
513 case math.IsInf(f, -1):
514 g.P("var ", defVarName, " ", goType, " = ", funcCall("Inf", "(-1)"))
515 case math.IsInf(f, 1):
516 g.P("var ", defVarName, " ", goType, " = ", funcCall("Inf", "(1)"))
517 case math.IsNaN(f):
518 g.P("var ", defVarName, " ", goType, " = ", funcCall("NaN", "()"))
519 default:
520 g.P("const ", defVarName, " ", goType, " = ", f)
521 }
522 default:
Damien Neil77f82fe2018-09-13 10:59:17 -0700523 goType, _ := fieldGoType(g, field)
Damien Neilebc699d2018-09-13 08:50:13 -0700524 g.P("const ", defVarName, " ", goType, " = ", def.Interface())
525 }
526 }
527 g.P()
528
Damien Neil77f82fe2018-09-13 10:59:17 -0700529 // Getters.
530 for _, field := range message.Fields {
Damien Neil1fa78d82018-09-13 13:12:36 -0700531 if field.OneofType != nil {
532 if field == field.OneofType.Fields[0] {
533 genOneofTypes(gen, g, f, message, field.OneofType)
534 }
535 }
Damien Neil77f82fe2018-09-13 10:59:17 -0700536 goType, pointer := fieldGoType(g, field)
537 defaultValue := fieldDefaultValue(g, message, field)
Damien Neil55fe1c02018-09-17 15:11:24 -0700538 if fieldOptions(gen, field).GetDeprecated() {
539 g.P(deprecationComment(true))
540 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700541 g.P("func (m *", message.GoIdent, ") Get", field.GoName, "() ", goType, " {")
542 if field.OneofType != nil {
Damien Neil81d6d832018-09-19 12:12:17 -0700543 g.P("if x, ok := m.Get", field.OneofType.GoName, "().(*", fieldOneofType(field), "); ok {")
Damien Neil1fa78d82018-09-13 13:12:36 -0700544 g.P("return x.", field.GoName)
545 g.P("}")
Damien Neil77f82fe2018-09-13 10:59:17 -0700546 } else {
Damien Neil1fa78d82018-09-13 13:12:36 -0700547 if field.Desc.Syntax() == protoreflect.Proto3 || defaultValue == "nil" {
548 g.P("if m != nil {")
549 } else {
550 g.P("if m != nil && m.", field.GoName, " != nil {")
551 }
552 star := ""
553 if pointer {
554 star = "*"
555 }
556 g.P("return ", star, " m.", field.GoName)
557 g.P("}")
Damien Neil77f82fe2018-09-13 10:59:17 -0700558 }
Damien Neil77f82fe2018-09-13 10:59:17 -0700559 g.P("return ", defaultValue)
560 g.P("}")
561 g.P()
562 }
Damien Neila1c6abc2018-09-12 13:36:34 -0700563
Damien Neilce36f8d2018-09-13 15:19:08 -0700564 genWellKnownType(g, message.GoIdent, message.Desc)
Damien Neil1fa78d82018-09-13 13:12:36 -0700565
566 if len(message.Oneofs) > 0 {
567 genOneofFuncs(gen, g, f, message)
568 }
Damien Neil993c04d2018-09-14 15:41:11 -0700569 for _, extension := range message.Extensions {
570 genExtension(gen, g, f, extension)
571 }
Damien Neilc7d07d92018-08-22 13:46:02 -0700572}
Damien Neilcab8dfe2018-09-06 14:51:28 -0700573
Damien Neil77f82fe2018-09-13 10:59:17 -0700574// fieldGoType returns the Go type used for a field.
575//
576// If it returns pointer=true, the struct field is a pointer to the type.
577func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) (goType string, pointer bool) {
Damien Neil77f82fe2018-09-13 10:59:17 -0700578 pointer = true
Damien Neil658051b2018-09-10 12:26:21 -0700579 switch field.Desc.Kind() {
580 case protoreflect.BoolKind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700581 goType = "bool"
Damien Neil658051b2018-09-10 12:26:21 -0700582 case protoreflect.EnumKind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700583 goType = g.QualifiedGoIdent(field.EnumType.GoIdent)
Damien Neil658051b2018-09-10 12:26:21 -0700584 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700585 goType = "int32"
Damien Neil658051b2018-09-10 12:26:21 -0700586 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700587 goType = "uint32"
Damien Neil658051b2018-09-10 12:26:21 -0700588 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700589 goType = "int64"
Damien Neil658051b2018-09-10 12:26:21 -0700590 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700591 goType = "uint64"
Damien Neil658051b2018-09-10 12:26:21 -0700592 case protoreflect.FloatKind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700593 goType = "float32"
Damien Neil658051b2018-09-10 12:26:21 -0700594 case protoreflect.DoubleKind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700595 goType = "float64"
Damien Neil658051b2018-09-10 12:26:21 -0700596 case protoreflect.StringKind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700597 goType = "string"
Damien Neil658051b2018-09-10 12:26:21 -0700598 case protoreflect.BytesKind:
Damien Neil77f82fe2018-09-13 10:59:17 -0700599 goType = "[]byte"
600 pointer = false
Damien Neil658051b2018-09-10 12:26:21 -0700601 case protoreflect.MessageKind, protoreflect.GroupKind:
Damien Neil0bd5a382018-09-13 15:07:10 -0700602 if field.Desc.IsMap() {
603 keyType, _ := fieldGoType(g, field.MessageType.Fields[0])
604 valType, _ := fieldGoType(g, field.MessageType.Fields[1])
605 return fmt.Sprintf("map[%v]%v", keyType, valType), false
606 }
Damien Neil77f82fe2018-09-13 10:59:17 -0700607 goType = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
608 pointer = false
Damien Neil658051b2018-09-10 12:26:21 -0700609 }
610 if field.Desc.Cardinality() == protoreflect.Repeated {
Damien Neil77f82fe2018-09-13 10:59:17 -0700611 goType = "[]" + goType
612 pointer = false
Damien Neil658051b2018-09-10 12:26:21 -0700613 }
614 if field.Desc.Syntax() == protoreflect.Proto3 {
Damien Neil77f82fe2018-09-13 10:59:17 -0700615 pointer = false
Damien Neil658051b2018-09-10 12:26:21 -0700616 }
Damien Neil77f82fe2018-09-13 10:59:17 -0700617 return goType, pointer
Damien Neil658051b2018-09-10 12:26:21 -0700618}
619
620func fieldProtobufTag(field *protogen.Field) string {
621 var tag []string
622 // wire type
623 tag = append(tag, wireTypes[field.Desc.Kind()])
624 // field number
625 tag = append(tag, strconv.Itoa(int(field.Desc.Number())))
626 // cardinality
627 switch field.Desc.Cardinality() {
628 case protoreflect.Optional:
629 tag = append(tag, "opt")
630 case protoreflect.Required:
631 tag = append(tag, "req")
632 case protoreflect.Repeated:
633 tag = append(tag, "rep")
634 }
Damien Neild4803f52018-09-19 11:43:35 -0700635 if field.Desc.IsPacked() {
636 tag = append(tag, "packed")
637 }
Damien Neil658051b2018-09-10 12:26:21 -0700638 // TODO: packed
639 // name
640 name := string(field.Desc.Name())
641 if field.Desc.Kind() == protoreflect.GroupKind {
642 // The name of the FieldDescriptor for a group field is
643 // lowercased. To find the original capitalization, we
644 // look in the field's MessageType.
645 name = string(field.MessageType.Desc.Name())
646 }
647 tag = append(tag, "name="+name)
648 // JSON name
649 if jsonName := field.Desc.JSONName(); jsonName != "" && jsonName != name {
650 tag = append(tag, "json="+jsonName)
651 }
652 // proto3
653 if field.Desc.Syntax() == protoreflect.Proto3 {
654 tag = append(tag, "proto3")
655 }
656 // enum
657 if field.Desc.Kind() == protoreflect.EnumKind {
658 tag = append(tag, "enum="+enumRegistryName(field.EnumType))
659 }
660 // oneof
661 if field.Desc.OneofType() != nil {
662 tag = append(tag, "oneof")
663 }
Damien Neilebc699d2018-09-13 08:50:13 -0700664 // default value
665 // This must appear last in the tag, since commas in strings aren't escaped.
666 if field.Desc.HasDefault() {
667 var def string
668 switch field.Desc.Kind() {
669 case protoreflect.BoolKind:
670 if field.Desc.Default().Bool() {
671 def = "1"
672 } else {
673 def = "0"
674 }
675 case protoreflect.BytesKind:
676 def = string(field.Desc.Default().Bytes())
677 case protoreflect.FloatKind, protoreflect.DoubleKind:
678 f := field.Desc.Default().Float()
679 switch {
680 case math.IsInf(f, -1):
681 def = "-inf"
682 case math.IsInf(f, 1):
683 def = "inf"
684 case math.IsNaN(f):
685 def = "nan"
686 default:
687 def = fmt.Sprint(f)
688 }
689 default:
690 def = fmt.Sprint(field.Desc.Default().Interface())
691 }
692 tag = append(tag, "def="+def)
693 }
Damien Neil658051b2018-09-10 12:26:21 -0700694 return strings.Join(tag, ",")
695}
696
Damien Neil77f82fe2018-09-13 10:59:17 -0700697func fieldDefaultValue(g *protogen.GeneratedFile, message *protogen.Message, field *protogen.Field) string {
698 if field.Desc.Cardinality() == protoreflect.Repeated {
699 return "nil"
700 }
701 if field.Desc.HasDefault() {
Damien Neil1fa78d82018-09-13 13:12:36 -0700702 defVarName := "Default_" + message.GoIdent.GoName + "_" + field.GoName
Damien Neil77f82fe2018-09-13 10:59:17 -0700703 if field.Desc.Kind() == protoreflect.BytesKind {
704 return "append([]byte(nil), " + defVarName + "...)"
705 }
706 return defVarName
707 }
708 switch field.Desc.Kind() {
709 case protoreflect.BoolKind:
710 return "false"
711 case protoreflect.StringKind:
712 return `""`
713 case protoreflect.MessageKind, protoreflect.GroupKind, protoreflect.BytesKind:
714 return "nil"
715 case protoreflect.EnumKind:
716 return g.QualifiedGoIdent(field.EnumType.Values[0].GoIdent)
717 default:
718 return "0"
719 }
720}
721
Damien Neil658051b2018-09-10 12:26:21 -0700722var wireTypes = map[protoreflect.Kind]string{
723 protoreflect.BoolKind: "varint",
724 protoreflect.EnumKind: "varint",
725 protoreflect.Int32Kind: "varint",
726 protoreflect.Sint32Kind: "zigzag32",
727 protoreflect.Uint32Kind: "varint",
728 protoreflect.Int64Kind: "varint",
729 protoreflect.Sint64Kind: "zigzag64",
730 protoreflect.Uint64Kind: "varint",
731 protoreflect.Sfixed32Kind: "fixed32",
732 protoreflect.Fixed32Kind: "fixed32",
733 protoreflect.FloatKind: "fixed32",
734 protoreflect.Sfixed64Kind: "fixed64",
735 protoreflect.Fixed64Kind: "fixed64",
736 protoreflect.DoubleKind: "fixed64",
737 protoreflect.StringKind: "bytes",
738 protoreflect.BytesKind: "bytes",
739 protoreflect.MessageKind: "bytes",
740 protoreflect.GroupKind: "group",
741}
742
743func fieldJSONTag(field *protogen.Field) string {
744 return string(field.Desc.Name()) + ",omitempty"
745}
746
Damien Neild39efc82018-09-24 12:38:10 -0700747func genExtension(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, extension *protogen.Extension) {
Damien Neil154da982018-09-19 13:21:58 -0700748 // Special case for proto2 message sets: If this extension is extending
749 // proto2.bridge.MessageSet, and its final name component is "message_set_extension",
750 // then drop that last component.
751 //
752 // TODO: This should be implemented in the text formatter rather than the generator.
753 // In addition, the situation for when to apply this special case is implemented
754 // differently in other languages:
755 // https://github.com/google/protobuf/blob/aff10976/src/google/protobuf/text_format.cc#L1560
756 name := extension.Desc.FullName()
757 if isExtensionMessageSetElement(gen, extension) {
758 name = name.Parent()
759 }
760
Damien Neil993c04d2018-09-14 15:41:11 -0700761 g.P("var ", extensionVar(f, extension), " = &", protogen.GoIdent{
762 GoImportPath: protoPackage,
763 GoName: "ExtensionDesc",
764 }, "{")
765 g.P("ExtendedType: (*", extension.ExtendedType.GoIdent, ")(nil),")
766 goType, pointer := fieldGoType(g, extension)
767 if pointer {
768 goType = "*" + goType
769 }
770 g.P("ExtensionType: (", goType, ")(nil),")
771 g.P("Field: ", extension.Desc.Number(), ",")
Damien Neil154da982018-09-19 13:21:58 -0700772 g.P("Name: ", strconv.Quote(string(name)), ",")
Damien Neil993c04d2018-09-14 15:41:11 -0700773 g.P("Tag: ", strconv.Quote(fieldProtobufTag(extension)), ",")
774 g.P("Filename: ", strconv.Quote(f.Desc.Path()), ",")
775 g.P("}")
776 g.P()
777}
778
Damien Neil154da982018-09-19 13:21:58 -0700779func isExtensionMessageSetElement(gen *protogen.Plugin, extension *protogen.Extension) bool {
780 return extension.ParentMessage != nil &&
781 messageOptions(gen, extension.ExtendedType).GetMessageSetWireFormat() &&
782 extension.Desc.Name() == "message_set_extension"
783}
784
Damien Neil993c04d2018-09-14 15:41:11 -0700785// extensionVar returns the var holding the ExtensionDesc for an extension.
Damien Neild39efc82018-09-24 12:38:10 -0700786func extensionVar(f *fileInfo, extension *protogen.Extension) protogen.GoIdent {
Damien Neil993c04d2018-09-14 15:41:11 -0700787 name := "E_"
788 if extension.ParentMessage != nil {
789 name += extension.ParentMessage.GoIdent.GoName + "_"
790 }
791 name += extension.GoName
792 return protogen.GoIdent{
793 GoImportPath: f.GoImportPath,
794 GoName: name,
795 }
796}
797
Damien Neilce36f8d2018-09-13 15:19:08 -0700798// genInitFunction generates an init function that registers the types in the
799// generated file with the proto package.
Damien Neild39efc82018-09-24 12:38:10 -0700800func genInitFunction(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
Damien Neil993c04d2018-09-14 15:41:11 -0700801 if len(f.allMessages) == 0 && len(f.allEnums) == 0 && len(f.allExtensions) == 0 {
Damien Neilce36f8d2018-09-13 15:19:08 -0700802 return
803 }
804
805 g.P("func init() {")
Damien Neil154da982018-09-19 13:21:58 -0700806 for _, enum := range f.allEnums {
807 name := enum.GoIdent.GoName
808 g.P(protogen.GoIdent{
809 GoImportPath: protoPackage,
810 GoName: "RegisterEnum",
811 }, fmt.Sprintf("(%q, %s_name, %s_value)", enumRegistryName(enum), name, name))
812 }
Damien Neilce36f8d2018-09-13 15:19:08 -0700813 for _, message := range f.allMessages {
814 if message.Desc.IsMapEntry() {
815 continue
816 }
817
Damien Neil154da982018-09-19 13:21:58 -0700818 for _, extension := range message.Extensions {
819 genRegisterExtension(gen, g, f, extension)
820 }
821
Damien Neilce36f8d2018-09-13 15:19:08 -0700822 name := message.GoIdent.GoName
823 g.P(protogen.GoIdent{
824 GoImportPath: protoPackage,
825 GoName: "RegisterType",
826 }, fmt.Sprintf("((*%s)(nil), %q)", name, message.Desc.FullName()))
827
828 // Types of map fields, sorted by the name of the field message type.
829 var mapFields []*protogen.Field
830 for _, field := range message.Fields {
831 if field.Desc.IsMap() {
832 mapFields = append(mapFields, field)
833 }
834 }
835 sort.Slice(mapFields, func(i, j int) bool {
836 ni := mapFields[i].MessageType.Desc.FullName()
837 nj := mapFields[j].MessageType.Desc.FullName()
838 return ni < nj
839 })
840 for _, field := range mapFields {
841 typeName := string(field.MessageType.Desc.FullName())
842 goType, _ := fieldGoType(g, field)
843 g.P(protogen.GoIdent{
844 GoImportPath: protoPackage,
845 GoName: "RegisterMapType",
846 }, fmt.Sprintf("((%v)(nil), %q)", goType, typeName))
847 }
848 }
Damien Neil154da982018-09-19 13:21:58 -0700849 for _, extension := range f.Extensions {
850 genRegisterExtension(gen, g, f, extension)
Damien Neil993c04d2018-09-14 15:41:11 -0700851 }
Damien Neilce36f8d2018-09-13 15:19:08 -0700852 g.P("}")
853 g.P()
854}
855
Damien Neild39efc82018-09-24 12:38:10 -0700856func genRegisterExtension(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, extension *protogen.Extension) {
Damien Neil154da982018-09-19 13:21:58 -0700857 g.P(protogen.GoIdent{
858 GoImportPath: protoPackage,
859 GoName: "RegisterExtension",
860 }, "(", extensionVar(f, extension), ")")
861 if isExtensionMessageSetElement(gen, extension) {
862 goType, pointer := fieldGoType(g, extension)
863 if pointer {
864 goType = "*" + goType
865 }
866 g.P(protogen.GoIdent{
867 GoImportPath: protoPackage,
868 GoName: "RegisterMessageSetType",
869 }, "((", goType, ")(nil), ", extension.Desc.Number(), ",", strconv.Quote(string(extension.Desc.FullName().Parent())), ")")
870 }
871}
872
Damien Neild39efc82018-09-24 12:38:10 -0700873func genComment(g *protogen.GeneratedFile, f *fileInfo, path []int32) (hasComment bool) {
Damien Neilcab8dfe2018-09-06 14:51:28 -0700874 for _, loc := range f.locationMap[pathKey(path)] {
875 if loc.LeadingComments == nil {
876 continue
877 }
878 for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
Damien Neil1fa78d82018-09-13 13:12:36 -0700879 hasComment = true
Damien Neilcab8dfe2018-09-06 14:51:28 -0700880 g.P("//", line)
881 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700882 break
Damien Neilcab8dfe2018-09-06 14:51:28 -0700883 }
Damien Neil1fa78d82018-09-13 13:12:36 -0700884 return hasComment
Damien Neilcab8dfe2018-09-06 14:51:28 -0700885}
886
Damien Neil55fe1c02018-09-17 15:11:24 -0700887// deprecationComment returns a standard deprecation comment if deprecated is true.
888func deprecationComment(deprecated bool) string {
889 if !deprecated {
890 return ""
891 }
892 return "// Deprecated: Do not use."
893}
894
Damien Neilcab8dfe2018-09-06 14:51:28 -0700895// pathKey converts a location path to a string suitable for use as a map key.
896func pathKey(path []int32) string {
897 var buf []byte
898 for i, x := range path {
899 if i != 0 {
900 buf = append(buf, ',')
901 }
902 buf = strconv.AppendInt(buf, int64(x), 10)
903 }
904 return string(buf)
905}
Damien Neil46abb572018-09-07 12:45:37 -0700906
907func genWellKnownType(g *protogen.GeneratedFile, ident protogen.GoIdent, desc protoreflect.Descriptor) {
908 if wellKnownTypes[desc.FullName()] {
909 g.P("func (", ident, `) XXX_WellKnownType() string { return "`, desc.Name(), `" }`)
910 g.P()
911 }
912}
913
914// Names of messages and enums for which we will generate XXX_WellKnownType methods.
915var wellKnownTypes = map[protoreflect.FullName]bool{
916 "google.protobuf.NullValue": true,
917}