Fix not being able to put Optional into functions accepting Optionals.

This commit is contained in:
Tadeas Kriz 2019-03-20 19:17:55 +01:00
parent 417075a94a
commit 4b403883ea
6 changed files with 128 additions and 32 deletions

View File

@ -71,14 +71,20 @@ public struct Generator {
private func matchableGenerics(with parameters: [MethodParameter]) -> String { private func matchableGenerics(with parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" } guard parameters.isEmpty == false else { return "" }
let genericParameters = (1...parameters.count).map { "M\($0): Cuckoo.Matchable" }.joined(separator: ", ") let genericParameters = parameters.enumerated().map { index, parameter -> String in
let type = parameter.isOptional ? "OptionalMatchable" : "Matchable"
return "M\(index + 1): Cuckoo.\(type)"
}.joined(separator: ", ")
return "<\(genericParameters)>" return "<\(genericParameters)>"
} }
private func matchableGenerics(where parameters: [MethodParameter]) -> String { private func matchableGenerics(where parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" } guard parameters.isEmpty == false else { return "" }
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.type.withoutAttributes.unoptionaled.sugarized))" }.joined(separator: ", ") let whereClause = parameters.enumerated().map { index, parameter in
let type = parameter.isOptional ? "OptionalMatchedType" : "MatchedType"
return "M\(index + 1).\(type) == \(genericSafeType(from: parameter.type.withoutAttributes.unoptionaled.sugarized))"
}.joined(separator: ", ")
return " where \(whereClause)" return " where \(whereClause)"
} }

View File

@ -15,8 +15,12 @@ public protocol Matchable {
/// Matcher for this instance. This should be an equalTo type of a matcher, but it is not required. /// Matcher for this instance. This should be an equalTo type of a matcher, but it is not required.
var matcher: ParameterMatcher<MatchedType> { get } var matcher: ParameterMatcher<MatchedType> { get }
}
var optionalMatcher: ParameterMatcher<MatchedType?> { get } public protocol OptionalMatchable {
associatedtype OptionalMatchedType
var optionalMatcher: ParameterMatcher<OptionalMatchedType?> { get }
} }
public extension Matchable { public extension Matchable {
@ -50,9 +54,20 @@ extension Optional: Matchable where Wrapped: Matchable, Wrapped.MatchedType == W
} }
} }
extension Matchable where Self == MatchedType { extension Optional: OptionalMatchable where Wrapped: OptionalMatchable, Wrapped.OptionalMatchedType == Wrapped {
public var optionalMatcher: ParameterMatcher<MatchedType?> { public typealias OptionalMatchedType = Wrapped
return Optional(self).matcher
public var optionalMatcher: ParameterMatcher<Wrapped?> {
return ParameterMatcher<Wrapped?> { other in
switch (self, other) {
case (.none, .none):
return true
case (.some(let lhs), .some(let rhs)):
return lhs.optionalMatcher.matches(rhs)
default:
return false
}
}
} }
} }
@ -62,32 +77,85 @@ extension Matchable where Self: Equatable {
} }
} }
extension OptionalMatchable where OptionalMatchedType == Self, Self: Equatable {
public var optionalMatcher: ParameterMatcher<OptionalMatchedType?> {
return ParameterMatcher { other in
return Optional(self) == other
}
}
}
extension Bool: Matchable {} extension Bool: Matchable {}
extension Bool: OptionalMatchable {
public typealias OptionalMatchedType = Bool
}
extension String: Matchable {} extension String: Matchable {}
extension String: OptionalMatchable {
public typealias OptionalMatchedType = String
}
extension Float: Matchable {} extension Float: Matchable {}
extension Float: OptionalMatchable {
public typealias OptionalMatchedType = Float
}
extension Double: Matchable {} extension Double: Matchable {}
extension Double: OptionalMatchable {
public typealias OptionalMatchedType = Double
}
extension Character: Matchable {} extension Character: Matchable {}
extension Character: OptionalMatchable {
public typealias OptionalMatchedType = Character
}
extension Int: Matchable {} extension Int: Matchable {}
extension Int: OptionalMatchable {
public typealias OptionalMatchedType = Int
}
extension Int8: Matchable {} extension Int8: Matchable {}
extension Int8: OptionalMatchable {
public typealias OptionalMatchedType = Int8
}
extension Int16: Matchable {} extension Int16: Matchable {}
extension Int16: OptionalMatchable {
public typealias OptionalMatchedType = Int16
}
extension Int32: Matchable {} extension Int32: Matchable {}
extension Int32: OptionalMatchable {
public typealias OptionalMatchedType = Int32
}
extension Int64: Matchable {} extension Int64: Matchable {}
extension Int64: OptionalMatchable {
public typealias OptionalMatchedType = Int64
}
extension UInt: Matchable {} extension UInt: Matchable {}
extension UInt: OptionalMatchable {
public typealias OptionalMatchedType = UInt
}
extension UInt8: Matchable {} extension UInt8: Matchable {}
extension UInt8: OptionalMatchable {
public typealias OptionalMatchedType = UInt8
}
extension UInt16: Matchable {} extension UInt16: Matchable {}
extension UInt16: OptionalMatchable {
public typealias OptionalMatchedType = UInt16
}
extension UInt32: Matchable {} extension UInt32: Matchable {}
extension UInt32: OptionalMatchable {
public typealias OptionalMatchedType = UInt32
}
extension UInt64: Matchable {} extension UInt64: Matchable {}
extension UInt64: OptionalMatchable {
public typealias OptionalMatchedType = UInt64
}

