Skip to content

Commit fc2aeda

Browse files
MaelRBMatyasKriz
authored andcommitted
Add support for typed-throws methods
1 parent 5253e78 commit fc2aeda

18 files changed

+123
-66
lines changed

Generator/Sources/Internal/Crawlers/Crawler.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ extension Crawler {
286286

287287
let getAccessor = accessors.first { $0.accessorSpecifier.tokenKind == .keyword(.get) }
288288
effects = Variable.Effects(
289-
isThrowing: getAccessor?.effectSpecifiers?.throwsSpecifier?.isPresent == true,
289+
isThrowing: getAccessor?.effectSpecifiers?.throwsClause != nil,
290290
isAsync: getAccessor?.effectSpecifiers?.asyncSpecifier?.isPresent == true
291291
)
292292
case .getter:
@@ -330,7 +330,7 @@ extension Crawler {
330330
genericParameters: genericParameters(from: initializer.genericParameterClause?.parameters),
331331
parameters: parameters,
332332
asyncType: nil,
333-
throwType: initializer.signature.effectSpecifiers?.throwsSpecifier.flatMap { ThrowType(rawValue: $0.filteredDescription) },
333+
throwType: ThrowType(syntax: initializer.signature.effectSpecifiers?.throwsClause),
334334
returnType: nil,
335335
whereConstraints: genericRequirements(from: initializer.genericWhereClause?.requirements)
336336
),
@@ -365,7 +365,7 @@ extension Crawler {
365365
genericParameters: genericParameters(from: method.genericParameterClause?.parameters),
366366
parameters: parameters,
367367
asyncType: method.signature.effectSpecifiers?.asyncSpecifier.flatMap { AsyncType(rawValue: $0.filteredDescription) },
368-
throwType: method.signature.effectSpecifiers?.throwsSpecifier.flatMap { ThrowType(rawValue: $0.filteredDescription) },
368+
throwType: ThrowType(syntax: method.signature.effectSpecifiers?.throwsClause),
369369
returnType: returnType ?? ComplexType.type("Void"),
370370
whereConstraints: genericRequirements(from: method.genericWhereClause?.requirements)
371371
),

Generator/Sources/Internal/Templates/MockTemplate.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ extension {{ container.parentFullyQualifiedName }} {
124124
"{{method.fullyQualifiedName}}",
125125
parameters: ({{method.parameterNames}}),
126126
escapingParameters: ({{method.escapingParameterNames}}),
127+
{% if method.throwsOnly %}errorType: {{ method.throwTypeError }}.self,{% endif %}
127128
superclassCall: {%+ if container.isImplementation %}{% if method.isAsync %}await {%+ endif %}super.{{method.name}}({{method.call}}){% else %}Cuckoo.MockManager.crashOnProtocolSuperclassCall(){% endif %},
128129
defaultCall: {%+ if method.isAsync %}await {%+ endif %}__defaultImplStub!.{{method.name}}{%if method.isOptional %}!{%endif%}({{method.call}})
129130
){{ method.parameters|closeNestedClosure }}

Generator/Sources/Internal/Templates/StubbingProxyTemplate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extension Templates {
1313
{% for attribute in property.attributes %}
1414
{{ attribute }}
1515
{% endfor %}
16-
var {{property.name}}: Cuckoo.{{ property.stubType }}<{{ container.mockName }}, {% if property.isReadOnly %}{{property.type|genericSafe}}{% else %}{{property.nonOptionalType|genericSafe}}{% endif %}> {
16+
var {{property.name}}: Cuckoo.{{ property.stubType }}<{{ container.mockName }}, {% if property.isReadOnly %}{{property.type|genericSafe}}{% else %}{{property.nonOptionalType|genericSafe}}{% endif %}{% if method.isThrowing %},{{ method.throwTypeError }}{% endif %}> {
1717
return .init(manager: cuckoo_manager, name: "{{property.name}}")
1818
}
1919
{% if property.hasUnavailablePlatforms %}
@@ -25,7 +25,7 @@ extension Templates {
2525
{% for attribute in method.attributes %}
2626
{{ attribute }}
2727
{% endfor %}
28-
func {{method.name|escapeReservedKeywords}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.genericInputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.self|matchableGenericWhereClause}} {
28+
func {{method.name|escapeReservedKeywords}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.genericInputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}{% if method.isThrowing %},{{ method.throwTypeError }}{% endif %}>{{method.self|matchableGenericWhereClause}} {
2929
{{method.parameters|parameterMatchers}}
3030
return .init(stub: cuckoo_manager.createStub(for: {{ container.mockName }}.self,
3131
method: "{{method.fullyQualifiedName}}",

Generator/Sources/Internal/Tokens/ComplexType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ extension ComplexType.Closure.Effects {
253253
if effectSpecifiers.asyncSpecifier?.isPresent == true {
254254
effects.insert(.async)
255255
}
256-
if effectSpecifiers.throwsSpecifier?.isPresent == true {
256+
if effectSpecifiers.throwsClause != nil {
257257
effects.insert(.throws)
258258
}
259259
self = effects

Generator/Sources/Internal/Tokens/Method.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ extension Method {
3939
var isThrowing: Bool {
4040
signature.throwType.map { $0.isThrowing || $0.isRethrowing } ?? false
4141
}
42+
43+
var throwsOnly: Bool {
44+
signature.throwType.map { $0.isThrowing } ?? false
45+
}
4246

4347
var returnType: ComplexType? {
4448
signature.returnType
@@ -99,7 +103,9 @@ extension Method {
99103
"returnType": returnType?.description ?? "",
100104
"isAsync": isAsync,
101105
"isThrowing": isThrowing,
102-
"throwType": signature.throwType?.description ?? "",
106+
"throwsOnly": throwsOnly,
107+
"throwType": signature.throwType?.keyword ?? "",
108+
"throwTypeError": signature.throwType?.type ?? "",
103109
"fullyQualifiedName": fullyQualifiedName,
104110
"call": call,
105111
"parameterSignature": signature.parameters.map { $0.description }.joined(separator: ", "),
Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,66 @@
1-
enum ThrowType: String, CustomStringConvertible, Equatable {
2-
case `throws`
1+
import SwiftSyntax
2+
3+
enum ThrowType: CustomStringConvertible, Equatable {
4+
case `throws`(String?)
35
case `rethrows`
46

5-
init?(string: String) {
6-
if string.trimmed.hasPrefix(ThrowType.throws.rawValue) {
7-
self = .throws
8-
} else if string.trimmed.hasPrefix(ThrowType.rethrows.rawValue) {
7+
init?(syntax: ThrowsClauseSyntax?) {
8+
guard let syntax else { return nil }
9+
let keyword = syntax.throwsSpecifier.text
10+
let type: String? = syntax.type?.as(IdentifierTypeSyntax.self)?.name.text
11+
if keyword.trimmed.hasPrefix("throws") {
12+
self = .throws(type)
13+
} else if keyword.trimmed.hasPrefix("rethrows") {
914
self = .rethrows
1015
} else {
1116
return nil
1217
}
1318
}
1419

1520
var isThrowing: Bool {
16-
self == .throws
21+
if case .throws = self {
22+
return true
23+
} else {
24+
return false
25+
}
1726
}
1827

1928
var isRethrowing: Bool {
20-
self == .rethrows
29+
if case .rethrows = self {
30+
return true
31+
} else {
32+
return false
33+
}
2134
}
2235

2336
var description: String {
24-
rawValue
37+
switch self {
38+
case .throws(let type):
39+
if let type {
40+
return "throws(\(type))"
41+
} else {
42+
return "throws"
43+
}
44+
case .rethrows:
45+
return "rethrows"
46+
}
47+
}
48+
49+
var keyword: String {
50+
switch self {
51+
case .throws:
52+
return "throws"
53+
case .rethrows:
54+
return "rethrows"
55+
}
56+
}
57+
58+
var type: String {
59+
switch self {
60+
case .throws(let type):
61+
return type ?? "Error"
62+
case .rethrows:
63+
return "Error"
64+
}
2565
}
2666
}

Source/MockManager.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ public class MockManager {
5050
return await callRethrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
5151
}
5252

53-
private func callRethrowsInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () throws -> OUT, defaultCall: () throws -> OUT) rethrows -> OUT {
53+
private func callRethrowsInternal<IN, OUT, ERROR>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () throws(ERROR) -> OUT, defaultCall: () throws(ERROR) -> OUT) rethrows -> OUT {
5454
let stubCall = ConcreteStubCall(method: method, parameters: escapingParameters)
5555
queue.sync {
5656
stubCalls.append(stubCall)
5757
unverifiedStubCallsIndexes.append(stubCalls.count - 1)
5858
}
5959

60-
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
60+
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT, ERROR> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
6161

6262
guard let action = queue.sync(execute: {
6363
return stub.actions.count > 1 ? stub.actions.removeFirst() : stub.actions.first
@@ -90,14 +90,14 @@ public class MockManager {
9090
}
9191
}
9292

93-
private func callRethrowsInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async throws -> OUT, defaultCall: () async throws -> OUT) async rethrows -> OUT {
93+
private func callRethrowsInternal<IN, OUT, ERROR>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async throws(ERROR) -> OUT, defaultCall: () async throws(ERROR) -> OUT) async rethrows -> OUT {
9494
let stubCall = ConcreteStubCall(method: method, parameters: escapingParameters)
9595
queue.sync {
9696
stubCalls.append(stubCall)
9797
unverifiedStubCallsIndexes.append(stubCalls.count - 1)
9898
}
9999

100-
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
100+
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT, ERROR> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
101101

102102
guard let action = queue.sync(execute: {
103103
return stub.actions.count > 1 ? stub.actions.removeFirst() : stub.actions.first
@@ -130,14 +130,14 @@ public class MockManager {
130130
}
131131
}
132132

133-
private func callThrowsInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () throws -> OUT, defaultCall: () throws -> OUT) throws -> OUT {
133+
private func callThrowsInternal<IN, OUT, ERROR>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () throws(ERROR) -> OUT, defaultCall: () throws(ERROR) -> OUT) throws(ERROR) -> OUT {
134134
let stubCall = ConcreteStubCall(method: method, parameters: escapingParameters)
135135
queue.sync {
136136
stubCalls.append(stubCall)
137137
unverifiedStubCallsIndexes.append(stubCalls.count - 1)
138138
}
139139

140-
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
140+
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT, ERROR> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
141141

142142
guard let action = queue.sync(execute: {
143143
return stub.actions.count > 1 ? stub.actions.removeFirst() : stub.actions.first
@@ -166,14 +166,14 @@ public class MockManager {
166166
}
167167
}
168168

169-
private func callThrowsInternal<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async throws -> OUT, defaultCall: () async throws -> OUT) async throws -> OUT {
169+
private func callThrowsInternal<IN, OUT, ERROR>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: () async throws(ERROR) -> OUT, defaultCall: () async throws(ERROR) -> OUT) async throws(ERROR) -> OUT {
170170
let stubCall = ConcreteStubCall(method: method, parameters: escapingParameters)
171171
queue.sync {
172172
stubCalls.append(stubCall)
173173
unverifiedStubCallsIndexes.append(stubCalls.count - 1)
174174
}
175175

176-
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
176+
if let stub = (stubs.filter { $0.method == method }.compactMap { $0 as? ConcreteStub<IN, OUT, ERROR> }.filter { $0.parameterMatchers.reduce(true) { $0 && $1.matches(parameters) } }.first) {
177177

178178
guard let action = queue.sync(execute: {
179179
return stub.actions.count > 1 ? stub.actions.removeFirst() : stub.actions.first
@@ -202,14 +202,14 @@ public class MockManager {
202202
}
203203
}
204204

205-
public func createStub<MOCK: ClassMock, IN, OUT>(for _: MOCK.Type, method: String, parameterMatchers: [ParameterMatcher<IN>]) -> ClassConcreteStub<IN, OUT> {
206-
let stub = ClassConcreteStub<IN, OUT>(method: method, parameterMatchers: parameterMatchers)
205+
public func createStub<MOCK: ClassMock, IN, OUT, ERROR>(for _: MOCK.Type, method: String, parameterMatchers: [ParameterMatcher<IN>]) -> ClassConcreteStub<IN, OUT, ERROR> {
206+
let stub = ClassConcreteStub<IN, OUT, ERROR>(method: method, parameterMatchers: parameterMatchers)
207207
stubs.insert(stub, at: 0)
208208
return stub
209209
}
210210

211-
public func createStub<MOCK: ProtocolMock, IN, OUT>(for _: MOCK.Type, method: String, parameterMatchers: [ParameterMatcher<IN>]) -> ConcreteStub<IN, OUT> {
212-
let stub = ConcreteStub<IN, OUT>(method: method, parameterMatchers: parameterMatchers)
211+
public func createStub<MOCK: ProtocolMock, IN, OUT, ERROR>(for _: MOCK.Type, method: String, parameterMatchers: [ParameterMatcher<IN>]) -> ConcreteStub<IN, OUT, ERROR> {
212+
let stub = ConcreteStub<IN, OUT, ERROR>(method: method, parameterMatchers: parameterMatchers)
213213
stubs.insert(stub, at: 0)
214214
return stub
215215
}
@@ -327,11 +327,11 @@ extension MockManager {
327327

328328
extension MockManager {
329329
public func getterThrows<T>(_ property: String, superclassCall: @autoclosure () throws -> T, defaultCall: @autoclosure () throws -> T) throws -> T {
330-
return try callThrows(getterName(property), parameters: Void(), escapingParameters: Void(), superclassCall: try superclassCall(), defaultCall: try defaultCall())
330+
return try callThrows(getterName(property), parameters: Void(), escapingParameters: Void(), errorType: (Error).self, superclassCall: try superclassCall(), defaultCall: try defaultCall())
331331
}
332332

333333
public func getterThrows<T>(_ property: String, superclassCall: @autoclosure () async throws -> T, defaultCall: @autoclosure () async throws -> T) async throws -> T {
334-
return try await callThrows(getterName(property), parameters: Void(), escapingParameters: Void(), superclassCall: try await superclassCall(), defaultCall: try await defaultCall())
334+
return try await callThrows(getterName(property), parameters: Void(), escapingParameters: Void(), errorType: (Error).self, superclassCall: try await superclassCall(), defaultCall: try await defaultCall())
335335
}
336336
}
337337

@@ -346,11 +346,11 @@ extension MockManager {
346346
}
347347

348348
extension MockManager {
349-
public func callThrows<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: @autoclosure () throws -> OUT, defaultCall: @autoclosure () throws -> OUT) throws -> OUT {
349+
public func callThrows<IN, OUT, ERROR>(_ method: String, parameters: IN, escapingParameters: IN, errorType: ERROR.Type, superclassCall: @autoclosure () throws(ERROR) -> OUT, defaultCall: @autoclosure () throws(ERROR) -> OUT) throws(ERROR) -> OUT {
350350
return try callThrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
351351
}
352352

353-
public func callThrows<IN, OUT>(_ method: String, parameters: IN, escapingParameters: IN, superclassCall: @autoclosure () async throws -> OUT, defaultCall: @autoclosure () async throws -> OUT) async throws -> OUT {
353+
public func callThrows<IN, OUT, ERROR>(_ method: String, parameters: IN, escapingParameters: IN, errorType: ERROR.Type, superclassCall: @autoclosure () async throws(ERROR) -> OUT, defaultCall: @autoclosure () async throws(ERROR) -> OUT) async throws(ERROR) -> OUT {
354354
return try await callThrowsInternal(method, parameters: parameters, escapingParameters: escapingParameters, superclassCall: superclassCall, defaultCall: defaultCall)
355355
}
356356
}

Source/Stubbing/Stub.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ public protocol Stub {
22
var method: String { get }
33
}
44

5-
public class ConcreteStub<IN, OUT>: Stub {
5+
public class ConcreteStub<IN, OUT, ERROR>: Stub {
66
public let method: String
77
let parameterMatchers: [ParameterMatcher<IN>]
8-
var actions: [StubAction<IN, OUT>] = []
8+
var actions: [StubAction<IN, OUT, ERROR>] = []
99

1010
init(method: String, parameterMatchers: [ParameterMatcher<IN>]) {
1111
self.method = method
1212
self.parameterMatchers = parameterMatchers
1313
}
1414

15-
func appendAction(_ action: StubAction<IN, OUT>) {
15+
func appendAction(_ action: StubAction<IN, OUT, ERROR>) {
1616
actions.append(action)
1717
}
1818
}
1919

20-
public class ClassConcreteStub<IN, OUT>: ConcreteStub<IN, OUT> { }
20+
public class ClassConcreteStub<IN, OUT, ERROR>: ConcreteStub<IN, OUT, ERROR> { }

Source/Stubbing/StubAction.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
enum StubAction<IN, OUT> {
2-
case callImplementation((IN) throws -> OUT)
1+
enum StubAction<IN, OUT, ERROR> {
2+
case callImplementation((IN) throws(ERROR) -> OUT)
33
case returnValue(OUT)
4-
case throwError(Error)
4+
case throwError(ERROR)
55
case callRealImplementation
66
}

Source/Stubbing/StubFunction/StubFunction.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ public protocol StubFunction: StubFunctionThenTrait, StubFunctionThenReturnTrait
22
}
33

44
public struct ProtocolStubFunction<IN, OUT>: StubFunction {
5-
public let stub: ConcreteStub<IN, OUT>
5+
public let stub: ConcreteStub<IN, OUT, Never>
66

7-
public init(stub: ConcreteStub<IN, OUT>) {
7+
public init(stub: ConcreteStub<IN, OUT, Never>) {
88
self.stub = stub
99
}
1010
}
1111

1212
public struct ClassStubFunction<IN, OUT>: StubFunction, StubFunctionThenCallRealImplementationTrait {
13-
public let stub: ConcreteStub<IN, OUT>
13+
public let stub: ConcreteStub<IN, OUT, Never>
1414

15-
public init(stub: ClassConcreteStub<IN, OUT>) {
15+
public init(stub: ClassConcreteStub<IN, OUT, Never>) {
1616
self.stub = stub
1717
}
1818
}

0 commit comments

Comments
 (0)