| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- // 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.
- ///
- // -----------------------------------------------------------------------------
- // NOTES:
- // 1. `lazy` and `weak` (or `unowned`) doesn't seem to work, so the impl here
- // can't simply keep the `Resolver` and look things up when first accessed
- // instead `bind()` is used to force those lookups to happen.
- // 2. Despite the Swift docs seeming to say `unowned` should work, there are
- // compile errors, `weak` ends up being used even though this code doesn't
- // need the zeroing behaviors. If it did, things will be a little faster
- // as the tracking for weak references wouldn't be needed.
- import Foundation
- import SwiftProtobuf
- public final class DescriptorSet {
- public let files: [FileDescriptor]
- private let registry = Registry()
- public convenience init(proto: Google_Protobuf_FileDescriptorSet) {
- self.init(protos: proto.file)
- }
- public init(protos: [Google_Protobuf_FileDescriptorProto]) {
- let registry = self.registry
- self.files = protos.map { return FileDescriptor(proto: $0, registry: registry) }
- }
- public func lookupFileDescriptor(protoName name: String) -> FileDescriptor {
- return registry.fileDescriptor(name: name)
- }
- public func lookupDescriptor(protoName name: String) -> Descriptor {
- return registry.descriptor(name: name)
- }
- public func lookupEnumDescriptor(protoName name: String) -> EnumDescriptor {
- return registry.enumDescriptor(name: name)
- }
- public func lookupServiceDescriptor(protoName name: String) -> ServiceDescriptor {
- return registry.serviceDescriptor(name: 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? {
- return registry.fileDescriptor(named: name)
- }
- }
- public final class FileDescriptor {
- 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
- }
- }
- }
- public let proto: Google_Protobuf_FileDescriptorProto
- public var name: String { return proto.name }
- public var package: String { return proto.package }
- public let syntax: Syntax
- public let dependencies: [FileDescriptor]
- public var publicDependencies: [FileDescriptor] { return proto.publicDependency.map { dependencies[Int($0)] } }
- public var weakDependencies: [FileDescriptor] { return proto.weakDependency.map { dependencies[Int($0)] } }
- public let enums: [EnumDescriptor]
- public let messages: [Descriptor]
- public let extensions: [FieldDescriptor]
- public let services: [ServiceDescriptor]
- public var fileOptions: Google_Protobuf_FileOptions { return proto.options }
- public var isDeprecated: Bool { return proto.options.deprecated }
- fileprivate init(proto: Google_Protobuf_FileDescriptorProto, registry: Registry) {
- self.proto = proto
- self.syntax = Syntax(rawValue: proto.syntax)!
- let prefix: String
- let protoPackage = proto.package
- if protoPackage.isEmpty {
- prefix = ""
- } else {
- prefix = "." + protoPackage
- }
- self.enums = proto.enumType.enumeratedMap {
- return EnumDescriptor(proto: $1, index: $0, registry: registry, fullNamePrefix: prefix)
- }
- self.messages = proto.messageType.enumeratedMap {
- return Descriptor(proto: $1, index: $0, registry: registry, fullNamePrefix: prefix)
- }
- self.extensions = proto.extension.enumeratedMap {
- return FieldDescriptor(proto: $1, index: $0, registry: registry, isExtension: true)
- }
- self.services = proto.service.enumeratedMap {
- return ServiceDescriptor(proto: $1, index: $0, registry: registry, fullNamePrefix: prefix)
- }
- // 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.
- self.dependencies = proto.dependency.map { return registry.fileDescriptor(name: $0) }
- // 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.
- self.enums.forEach { $0.bind(file: self, registry: registry, containingType: nil) }
- self.messages.forEach { $0.bind(file: self, registry: registry, containingType: nil) }
- self.extensions.forEach { $0.bind(file: self, registry: registry, containingType: nil) }
- self.services.forEach { $0.bind(file: self, registry: registry) }
- }
- 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 self.proto.sourceCodeInfo.location {
- let intList = loc.path.map { return Int($0) }
- result[IndexPath(indexes: intList)] = loc
- }
- return result
- }()
- }
- public final class Descriptor {
- public let proto: Google_Protobuf_DescriptorProto
- let index: Int
- public let fullName: String
- public var name: String { return proto.name }
- public private(set) weak var file: FileDescriptor!
- public private(set) weak var containingType: Descriptor?
- public let enums: [EnumDescriptor]
- public let messages: [Descriptor]
- public let fields: [FieldDescriptor]
- 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()`.
- return self.oneofs.filter { !$0.isSynthetic }
- }()
- public let extensions: [FieldDescriptor]
- public var extensionRanges: [Google_Protobuf_DescriptorProto.ExtensionRange] {
- return 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)])
- public private(set) lazy var normalizedExtensionRanges: [Google_Protobuf_DescriptorProto.ExtensionRange] = {
- var ordered = self.extensionRanges.sorted(by: { return $0.start < $1.start })
- if ordered.count > 1 {
- for i in (0..<(ordered.count - 1)).reversed() {
- if ordered[i].end == ordered[i+1].start {
- 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.
- 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
- }()
- /// True/False if this Message is just for a `map<>` entry.
- public var isMapEntry: Bool { return proto.options.mapEntry }
- /// Returns the `FieldDescriptor`s for the "key" and "value" fields. If
- /// this isn't a map entry field, returns nil.
- public var mapKeyAndValue: (key: FieldDescriptor, value: FieldDescriptor)? {
- guard isMapEntry else { return nil }
- assert(fields.count == 2)
- return (key: fields[0], value: fields[1])
- }
- public var useMessageSetWireFormat: Bool { return proto.options.messageSetWireFormat }
- fileprivate init(proto: Google_Protobuf_DescriptorProto,
- index: Int,
- registry: Registry,
- fullNamePrefix prefix: String) {
- self.proto = proto
- self.index = index
- let fullName = "\(prefix).\(proto.name)"
- self.fullName = fullName
- self.enums = proto.enumType.enumeratedMap {
- return EnumDescriptor(proto: $1, index: $0, registry: registry, fullNamePrefix: fullName)
- }
- self.messages = proto.nestedType.enumeratedMap {
- return Descriptor(proto: $1, index: $0, registry: registry, fullNamePrefix: fullName)
- }
- self.fields = proto.field.enumeratedMap {
- return FieldDescriptor(proto: $1, index: $0, registry: registry)
- }
- self.oneofs = proto.oneofDecl.enumeratedMap {
- return OneofDescriptor(proto: $1, index: $0, registry: registry)
- }
- self.extensions = proto.extension.enumeratedMap {
- return FieldDescriptor(proto: $1, index: $0, registry: registry, isExtension: true)
- }
- // Done initializing, register ourselves.
- registry.register(message: self)
- }
- fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) {
- self.file = file
- self.containingType = containingType
- self.enums.forEach { $0.bind(file: file, registry: registry, containingType: self) }
- self.messages.forEach { $0.bind(file: file, registry: registry, containingType: self) }
- self.fields.forEach { $0.bind(file: file, registry: registry, containingType: self) }
- self.oneofs.forEach { $0.bind(registry: registry, containingType: self) }
- self.extensions.forEach { $0.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 self.oneofs {
- if o.isSynthetic {
- seenSynthetic = true
- } else {
- assert(!seenSynthetic)
- }
- }
- }
- }
- public final class EnumDescriptor {
- public let proto: Google_Protobuf_EnumDescriptorProto
- let index: Int
- public let fullName: String
- public var name: String { return proto.name }
- public private(set) weak var file: FileDescriptor!
- public private(set) weak var containingType: Descriptor?
- // This is lazy so it is they are created only when needed, that way an
- // import doesn't have to do all this work unless the enum is used by
- // the importer.
- public private(set) lazy var values: [EnumValueDescriptor] = {
- var firstValues = [Int32:EnumValueDescriptor]()
- var result = [EnumValueDescriptor]()
- var i = 0
- for p in self.proto.value {
- let aliasing = firstValues[p.number]
- let d = EnumValueDescriptor(proto: p, index: i, enumType: self, aliasing: aliasing)
- result.append(d)
- i += 1
- if let aliasing = aliasing {
- aliasing.aliases.append(d)
- } else {
- firstValues[d.number] = d
- }
- }
- return result
- }()
- public var defaultValue: EnumValueDescriptor {
- // The compiler requires the be atleast one value, so force unwrap is safe.
- return values.first!
- }
- fileprivate init(proto: Google_Protobuf_EnumDescriptorProto,
- index: Int,
- registry: Registry,
- fullNamePrefix prefix: String) {
- self.proto = proto
- self.index = index
- self.fullName = "\(prefix).\(proto.name)"
- // Done initializing, register ourselves.
- registry.register(enum: self)
- }
- fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) {
- self.file = file
- self.containingType = containingType
- }
- }
- public final class EnumValueDescriptor {
- public let proto: Google_Protobuf_EnumValueDescriptorProto
- let index: Int
- public private(set) weak var enumType: EnumDescriptor!
- public weak var file: FileDescriptor! { return enumType.file }
- public var name: String { return proto.name }
- public var fullName: String
- public var number: Int32 { return proto.number }
- public private(set) weak var aliasOf: EnumValueDescriptor?
- public fileprivate(set) var aliases: [EnumValueDescriptor] = []
- fileprivate init(proto: Google_Protobuf_EnumValueDescriptorProto,
- index: Int,
- enumType: EnumDescriptor,
- aliasing: EnumValueDescriptor?) {
- self.proto = proto
- self.index = index
- self.enumType = enumType
- aliasOf = aliasing
- let fullName = "\(enumType.fullName).\(proto.name)"
- self.fullName = fullName
- }
- }
- public final class OneofDescriptor {
- public let proto: Google_Protobuf_OneofDescriptorProto
- let index: Int
- public private(set) weak var containingType: Descriptor!
- public weak var file: FileDescriptor! { return containingType.file }
- public var name: String { return proto.name }
- /// 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.
- public var isSynthetic: Bool {
- return fields.count == 1 && fields.first!.proto3Optional
- }
- public private(set) lazy var fields: [FieldDescriptor] = {
- let myIndex = Int32(self.index)
- return self.containingType.fields.filter { $0.oneofIndex == myIndex }
- }()
- fileprivate init(proto: Google_Protobuf_OneofDescriptorProto,
- index: Int,
- registry: Registry) {
- self.proto = proto
- self.index = index
- }
- fileprivate func bind(registry: Registry, containingType: Descriptor) {
- self.containingType = containingType
- }
- }
- public final class FieldDescriptor {
- public let proto: Google_Protobuf_FieldDescriptorProto
- let index: Int
- public private(set) weak var file: FileDescriptor!
- /// The Descriptor of the message which this is a field of. For extensions,
- /// this is the extended type.
- public private(set) weak var containingType: Descriptor!
- public var name: String { return proto.name }
- public var number: Int32 { return proto.number }
- public var label: Google_Protobuf_FieldDescriptorProto.Label { return proto.label }
- public var type: Google_Protobuf_FieldDescriptorProto.TypeEnum { return proto.type }
- var proto3Optional: Bool { return proto.proto3Optional }
- /// Returns true if this field was syntactically written with "optional" in the
- /// .proto file. Excludes singular proto3 fields that do not have a label.
- public var hasOptionalKeyword: Bool {
- return proto3Optional ||
- (file.syntax == .proto2 && label == .optional && oneofIndex == nil)
- }
- /// 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 {
- guard label != .repeated else { return false }
- switch type {
- case .group, .message:
- // Groups/messages always get field presence.
- return true
- default:
- return file.syntax == .proto2 || oneofIndex != nil
- }
- }
- public var fullName: String {
- // Since the fullName isn't needed on Fields that often, compute it on demand.
- let prefix: String
- if isExtension {
- if let extensionScope = extensionScope {
- prefix = extensionScope.fullName
- } else {
- let package = file.package
- if package.isEmpty {
- prefix = ""
- } else {
- prefix = ".\(package)"
- }
- }
- } else {
- prefix = containingType.fullName
- }
- return "\(prefix).\(proto.name)"
- }
- public var jsonName: String? {
- guard proto.hasJsonName else { return nil }
- return proto.jsonName
- }
- /// The default value (string) set in the proto file.
- public var explicitDefaultValue: String? {
- if !proto.hasDefaultValue {
- return nil
- }
- return proto.defaultValue
- }
- /// True if this field is a map.
- public var isMap: Bool {
- // Maps are releated messages.
- if label != .repeated || type != .message {
- return false
- }
- return messageType.isMapEntry
- }
- /// If this is an extension field.
- public let isExtension: Bool
- /// 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 private(set) weak var extensionScope: Descriptor?
- /// The index in a oneof this field is in.
- public let oneofIndex: Int32?
- /// The oneof this field is a member of.
- public var oneof: OneofDescriptor? {
- guard let oneofIndex = oneofIndex else { return nil }
- assert(!isExtension)
- return containingType!.oneofs[Int(oneofIndex)]
- }
- /// The non synthetic oneof this field is a member of.
- public var realOneof: OneofDescriptor? {
- guard let oneof = oneof, !oneof.isSynthetic else { return nil }
- return oneof
- }
- /// When this is a message field, the message's desciptor.
- public private(set) weak var messageType: Descriptor!
- /// When this is a enum field, the enum's desciptor.
- public private(set) weak var enumType: EnumDescriptor!
- /// Should this field be packed format.
- public var isPacked: Bool {
- // NOTE: As of May 2017, the proto3 spec says:
- //
- // https://developers.google.com/protocol-buffers/docs/proto3#specifying-field-rules -
- // "In proto3, repeated fields of scalar numeric types use packed encoding by default."
- //
- // But this does not result in the field option for packed being set by protoc. Instead
- // there is some interesting logic in the C++ desciptor classes that causes the field
- // to be packed, but does leave the door open for it not to be backed. So the logic
- // here and in the helpers this cases duplicates that logic.
- // This logic comes from the C++ FieldDescriptor::is_packed() impl.
- guard isPackable else { return false }
- if file.syntax == .proto2 {
- return proto.hasOptions && proto.options.packed
- } else {
- return !proto.hasOptions || !proto.options.hasPacked || proto.options.packed
- }
- }
- public var options: Google_Protobuf_FieldOptions { return proto.options }
- fileprivate init(proto: Google_Protobuf_FieldDescriptorProto,
- index: Int,
- registry: Registry,
- isExtension: Bool = false) {
- self.proto = proto
- self.index = index
- self.isExtension = isExtension
- if proto.hasOneofIndex {
- assert(!isExtension)
- oneofIndex = proto.oneofIndex
- } else {
- oneofIndex = nil
- // 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.
- assert(!proto.proto3Optional || isExtension)
- }
- }
- fileprivate func bind(file: FileDescriptor, registry: Registry, containingType: Descriptor?) {
- self.file = file
- assert(isExtension == !proto.extendee.isEmpty)
- if isExtension {
- extensionScope = containingType
- self.containingType = registry.descriptor(name: proto.extendee)
- } else {
- self.containingType = containingType
- }
- switch type {
- case .group, .message:
- messageType = registry.descriptor(name: proto.typeName)
- case .enum:
- enumType = registry.enumDescriptor(name: proto.typeName)
- default:
- break
- }
- }
- }
- public final class ServiceDescriptor {
- public let proto: Google_Protobuf_ServiceDescriptorProto
- let index: Int
- public let fullName: String
- public var name: String { return proto.name }
- public private(set) weak var file: FileDescriptor!
- public let methods: [MethodDescriptor]
- fileprivate init(proto: Google_Protobuf_ServiceDescriptorProto,
- index: Int,
- registry: Registry,
- fullNamePrefix prefix: String) {
- self.proto = proto
- self.index = index
- let fullName = "\(prefix).\(proto.name)"
- self.fullName = fullName
- self.methods = proto.method.enumeratedMap {
- return MethodDescriptor(proto: $1, index: $0, registry: registry)
- }
- // Done initializing, register ourselves.
- registry.register(service: self)
- }
- fileprivate func bind(file: FileDescriptor, registry: Registry) {
- self.file = file
- methods.forEach { $0.bind(service: self, registry: registry) }
- }
- }
- public final class MethodDescriptor {
- public let proto: Google_Protobuf_MethodDescriptorProto
- let index: Int
- public private(set) weak var service: ServiceDescriptor!
- public weak var file: FileDescriptor! { return service.file }
- public var name: String { return proto.name }
- public private(set) var inputType: Descriptor!
- 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
- fileprivate init(proto: Google_Protobuf_MethodDescriptorProto,
- index: Int,
- registry: Registry) {
- self.proto = proto
- self.index = index
- self.clientStreaming = proto.clientStreaming
- self.serverStreaming = proto.serverStreaming
- }
- fileprivate func bind(service: ServiceDescriptor, registry: Registry) {
- self.service = service
- inputType = registry.descriptor(name: proto.inputType)
- outputType = registry.descriptor(name: proto.outputType)
- }
- }
- /// Helper used under the hood to build the mapping tables and look things up.
- fileprivate final class Registry {
- private var fileMap = [String:FileDescriptor]()
- 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
- }
- // These are forced unwraps as the FileDescriptorSet should always be valid from protoc.
- func fileDescriptor(name: String) -> FileDescriptor {
- return fileMap[name]!
- }
- func descriptor(name: String) -> Descriptor {
- return messageMap[name]!
- }
- func enumDescriptor(name: String) -> EnumDescriptor {
- return enumMap[name]!
- }
- func serviceDescriptor(name: String) -> ServiceDescriptor {
- return serviceMap[name]!
- }
- func fileDescriptor(named name: String) -> FileDescriptor? {
- return fileMap[name]
- }
- }
|