FeatureResolver.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. class FeatureResolver {
  27. enum Error: Swift.Error, Equatable, CustomStringConvertible {
  28. case unsupported(edition: Google_Protobuf_Edition,
  29. supported: ClosedRange<Google_Protobuf_Edition>)
  30. case noDefault(edition: Google_Protobuf_Edition)
  31. case invalidExtension(type: String)
  32. var description: String {
  33. switch self {
  34. case .unsupported(let edition, let supported):
  35. return "Edition \(edition) is not in the supported range (\(supported))"
  36. case .noDefault(let edition):
  37. return "No default value found for edition \(edition)"
  38. case .invalidExtension(let type):
  39. return "Passed an extension that wasn't to google.protobuf.FeatureSet: \(type)"
  40. }
  41. }
  42. }
  43. /// The requested Edition.
  44. let edition: Google_Protobuf_Edition
  45. /// The detaults to use for this edition.
  46. let defaultFeatureSet: Google_Protobuf_FeatureSet
  47. private let extensionMap: (any ExtensionMap)?
  48. /// Construct a resolver for a given edition with the correct defaults.
  49. ///
  50. /// - Parameters:
  51. /// - edition: The edition of defaults desired.
  52. /// - defaults: A `Google_Protobuf_FeatureSetDefaults` created by protoc
  53. /// from one or more proto files that define `Google_Protobuf_FeatureSet`
  54. /// and any extensions.
  55. /// - extensions: A list of Protobuf Extension extensions to
  56. /// `google.protobuf.FeatureSet` that define custom features. If used, the
  57. /// `defaults` should have been parsed with the extensions being
  58. /// supported.
  59. /// - Returns: A configured resolver for the given edition/defaults.
  60. /// - Throws: `FeatureResolver.Error` if there edition requested can't be
  61. /// supported by the given defaults.
  62. init(
  63. edition: Google_Protobuf_Edition,
  64. featureSetDefaults defaults: Google_Protobuf_FeatureSetDefaults,
  65. featureExtensions extensions: [any AnyMessageExtension] = []
  66. ) throws {
  67. guard edition >= defaults.minimumEdition &&
  68. edition <= defaults.maximumEdition else {
  69. throw Error.unsupported(edition: edition,
  70. supported: defaults.minimumEdition...defaults.maximumEdition)
  71. }
  72. // When protoc generates defaults, they are ordered, so find the last one.
  73. var found: Google_Protobuf_FeatureSetDefaults.FeatureSetEditionDefault?
  74. for d in defaults.defaults {
  75. guard d.edition <= edition else { break }
  76. found = d
  77. }
  78. guard let found = found else {
  79. throw Error.noDefault(edition: edition)
  80. }
  81. self.edition = edition
  82. if extensions.isEmpty {
  83. extensionMap = nil
  84. } else {
  85. for e in extensions {
  86. if e.messageType != Google_Protobuf_FeatureSet.self {
  87. throw Error.invalidExtension(type: e.messageType.protoMessageName)
  88. }
  89. }
  90. var simpleMap = SimpleExtensionMap()
  91. simpleMap.insert(contentsOf: extensions)
  92. extensionMap = simpleMap
  93. }
  94. var features = found.fixedFeatures
  95. // Don't yet have a message level merge, so bounce through serialization.
  96. let bytes: [UInt8] = try! found.overridableFeatures.serializedBytes()
  97. try! features.merge(serializedBytes: bytes, extensions: extensionMap)
  98. defaultFeatureSet = features
  99. }
  100. /// Resolve the Features for a File.
  101. func resolve(_ options: Google_Protobuf_FileOptions) -> Google_Protobuf_FeatureSet {
  102. /// There is no parent, so the default options are used.
  103. return resolve(features: options.hasFeatures ? options.features : nil,
  104. resolvedParent: defaultFeatureSet)
  105. }
  106. /// Resolve the Features for a Field.
  107. ///
  108. /// This needs to the full FieldDescriptorProto incase it has to do fallback
  109. /// inference.
  110. func resolve(_ proto: Google_Protobuf_FieldDescriptorProto,
  111. resolvedParent: Google_Protobuf_FeatureSet
  112. ) -> Google_Protobuf_FeatureSet {
  113. if edition >= .edition2023 {
  114. return resolve(features: proto.options.hasFeatures ? proto.options.features : nil,
  115. resolvedParent: resolvedParent)
  116. }
  117. // For `.proto2` and `.proto3`, some of the field behaviors have to be
  118. // figured out as they can't be captured in the defaults and inherrited.
  119. // See `InferLegacyProtoFeatures` in the C++ descriptor.cc implementation
  120. // for this logic.
  121. var features = Google_Protobuf_FeatureSet()
  122. if proto.label == .required {
  123. features.fieldPresence = .legacyRequired
  124. }
  125. if proto.type == .group {
  126. features.messageEncoding = .delimited
  127. }
  128. let options = proto.options
  129. if options.packed {
  130. features.repeatedFieldEncoding = .packed
  131. }
  132. if edition == .proto3 && options.hasPacked && !options.packed {
  133. features.repeatedFieldEncoding = .expanded
  134. }
  135. // Now now merge the rest of the inherrited info from the defaults.
  136. return resolve(features: features, resolvedParent: defaultFeatureSet)
  137. }
  138. /// Resolve the Features for a given descriptor's options, the resolvedParent
  139. /// values used to inherrit from.
  140. func resolve<T: ProvidesFeatureSets>(
  141. _ options: T,
  142. resolvedParent: Google_Protobuf_FeatureSet
  143. ) -> Google_Protobuf_FeatureSet {
  144. return resolve(features: options.hasFeatures ? options.features : nil,
  145. resolvedParent: resolvedParent)
  146. }
  147. /// Helper to do the merging.
  148. func resolve(features: Google_Protobuf_FeatureSet?,
  149. resolvedParent: Google_Protobuf_FeatureSet
  150. ) -> Google_Protobuf_FeatureSet {
  151. var result = resolvedParent
  152. if let features = features {
  153. // Don't yet have a message level merge, so bounce through serialization.
  154. let bytes: [UInt8] = try! features.serializedBytes()
  155. try! result.merge(serializedBytes: bytes, extensions: extensionMap)
  156. }
  157. return result
  158. }
  159. }