Skip to content

Commit a958571

Browse files
dylanmarykMatty Cross
authored andcommitted
Fix crash when stubbing function that takes Sendable closure
1 parent 5161ec0 commit a958571

File tree

8 files changed

+317
-195
lines changed

8 files changed

+317
-195
lines changed

Generator/Sources/Internal/GeneratorHelper.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ struct GeneratorHelper {
4040

4141
let matchableWhereConstraints = method.signature.parameters.enumerated().map { index, parameter -> String in
4242
let type = parameter.type.isOptional ? "OptionalMatchedType" : "MatchedType"
43-
return "M\(index + 1).\(type) == \(genericSafeType(from: parameter.type.withoutAttributes().unoptionaled.description))"
43+
return "M\(index + 1).\(type) == \(genericSafeType(from: parameter.type.withoutAttributes(except: ["@Sendable"]).unoptionaled.description))"
4444
}
4545
let methodWhereConstraints = method.signature.whereConstraints
4646
return " where \((matchableWhereConstraints + methodWhereConstraints).joined(separator: ", "))"
@@ -57,7 +57,7 @@ struct GeneratorHelper {
5757
private static func parameterMatchers(for parameters: [MethodParameter]) -> String {
5858
guard parameters.isEmpty == false else { return "let matchers: [Cuckoo.ParameterMatcher<Void>] = []" }
5959

60-
let tupleType = parameters.map { $0.type.withoutAttributes().description }.joined(separator: ", ")
60+
let tupleType = parameters.map { $0.type.withoutAttributes(except: ["@Sendable"]).description }.joined(separator: ", ")
6161
let matchers = parameters
6262
// Enumeration is done after filtering out parameters without usable names.
6363
.enumerated()

Generator/Sources/Internal/Tokens/Method.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ extension Method {
106106
"parameterSignatureWithoutNames": signature.parameters.map { "\($0.name): \($0.type)" }.joined(separator: ", "),
107107
"argumentSignature": signature.parameters.map { $0.type.description }.joined(separator: ", "),
108108
"stubFunction": stubFunction,
109-
"inputTypes": signature.parameters.map { $0.type.withoutAttributes(except: ["@escaping"]).description }.joined(separator: ", "),
110-
"genericInputTypes": signature.parameters.map { $0.type.withoutAttributes().description }.joined(separator: ", "),
109+
"inputTypes": signature.parameters.map { $0.type.withoutAttributes(except: ["@escaping", "@Sendable"]).description }.joined(separator: ", "),
110+
"genericInputTypes": signature.parameters.map { $0.type.withoutAttributes(except: ["@Sendable"]).description }.joined(separator: ", "),
111111
"isOptional": isOptional,
112112
"hasClosureParams": hasClosureParams,
113113
"hasOptionalParams": hasOptionalParams,

Source/Matching/ParameterMatcher+closures.swift

Lines changed: 245 additions & 0 deletions
Large diffs are not rendered by default.

Source/Matching/ParameterMatcherFunctions.swift

Lines changed: 0 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -44,182 +44,6 @@ public func anyString() -> ParameterMatcher<String> {
4444
ParameterMatcher()
4545
}
4646

47-
/// Returns a matcher matching any throwing closure.
48-
public func anyThrowingClosure<OUT>() -> ParameterMatcher<() throws -> OUT> {
49-
ParameterMatcher()
50-
}
51-
52-
/// Returns a matcher matching any throwing closure.
53-
public func anyThrowingClosure<IN1, OUT>() -> ParameterMatcher<(IN1) throws -> OUT> {
54-
ParameterMatcher()
55-
}
56-
57-
/// Returns a matcher matching any throwing closure.
58-
public func anyThrowingClosure<IN1, IN2, OUT>() -> ParameterMatcher<(IN1, IN2) throws -> OUT> {
59-
ParameterMatcher()
60-
}
61-
62-
/// Returns a matcher matching any throwing closure.
63-
public func anyThrowingClosure<IN1, IN2, IN3, OUT>() -> ParameterMatcher<(IN1, IN2, IN3) throws -> OUT> {
64-
ParameterMatcher()
65-
}
66-
67-
/// Returns a matcher matching any throwing closure.
68-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4) throws -> OUT> {
69-
ParameterMatcher()
70-
}
71-
72-
/// Returns a matcher matching any throwing closure.
73-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5) throws -> OUT> {
74-
ParameterMatcher()
75-
}
76-
77-
/// Returns a matcher matching any throwing closure.
78-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, IN6, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6) throws -> OUT> {
79-
ParameterMatcher()
80-
}
81-
82-
/// Returns a matcher matching any throwing closure.
83-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, IN6, IN7, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6, IN7) throws -> OUT> {
84-
ParameterMatcher()
85-
}
86-
87-
/// Returns a matcher matching any non-throwing closure.
88-
public func anyClosure<OUT>() -> ParameterMatcher<() -> OUT> {
89-
ParameterMatcher()
90-
}
91-
92-
/// Returns a matcher matching any non-throwing closure.
93-
public func anyClosure<IN1, OUT>() -> ParameterMatcher<(IN1) -> OUT> {
94-
ParameterMatcher()
95-
}
96-
97-
/// Returns a matcher matching any non-throwing closure.
98-
public func anyClosure<IN1, IN2, OUT>() -> ParameterMatcher<(IN1, IN2) -> OUT> {
99-
ParameterMatcher()
100-
}
101-
102-
/// Returns a matcher matching any non-throwing closure.
103-
public func anyClosure<IN1, IN2, IN3, OUT>() -> ParameterMatcher<(IN1, IN2, IN3) -> OUT> {
104-
ParameterMatcher()
105-
}
106-
107-
/// Returns a matcher matching any non-throwing closure.
108-
public func anyClosure<IN1, IN2, IN3, IN4, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4) -> OUT> {
109-
ParameterMatcher()
110-
}
111-
112-
/// Returns a matcher matching any non-throwing closure.
113-
public func anyClosure<IN1, IN2, IN3, IN4, IN5, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5) -> OUT> {
114-
ParameterMatcher()
115-
}
116-
117-
/// Returns a matcher matching any non-throwing closure.
118-
public func anyClosure<IN1, IN2, IN3, IN4, IN5, IN6, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6) -> OUT> {
119-
ParameterMatcher()
120-
}
121-
122-
/// Returns a matcher matching any non-throwing closure.
123-
public func anyClosure<IN1, IN2, IN3, IN4, IN5, IN6, IN7, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6, IN7) -> OUT> {
124-
ParameterMatcher()
125-
}
126-
127-
/// Returns a matcher matching any throwing closure.
128-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
129-
public func anyThrowingClosure<OUT>() -> ParameterMatcher<() async throws -> OUT> {
130-
ParameterMatcher()
131-
}
132-
133-
/// Returns a matcher matching any throwing closure.
134-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
135-
public func anyThrowingClosure<IN1, OUT>() -> ParameterMatcher<(IN1) async throws -> OUT> {
136-
ParameterMatcher()
137-
}
138-
139-
/// Returns a matcher matching any throwing closure.
140-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
141-
public func anyThrowingClosure<IN1, IN2, OUT>() -> ParameterMatcher<(IN1, IN2) async throws -> OUT> {
142-
ParameterMatcher()
143-
}
144-
145-
/// Returns a matcher matching any throwing closure.
146-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
147-
public func anyThrowingClosure<IN1, IN2, IN3, OUT>() -> ParameterMatcher<(IN1, IN2, IN3) async throws -> OUT> {
148-
ParameterMatcher()
149-
}
150-
151-
/// Returns a matcher matching any throwing closure.
152-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
153-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4) async throws -> OUT> {
154-
ParameterMatcher()
155-
}
156-
157-
/// Returns a matcher matching any throwing closure.
158-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
159-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5) async throws -> OUT> {
160-
ParameterMatcher()
161-
}
162-
163-
/// Returns a matcher matching any throwing closure.
164-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
165-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, IN6, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6) async throws -> OUT> {
166-
ParameterMatcher()
167-
}
168-
169-
/// Returns a matcher matching any throwing closure.
170-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
171-
public func anyThrowingClosure<IN1, IN2, IN3, IN4, IN5, IN6, IN7, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6, IN7) async throws -> OUT> {
172-
ParameterMatcher()
173-
}
174-
175-
/// Returns a matcher matching any non-throwing closure.
176-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
177-
public func anyClosure<OUT>() -> ParameterMatcher<() async -> OUT> {
178-
ParameterMatcher()
179-
}
180-
181-
/// Returns a matcher matching any non-throwing closure.
182-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
183-
public func anyClosure<IN1, OUT>() -> ParameterMatcher<(IN1) async -> OUT> {
184-
ParameterMatcher()
185-
}
186-
187-
/// Returns a matcher matching any non-throwing closure.
188-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
189-
public func anyClosure<IN1, IN2, OUT>() -> ParameterMatcher<(IN1, IN2) async -> OUT> {
190-
ParameterMatcher()
191-
}
192-
193-
/// Returns a matcher matching any non-throwing closure.
194-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
195-
public func anyClosure<IN1, IN2, IN3, OUT>() -> ParameterMatcher<(IN1, IN2, IN3) async -> OUT> {
196-
ParameterMatcher()
197-
}
198-
199-
/// Returns a matcher matching any non-throwing closure.
200-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
201-
public func anyClosure<IN1, IN2, IN3, IN4, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4) async -> OUT> {
202-
ParameterMatcher()
203-
}
204-
205-
/// Returns a matcher matching any non-throwing closure.
206-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
207-
public func anyClosure<IN1, IN2, IN3, IN4, IN5, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5) async -> OUT> {
208-
ParameterMatcher()
209-
}
210-
211-
/// Returns a matcher matching any non-throwing closure.
212-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
213-
public func anyClosure<IN1, IN2, IN3, IN4, IN5, IN6, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6) async -> OUT> {
214-
ParameterMatcher()
215-
}
216-
217-
/// Returns a matcher matching any non-throwing closure.
218-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
219-
public func anyClosure<IN1, IN2, IN3, IN4, IN5, IN6, IN7, OUT>() -> ParameterMatcher<(IN1, IN2, IN3, IN4, IN5, IN6, IN7) async -> OUT> {
220-
ParameterMatcher()
221-
}
222-
22347
/// Returns a matcher matching any T value or nil.
22448
public func any<T>(_ type: T.Type = T.self) -> ParameterMatcher<T> {
22549
ParameterMatcher()
@@ -268,17 +92,6 @@ public func anyString() -> ParameterMatcher<String?> {
26892
notNil()
26993
}
27094

271-
/// Returns a matcher matching any closure.
272-
public func anyClosure<IN, OUT>() -> ParameterMatcher<(((IN)) -> OUT)?> {
273-
notNil()
274-
}
275-
276-
/// Returns a matcher matching any closure.
277-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
278-
public func anyClosure<IN, OUT>() -> ParameterMatcher<(((IN)) async -> OUT)?> {
279-
notNil()
280-
}
281-
28295
public func anyOptionalThrowingClosure<IN, OUT>() -> ParameterMatcher<(((IN)) throws -> OUT)?> {
28396
notNil()
28497
}

Source/MockManager.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ public class MockManager {
4646
return callRethrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
4747
}
4848

49-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
5049
private func callInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async -> OUT, defaultCall: () async -> OUT) async -> OUT {
5150
return await callRethrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
5251
}
@@ -91,7 +90,6 @@ public class MockManager {
9190
}
9291
}
9392

