From a2f9e68e72b13cd863f6140e4dcb53dea7c3e1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matya=CC=81s=CC=8C=20Kr=CC=8Ci=CC=81z=CC=8C?= Date: Wed, 20 Mar 2019 20:31:45 +0100 Subject: [PATCH] Add support for inheritance generic parameters in methods. --- .../CuckooGeneratorFramework/Generator.swift | 34 ++++++++----------- .../Templates/MockTemplate.swift | 2 +- .../Templates/NopImplStubTemplate.swift | 2 +- .../Templates/StubbingProxyTemplate.swift | 2 +- .../Templates/VerificationProxyTemplate.swift | 2 +- .../CuckooGeneratorFramework/Tokenizer.swift | 25 ++++++++++++-- .../Tokens/ClassMethod.swift | 2 ++ .../Tokens/Initializer.swift | 2 ++ .../Tokens/Method.swift | 8 +++++ .../Tokens/ProtocolMethod.swift | 2 ++ Tests/Source/TestedClass.swift | 9 +++++ 11 files changed, 65 insertions(+), 25 deletions(-) diff --git a/Generator/Source/CuckooGeneratorFramework/Generator.swift b/Generator/Source/CuckooGeneratorFramework/Generator.swift index 47edccd..8f06331 100644 --- a/Generator/Source/CuckooGeneratorFramework/Generator.swift +++ b/Generator/Source/CuckooGeneratorFramework/Generator.swift @@ -28,13 +28,13 @@ public struct Generator { } ext.registerFilter("matchableGenericNames") { (value: Any?) in - guard let parameters = value as? [MethodParameter] else { return value } - return self.matchableGenerics(with: parameters) + guard let method = value as? Method else { return value } + return self.matchableGenericTypes(from: method) } - ext.registerFilter("matchableGenericWhere") { (value: Any?) in - guard let parameters = value as? [MethodParameter] else { return value } - return self.matchableGenerics(where: parameters) + ext.registerFilter("matchableGenericWhereClause") { (value: Any?) in + guard let method = value as? Method else { return value } + return self.matchableGenericsWhereClause(from: method) } ext.registerFilter("matchableParameterSignature") { (value: Any?) in @@ -68,24 +68,20 @@ public struct Generator { return try environment.renderTemplate(string: Templates.mock, context: ["containers": containers, "debug": debug]) } - private func matchableGenerics(with parameters: [MethodParameter]) -> String { - guard parameters.isEmpty == false else { return "" } + private func matchableGenericTypes(from method: Method) -> String { + guard method.parameters.isEmpty == false else { return "" } - 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)>" + let matchableGenericParameters = (1...method.parameters.count).map { "M\($0): Cuckoo.Matchable" } + let methodGenericParameters = method.genericParameters.map { $0.description } + return "<\((matchableGenericParameters + methodGenericParameters).joined(separator: ", "))>" } - private func matchableGenerics(where parameters: [MethodParameter]) -> String { - guard parameters.isEmpty == false else { return "" } + private func matchableGenericsWhereClause(from method: Method) -> String { + guard method.parameters.isEmpty == false else { return "" } - 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)" + let matchableWhereConstraints = method.parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.typeWithoutAttributes))" } + let methodWhereConstraints = method.whereConstraints + return " where \((matchableWhereConstraints + methodWhereConstraints).joined(separator: ", "))" } private func matchableParameterSignature(with parameters: [MethodParameter]) -> String { diff --git a/Generator/Source/CuckooGeneratorFramework/Templates/MockTemplate.swift b/Generator/Source/CuckooGeneratorFramework/Templates/MockTemplate.swift index cf3b0dc..898f27f 100644 --- a/Generator/Source/CuckooGeneratorFramework/Templates/MockTemplate.swift +++ b/Generator/Source/CuckooGeneratorFramework/Templates/MockTemplate.swift @@ -102,7 +102,7 @@ extension Templates { {% for attribute in method.attributes %} {{ attribute.text }} {% endfor %} - {{ method.accessibility }}{% if container.isImplementation and method.isOverriding %} override{% endif %} func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} { + {{ method.accessibility }}{% if container.isImplementation and method.isOverriding %} override{% endif %} func {{ method.name }}{{ method.genericParameters }}({{ method.parameterSignature }}) {{ method.returnSignature }} {{ method.whereClause }} { {{ method.parameters|openNestedClosure:method.isThrowing }} return{% if method.isThrowing %} try{% endif %} cuckoo_manager.call{% if method.isThrowing %}Throws{% endif %}("{{method.fullyQualifiedName}}", parameters: ({{method.parameterNames}}), diff --git a/Generator/Source/CuckooGeneratorFramework/Templates/NopImplStubTemplate.swift b/Generator/Source/CuckooGeneratorFramework/Templates/NopImplStubTemplate.swift index 9ad2bde..ca3c7ac 100644 --- a/Generator/Source/CuckooGeneratorFramework/Templates/NopImplStubTemplate.swift +++ b/Generator/Source/CuckooGeneratorFramework/Templates/NopImplStubTemplate.swift @@ -28,7 +28,7 @@ extension Templates { {% endfor %} {% for method in container.methods %} - {{ method.accessibility }}{% if container.@type == "ClassDeclaration" and method.isOverriding %} override{% endif %} func {{ method.name }}({{ method.parameterSignature }}) {{ method.returnSignature }} { + {{ method.accessibility }}{% if container.@type == "ClassDeclaration" and method.isOverriding %} override{% endif %} func {{ method.name }}{{ method.genericParameters }}({{ method.parameterSignature }}) {{ method.returnSignature }} {{ method.whereClause }} { return DefaultValueRegistry.defaultValue(for: {{method.returnType|genericSafe}}.self) } {% endfor %} diff --git a/Generator/Source/CuckooGeneratorFramework/Templates/StubbingProxyTemplate.swift b/Generator/Source/CuckooGeneratorFramework/Templates/StubbingProxyTemplate.swift index f4d878d..b30329e 100644 --- a/Generator/Source/CuckooGeneratorFramework/Templates/StubbingProxyTemplate.swift +++ b/Generator/Source/CuckooGeneratorFramework/Templates/StubbingProxyTemplate.swift @@ -21,7 +21,7 @@ extension Templates { } {% endfor %} {% for method in container.methods %} - func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.inputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.parameters|matchableGenericWhere}} { + func {{method.name}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> {{method.stubFunction}}<({{method.inputTypes|genericSafe}}){%if method.returnType != "Void" %}, {{method.returnType|genericSafe}}{%endif%}>{{method.self|matchableGenericWhereClause}} { {{method.parameters|parameterMatchers}} return .init(stub: cuckoo_manager.createStub(for: {{ container.mockName }}.self, method: "{{method.fullyQualifiedName}}", parameterMatchers: matchers)) } diff --git a/Generator/Source/CuckooGeneratorFramework/Templates/VerificationProxyTemplate.swift b/Generator/Source/CuckooGeneratorFramework/Templates/VerificationProxyTemplate.swift index af30d80..41f9266 100644 --- a/Generator/Source/CuckooGeneratorFramework/Templates/VerificationProxyTemplate.swift +++ b/Generator/Source/CuckooGeneratorFramework/Templates/VerificationProxyTemplate.swift @@ -28,7 +28,7 @@ extension Templates { {% for method in container.methods %} @discardableResult - func {{method.name}}{{method.parameters|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<({{method.inputTypes|genericSafe}}), {{method.returnType|genericSafe}}>{{method.parameters|matchableGenericWhere}} { + func {{method.name}}{{method.self|matchableGenericNames}}({{method.parameters|matchableParameterSignature}}) -> Cuckoo.__DoNotUse<{{method.returnType|genericSafe}}>{{method.self|matchableGenericWhereClause}} { {{method.parameters|parameterMatchers}} return cuckoo_manager.verify("{{method.fullyQualifiedName}}", callMatcher: callMatcher, parameterMatchers: matchers, sourceLocation: sourceLocation) } diff --git a/Generator/Source/CuckooGeneratorFramework/Tokenizer.swift b/Generator/Source/CuckooGeneratorFramework/Tokenizer.swift index f051588..fcea630 100644 --- a/Generator/Source/CuckooGeneratorFramework/Tokenizer.swift +++ b/Generator/Source/CuckooGeneratorFramework/Tokenizer.swift @@ -192,6 +192,23 @@ public struct Tokenizer { returnTypeString = "Void" } let returnType = WrappableType(parsing: returnTypeString) + let genericParameters = tokenize(dictionary[Key.Substructure.rawValue] as? [SourceKitRepresentable] ?? []).only(GenericParameter.self) + + // TODO: add support for where constraints + let whereConstraints: [String] = [] +// if let bodyRange = bodyRange { +// returnSignature = source.utf8[nameRange!.endIndex.. public var attributes: [Attribute] + public var genericParameters: [GenericParameter] + public var whereConstraints: [String] public var isOptional: Bool { return false } diff --git a/Generator/Source/CuckooGeneratorFramework/Tokens/Initializer.swift b/Generator/Source/CuckooGeneratorFramework/Tokens/Initializer.swift index ae5066c..1f01703 100644 --- a/Generator/Source/CuckooGeneratorFramework/Tokens/Initializer.swift +++ b/Generator/Source/CuckooGeneratorFramework/Tokens/Initializer.swift @@ -17,6 +17,8 @@ public struct Initializer: Method, HasAccessibility { public var isOverriding: Bool public var required: Bool public var attributes: [Attribute] + public var genericParameters: [GenericParameter] + public var whereConstraints: [String] public var isOptional: Bool { return false diff --git a/Generator/Source/CuckooGeneratorFramework/Tokens/Method.swift b/Generator/Source/CuckooGeneratorFramework/Tokens/Method.swift index 33b400a..b55cdfd 100644 --- a/Generator/Source/CuckooGeneratorFramework/Tokens/Method.swift +++ b/Generator/Source/CuckooGeneratorFramework/Tokens/Method.swift @@ -19,6 +19,8 @@ public protocol Method: Token, HasAccessibility { var hasClosureParams: Bool { get } var hasOptionalParams: Bool { get } var attributes: [Attribute] { get } + var genericParameters: [GenericParameter] { get } + var whereConstraints: [String] { get } } public extension Method { @@ -95,7 +97,11 @@ public extension Method { } }.joined(separator: ", ") + let genericParametersString = genericParameters.map { $0.description }.joined(separator: ", ") + let isGeneric = !genericParameters.isEmpty + return [ + "self": self, "name": rawName, "accessibility": accessibility.sourceName, "returnSignature": returnSignature, @@ -117,6 +123,8 @@ public extension Method { "hasClosureParams": hasClosureParams, "hasOptionalParams": hasOptionalParams, "attributes": attributes.filter { $0.isSupported }, + "genericParameters": isGeneric ? "<\(genericParametersString)>" : "", + "whereClause": whereConstraints.isEmpty ? "" : "where \(whereConstraints.joined(separator: ", "))" ] } } diff --git a/Generator/Source/CuckooGeneratorFramework/Tokens/ProtocolMethod.swift b/Generator/Source/CuckooGeneratorFramework/Tokens/ProtocolMethod.swift index cac4595..6617323 100644 --- a/Generator/Source/CuckooGeneratorFramework/Tokens/ProtocolMethod.swift +++ b/Generator/Source/CuckooGeneratorFramework/Tokens/ProtocolMethod.swift @@ -15,6 +15,8 @@ public struct ProtocolMethod: Method { public var nameRange: CountableRange public var parameters: [MethodParameter] public var attributes: [Attribute] + public var genericParameters: [GenericParameter] + public var whereConstraints: [String] public var isOptional: Bool { return attributes.map { $0.kind }.contains(.optional) diff --git a/Tests/Source/TestedClass.swift b/Tests/Source/TestedClass.swift index 20ba544..a2166a8 100644 --- a/Tests/Source/TestedClass.swift +++ b/Tests/Source/TestedClass.swift @@ -201,6 +201,15 @@ final class FinalClass { var shouldBeIgnoredByCuckoo = true } +protocol GenericFunctionProtocol { + func method(param: T) +} + +class GenericFunctionClass { + func method(param: T) where T: CustomStringConvertible { + + } +} public class InternalFieldsInPublicClass { internal var field: Int? = nil