From f8ff5eea554e8ad8ac3948f06a3e7f37958952bd Mon Sep 17 00:00:00 2001 From: Simon Feistel Date: Thu, 11 Sep 2025 10:40:54 +0200 Subject: [PATCH 1/6] Support typed throws for functions --- .../SpyableMacro/Factories/SpyFactory.swift | 7 ++++++- .../Factories/ThrowableErrorFactory.swift | 10 +++++++++- .../Macro/UT_SpyableMacro.swift | 20 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Sources/SpyableMacro/Factories/SpyFactory.swift b/Sources/SpyableMacro/Factories/SpyFactory.swift index 4634c94..82b36d0 100644 --- a/Sources/SpyableMacro/Factories/SpyFactory.swift +++ b/Sources/SpyableMacro/Factories/SpyFactory.swift @@ -161,12 +161,17 @@ struct SpyFactory { #if canImport(SwiftSyntax600) let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsClause? .throwsSpecifier + let throwsType = functionDeclaration.signature.effectSpecifiers?.throwsClause?.type #else let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsSpecifier #endif if throwsSpecifier != nil { - try throwableErrorFactory.variableDeclaration(variablePrefix: variablePrefix) + if let typeSpecifier = throwsType?.description { + try throwableErrorFactory.typedVariableDeclaration(variablePrefix: variablePrefix, typeSpecifier: typeSpecifier) + } else { + try throwableErrorFactory.variableDeclaration(variablePrefix: variablePrefix) + } } if let returnType = functionDeclaration.signature.returnClause?.type { diff --git a/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift b/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift index 770c6d2..8da1277 100644 --- a/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift +++ b/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift @@ -32,7 +32,15 @@ struct ThrowableErrorFactory { func variableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax { try VariableDeclSyntax( """ - var \(variableIdentifier(variablePrefix: variablePrefix)): (any Error)? + var \(variableIdentifier(variablePrefix: variablePrefix))\(TokenSyntax.colonToken()) \(TokenSyntax.leftParenToken())any Error\(TokenSyntax.rightParenToken())\(TokenSyntax.postfixQuestionMarkToken()) + """ + ) + } + + func typedVariableDeclaration(variablePrefix: String, typeSpecifier: String) throws -> VariableDeclSyntax { + try VariableDeclSyntax( + """ + var \(variableIdentifier(variablePrefix: variablePrefix))\(TokenSyntax.colonToken()) \(TokenSyntax.identifier(typeSpecifier))\(TokenSyntax.postfixQuestionMarkToken()) """ ) } diff --git a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift index bc404a9..3eb320b 100644 --- a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift +++ b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift @@ -32,6 +32,7 @@ final class UT_SpyableMacro: XCTestCase { mutating func logout() func initialize(name: String, secondName: String?) func fetchConfig() async throws -> [String: String] + func fetchConfigTypedThrow() async throws(ConfigError) -> [String: String] func fetchData(_ name: (String, count: Int)) async -> (() -> Void) func fetchUsername(context: String, completion: @escaping (String) -> Void) func onTapBack(context: String, action: () -> Void) @@ -126,6 +127,25 @@ final class UT_SpyableMacro: XCTestCase { return fetchConfigReturnValue } } + public var fetchConfigTypedThrowCallsCount = 0 + public var fetchConfigTypedThrowCalled: Bool { + return fetchConfigTypedThrowCallsCount > 0 + } + public var fetchConfigTypedThrowThrowableError: ConfigError? + public var fetchConfigTypedThrowReturnValue: [String: String]! + public var fetchConfigTypedThrowClosure: (() async throws(ConfigError) -> [String: String])? + public + func fetchConfigTypedThrow() async throws(ConfigError) -> [String: String] { + fetchConfigTypedThrowCallsCount += 1 + if let fetchConfigTypedThrowThrowableError { + throw fetchConfigTypedThrowThrowableError + } + if fetchConfigTypedThrowClosure != nil { + return try await fetchConfigTypedThrowClosure!() + } else { + return fetchConfigTypedThrowReturnValue + } + } public var fetchDataCallsCount = 0 public var fetchDataCalled: Bool { return fetchDataCallsCount > 0 From 38f68353169ce02defefde9d52787da79dff2133 Mon Sep 17 00:00:00 2001 From: Simon Feistel Date: Thu, 11 Sep 2025 10:48:23 +0200 Subject: [PATCH 2/6] - extend unit tests --- .../Factories/UT_SpyFactory.swift | 38 +++++++++++++++++++ .../Factories/UT_ThrowableErrorFactory.swift | 14 +++++++ 2 files changed, 52 insertions(+) diff --git a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift index 5b3aaf9..60eaa7a 100644 --- a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift @@ -434,6 +434,44 @@ final class UT_SpyFactory: XCTestCase { ) } + func testDeclarationThrowsTyped() throws { + try assertProtocol( + withDeclaration: """ + protocol ServiceProtocol { + func foo(_ added: ((text: String) -> Void)?) throws(ExampleError) -> (() -> Int)? + } + """, + expectingClassDeclaration: """ + class ServiceProtocolSpy: ServiceProtocol, @unchecked Sendable { + init() { + } + var fooCallsCount = 0 + var fooCalled: Bool { + return fooCallsCount > 0 + } + var fooReceivedAdded: ((text: String) -> Void)? + var fooReceivedInvocations: [((text: String) -> Void)?] = [] + var fooThrowableError: ExampleError? + var fooReturnValue: (() -> Int)? + var fooClosure: ((((text: String) -> Void)?) throws(ExampleError) -> (() -> Int)?)? + func foo(_ added: ((text: String) -> Void)?) throws(ExampleError) -> (() -> Int)? { + fooCallsCount += 1 + fooReceivedAdded = (added) + fooReceivedInvocations.append((added)) + if let fooThrowableError { + throw fooThrowableError + } + if fooClosure != nil { + return try fooClosure!(added) + } else { + return fooReturnValue + } + } + } + """ + ) + } + func testDeclarationReturnsExistential() throws { try assertProtocol( withDeclaration: """ diff --git a/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift b/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift index 0cb9439..855749e 100644 --- a/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift @@ -20,6 +20,20 @@ final class UT_ThrowableErrorFactory: XCTestCase { ) } + func testTypedVariableDeclaration() throws { + let variablePrefix = "functionName" + let typeSpecifier = "ExampleError" + + let result = try ThrowableErrorFactory().typedVariableDeclaration(variablePrefix: variablePrefix, typeSpecifier: typeSpecifier) + + assertBuildResult( + result, + """ + var functionNameThrowableError: ExampleError? + """ + ) + } + // MARK: - Throw Error Expression func testThrowErrorExpression() { From c9627c81903ff9e2d48e43e820ba2cbfd22e652d Mon Sep 17 00:00:00 2001 From: Simon Feistel Date: Thu, 11 Sep 2025 11:10:17 +0200 Subject: [PATCH 3/6] - add private helper functions --- .../SpyableMacro/Factories/SpyFactory.swift | 6 +--- .../Factories/ThrowableErrorFactory.swift | 34 ++++++++++++------- .../Factories/UT_ThrowableErrorFactory.swift | 2 +- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Sources/SpyableMacro/Factories/SpyFactory.swift b/Sources/SpyableMacro/Factories/SpyFactory.swift index 82b36d0..b52bb02 100644 --- a/Sources/SpyableMacro/Factories/SpyFactory.swift +++ b/Sources/SpyableMacro/Factories/SpyFactory.swift @@ -167,11 +167,7 @@ struct SpyFactory { #endif if throwsSpecifier != nil { - if let typeSpecifier = throwsType?.description { - try throwableErrorFactory.typedVariableDeclaration(variablePrefix: variablePrefix, typeSpecifier: typeSpecifier) - } else { - try throwableErrorFactory.variableDeclaration(variablePrefix: variablePrefix) - } + try throwableErrorFactory.variableDeclaration(variablePrefix: variablePrefix, typeSpecifier: throwsType?.description) } if let returnType = functionDeclaration.signature.returnClause?.type { diff --git a/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift b/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift index 8da1277..26bc3c7 100644 --- a/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift +++ b/Sources/SpyableMacro/Factories/ThrowableErrorFactory.swift @@ -29,28 +29,36 @@ import SwiftSyntaxBuilder /// your tests. You can use it to simulate different scenarios and verify that your code handles /// errors correctly. struct ThrowableErrorFactory { - func variableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax { - try VariableDeclSyntax( + func variableDeclaration(variablePrefix: String, typeSpecifier: String? = nil) throws -> VariableDeclSyntax { + if let typeSpecifier { + return try typedVariableDeclaration(variablePrefix: variablePrefix, typeSpecifier: typeSpecifier) + } else { + return try untypedVariableDeclaration(variablePrefix: variablePrefix) + } + } + + func throwErrorExpression(variablePrefix: String) -> ExprSyntax { + ExprSyntax( """ - var \(variableIdentifier(variablePrefix: variablePrefix))\(TokenSyntax.colonToken()) \(TokenSyntax.leftParenToken())any Error\(TokenSyntax.rightParenToken())\(TokenSyntax.postfixQuestionMarkToken()) + if let \(variableIdentifier(variablePrefix: variablePrefix)) { + throw \(variableIdentifier(variablePrefix: variablePrefix)) + } """ ) } - func typedVariableDeclaration(variablePrefix: String, typeSpecifier: String) throws -> VariableDeclSyntax { - try VariableDeclSyntax( - """ - var \(variableIdentifier(variablePrefix: variablePrefix))\(TokenSyntax.colonToken()) \(TokenSyntax.identifier(typeSpecifier))\(TokenSyntax.postfixQuestionMarkToken()) - """ + private func untypedVariableDeclaration(variablePrefix: String) throws -> VariableDeclSyntax { + return try VariableDeclSyntax( + """ + var \(variableIdentifier(variablePrefix: variablePrefix))\(TokenSyntax.colonToken()) \(TokenSyntax.leftParenToken())any Error\(TokenSyntax.rightParenToken())\(TokenSyntax.postfixQuestionMarkToken()) + """ ) } - func throwErrorExpression(variablePrefix: String) -> ExprSyntax { - ExprSyntax( + private func typedVariableDeclaration(variablePrefix: String, typeSpecifier: String) throws -> VariableDeclSyntax { + try VariableDeclSyntax( """ - if let \(variableIdentifier(variablePrefix: variablePrefix)) { - throw \(variableIdentifier(variablePrefix: variablePrefix)) - } + var \(variableIdentifier(variablePrefix: variablePrefix))\(TokenSyntax.colonToken()) \(TokenSyntax.identifier(typeSpecifier))\(TokenSyntax.postfixQuestionMarkToken()) """ ) } diff --git a/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift b/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift index 855749e..c56b570 100644 --- a/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_ThrowableErrorFactory.swift @@ -24,7 +24,7 @@ final class UT_ThrowableErrorFactory: XCTestCase { let variablePrefix = "functionName" let typeSpecifier = "ExampleError" - let result = try ThrowableErrorFactory().typedVariableDeclaration(variablePrefix: variablePrefix, typeSpecifier: typeSpecifier) + let result = try ThrowableErrorFactory().variableDeclaration(variablePrefix: variablePrefix, typeSpecifier: typeSpecifier) assertBuildResult( result, From 17468d0cc09ac3aabe0d6792fffd0b081faa2948 Mon Sep 17 00:00:00 2001 From: Simon Feistel Date: Thu, 11 Sep 2025 11:20:12 +0200 Subject: [PATCH 4/6] - fix for older swift syntax versions --- Sources/SpyableMacro/Factories/SpyFactory.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SpyableMacro/Factories/SpyFactory.swift b/Sources/SpyableMacro/Factories/SpyFactory.swift index b52bb02..d7e6ba0 100644 --- a/Sources/SpyableMacro/Factories/SpyFactory.swift +++ b/Sources/SpyableMacro/Factories/SpyFactory.swift @@ -164,6 +164,7 @@ struct SpyFactory { let throwsType = functionDeclaration.signature.effectSpecifiers?.throwsClause?.type #else let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsSpecifier + let throwsType: TypeSyntax? = nil #endif if throwsSpecifier != nil { From fb33f4b1e924b9d5638213e8e7d9b5a77f520fcf Mon Sep 17 00:00:00 2001 From: Simon Feistel Date: Thu, 11 Sep 2025 12:15:34 +0200 Subject: [PATCH 5/6] - account for older SwiftSyntax versions for new test cases --- .../SpyableMacro/Factories/SpyFactory.swift | 23 ++++--- .../Factories/UT_SpyFactory.swift | 2 + .../Macro/UT_SpyableMacro.swift | 66 +++++++++++++------ 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/Sources/SpyableMacro/Factories/SpyFactory.swift b/Sources/SpyableMacro/Factories/SpyFactory.swift index d7e6ba0..dbd6429 100644 --- a/Sources/SpyableMacro/Factories/SpyFactory.swift +++ b/Sources/SpyableMacro/Factories/SpyFactory.swift @@ -158,14 +158,17 @@ struct SpyFactory { ) } - #if canImport(SwiftSyntax600) - let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsClause? - .throwsSpecifier - let throwsType = functionDeclaration.signature.effectSpecifiers?.throwsClause?.type - #else - let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsSpecifier - let throwsType: TypeSyntax? = nil - #endif +#if canImport(SwiftSyntax600) + let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsClause? + .throwsSpecifier + let throwsType = functionDeclaration.signature.effectSpecifiers?.throwsClause?.type +#else + let throwsSpecifier = functionDeclaration.signature.effectSpecifiers?.throwsSpecifier + // this should lead to the legacy behaviour (e.g.) + // var fooThrowableError: (any Error)? // Any Error because throwsType == nil + // func foo(_ added: ((text: String) -> Void)?) throws(ExampleError) -> (() -> Int)? // function signature with typed error + let throwsType: TypeSyntax? = nil +#endif if throwsSpecifier != nil { try throwableErrorFactory.variableDeclaration(variablePrefix: variablePrefix, typeSpecifier: throwsType?.description) @@ -253,7 +256,7 @@ extension SyntaxProtocol { fileprivate var removingLeadingSpaces: Self { with( \.leadingTrivia, - Trivia( + Trivia( pieces: leadingTrivia .filter { @@ -263,7 +266,7 @@ extension SyntaxProtocol { true } } - ) + ) ) } } diff --git a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift index 60eaa7a..94ff476 100644 --- a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift @@ -434,6 +434,7 @@ final class UT_SpyFactory: XCTestCase { ) } +#if canImport(SwiftSyntax600) func testDeclarationThrowsTyped() throws { try assertProtocol( withDeclaration: """ @@ -471,6 +472,7 @@ final class UT_SpyFactory: XCTestCase { """ ) } +#endif func testDeclarationReturnsExistential() throws { try assertProtocol( diff --git a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift index 3eb320b..6dc9f58 100644 --- a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift +++ b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift @@ -32,7 +32,6 @@ final class UT_SpyableMacro: XCTestCase { mutating func logout() func initialize(name: String, secondName: String?) func fetchConfig() async throws -> [String: String] - func fetchConfigTypedThrow() async throws(ConfigError) -> [String: String] func fetchData(_ name: (String, count: Int)) async -> (() -> Void) func fetchUsername(context: String, completion: @escaping (String) -> Void) func onTapBack(context: String, action: () -> Void) @@ -127,25 +126,6 @@ final class UT_SpyableMacro: XCTestCase { return fetchConfigReturnValue } } - public var fetchConfigTypedThrowCallsCount = 0 - public var fetchConfigTypedThrowCalled: Bool { - return fetchConfigTypedThrowCallsCount > 0 - } - public var fetchConfigTypedThrowThrowableError: ConfigError? - public var fetchConfigTypedThrowReturnValue: [String: String]! - public var fetchConfigTypedThrowClosure: (() async throws(ConfigError) -> [String: String])? - public - func fetchConfigTypedThrow() async throws(ConfigError) -> [String: String] { - fetchConfigTypedThrowCallsCount += 1 - if let fetchConfigTypedThrowThrowableError { - throw fetchConfigTypedThrowThrowableError - } - if fetchConfigTypedThrowClosure != nil { - return try await fetchConfigTypedThrowClosure!() - } else { - return fetchConfigTypedThrowReturnValue - } - } public var fetchDataCallsCount = 0 public var fetchDataCalled: Bool { return fetchDataCallsCount > 0 @@ -229,6 +209,52 @@ final class UT_SpyableMacro: XCTestCase { ) } +#if canImport(SwiftSyntax600) + func testMacroWithTypedThrow() { + let protocolDeclaration = """ + public protocol ServiceProtocol { + func fetchConfigTypedThrow() async throws(ConfigError) -> [String: String] + } + """ + + assertMacroExpansion( + """ + @Spyable + \(protocolDeclaration) + """, + expandedSource: """ + + \(protocolDeclaration) + + public class ServiceProtocolSpy: ServiceProtocol, @unchecked Sendable { + public init() { + } + public var fetchConfigTypedThrowCallsCount = 0 + public var fetchConfigTypedThrowCalled: Bool { + return fetchConfigTypedThrowCallsCount > 0 + } + public var fetchConfigTypedThrowThrowableError: ConfigError? + public var fetchConfigTypedThrowReturnValue: [String: String]! + public var fetchConfigTypedThrowClosure: (() async throws(ConfigError) -> [String: String])? + public + func fetchConfigTypedThrow() async throws(ConfigError) -> [String: String] { + fetchConfigTypedThrowCallsCount += 1 + if let fetchConfigTypedThrowThrowableError { + throw fetchConfigTypedThrowThrowableError + } + if fetchConfigTypedThrowClosure != nil { + return try await fetchConfigTypedThrowClosure!() + } else { + return fetchConfigTypedThrowReturnValue + } + } + } + """, + macros: sut + ) + } +#endif + // MARK: - `behindPreprocessorFlag` argument func testMacroWithNoArgument() { From 672206cf4946033d8777db218b4eff088f738542 Mon Sep 17 00:00:00 2001 From: Simon Feistel Date: Thu, 11 Sep 2025 14:27:58 +0200 Subject: [PATCH 6/6] - account for platform support --- .../FunctionImplementationFactory.swift | 79 +++++++++++++++++-- .../Factories/UT_ClosureFactory.swift | 10 +++ .../UT_FunctionImplementationFactory.swift | 26 ++++++ .../Factories/UT_SpyFactory.swift | 2 +- .../Macro/UT_SpyableMacro.swift | 2 +- 5 files changed, 110 insertions(+), 9 deletions(-) diff --git a/Sources/SpyableMacro/Factories/FunctionImplementationFactory.swift b/Sources/SpyableMacro/Factories/FunctionImplementationFactory.swift index afa6922..ee31aad 100644 --- a/Sources/SpyableMacro/Factories/FunctionImplementationFactory.swift +++ b/Sources/SpyableMacro/Factories/FunctionImplementationFactory.swift @@ -87,13 +87,13 @@ struct FunctionImplementationFactory { ) } - #if canImport(SwiftSyntax600) - let throwsSpecifier = protocolFunctionDeclaration.signature.effectSpecifiers?.throwsClause? - .throwsSpecifier - #else - let throwsSpecifier = protocolFunctionDeclaration.signature.effectSpecifiers? - .throwsSpecifier - #endif +#if canImport(SwiftSyntax600) + let throwsSpecifier = protocolFunctionDeclaration.signature.effectSpecifiers?.throwsClause? + .throwsSpecifier +#else + let throwsSpecifier = protocolFunctionDeclaration.signature.effectSpecifiers? + .throwsSpecifier +#endif if throwsSpecifier != nil { throwableErrorFactory.throwErrorExpression(variablePrefix: variablePrefix) @@ -123,6 +123,9 @@ struct FunctionImplementationFactory { // due to the bug: https://github.com/apple/swift-syntax/issues/2352 IfExprSyntax( conditions: ConditionElementListSyntax { +#if canImport(SwiftSyntax600) + typedThrowsCondition(protocolFunctionDeclaration: protocolFunctionDeclaration) // if working with typed throws, they are only supported from certain platforms on +#endif ConditionElementSyntax( condition: .expression( ExprSyntax( @@ -154,6 +157,68 @@ struct FunctionImplementationFactory { } ) } + +#if canImport(SwiftSyntax600) + private func typedThrowsCondition(protocolFunctionDeclaration: FunctionDeclSyntax) -> [ConditionElementSyntax] { + guard protocolFunctionDeclaration.signature.effectSpecifiers?.throwsClause?.type != nil else { + return [] + } + return [ + ConditionElementSyntax( + condition: .availability( + AvailabilityConditionSyntax( + availabilityKeyword: .poundAvailableToken(), + availabilityArguments: AvailabilityArgumentListSyntax { + AvailabilityArgumentSyntax( + argument: .availabilityVersionRestriction( + PlatformVersionSyntax( + platform: .identifier("iOS 18.0.0") // iOS 18+ + ) + ) + ) + AvailabilityArgumentSyntax( + argument: .availabilityVersionRestriction( + PlatformVersionSyntax( + platform: .identifier("macOS 15.0.0") // macOS 15+ + ) + ) + ) + AvailabilityArgumentSyntax( + argument: .availabilityVersionRestriction( + PlatformVersionSyntax( + platform: .identifier("tvOS 18.0.0") // tvOS 18+ + ) + ) + ) + AvailabilityArgumentSyntax( + argument: .availabilityVersionRestriction( + PlatformVersionSyntax( + platform: .identifier("watchOS 11.0.0") // watchOS 11+ + ) + ) + ) + AvailabilityArgumentSyntax( + argument: .availabilityVersionRestriction( + PlatformVersionSyntax( + platform: .identifier("macCatalyst 18.0.0") // macCatalyst 18+ + ) + ) + ) + AvailabilityArgumentSyntax( + argument: .availabilityVersionRestriction( + PlatformVersionSyntax( + platform: .identifier("*") + ) + ) + ) + } + ) + ) + ) + ] + } +#endif + } extension DeclModifierListSyntax { diff --git a/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift b/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift index 5031f90..3e8c9df 100644 --- a/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_ClosureFactory.swift @@ -39,6 +39,16 @@ final class UT_ClosureFactory: XCTestCase { ) } +#if canImport(SwiftSyntax600) + func testVariableDeclarationThrowsTyped() throws { + try assertProtocolFunction( + withFunctionDeclaration: "func _ignore_() throws(ExampleError)", + prefixForVariable: "_prefix_", + expectingVariableDeclaration: "var _prefix_Closure: (() throws(ExampleError) -> Void)?" + ) + } +#endif + func testVariableDeclarationReturnValue() throws { try assertProtocolFunction( withFunctionDeclaration: "func _ignore_() -> Data", diff --git a/Tests/SpyableMacroTests/Factories/UT_FunctionImplementationFactory.swift b/Tests/SpyableMacroTests/Factories/UT_FunctionImplementationFactory.swift index 271a754..3724efc 100644 --- a/Tests/SpyableMacroTests/Factories/UT_FunctionImplementationFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_FunctionImplementationFactory.swift @@ -95,6 +95,32 @@ final class UT_FunctionImplementationFactory: XCTestCase { ) } +#if canImport(SwiftSyntax600) + func testDeclarationReturnValueAsyncThrowsTyped() throws { + try assertProtocolFunction( + withFunctionDeclaration: """ + func foo(_ bar: String) async throws(ExampleError) -> (text: String, tuple: (count: Int?, Date)) + """, + prefixForVariable: "_prefix_", + expectingFunctionDeclaration: """ + func foo(_ bar: String) async throws(ExampleError) -> (text: String, tuple: (count: Int?, Date)) { + _prefix_CallsCount += 1 + _prefix_ReceivedBar = (bar) + _prefix_ReceivedInvocations.append((bar)) + if let _prefix_ThrowableError { + throw _prefix_ThrowableError + } + if #available(iOS 18.0.0, macOS 15.0.0, tvOS 18.0.0, watchOS 11.0.0, macCatalyst 18.0.0, *), _prefix_Closure != nil { + return try await _prefix_Closure!(bar) + } else { + return _prefix_ReturnValue + } + } + """ + ) + } +#endif + func testDeclarationWithMutatingKeyword() throws { try assertProtocolFunction( withFunctionDeclaration: "mutating func foo()", diff --git a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift index 94ff476..9ccfeb8 100644 --- a/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift +++ b/Tests/SpyableMacroTests/Factories/UT_SpyFactory.swift @@ -462,7 +462,7 @@ final class UT_SpyFactory: XCTestCase { if let fooThrowableError { throw fooThrowableError } - if fooClosure != nil { + if #available(iOS 18.0.0, macOS 15.0.0, tvOS 18.0.0, watchOS 11.0.0, macCatalyst 18.0.0, *), fooClosure != nil { return try fooClosure!(added) } else { return fooReturnValue diff --git a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift index 6dc9f58..e0b4b88 100644 --- a/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift +++ b/Tests/SpyableMacroTests/Macro/UT_SpyableMacro.swift @@ -242,7 +242,7 @@ final class UT_SpyableMacro: XCTestCase { if let fetchConfigTypedThrowThrowableError { throw fetchConfigTypedThrowThrowableError } - if fetchConfigTypedThrowClosure != nil { + if #available(iOS 18.0.0, macOS 15.0.0, tvOS 18.0.0, watchOS 11.0.0, macCatalyst 18.0.0, *), fetchConfigTypedThrowClosure != nil { return try await fetchConfigTypedThrowClosure!() } else { return fetchConfigTypedThrowReturnValue