Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 1 | // 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 | |
| 5 | package internal_gengo |
| 6 | |
| 7 | import ( |
| 8 | "fmt" |
| 9 | "math" |
Joe Tsai | 24ceb2b | 2018-12-04 22:53:56 -0800 | [diff] [blame] | 10 | "os" |
Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 11 | "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 Tsai | 24ceb2b | 2018-12-04 22:53:56 -0800 | [diff] [blame] | 20 | // Remember to remove the copy in internal/protogen/goldentest. |
| 21 | var enableReflectFlag = os.Getenv("PROTOC_GEN_GO_ENABLE_REFLECT") != "" |
| 22 | |
| 23 | func enableReflection(f *protogen.File) bool { |
| 24 | return enableReflectFlag || isDescriptor(f) |
| 25 | } |
| 26 | |
| 27 | // TODO: Remove special-casing for descriptor proto. |
| 28 | func isDescriptor(f *protogen.File) bool { |
| 29 | return f.Desc.Path() == "google/protobuf/descriptor.proto" && f.Desc.Package() == "google.protobuf" |
| 30 | } |
Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 31 | |
| 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. |
| 35 | const minimumVersion = 0 |
| 36 | |
| 37 | const ( |
| 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. |
| 50 | type 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 | |
| 57 | func (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 | |
| 81 | func genReflectInitFunction(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) { |
Joe Tsai | 24ceb2b | 2018-12-04 22:53:56 -0800 | [diff] [blame] | 82 | if !enableReflection(f.File) { |
Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 83 | 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 | |
| 173 | func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) { |
Joe Tsai | 24ceb2b | 2018-12-04 22:53:56 -0800 | [diff] [blame] | 174 | if !enableReflection(f.File) { |
Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 175 | 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 | |
| 331 | func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) { |
Joe Tsai | 24ceb2b | 2018-12-04 22:53:56 -0800 | [diff] [blame] | 332 | if !enableReflection(f.File) { |
Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 333 | 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 | |
| 353 | func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) { |
Joe Tsai | 24ceb2b | 2018-12-04 22:53:56 -0800 | [diff] [blame] | 354 | if !enableReflection(f.File) { |
Joe Tsai | b6405bd | 2018-11-15 14:44:37 -0800 | [diff] [blame] | 355 | 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 | |
| 383 | func fileDescVarName(f *fileInfo) string { |
| 384 | return "xxx_" + f.GoDescriptorIdent.GoName + "_FileDesc" |
| 385 | } |
| 386 | func enumTypesVarName(f *fileInfo) string { |
| 387 | return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumTypes" |
| 388 | } |
| 389 | func enumDescsVarName(f *fileInfo) string { |
| 390 | return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumDescs" |
| 391 | } |
| 392 | func messageTypesVarName(f *fileInfo) string { |
| 393 | return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageTypes" |
| 394 | } |
| 395 | func messageDescsVarName(f *fileInfo) string { |
| 396 | return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageDescs" |
| 397 | } |
| 398 | func extensionDescsVarName(f *fileInfo) string { |
| 399 | return "xxx_" + f.GoDescriptorIdent.GoName + "_ExtensionDescs" |
| 400 | } |
| 401 | func shadowTypeName(ident protogen.GoIdent) string { |
| 402 | return "xxx_" + ident.GoName |
| 403 | } |