blob: 69de0df604d04b2014537a7eb56a866f304a5b2e [file] [log] [blame]
Joe Tsaib6405bd2018-11-15 14:44:37 -08001// 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
5package internal_gengo
6
7import (
8 "fmt"
9 "math"
Joe Tsai24ceb2b2018-12-04 22:53:56 -080010 "os"
Joe Tsaib6405bd2018-11-15 14:44:37 -080011 "reflect"
12 "strconv"
13 "strings"
14
15 "github.com/golang/protobuf/v2/protogen"
16 "github.com/golang/protobuf/v2/reflect/protoreflect"
17)
18
19// TODO: Remove this flag.
Joe Tsai24ceb2b2018-12-04 22:53:56 -080020// Remember to remove the copy in internal/protogen/goldentest.
21var enableReflectFlag = os.Getenv("PROTOC_GEN_GO_ENABLE_REFLECT") != ""
22
23func enableReflection(f *protogen.File) bool {
24 return enableReflectFlag || isDescriptor(f)
25}
26
27// TODO: Remove special-casing for descriptor proto.
28func isDescriptor(f *protogen.File) bool {
29 return f.Desc.Path() == "google/protobuf/descriptor.proto" && f.Desc.Package() == "google.protobuf"
30}
Joe Tsaib6405bd2018-11-15 14:44:37 -080031
32// minimumVersion is minimum version of the v2 proto package that is required.
33// This is incremented every time the generated code relies on some property
34// in the proto package that was introduced in a later version.
35const minimumVersion = 0
36
37const (
38 protoimplPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/runtime/protoimpl")
39 protoreflectPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/protoreflect")
40 prototypePackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/prototype")
41)
42
43// TODO: Add support for proto options.
44
Joe Tsaib6405bd2018-11-15 14:44:37 -080045func genReflectInitFunction(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -080046 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -080047 return
48 }
49
Joe Tsai9667c482018-12-05 15:42:52 -080050 if len(f.allEnums)+len(f.allMessages)+len(f.allExtensions)+len(f.Services) == 0 {
Joe Tsaib6405bd2018-11-15 14:44:37 -080051 return
52 }
53
54 g.P("func init() {")
55
56 // TODO: Fix up file imports to reference a protoreflect.FileDescriptor
57 // in a remote dependency. Since we cannot yet rely on the existence of
58 // a variable containing the file descriptor, we find a random message or
59 // enum the package and see if we can ascend to the parent file descriptor.
60
61 fileDescVar := fileDescVarName(f)
62 enumTypesVar := enumTypesVarName(f)
63 enumDescsVar := enumDescsVarName(f)
64 messageTypesVar := messageTypesVarName(f)
65 messageDescsVar := messageDescsVarName(f)
66
67 // Populate all declarations for messages and enums.
68 // These are not declared in the literals to avoid an initialization loop.
69 if enums := f.Enums; len(enums) > 0 {
Joe Tsai9667c482018-12-05 15:42:52 -080070 i := f.allEnumsByPtr[enums[0]]
Joe Tsaib6405bd2018-11-15 14:44:37 -080071 g.P(fileDescVar, ".Enums = ", enumDescsVar, "[", i, ":", i+len(enums), "]")
72 }
73 if messages := f.Messages; len(messages) > 0 {
Joe Tsai9667c482018-12-05 15:42:52 -080074 i := f.allMessagesByPtr[messages[0]]
Joe Tsaib6405bd2018-11-15 14:44:37 -080075 g.P(fileDescVar, ".Messages = ", messageDescsVar, "[", i, ":", i+len(messages), "]")
76 }
Joe Tsai9667c482018-12-05 15:42:52 -080077 for i, message := range f.allMessages {
Joe Tsaib6405bd2018-11-15 14:44:37 -080078 if enums := message.Enums; len(enums) > 0 {
Joe Tsai9667c482018-12-05 15:42:52 -080079 j := f.allEnumsByPtr[enums[0]]
Joe Tsaib6405bd2018-11-15 14:44:37 -080080 g.P(messageDescsVar, "[", i, "].Enums = ", enumDescsVar, "[", j, ":", j+len(enums), "]")
81 }
82 if messages := message.Messages; len(messages) > 0 {
Joe Tsai9667c482018-12-05 15:42:52 -080083 j := f.allMessagesByPtr[messages[0]]
Joe Tsaib6405bd2018-11-15 14:44:37 -080084 g.P(messageDescsVar, "[", i, "].Messages = ", messageDescsVar, "[", j, ":", j+len(messages), "]")
85 }
86 }
87
88 // Populate all dependencies for messages and enums.
89 //
90 // Externally defined enums and messages may or may not support the
91 // v2 protobuf reflection interfaces. The EnumTypeOf and MessageTypeOf
92 // helper functions checks for compliance and derives a v2 type from the
93 // legacy v1 enum or message if necessary.
Joe Tsai9667c482018-12-05 15:42:52 -080094 for i, message := range f.allMessages {
Joe Tsaib6405bd2018-11-15 14:44:37 -080095 for j, field := range message.Fields {
96 fieldSel := fmt.Sprintf("[%d].Fields[%d]", i, j)
97 if et := field.EnumType; et != nil {
Joe Tsai9667c482018-12-05 15:42:52 -080098 idx, ok := f.allEnumsByPtr[et]
Joe Tsaib6405bd2018-11-15 14:44:37 -080099 if ok {
100 // Locally defined enums are found in the type array.
101 g.P(messageDescsVar, fieldSel, ".EnumType = ", enumTypesVar, "[", idx, "]")
102 } else {
103 // Externally defined enums may need special handling.
104 g.P(messageDescsVar, fieldSel, ".EnumType = ", protoimplPackage.Ident("X"), ".EnumTypeOf(", et.GoIdent, "(0))")
105 }
106 }
107 if mt := field.MessageType; mt != nil {
Joe Tsai9667c482018-12-05 15:42:52 -0800108 idx, ok := f.allMessagesByPtr[mt]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800109 if ok {
110 if mt.Desc.IsMapEntry() {
111 // Map entry types have no Go type generated for them.
112 g.P(messageDescsVar, fieldSel, ".MessageType = ", messageDescsVar, "[", idx, "].Reference()")
113 } else {
114 // Locally defined messages are found in the type array.
115 g.P(messageDescsVar, fieldSel, ".MessageType = ", messageTypesVar, "[", idx, "].Type")
116 }
117 } else {
118 // Externally defined messages may need special handling.
119 g.P(messageDescsVar, fieldSel, ".MessageType = ", protoimplPackage.Ident("X"), ".MessageTypeOf((*", mt.GoIdent, ")(nil))")
120 }
121 }
122 }
123 }
124 // TODO: Fix up extension dependencies.
125 // TODO: Fix up method dependencies.
126
127 // Construct the file descriptor.
128 g.P("var err error")
129 g.P(f.GoDescriptorIdent, ", err = ", prototypePackage.Ident("NewFile"), "(&", fileDescVarName(f), ")")
130 g.P("if err != nil { panic(err) }")
131
132 // TODO: Add v2 registration and stop v1 registration in genInitFunction.
133
Joe Tsaia4cbffe2018-12-06 13:01:52 -0800134 // The descriptor proto needs to register the option types with the
135 // prototype so that the package can properly handle those option types.
136 if isDescriptor(f.File) {
137 for _, m := range f.allMessages {
138 name := m.GoIdent.GoName
139 if strings.HasSuffix(name, "Options") {
140 g.P(prototypePackage.Ident("X"), ".Register", name, "((*", name, ")(nil))")
141 }
142 }
143 }
144
Joe Tsaib6405bd2018-11-15 14:44:37 -0800145 g.P("}")
146}
147
148func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800149 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800150 return
151 }
152
153 // Emit a static check that enforces a minimum version of the proto package.
154 g.P("const _ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("Version"), " - ", minimumVersion, ")")
155
156 g.P("var ", f.GoDescriptorIdent, " ", protoreflectPackage.Ident("FileDescriptor"))
157 g.P()
158
159 // Generate literal for file descriptor.
160 fileDescVar := fileDescVarName(f)
161 g.P("var ", fileDescVar, " = ", prototypePackage.Ident("File"), "{")
162 g.P("Syntax: ", protoreflectPackage.Ident(f.Desc.Syntax().GoString()), ",")
163 g.P("Path: ", strconv.Quote(f.Desc.Path()), ",")
164 g.P("Package: ", strconv.Quote(string(f.Desc.Package())), ",")
165 if imps := f.Desc.Imports(); imps.Len() > 0 {
166 g.P("Imports: ", "[]", protoreflectPackage.Ident("FileImport"), "{")
167 for i := 0; i < imps.Len(); i++ {
168 imp := imps.Get(i)
169 path := strconv.Quote(imp.Path())
170 pkg := strconv.Quote(string(imp.Package()))
171 var isPublic, isWeak string
172 if imp.IsPublic {
173 isPublic = ", IsPublic: true"
174 }
175 if imp.IsWeak {
176 isWeak = ", IsWeak: true"
177 }
178 // NOTE: FileDescriptor may be updated later by init.
179 g.P("{FileDescriptor: ", prototypePackage.Ident("PlaceholderFile"), "(", path, ", ", pkg, ")", isPublic, isWeak, "},")
180 }
181 g.P("},")
182 }
183 // NOTE: Messages, Enums, Extensions, and Services are populated by init.
184 g.P("}")
185
186 // Generate literals for enum descriptors.
Joe Tsai9667c482018-12-05 15:42:52 -0800187 if len(f.allEnums) > 0 {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800188 enumTypesVar := enumTypesVarName(f)
189 enumDescsVar := enumDescsVarName(f)
Joe Tsai9667c482018-12-05 15:42:52 -0800190 g.P("var ", enumTypesVar, " = [", len(f.allEnums), "]", protoreflectPackage.Ident("EnumType"), "{")
191 for i, enum := range f.allEnums {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800192 g.P(prototypePackage.Ident("GoEnum"), "(")
193 g.P(enumDescsVar, "[", i, "].Reference(),")
Damien Neila8593ba2019-01-08 16:18:07 -0800194 g.P("func(_ ", protoreflectPackage.Ident("EnumType"), ", n ", protoreflectPackage.Ident("EnumNumber"), ") ", protoreflectPackage.Ident("Enum"), " {")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800195 g.P("return ", enum.GoIdent, "(n)")
196 g.P("},")
197 g.P("),")
198 }
199 g.P("}")
200
Joe Tsai9667c482018-12-05 15:42:52 -0800201 g.P("var ", enumDescsVar, " = [", len(f.allEnums), "]", prototypePackage.Ident("Enum"), "{")
202 for _, enum := range f.allEnums {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800203 g.P("{")
204 g.P("Name: ", strconv.Quote(string(enum.Desc.Name())), ",")
205 g.P("Values: []", prototypePackage.Ident("EnumValue"), "{")
206 for _, value := range enum.Values {
207 g.P("{Name: ", strconv.Quote(string(value.Desc.Name())), ", Number: ", value.Desc.Number(), "},")
208 }
209 g.P("},")
Joe Tsaibce82b82018-12-06 09:39:03 -0800210 if resvNames := enum.Desc.ReservedNames(); resvNames.Len() > 0 {
211 var ss []string
212 for i := 0; i < resvNames.Len(); i++ {
213 s := resvNames.Get(i)
214 ss = append(ss, strconv.Quote(string(s)))
215 }
216 g.P("ReservedNames: []", protoreflectPackage.Ident("Name"), "{", strings.Join(ss, ","), "},")
217 }
218 if resvRanges := enum.Desc.ReservedRanges(); resvRanges.Len() > 0 {
219 var ss []string
220 for i := 0; i < resvRanges.Len(); i++ {
221 r := resvRanges.Get(i)
222 ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
223 }
224 g.P("ReservedRanges: [][2]", protoreflectPackage.Ident("EnumNumber"), "{", strings.Join(ss, ","), "},")
225 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800226 g.P("},")
227 }
228 g.P("}")
229 }
230
231 // Generate literals for message descriptors.
Joe Tsai9667c482018-12-05 15:42:52 -0800232 if len(f.allMessages) > 0 {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800233 messageTypesVar := messageTypesVarName(f)
234 messageDescsVar := messageDescsVarName(f)
Joe Tsai9667c482018-12-05 15:42:52 -0800235 g.P("var ", messageTypesVar, " = [", len(f.allMessages), "]", protoimplPackage.Ident("MessageType"), "{")
236 for i, message := range f.allMessages {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800237 if message.Desc.IsMapEntry() {
238 // Map entry types have no Go type generated for them.
239 g.P("{ /* no message type for ", message.GoIdent, " */ },")
240 continue
241 }
242 g.P("{Type: ", prototypePackage.Ident("GoMessage"), "(")
243 g.P(messageDescsVar, "[", i, "].Reference(),")
244 g.P("func(", protoreflectPackage.Ident("MessageType"), ") ", protoreflectPackage.Ident("ProtoMessage"), " {")
245 g.P("return new(", message.GoIdent, ")")
246 g.P("},")
247 g.P(")},")
248 }
249 g.P("}")
250
Joe Tsai9667c482018-12-05 15:42:52 -0800251 g.P("var ", messageDescsVar, " = [", len(f.allMessages), "]", prototypePackage.Ident("Message"), "{")
252 for _, message := range f.allMessages {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800253 g.P("{")
254 g.P("Name: ", strconv.Quote(string(message.Desc.Name())), ",")
255 if fields := message.Desc.Fields(); fields.Len() > 0 {
256 g.P("Fields: []", prototypePackage.Ident("Field"), "{")
257 for i := 0; i < fields.Len(); i++ {
258 field := fields.Get(i)
259 g.P("{")
260 g.P("Name: ", strconv.Quote(string(field.Name())), ",")
261 g.P("Number: ", field.Number(), ",")
262 g.P("Cardinality: ", protoreflectPackage.Ident(field.Cardinality().GoString()), ",")
263 g.P("Kind: ", protoreflectPackage.Ident(field.Kind().GoString()), ",")
Joe Tsaibce82b82018-12-06 09:39:03 -0800264 if field.HasJSONName() {
265 g.P("JSONName: ", strconv.Quote(field.JSONName()), ",")
266 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800267 if field.HasDefault() {
268 v := field.Default().Interface()
269 typeName := reflect.TypeOf(v).Name()
270 valLit := fmt.Sprint(v)
271 switch v.(type) {
272 case protoreflect.EnumNumber:
273 typeName = "string"
274 valLit = strconv.Quote(string(field.DefaultEnumValue().Name()))
275 case float32, float64:
276 switch f := field.Default().Float(); {
277 case math.IsInf(f, -1):
278 valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(-1)"
279 case math.IsInf(f, +1):
280 valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(+1)"
281 case math.IsNaN(f):
282 valLit = g.QualifiedGoIdent(mathPackage.Ident("NaN")) + "()"
283 }
284 case string, []byte:
285 valLit = fmt.Sprintf("%q", v)
286 }
287 g.P("Default: ", protoreflectPackage.Ident("ValueOf"), "(", typeName, "(", valLit, ")),")
288 }
289 if oneof := field.OneofType(); oneof != nil {
290 g.P("OneofName: ", strconv.Quote(string(oneof.Name())), ",")
291 }
Damien Neil232ea152018-12-10 15:14:36 -0800292 if field.IsPacked() {
293 g.P("IsPacked: ", prototypePackage.Ident("True"), ",")
294 } else {
295 g.P("IsPacked: ", prototypePackage.Ident("False"), ",")
296 }
297 if field.IsWeak() {
298 g.P("IsWeak: true,")
299 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800300 // NOTE: MessageType and EnumType are populated by init.
301 g.P("},")
302 }
303 g.P("},")
304 }
305 if oneofs := message.Desc.Oneofs(); oneofs.Len() > 0 {
306 g.P("Oneofs: []", prototypePackage.Ident("Oneof"), "{")
307 for i := 0; i < oneofs.Len(); i++ {
308 oneof := oneofs.Get(i)
309 g.P("{Name: ", strconv.Quote(string(oneof.Name())), "},")
310 }
311 g.P("},")
312 }
Joe Tsaibce82b82018-12-06 09:39:03 -0800313 if resvNames := message.Desc.ReservedNames(); resvNames.Len() > 0 {
314 var ss []string
315 for i := 0; i < resvNames.Len(); i++ {
316 s := resvNames.Get(i)
317 ss = append(ss, strconv.Quote(string(s)))
318 }
319 g.P("ReservedNames: []", protoreflectPackage.Ident("Name"), "{", strings.Join(ss, ","), "},")
320 }
321 if resvRanges := message.Desc.ReservedRanges(); resvRanges.Len() > 0 {
322 var ss []string
323 for i := 0; i < resvRanges.Len(); i++ {
324 r := resvRanges.Get(i)
325 ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
326 }
327 g.P("ReservedRanges: [][2]", protoreflectPackage.Ident("FieldNumber"), "{", strings.Join(ss, ","), "},")
328 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800329 if extRanges := message.Desc.ExtensionRanges(); extRanges.Len() > 0 {
330 var ss []string
331 for i := 0; i < extRanges.Len(); i++ {
332 r := extRanges.Get(i)
333 ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
334 }
335 g.P("ExtensionRanges: [][2]", protoreflectPackage.Ident("FieldNumber"), "{", strings.Join(ss, ","), "},")
336 }
Damien Neil232ea152018-12-10 15:14:36 -0800337 if message.Desc.IsMapEntry() {
338 g.P("IsMapEntry: true,")
339 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800340 // NOTE: Messages, Enums, and Extensions are populated by init.
341 g.P("},")
342 }
343 g.P("}")
344 }
345
346 // TODO: Add support for extensions.
347 // TODO: Add support for services.
348}
349
350func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800351 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800352 return
353 }
354
Joe Tsai9667c482018-12-05 15:42:52 -0800355 idx := f.allEnumsByPtr[enum]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800356 typesVar := enumTypesVarName(f)
Damien Neila8593ba2019-01-08 16:18:07 -0800357 g.P("func (e ", enum.GoIdent, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800358 g.P("return ", typesVar, "[", idx, "]")
359 g.P("}")
Damien Neila8593ba2019-01-08 16:18:07 -0800360 g.P("func (e ", enum.GoIdent, ") Number() ", protoreflectPackage.Ident("EnumNumber"), " {")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800361 g.P("return ", protoreflectPackage.Ident("EnumNumber"), "(e)")
362 g.P("}")
363}
364
365func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800366 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800367 return
368 }
369
370 shadowType := shadowTypeName(message.GoIdent)
371 g.P("type ", shadowType, " struct{m *", message.GoIdent, "}")
372 g.P()
373
Joe Tsai9667c482018-12-05 15:42:52 -0800374 idx := f.allMessagesByPtr[message]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800375 typesVar := messageTypesVarName(f)
376 g.P("func (m *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
377 g.P("return ", shadowType, "{m}")
378 g.P("}")
379 g.P("func (m ", shadowType, ") Type() ", protoreflectPackage.Ident("MessageType"), " {")
380 g.P("return ", typesVar, "[", idx, "].Type")
381 g.P("}")
382 g.P("func (m ", shadowType, ") KnownFields() ", protoreflectPackage.Ident("KnownFields"), " {")
383 g.P("return ", typesVar, "[", idx, "].KnownFieldsOf(m.m)")
384 g.P("}")
385 g.P("func (m ", shadowType, ") UnknownFields() ", protoreflectPackage.Ident("UnknownFields"), " {")
386 g.P("return ", typesVar, "[", idx, "].UnknownFieldsOf(m.m)")
387 g.P("}")
388 g.P("func (m ", shadowType, ") Interface() ", protoreflectPackage.Ident("ProtoMessage"), " {")
389 g.P("return m.m")
390 g.P("}")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800391 g.P()
392}
393
394func fileDescVarName(f *fileInfo) string {
395 return "xxx_" + f.GoDescriptorIdent.GoName + "_FileDesc"
396}
397func enumTypesVarName(f *fileInfo) string {
398 return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumTypes"
399}
400func enumDescsVarName(f *fileInfo) string {
401 return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumDescs"
402}
403func messageTypesVarName(f *fileInfo) string {
404 return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageTypes"
405}
406func messageDescsVarName(f *fileInfo) string {
407 return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageDescs"
408}
409func extensionDescsVarName(f *fileInfo) string {
410 return "xxx_" + f.GoDescriptorIdent.GoName + "_ExtensionDescs"
411}
412func shadowTypeName(ident protogen.GoIdent) string {
413 return "xxx_" + ident.GoName
414}