// 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 ( pimpl ) // 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 filedesc.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(protoreflect.MessageType) error RegisterEnum(protoreflect.EnumType) error RegisterExtension(protoreflect.ExtensionType) error } } // Out is the output of the builder. type Out struct { File protoreflect.FileDescriptor } func ( Builder) () ( Out) { // Replace the resolver with one that resolves dependencies by index, // which is faster and more reliable than relying on the global registry. if .File.FileRegistry == nil { .File.FileRegistry = protoregistry.GlobalFiles } .File.FileRegistry = &resolverByIndex{ goTypes: .GoTypes, depIdxs: .DependencyIndexes, fileRegistry: .File.FileRegistry, } // Initialize registry if unpopulated. if .TypeRegistry == nil { .TypeRegistry = protoregistry.GlobalTypes } := .File.Build() .File = .File // Process enums. := .GoTypes[:len(.Enums)] if len(.EnumInfos) != len(.Enums) { panic("mismatching enum lengths") } if len(.Enums) > 0 { for := range .Enums { .EnumInfos[] = pimpl.EnumInfo{ GoReflectType: reflect.TypeOf([]), Desc: &.Enums[], } // Register enum types. if := .TypeRegistry.RegisterEnum(&.EnumInfos[]); != nil { panic() } } } // Process messages. := .GoTypes[len(.Enums):][:len(.Messages)] if len(.MessageInfos) != len(.Messages) { panic("mismatching message lengths") } if len(.Messages) > 0 { for := range .Messages { if [] == nil { continue // skip map entry } .MessageInfos[].GoReflectType = reflect.TypeOf([]) .MessageInfos[].Desc = &.Messages[] // Register message types. if := .TypeRegistry.RegisterMessage(&.MessageInfos[]); != nil { panic() } } // As a special-case for descriptor.proto, // locally register concrete message type for the options. if .File.Path() == "google/protobuf/descriptor.proto" && .File.Package() == "google.protobuf" { for := range .Messages { switch .Messages[].Name() { case "FileOptions": descopts.File = [].(protoreflect.ProtoMessage) case "EnumOptions": descopts.Enum = [].(protoreflect.ProtoMessage) case "EnumValueOptions": descopts.EnumValue = [].(protoreflect.ProtoMessage) case "MessageOptions": descopts.Message = [].(protoreflect.ProtoMessage) case "FieldOptions": descopts.Field = [].(protoreflect.ProtoMessage) case "OneofOptions": descopts.Oneof = [].(protoreflect.ProtoMessage) case "ExtensionRangeOptions": descopts.ExtensionRange = [].(protoreflect.ProtoMessage) case "ServiceOptions": descopts.Service = [].(protoreflect.ProtoMessage) case "MethodOptions": descopts.Method = [].(protoreflect.ProtoMessage) } } } } // Process extensions. if len(.ExtensionInfos) != len(.Extensions) { panic("mismatching extension lengths") } var int32 for := range .Extensions { // For enum and message kinds, determine the referent Go type so // that we can construct their constructors. const = 2 var reflect.Type switch .Extensions[].L1.Kind { case protoreflect.EnumKind: := depIdxs.Get(.DependencyIndexes, , ) = reflect.TypeOf(.GoTypes[]) ++ case protoreflect.MessageKind, protoreflect.GroupKind: := depIdxs.Get(.DependencyIndexes, , ) = reflect.TypeOf(.GoTypes[]) ++ default: = goTypeForPBKind[.Extensions[].L1.Kind] } if .Extensions[].IsList() { = reflect.SliceOf() } pimpl.InitExtensionInfo(&.ExtensionInfos[], &.Extensions[], ) // Register extension types. if := .TypeRegistry.RegisterExtension(&.ExtensionInfos[]); != nil { panic() } } return } var goTypeForPBKind = map[protoreflect.Kind]reflect.Type{ protoreflect.BoolKind: reflect.TypeOf(bool(false)), protoreflect.Int32Kind: reflect.TypeOf(int32(0)), protoreflect.Sint32Kind: reflect.TypeOf(int32(0)), protoreflect.Sfixed32Kind: reflect.TypeOf(int32(0)), protoreflect.Int64Kind: reflect.TypeOf(int64(0)), protoreflect.Sint64Kind: reflect.TypeOf(int64(0)), protoreflect.Sfixed64Kind: reflect.TypeOf(int64(0)), protoreflect.Uint32Kind: reflect.TypeOf(uint32(0)), protoreflect.Fixed32Kind: reflect.TypeOf(uint32(0)), protoreflect.Uint64Kind: reflect.TypeOf(uint64(0)), protoreflect.Fixed64Kind: reflect.TypeOf(uint64(0)), protoreflect.FloatKind: reflect.TypeOf(float32(0)), protoreflect.DoubleKind: reflect.TypeOf(float64(0)), protoreflect.StringKind: reflect.TypeOf(string("")), protoreflect.BytesKind: reflect.TypeOf([]byte(nil)), } type depIdxs []int32 // Get retrieves the jth element of the ith sub-list. func ( depIdxs) (, int32) int32 { return [[int32(len())--1]+] } type ( resolverByIndex struct { goTypes []interface{} depIdxs depIdxs fileRegistry } fileRegistry interface { FindFileByPath(string) (protoreflect.FileDescriptor, error) FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error) RegisterFile(protoreflect.FileDescriptor) error } ) func ( *resolverByIndex) (, int32, []filedesc.Enum, []filedesc.Message) protoreflect.EnumDescriptor { if := int(.depIdxs.Get(, )); int() < len()+len() { return &[] } else { return pimpl.Export{}.EnumDescriptorOf(.goTypes[]) } } func ( *resolverByIndex) (, int32, []filedesc.Enum, []filedesc.Message) protoreflect.MessageDescriptor { if := int(.depIdxs.Get(, )); < len()+len() { return &[-len()] } else { return pimpl.Export{}.MessageDescriptorOf(.goTypes[]) } }