diff --git a/Sources/MockoloFramework/Utils/StringExtensions.swift b/Sources/MockoloFramework/Utils/StringExtensions.swift index 3e24050f..5a0025b6 100644 --- a/Sources/MockoloFramework/Utils/StringExtensions.swift +++ b/Sources/MockoloFramework/Utils/StringExtensions.swift @@ -119,6 +119,7 @@ extension String { static let arrayTypeSugarName = "[]" static let dictionaryTypeSugarName = "[:]" static let optionalTypeSugarName = "?" + static let optionalAutounwrappedTypeSugarName = "!" var safeName: String { var text = self diff --git a/Sources/MockoloFramework/Utils/SwiftType.swift b/Sources/MockoloFramework/Utils/SwiftType.swift index d1165cb2..3de21ced 100644 --- a/Sources/MockoloFramework/Utils/SwiftType.swift +++ b/Sources/MockoloFramework/Utils/SwiftType.swift @@ -57,7 +57,6 @@ struct SwiftType: Equatable, CustomStringConvertible { var kind: Kind var attributes: [String] = [] var someOrAny: SomeOrAny? - var isIUO: Bool = false var hasEllipsis: Bool = false var typeName: String { @@ -88,6 +87,8 @@ struct SwiftType: Equatable, CustomStringConvertible { switch nominal.name { case .optionalTypeSugarName where nominal.genericParameterTypes.count == 1: repr += "\(nominal.genericParameterTypes[0])?" + case .optionalAutounwrappedTypeSugarName where nominal.genericParameterTypes.count == 1: + repr += "\(nominal.genericParameterTypes[0])!" case .arrayTypeSugarName where nominal.genericParameterTypes.count == 1: repr += "[\(nominal.genericParameterTypes[0])]" case .dictionaryTypeSugarName where nominal.genericParameterTypes.count == 2: @@ -121,14 +122,6 @@ struct SwiftType: Equatable, CustomStringConvertible { case .composition(let composition): repr += composition.elements.map(\.description).joined(separator: " & ") } - if isIUO { - switch kind { - case .tuple, .nominal: - repr += "!" - case .closure, .composition: - repr = "(\(repr))!" - } - } if hasEllipsis { repr += "..." } @@ -191,7 +184,11 @@ struct SwiftType: Equatable, CustomStringConvertible { } var isOptional: Bool { - isNominal(named: .optional) || isNominal(named: .optionalTypeSugarName) + isNominal(named: .optional) || isNominal(named: .optionalTypeSugarName) || isNominal(named: .optionalAutounwrappedTypeSugarName) + } + + var isIUO: Bool { + isNominal(named: .optionalAutounwrappedTypeSugarName) } var isSelf: Bool { @@ -286,9 +283,8 @@ struct SwiftType: Equatable, CustomStringConvertible { ret = unwrapped } } - ret.isIUO = true - return ret.description + return ret.iuoWrapped().description } func defaultVal(with overrides: [String: String]? = nil, overrideKey: String = "", isInitParam: Bool = false) -> String? { @@ -343,7 +339,7 @@ struct SwiftType: Equatable, CustomStringConvertible { var processedType = subtype.processTypeParams(with: typeParams) processedType.attributes.removeAll(where: { $0 == .inout }) processedType.attributes.removeAll(where: { $0 == .escaping }) - processedType.isIUO = false + processedType.unwrapIUO() return processedType } @@ -456,10 +452,9 @@ struct SwiftType: Equatable, CustomStringConvertible { returnAsType = unwrapped asSuffix = "?" } else if returnType.isIUO { - displayableReturnType = .Any - displayableReturnType.isIUO = true + displayableReturnType = .Any.iuoWrapped() var asType = returnType - asType.isIUO = false + asType.unwrapIUO() returnAsType = asType } else if returnType.isSelf { returnAsType = .Self @@ -476,11 +471,18 @@ struct SwiftType: Equatable, CustomStringConvertible { returnTypeCast = " as! " + .`Self` } + // `!` (IUO) is illegal inside a closure type, so an IUO param/return must render as a plain + // optional here (it still witnesses the IUO requirement). + displayableReturnType.unwrapIUO() var resultType = SwiftType( kind: .closure(.init( isAsync: isAsync, throwing: throwing, - arguments: params.map { .init(type: $0.processTypeParams(with: typeParams)) }, + arguments: params.map { + var argType = $0.processTypeParams(with: typeParams) + argType.unwrapIUO() + return .init(type: argType) + }, returning: displayableReturnType )) ) @@ -630,8 +632,7 @@ extension SwiftType { case .implicitlyUnwrappedOptionalType(let syntax): // T! let base = SwiftType(typeSyntax: syntax.wrappedType) - self = base.optionalWrapped() - self.isIUO = true + self = base.iuoWrapped() case .identifierType(let syntax): // T @@ -720,6 +721,12 @@ extension SwiftType { ) } + func iuoWrapped() -> SwiftType { + return copy( + kind: .nominal(.init(name: .optionalAutounwrappedTypeSugarName, genericParameterTypes: [.init(kind: kind)])) + ) + } + func optionalUnwrapped() -> SwiftType? { guard isOptional else { return nil @@ -730,6 +737,17 @@ extension SwiftType { return nominal.genericParameterTypes.first } + mutating func unwrapIUO() { + guard isNominal(named: .optionalAutounwrappedTypeSugarName) else { + return + } + guard case .nominal(let nominal) = kind, + let wrapped = nominal.genericParameterTypes.first else { + return + } + self = wrapped + } + func copy(kind: Kind) -> Self { var copy = self copy.kind = kind diff --git a/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift b/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift index 31c14c37..b1afa7e9 100644 --- a/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift +++ b/Tests/TestFuncs/TestBasicFuncs/FixtureNonSimpleFuncs.swift @@ -35,6 +35,40 @@ import MockoloFramework } } +@Fixture enum iuoFuncs { + /// @mockable + public protocol IUOFunc { + func f(x: Int!) -> Int! + subscript(i: Int!) -> Int! { get } + } + + @Fixture enum expected { + public class IUOFuncMock: IUOFunc { + public init() { } + public private(set) var fCallCount = 0 + public var fHandler: ((Int) -> Int)? + public func f(x: Int!) -> Int! { + fCallCount += 1 + if let fHandler = fHandler { + return fHandler(x) + } + return nil + } + public private(set) var subscriptCallCount = 0 + public var subscriptHandler: ((Int) -> Int)? + public subscript(i: Int!) -> Int! { + get { + subscriptCallCount += 1 + if let subscriptHandler = subscriptHandler { + return subscriptHandler(i) + } + return nil + } + } + } + } +} + @Fixture enum subscripts { /// @mockable protocol SubscriptProtocol { diff --git a/Tests/TestFuncs/TestBasicFuncs/FuncBasicTests.swift b/Tests/TestFuncs/TestBasicFuncs/FuncBasicTests.swift index ea5f0804..1af22d77 100644 --- a/Tests/TestFuncs/TestBasicFuncs/FuncBasicTests.swift +++ b/Tests/TestFuncs/TestBasicFuncs/FuncBasicTests.swift @@ -9,6 +9,11 @@ class BasicFuncTests: MockoloTestCase { dstContent: subscripts.expected._source) } + func testIUOFuncs() { + verify(srcContent: iuoFuncs._source, + dstContent: iuoFuncs.expected._source) + } + func testGetOnlySubscripts() { verify(srcContent: getOnlySubscripts._source, dstContent: getOnlySubscripts.expected._source) diff --git a/Tests/TestVars/FixtureNonSimpleVars.swift b/Tests/TestVars/FixtureNonSimpleVars.swift index 05330f31..821bfeb2 100644 --- a/Tests/TestVars/FixtureNonSimpleVars.swift +++ b/Tests/TestVars/FixtureNonSimpleVars.swift @@ -47,3 +47,24 @@ import MockoloFramework } } } + +@Fixture enum iuoVars { + /// @mockable + public protocol IUOVar { + var a: Int! { get } + var b: Int! { get set } + } + + @Fixture enum expected { + public class IUOVarMock: IUOVar { + public init() { } + public init(a: Int! = nil, b: Int! = nil) { + self.a = a + self.b = b + } + public var a: Int! = nil + public private(set) var bSetCallCount = 0 + public var b: Int! = nil { didSet { bSetCallCount += 1 } } + } + } +} diff --git a/Tests/TestVars/VarTests.swift b/Tests/TestVars/VarTests.swift index bb510f91..28a6b8bb 100644 --- a/Tests/TestVars/VarTests.swift +++ b/Tests/TestVars/VarTests.swift @@ -21,6 +21,11 @@ class VarTests: MockoloTestCase { allowSetCallCount: true) } + func testIUOVars() { + verify(srcContent: iuoVars._source, + dstContent: iuoVars.expected._source) + } + #if compiler(>=6.0) func testAsyncThrows() { verify(srcContent: asyncThrowsVars._source,