Skip to content

Commit

Permalink
Fix optional members in Obj-C protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewchang-bird committed Jan 28, 2022
1 parent 9d5e886 commit 8fef295
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 22 deletions.
8 changes: 8 additions & 0 deletions Mockingbird.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@
28843B2726AE710400AFB8DF /* MKBTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 28843B2526AE710400AFB8DF /* MKBTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; };
28843B2826AE710400AFB8DF /* MKBTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 28843B2626AE710400AFB8DF /* MKBTestUtils.m */; };
28843B2A26AE77E800AFB8DF /* InlinePropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28843B2926AE77E800AFB8DF /* InlinePropertyTests.swift */; };
288678E6279B9C25004FFB3D /* ObjCProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288678E5279B9C25004FFB3D /* ObjCProtocol.swift */; };
288678E8279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 288678E7279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift */; };
28874F8B26BF12DD00097529 /* ArgumentParser in Frameworks */ = {isa = PBXBuildFile; productRef = 28874F8A26BF12DD00097529 /* ArgumentParser */; };
28874F8D26BF7AB000097529 /* Configure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F8C26BF7AB000097529 /* Configure.swift */; };
28874F8F26BF7C3C00097529 /* XcodeProjPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */; };
Expand Down Expand Up @@ -645,6 +647,8 @@
28843B2526AE710400AFB8DF /* MKBTestUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MKBTestUtils.h; sourceTree = "<group>"; };
28843B2626AE710400AFB8DF /* MKBTestUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MKBTestUtils.m; sourceTree = "<group>"; };
28843B2926AE77E800AFB8DF /* InlinePropertyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePropertyTests.swift; sourceTree = "<group>"; };
288678E5279B9C25004FFB3D /* ObjCProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjCProtocol.swift; sourceTree = "<group>"; };
288678E7279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectiveCProtocolTests.swift; sourceTree = "<group>"; };
28874F8C26BF7AB000097529 /* Configure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configure.swift; sourceTree = "<group>"; };
28874F8E26BF7C3C00097529 /* XcodeProjPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeProjPath.swift; sourceTree = "<group>"; };
28874F9026BF7FA400097529 /* ValidatableArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatableArgument.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1562,6 +1566,7 @@
28A1F3C326ADD57C002F282D /* MinimalTestTypes.swift */,
OBJ_196 /* ModuleImportCases.swift */,
28D08CCF277477F700AE7C39 /* ObjCParameters.swift */,
288678E5279B9C25004FFB3D /* ObjCProtocol.swift */,
OBJ_198 /* OpaquelyInheritedTypes.swift */,
OBJ_199 /* Optionals.swift */,
OBJ_200 /* OverloadedMethods.swift */,
Expand Down Expand Up @@ -1745,6 +1750,7 @@
OBJ_261 /* LastSetValueStubTests.swift */,
28719AF426B23AB200C38C2C /* ObjectiveCTests.swift */,
28D08CD1277479B600AE7C39 /* ObjectiveCParameterTests.swift */,
288678E7279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift */,
28D08CCD2774247C00AE7C39 /* OptionalsTests.swift */,
OBJ_262 /* OrderedVerificationTests.swift */,
OBJ_263 /* OverloadedMethodTests.swift */,
Expand Down Expand Up @@ -2555,6 +2561,7 @@
OBJ_1066 /* InitializerTests.swift in Sources */,
OBJ_1067 /* LastSetValueStubTests.swift in Sources */,
OBJ_1068 /* OrderedVerificationTests.swift in Sources */,
288678E8279B9C5E004FFB3D /* ObjectiveCProtocolTests.swift in Sources */,
OBJ_1069 /* OverloadedMethodTests.swift in Sources */,
OBJ_1070 /* SequentialValueStubbingTests.swift in Sources */,
OBJ_1071 /* StubbingTests.swift in Sources */,
Expand Down Expand Up @@ -2597,6 +2604,7 @@
OBJ_1136 /* Extensions.swift in Sources */,
OBJ_1137 /* ExternalModuleClassScopedTypes.swift in Sources */,
OBJ_1138 /* ExternalModuleImplicitlyImportedTypes.swift in Sources */,
288678E6279B9C25004FFB3D /* ObjCProtocol.swift in Sources */,
OBJ_1139 /* ExternalModuleTypealiasing.swift in Sources */,
5E45ADF7279DB9A9004AB972 /* AsyncMethods.swift in Sources */,
OBJ_1140 /* ExternalModuleTypes.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,11 @@ class MethodTemplate: Template {
isAsync: method.isAsync,
isThrowing: method.isThrowing,
isStatic: method.kind.typeScope.isStatic,
isOptional: method.attributes.contains(.optional),
callMember: { scope in
let scopedName = "\(scope).\(backticked: self.method.shortName)"
let optionalPostfix = self.method.attributes.contains(.optional) ? "?" : ""
let scopedName =
"\(scope).\(backticked: self.method.shortName)\(optionalPostfix)"
guard self.method.isVariadic else {
return FunctionCallTemplate(
name: scopedName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class SubscriptMethodTemplate: MethodTemplate {
isAsync: method.isAsync,
isThrowing: method.isThrowing,
isStatic: method.kind.typeScope.isStatic,
isOptional: method.attributes.contains(.optional),
callMember: { scope in
return "\(scope)[\(separated: callArguments)]"
},
Expand All @@ -71,7 +72,13 @@ class SubscriptMethodTemplate: MethodTemplate {
isAsync: method.isAsync,
isThrowing: method.isThrowing,
isStatic: method.kind.typeScope.isStatic,
isOptional: method.attributes.contains(.optional),
callMember: { scope in
guard !self.method.attributes.contains(.optional) else {
// Optional readwrite subscripts cannot be assigned to:
// Cannot assign through subscript: 'object' is immutable
return ""
}
return "\(scope)[\(separated: callArguments)] = newValue"
},
invocationArguments: setterInvocationArguments).render())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ThunkTemplate: Template {
let isAsync: Bool
let isThrowing: Bool
let isStatic: Bool
let isOptional: Bool
let callMember: (_ scope: Scope) -> String
let invocationArguments: [(argumentLabel: String?, parameterName: String)]

Expand All @@ -33,6 +34,7 @@ class ThunkTemplate: Template {
isAsync: Bool,
isThrowing: Bool,
isStatic: Bool,
isOptional: Bool,
callMember: @escaping (_ scope: Scope) -> String,
invocationArguments: [(argumentLabel: String?, parameterName: String)]) {
self.mockableType = mockableType
Expand All @@ -44,6 +46,7 @@ class ThunkTemplate: Template {
self.isAsync = isAsync
self.isThrowing = isThrowing
self.isStatic = isStatic
self.isOptional = isOptional
self.callMember = callMember
self.invocationArguments = invocationArguments
}
Expand Down Expand Up @@ -100,6 +103,11 @@ class ThunkTemplate: Template {
])) as \(returnType)
""").render()
}()
let callProxyObject: String = {
let objectInvocation = callMember(.object)
guard !objectInvocation.isEmpty else { return "" }
return "let mkbValue: \(returnType) = \(objectInvocation)"
}()

let supertype = isStatic ? "MockingbirdSupertype.Type" : "MockingbirdSupertype"
let didInvoke = FunctionCallTemplate(name: "self.mockingbirdContext.mocking.didInvoke",
Expand Down Expand Up @@ -131,15 +139,17 @@ class ThunkTemplate: Template {
controlExpression: "mkbTargetBox.target",
cases: [
(".super", isSubclass ? "break" : "return \(callMember(.super))"),
(".object" + (isProxyable ? "(let mkbObject)" : ""), !isProxyable ? "break" : """
\(GuardStatementTemplate(
condition: "var mkbObject = mkbObject as? \(supertype)", body: "break"))
let mkbValue: \(returnType) = \(callMember(.object))
\(FunctionCallTemplate(
(".object" + (isProxyable ? "(let mkbObject)" : ""), !isProxyable ? "break" :
String(lines: [
GuardStatementTemplate(
condition: "var mkbObject = mkbObject as? \(supertype)", body: "break").render(),
!isOptional || callProxyObject.isEmpty ? callProxyObject :
GuardStatementTemplate(condition: callProxyObject, body: "break").render(),
FunctionCallTemplate(
name: "self.mockingbirdContext.proxy.updateTarget",
arguments: [(nil, "&mkbObject"), ("in", "mkbTargetBox")]))
return mkbValue
""")
arguments: [(nil, "&mkbObject"), ("in", "mkbTargetBox")]).render(),
callProxyObject.isEmpty ? "" : "return mkbValue",
])),
]).render()).render(),
]))
\(IfStatementTemplate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class VariableTemplate: Template {
isAsync: false,
isThrowing: false,
isStatic: variable.kind.typeScope.isStatic,
isOptional: variable.attributes.contains(.optional),
callMember: { scope in
return "\(scope).\(backticked: self.variable.name)"
},
Expand All @@ -90,7 +91,13 @@ class VariableTemplate: Template {
isAsync: false,
isThrowing: false,
isStatic: variable.kind.typeScope.isStatic,
isOptional: variable.attributes.contains(.optional),
callMember: { scope in
guard !self.variable.attributes.contains(.optional) else {
// Optional readwrite properties cannot be assigned to:
// `Cannot assign to property: 'object' is immutable`
return ""
}
return "\(scope).\(backticked: self.variable.name) = newValue"
},
invocationArguments: [(nil, "newValue")]).render())
Expand Down
26 changes: 14 additions & 12 deletions Sources/MockingbirdGenerator/Parser/Models/TypeAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,23 @@ struct Attributes: OptionSet, Hashable {
static let convenience = Attributes(rawValue: 1 << 5)
static let override = Attributes(rawValue: 1 << 6)
static let objcName = Attributes(rawValue: 1 << 7)
static let `optional` = Attributes(rawValue: 1 << 8)

// MARK: Inferred attributes
static let constant = Attributes(rawValue: 1 << 8)
static let readonly = Attributes(rawValue: 1 << 9)
static let async = Attributes(rawValue: 1 << 10)
static let `throws` = Attributes(rawValue: 1 << 11)
static let `inout` = Attributes(rawValue: 1 << 12)
static let variadic = Attributes(rawValue: 1 << 13)
static let failable = Attributes(rawValue: 1 << 14)
static let unwrappedFailable = Attributes(rawValue: 1 << 15)
static let closure = Attributes(rawValue: 1 << 16)
static let escaping = Attributes(rawValue: 1 << 17)
static let autoclosure = Attributes(rawValue: 1 << 18)
static let constant = Attributes(rawValue: 1 << 9)
static let readonly = Attributes(rawValue: 1 << 10)
static let async = Attributes(rawValue: 1 << 11)
static let `throws` = Attributes(rawValue: 1 << 12)
static let `inout` = Attributes(rawValue: 1 << 13)
static let variadic = Attributes(rawValue: 1 << 14)
static let failable = Attributes(rawValue: 1 << 15)
static let unwrappedFailable = Attributes(rawValue: 1 << 16)
static let closure = Attributes(rawValue: 1 << 17)
static let escaping = Attributes(rawValue: 1 << 18)
static let autoclosure = Attributes(rawValue: 1 << 19)

// MARK: Custom attributes
static let implicit = Attributes(rawValue: 1 << 19)
static let implicit = Attributes(rawValue: 1 << 20)

static let attributesKey = "key.attributes"
static let attributeKey = "key.attribute"
Expand All @@ -121,6 +122,7 @@ extension Attributes {
case .convenience: self = .convenience
case .override: self = .override
case .objcName: self = .objcName
case .`optional`: self = .`optional`
default: return nil
}
}
Expand Down
23 changes: 23 additions & 0 deletions Sources/MockingbirdTestsHost/ObjCProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Foundation

@objc protocol ObjCProtocol: Foundation.NSObjectProtocol {
@objc func trivial()
@objc func parameterizedReturning(param: String) -> Bool

@objc var property: Bool { get }
@objc var readwriteProperty: Bool { get set }

// It’s possible to define Obj-C protocols with overloaded subscript requirements, but it can
// never be implemented in Swift as the compiler will complain about the conflicting selectors.
// @objc subscript(param: Int) -> Int { get set }

// MARK: Optional

@objc optional func optionalTrivial()
@objc optional func optionalParameterizedReturning(param: String) -> Bool

@objc optional var optionalProperty: Bool { get }
@objc optional var optionalReadwriteProperty: Bool { get set }

@objc optional subscript(param: Int) -> Bool { get set }
}
Loading

0 comments on commit 8fef295

Please sign in to comment.