From ea93a59345bc309de3378c90c29e468beda1d4cf Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Thu, 23 May 2024 13:33:20 +0200 Subject: [PATCH] Enable strict concurrency checking --- Package@swift-5.9.swift | 34 ++++ .../BSON/Codable/Decoding/BSONDecoder.swift | 2 +- .../Decoding/BSONDecoderSettings.swift | 156 ++++++++++-------- Sources/BSON/Document/Document+Helpers.swift | 2 +- Sources/BSON/Document/Document.swift | 3 +- 5 files changed, 121 insertions(+), 76 deletions(-) create mode 100644 Package@swift-5.9.swift diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift new file mode 100644 index 0000000..a2d9337 --- /dev/null +++ b/Package@swift-5.9.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.9 + +import PackageDescription + +let package = Package( + name: "BSON", + platforms: [ + .macOS(.v10_15), + .iOS(.v13) + ], + products: [ + .library( + name: "BSON", + targets: ["BSON"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.46.0") + ], + targets: [ + .target( + name: "BSON", + dependencies: [ + .product(name: "NIOCore", package: "swift-nio"), + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete"), + ] + ), + .testTarget( + name: "BSONTests", + dependencies: ["BSON"]) + ], + swiftLanguageVersions: [.v4_2] +) diff --git a/Sources/BSON/Codable/Decoding/BSONDecoder.swift b/Sources/BSON/Codable/Decoding/BSONDecoder.swift index 3304d05..921577a 100644 --- a/Sources/BSON/Codable/Decoding/BSONDecoder.swift +++ b/Sources/BSON/Codable/Decoding/BSONDecoder.swift @@ -1,7 +1,7 @@ import Foundation /// A helper that is able to decode BSON data types into a `Decodable` type. -public struct BSONDecoder { +public struct BSONDecoder: @unchecked Sendable { /// The configuration used for decoding public var settings: BSONDecoderSettings diff --git a/Sources/BSON/Codable/Decoding/BSONDecoderSettings.swift b/Sources/BSON/Codable/Decoding/BSONDecoderSettings.swift index 2c4753a..f892f86 100644 --- a/Sources/BSON/Codable/Decoding/BSONDecoderSettings.swift +++ b/Sources/BSON/Codable/Decoding/BSONDecoderSettings.swift @@ -1,90 +1,102 @@ +import NIOConcurrencyHelpers + /// A configuration structs that contains all strategies for (lossy) decoding values -public struct BSONDecoderSettings { +public struct BSONDecoderSettings: Sendable { /// Decodes values only if they are exactly matching the expectation. /// /// For non-BSON types, the following mapping applies: /// /// - Float: Decode from Double /// - Non-native Integer types: .anyInteger - public static let strict: BSONDecoderSettings = BSONDecoderSettings( - fastPath: false, - decodeNullAsNil: false, - filterDollarPrefix: false, - stringDecodingStrategy: .string, - decodeObjectIdFromString: false, - timestampToDateDecodingStrategy: .never, - floatDecodingStrategy: .double, - doubleDecodingStrategy: .double, - int8DecodingStrategy: .anyInteger, - int16DecodingStrategy: .anyInteger, - int32DecodingStrategy: .int32, - int64DecodingStrategy: .int64, - intDecodingStrategy: .anyInteger, - uint8DecodingStrategy: .anyInteger, - uint16DecodingStrategy: .anyInteger, - uint32DecodingStrategy: .anyInteger, - uint64DecodingStrategy: .anyInteger, - uintDecodingStrategy: .anyInteger - ) - + public static var strict: BSONDecoderSettings { + BSONDecoderSettings( + fastPath: false, + decodeNullAsNil: false, + filterDollarPrefix: false, + stringDecodingStrategy: .string, + decodeObjectIdFromString: false, + timestampToDateDecodingStrategy: .never, + floatDecodingStrategy: .double, + doubleDecodingStrategy: .double, + int8DecodingStrategy: .anyInteger, + int16DecodingStrategy: .anyInteger, + int32DecodingStrategy: .int32, + int64DecodingStrategy: .int64, + intDecodingStrategy: .anyInteger, + uint8DecodingStrategy: .anyInteger, + uint16DecodingStrategy: .anyInteger, + uint32DecodingStrategy: .anyInteger, + uint64DecodingStrategy: .anyInteger, + uintDecodingStrategy: .anyInteger + ) + } + /// Uses ``FastBSONDecoder`` - public static let fastPath: BSONDecoderSettings = BSONDecoderSettings( - fastPath: true, - decodeNullAsNil: false, - filterDollarPrefix: false, - stringDecodingStrategy: .string, - decodeObjectIdFromString: false, - timestampToDateDecodingStrategy: .never, - floatDecodingStrategy: .double, - doubleDecodingStrategy: .double, - int8DecodingStrategy: .anyInteger, - int16DecodingStrategy: .anyInteger, - int32DecodingStrategy: .int32, - int64DecodingStrategy: .int64, - intDecodingStrategy: .anyInteger, - uint8DecodingStrategy: .anyInteger, - uint16DecodingStrategy: .anyInteger, - uint32DecodingStrategy: .anyInteger, - uint64DecodingStrategy: .anyInteger, - uintDecodingStrategy: .anyInteger - ) + public static var fastPath: BSONDecoderSettings { + BSONDecoderSettings( + fastPath: true, + decodeNullAsNil: false, + filterDollarPrefix: false, + stringDecodingStrategy: .string, + decodeObjectIdFromString: false, + timestampToDateDecodingStrategy: .never, + floatDecodingStrategy: .double, + doubleDecodingStrategy: .double, + int8DecodingStrategy: .anyInteger, + int16DecodingStrategy: .anyInteger, + int32DecodingStrategy: .int32, + int64DecodingStrategy: .int64, + intDecodingStrategy: .anyInteger, + uint8DecodingStrategy: .anyInteger, + uint16DecodingStrategy: .anyInteger, + uint32DecodingStrategy: .anyInteger, + uint64DecodingStrategy: .anyInteger, + uintDecodingStrategy: .anyInteger + ) + } + + private static let _default = NIOLockedValueBox(.adaptive) + public static var `default`: BSONDecoderSettings { + get { _default.withLockedValue { $0 } } + set { _default.withLockedValue { $0 = newValue } } + } - public static var `default`: BSONDecoderSettings = .adaptive - /// Tries to decode values, even if the types do not match. Some precision loss is possible. - public static let adaptive: BSONDecoderSettings = BSONDecoderSettings( - fastPath: false, - decodeNullAsNil: true, - filterDollarPrefix: false, - stringDecodingStrategy: .adaptive, - decodeObjectIdFromString: true, - timestampToDateDecodingStrategy: .relativeToUnixEpoch, - floatDecodingStrategy: .adaptive, - doubleDecodingStrategy: .adaptive, - int8DecodingStrategy: .adaptive, - int16DecodingStrategy: .adaptive, - int32DecodingStrategy: .adaptive, - int64DecodingStrategy: .adaptive, - intDecodingStrategy: .adaptive, - uint8DecodingStrategy: .adaptive, - uint16DecodingStrategy: .adaptive, - uint32DecodingStrategy: .adaptive, - uint64DecodingStrategy: .adaptive, - uintDecodingStrategy: .adaptive - ) - + public static var adaptive: BSONDecoderSettings { + BSONDecoderSettings( + fastPath: false, + decodeNullAsNil: true, + filterDollarPrefix: false, + stringDecodingStrategy: .adaptive, + decodeObjectIdFromString: true, + timestampToDateDecodingStrategy: .relativeToUnixEpoch, + floatDecodingStrategy: .adaptive, + doubleDecodingStrategy: .adaptive, + int8DecodingStrategy: .adaptive, + int16DecodingStrategy: .adaptive, + int32DecodingStrategy: .adaptive, + int64DecodingStrategy: .adaptive, + intDecodingStrategy: .adaptive, + uint8DecodingStrategy: .adaptive, + uint16DecodingStrategy: .adaptive, + uint32DecodingStrategy: .adaptive, + uint64DecodingStrategy: .adaptive, + uintDecodingStrategy: .adaptive + ) + } + /// A strategy used to decode `P` from a BSON `Primitive?` value /// /// If the key (`String`) is nil the value was not associated with a Dictionary Document. /// /// If the value (`Primitive`) is nil, the value was not found at all but can be overwritten with a default - public typealias DecodingStrategy

