FeatureResolver.swift 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Sources/SwiftProtobufPluginLibrary/FeatureResolve.swift - Feature helpers
  2. //
  3. // Copyright (c) 2014 - 2024 Apple Inc. and the project authors
  4. // Licensed under Apache License v2.0 with Runtime Library Exception
  5. //
  6. // See LICENSE.txt for license information:
  7. // https://github.com/apple/swift-protobuf/blob/main/LICENSE.txt
  8. //
  9. // -----------------------------------------------------------------------------
  10. import SwiftProtobuf
  11. protocol ProvidesFeatureSets {
  12. var features: Google_Protobuf_FeatureSet { get }
  13. var hasFeatures: Bool { get }
  14. }
  15. // Skip `Google_Protobuf_FileOptions`, special case of `resolve`.
  16. extension Google_Protobuf_MessageOptions: ProvidesFeatureSets {}
  17. extension Google_Protobuf_EnumOptions: ProvidesFeatureSets {}
  18. // Skip `Google_Protobuf_FieldOptions`, Field is special case of `resolve`.
  19. extension Google_Protobuf_OneofOptions: ProvidesFeatureSets {}
  20. extension Google_Protobuf_ExtensionRangeOptions: ProvidesFeatureSets {}
  21. extension Google_Protobuf_EnumValueOptions: ProvidesFeatureSets {}
  22. extension Google_Protobuf_ServiceOptions: ProvidesFeatureSets {}
  23. extension Google_Protobuf_MethodOptions: ProvidesFeatureSets {}
  24. /// Encapsulates the process of Feature resolution, sorta like the upstream
  25. /// `feature_resolver.cpp`.
  26. package class FeatureResolver {
  27. package enum Error: Swift.Error, Equatable, CustomStringConvertible {
  28. case unsupported(
  29. edition: Google_Protobuf_Edition,
  30. supported: ClosedRange<Google_Protobuf_Edition>
  31. )
  32. case noDefault(edition: Google_Protobuf_Edition)
  33. case invalidExtension(type: String)
  34. package var description: String {
  35. switch self {
  36. case .unsupported(let edition, let supported):
  37. return "Edition \(edition) is not in the supported range (\(supported))"
  38. case .noDefault(let edition):
  39. return "No default value found for edition \(edition)"
  40. case .invalidExtension(let type):
  41. return "Passed an extension that wasn't to google.protobuf.FeatureSet: \(type)"
  42. }
  43. }
  44. }
  45. /// The requested Edition.
  46. package let edition: Google_Protobuf_Edition
  47. /// The detaults to use for this edition.
  48. package let defaultFeatureSet: Google_Protobuf_FeatureSet
  49. private let extensionMap: (any ExtensionMap)?
  50. /// Construct a resolver for a given edition with the correct defaults.
  51. ///
  52. /// - Parameters:
  53. /// - edition: The edition of defaults desired.
  54. /// - defaults: A `Google_Protobuf_FeatureSetDefaults` created by protoc
  55. /// from one or more proto files that define `Google_Protobuf_FeatureSet`
  56. /// and any extensions.
  57. /// - extensions: A list of Protobuf Extension extensions to
  58. /// `google.protobuf.FeatureSet` that define custom features. If used, the
  59. /// `defaults` should have been parsed with the extensions being
  60. /// supported.
  61. /// - Returns: A configured resolver for the given edition/defaults.
  62. /// - Throws: `FeatureResolver.Error` if there edition requested can't be
  63. /// supported by the given defaults.
  64. package init(
  65. edition: Google_Protobuf_Edition,
  66. featureSetDefaults defaults: Google_Protobuf_FeatureSetDefaults,
  67. featureExtensions extensions: [any AnyMessageExtension] = []
  68. ) throws {
  69. guard edition >= defaults.minimumEdition && edition <= defaults.maximumEdition else {
  70. throw Error.unsupported(
  71. edition: edition,
  72. supported: defaults.minimumEdition...defaults.maximumEdition
  73. )
  74. }
  75. // When protoc generates defaults, they are ordered, so find the last one.
  76. var found: Google_Protobuf_FeatureSetDefaults.FeatureSetEditionDefault?
  77. for d in defaults.defaults {
  78. guard d.edition <= edition else { break }
  79. found = d
  80. }
  81. guard let found = found else {
  82. throw Error.noDefault(edition: edition)
  83. }
  84. self.edition = edition
  85. if extensions.isEmpty {
  86. extensionMap = nil
  87. } else {
  88. for e in extensions {
  89. if e.messageType != Google_Protobuf_FeatureSet.self {
  90. throw Error.invalidExtension(type: e.messageType.protoMessageName)
  91. }
  92. }
  93. var simpleMap = SimpleExtensionMap()
  94. simpleMap.insert(contentsOf: extensions)
  95. extensionMap = simpleMap
  96. }
  97. var features = found.fixedFeatures
  98. // Don't yet have a message level merge, so bounce through serialization.
  99. let bytes: [UInt8] = try! found.overridableFeatures.serializedBytes()
  100. try! features.merge(serializedBytes: bytes, extensions: extensionMap)
  101. defaultFeatureSet = features
  102. }
  103. /// Resolve the Features for a File.
  104. func resolve(_ options: Google_Protobuf_FileOptions) -> Google_Protobuf_FeatureSet {
  105. /// There is no parent, so the default options are used.
  106. resolve(
  107. features: options.hasFeatures ? options.features : nil,
  108. resolvedParent: defaultFeatureSet
  109. )
  110. }
  111. /// Resolve the Features for a Field.
  112. ///
  113. /// This needs to the full FieldDescriptorProto incase it has to do fallback
  114. /// inference.
  115. package func resolve(
  116. _ proto: Google_Protobuf_FieldDescriptorProto,
  117. resolvedParent: Google_Protobuf_FeatureSet
  118. ) -> Google_Protobuf_FeatureSet {
  119. if edition >= .edition2023 {
  120. return resolve(
  121. features: proto.options.hasFeatures ? proto.options.features : nil,
  122. resolvedParent: resolvedParent
  123. )
  124. }
  125. // For `.proto2` and `.proto3`, some of the field behaviors have to be
  126. // figured out as they can't be captured in the defaults and inherrited.
  127. // See `InferLegacyProtoFeatures` in the C++ descriptor.cc implementation
  128. // for this logic.
  129. var features = Google_Protobuf_FeatureSet()
  130. if proto.label == .required {
  131. features.fieldPresence = .legacyRequired
  132. }
  133. if proto.type == .group {
  134. features.messageEncoding = .delimited
  135. }
  136. let options = proto.options
  137. if options.packed {
  138. features.repeatedFieldEncoding = .packed
  139. }
  140. if edition == .proto3 && options.hasPacked && !options.packed {
  141. features.repeatedFieldEncoding = .expanded
  142. }
  143. // Now now merge the rest of the inherrited info from the defaults.
  144. return resolve(features: features, resolvedParent: defaultFeatureSet)
  145. }
  146. /// Resolve the Features for a given descriptor's options, the resolvedParent
  147. /// values used to inherrit from.
  148. func resolve<T: ProvidesFeatureSets>(
  149. _ options: T,
  150. resolvedParent: Google_Protobuf_FeatureSet
  151. ) -> Google_Protobuf_FeatureSet {
  152. resolve(
  153. features: options.hasFeatures ? options.features : nil,
  154. resolvedParent: resolvedParent
  155. )
  156. }
  157. /// Helper to do the merging.
  158. package func resolve(
  159. features: Google_Protobuf_FeatureSet?,
  160. resolvedParent: Google_Protobuf_FeatureSet
  161. ) -> Google_Protobuf_FeatureSet {
  162. var result = resolvedParent
  163. if let features = features {
  164. // Don't yet have a message level merge, so bounce through serialization.
  165. let bytes: [UInt8] = try! features.serializedBytes()
  166. try! result.merge(serializedBytes: bytes, extensions: extensionMap)
  167. }
  168. return result
  169. }
  170. }