cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
// 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"
|
2018-12-04 22:53:56 -08:00
|
|
|
"os"
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/v2/protogen"
|
|
|
|
"github.com/golang/protobuf/v2/reflect/protoreflect"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO: Remove this flag.
|
2018-12-04 22:53:56 -08:00
|
|
|
// 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"
|
|
|
|
}
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
// fileReflect is embedded in fileInfo to maintain state needed for reflection.
|
|
|
|
//
|
|
|
|
// TODO: Remove this when we have the freedom to change the order of
|
|
|
|
// fileInfo.{allEnums,allMessages,allExtensions} to be a breadth-first search
|
|
|
|
// to ensure that all declarations are coalesced together.
|
|
|
|
type fileReflect struct {
|
|
|
|
allEnums []*protogen.Enum
|
|
|
|
allEnumsByPtr map[*protogen.Enum]int // value is index into allEnums
|
|
|
|
allMessages []*protogen.Message
|
|
|
|
allMessagesByPtr map[*protogen.Message]int // value is index into allMessages
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *fileReflect) init(f *fileInfo) {
|
|
|
|
r.allEnums = append(r.allEnums, f.Enums...)
|
|
|
|
r.allMessages = append(r.allMessages, f.Messages...)
|
|
|
|
walkMessages(f.Messages, func(m *protogen.Message) {
|
|
|
|
r.allEnums = append(r.allEnums, m.Enums...)
|
|
|
|
r.allMessages = append(r.allMessages, m.Messages...)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Derive a reverse mapping of enum and message pointers to their index
|
|
|
|
// in allEnums and allMessages.
|
|
|
|
if len(r.allEnums) > 0 {
|
|
|
|
r.allEnumsByPtr = make(map[*protogen.Enum]int)
|
|
|
|
for i, e := range r.allEnums {
|
|
|
|
r.allEnumsByPtr[e] = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(r.allMessages) > 0 {
|
|
|
|
r.allMessagesByPtr = make(map[*protogen.Message]int)
|
|
|
|
for i, m := range r.allMessages {
|
|
|
|
r.allMessagesByPtr[m] = i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func genReflectInitFunction(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
|
2018-12-04 22:53:56 -08:00
|
|
|
if !enableReflection(f.File) {
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(f.fileReflect.allEnums)+len(f.fileReflect.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.fileReflect.allEnumsByPtr[enums[0]]
|
|
|
|
g.P(fileDescVar, ".Enums = ", enumDescsVar, "[", i, ":", i+len(enums), "]")
|
|
|
|
}
|
|
|
|
if messages := f.Messages; len(messages) > 0 {
|
|
|
|
i := f.fileReflect.allMessagesByPtr[messages[0]]
|
|
|
|
g.P(fileDescVar, ".Messages = ", messageDescsVar, "[", i, ":", i+len(messages), "]")
|
|
|
|
}
|
|
|
|
for i, message := range f.fileReflect.allMessages {
|
|
|
|
if enums := message.Enums; len(enums) > 0 {
|
|
|
|
j := f.fileReflect.allEnumsByPtr[enums[0]]
|
|
|
|
g.P(messageDescsVar, "[", i, "].Enums = ", enumDescsVar, "[", j, ":", j+len(enums), "]")
|
|
|
|
}
|
|
|
|
if messages := message.Messages; len(messages) > 0 {
|
|
|
|
j := f.fileReflect.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.fileReflect.allMessages {
|
|
|
|
for j, field := range message.Fields {
|
|
|
|
fieldSel := fmt.Sprintf("[%d].Fields[%d]", i, j)
|
|
|
|
if et := field.EnumType; et != nil {
|
|
|
|
idx, ok := f.fileReflect.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.fileReflect.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) {
|
2018-12-04 22:53:56 -08:00
|
|
|
if !enableReflection(f.File) {
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
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.fileReflect.allEnums) > 0 {
|
|
|
|
enumTypesVar := enumTypesVarName(f)
|
|
|
|
enumDescsVar := enumDescsVarName(f)
|
|
|
|
g.P("var ", enumTypesVar, " = [", len(f.fileReflect.allEnums), "]", protoreflectPackage.Ident("EnumType"), "{")
|
|
|
|
for i, enum := range f.fileReflect.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.fileReflect.allEnums), "]", prototypePackage.Ident("Enum"), "{")
|
|
|
|
for _, enum := range f.fileReflect.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.fileReflect.allMessages) > 0 {
|
|
|
|
messageTypesVar := messageTypesVarName(f)
|
|
|
|
messageDescsVar := messageDescsVarName(f)
|
|
|
|
g.P("var ", messageTypesVar, " = [", len(f.fileReflect.allMessages), "]", protoimplPackage.Ident("MessageType"), "{")
|
|
|
|
for i, message := range f.fileReflect.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.fileReflect.allMessages), "]", prototypePackage.Ident("Message"), "{")
|
|
|
|
for _, message := range f.fileReflect.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) {
|
2018-12-04 22:53:56 -08:00
|
|
|
if !enableReflection(f.File) {
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
shadowType := shadowTypeName(enum.GoIdent)
|
|
|
|
g.P("type ", shadowType, " ", enum.GoIdent)
|
|
|
|
g.P()
|
|
|
|
|
|
|
|
idx := f.fileReflect.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) {
|
2018-12-04 22:53:56 -08:00
|
|
|
if !enableReflection(f.File) {
|
cmd/protoc-gen-go: add support for protobuf reflection
Implement support in protoc-gen-go for generating messages and enums
that satisfy the v2 protobuf reflection interfaces. Specifically, the following
are added:
* top-level variable representing the file descriptor
* ProtoReflect method on enums (to implement protoV2.Enum)
* ProtoReflect method on messages (to implement protoV2.Message)
The following are not supported yet:
* resolving transitive dependencies for file imports
* Extension descriptors
* Service descriptors
The implementation approach creates a single array for all the message and enum
declarations and references sections of that array to complete dependencies.
Since protobuf declarations can form a graph (a message may depend on itself),
it is difficult to construct a graph as a single literal. One way is to use
placeholder descriptors, but that is not efficient as it requires encoding
the full name of each dependent enum and message and then later resolving it;
thus, both expanding the binary size and also increasing initialization cost.
Instead, we add a prototype.{Enum,Message}.Reference method to obtain a
descriptor reference for the purposes for satisfying dependencies.
As such, nested declarations and dependencies are populated in an init function.
Other changes to support the implementation:
* Added a protoimpl package to expose the MessageType type and also the
MessageTypeOf and EnumTypeOf helper functions.
* Added a protogen.File.GoIdent field to provide a suggested variable name
for the file descriptor.
* Added prototype.{Enum,Message}.Reference that provides a descriptor reference
for the purposes for satisfying cyclic dependencies.
* Added protoreflect.{Syntax,Cardinality,Kind}.GoString to obtain a Go source
identifier that represents the given constant.
Change-Id: I9455764882dee6ad10f251901e7d419091e8bf1d
Reviewed-on: https://go-review.googlesource.com/c/150074
Reviewed-by: Damien Neil <dneil@google.com>
2018-11-15 14:44:37 -08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
shadowType := shadowTypeName(message.GoIdent)
|
|
|
|
g.P("type ", shadowType, " struct{m *", message.GoIdent, "}")
|
|
|
|
g.P()
|
|
|
|
|
|
|
|
idx := f.fileReflect.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
|
|
|
|
}
|