// Sources/SwiftProtobufPluginLibrary/Descriptor.swift - Descriptor wrappers // // Copyright (c) 2014 - 2017 Apple Inc. and the project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See LICENSE.txt for license information: // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt // // ----------------------------------------------------------------------------- /// /// This is like Descriptor.{h,cc} in the google/protobuf C++ code, it provides /// wrappers around the protos to make a more usable object graph for generation /// and also provides some SwiftProtobuf specific additions that would be useful /// to anyone generating something that uses SwiftProtobufs (like support the /// `service` messages). It is *not* the intent for these to eventually be used /// as part of some reflection or generate message api. /// /// Unlike the C++ Descriptors, the intent is for these to *only* be used within /// the context of a protoc plugin, meaning, the /// `Google_Protobuf_FileDescriptorSet` used to create these will be *always* /// be well formed by protoc and the guarentees it provides. /// // ----------------------------------------------------------------------------- import Foundation import SwiftProtobuf /// The front interface for building/getting descriptors. The objects /// vended from the here are different from the raw /// `Google_Protobuf_*Proto` types in that they have all the cross object /// references resolved or wired up, making for an easier to use object /// model. /// /// This is like the `DescriptorPool` class in the C++ protobuf library. public final class DescriptorSet { /// The list of `FileDescriptor`s in this set. public let files: [FileDescriptor] private let registry = Registry() // Construct out of a `Google_Protobuf_FileDescriptorSet` likely // created by protoc. public convenience init(proto: Google_Protobuf_FileDescriptorSet) { self.init(protos: proto.file) } /// The bundled in `google.protobuf.FeatureSetDefault` that defines what /// the plugin library can support. private static let bundledFeatureSetDefaults = // Decoding the bundle defaults better never fail try! Google_Protobuf_FeatureSetDefaults(serializedBytes: bundledFeatureSetDefaultBytes) /// The range of Editions that the library can support. /// /// This will limit what edition versions a plugin can claim to support. public static var bundledEditionsSupport: ClosedRange { bundledFeatureSetDefaults.minimumEdition...bundledFeatureSetDefaults.maximumEdition } /// Construct out of a ordered list of /// `Google_Protobuf_FileDescriptorProto`s likely created by protoc. public convenience init(protos: [Google_Protobuf_FileDescriptorProto]) { self.init( protos: protos, featureSetDefaults: DescriptorSet.bundledFeatureSetDefaults ) } /// Construct out of a ordered list of /// `Google_Protobuf_FileDescriptorProto`s likely created by protoc. Since /// .proto files can import other .proto files, the imports have to be /// listed before the things that use them so the graph can be /// reconstructed. /// /// - Parameters: /// - protos: An ordered list of `Google_Protobuf_FileDescriptorProto`. /// They must be order such that a file is provided before another file /// that depends on it. /// - featureSetDefaults: A `Google_Protobuf_FeatureSetDefaults` that provides /// the Feature defaults to use when parsing the give File protos. /// - featureExtensions: A list of Protobuf Extension extensions to /// `google.protobuf.FeatureSet` that define custom features. If used, the /// `defaults` should have been parsed with the extensions being /// supported. public init( protos: [Google_Protobuf_FileDescriptorProto], featureSetDefaults: Google_Protobuf_FeatureSetDefaults, featureExtensions: [any AnyMessageExtension] = [] ) { precondition( Self.bundledEditionsSupport.contains(featureSetDefaults.minimumEdition), "Attempt to use a FeatureSetDefault minimumEdition that isn't supported by the library." ) precondition( Self.bundledEditionsSupport.contains(featureSetDefaults.maximumEdition), "Attempt to use a FeatureSetDefault maximumEdition that isn't supported by the library." ) // If a protoc is too old ≤v26, it might have `features` instead of `overridable_features` and // `fixed_features`, try to catch that. precondition( nil == featureSetDefaults.defaults.first(where: { !$0.hasOverridableFeatures && !$0.hasFixedFeatures }), "These FeatureSetDefault don't appear valid, make sure you are using a new enough protoc to generate them. " ) let registry = self.registry self.files = protos.map { FileDescriptor( proto: $0, featureSetDefaults: featureSetDefaults, featureExtensions: featureExtensions, registry: registry ) } } /// Lookup a specific file. The names for files are what was captured in /// the `Google_Protobuf_FileDescriptorProto` when it was created, protoc /// uses the path name for how the file was found. /// /// This is a legacy api since it requires the file to be found or it aborts. /// Mainly kept for grpc-swift compatibility. @available(*, deprecated, renamed: "fileDescriptor(named:)") public func lookupFileDescriptor(protoName name: String) -> FileDescriptor { registry.fileDescriptor(named: name)! } /// Find a specific file. The names for files are what was captured in /// the `Google_Protobuf_FileDescriptorProto` when it was created, protoc /// uses the path name for how the file was found. public func fileDescriptor(named name: String) -> FileDescriptor? { registry.fileDescriptor(named: name) } /// Find the `Descriptor` for a named proto message. /// /// This is a legacy api since it requires the proto to be found or it aborts. /// Mainly kept for grpc-swift compatibility. @available(*, deprecated, renamed: "descriptor(named:)") public func lookupDescriptor(protoName: String) -> Descriptor { self.descriptor(named: protoName)! } /// Find the `Descriptor` for a named proto message. public func descriptor(named fullName: String) -> Descriptor? { registry.descriptor(named: ".\(fullName)") } /// Find the `EnumDescriptor` for a named proto enum. /// /// This is a legacy api since it requires the enum to be found or it aborts. /// Mainly kept for grpc-swift compatibility. @available(*, deprecated, renamed: "enumDescriptor(named:)") public func lookupEnumDescriptor(protoName: String) -> EnumDescriptor { enumDescriptor(named: protoName)! } /// Find the `EnumDescriptor` for a named proto enum. public func enumDescriptor(named fullName: String) -> EnumDescriptor? { registry.enumDescriptor(named: ".\(fullName)") } /// Find the `ServiceDescriptor` for a named proto service. /// /// This is a legacy api since it requires the enum to be found or it aborts. /// Mainly kept for grpc-swift compatibility. @available(*, deprecated, renamed: "serviceDescriptor(named:)") public func lookupServiceDescriptor(protoName: String) -> ServiceDescriptor { serviceDescriptor(named: protoName)! } /// Find the `ServiceDescriptor` for a named proto service. public func serviceDescriptor(named fullName: String) -> ServiceDescriptor? { registry.serviceDescriptor(named: ".\(fullName)") } } /// Options for collected a proto object from a Descriptor. public struct ExtractProtoOptions { /// If the `SourceCodeInfo` should also be included in the proto file. /// /// If embedding the descriptor in a binary for some reason, normally the `SourceCodeInfo` /// isn't needed and would just be an increas in binary size. public var includeSourceCodeInfo: Bool = false /// Copy on the _header_ for the descriptor. This mainly means leave out any of the nested /// descriptors (messages, enums, etc.). public var headerOnly: Bool = false // NOTE: in the future maybe add toggles to model the behavior of the *Descriptor::Copy*To() // apis. public init() {} } /// Models a .proto file. `FileDescriptor`s are not directly created, /// instead they are constructed/fetched via the `DescriptorSet` or /// they are directly accessed via a `file` property on all the other /// types of descriptors. public final class FileDescriptor { @available(*, deprecated, message: "This enum has been deprecated. Use `Google_Protobuf_Edition` instead.") public enum Syntax: String { case proto2 case proto3 public init?(rawValue: String) { switch rawValue { case "proto2", "": self = .proto2 case "proto3": self = .proto3 default: return nil } } } /// The filename used with protoc. public let name: String /// The proto package. public let package: String @available(*, deprecated, message: "This property has been deprecated. Use `edition` instead.") public var syntax: Syntax { Syntax(rawValue: self._proto.syntax)! } /// The edition of the file. public let edition: Google_Protobuf_Edition /// The resolved features for this File. public let features: Google_Protobuf_FeatureSet /// The imports for this file. public let dependencies: [FileDescriptor] /// The subset of the imports that were declared `public`. public let publicDependencies: [FileDescriptor] /// The subset of the imports that were declared `weak`. public let weakDependencies: [FileDescriptor] /// The enum defintions at the file scope level. public let enums: [EnumDescriptor] /// The message defintions at the file scope level. public let messages: [Descriptor] /// The extension field defintions at the file scope level. public let extensions: [FieldDescriptor] /// The service defintions at the file scope level. public let services: [ServiceDescriptor] /// The `Google_Protobuf_FileOptions` set on this file. @available(*, deprecated, renamed: "options") public var fileOptions: Google_Protobuf_FileOptions { self.options } /// The `Google_Protobuf_FileOptions` set on this file. public let options: Google_Protobuf_FileOptions private let sourceCodeInfo: Google_Protobuf_SourceCodeInfo /// Extract contents of this descriptor in Proto form. /// /// - Parameters: /// - options: Controls what information is include/excluded when creating the Proto version. /// /// - Returns: A `Google_Protobuf_FileDescriptorProto`. public func extractProto( options: ExtractProtoOptions = ExtractProtoOptions() ) -> Google_Protobuf_FileDescriptorProto { // In the future it might make sense to model this like the C++, where the protos is built up // on demand instead of keeping the object around. var result = _proto if !options.includeSourceCodeInfo { result.clearSourceCodeInfo() } if options.headerOnly { // For FileDescriptor, make `headerOnly` mean the same things as C++ // `FileDescriptor::CopyHeaderTo()`. result.dependency = [] result.publicDependency = [] result.weakDependency = [] result.messageType = [] result.enumType = [] result.messageType = [] result.service = [] result.extension = [] } return result } /// The proto version of the descriptor that defines this File. @available(*, deprecated, renamed: "extractProto()") public var proto: Google_Protobuf_FileDescriptorProto { _proto } private let _proto: Google_Protobuf_FileDescriptorProto @available(*, deprecated, message: "Use `fileOptions/deprecated` instead.") public var isDeprecated: Bool { proto.options.deprecated } fileprivate init( proto: Google_Protobuf_FileDescriptorProto, featureSetDefaults: Google_Protobuf_FeatureSetDefaults, featureExtensions: [any AnyMessageExtension], registry: Registry ) { self.name = proto.name self.package = proto.package // This logic comes from upstream `DescriptorBuilder::BuildFileImpl()`. if proto.hasEdition { self.edition = proto.edition } else { switch proto.syntax { case "", "proto2": self.edition = .proto2 case "proto3": self.edition = .proto3 default: self.edition = .unknown fatalError( "protoc provided an expected value (\"\(proto.syntax)\") for syntax/edition: \(proto.name)" ) } } // TODO: Revsit capturing the error here and see about exposing it out // to be reported via plugins. let featureResolver: FeatureResolver do { featureResolver = try FeatureResolver( edition: self.edition, featureSetDefaults: featureSetDefaults, featureExtensions: featureExtensions ) } catch let e { fatalError("Failed to make a FeatureResolver for \(self.name): \(e)") } let resolvedFeatures = featureResolver.resolve(proto.options) self.features = resolvedFeatures self.options = proto.options let protoPackage = proto.package self.enums = proto.enumType.enumerated().map { EnumDescriptor( proto: $0.element, index: $0.offset, parentFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry, scope: protoPackage ) } self.messages = proto.messageType.enumerated().map { Descriptor( proto: $0.element, index: $0.offset, parentFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry, scope: protoPackage ) } self.extensions = proto.extension.enumerated().map { FieldDescriptor( extension: $0.element, index: $0.offset, parentFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry ) } self.services = proto.service.enumerated().map { ServiceDescriptor( proto: $0.element, index: $0.offset, fileFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry, scope: protoPackage ) } // The compiler ensures there aren't cycles between a file and dependencies, so // this doesn't run the risk of creating any retain cycles that would force these // to have to be weak. let dependencies = proto.dependency.map { registry.fileDescriptor(named: $0)! } self.dependencies = dependencies self.publicDependencies = proto.publicDependency.map { dependencies[Int($0)] } self.weakDependencies = proto.weakDependency.map { dependencies[Int($0)] } self.sourceCodeInfo = proto.sourceCodeInfo self._proto = proto // Done initializing, register ourselves. registry.register(file: self) // descriptor.proto documents the files will be in deps order. That means we // any external reference will have been in the previous files in the set. for e in enums { e.bind(file: self, registry: registry, containingType: nil) } for m in messages { m.bind(file: self, registry: registry, containingType: nil) } for e in extensions { e.bind(file: self, registry: registry, containingType: nil) } for s in services { s.bind(file: self, registry: registry) } } /// Fetch the source information for a give path. For more details on the paths /// and what this information is, see `Google_Protobuf_SourceCodeInfo`. /// /// For simpler access to the comments for give message, fields, enums; see /// `Descriptor+Extensions.swift` and the `ProvidesLocationPath` and /// `ProvidesSourceCodeLocation` protocols. public func sourceCodeInfoLocation(path: IndexPath) -> Google_Protobuf_SourceCodeInfo.Location? { guard let location = locationMap[path] else { return nil } return location } // Lazy so this can be computed on demand, as the imported files won't need // comments during generation. private lazy var locationMap: [IndexPath: Google_Protobuf_SourceCodeInfo.Location] = { var result: [IndexPath: Google_Protobuf_SourceCodeInfo.Location] = [:] for loc in sourceCodeInfo.location { let intList = loc.path.map { Int($0) } result[IndexPath(indexes: intList)] = loc } return result }() } /// Describes a type of protocol message, or a particular group within a /// message. `Descriptor`s are not directly created, instead they are /// constructed/fetched via the `DescriptorSet` or they are directly accessed /// via a `messageType` property on `FieldDescriptor`s, etc. public final class Descriptor { // We can't assign a value directly to `proto` in the init because we get the // deprecation warning. This private prop only exists as a workaround to avoid // this warning and preserve backwards compatibility - it should be removed // when removing `proto`. private let _proto: Google_Protobuf_DescriptorProto @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var proto: Google_Protobuf_DescriptorProto { _proto } /// The type of this Message. public enum WellKnownType: String { /// An instance of google.protobuf.DoubleValue. case doubleValue = "google.protobuf.DoubleValue" /// An instance of google.protobuf.FloatValue. case floatValue = "google.protobuf.FloatValue" /// An instance of google.protobuf.Int64Value. case int64Value = "google.protobuf.Int64Value" /// An instance of google.protobuf.UInt64Value. case uint64Value = "google.protobuf.UInt64Value" /// An instance of google.protobuf.Int32Value. case int32Value = "google.protobuf.Int32Value" /// An instance of google.protobuf.UInt32Value. case uint32Value = "google.protobuf.UInt32Value" /// An instance of google.protobuf.StringValue. case stringValue = "google.protobuf.StringValue" /// An instance of google.protobuf.BytesValue. case bytesValue = "google.protobuf.BytesValue" /// An instance of google.protobuf.BoolValue. case boolValue = "google.protobuf.BoolValue" /// An instance of google.protobuf.Any. case any = "google.protobuf.Any" /// An instance of google.protobuf.FieldMask. case fieldMask = "google.protobuf.FieldMask" /// An instance of google.protobuf.Duration. case duration = "google.protobuf.Duration" /// An instance of google.protobuf.Timestamp. case timestamp = "google.protobuf.Timestamp" /// An instance of google.protobuf.Value. case value = "google.protobuf.Value" /// An instance of google.protobuf.ListValue. case listValue = "google.protobuf.ListValue" /// An instance of google.protobuf.Struct. case `struct` = "google.protobuf.Struct" } /// Describes an extension range of a message. `ExtensionRange`s are not /// directly created, instead they are constructed/fetched via the /// `Descriptor`. public final class ExtensionRange { /// The start field number of this range (inclusive). public let start: Int32 // The end field number of this range (exclusive). public fileprivate(set) var end: Int32 // Tndex of this extension range within the message's extension range array. public let index: Int /// The resolved features for this ExtensionRange. public let features: Google_Protobuf_FeatureSet /// The `Google_Protobuf_ExtensionRangeOptions` set on this ExtensionRange. public let options: Google_Protobuf_ExtensionRangeOptions /// The name of the containing type, not including its scope. public var name: String { containingType.name } /// The fully-qualified name of the containing type, scope delimited by /// periods. public var fullName: String { containingType.fullName } /// The .proto file in which this ExtensionRange was defined. public var file: FileDescriptor! { containingType.file } /// The descriptor that owns with ExtensionRange. public var containingType: Descriptor { _containingType! } // Storage for `containingType`, will be set by bind() private unowned var _containingType: Descriptor? fileprivate init( proto: Google_Protobuf_DescriptorProto.ExtensionRange, index: Int, features: Google_Protobuf_FeatureSet ) { self.start = proto.start self.end = proto.end self.index = index self.features = features self.options = proto.options } fileprivate func bind(containingType: Descriptor, registry: Registry) { self._containingType = containingType } } /// The name of the message type, not including its scope. public let name: String /// The fully-qualified name of the message type, scope delimited by /// periods. For example, message type "Foo" which is declared in package /// "bar" has full name "bar.Foo". If a type "Baz" is nested within /// Foo, Baz's `fullName` is "bar.Foo.Baz". To get only the part that /// comes after the last '.', use name(). public let fullName: String /// Index of this descriptor within the file or containing type's message /// type array. public let index: Int /// The .proto file in which this message type was defined. public var file: FileDescriptor! { _file! } /// If this Descriptor describes a nested type, this returns the type /// in which it is nested. public private(set) unowned var containingType: Descriptor? /// The resolved features for this Descriptor. public let features: Google_Protobuf_FeatureSet /// The `Google_Protobuf_MessageOptions` set on this Message. public let options: Google_Protobuf_MessageOptions // If this descriptor represents a well known type, which type it is. public let wellKnownType: WellKnownType? /// The enum defintions under this message. public let enums: [EnumDescriptor] /// The message defintions under this message. In the C++ Descriptor this /// is `nested_type`. public let messages: [Descriptor] /// The fields of this message. public let fields: [FieldDescriptor] /// The oneofs in this message. This can include synthetic oneofs. public let oneofs: [OneofDescriptor] /// Non synthetic oneofs. /// /// These always come first (enforced by the C++ Descriptor code). So this is always a /// leading subset of `oneofs` (or the same if there are no synthetic entries). public private(set) lazy var realOneofs: [OneofDescriptor] = { // Lazy because `isSynthetic` can't be called until after `bind()`. self.oneofs.filter { !$0._isSynthetic } }() /// The extension field defintions under this message. public let extensions: [FieldDescriptor] /// The extension ranges declared for this message. They are returned in /// the order they are defined in the .proto file. public let messageExtensionRanges: [ExtensionRange] /// The extension ranges declared for this message. They are returned in /// the order they are defined in the .proto file. @available(*, deprecated, message: "This property is now deprecated: please use proto.extensionRange instead.") public var extensionRanges: [Google_Protobuf_DescriptorProto.ExtensionRange] { proto.extensionRange } /// The `extensionRanges` are in the order they appear in the original .proto /// file; this orders them and then merges any ranges that are actually /// contiguious (i.e. - [(21,30),(10,20)] -> [(10,30)]) @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public private(set) lazy var normalizedExtensionRanges: [Google_Protobuf_DescriptorProto.ExtensionRange] = { var ordered = self.extensionRanges.sorted(by: { $0.start < $1.start }) if ordered.count > 1 { for i in (0..<(ordered.count - 1)).reversed() { if ordered[i].end == ordered[i + 1].start { // This is why we need `end`'s setter to be `fileprivate` instead of // having it be a `let`. // We should turn it back into a let once we get rid of this prop. ordered[i].end = ordered[i + 1].end ordered.remove(at: i + 1) } } } return ordered }() /// The `extensionRanges` from `normalizedExtensionRanges`, but takes a step /// further in that any ranges that do _not_ have any fields inbetween them /// are also merged together. These can then be used in context where it is /// ok to include field numbers that have to be extension or unknown fields. @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public private(set) lazy var ambitiousExtensionRanges: [Google_Protobuf_DescriptorProto.ExtensionRange] = { var merged = self.normalizedExtensionRanges var sortedFields = self.fields.sorted { $0.number < $1.number } if merged.count > 1 { var fieldNumbersReversedIterator = self.fields.map({ Int($0.number) }).sorted(by: { $0 > $1 }).makeIterator() var nextFieldNumber = fieldNumbersReversedIterator.next() while nextFieldNumber != nil && merged.last!.start < nextFieldNumber! { nextFieldNumber = fieldNumbersReversedIterator.next() } for i in (0..<(merged.count - 1)).reversed() { if nextFieldNumber == nil || merged[i].start > nextFieldNumber! { // No fields left or range starts after the next field, merge it with // the previous one. merged[i].end = merged[i + 1].end merged.remove(at: i + 1) } else { // can't merge, find the next field number below this range. while nextFieldNumber != nil && merged[i].start < nextFieldNumber! { nextFieldNumber = fieldNumbersReversedIterator.next() } } } } return merged }() /// The reserved field number ranges for this message. These are returned /// in the order they are defined in the .proto file. public let reservedRanges: [Range] /// The reserved field names for this message. These are returned in the /// order they are defined in the .proto file. public let reservedNames: [String] /// True/False if this Message is just for a `map<>` entry. @available(*, deprecated, renamed: "options.mapEntry") public var isMapEntry: Bool { options.mapEntry } /// Returns the `FieldDescriptor`s for the "key" and "value" fields. If /// this isn't a map entry field, returns nil. /// /// This is like the C++ Descriptor `map_key()` and `map_value()` methods. public var mapKeyAndValue: (key: FieldDescriptor, value: FieldDescriptor)? { guard options.mapEntry else { return nil } precondition(fields.count == 2) return (key: fields[0], value: fields[1]) } // Storage for `file`, will be set by bind() private unowned var _file: FileDescriptor? @available(*, deprecated, renamed: "options.messageSetWireFormat") public var useMessageSetWireFormat: Bool { options.messageSetWireFormat } fileprivate init( proto: Google_Protobuf_DescriptorProto, index: Int, parentFeatures: Google_Protobuf_FeatureSet, featureResolver: FeatureResolver, registry: Registry, scope: String ) { self._proto = proto self.name = proto.name let fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)" self.fullName = fullName self.index = index let resolvedFeatures = featureResolver.resolve(proto.options, resolvedParent: parentFeatures) self.features = resolvedFeatures self.options = proto.options self.wellKnownType = WellKnownType(rawValue: fullName) self.reservedRanges = proto.reservedRange.map { $0.start..<$0.end } self.reservedNames = proto.reservedName // TODO: This can skip the synthetic oneofs as no features can be set on // them to inherrit things. let oneofFeatures = proto.oneofDecl.map { featureResolver.resolve($0.options, resolvedParent: resolvedFeatures) } self.messageExtensionRanges = proto.extensionRange.enumerated().map { ExtensionRange( proto: $0.element, index: $0.offset, features: featureResolver.resolve( $0.element.options, resolvedParent: resolvedFeatures ) ) } self.enums = proto.enumType.enumerated().map { EnumDescriptor( proto: $0.element, index: $0.offset, parentFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry, scope: fullName ) } self.messages = proto.nestedType.enumerated().map { Descriptor( proto: $0.element, index: $0.offset, parentFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry, scope: fullName ) } self.fields = proto.field.enumerated().map { // For field Features inherrit from the parent oneof or message. A // synthetic oneof (for proto3 optional) can't get features, so those // don't come into play. let inRealOneof = $0.element.hasOneofIndex && !$0.element.proto3Optional return FieldDescriptor( messageField: $0.element, index: $0.offset, parentFeatures: inRealOneof ? oneofFeatures[Int($0.element.oneofIndex)] : resolvedFeatures, featureResolver: featureResolver, registry: registry ) } self.oneofs = proto.oneofDecl.enumerated().map { OneofDescriptor( proto: $0.element, index: $0.offset, features: oneofFeatures[$0.offset], registry: registry ) } self.extensions = proto.extension.enumerated().map { FieldDescriptor( extension: $0.element, index: $0.offset, parentFeatures: resolvedFeatures, featureResolver: featureResolver, registry: registry ) } // Done initializing, register ourselves. registry.register(message: self) } fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) { _file = file self.containingType = containingType for e in messageExtensionRanges { e.bind(containingType: self, registry: registry) } for e in enums { e.bind(file: file, registry: registry, containingType: self) } for m in messages { m.bind(file: file, registry: registry, containingType: self) } for f in fields { f.bind(file: file, registry: registry, containingType: self) } for o in oneofs { o.bind(registry: registry, containingType: self) } for e in extensions { e.bind(file: file, registry: registry, containingType: self) } // Synthetic oneofs come after normal oneofs. The C++ Descriptor enforces this, only // here as a secondary validation because other code can rely on it. var seenSynthetic = false for o in oneofs { if seenSynthetic { // Once we've seen one synthetic, all the rest must also be synthetic. precondition(o._isSynthetic) } else { seenSynthetic = o._isSynthetic } } } } /// Describes a type of protocol enum. `EnumDescriptor`s are not directly /// created, instead they are constructed/fetched via the `DescriptorSet` or /// they are directly accessed via a `EnumType` property on `FieldDescriptor`s, /// etc. public final class EnumDescriptor { // We can't assign a value directly to `proto` in the init because we get the // deprecation warning. This private prop only exists as a workaround to avoid // this warning and preserve backwards compatibility - it should be removed // when removing `proto`. private let _proto: Google_Protobuf_EnumDescriptorProto @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var proto: Google_Protobuf_EnumDescriptorProto { _proto } /// The name of this enum type in the containing scope. public let name: String /// The fully-qualified name of the enum type, scope delimited by periods. public let fullName: String /// Index of this enum within the file or containing message's enums. public let index: Int /// The .proto file in which this message type was defined. public var file: FileDescriptor! { _file! } /// If this Descriptor describes a nested type, this returns the type /// in which it is nested. public private(set) unowned var containingType: Descriptor? /// The resolved features for this Enum. public let features: Google_Protobuf_FeatureSet /// The values defined for this enum. Guaranteed (by protoc) to be atleast /// one item. These are returned in the order they were defined in the .proto /// file. public let values: [EnumValueDescriptor] /// The `Google_Protobuf_MessageOptions` set on this enum. public let options: Google_Protobuf_EnumOptions /// The reserved value ranges for this enum. These are returned in the order /// they are defined in the .proto file. public let reservedRanges: [ClosedRange] /// The reserved value names for this enum. These are returned in the order /// they are defined in the .proto file. public let reservedNames: [String] /// Returns true whether this is a "closed" enum, meaning that it: /// - Has a fixed set of named values. /// - Encountering values not in this set causes them to be treated as unknown /// fields. /// - The first value (i.e., the default) may be nonzero. public var isClosed: Bool { // Implementation comes from C++ EnumDescriptor::is_closed(). features.enumType == .closed } // Storage for `file`, will be set by bind() private unowned var _file: FileDescriptor? @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var defaultValue: EnumValueDescriptor { // The compiler requires the be atleast one value, so force unwrap is safe. values.first! } fileprivate init( proto: Google_Protobuf_EnumDescriptorProto, index: Int, parentFeatures: Google_Protobuf_FeatureSet, featureResolver: FeatureResolver, registry: Registry, scope: String ) { self._proto = proto self.name = proto.name self.fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)" self.index = index let resolvedFeatures = featureResolver.resolve(proto.options, resolvedParent: parentFeatures) self.features = resolvedFeatures self.options = proto.options self.reservedRanges = proto.reservedRange.map { $0.start...$0.end } self.reservedNames = proto.reservedName self.values = proto.value.enumerated().map { EnumValueDescriptor( proto: $0.element, index: $0.offset, features: featureResolver.resolve( $0.element.options, resolvedParent: resolvedFeatures ), scope: scope ) } // Done initializing, register ourselves. registry.register(enum: self) for v in values { v.bind(enumType: self) } } fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) { _file = file self.containingType = containingType } } /// Describes an individual enum constant of a particular type. To get the /// `EnumValueDescriptor` for a given enum value, first get the `EnumDescriptor` /// for its type. public final class EnumValueDescriptor { // We can't assign a value directly to `proto` in the init because we get the // deprecation warning. This private prop only exists as a workaround to avoid // this warning and preserve backwards compatibility - it should be removed // when removing `proto`. private let _proto: Google_Protobuf_EnumValueDescriptorProto @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var proto: Google_Protobuf_EnumValueDescriptorProto { _proto } /// Name of this enum constant. public let name: String private var _fullName: String /// The full_name of an enum value is a sibling symbol of the enum type. /// e.g. the full name of FieldDescriptorProto::TYPE_INT32 is actually /// "google.protobuf.FieldDescriptorProto.TYPE_INT32", NOT /// "google.protobuf.FieldDescriptorProto.Type.TYPE_INT32". This is to conform /// with C++ scoping rules for enums. public var fullName: String { get { self._fullName } @available(*, deprecated, message: "fullName is now read-only") set { self._fullName = newValue } } /// Index within the enums's `EnumDescriptor`. public let index: Int /// Numeric value of this enum constant. public let number: Int32 @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public private(set) weak var aliasOf: EnumValueDescriptor? @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public fileprivate(set) var aliases: [EnumValueDescriptor] = [] /// The resolved features for this EnumValue. public let features: Google_Protobuf_FeatureSet /// The .proto file in which this message type was defined. public var file: FileDescriptor! { enumType.file } /// The type of this value. public var enumType: EnumDescriptor! { _enumType! } /// The `Google_Protobuf_EnumValueOptions` set on this value. public let options: Google_Protobuf_EnumValueOptions // Storage for `enumType`, will be set by bind() private unowned var _enumType: EnumDescriptor? fileprivate init( proto: Google_Protobuf_EnumValueDescriptorProto, index: Int, features: Google_Protobuf_FeatureSet, scope: String ) { self._proto = proto self.name = proto.name self._fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)" self.index = index self.features = features self.number = proto.number self.options = proto.options } fileprivate func bind(enumType: EnumDescriptor) { self._enumType = enumType } } /// Describes a oneof defined in a message type. public final class OneofDescriptor { // We can't assign a value directly to `proto` in the init because we get the // deprecation warning. This private prop only exists as a workaround to avoid // this warning and preserve backwards compatibility - it should be removed // when removing `proto`. private let _proto: Google_Protobuf_OneofDescriptorProto @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var proto: Google_Protobuf_OneofDescriptorProto { _proto } /// Name of this oneof. public let name: String /// Fully-qualified name of the oneof. public var fullName: String { "\(containingType.fullName).\(name)" } /// Index of this oneof within the message's oneofs. public let index: Int /// The resolved features for this Oneof. public let features: Google_Protobuf_FeatureSet /// Returns whether this oneof was inserted by the compiler to wrap a proto3 /// optional field. If this returns true, code generators should *not* emit it. var _isSynthetic: Bool { fields.count == 1 && fields.first!.proto3Optional } @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var isSynthetic: Bool { _isSynthetic } /// The .proto file in which this oneof type was defined. public var file: FileDescriptor! { containingType.file } /// The Descriptor of the message that defines this oneof. public var containingType: Descriptor! { _containingType! } /// The `Google_Protobuf_OneofOptions` set on this oneof. public let options: Google_Protobuf_OneofOptions /// The members of this oneof, in the order in which they were declared in the /// .proto file. public private(set) lazy var fields: [FieldDescriptor] = { let myIndex = Int32(self.index) return containingType.fields.filter { $0.oneofIndex == myIndex } }() // Storage for `containingType`, will be set by bind() private unowned var _containingType: Descriptor? fileprivate init( proto: Google_Protobuf_OneofDescriptorProto, index: Int, features: Google_Protobuf_FeatureSet, registry: Registry ) { self._proto = proto self.name = proto.name self.index = index self.features = features self.options = proto.options } fileprivate func bind(registry: Registry, containingType: Descriptor) { _containingType = containingType } } /// Describes a single field of a message. To get the descriptor for a given /// field, first get the `Descriptor` for the message in which it is defined, /// then find the field. To get a `FieldDescriptor` for an extension, get the /// `Descriptor` or `FileDescriptor` for its containing scope, find the /// extension. public final class FieldDescriptor { // We can't assign a value directly to `proto` in the init because we get the // deprecation warning. This private prop only exists as a workaround to avoid // this warning and preserve backwards compatibility - it should be removed // when removing `proto`. private let _proto: Google_Protobuf_FieldDescriptorProto @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var proto: Google_Protobuf_FieldDescriptorProto { _proto } /// Name of this field within the message. public let name: String /// Fully-qualified name of the field. public var fullName: String { // Since the fullName isn't needed on fields that often, compute it on demand. guard isExtension else { // Normal field on a message. return "\(containingType.fullName).\(name)" } if let extensionScope = extensionScope { return "\(extensionScope.fullName).\(name)" } let package = file.package return package.isEmpty ? name : "\(package).\(name)" } /// JSON name of this field. public let jsonName: String? public let features: Google_Protobuf_FeatureSet /// File in which this field was defined. public var file: FileDescriptor! { _file! } /// If this is an extension field. public let isExtension: Bool /// The field number. public let number: Int32 /// Valid field numbers are positive integers up to kMaxNumber. static let kMaxNumber: Int = (1 << 29) - 1 /// First field number reserved for the protocol buffer library /// implementation. Users may not declare fields that use reserved numbers. static let kFirstReservedNumber: Int = 19000 /// Last field number reserved for the protocol buffer library implementation. /// Users may not declare fields that use reserved numbers. static let kLastReservedNumber: Int = 19999 /// Declared type of this field. public private(set) var type: Google_Protobuf_FieldDescriptorProto.TypeEnum /// This should never be called directly. Use isRequired and isRepeated /// helper methods instead. @available(*, deprecated, message: "Use isRequired or isRepeated instead.") public var label: Google_Protobuf_FieldDescriptorProto.Label { if isRepeated { return .repeated } else if isRequired { return .required } else { return .optional } } // Storage for `label`, used by other apis. private var _label: Google_Protobuf_FieldDescriptorProto.Label /// Whether or not the field is required. For proto2 required fields and /// Editions `LEGACY_REQUIRED` fields. public var isRequired: Bool { // Implementation comes from FieldDescriptor::is_required() features.fieldPresence == .legacyRequired } /// Whether or not the field is repeated/map field. public var isRepeated: Bool { _label == .repeated } /// Use !isRequired() && !isRepeated() instead. @available(*, deprecated, message: "Use !isRequired && !isRepeated instead.") public var isOptional: Bool { label == .optional } /// Is this field packable. public var isPackable: Bool { // This logic comes from the C++ FieldDescriptor::is_packable() impl. isRepeated && FieldDescriptor.isPackable(type: type) } /// If this field is packable and packed. public var isPacked: Bool { // This logic comes from the C++ FieldDescriptor::is_packed() impl. guard isPackable else { return false } return features.repeatedFieldEncoding == .packed } /// True if this field is a map. public var isMap: Bool { // This logic comes from the C++ FieldDescriptor::is_map() impl. type == .message && messageType!.options.mapEntry } /// Returns true if this field was syntactically written with "optional" in the /// .proto file. Excludes singular proto3 fields that do not have a label. package var _hasOptionalKeyword: Bool { // This logic comes from the C++ FieldDescriptor::has_optional_keyword() // impl. proto3Optional || (file.edition == .proto2 && !isRequired && !isRepeated && oneofIndex == nil) } @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var hasOptionalKeyword: Bool { _hasOptionalKeyword } /// Returns true if this field tracks presence, ie. does the field /// distinguish between "unset" and "present with default value." /// This includes required, optional, and oneof fields. It excludes maps, /// repeated fields, and singular proto3 fields without "optional". public var hasPresence: Bool { // This logic comes from the C++ FieldDescriptor::has_presence() impl. guard !isRepeated else { return false } switch type { case .group, .message: // Groups/messages always get field presence. return true default: break } return isExtension || oneofIndex != nil || features.fieldPresence != .implicit } /// Returns true if this is a string field and should do UTF-8 validation. /// /// This api is for completeness, but it likely should never be used. The /// concept comes from the C++ FieldDescriptory::requires_utf8_validation(), /// but doesn't make a lot of sense for Swift Protobuf because `string` fields /// are modeled as Swift `String` objects, and thus they always have to be /// valid UTF-8. If something were to try putting something else in the field, /// the library won't be able to parse it. While that sounds bad, other /// languages have similar issues with their _string_ types and thus have the /// same issues. public var requiresUTF8Validation: Bool { type == .string && features.utf8Validation == .verify } /// Index of this field within the message's fields, or the file or /// extension scope's extensions. public let index: Int /// The explicitly declared default value for this field. /// /// This is the *raw* string value from the .proto file that was listed as /// the default, it is up to the consumer to convert it correctly for the /// type of this field. The C++ FieldDescriptor does offer some apis to /// help with that, but at this time, that is not provided here. @available(*, deprecated, renamed: "defaultValue") public var explicitDefaultValue: String? { defaultValue } /// The explicitly declared default value for this field. /// /// This is the *raw* string value from the .proto file that was listed as /// the default, it is up to the consumer to convert it correctly for the /// type of this field. The C++ FieldDescriptor does offer some apis to /// help with that, but at this time, that is not provided here. public let defaultValue: String? /// The `Descriptor` of the message which this is a field of. For extensions, /// this is the extended type. public var containingType: Descriptor! { _containingType! } /// The oneof this field is a member of. @available(*, deprecated, renamed: "containingOneof") public var oneof: OneofDescriptor? { containingOneof } /// The oneof this field is a member of. public var containingOneof: OneofDescriptor? { guard let oneofIndex = oneofIndex else { return nil } return containingType.oneofs[Int(oneofIndex)] } /// The non synthetic oneof this field is a member of. @available(*, deprecated, renamed: "realContainingOneof") public var realOneof: OneofDescriptor? { realContainingOneof } /// The non synthetic oneof this field is a member of. public var realContainingOneof: OneofDescriptor? { guard let oneof = containingOneof, !oneof._isSynthetic else { return nil } return oneof } /// The index in a oneof this field is in. public let oneofIndex: Int32? // This builds basically a union for the storage for `extensionScope` // and the value to look it up with. private enum ExtensionScopeStorage { case extendee(String) // The value to be used for looked up during `bind()`. case message(UnownedBox) } private var _extensionScopeStorage: ExtensionScopeStorage? /// Extensions can be declared within the scope of another message. If this /// is an extension field, then this will be the scope it was declared in /// nil if was declared at a global scope. public var extensionScope: Descriptor? { guard case .message(let boxed) = _extensionScopeStorage else { return nil } return boxed.value } // This builds basically a union for the storage for `messageType` // and `enumType` since only one can needed at a time. private enum FieldTypeStorage { case typeName(String) // The value to be looked up during `bind()`. case message(UnownedBox) case `enum`(UnownedBox) } private var _fieldTypeStorage: FieldTypeStorage? /// When this is a message/group field, that message's `Descriptor`. public var messageType: Descriptor! { guard case .message(let boxed) = _fieldTypeStorage else { return nil } return boxed.value } /// When this is a enum field, that enum's `EnumDescriptor`. public var enumType: EnumDescriptor! { guard case .enum(let boxed) = _fieldTypeStorage else { return nil } return boxed.value } /// The FieldOptions for this field. public var options: Google_Protobuf_FieldOptions let proto3Optional: Bool // Storage for `containingType`, will be set by bind() private unowned var _containingType: Descriptor? // Storage for `file`, will be set by bind() private unowned var _file: FileDescriptor? fileprivate convenience init( messageField proto: Google_Protobuf_FieldDescriptorProto, index: Int, parentFeatures: Google_Protobuf_FeatureSet, featureResolver: FeatureResolver, registry: Registry ) { precondition(proto.extendee.isEmpty) // Only for extensions // On regular fields, it only makes sense to get `.proto3Optional` // when also in a (synthetic) oneof. So...no oneof index, it better // not be `.proto3Optional` precondition(proto.hasOneofIndex || !proto.proto3Optional) self.init( proto: proto, index: index, parentFeatures: parentFeatures, featureResolver: featureResolver, registry: registry, isExtension: false ) } fileprivate convenience init( extension proto: Google_Protobuf_FieldDescriptorProto, index: Int, parentFeatures: Google_Protobuf_FeatureSet, featureResolver: FeatureResolver, registry: Registry ) { precondition(!proto.extendee.isEmpty) // Required for extensions // FieldDescriptorProto is used for fields or extensions, generally // .proto3Optional only makes sense on fields if it is in a oneof. But, // it is allowed on extensions. For information on that, see // https://github.com/protocolbuffers/protobuf/issues/8234#issuecomment-774224376 // The C++ Descriptor code encorces the field/oneof part, but nothing // is checked on the oneof side. precondition(!proto.hasOneofIndex) self.init( proto: proto, index: index, parentFeatures: parentFeatures, featureResolver: featureResolver, registry: registry, isExtension: true ) } private init( proto: Google_Protobuf_FieldDescriptorProto, index: Int, parentFeatures: Google_Protobuf_FeatureSet, featureResolver: FeatureResolver, registry: Registry, isExtension: Bool ) { self._proto = proto self.name = proto.name self.index = index self.features = featureResolver.resolve(proto, resolvedParent: parentFeatures) self.defaultValue = proto.hasDefaultValue ? proto.defaultValue : nil precondition(proto.hasJsonName) // protoc should always set the name self.jsonName = proto.jsonName self.isExtension = isExtension self.number = proto.number // This remapping is based follow part of what upstream // `DescriptorBuilder::PostProcessFieldFeatures()` does. It is needed to // help ensure basic transforms from .proto2 to .edition2023 generate the // same code/behaviors. if proto.type == .message && self.features.messageEncoding == .delimited { self.type = .group } else { self.type = proto.type } // This remapping is based follow part of what upstream // `DescriptorBuilder::PostProcessFieldFeatures()` does. If generators use // helper instead of access `label` directly, they won't need this, but for // consistency, remap `label` to expose the pre Editions/Features value. if self.features.fieldPresence == .legacyRequired && proto.label == .optional { self._label = .required } else { self._label = proto.label } self.options = proto.options self.oneofIndex = proto.hasOneofIndex ? proto.oneofIndex : nil self.proto3Optional = proto.proto3Optional self._extensionScopeStorage = isExtension ? .extendee(proto.extendee) : nil switch type { case .group, .message, .enum: _fieldTypeStorage = .typeName(proto.typeName) default: _fieldTypeStorage = nil } } fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) { _file = file // See the defintions of `containingType` and `extensionScope`, this // dance can otherwise be a little confusing. if case .extendee(let extendee) = _extensionScopeStorage { _containingType = registry.descriptor(named: extendee)! if let containingType = containingType { _extensionScopeStorage = .message(UnownedBox(value: containingType)) } else { _extensionScopeStorage = nil // Top level } } else { _containingType = containingType } if case .typeName(let typeName) = _fieldTypeStorage { if type == .enum { _fieldTypeStorage = .enum(UnownedBox(value: registry.enumDescriptor(named: typeName)!)) } else { let msgtype = registry.descriptor(named: typeName)! _fieldTypeStorage = .message(UnownedBox(value: msgtype)) if type == .group && (msgtype.options.mapEntry || (_containingType != nil && _containingType!.options.mapEntry)) { type = .message } } } } } /// Describes an RPC service. /// /// SwiftProtobuf does *not* generate anything for these (or methods), but /// they are here to support things that generate based off RPCs defined in /// .proto file (gRPC, etc.). public final class ServiceDescriptor { // We can't assign a value directly to `proto` in the init because we get the // deprecation warning. This private prop only exists as a workaround to avoid // this warning and preserve backwards compatibility - it should be removed // when removing `proto`. private let _proto: Google_Protobuf_ServiceDescriptorProto @available(*, deprecated, message: "Please open a GitHub issue if you think functionality is missing.") public var proto: Google_Protobuf_ServiceDescriptorProto { _proto } /// The name of the service, not including its containing scope. public let name: String /// The fully-qualified name of the service, scope delimited by periods. public let fullName: String /// Index of this service within the file's services. public let index: Int /// The .proto file in which this service was defined public var file: FileDescriptor! { _file! } /// The resolved features for this Service. public let features: Google_Protobuf_FeatureSet /// Get `Google_Protobuf_ServiceOptions` for this service. public let options: Google_Protobuf_ServiceOptions /// The methods defined on this service. These are returned in the order they /// were defined in the .proto file. public let methods: [MethodDescriptor] // Storage for `file`, will be set by bind() private unowned var _file: FileDescriptor? fileprivate init( proto: Google_Protobuf_ServiceDescriptorProto, index: Int, fileFeatures: Google_Protobuf_FeatureSet, featureResolver: FeatureResolver, registry: Registry, scope: String ) { self._proto = proto self.name = proto.name self.fullName = scope.isEmpty ? proto.name : "\(scope).\(proto.name)" self.index = index let resolvedFeatures = featureResolver.resolve(proto.options, resolvedParent: fileFeatures) self.features = resolvedFeatures self.options = proto.options self.methods = proto.method.enumerated().map { MethodDescriptor( proto: $0.element, index: $0.offset, features: featureResolver.resolve($0.element.options, resolvedParent: resolvedFeatures), registry: registry ) } // Done initializing, register ourselves. registry.register(service: self) } fileprivate func bind(file: FileDescriptor, registry: Registry) { _file = file for m in methods { m.bind(service: self, registry: registry) } } } /// Describes an individual service method. /// /// SwiftProtobuf does *not* generate anything for these (or services), but /// they are here to support things that generate based off RPCs defined in /// .proto file (gRPC, etc.). public final class MethodDescriptor { /// The name of the method, not including its containing scope. public let name: String /// The fully-qualified name of the method, scope delimited by periods. public var fullName: String { "\(service.fullName).\(name)" } /// Index of this service within the file's services. public let index: Int /// The .proto file in which this service was defined public var file: FileDescriptor! { service.file } /// The service tha defines this method. public var service: ServiceDescriptor! { _service! } /// The resolved features for this Method. public let features: Google_Protobuf_FeatureSet /// Get `Google_Protobuf_MethodOptions` for this method. public let options: Google_Protobuf_MethodOptions /// The type of protocol message which this method accepts as input. public private(set) var inputType: Descriptor! /// The type of protocol message which this message produces as output. public private(set) var outputType: Descriptor! /// Whether the client streams multiple requests. public let clientStreaming: Bool // Whether the server streams multiple responses. public let serverStreaming: Bool /// The proto version of the descriptor that defines this method. @available( *, deprecated, message: "Use the properties directly or open a GitHub issue for something missing" ) public var proto: Google_Protobuf_MethodDescriptorProto { _proto } private let _proto: Google_Protobuf_MethodDescriptorProto // Storage for `service`, will be set by bind() private unowned var _service: ServiceDescriptor? fileprivate init( proto: Google_Protobuf_MethodDescriptorProto, index: Int, features: Google_Protobuf_FeatureSet, registry: Registry ) { self.name = proto.name self.index = index self.features = features self.options = proto.options self.clientStreaming = proto.clientStreaming self.serverStreaming = proto.serverStreaming // Can look these up because all the Descriptors are already registered self.inputType = registry.descriptor(named: proto.inputType)! self.outputType = registry.descriptor(named: proto.outputType)! self._proto = proto } fileprivate func bind(service: ServiceDescriptor, registry: Registry) { self._service = service } } /// Helper used under the hood to build the mapping tables and look things up. /// /// All fullNames are like defined in descriptor.proto, they start with a /// leading '.'. This simplifies the string ops when assembling the message /// graph. private final class Registry { private var fileMap = [String: FileDescriptor]() // These three are all keyed by the full_name prefixed with a '.'. private var messageMap = [String: Descriptor]() private var enumMap = [String: EnumDescriptor]() private var serviceMap = [String: ServiceDescriptor]() init() {} func register(file: FileDescriptor) { fileMap[file.name] = file } func register(message: Descriptor) { messageMap[".\(message.fullName)"] = message } func register(enum e: EnumDescriptor) { enumMap[".\(e.fullName)"] = e } func register(service: ServiceDescriptor) { serviceMap[".\(service.fullName)"] = service } func fileDescriptor(named name: String) -> FileDescriptor? { fileMap[name] } func descriptor(named fullName: String) -> Descriptor? { messageMap[fullName] } func enumDescriptor(named fullName: String) -> EnumDescriptor? { enumMap[fullName] } func serviceDescriptor(named fullName: String) -> ServiceDescriptor? { serviceMap[fullName] } } /// Helper for making an enum associated value `unowned`. private struct UnownedBox { unowned let value: T }