blob: 6b6ea685fb29574101c0617ecb8fda6adddc6e22 [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
134 g.P("}")
135}
136
137func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800138 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800139 return
140 }
141
142 // Emit a static check that enforces a minimum version of the proto package.
143 g.P("const _ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("Version"), " - ", minimumVersion, ")")
144
145 g.P("var ", f.GoDescriptorIdent, " ", protoreflectPackage.Ident("FileDescriptor"))
146 g.P()
147
148 // Generate literal for file descriptor.
149 fileDescVar := fileDescVarName(f)
150 g.P("var ", fileDescVar, " = ", prototypePackage.Ident("File"), "{")
151 g.P("Syntax: ", protoreflectPackage.Ident(f.Desc.Syntax().GoString()), ",")
152 g.P("Path: ", strconv.Quote(f.Desc.Path()), ",")
153 g.P("Package: ", strconv.Quote(string(f.Desc.Package())), ",")
154 if imps := f.Desc.Imports(); imps.Len() > 0 {
155 g.P("Imports: ", "[]", protoreflectPackage.Ident("FileImport"), "{")
156 for i := 0; i < imps.Len(); i++ {
157 imp := imps.Get(i)
158 path := strconv.Quote(imp.Path())
159 pkg := strconv.Quote(string(imp.Package()))
160 var isPublic, isWeak string
161 if imp.IsPublic {
162 isPublic = ", IsPublic: true"
163 }
164 if imp.IsWeak {
165 isWeak = ", IsWeak: true"
166 }
167 // NOTE: FileDescriptor may be updated later by init.
168 g.P("{FileDescriptor: ", prototypePackage.Ident("PlaceholderFile"), "(", path, ", ", pkg, ")", isPublic, isWeak, "},")
169 }
170 g.P("},")
171 }
172 // NOTE: Messages, Enums, Extensions, and Services are populated by init.
173 g.P("}")
174
175 // Generate literals for enum descriptors.
Joe Tsai9667c482018-12-05 15:42:52 -0800176 if len(f.allEnums) > 0 {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800177 enumTypesVar := enumTypesVarName(f)
178 enumDescsVar := enumDescsVarName(f)
Joe Tsai9667c482018-12-05 15:42:52 -0800179 g.P("var ", enumTypesVar, " = [", len(f.allEnums), "]", protoreflectPackage.Ident("EnumType"), "{")
180 for i, enum := range f.allEnums {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800181 g.P(prototypePackage.Ident("GoEnum"), "(")
182 g.P(enumDescsVar, "[", i, "].Reference(),")
183 g.P("func(_ ", protoreflectPackage.Ident("EnumType"), ", n ", protoreflectPackage.Ident("EnumNumber"), ") ", protoreflectPackage.Ident("ProtoEnum"), " {")
184 g.P("return ", enum.GoIdent, "(n)")
185 g.P("},")
186 g.P("),")
187 }
188 g.P("}")
189
Joe Tsai9667c482018-12-05 15:42:52 -0800190 g.P("var ", enumDescsVar, " = [", len(f.allEnums), "]", prototypePackage.Ident("Enum"), "{")
191 for _, enum := range f.allEnums {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800192 g.P("{")
193 g.P("Name: ", strconv.Quote(string(enum.Desc.Name())), ",")
194 g.P("Values: []", prototypePackage.Ident("EnumValue"), "{")
195 for _, value := range enum.Values {
196 g.P("{Name: ", strconv.Quote(string(value.Desc.Name())), ", Number: ", value.Desc.Number(), "},")
197 }
198 g.P("},")
199 g.P("},")
200 }
201 g.P("}")
202 }
203
204 // Generate literals for message descriptors.
Joe Tsai9667c482018-12-05 15:42:52 -0800205 if len(f.allMessages) > 0 {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800206 messageTypesVar := messageTypesVarName(f)
207 messageDescsVar := messageDescsVarName(f)
Joe Tsai9667c482018-12-05 15:42:52 -0800208 g.P("var ", messageTypesVar, " = [", len(f.allMessages), "]", protoimplPackage.Ident("MessageType"), "{")
209 for i, message := range f.allMessages {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800210 if message.Desc.IsMapEntry() {
211 // Map entry types have no Go type generated for them.
212 g.P("{ /* no message type for ", message.GoIdent, " */ },")
213 continue
214 }
215 g.P("{Type: ", prototypePackage.Ident("GoMessage"), "(")
216 g.P(messageDescsVar, "[", i, "].Reference(),")
217 g.P("func(", protoreflectPackage.Ident("MessageType"), ") ", protoreflectPackage.Ident("ProtoMessage"), " {")
218 g.P("return new(", message.GoIdent, ")")
219 g.P("},")
220 g.P(")},")
221 }
222 g.P("}")
223
Joe Tsai9667c482018-12-05 15:42:52 -0800224 g.P("var ", messageDescsVar, " = [", len(f.allMessages), "]", prototypePackage.Ident("Message"), "{")
225 for _, message := range f.allMessages {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800226 g.P("{")
227 g.P("Name: ", strconv.Quote(string(message.Desc.Name())), ",")
228 if fields := message.Desc.Fields(); fields.Len() > 0 {
229 g.P("Fields: []", prototypePackage.Ident("Field"), "{")
230 for i := 0; i < fields.Len(); i++ {
231 field := fields.Get(i)
232 g.P("{")
233 g.P("Name: ", strconv.Quote(string(field.Name())), ",")
234 g.P("Number: ", field.Number(), ",")
235 g.P("Cardinality: ", protoreflectPackage.Ident(field.Cardinality().GoString()), ",")
236 g.P("Kind: ", protoreflectPackage.Ident(field.Kind().GoString()), ",")
237 // TODO: omit JSONName if it can be derived from Name?
238 g.P("JSONName: ", strconv.Quote(field.JSONName()), ",")
239 if field.HasDefault() {
240 v := field.Default().Interface()
241 typeName := reflect.TypeOf(v).Name()
242 valLit := fmt.Sprint(v)
243 switch v.(type) {
244 case protoreflect.EnumNumber:
245 typeName = "string"
246 valLit = strconv.Quote(string(field.DefaultEnumValue().Name()))
247 case float32, float64:
248 switch f := field.Default().Float(); {
249 case math.IsInf(f, -1):
250 valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(-1)"
251 case math.IsInf(f, +1):
252 valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(+1)"
253 case math.IsNaN(f):
254 valLit = g.QualifiedGoIdent(mathPackage.Ident("NaN")) + "()"
255 }
256 case string, []byte:
257 valLit = fmt.Sprintf("%q", v)
258 }
259 g.P("Default: ", protoreflectPackage.Ident("ValueOf"), "(", typeName, "(", valLit, ")),")
260 }
261 if oneof := field.OneofType(); oneof != nil {
262 g.P("OneofName: ", strconv.Quote(string(oneof.Name())), ",")
263 }
264 // NOTE: MessageType and EnumType are populated by init.
265 g.P("},")
266 }
267 g.P("},")
268 }
269 if oneofs := message.Desc.Oneofs(); oneofs.Len() > 0 {
270 g.P("Oneofs: []", prototypePackage.Ident("Oneof"), "{")
271 for i := 0; i < oneofs.Len(); i++ {
272 oneof := oneofs.Get(i)
273 g.P("{Name: ", strconv.Quote(string(oneof.Name())), "},")
274 }
275 g.P("},")
276 }
277 if extRanges := message.Desc.ExtensionRanges(); extRanges.Len() > 0 {
278 var ss []string
279 for i := 0; i < extRanges.Len(); i++ {
280 r := extRanges.Get(i)
281 ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
282 }
283 g.P("ExtensionRanges: [][2]", protoreflectPackage.Ident("FieldNumber"), "{", strings.Join(ss, ","), "},")
284 }
285 // NOTE: Messages, Enums, and Extensions are populated by init.
286 g.P("},")
287 }
288 g.P("}")
289 }
290
291 // TODO: Add support for extensions.
292 // TODO: Add support for services.
293}
294
295func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800296 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800297 return
298 }
299
300 shadowType := shadowTypeName(enum.GoIdent)
301 g.P("type ", shadowType, " ", enum.GoIdent)
302 g.P()
303
Joe Tsai9667c482018-12-05 15:42:52 -0800304 idx := f.allEnumsByPtr[enum]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800305 typesVar := enumTypesVarName(f)
306 g.P("func (e ", enum.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Enum"), " {")
307 g.P("return (", shadowType, ")(e)")
308 g.P("}")
309 g.P("func (e ", shadowType, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
310 g.P("return ", typesVar, "[", idx, "]")
311 g.P("}")
312 g.P("func (e ", shadowType, ") Number() ", protoreflectPackage.Ident("EnumNumber"), " {")
313 g.P("return ", protoreflectPackage.Ident("EnumNumber"), "(e)")
314 g.P("}")
315}
316
317func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800318 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800319 return
320 }
321
322 shadowType := shadowTypeName(message.GoIdent)
323 g.P("type ", shadowType, " struct{m *", message.GoIdent, "}")
324 g.P()
325
Joe Tsai9667c482018-12-05 15:42:52 -0800326 idx := f.allMessagesByPtr[message]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800327 typesVar := messageTypesVarName(f)
328 g.P("func (m *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
329 g.P("return ", shadowType, "{m}")
330 g.P("}")
331 g.P("func (m ", shadowType, ") Type() ", protoreflectPackage.Ident("MessageType"), " {")
332 g.P("return ", typesVar, "[", idx, "].Type")
333 g.P("}")
334 g.P("func (m ", shadowType, ") KnownFields() ", protoreflectPackage.Ident("KnownFields"), " {")
335 g.P("return ", typesVar, "[", idx, "].KnownFieldsOf(m.m)")
336 g.P("}")
337 g.P("func (m ", shadowType, ") UnknownFields() ", protoreflectPackage.Ident("UnknownFields"), " {")
338 g.P("return ", typesVar, "[", idx, "].UnknownFieldsOf(m.m)")
339 g.P("}")
340 g.P("func (m ", shadowType, ") Interface() ", protoreflectPackage.Ident("ProtoMessage"), " {")
341 g.P("return m.m")
342 g.P("}")
343 g.P("func (m ", shadowType, ") ProtoMutable() {}")
344 g.P()
345}
346
347func fileDescVarName(f *fileInfo) string {
348 return "xxx_" + f.GoDescriptorIdent.GoName + "_FileDesc"
349}
350func enumTypesVarName(f *fileInfo) string {
351 return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumTypes"
352}
353func enumDescsVarName(f *fileInfo) string {
354 return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumDescs"
355}
356func messageTypesVarName(f *fileInfo) string {
357 return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageTypes"
358}
359func messageDescsVarName(f *fileInfo) string {
360 return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageDescs"
361}
362func extensionDescsVarName(f *fileInfo) string {
363 return "xxx_" + f.GoDescriptorIdent.GoName + "_ExtensionDescs"
364}
365func shadowTypeName(ident protogen.GoIdent) string {
366 return "xxx_" + ident.GoName
367}