94-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
9593
private func callRethrowsInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async throws -> OUT, defaultCall: () async throws -> OUT) async rethrows -> OUT {
9694
let stubCall = ConcreteStubCall(method: method, parameters: escapingParameters)
9795
queue.sync {
@@ -168,7 +166,6 @@ public class MockManager {
168166
}
169167
}
170168

171-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
172169
private func callThrowsInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async throws -> OUT, defaultCall: () async throws -> OUT) async throws -> OUT {
173170
let stubCall = ConcreteStubCall(method: method, parameters: escapingParameters)
174171
queue.sync {
@@ -363,7 +360,6 @@ extension MockManager {
363360
return try callRethrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
364361
}
365362

366-
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
367363
public func callRethrows<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: @autoclosure () async throws -> OUT, defaultCall: @autoclosure () async throws -> OUT) async rethrows -> OUT {
368364
return try await callRethrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
369365
}

Tests/Swift/Matching/ParameterMatcherFunctionsTest.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ final class ParameterMatcherFunctionsTest: XCTestCase {
7171
XCTAssertTrue((anyClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _, _, _ in 0 })
7272
}
7373

74+
func testAnySendableClosure() {
75+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable () -> Int>).matches { 0 })
76+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int) -> Int>).matches { _ in 0 })
77+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int, Int) -> Int>).matches { _, _ in 0 })
78+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int, Int, Int) -> Int>).matches { _, _, _ in 0 })
79+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int) -> Int>).matches { _, _, _, _ in 0 })
80+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _ in 0 })
81+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _, _ in 0 })
82+
XCTAssertTrue((anyClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int, Int, Int) -> Int>).matches { _, _, _, _, _, _, _ in 0 })
83+
}
84+
7485
func testAnyThrowingClosure() {
7586
struct MockError: Error { }
7687

@@ -93,6 +104,27 @@ final class ParameterMatcherFunctionsTest: XCTestCase {
93104
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<(Int, Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _, _ in throw MockError() })
94105
}
95106