View File

@ -17,14 +17,30 @@ public struct ParameterMatcher<T>: Matchable {
public var matcher: ParameterMatcher<T> { public var matcher: ParameterMatcher<T> {
return self return self
} }
public var optionalMatcher: ParameterMatcher<Optional<T>> {
return ParameterMatcher<Optional<T>> { other in
other.map(self.matchesFunction) ?? false
}
}
public func matches(_ input: T) -> Bool { public func matches(_ input: T) -> Bool {
return matchesFunction(input) return matchesFunction(input)
} }
} }
public protocol CuckooOptionalType {
associatedtype Wrapped
static func from(optional: Optional<Wrapped>) -> Self
}
extension Optional: CuckooOptionalType {
public static func from(optional: Optional<Wrapped>) -> Optional<Wrapped> {
return optional
}
}
extension ParameterMatcher: OptionalMatchable where T: CuckooOptionalType {
public typealias OptionalMatchedType = T.Wrapped
public var optionalMatcher: ParameterMatcher<T.Wrapped?> {
return ParameterMatcher<T.Wrapped?> { other in
other.map { self.matchesFunction(T.from(optional: $0)) } ?? false
}
}
}

View File

@ -20,7 +20,7 @@ public func wrap<M: Matchable, IN>(matchable: M, mapping: @escaping (IN) -> M.Ma
} }
} }
public func wrap<M: Matchable, IN, O>(matchable: M, mapping: @escaping (IN) -> M.MatchedType?) -> ParameterMatcher<IN> where M.MatchedType == O { public func wrap<M: OptionalMatchable, IN, O>(matchable: M, mapping: @escaping (IN) -> M.OptionalMatchedType?) -> ParameterMatcher<IN> where M.OptionalMatchedType == O {
return ParameterMatcher { return ParameterMatcher {
return matchable.optionalMatcher.matches(mapping($0)) return matchable.optionalMatcher.matches(mapping($0))
} }

View File

@ -206,17 +206,23 @@ class ClassTest: XCTestCase {
let mock = MockOptionalParamsClass() let mock = MockOptionalParamsClass()
stub(mock) { mock in stub(mock) { mock in
// when(mock.clashingFunction(param1: anyInt(), param2: "Henlo Fren")).thenDoNothing() when(mock.clashingFunction(param1: Optional(1), param2: "Henlo Fren") as Cuckoo.ClassStubNoReturnFunction<(Int?, String)>).thenDoNothing()
// when(mock.clashingFunction(param1: anyInt(), param2: Optional("What's the question?"))).then { print("What's 6 times 9? \($0.0)") } when(mock.clashingFunction(param1: Optional.none, param2: "Henlo Fren") as Cuckoo.ClassStubNoReturnFunction<(Int?, String)>).thenDoNothing()
// mock.function(param: "string") when(mock.clashingFunction(param1: anyInt(), param2: "What's the question?") as Cuckoo.ClassStubNoReturnFunction<(Int, String?)>).then {
print("What's 6 times 9? \($0.0)")
}
when(mock.clashingFunction(param1: anyInt(), param2: isNil()) as Cuckoo.ClassStubNoReturnFunction<(Int, String?)>).then {
print("What's 6 times 9? \($0.0)")
}
mock.function(param: "string")
when(mock.function(param: "string")).thenDoNothing() when(mock.function(param: "string")).thenDoNothing()
} }
// mock.clashingFunction(param1: Optional(22), param2: "Henlo Fren") mock.clashingFunction(param1: Optional(22), param2: "Henlo Fren")
// mock.clashingFunction(param1: 42, param2: Optional("What's the question?")) mock.clashingFunction(param1: 42, param2: Optional("What's the question?"))
// verify(mock).clashingFunction(param1: anyInt(), param2: "Henlo Fren") _ = verify(mock).clashingFunction(param1: anyInt(), param2: "Henlo Fren") as Cuckoo.__DoNotUse<(Int?, String), Void>
// verify(mock).clashingFunction(param1: 42, param2: Optional("What's the question?")) _ = verify(mock).clashingFunction(param1: 42, param2: "What's the question?") as Cuckoo.__DoNotUse<(Int, String?), Void>
} }
private enum TestError: Error { private enum TestError: Error {

View File

@ -145,16 +145,16 @@ class StubbingTest: XCTestCase {
when(stub.subSubMethod()).thenReturn("not nil") when(stub.subSubMethod()).thenReturn("not nil")
// sub-class // sub-class
// when(stub.withImplicitlyUnwrappedOptional(i: anyInt())).thenReturn("implicit unwrapped return") when(stub.withImplicitlyUnwrappedOptional(i: anyInt())).thenReturn("implicit unwrapped return")
// when(stub.withThrows()).thenReturn(10) when(stub.withThrows()).thenReturn(10)
// when(stub.withNamedTuple(tuple: any())).thenReturn(11) when(stub.withNamedTuple(tuple: any())).thenReturn(11)
// when(stub.subclassMethod()).thenReturn(12) when(stub.subclassMethod()).thenReturn(12)
// when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2) // when(stub.withOptionalClosureAndReturn(anyString(), closure: isNil())).thenReturn(2)
// when(stub.withClosureAndParam(anyString(), closure: anyClosure())).thenReturn(3) when(stub.withClosureAndParam(anyString(), closure: anyClosure())).thenReturn(3)
// when(stub.withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())).thenReturn(4) when(stub.withMultClosures(closure: anyClosure(), closureB: anyClosure(), closureC: anyClosure())).thenReturn(4)
// when(stub.withThrowingClosure(closure: anyThrowingClosure())).thenReturn("throwing closure") when(stub.withThrowingClosure(closure: anyThrowingClosure())).thenReturn("throwing closure")
// when(stub.withThrowingClosureThrows(closure: anyThrowingClosure())).thenReturn("closure throwing") when(stub.withThrowingClosureThrows(closure: anyThrowingClosure())).thenReturn("closure throwing")
// when(stub.withThrowingEscapingClosure(closure: anyThrowingClosure())).thenReturn("escaping closure") when(stub.withThrowingEscapingClosure(closure: anyThrowingClosure())).thenReturn("escaping closure")
// when(stub.withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())).thenReturn("optional closure throwing") // when(stub.withThrowingOptionalClosureThrows(closure: anyOptionalThrowingClosure())).thenReturn("optional closure throwing")
when(stub.methodWithParameter(anyString())).thenReturn("parameter string") when(stub.methodWithParameter(anyString())).thenReturn("parameter string")
when(stub.methodWithParameter(anyInt())).thenReturn("parameter int") when(stub.methodWithParameter(anyInt())).thenReturn("parameter int")
@ -181,9 +181,9 @@ class StubbingTest: XCTestCase {
when(stub.withEscape(anyString(), action: anyClosure())).then { _ in when(stub.withEscape(anyString(), action: anyClosure())).then { _ in
callWithEscape = true callWithEscape = true
} }
// when(stub.withOptionalClosure(anyString(), closure: anyClosure())).then { _ in when(stub.withOptionalClosure(anyString(), closure: anyClosure())).then { _ in
// callWithOptionalClosure = true callWithOptionalClosure = true
// } }
when(stub.withLabelAndUnderscore(labelA: anyString(), anyString())).then { _ in when(stub.withLabelAndUnderscore(labelA: anyString(), anyString())).then { _ in
callWithLabelAndUnderscore = true callWithLabelAndUnderscore = true
} }