diff --git a/Sources/Nimble/AsyncExpression.swift b/Sources/Nimble/AsyncExpression.swift index 75829c9ee..631071a62 100644 --- a/Sources/Nimble/AsyncExpression.swift +++ b/Sources/Nimble/AsyncExpression.swift @@ -1,4 +1,4 @@ -private actor MemoizedClosure { +private actor MemoizedClosure { var closure: @Sendable () async throws -> T var cache: T? @@ -25,7 +25,7 @@ private actor MemoizedClosure { // Memoizes the given closure, only calling the passed // closure once; even if repeat calls to the returned closure -private func memoizedClosure(_ closure: @escaping @Sendable () async throws -> T) -> @Sendable (Bool) async throws -> T { +private func memoizedClosure(_ closure: @escaping @Sendable () async throws -> T) -> @Sendable (Bool) async throws -> T { let memoized = MemoizedClosure(closure) return { withoutCaching in try await memoized.call(withoutCaching) @@ -43,7 +43,7 @@ private func memoizedClosure(_ closure: @escaping @Sendable () async throws - /// /// This provides a common consumable API for matchers to utilize to allow /// Nimble to change internals to how the captured closure is managed. -public struct AsyncExpression: Sendable { +public struct AsyncExpression: Sendable { internal let _expression: @Sendable (Bool) async throws -> Value? internal let _withoutCaching: Bool public let location: SourceLocation diff --git a/Sources/Nimble/DSL+AsyncAwait.swift b/Sources/Nimble/DSL+AsyncAwait.swift index 26ec5b3e4..3149c3f84 100644 --- a/Sources/Nimble/DSL+AsyncAwait.swift +++ b/Sources/Nimble/DSL+AsyncAwait.swift @@ -3,7 +3,7 @@ import Dispatch #endif /// Make an ``AsyncExpectation`` on a given actual value. The value given is lazily evaluated. -public func expect(file: FileString = #file, line: UInt = #line, _ expression: @escaping @Sendable () async throws -> T?) -> AsyncExpectation { +public func expect(file: FileString = #file, line: UInt = #line, _ expression: @escaping @Sendable () async throws -> T?) -> AsyncExpectation { return AsyncExpectation( expression: AsyncExpression( expression: expression, @@ -12,7 +12,7 @@ public func expect(file: FileString = #file, line: UInt = #line, _ expression } /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked. -public func expect(file: FileString = #file, line: UInt = #line, _ expression: @Sendable () -> (@Sendable () async throws -> T)) -> AsyncExpectation { +public func expect(file: FileString = #file, line: UInt = #line, _ expression: @Sendable () -> (@Sendable () async throws -> T)) -> AsyncExpectation { return AsyncExpectation( expression: AsyncExpression( expression: expression(), @@ -21,7 +21,7 @@ public func expect(file: FileString = #file, line: UInt = #line, _ expression } /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked. -public func expect(file: FileString = #file, line: UInt = #line, _ expression: @Sendable () -> (@Sendable () async throws -> T?)) -> AsyncExpectation { +public func expect(file: FileString = #file, line: UInt = #line, _ expression: @Sendable () -> (@Sendable () async throws -> T?)) -> AsyncExpectation { return AsyncExpectation( expression: AsyncExpression( expression: expression(), @@ -40,7 +40,7 @@ public func expect(file: FileString = #file, line: UInt = #line, _ expression: @ /// Make an ``AsyncExpectation`` on a given actual value. The value given is lazily evaluated. /// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation`. -public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping @Sendable () async throws -> T?) async -> AsyncExpectation { +public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping @Sendable () async throws -> T?) async -> AsyncExpectation { return AsyncExpectation( expression: AsyncExpression( expression: expression, @@ -50,7 +50,7 @@ public func expecta(file: FileString = #file, line: UInt = #line, _ expressio /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked. /// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation` -public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @Sendable () -> (@Sendable () async throws -> T)) async -> AsyncExpectation { +public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @Sendable () -> (@Sendable () async throws -> T)) async -> AsyncExpectation { return AsyncExpectation( expression: AsyncExpression( expression: expression(), @@ -60,7 +60,7 @@ public func expecta(file: FileString = #file, line: UInt = #line, _ expressio /// Make an ``AsyncExpectation`` on a given actual value. The closure is lazily invoked. /// This is provided to avoid confusion between `expect -> SyncExpectation` and `expect -> AsyncExpectation` -public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @Sendable () -> (@Sendable () async throws -> T?)) async -> AsyncExpectation { +public func expecta(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @Sendable () -> (@Sendable () async throws -> T?)) async -> AsyncExpectation { return AsyncExpectation( expression: AsyncExpression( expression: expression(), diff --git a/Sources/Nimble/Expectation.swift b/Sources/Nimble/Expectation.swift index 8312ada2a..acdb3180b 100644 --- a/Sources/Nimble/Expectation.swift +++ b/Sources/Nimble/Expectation.swift @@ -50,7 +50,7 @@ internal func execute(_ expression: AsyncExpression, _ style: ExpectationS } } -public enum ExpectationStatus: Equatable { +public enum ExpectationStatus: Equatable, Sendable { /// No predicates have been performed. case pending @@ -208,7 +208,9 @@ public struct SyncExpectation: Expectation { public func notTo(_ predicate: Predicate, description: String? = nil) -> Self { toNot(predicate, description: description) } +} +extension SyncExpectation where Value: Sendable { // MARK: - AsyncPredicates /// Tests the actual value using a matcher to match. @discardableResult @@ -237,7 +239,7 @@ public struct SyncExpectation: Expectation { // - NMBExpectation for Objective-C interface } -public struct AsyncExpectation: Expectation { +public struct AsyncExpectation: Expectation, Sendable { public let expression: AsyncExpression /// The status of the test after predicates have been evaluated. diff --git a/Sources/Nimble/Matchers/AsyncPredicate.swift b/Sources/Nimble/Matchers/AsyncPredicate.swift index a3aed7d8e..7f5973257 100644 --- a/Sources/Nimble/Matchers/AsyncPredicate.swift +++ b/Sources/Nimble/Matchers/AsyncPredicate.swift @@ -1,10 +1,10 @@ -public protocol AsyncablePredicate { - associatedtype T - func satisfies(_ expression: AsyncExpression) async throws -> PredicateResult +public protocol AsyncablePredicate { + associatedtype Value: Sendable + func satisfies(_ expression: AsyncExpression) async throws -> PredicateResult } extension Predicate: AsyncablePredicate { - public func satisfies(_ expression: AsyncExpression) async throws -> PredicateResult { + public func satisfies(_ expression: AsyncExpression) async throws -> PredicateResult where T: Sendable { try satisfies(await expression.toSynchronousExpression()) } }