107+
func testAnySendableThrowingClosure() {
108+
struct MockError: Error { }
109+
110+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable () throws -> Int>).matches { 0 })
111+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int) throws -> Int>).matches { _ in 0 })
112+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int) throws -> Int>).matches { _, _ in 0 })
113+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int) throws -> Int>).matches { _, _, _ in 0 })
114+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _ in 0 })
115+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _ in 0 })
116+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _ in 0 })
117+
118+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable () throws -> Int>).matches { throw MockError() })
119+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int) throws -> Int>).matches { _ in throw MockError() })
120+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int) throws -> Int>).matches { _, _ in throw MockError() })
121+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int) throws -> Int>).matches { _, _, _ in throw MockError() })
122+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _ in throw MockError() })
123+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _ in throw MockError() })
124+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _ in throw MockError() })
125+
XCTAssertTrue((anyThrowingClosure() as ParameterMatcher<@Sendable (Int, Int, Int, Int, Int, Int, Int) throws -> Int>).matches { _, _, _, _, _, _, _ in throw MockError() })
126+
}
127+
96128
func testAnyOptionalThrowingClosure() {
97129
XCTAssertTrue(anyOptionalThrowingClosure().matches { (i: String) in 0 })
98130
XCTAssertTrue(anyOptionalThrowingClosure().matches { (p: Int) throws in 1 })

Tests/Swift/Source/TestedClass.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,21 +122,41 @@ class TestedClass {
122122
return nil
123123
}
124124

125+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
126+
func withAsyncSendableClosure(closure: @Sendable (String) async -> String?) -> String? {
127+
return nil
128+
}
129+
125130
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
126131
func withAsyncClosureAsync(closure: (String) async -> String?) async -> String? {
127132
return nil
128133
}
129134

135+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
136+
func withAsyncSendableClosureAsync(closure: @Sendable (String) async -> String?) async -> String? {
137+
return nil
138+
}
139+
130140
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
131141
func withAsyncEscapingClosure(closure: @escaping (String) async -> String?) -> String? {
132142
return nil
133143
}
134144

145+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
146+
func withAsyncEscapingSendableClosure(closure: @escaping @Sendable (String) async -> String?) -> String? {
147+
return nil
148+
}
149+
135150
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
136151
func withAsyncOptionalClosureAsync(closure: ((String) async -> String?)?) async -> String? {
137152
return nil
138153
}
139154

155+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
156+
func withAsyncSendableOptionalClosureAsync(closure: (@Sendable (String) async -> String?)?) async -> String? {
157+
return nil
158+
}
159+
140160
func withThrows() throws -> Int {
141161
return 0
142162
}

0 commit comments

Comments
 (0)