diff --git a/Nimble.xcodeproj/project.pbxproj b/Nimble.xcodeproj/project.pbxproj index dd5ec8095..8394174a0 100644 --- a/Nimble.xcodeproj/project.pbxproj +++ b/Nimble.xcodeproj/project.pbxproj @@ -143,6 +143,7 @@ 898F28B025D9F4C30052B8D0 /* AlwaysFailMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */; }; 899441EF2902EE4B00C1FAF9 /* AsyncAwaitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */; }; 899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */; }; + 8998C1082CBD9980009A3E1C /* BeLogical+Conformances.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8998C1072CBD9980009A3E1C /* BeLogical+Conformances.swift */; }; 89B8C60F2C6476A6001F12D3 /* Negation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B8C60E2C6476A6001F12D3 /* Negation.swift */; }; 89B8C6112C6478F2001F12D3 /* NegationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B8C6102C6478F2001F12D3 /* NegationTest.swift */; }; 89C297CC2A911CDA002A143F /* AsyncTimerSequenceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */; }; @@ -336,6 +337,7 @@ 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysFailMatcher.swift; sourceTree = ""; }; 899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitTest.swift; sourceTree = ""; }; 899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DSL+AsyncAwait.swift"; sourceTree = ""; }; + 8998C1072CBD9980009A3E1C /* BeLogical+Conformances.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BeLogical+Conformances.swift"; sourceTree = ""; }; 89B8C60E2C6476A6001F12D3 /* Negation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Negation.swift; sourceTree = ""; }; 89B8C6102C6478F2001F12D3 /* NegationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NegationTest.swift; sourceTree = ""; }; 89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncTimerSequenceTest.swift; sourceTree = ""; }; @@ -595,6 +597,7 @@ 1FD8CD151968AB07008ED995 /* BeLessThan.swift */, 1FD8CD161968AB07008ED995 /* BeLessThanOrEqual.swift */, 1FD8CD171968AB07008ED995 /* BeLogical.swift */, + 8998C1072CBD9980009A3E1C /* BeLogical+Conformances.swift */, 1FD8CD181968AB07008ED995 /* BeNil.swift */, 106112BC2251DFE7000A5848 /* BeResult.swift */, 1F91DD301C74BF61002C309F /* BeVoid.swift */, @@ -900,6 +903,7 @@ 7A0A26231E7F52360092A34E /* ToSucceed.swift in Sources */, 89F5E0862908E655001F9377 /* Polling+AsyncAwait.swift in Sources */, 897F84F42BA922B500BF354B /* NSLocking+Nimble.swift in Sources */, + 8998C1082CBD9980009A3E1C /* BeLogical+Conformances.swift in Sources */, 899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */, 1FD8CD491968AB07008ED995 /* BeGreaterThanOrEqualTo.swift in Sources */, 1FE661571E6574E30035F243 /* ExpectationMessage.swift in Sources */, diff --git a/Sources/Nimble/AsyncExpression.swift b/Sources/Nimble/AsyncExpression.swift index 22c366cf2..35915bd4e 100644 --- a/Sources/Nimble/AsyncExpression.swift +++ b/Sources/Nimble/AsyncExpression.swift @@ -9,9 +9,15 @@ private final class MemoizedClosure: Sendable { } private let lock = NSRecursiveLock() +#if swift(>=5.10) nonisolated(unsafe) private var _state = State.notStarted nonisolated(unsafe) private var _continuations = [CheckedContinuation]() nonisolated(unsafe) private var _task: Task? +#else + private var _state = State.notStarted + private var _continuations = [CheckedContinuation]() + private var _task: Task? +#endif let closure: @Sendable () async throws -> T @@ -25,9 +31,9 @@ private final class MemoizedClosure: Sendable { @Sendable func callAsFunction(_ withoutCaching: Bool) async throws -> T { if withoutCaching { - try await closure() + return try await closure() } else { - try await withCheckedThrowingContinuation { continuation in + return try await withCheckedThrowingContinuation { continuation in lock.withLock { switch _state { case .notStarted: diff --git a/Sources/Nimble/Expression.swift b/Sources/Nimble/Expression.swift index 8b91d4390..af388d7a9 100644 --- a/Sources/Nimble/Expression.swift +++ b/Sources/Nimble/Expression.swift @@ -2,7 +2,11 @@ import Foundation private final class MemoizedValue: Sendable { private let lock = NSRecursiveLock() +#if swift(>=5.10) nonisolated(unsafe) private var cache: T? = nil +#else + private var cache: T? = nil +#endif private let closure: @Sendable () throws -> T init(_ closure: @escaping @Sendable () throws -> T) { diff --git a/Sources/Nimble/Matchers/BeLogical+Conformances.swift b/Sources/Nimble/Matchers/BeLogical+Conformances.swift new file mode 100644 index 000000000..847ee4c43 --- /dev/null +++ b/Sources/Nimble/Matchers/BeLogical+Conformances.swift @@ -0,0 +1,150 @@ +import Foundation + +#if swift(>=6) +extension Int8: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int8Value + } +} + +extension UInt8: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint8Value + } +} + +extension Int16: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int16Value + } +} + +extension UInt16: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint16Value + } +} + +extension Int32: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int32Value + } +} + +extension UInt32: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint32Value + } +} + +extension Int64: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int64Value + } +} + +extension UInt64: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint64Value + } +} + +extension Float: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).floatValue + } +} + +extension Double: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).doubleValue + } +} + +extension Int: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).intValue + } +} + +extension UInt: @retroactive ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uintValue + } +} + +#else + +extension Int8: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int8Value + } +} + +extension UInt8: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint8Value + } +} + +extension Int16: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int16Value + } +} + +extension UInt16: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint16Value + } +} + +extension Int32: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int32Value + } +} + +extension UInt32: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint32Value + } +} + +extension Int64: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).int64Value + } +} + +extension UInt64: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uint64Value + } +} + +extension Float: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).floatValue + } +} + +extension Double: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).doubleValue + } +} + +extension Int: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).intValue + } +} + +extension UInt: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = NSNumber(value: value).uintValue + } +} + +#endif diff --git a/Sources/Nimble/Matchers/BeLogical.swift b/Sources/Nimble/Matchers/BeLogical.swift index d24444d7e..dd6d7fa37 100644 --- a/Sources/Nimble/Matchers/BeLogical.swift +++ b/Sources/Nimble/Matchers/BeLogical.swift @@ -1,77 +1,5 @@ import Foundation -extension Int8: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).int8Value - } -} - -extension UInt8: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).uint8Value - } -} - -extension Int16: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).int16Value - } -} - -extension UInt16: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).uint16Value - } -} - -extension Int32: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).int32Value - } -} - -extension UInt32: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).uint32Value - } -} - -extension Int64: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).int64Value - } -} - -extension UInt64: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).uint64Value - } -} - -extension Float: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).floatValue - } -} - -extension Double: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).doubleValue - } -} - -extension Int: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).intValue - } -} - -extension UInt: @retroactive ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { - self = NSNumber(value: value).uintValue - } -} - internal func rename(_ matcher: Matcher, failureMessage message: ExpectationMessage) -> Matcher { return Matcher { actualExpression in let result = try matcher.satisfies(actualExpression) diff --git a/Sources/Nimble/Matchers/PostNotification.swift b/Sources/Nimble/Matchers/PostNotification.swift index 4bbeeff75..caf55e7d8 100644 --- a/Sources/Nimble/Matchers/PostNotification.swift +++ b/Sources/Nimble/Matchers/PostNotification.swift @@ -4,11 +4,17 @@ import Foundation final class NotificationCollector: Sendable { +#if swift(>=5.10) nonisolated(unsafe) private(set) var observedNotifications: [Notification] nonisolated(unsafe) private(set) var observedNotificationDescriptions: [String] + nonisolated(unsafe) private var tokens: [NSObjectProtocol] +#else + private(set) var observedNotifications: [Notification] + private(set) var observedNotificationDescriptions: [String] + private var tokens: [NSObjectProtocol] +#endif private let notificationCenter: NotificationCenter private let names: Set - nonisolated(unsafe) private var tokens: [NSObjectProtocol] private let lock = NSRecursiveLock() required init(notificationCenter: NotificationCenter, names: Set = []) { @@ -55,13 +61,22 @@ final class NotificationCollector: Sendable { } #if !os(Windows) +#if swift(>=5.10) nonisolated(unsafe) private let mainThread = pthread_self() #else +private let mainThread = pthread_self() +#endif +#else private let mainThread = Thread.mainThread #endif private final class OnlyOnceChecker: Sendable { +#if swift(>=5.10) nonisolated(unsafe) var hasRun = false +#else + var hasRun = false +#endif + let lock = NSRecursiveLock() func runOnlyOnce(_ closure: @Sendable () throws -> Void) rethrows { diff --git a/Sources/Nimble/Polling.swift b/Sources/Nimble/Polling.swift index f54b4caf1..c9591f357 100644 --- a/Sources/Nimble/Polling.swift +++ b/Sources/Nimble/Polling.swift @@ -37,8 +37,13 @@ public struct AsyncDefaults { public struct PollingDefaults: @unchecked Sendable { private static let lock = NSRecursiveLock() +#if swift(>=5.10) nonisolated(unsafe) private static var _timeout: NimbleTimeInterval = .seconds(1) nonisolated(unsafe) private static var _pollInterval: NimbleTimeInterval = .milliseconds(10) +#else + private static var _timeout: NimbleTimeInterval = .seconds(1) + private static var _pollInterval: NimbleTimeInterval = .milliseconds(10) +#endif public static var timeout: NimbleTimeInterval { get { diff --git a/Sources/Nimble/Utils/LockedContainer.swift b/Sources/Nimble/Utils/LockedContainer.swift index fcc8455ba..c9e6a1084 100644 --- a/Sources/Nimble/Utils/LockedContainer.swift +++ b/Sources/Nimble/Utils/LockedContainer.swift @@ -30,3 +30,14 @@ final class LockedContainer: @unchecked Sendable { _value = newValue } } + +extension NSLocking { + func withLock(_ body: () throws -> R) rethrows -> R { + self.lock() + defer { + self.unlock() + } + + return try body() + } +} diff --git a/Sources/Nimble/Utils/PollAwait.swift b/Sources/Nimble/Utils/PollAwait.swift index 5f5913460..193594256 100644 --- a/Sources/Nimble/Utils/PollAwait.swift +++ b/Sources/Nimble/Utils/PollAwait.swift @@ -105,7 +105,11 @@ internal enum PollStatus { /// Holds the resulting value from an asynchronous expectation. /// This class is thread-safe at receiving a "response" to this promise. internal final class AwaitPromise: Sendable { +#if swift(>=5.10) nonisolated(unsafe) private(set) internal var asyncResult: PollResult = .incomplete +#else + private(set) internal var asyncResult: PollResult = .incomplete +#endif private let signal: DispatchSemaphore init() { @@ -320,13 +324,13 @@ internal class Awaiter { ) -> AwaitPromiseBuilder { let promise = AwaitPromise() let timeoutSource = createTimerSource(timeoutQueue) - nonisolated(unsafe) var completionCount = 0 + let completionCount = LockedContainer(0) let lock = NSRecursiveLock() let trigger = PollAwaitTrigger(timeoutSource: timeoutSource, actionSource: nil) { try closure { result in lock.withLock { - completionCount += 1 - if completionCount < 2 { + completionCount.operate { $0 + 1 } + if completionCount.value < 2 { @Sendable func completeBlock() { if promise.resolveResult(.completed(result)) { #if canImport(CoreFoundation)