297 lines
10 KiB
Go
Vendored
297 lines
10 KiB
Go
Vendored
// Copyright 2019 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 filetype provides functionality for wrapping descriptors
|
|
// with Go type information.
|
|
package filetype
|
|
|
|
import (
|
|
"reflect"
|
|
|
|
"google.golang.org/protobuf/internal/descopts"
|
|
fdesc "google.golang.org/protobuf/internal/filedesc"
|
|
pimpl "google.golang.org/protobuf/internal/impl"
|
|
pref "google.golang.org/protobuf/reflect/protoreflect"
|
|
preg "google.golang.org/protobuf/reflect/protoregistry"
|
|
)
|
|
|
|
// Builder constructs type descriptors from a raw file descriptor
|
|
// and associated Go types for each enum and message declaration.
|
|
//
|
|
//
|
|
// Flattened Ordering
|
|
//
|
|
// The protobuf type system represents declarations as a tree. Certain nodes in
|
|
// the tree require us to either associate it with a concrete Go type or to
|
|
// resolve a dependency, which is information that must be provided separately
|
|
// since it cannot be derived from the file descriptor alone.
|
|
//
|
|
// However, representing a tree as Go literals is difficult to simply do in a
|
|
// space and time efficient way. Thus, we store them as a flattened list of
|
|
// objects where the serialization order from the tree-based form is important.
|
|
//
|
|
// The "flattened ordering" is defined as a tree traversal of all enum, message,
|
|
// extension, and service declarations using the following algorithm:
|
|
//
|
|
// def VisitFileDecls(fd):
|
|
// for e in fd.Enums: yield e
|
|
// for m in fd.Messages: yield m
|
|
// for x in fd.Extensions: yield x
|
|
// for s in fd.Services: yield s
|
|
// for m in fd.Messages: yield from VisitMessageDecls(m)
|
|
//
|
|
// def VisitMessageDecls(md):
|
|
// for e in md.Enums: yield e
|
|
// for m in md.Messages: yield m
|
|
// for x in md.Extensions: yield x
|
|
// for m in md.Messages: yield from VisitMessageDecls(m)
|
|
//
|
|
// The traversal starts at the root file descriptor and yields each direct
|
|
// declaration within each node before traversing into sub-declarations
|
|
// that children themselves may have.
|
|
type Builder struct {
|
|
// File is the underlying file descriptor builder.
|
|
File fdesc.Builder
|
|
|
|
// GoTypes is a unique set of the Go types for all declarations and
|
|
// dependencies. Each type is represented as a zero value of the Go type.
|
|
//
|
|
// Declarations are Go types generated for enums and messages directly
|
|
// declared (not publicly imported) in the proto source file.
|
|
// Messages for map entries are accounted for, but represented by nil.
|
|
// Enum declarations in "flattened ordering" come first, followed by
|
|
// message declarations in "flattened ordering".
|
|
//
|
|
// Dependencies are Go types for enums or messages referenced by
|
|
// message fields (excluding weak fields), for parent extended messages of
|
|
// extension fields, for enums or messages referenced by extension fields,
|
|
// and for input and output messages referenced by service methods.
|
|
// Dependencies must come after declarations, but the ordering of
|
|
// dependencies themselves is unspecified.
|
|
GoTypes []interface{}
|
|
|
|
// DependencyIndexes is an ordered list of indexes into GoTypes for the
|
|
// dependencies of messages, extensions, or services.
|
|
//
|
|
// There are 5 sub-lists in "flattened ordering" concatenated back-to-back:
|
|
// 0. Message field dependencies: list of the enum or message type
|
|
// referred to by every message field.
|
|
// 1. Extension field targets: list of the extended parent message of
|
|
// every extension.
|
|
// 2. Extension field dependencies: list of the enum or message type
|
|
// referred to by every extension field.
|
|
// 3. Service method inputs: list of the input message type
|
|
// referred to by every service method.
|
|
// 4. Service method outputs: list of the output message type
|
|
// referred to by every service method.
|
|
//
|
|
// The offset into DependencyIndexes for the start of each sub-list
|
|
// is appended to the end in reverse order.
|
|
DependencyIndexes []int32
|
|
|
|
// EnumInfos is a list of enum infos in "flattened ordering".
|
|
EnumInfos []pimpl.EnumInfo
|
|
|
|
// MessageInfos is a list of message infos in "flattened ordering".
|
|
// If provided, the GoType and PBType for each element is populated.
|
|
//
|
|
// Requirement: len(MessageInfos) == len(Build.Messages)
|
|
MessageInfos []pimpl.MessageInfo
|
|
|
|
// ExtensionInfos is a list of extension infos in "flattened ordering".
|
|
// Each element is initialized and registered with the protoregistry package.
|
|
//
|
|
// Requirement: len(LegacyExtensions) == len(Build.Extensions)
|
|
ExtensionInfos []pimpl.ExtensionInfo
|
|
|
|
// TypeRegistry is the registry to register each type descriptor.
|
|
// If nil, it uses protoregistry.GlobalTypes.
|
|
TypeRegistry interface {
|
|
RegisterMessage(pref.MessageType) error
|
|
RegisterEnum(pref.EnumType) error
|
|
RegisterExtension(pref.ExtensionType) error
|
|
}
|
|
}
|
|
|
|
// Out is the output of the builder.
|
|
type Out struct {
|
|
File pref.FileDescriptor
|
|
}
|
|
|
|
func (tb Builder) Build() (out Out) {
|
|
// Replace the resolver with one that resolves dependencies by index,
|
|
// which is faster and more reliable than relying on the global registry.
|
|
if tb.File.FileRegistry == nil {
|
|
tb.File.FileRegistry = preg.GlobalFiles
|
|
}
|
|
tb.File.FileRegistry = &resolverByIndex{
|
|
goTypes: tb.GoTypes,
|
|
depIdxs: tb.DependencyIndexes,
|
|
fileRegistry: tb.File.FileRegistry,
|
|
}
|
|
|
|
// Initialize registry if unpopulated.
|
|
if tb.TypeRegistry == nil {
|
|
tb.TypeRegistry = preg.GlobalTypes
|
|
}
|
|
|
|
fbOut := tb.File.Build()
|
|
out.File = fbOut.File
|
|
|
|
// Process enums.
|
|
enumGoTypes := tb.GoTypes[:len(fbOut.Enums)]
|
|
if len(tb.EnumInfos) != len(fbOut.Enums) {
|
|
panic("mismatching enum lengths")
|
|
}
|
|
if len(fbOut.Enums) > 0 {
|
|
for i := range fbOut.Enums {
|
|
tb.EnumInfos[i] = pimpl.EnumInfo{
|
|
GoReflectType: reflect.TypeOf(enumGoTypes[i]),
|
|
Desc: &fbOut.Enums[i],
|
|
}
|
|
// Register enum types.
|
|
if err := tb.TypeRegistry.RegisterEnum(&tb.EnumInfos[i]); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process messages.
|
|
messageGoTypes := tb.GoTypes[len(fbOut.Enums):][:len(fbOut.Messages)]
|
|
if len(tb.MessageInfos) != len(fbOut.Messages) {
|
|
panic("mismatching message lengths")
|
|
}
|
|
if len(fbOut.Messages) > 0 {
|
|
for i := range fbOut.Messages {
|
|
if messageGoTypes[i] == nil {
|
|
continue // skip map entry
|
|
}
|
|
|
|
tb.MessageInfos[i].GoReflectType = reflect.TypeOf(messageGoTypes[i])
|
|
tb.MessageInfos[i].Desc = &fbOut.Messages[i]
|
|
|
|
// Register message types.
|
|
if err := tb.TypeRegistry.RegisterMessage(&tb.MessageInfos[i]); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// As a special-case for descriptor.proto,
|
|
// locally register concrete message type for the options.
|
|
if out.File.Path() == "google/protobuf/descriptor.proto" && out.File.Package() == "google.protobuf" {
|
|
for i := range fbOut.Messages {
|
|
switch fbOut.Messages[i].Name() {
|
|
case "FileOptions":
|
|
descopts.File = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "EnumOptions":
|
|
descopts.Enum = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "EnumValueOptions":
|
|
descopts.EnumValue = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "MessageOptions":
|
|
descopts.Message = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "FieldOptions":
|
|
descopts.Field = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "OneofOptions":
|
|
descopts.Oneof = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "ExtensionRangeOptions":
|
|
descopts.ExtensionRange = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "ServiceOptions":
|
|
descopts.Service = messageGoTypes[i].(pref.ProtoMessage)
|
|
case "MethodOptions":
|
|
descopts.Method = messageGoTypes[i].(pref.ProtoMessage)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process extensions.
|
|
if len(tb.ExtensionInfos) != len(fbOut.Extensions) {
|
|
panic("mismatching extension lengths")
|
|
}
|
|
var depIdx int32
|
|
for i := range fbOut.Extensions {
|
|
// For enum and message kinds, determine the referent Go type so
|
|
// that we can construct their constructors.
|
|
const listExtDeps = 2
|
|
var goType reflect.Type
|
|
switch fbOut.Extensions[i].L1.Kind {
|
|
case pref.EnumKind:
|
|
j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx)
|
|
goType = reflect.TypeOf(tb.GoTypes[j])
|
|
depIdx++
|
|
case pref.MessageKind, pref.GroupKind:
|
|
j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx)
|
|
goType = reflect.TypeOf(tb.GoTypes[j])
|
|
depIdx++
|
|
default:
|
|
goType = goTypeForPBKind[fbOut.Extensions[i].L1.Kind]
|
|
}
|
|
if fbOut.Extensions[i].IsList() {
|
|
goType = reflect.SliceOf(goType)
|
|
}
|
|
|
|
pimpl.InitExtensionInfo(&tb.ExtensionInfos[i], &fbOut.Extensions[i], goType)
|
|
|
|
// Register extension types.
|
|
if err := tb.TypeRegistry.RegisterExtension(&tb.ExtensionInfos[i]); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
var goTypeForPBKind = map[pref.Kind]reflect.Type{
|
|
pref.BoolKind: reflect.TypeOf(bool(false)),
|
|
pref.Int32Kind: reflect.TypeOf(int32(0)),
|
|
pref.Sint32Kind: reflect.TypeOf(int32(0)),
|
|
pref.Sfixed32Kind: reflect.TypeOf(int32(0)),
|
|
pref.Int64Kind: reflect.TypeOf(int64(0)),
|
|
pref.Sint64Kind: reflect.TypeOf(int64(0)),
|
|
pref.Sfixed64Kind: reflect.TypeOf(int64(0)),
|
|
pref.Uint32Kind: reflect.TypeOf(uint32(0)),
|
|
pref.Fixed32Kind: reflect.TypeOf(uint32(0)),
|
|
pref.Uint64Kind: reflect.TypeOf(uint64(0)),
|
|
pref.Fixed64Kind: reflect.TypeOf(uint64(0)),
|
|
pref.FloatKind: reflect.TypeOf(float32(0)),
|
|
pref.DoubleKind: reflect.TypeOf(float64(0)),
|
|
pref.StringKind: reflect.TypeOf(string("")),
|
|
pref.BytesKind: reflect.TypeOf([]byte(nil)),
|
|
}
|
|
|
|
type depIdxs []int32
|
|
|
|
// Get retrieves the jth element of the ith sub-list.
|
|
func (x depIdxs) Get(i, j int32) int32 {
|
|
return x[x[int32(len(x))-i-1]+j]
|
|
}
|
|
|
|
type (
|
|
resolverByIndex struct {
|
|
goTypes []interface{}
|
|
depIdxs depIdxs
|
|
fileRegistry
|
|
}
|
|
fileRegistry interface {
|
|
FindFileByPath(string) (pref.FileDescriptor, error)
|
|
FindDescriptorByName(pref.FullName) (pref.Descriptor, error)
|
|
RegisterFile(pref.FileDescriptor) error
|
|
}
|
|
)
|
|
|
|
func (r *resolverByIndex) FindEnumByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.EnumDescriptor {
|
|
if depIdx := int(r.depIdxs.Get(i, j)); int(depIdx) < len(es)+len(ms) {
|
|
return &es[depIdx]
|
|
} else {
|
|
return pimpl.Export{}.EnumDescriptorOf(r.goTypes[depIdx])
|
|
}
|
|
}
|
|
|
|
func (r *resolverByIndex) FindMessageByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.MessageDescriptor {
|
|
if depIdx := int(r.depIdxs.Get(i, j)); depIdx < len(es)+len(ms) {
|
|
return &ms[depIdx-len(es)]
|
|
} else {
|
|
return pimpl.Export{}.MessageDescriptorOf(r.goTypes[depIdx])
|
|
}
|
|
}
|