protobuf-go/cmd/protoc-gen-go/internal_gengo/reflect.go
Joe Tsai 3bc7d6f5cd reflect: switch MessageType.New to return Message
Most usages of New actually prefer to interact with the reflective view
rather than the native Go type. Thus, change New to return that instead.
This parallels reflect.New, which returns the reflective view
(i.e., reflect.Value) instead of native type (i.e., interface{}).
We make the equivalent change to KnownFields.NewMessage, List.NewMessage,
and Map.NewMessage for consistency.

Since this is a subtle change where the type system will not always
catch the changed type, this change was made by both changing the type
and renaming the function to NewXXX and manually looking at every usage
of the the function to ensure that the usage correctly operates
on either the native Go type or the reflective view of the type.
After the entire codebase was cleaned up, a rename was performed to convert
NewXXX back to New.

Change-Id: I153fef627b4bf0a427e4039ce0aaec52e20c7950
Reviewed-on: https://go-review.googlesource.com/c/157077
Reviewed-by: Damien Neil <dneil@google.com>
2019-01-09 20:29:29 +00:00

415 lines
15 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.
// The descriptor proto needs to register the option types with the
// prototype so that the package can properly handle those option types.
if isDescriptor(f.File) {
for _, m := range f.allMessages {
name := m.GoIdent.GoName
if strings.HasSuffix(name, "Options") {
g.P(prototypePackage.Ident("X"), ".Register", name, "((*", name, ")(nil))")
}
}
}
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("Enum"), " {")
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("},")
if resvNames := enum.Desc.ReservedNames(); resvNames.Len() > 0 {
var ss []string
for i := 0; i < resvNames.Len(); i++ {
s := resvNames.Get(i)
ss = append(ss, strconv.Quote(string(s)))
}
g.P("ReservedNames: []", protoreflectPackage.Ident("Name"), "{", strings.Join(ss, ","), "},")
}
if resvRanges := enum.Desc.ReservedRanges(); resvRanges.Len() > 0 {
var ss []string
for i := 0; i < resvRanges.Len(); i++ {
r := resvRanges.Get(i)
ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
}
g.P("ReservedRanges: [][2]", protoreflectPackage.Ident("EnumNumber"), "{", strings.Join(ss, ","), "},")
}
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("Message"), " {")
g.P("return ", shadowTypeName(message.GoIdent), "{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()), ",")
if field.HasJSONName() {
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())), ",")
}
if field.IsPacked() {
g.P("IsPacked: ", prototypePackage.Ident("True"), ",")
} else {
g.P("IsPacked: ", prototypePackage.Ident("False"), ",")
}
if field.IsWeak() {
g.P("IsWeak: true,")
}
// 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 resvNames := message.Desc.ReservedNames(); resvNames.Len() > 0 {
var ss []string
for i := 0; i < resvNames.Len(); i++ {
s := resvNames.Get(i)
ss = append(ss, strconv.Quote(string(s)))
}
g.P("ReservedNames: []", protoreflectPackage.Ident("Name"), "{", strings.Join(ss, ","), "},")
}
if resvRanges := message.Desc.ReservedRanges(); resvRanges.Len() > 0 {
var ss []string
for i := 0; i < resvRanges.Len(); i++ {
r := resvRanges.Get(i)
ss = append(ss, fmt.Sprintf("{%d,%d}", r[0], r[1]))
}
g.P("ReservedRanges: [][2]", protoreflectPackage.Ident("FieldNumber"), "{", strings.Join(ss, ","), "},")
}
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, ","), "},")
}
if message.Desc.IsMapEntry() {
g.P("IsMapEntry: true,")
}
// 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
}
idx := f.allEnumsByPtr[enum]
typesVar := enumTypesVarName(f)
g.P("func (e ", enum.GoIdent, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
g.P("return ", typesVar, "[", idx, "]")
g.P("}")
g.P("func (e ", enum.GoIdent, ") 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 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
}