blob: a03373f7bb346c73575a97a4e3bd6fe20b3ebc62 [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"
10 "reflect"
11 "strconv"
12 "strings"
13
14 "github.com/golang/protobuf/v2/protogen"
15 "github.com/golang/protobuf/v2/reflect/protoreflect"
16)
17
18// TODO: Remove this flag.
19const enableReflect = true
20
21// minimumVersion is minimum version of the v2 proto package that is required.
22// This is incremented every time the generated code relies on some property
23// in the proto package that was introduced in a later version.
24const minimumVersion = 0
25
26const (
27 protoimplPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/runtime/protoimpl")
28 protoreflectPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/protoreflect")
29 prototypePackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/prototype")
30)
31
32// TODO: Add support for proto options.
33
34// fileReflect is embedded in fileInfo to maintain state needed for reflection.
35//
36// TODO: Remove this when we have the freedom to change the order of
37// fileInfo.{allEnums,allMessages,allExtensions} to be a breadth-first search
38// to ensure that all declarations are coalesced together.
39type fileReflect struct {
40 allEnums []*protogen.Enum
41 allEnumsByPtr map[*protogen.Enum]int // value is index into allEnums
42 allMessages []*protogen.Message
43 allMessagesByPtr map[*protogen.Message]int // value is index into allMessages
44}
45
46func (r *fileReflect) init(f *fileInfo) {
47 r.allEnums = append(r.allEnums, f.Enums...)
48 r.allMessages = append(r.allMessages, f.Messages...)
49 walkMessages(f.Messages, func(m *protogen.Message) {
50 r.allEnums = append(r.allEnums, m.Enums...)
51 r.allMessages = append(r.allMessages, m.Messages...)
52 })
53
54 // Derive a reverse mapping of enum and message pointers to their index
55 // in allEnums and allMessages.
56 if len(r.allEnums) > 0 {
57 r.allEnumsByPtr = make(map[*protogen.Enum]int)
58 for i, e := range r.allEnums {
59 r.allEnumsByPtr[e] = i
60 }
61 }
62 if len(r.allMessages) > 0 {
63 r.allMessagesByPtr = make(map[*protogen.Message]int)
64 for i, m := range r.allMessages {
65 r.allMessagesByPtr[m] = i
66 }
67 }
68}
69
70func genReflectInitFunction(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
71 if !enableReflect {
72 return
73 }
74
75 if len(f.fileReflect.allEnums)+len(f.fileReflect.allMessages)+len(f.allExtensions)+len(f.Services) == 0 {
76 return
77 }
78
79 g.P("func init() {")
80
81 // TODO: Fix up file imports to reference a protoreflect.FileDescriptor
82 // in a remote dependency. Since we cannot yet rely on the existence of
83 // a variable containing the file descriptor, we find a random message or
84 // enum the package and see if we can ascend to the parent file descriptor.
85
86 fileDescVar := fileDescVarName(f)
87 enumTypesVar := enumTypesVarName(f)
88 enumDescsVar := enumDescsVarName(f)
89 messageTypesVar := messageTypesVarName(f)
90 messageDescsVar := messageDescsVarName(f)
91
92 // Populate all declarations for messages and enums.
93 // These are not declared in the literals to avoid an initialization loop.
94 if enums := f.Enums; len(enums) > 0 {
95 i := f.fileReflect.allEnumsByPtr[enums[0]]
96 g.P(fileDescVar, ".Enums = ", enumDescsVar, "[", i, ":", i+len(enums), "]")
97 }
98 if messages := f.Messages; len(messages) > 0 {
99 i := f.fileReflect.allMessagesByPtr[messages[0]]
100 g.P(fileDescVar, ".Messages = ", messageDescsVar, "[", i, ":", i+len(messages), "]")
101 }
102 for i, message := range f.fileReflect.allMessages {
103 if enums := message.Enums; len(enums) > 0 {
104 j := f.fileReflect.allEnumsByPtr[enums[0]]
105 g.P(messageDescsVar, "[", i, "].Enums = ", enumDescsVar, "[", j, ":", j+len(enums), "]")
106 }
107 if messages := message.Messages; len(messages) > 0 {
108 j := f.fileReflect.allMessagesByPtr[messages[0]]
109 g.P(messageDescsVar, "[", i, "].Messages = ", messageDescsVar, "[", j, ":", j+len(messages), "]")
110 }
111 }
112
113 // Populate all dependencies for messages and enums.
114 //
115 // Externally defined enums and messages may or may not support the
116 // v2 protobuf reflection interfaces. The EnumTypeOf and MessageTypeOf
117 // helper functions checks for compliance and derives a v2 type from the
118 // legacy v1 enum or message if necessary.
119 for i, message := range f.fileReflect.allMessages {
120 for j, field := range message.Fields {
121 fieldSel := fmt.Sprintf("[%d].Fields[%d]", i, j)
122 if et := field.EnumType; et != nil {
123 idx, ok := f.fileReflect.allEnumsByPtr[et]
124 if ok {
125 // Locally defined enums are found in the type array.
126 g.P(messageDescsVar, fieldSel, ".EnumType = ", enumTypesVar, "[", idx, "]")
127 } else {
128 // Externally defined enums may need special handling.
129 g.P(messageDescsVar, fieldSel, ".EnumType = ", protoimplPackage.Ident("X"), ".EnumTypeOf(", et.GoIdent, "(0))")
130 }
131 }
132 if mt := field.MessageType; mt != nil {
133 idx, ok := f.fileReflect.allMessagesByPtr[mt]
134 if ok {
135 if mt.Desc.IsMapEntry() {
136 // Map entry types have no Go type generated for them.
137 g.P(messageDescsVar, fieldSel, ".MessageType = ", messageDescsVar, "[", idx, "].Reference()")
138 } else {
139 // Locally defined messages are found in the type array.
140 g.P(messageDescsVar, fieldSel, ".MessageType = ", messageTypesVar, "[", idx, "].Type")
141 }
142 } else {
143 // Externally defined messages may need special handling.
144 g.P(messageDescsVar, fieldSel, ".MessageType = ", protoimplPackage.Ident("X"), ".MessageTypeOf((*", mt.GoIdent, ")(nil))")
145 }
146 }
147 }
148 }
149 // TODO: Fix up extension dependencies.
150 // TODO: Fix up method dependencies.
151
152 // Construct the file descriptor.
153 g.P("var err error")
154 g.P(f.GoDescriptorIdent, ", err = ", prototypePackage.Ident("NewFile"), "(&", fileDescVarName(f), ")")
155 g.P("if err != nil { panic(err) }")
156
157 // TODO: Add v2 registration and stop v1 registration in genInitFunction.
158
159 g.P("}")
160}
161
162func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
163 if !enableReflect {
164 return
165 }
166
167 // Emit a static check that enforces a minimum version of the proto package.
168 g.P("const _ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("Version"), " - ", minimumVersion, ")")
169
170 g.P("var ", f.GoDescriptorIdent, " ", protoreflectPackage.Ident("FileDescriptor"))
171 g.P()
172
173 // Generate literal for file descriptor.
174 fileDescVar := fileDescVarName(f)
175 g.P("var ", fileDescVar, " = ", prototypePackage.Ident("File"), "{")
176 g.P("Syntax: ", protoreflectPackage.Ident(f.Desc.Syntax().GoString()), ",")
177 g.P("Path: ", strconv.Quote(f.Desc.Path()), ",")
178 g.P("Package: ", strconv.Quote(string(f.Desc.Package())), ",")
179 if imps := f.Desc.Imports(); imps.Len() > 0 {
180 g.P("Imports: ", "[]", protoreflectPackage.Ident("FileImport"), "{")
181 for i := 0; i < imps.Len(); i++ {
182 imp := imps.Get(i)
183 path := strconv.Quote(imp.Path())
184 pkg := strconv.Quote(string(imp.Package()))
185 var isPublic, isWeak string
186 if imp.IsPublic {
187 isPublic = ", IsPublic: true"
188 }
189 if imp.IsWeak {
190 isWeak = ", IsWeak: true"
191 }
192 // NOTE: FileDescriptor may be updated later by init.
193 g.P("{FileDescriptor: ", prototypePackage.Ident("PlaceholderFile"), "(", path, ", ", pkg, ")", isPublic, isWeak, "},")
194 }
195 g.P("},")
196 }
197 // NOTE: Messages, Enums, Extensions, and Services are populated by init.
198 g.P("}")
199
200 // Generate literals for enum descriptors.
201 if len(f.fileReflect.allEnums) > 0 {
202 enumTypesVar := enumTypesVarName(f)
203 enumDescsVar := enumDescsVarName(f)
204 g.P("var ", enumTypesVar, " = [", len(f.fileReflect.allEnums), "]", protoreflectPackage.Ident("EnumType"), "{")
205 for i, enum := range f.fileReflect.allEnums {
206 g.P(prototypePackage.Ident("GoEnum"), "(")
207 g.P(enumDescsVar, "[", i, "].Reference(),")
208 g.P("func(_ ", protoreflectPackage.Ident("EnumType"), ", n ", protoreflectPackage.Ident("EnumNumber"), ") ", protoreflectPackage.Ident("ProtoEnum"), " {")
209 g.P("return ", enum.GoIdent, "(n)")
210 g.P("},")
211 g.P("),")
212 }
213 g.P("}")
214
215 g.P("var ", enumDescsVar, " = [", len(f.fileReflect.allEnums), "]", prototypePackage.Ident("Enum"), "{")
216 for _, enum := range f.fileReflect.allEnums {
217 g.P("{")
218 g.P("Name: ", strconv.Quote(string(enum.Desc.Name())), ",")
219 g.P("Values: []", prototypePackage.Ident("EnumValue"), "{")
220 for _, value := range enum.Values {
221 g.P("{Name: ", strconv.Quote(string(value.Desc.Name())), ", Number: ", value.Desc.Number(), "},")
222 }
223 g.P("},")
224 g.P("},")
225 }
226 g.P("}")
227 }
228
229 // Generate literals for message descriptors.
230 if len(f.fileReflect.allMessages) > 0 {
231 messageTypesVar := messageTypesVarName(f)
232 messageDescsVar := messageDescsVarName(f)
233 g.P("var ", messageTypesVar, " = [", len(f.fileReflect.allMessages), "]", protoimplPackage.Ident("MessageType"), "{")
234 for i, message := range f.fileReflect.allMessages {
235 if message.Desc.IsMapEntry() {
236 // Map entry types have no Go type generated for them.
237 g.P("{ /* no message type for ", message.GoIdent, " */ },")
238 continue
239 }
240 g.P("{Type: ", prototypePackage.Ident("GoMessage"), "(")
241 g.P(messageDescsVar, "[", i, "].Reference(),")
242 g.P("func(", protoreflectPackage.Ident("MessageType"), ") ", protoreflectPackage.Ident("ProtoMessage"), " {")
243 g.P("return new(", message.GoIdent, ")")
244 g.P("},")
245 g.P(")},")
246 }
247 g.P("}")
248
249 g.P("var ", messageDescsVar, " = [", len(f.fileReflect.allMessages), "]", prototypePackage.Ident("Message"), "{")
250 for _, message := range f.fileReflect.allMessages {
251 g.P("{")
252 g.P("Name: ", strconv.Quote(string(message.Desc.Name())), ",")
253 if fields := message.Desc.Fields(); fields.Len() > 0 {
254 g.P("Fields: []", prototypePackage.Ident("Field"), "{")
255 for i := 0; i < fields.Len(); i++ {
256 field := fields.Get(i)
257 g.P("{")
258 g.P("Name: ", strconv.Quote(string(field.Name())), ",")
259 g.P("Number: ", field.Number(), ",")
260 g.P("Cardinality: ", protoreflectPackage.Ident(field.Cardinality().GoString()), ",")
261 g.P("Kind: ", protoreflectPackage.Ident(field.Kind().GoString()), ",")
262 // TODO: omit JSONName if it can be derived from Name?
263 g.P("JSONName: ", strconv.Quote(field.JSONName()), ",")
264 if field.HasDefault() {
265 v := field.Default().Interface()
266 typeName := reflect.TypeOf(v).Name()
267 valLit := fmt.Sprint(v)
268 switch v.(type) {
269 case protoreflect.EnumNumber:
270 typeName = "string"
271 valLit = strconv.Quote(string(field.DefaultEnumValue().Name()))
272 case float32, float64:
273 switch f := field.Default().Float(); {
274 case math.IsInf(f, -1):
275 valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(-1)"
276 case math.IsInf(f, +1):
277 valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(+1)"
278 case math.IsNaN(f):
279 valLit = g.QualifiedGoIdent(mathPackage.Ident("NaN")) + "()"
280 }
281 case string, []byte:
282 valLit = fmt.Sprintf("%q", v)
283 }
284 g.P("Default: ", protoreflectPackage.Ident("ValueOf"), "(", typeName, "(", valLit, ")),")
285 }
286 if oneof := field.OneofType(); oneof != nil {
287 g.P("OneofName: ", strconv.Quote(string(oneof.Name())), ",")
288 }
289 // NOTE: MessageType and EnumType are populated by init.
290 g.P("},")
291 }
292 g.P("},")
293 }
294 if oneofs := message.Desc.Oneofs(); oneofs.Len() > 0 {
295 g.P("Oneofs: []", prototypePackage.Ident("Oneof"), "{")
296 for i := 0; i < oneofs.Len(); i++ {
297 oneof := oneofs.Get(i)
298 g.P("{Name: ", strconv.Quote(string(oneof.Name())), "},")
299 }
300 g.P("},")
301 }
302 if extRanges := message.Desc.ExtensionRanges(); extRanges.Len() > 0 {
303 var ss []string
304 for i := 0; i < extRanges.Len(); i++ {
305 r := extRanges.Get(i)
306 ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
307 }
308 g.P("ExtensionRanges: [][2]", protoreflectPackage.Ident("FieldNumber"), "{", strings.Join(ss, ","), "},")
309 }
310 // NOTE: Messages, Enums, and Extensions are populated by init.
311 g.P("},")
312 }
313 g.P("}")
314 }
315
316 // TODO: Add support for extensions.
317 // TODO: Add support for services.
318}
319
320func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
321 if !enableReflect {
322 return
323 }
324
325 shadowType := shadowTypeName(enum.GoIdent)
326 g.P("type ", shadowType, " ", enum.GoIdent)
327 g.P()
328
329 idx := f.fileReflect.allEnumsByPtr[enum]
330 typesVar := enumTypesVarName(f)
331 g.P("func (e ", enum.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Enum"), " {")
332 g.P("return (", shadowType, ")(e)")
333 g.P("}")
334 g.P("func (e ", shadowType, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
335 g.P("return ", typesVar, "[", idx, "]")
336 g.P("}")
337 g.P("func (e ", shadowType, ") Number() ", protoreflectPackage.Ident("EnumNumber"), " {")
338 g.P("return ", protoreflectPackage.Ident("EnumNumber"), "(e)")
339 g.P("}")
340}
341
342func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
343 if !enableReflect {
344 return
345 }
346
347 shadowType := shadowTypeName(message.GoIdent)
348 g.P("type ", shadowType, " struct{m *", message.GoIdent, "}")
349 g.P()
350
351 idx := f.fileReflect.allMessagesByPtr[message]
352 typesVar := messageTypesVarName(f)
353 g.P("func (m *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
354 g.P("return ", shadowType, "{m}")
355 g.P("}")
356 g.P("func (m ", shadowType, ") Type() ", protoreflectPackage.Ident("MessageType"), " {")
357 g.P("return ", typesVar, "[", idx, "].Type")
358 g.P("}")
359 g.P("func (m ", shadowType, ") KnownFields() ", protoreflectPackage.Ident("KnownFields"), " {")
360 g.P("return ", typesVar, "[", idx, "].KnownFieldsOf(m.m)")
361 g.P("}")
362 g.P("func (m ", shadowType, ") UnknownFields() ", protoreflectPackage.Ident("UnknownFields"), " {")
363 g.P("return ", typesVar, "[", idx, "].UnknownFieldsOf(m.m)")
364 g.P("}")
365 g.P("func (m ", shadowType, ") Interface() ", protoreflectPackage.Ident("ProtoMessage"), " {")
366 g.P("return m.m")
367 g.P("}")
368 g.P("func (m ", shadowType, ") ProtoMutable() {}")
369 g.P()
370}
371
372func fileDescVarName(f *fileInfo) string {
373 return "xxx_" + f.GoDescriptorIdent.GoName + "_FileDesc"
374}
375func enumTypesVarName(f *fileInfo) string {
376 return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumTypes"
377}
378func enumDescsVarName(f *fileInfo) string {
379 return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumDescs"
380}
381func messageTypesVarName(f *fileInfo) string {
382 return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageTypes"
383}
384func messageDescsVarName(f *fileInfo) string {
385 return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageDescs"
386}
387func extensionDescsVarName(f *fileInfo) string {
388 return "xxx_" + f.GoDescriptorIdent.GoName + "_ExtensionDescs"
389}
390func shadowTypeName(ident protogen.GoIdent) string {
391 return "xxx_" + ident.GoName
392}