= (String?, Primitive?) throws -> P? - + public typealias DecodingStrategy

= @Sendable (String?, Primitive?) throws -> P? + /// A strategy used to decode float values /// Floats are not a native BSON type /// /// WARNING: This API may have cases added to it, do *not* manually switch over them - public enum FloatDecodingStrategy { + public enum FloatDecodingStrategy: Sendable { case string case double case adaptive @@ -95,7 +107,7 @@ public struct BSONDecoderSettings { /// Floats are not a native BSON type /// /// WARNING: This API may have cases added to it, do *not* manually switch over them - public enum IntegerDecodingStrategy { + public enum IntegerDecodingStrategy: Sendable { /// Decodes this integer type only from Strings case string @@ -122,7 +134,7 @@ public struct BSONDecoderSettings { /// Although Doubles are a native BSON type, lossy conversion may be favourable in certain circumstances /// /// WARNING: This API may have cases added to it, do *not* manually switch over them - public enum DoubleDecodingStrategy { + public enum DoubleDecodingStrategy: Sendable { /// Decodes only the correct type. No lossy decoding. case double @@ -148,7 +160,7 @@ public struct BSONDecoderSettings { /// A strategy used to influence decoding Strings /// /// WARNING: This API may have cases added to it, do *not* manually switch over them - public enum StringDecodingStrategy { + public enum StringDecodingStrategy: Sendable { /// Decode only strings themselves case string @@ -172,7 +184,7 @@ public struct BSONDecoderSettings { case custom(DecodingStrategy) } - public enum TimestampToDateDecodingStrategy { + public enum TimestampToDateDecodingStrategy: Sendable { /// Do not convert, and throw an error case never diff --git a/Sources/BSON/Document/Document+Helpers.swift b/Sources/BSON/Document/Document+Helpers.swift index ec991f8..705edec 100644 --- a/Sources/BSON/Document/Document+Helpers.swift +++ b/Sources/BSON/Document/Document+Helpers.swift @@ -63,7 +63,7 @@ public struct BSONValueNotFound: Error, CustomStringConvertible { } // TODO: Include more context. These errors are thrown in BSONDecoder but provide no information at all about the KeyPath, and are therefore useless. -struct BSONTypeConversionError: Error { +struct BSONTypeConversionError: Error, @unchecked Sendable { let from: A let to: Any.Type } diff --git a/Sources/BSON/Document/Document.swift b/Sources/BSON/Document/Document.swift index 7f40eba..2b08966 100644 --- a/Sources/BSON/Document/Document.swift +++ b/Sources/BSON/Document/Document.swift @@ -1,8 +1,7 @@ import Foundation import NIOCore -// TODO: ByteBuffer is missing Sendable annotation, but is Sendable -public struct Document: Primitive, @unchecked Sendable { +public struct Document: Primitive, Sendable { static let allocator = ByteBufferAllocator() /// The internal storage engine that stores BSON in it's original binary form