Add support for inheritance generic parameters in methods.

This commit is contained in:
Matyáš Kříž 2019-03-20 20:31:45 +01:00
parent 807dc8953b
commit a2f9e68e72
11 changed files with 65 additions and 25 deletions

View File

@ -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 {

View File

@ -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}}),

View File

@ -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 %}

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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..<bodyRange.startIndex].takeUntil(occurence: "{")?.trimmed ?? ""
// } else {
// returnSignature = source.utf8[nameRange!.endIndex..<range!.endIndex].trimmed
// if returnSignature.isEmpty {
// let untilThrows = String(source.utf8.dropFirst(nameRange!.endIndex))?
// .takeUntil(occurence: "throws").map { $0 + "throws" }?
// .trimmed
// if let untilThrows = untilThrows, untilThrows == "throws" || untilThrows == "rethrows" {
// returnSignature = "\(untilThrows)"
// }
// }
// }
// When bodyRange != nil, we need to create .ClassMethod instead of .ProtocolMethod
if let bodyRange = bodyRange {
@ -204,7 +221,9 @@ public struct Tokenizer {
nameRange: nameRange!,
parameters: parameters,
bodyRange: bodyRange,
attributes: attributes)
attributes: attributes,
genericParameters: genericParameters,
whereConstraints: whereConstraints)
} else {
return ProtocolMethod(
name: name,
@ -214,7 +233,9 @@ public struct Tokenizer {
range: range!,
nameRange: nameRange!,
parameters: parameters,
attributes: attributes)
attributes: attributes,
genericParameters: genericParameters,
whereConstraints: whereConstraints)
}
case Kinds.GenericParameter.rawValue:

View File

@ -16,6 +16,8 @@ public struct ClassMethod: Method {
public var parameters: [MethodParameter]
public var bodyRange: CountableRange<Int>
public var attributes: [Attribute]
public var genericParameters: [GenericParameter]
public var whereConstraints: [String]
public var isOptional: Bool {
return false
}

View File

@ -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

View File

@ -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: ", "))"
]
}
}

View File

@ -15,6 +15,8 @@ public struct ProtocolMethod: Method {
public var nameRange: CountableRange<Int>
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)

View File

@ -201,6 +201,15 @@ final class FinalClass {
var shouldBeIgnoredByCuckoo = true
}
protocol GenericFunctionProtocol {
func method<T>(param: T)
}
class GenericFunctionClass {
func method<T>(param: T) where T: CustomStringConvertible {
}
}
public class InternalFieldsInPublicClass {
internal var field: Int? = nil