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