protobuf-go/cmd/protoc-gen-go/internal_gengo/reflect.go
Joe Tsai 9667c4816d cmd/protoc-gen-go: reduce technical debt
The following TODOs were addressed:
* Consistently collect all enums, messages, and extensions in a breadth-first order.
The practical affect of this is that the declaration order in a Go file may change.
This simplifies reflection generation, which relies on consistent ordering.
* Removal of placeholder declarations (e.g., "var _ = proto.Marshal") since
protogen is intelligent about including imports as necessary.
* Always generate a default variable or constant for explicit empty strings.
The practical effect of this is the addition of new declarations in some cases.
However, it simplifies our logic such that it matches the protobuf data model.
* Generate the registration statements in a consistent order.

Change-Id: I627bb72589432bb65d53b50965ea88e5f7983977
Reviewed-on: https://go-review.googlesource.com/c/152778
Reviewed-by: Damien Neil <dneil@google.com>
2018-12-07 03:13:48 +00:00

368 lines
13 KiB
Go

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal_gengo
import (
"fmt"
"math"
"os"
"reflect"
"strconv"
"strings"
"github.com/golang/protobuf/v2/protogen"
"github.com/golang/protobuf/v2/reflect/protoreflect"
)
// TODO: Remove this flag.
// Remember to remove the copy in internal/protogen/goldentest.
var enableReflectFlag = os.Getenv("PROTOC_GEN_GO_ENABLE_REFLECT") != ""
func enableReflection(f *protogen.File) bool {
return enableReflectFlag || isDescriptor(f)
}
// TODO: Remove special-casing for descriptor proto.
func isDescriptor(f *protogen.File) bool {
return f.Desc.Path() == "google/protobuf/descriptor.proto" && f.Desc.Package() == "google.protobuf"
}
// minimumVersion is minimum version of the v2 proto package that is required.
// This is incremented every time the generated code relies on some property
// in the proto package that was introduced in a later version.
const minimumVersion = 0
const (
protoimplPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/runtime/protoimpl")
protoreflectPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/protoreflect")
prototypePackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/prototype")
)
// TODO: Add support for proto options.
func genReflectInitFunction(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
if !enableReflection(f.File) {
return
}
if len(f.allEnums)+len(f.allMessages)+len(f.allExtensions)+len(f.Services) == 0 {
return
}
g.P("func init() {")
// TODO: Fix up file imports to reference a protoreflect.FileDescriptor
// in a remote dependency. Since we cannot yet rely on the existence of
// a variable containing the file descriptor, we find a random message or
// enum the package and see if we can ascend to the parent file descriptor.
fileDescVar := fileDescVarName(f)
enumTypesVar := enumTypesVarName(f)
enumDescsVar := enumDescsVarName(f)
messageTypesVar := messageTypesVarName(f)
messageDescsVar := messageDescsVarName(f)
// Populate all declarations for messages and enums.
// These are not declared in the literals to avoid an initialization loop.
if enums := f.Enums; len(enums) > 0 {
i := f.allEnumsByPtr[enums[0]]
g.P(fileDescVar, ".Enums = ", enumDescsVar, "[", i, ":", i+len(enums), "]")
}
if messages := f.Messages; len(messages) > 0 {
i := f.allMessagesByPtr[messages[0]]
g.P(fileDescVar, ".Messages = ", messageDescsVar, "[", i, ":", i+len(messages), "]")
}
for i, message := range f.allMessages {
if enums := message.Enums; len(enums) > 0 {
j := f.allEnumsByPtr[enums[0]]
g.P(messageDescsVar, "[", i, "].Enums = ", enumDescsVar, "[", j, ":", j+len(enums), "]")
}
if messages := message.Messages; len(messages) > 0 {
j := f.allMessagesByPtr[messages[0]]
g.P(messageDescsVar, "[", i, "].Messages = ", messageDescsVar, "[", j, ":", j+len(messages), "]")
}
}
// Populate all dependencies for messages and enums.
//
// Externally defined enums and messages may or may not support the
// v2 protobuf reflection interfaces. The EnumTypeOf and MessageTypeOf
// helper functions checks for compliance and derives a v2 type from the
// legacy v1 enum or message if necessary.
for i, message := range f.allMessages {
for j, field := range message.Fields {
fieldSel := fmt.Sprintf("[%d].Fields[%d]", i, j)
if et := field.EnumType; et != nil {
idx, ok := f.allEnumsByPtr[et]
if ok {
// Locally defined enums are found in the type array.
g.P(messageDescsVar, fieldSel, ".EnumType = ", enumTypesVar, "[", idx, "]")
} else {
// Externally defined enums may need special handling.
g.P(messageDescsVar, fieldSel, ".EnumType = ", protoimplPackage.Ident("X"), ".EnumTypeOf(", et.GoIdent, "(0))")
}
}
if mt := field.MessageType; mt != nil {
idx, ok := f.allMessagesByPtr[mt]
if ok {
if mt.Desc.IsMapEntry() {
// Map entry types have no Go type generated for them.
g.P(messageDescsVar, fieldSel, ".MessageType = ", messageDescsVar, "[", idx, "].Reference()")
} else {
// Locally defined messages are found in the type array.
g.P(messageDescsVar, fieldSel, ".MessageType = ", messageTypesVar, "[", idx, "].Type")
}
} else {
// Externally defined messages may need special handling.
g.P(messageDescsVar, fieldSel, ".MessageType = ", protoimplPackage.Ident("X"), ".MessageTypeOf((*", mt.GoIdent, ")(nil))")
}
}
}
}
// TODO: Fix up extension dependencies.
// TODO: Fix up method dependencies.
// Construct the file descriptor.
g.P("var err error")
g.P(f.GoDescriptorIdent, ", err = ", prototypePackage.Ident("NewFile"), "(&", fileDescVarName(f), ")")
g.P("if err != nil { panic(err) }")
// TODO: Add v2 registration and stop v1 registration in genInitFunction.
g.P("}")
}
func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
if !enableReflection(f.File) {
return
}
// Emit a static check that enforces a minimum version of the proto package.
g.P("const _ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("Version"), " - ", minimumVersion, ")")
g.P("var ", f.GoDescriptorIdent, " ", protoreflectPackage.Ident("FileDescriptor"))
g.P()
// Generate literal for file descriptor.
fileDescVar := fileDescVarName(f)
g.P("var ", fileDescVar, " = ", prototypePackage.Ident("File"), "{")
g.P("Syntax: ", protoreflectPackage.Ident(f.Desc.Syntax().GoString()), ",")
g.P("Path: ", strconv.Quote(f.Desc.Path()), ",")
g.P("Package: ", strconv.Quote(string(f.Desc.Package())), ",")
if imps := f.Desc.Imports(); imps.Len() > 0 {
g.P("Imports: ", "[]", protoreflectPackage.Ident("FileImport"), "{")
for i := 0; i < imps.Len(); i++ {
imp := imps.Get(i)
path := strconv.Quote(imp.Path())
pkg := strconv.Quote(string(imp.Package()))
var isPublic, isWeak string
if imp.IsPublic {
isPublic = ", IsPublic: true"
}
if imp.IsWeak {
isWeak = ", IsWeak: true"
}
// NOTE: FileDescriptor may be updated later by init.
g.P("{FileDescriptor: ", prototypePackage.Ident("PlaceholderFile"), "(", path, ", ", pkg, ")", isPublic, isWeak, "},")
}
g.P("},")
}
// NOTE: Messages, Enums, Extensions, and Services are populated by init.
g.P("}")
// Generate literals for enum descriptors.
if len(f.allEnums) > 0 {
enumTypesVar := enumTypesVarName(f)
enumDescsVar := enumDescsVarName(f)
g.P("var ", enumTypesVar, " = [", len(f.allEnums), "]", protoreflectPackage.Ident("EnumType"), "{")
for i, enum := range f.allEnums {
g.P(prototypePackage.Ident("GoEnum"), "(")
g.P(enumDescsVar, "[", i, "].Reference(),")
g.P("func(_ ", protoreflectPackage.Ident("EnumType"), ", n ", protoreflectPackage.Ident("EnumNumber"), ") ", protoreflectPackage.Ident("ProtoEnum"), " {")
g.P("return ", enum.GoIdent, "(n)")
g.P("},")
g.P("),")
}
g.P("}")
g.P("var ", enumDescsVar, " = [", len(f.allEnums), "]", prototypePackage.Ident("Enum"), "{")
for _, enum := range f.allEnums {
g.P("{")
g.P("Name: ", strconv.Quote(string(enum.Desc.Name())), ",")
g.P("Values: []", prototypePackage.Ident("EnumValue"), "{")
for _, value := range enum.Values {
g.P("{Name: ", strconv.Quote(string(value.Desc.Name())), ", Number: ", value.Desc.Number(), "},")
}
g.P("},")
g.P("},")
}
g.P("}")
}
// Generate literals for message descriptors.
if len(f.allMessages) > 0 {
messageTypesVar := messageTypesVarName(f)
messageDescsVar := messageDescsVarName(f)
g.P("var ", messageTypesVar, " = [", len(f.allMessages), "]", protoimplPackage.Ident("MessageType"), "{")
for i, message := range f.allMessages {
if message.Desc.IsMapEntry() {
// Map entry types have no Go type generated for them.
g.P("{ /* no message type for ", message.GoIdent, " */ },")
continue
}
g.P("{Type: ", prototypePackage.Ident("GoMessage"), "(")
g.P(messageDescsVar, "[", i, "].Reference(),")
g.P("func(", protoreflectPackage.Ident("MessageType"), ") ", protoreflectPackage.Ident("ProtoMessage"), " {")
g.P("return new(", message.GoIdent, ")")
g.P("},")
g.P(")},")
}
g.P("}")
g.P("var ", messageDescsVar, " = [", len(f.allMessages), "]", prototypePackage.Ident("Message"), "{")
for _, message := range f.allMessages {
g.P("{")
g.P("Name: ", strconv.Quote(string(message.Desc.Name())), ",")
if fields := message.Desc.Fields(); fields.Len() > 0 {
g.P("Fields: []", prototypePackage.Ident("Field"), "{")
for i := 0; i < fields.Len(); i++ {
field := fields.Get(i)
g.P("{")
g.P("Name: ", strconv.Quote(string(field.Name())), ",")
g.P("Number: ", field.Number(), ",")
g.P("Cardinality: ", protoreflectPackage.Ident(field.Cardinality().GoString()), ",")
g.P("Kind: ", protoreflectPackage.Ident(field.Kind().GoString()), ",")
// TODO: omit JSONName if it can be derived from Name?
g.P("JSONName: ", strconv.Quote(field.JSONName()), ",")
if field.HasDefault() {
v := field.Default().Interface()
typeName := reflect.TypeOf(v).Name()
valLit := fmt.Sprint(v)
switch v.(type) {
case protoreflect.EnumNumber:
typeName = "string"
valLit = strconv.Quote(string(field.DefaultEnumValue().Name()))
case float32, float64:
switch f := field.Default().Float(); {
case math.IsInf(f, -1):
valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(-1)"
case math.IsInf(f, +1):
valLit = g.QualifiedGoIdent(mathPackage.Ident("Inf")) + "(+1)"
case math.IsNaN(f):
valLit = g.QualifiedGoIdent(mathPackage.Ident("NaN")) + "()"
}
case string, []byte:
valLit = fmt.Sprintf("%q", v)
}
g.P("Default: ", protoreflectPackage.Ident("ValueOf"), "(", typeName, "(", valLit, ")),")
}
if oneof := field.OneofType(); oneof != nil {
g.P("OneofName: ", strconv.Quote(string(oneof.Name())), ",")
}
// NOTE: MessageType and EnumType are populated by init.
g.P("},")
}
g.P("},")
}
if oneofs := message.Desc.Oneofs(); oneofs.Len() > 0 {
g.P("Oneofs: []", prototypePackage.Ident("Oneof"), "{")
for i := 0; i < oneofs.Len(); i++ {
oneof := oneofs.Get(i)
g.P("{Name: ", strconv.Quote(string(oneof.Name())), "},")
}
g.P("},")
}
if extRanges := message.Desc.ExtensionRanges(); extRanges.Len() > 0 {
var ss []string
for i := 0; i < extRanges.Len(); i++ {
r := extRanges.Get(i)
ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
}
g.P("ExtensionRanges: [][2]", protoreflectPackage.Ident("FieldNumber"), "{", strings.Join(ss, ","), "},")
}
// NOTE: Messages, Enums, and Extensions are populated by init.
g.P("},")
}
g.P("}")
}
// TODO: Add support for extensions.
// TODO: Add support for services.
}
func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
if !enableReflection(f.File) {
return
}
shadowType := shadowTypeName(enum.GoIdent)
g.P("type ", shadowType, " ", enum.GoIdent)
g.P()
idx := f.allEnumsByPtr[enum]
typesVar := enumTypesVarName(f)
g.P("func (e ", enum.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Enum"), " {")
g.P("return (", shadowType, ")(e)")
g.P("}")
g.P("func (e ", shadowType, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
g.P("return ", typesVar, "[", idx, "]")
g.P("}")
g.P("func (e ", shadowType, ") Number() ", protoreflectPackage.Ident("EnumNumber"), " {")
g.P("return ", protoreflectPackage.Ident("EnumNumber"), "(e)")
g.P("}")
}
func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
if !enableReflection(f.File) {
return
}
shadowType := shadowTypeName(message.GoIdent)
g.P("type ", shadowType, " struct{m *", message.GoIdent, "}")
g.P()
idx := f.allMessagesByPtr[message]
typesVar := messageTypesVarName(f)
g.P("func (m *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
g.P("return ", shadowType, "{m}")
g.P("}")
g.P("func (m ", shadowType, ") Type() ", protoreflectPackage.Ident("MessageType"), " {")
g.P("return ", typesVar, "[", idx, "].Type")
g.P("}")
g.P("func (m ", shadowType, ") KnownFields() ", protoreflectPackage.Ident("KnownFields"), " {")
g.P("return ", typesVar, "[", idx, "].KnownFieldsOf(m.m)")
g.P("}")
g.P("func (m ", shadowType, ") UnknownFields() ", protoreflectPackage.Ident("UnknownFields"), " {")
g.P("return ", typesVar, "[", idx, "].UnknownFieldsOf(m.m)")
g.P("}")
g.P("func (m ", shadowType, ") Interface() ", protoreflectPackage.Ident("ProtoMessage"), " {")
g.P("return m.m")
g.P("}")
g.P("func (m ", shadowType, ") ProtoMutable() {}")
g.P()
}
func fileDescVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_FileDesc"
}
func enumTypesVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumTypes"
}
func enumDescsVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_EnumDescs"
}
func messageTypesVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageTypes"
}
func messageDescsVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_MessageDescs"
}
func extensionDescsVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_ExtensionDescs"
}
func shadowTypeName(ident protogen.GoIdent) string {
return "xxx_" + ident.GoName
}