blob: 56a026dc6bee89725b037c795ac7245947fd624d [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 "strings"
12
13 "github.com/golang/protobuf/v2/protogen"
14 "github.com/golang/protobuf/v2/reflect/protoreflect"
15)
16
17// TODO: Remove this flag.
Joe Tsai24ceb2b2018-12-04 22:53:56 -080018// Remember to remove the copy in internal/protogen/goldentest.
19var enableReflectFlag = os.Getenv("PROTOC_GEN_GO_ENABLE_REFLECT") != ""
20
21func enableReflection(f *protogen.File) bool {
22 return enableReflectFlag || isDescriptor(f)
23}
24
25// TODO: Remove special-casing for descriptor proto.
26func isDescriptor(f *protogen.File) bool {
27 return f.Desc.Path() == "google/protobuf/descriptor.proto" && f.Desc.Package() == "google.protobuf"
28}
Joe Tsaib6405bd2018-11-15 14:44:37 -080029
30// minimumVersion is minimum version of the v2 proto package that is required.
31// This is incremented every time the generated code relies on some property
32// in the proto package that was introduced in a later version.
33const minimumVersion = 0
34
35const (
Damien Neil8012b442019-01-18 09:32:24 -080036 reflectPackage = protogen.GoImportPath("reflect")
Joe Tsaib6405bd2018-11-15 14:44:37 -080037 protoimplPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/runtime/protoimpl")
38 protoreflectPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/protoreflect")
39 prototypePackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/prototype")
40)
41
42// TODO: Add support for proto options.
43
Damien Neil8012b442019-01-18 09:32:24 -080044func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -080045 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -080046 return
47 }
48
Damien Neil8012b442019-01-18 09:32:24 -080049 // Emit a static check that enforces a minimum version of the proto package.
50 // TODO: This should appear higher up in the Go source file.
51 g.P("const _ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("Version"), " - ", minimumVersion, ")")
52
53 g.P("var ", f.GoDescriptorIdent, " ", protoreflectPackage.Ident("FileDescriptor"))
54 g.P()
55
56 if len(f.allEnums) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -080057 g.P("var ", enumTypesVarName(f), " = make([]", protoreflectPackage.Ident("EnumType"), ",", len(f.allEnums), ")")
Joe Tsaib6405bd2018-11-15 14:44:37 -080058 }
Damien Neil8012b442019-01-18 09:32:24 -080059 if len(f.allMessages) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -080060 g.P("var ", messageTypesVarName(f), " = make([]", protoimplPackage.Ident("MessageType"), ",", len(f.allMessages), ")")
Damien Neil8012b442019-01-18 09:32:24 -080061 }
62
63 // Generate a unique list of Go types for all declarations and dependencies,
64 // and the associated index into the type list for all dependencies.
65 var goTypes []string
66 var depIdxs []string
67 seen := map[protoreflect.FullName]int{}
68 genDep := func(name protoreflect.FullName, depSource string) {
69 if depSource != "" {
70 line := fmt.Sprintf("%d, // %s -> %s", seen[name], depSource, name)
71 depIdxs = append(depIdxs, line)
72 }
73 }
74 genEnum := func(e *protogen.Enum, depSource string) {
75 if e != nil {
76 name := e.Desc.FullName()
77 if _, ok := seen[name]; !ok {
78 line := fmt.Sprintf("(%s)(0), // %d: %s", g.QualifiedGoIdent(e.GoIdent), len(goTypes), name)
79 goTypes = append(goTypes, line)
80 seen[name] = len(seen)
81 }
82 if depSource != "" {
83 genDep(name, depSource)
84 }
85 }
86 }
87 genMessage := func(m *protogen.Message, depSource string) {
88 if m != nil {
89 name := m.Desc.FullName()
90 if _, ok := seen[name]; !ok {
91 line := fmt.Sprintf("(*%s)(nil), // %d: %s", g.QualifiedGoIdent(m.GoIdent), len(goTypes), name)
92 if m.Desc.IsMapEntry() {
93 // Map entry messages have no associated Go type.
94 line = fmt.Sprintf("nil, // %d: %s", len(goTypes), name)
95 }
96 goTypes = append(goTypes, line)
97 seen[name] = len(seen)
98 }
99 if depSource != "" {
100 genDep(name, depSource)
101 }
102 }
103 }
104
105 // This ordering is significant. See protoimpl.FileBuilder.GoTypes.
106 for _, enum := range f.allEnums {
107 genEnum(enum, "")
108 }
109 for _, message := range f.allMessages {
110 genMessage(message, "")
111 }
112 for _, extension := range f.allExtensions {
113 source := string(extension.Desc.FullName())
114 genMessage(extension.ExtendedType, source+":extendee")
115 }
116 for _, message := range f.allMessages {
117 for _, field := range message.Fields {
118 if field.Desc.IsWeak() {
119 continue
120 }
121 source := string(field.Desc.FullName())
122 genEnum(field.EnumType, source+":type_name")
123 genMessage(field.MessageType, source+":type_name")
124 }
125 }
126 for _, extension := range f.allExtensions {
127 source := string(extension.Desc.FullName())
128 genEnum(extension.EnumType, source+":type_name")
129 genMessage(extension.MessageType, source+":type_name")
130 }
131 for _, service := range f.Services {
132 for _, method := range service.Methods {
133 source := string(method.Desc.FullName())
134 genMessage(method.InputType, source+":input_type")
135 genMessage(method.OutputType, source+":output_type")
136 }
137 }
138 if len(depIdxs) > math.MaxInt32 {
139 panic("too many dependencies") // sanity check
140 }
141
142 g.P("var ", goTypesVarName(f), " = []interface{}{")
143 for _, s := range goTypes {
144 g.P(s)
145 }
146 g.P("}")
147
148 g.P("var ", depIdxsVarName(f), " = []int32{")
149 for _, s := range depIdxs {
150 g.P(s)
151 }
152 g.P("}")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800153
154 g.P("func init() {")
Damien Neil8012b442019-01-18 09:32:24 -0800155 if len(f.allMessages) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -0800156 g.P("messageTypes := make([]", protoreflectPackage.Ident("MessageType"), ",", len(f.allMessages), ")")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800157 }
Damien Neil8012b442019-01-18 09:32:24 -0800158 if len(f.allExtensions) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -0800159 g.P("extensionTypes := make([]", protoreflectPackage.Ident("ExtensionType"), ",", len(f.allExtensions), ")")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800160 }
161
Damien Neil8012b442019-01-18 09:32:24 -0800162 g.P(f.GoDescriptorIdent, " = ", protoimplPackage.Ident("FileBuilder"), "{")
163 g.P("RawDescriptor: ", f.descriptorRawVar, ",")
164 g.P("GoTypes: ", goTypesVarName(f), ",")
165 g.P("DependencyIndexes: ", depIdxsVarName(f), ",")
166 if len(f.allEnums) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -0800167 g.P("EnumOutputTypes: ", enumTypesVarName(f), ",")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800168 }
Damien Neil8012b442019-01-18 09:32:24 -0800169 if len(f.allMessages) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -0800170 g.P("MessageOutputTypes: messageTypes,")
Damien Neil8012b442019-01-18 09:32:24 -0800171 }
172 if len(f.allExtensions) > 0 {
Damien Neil6bb8dec2019-03-01 13:22:30 -0800173 g.P("ExtensionOutputTypes: extensionTypes,")
Damien Neil8012b442019-01-18 09:32:24 -0800174 }
175 g.P("}.Init()")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800176
Damien Neil8012b442019-01-18 09:32:24 -0800177 // Copy the local list of message types into the global array.
178 if len(f.allMessages) > 0 {
179 g.P("messageGoTypes := ", goTypesVarName(f), "[", len(f.allEnums), ":][:", len(f.allMessages), "]")
Damien Neil6bb8dec2019-03-01 13:22:30 -0800180 g.P("for i, mt := range messageTypes {")
Damien Neil8012b442019-01-18 09:32:24 -0800181 g.P(messageTypesVarName(f), "[i].GoType = ", reflectPackage.Ident("TypeOf"), "(messageGoTypes[i])")
182 g.P(messageTypesVarName(f), "[i].PBType = mt")
183 g.P("}")
184 }
185
186 // Copy the local list of extension types into each global variable.
187 for i, extension := range f.allExtensions {
188 g.P(extensionVar(f.File, extension), ".Type = extensionTypes[", i, "]")
189 }
Joe Tsaib6405bd2018-11-15 14:44:37 -0800190
191 // TODO: Add v2 registration and stop v1 registration in genInitFunction.
192
Joe Tsaia4cbffe2018-12-06 13:01:52 -0800193 // The descriptor proto needs to register the option types with the
194 // prototype so that the package can properly handle those option types.
195 if isDescriptor(f.File) {
196 for _, m := range f.allMessages {
197 name := m.GoIdent.GoName
198 if strings.HasSuffix(name, "Options") {
199 g.P(prototypePackage.Ident("X"), ".Register", name, "((*", name, ")(nil))")
200 }
201 }
202 }
203
Damien Neil8012b442019-01-18 09:32:24 -0800204 g.P(goTypesVarName(f), " = nil") // allow GC to reclaim resource
205 g.P(depIdxsVarName(f), " = nil") // allow GC to reclaim resource
Joe Tsaib6405bd2018-11-15 14:44:37 -0800206 g.P("}")
207}
208
Joe Tsaib6405bd2018-11-15 14:44:37 -0800209func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800210 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800211 return
212 }
213
Joe Tsai9667c482018-12-05 15:42:52 -0800214 idx := f.allEnumsByPtr[enum]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800215 typesVar := enumTypesVarName(f)
Damien Neila8593ba2019-01-08 16:18:07 -0800216 g.P("func (e ", enum.GoIdent, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800217 g.P("return ", typesVar, "[", idx, "]")
218 g.P("}")
Damien Neila8593ba2019-01-08 16:18:07 -0800219 g.P("func (e ", enum.GoIdent, ") Number() ", protoreflectPackage.Ident("EnumNumber"), " {")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800220 g.P("return ", protoreflectPackage.Ident("EnumNumber"), "(e)")
221 g.P("}")
222}
223
224func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
Joe Tsai24ceb2b2018-12-04 22:53:56 -0800225 if !enableReflection(f.File) {
Joe Tsaib6405bd2018-11-15 14:44:37 -0800226 return
227 }
228
Joe Tsai9667c482018-12-05 15:42:52 -0800229 idx := f.allMessagesByPtr[message]
Joe Tsaib6405bd2018-11-15 14:44:37 -0800230 typesVar := messageTypesVarName(f)
231 g.P("func (m *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
Damien Neil8012b442019-01-18 09:32:24 -0800232 g.P("return ", typesVar, "[", idx, "].MessageOf(m)")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800233 g.P("}")
Joe Tsaib6405bd2018-11-15 14:44:37 -0800234}
235
Damien Neil8012b442019-01-18 09:32:24 -0800236func goTypesVarName(f *fileInfo) string {
237 return "xxx_" + f.GoDescriptorIdent.GoName + "_goTypes"
238}
239func depIdxsVarName(f *fileInfo) string {
240 return "xxx_" + f.GoDescriptorIdent.GoName + "_depIdxs"
Joe Tsaib6405bd2018-11-15 14:44:37 -0800241}
242func enumTypesVarName(f *fileInfo) string {
Damien Neil8012b442019-01-18 09:32:24 -0800243 return "xxx_" + f.GoDescriptorIdent.GoName + "_enumTypes"
Joe Tsaib6405bd2018-11-15 14:44:37 -0800244}
245func messageTypesVarName(f *fileInfo) string {
Damien Neil8012b442019-01-18 09:32:24 -0800246 return "xxx_" + f.GoDescriptorIdent.GoName + "_messageTypes"
Joe Tsaib6405bd2018-11-15 14:44:37 -0800247}