Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make expect+async and AsyncExpression conform to Sendable #1067

Merged
merged 2 commits into from
Jul 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions Sources/Nimble/AsyncExpression.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
private actor MemoizedClosure<T> {
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<T>(_ closure: @escaping () async throws -> T) -> (Bool) async throws -> T {
var cache: T?
private func memoizedClosure<T>(_ 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)
}
}

Expand All @@ -21,8 +43,8 @@ private func memoizedClosure<T>(_ 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<Value> {
internal let _expression: (Bool) async throws -> Value?
public struct AsyncExpression<Value>: Sendable {
internal let _expression: @Sendable (Bool) async throws -> Value?
internal let _withoutCaching: Bool
public let location: SourceLocation
public let isClosure: Bool
Expand All @@ -38,7 +60,7 @@ public struct AsyncExpression<Value> {
/// 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
Expand All @@ -59,7 +81,7 @@ public struct AsyncExpression<Value> {
/// 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
Expand Down Expand Up @@ -90,7 +112,7 @@ public struct AsyncExpression<Value> {
///
/// - Parameter block: The block that can cast the current Expression value to a
/// new type.
public func cast<U>(_ block: @escaping (Value?) throws -> U?) -> AsyncExpression<U> {
public func cast<U>(_ block: @escaping @Sendable (Value?) throws -> U?) -> AsyncExpression<U> {
return AsyncExpression<U>(
expression: ({ try await block(self.evaluate()) }),
location: self.location,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nimble/Utils/SourceLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down