From b5fa8887b3584056fed46abe569bc0cbde190de4 Mon Sep 17 00:00:00 2001 From: Rachel Brindle Date: Thu, 27 Jul 2023 19:12:21 -0700 Subject: [PATCH] Make AsyncExpression conform to Sendable --- Sources/Nimble/AsyncExpression.swift | 44 +++++++++++++++++------ Sources/Nimble/Utils/SourceLocation.swift | 2 +- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Sources/Nimble/AsyncExpression.swift b/Sources/Nimble/AsyncExpression.swift index e643637c8..75829c9ee 100644 --- a/Sources/Nimble/AsyncExpression.swift +++ b/Sources/Nimble/AsyncExpression.swift @@ -1,12 +1,34 @@ +private actor MemoizedClosure { + var closure: @Sendable () async throws -> T + var cache: T? + + init(_ closure: @escaping @Sendable () async throws -> T) { + self.closure = closure + } + + func set(_ cache: T) -> T { + self.cache = cache + return cache + } + + func call(_ withoutCaching: Bool) async throws -> T { + if withoutCaching { + return try await closure() + } + if let cache { + return cache + } else { + return set(try await closure()) + } + } +} + // Memoizes the given closure, only calling the passed // closure once; even if repeat calls to the returned closure -private func memoizedClosure(_ closure: @escaping () async throws -> T) -> (Bool) async throws -> T { - var cache: T? +private func memoizedClosure(_ closure: @escaping @Sendable () async throws -> T) -> @Sendable (Bool) async throws -> T { + let memoized = MemoizedClosure(closure) return { withoutCaching in - if withoutCaching || cache == nil { - cache = try await closure() - } - return cache! + try await memoized.call(withoutCaching) } } @@ -21,8 +43,8 @@ private func memoizedClosure(_ closure: @escaping () async throws -> T) -> (B /// /// 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 { - internal let _expression: (Bool) async throws -> Value? +public struct AsyncExpression: Sendable { + internal let _expression: @Sendable (Bool) async throws -> Value? internal let _withoutCaching: Bool public let location: SourceLocation public let isClosure: Bool @@ -38,7 +60,7 @@ public struct AsyncExpression { /// requires an explicit closure. This gives Nimble /// flexibility if @autoclosure behavior changes between /// Swift versions. Nimble internals always sets this true. - public init(expression: @escaping () async throws -> Value?, location: SourceLocation, isClosure: Bool = true) { + public init(expression: @escaping @Sendable () async throws -> Value?, location: SourceLocation, isClosure: Bool = true) { self._expression = memoizedClosure(expression) self.location = location self._withoutCaching = false @@ -59,7 +81,7 @@ public struct AsyncExpression { /// requires an explicit closure. This gives Nimble /// flexibility if @autoclosure behavior changes between /// Swift versions. Nimble internals always sets this true. - public init(memoizedExpression: @escaping (Bool) async throws -> Value?, location: SourceLocation, withoutCaching: Bool, isClosure: Bool = true) { + public init(memoizedExpression: @escaping @Sendable (Bool) async throws -> Value?, location: SourceLocation, withoutCaching: Bool, isClosure: Bool = true) { self._expression = memoizedExpression self.location = location self._withoutCaching = withoutCaching @@ -90,7 +112,7 @@ public struct AsyncExpression { /// /// - Parameter block: The block that can cast the current Expression value to a /// new type. - public func cast(_ block: @escaping (Value?) throws -> U?) -> AsyncExpression { + public func cast(_ block: @escaping @Sendable (Value?) throws -> U?) -> AsyncExpression { return AsyncExpression( expression: ({ try await block(self.evaluate()) }), location: self.location, diff --git a/Sources/Nimble/Utils/SourceLocation.swift b/Sources/Nimble/Utils/SourceLocation.swift index 64838ffe9..722ce2c9d 100644 --- a/Sources/Nimble/Utils/SourceLocation.swift +++ b/Sources/Nimble/Utils/SourceLocation.swift @@ -11,7 +11,7 @@ public typealias FileString = StaticString public typealias FileString = String #endif -public final class SourceLocation: NSObject { +public final class SourceLocation: NSObject, Sendable { public let file: FileString public let line: UInt