From 97b64af4a57cc7628dd173615bc5fb3956541149 Mon Sep 17 00:00:00 2001 From: Mx-Iris <61279231+Mx-Iris@users.noreply.github.com> Date: Sun, 28 Jan 2024 17:18:02 +0800 Subject: [PATCH 1/3] Change swift-syntax version from `exact` to `from` --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index c9d90cc..e954f17 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/apple/swift-syntax.git", - exact: "509.0.0" + from: "509.0.0" ), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"), .package(url: "https://github.com/SwiftPackageIndex/SPIManifest.git", from: "0.12.0"), From ff66c22a9d2d6ef063d327246dd1e1c0b853307a Mon Sep 17 00:00:00 2001 From: Mx-Iris <61279231+Mx-Iris@users.noreply.github.com> Date: Wed, 7 Feb 2024 00:17:59 +0800 Subject: [PATCH 2/3] Add some Examples --- .../MacroToolkitExample.swift | 16 +++ ...ddAsyncImplementationAllMembersMacro.swift | 11 ++ .../AddAsyncImplementationCore.swift | 103 +++++++++++++++ .../AddAsyncImplementationMacro.swift | 14 ++ .../AddAsyncInterfaceAllMemberMacro.swift | 16 +++ .../AddAsyncInterfaceCore.swift | 74 +++++++++++ .../AddAsyncInterfaceMacro.swift | 14 ++ .../MacroToolkitExamplePlugin.swift | 14 +- .../MacroToolkitTests/MacroToolkitTests.swift | 120 ++++++++++++++++++ 9 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift create mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift create mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift create mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift create mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift create mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift diff --git a/Sources/MacroToolkitExample/MacroToolkitExample.swift b/Sources/MacroToolkitExample/MacroToolkitExample.swift index 985d6b7..74e7655 100644 --- a/Sources/MacroToolkitExample/MacroToolkitExample.swift +++ b/Sources/MacroToolkitExample/MacroToolkitExample.swift @@ -36,3 +36,19 @@ public macro CustomCodable() = @attached(memberAttribute) public macro DictionaryStorage() = #externalMacro(module: "MacroToolkitExamplePlugin", type: "DictionaryStorageMacro") + +@attached(peer, names: overloaded) +public macro AddAsyncInterface() = + #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncInterfaceMacro") + +@attached(member, names: arbitrary) +public macro AddAsyncInterfaceAllMembers() = + #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncInterfaceAllMembersMacro") + +@attached(peer, names: overloaded) +public macro AddAsyncImplementation() = + #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncImplementationMacro") + +@attached(member, names: arbitrary) +public macro AddAsyncImplementationAllMembers() = + #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncImplementationAllMembersMacro") diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift new file mode 100644 index 0000000..5d08b1e --- /dev/null +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift @@ -0,0 +1,11 @@ +import SwiftSyntax +import MacroToolkit +import SwiftSyntaxMacros + +public enum AddAsyncImplementationAllMembersMacro: MemberMacro { + public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { + declaration.memberBlock.members.map(\.decl).compactMap { + try? AddAsyncImplementationCore.expansion(of: nil, providingFunctionOf: $0) + } + } +} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift new file mode 100644 index 0000000..17f9cbb --- /dev/null +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift @@ -0,0 +1,103 @@ +import SwiftSyntax +import MacroToolkit +import SwiftSyntaxMacros + +// Modified from: https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/AddAsyncMacro.swift + +enum AddAsyncImplementationCore { + static func expansion(of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol) throws -> DeclSyntax { + // Only on functions at the moment. + guard let function = Function(declaration) else { + throw MacroError("@AddAsync only works on functions") + } + + // This only makes sense for non async functions. + guard !function.isAsync else { + throw MacroError("@AddAsync requires a non async function") + } + + // This only makes sense void functions + guard function.returnsVoid else { + throw MacroError("@AddAsync requires a function that returns void") + } + + // Requires a completion handler block as last parameter + guard + let completionHandlerType = function.parameters.last?.type.asFunctionType + else { + throw MacroError( + "@AddAsync requires a function that has a completion handler as last parameter") + } + + // Completion handler needs to return Void + guard completionHandlerType.returnType.isVoid else { + throw MacroError( + "@AddAsync requires a function that has a completion handler that returns Void") + } + + guard let returnType = completionHandlerType.parameters.first else { + throw MacroError( + "@AddAsync requires a function that has a completion handler that has one parameter" + ) + } + + // Destructure return type + let successReturnType: Type + let isResultReturn: Bool + if case let .simple("Result", (successType, _)) = destructure(returnType) { + isResultReturn = true + successReturnType = successType + } else { + isResultReturn = false + successReturnType = returnType + } + + // Remove completionHandler and comma from the previous parameter + let newParameters = function.parameters.dropLast() + + // Drop the @AddAsync attribute from the new declaration. + var filteredAttributes = function.attributes + if let node { + filteredAttributes = filteredAttributes.removing(node) + } + + let callArguments = newParameters.asPassthroughArguments + + let switchBody: ExprSyntax = + """ + switch returnValue { + case .success(let value): + continuation.resume(returning: value) + case .failure(let error): + continuation.resume(throwing: error) + } + """ + + let continuationExpr = + isResultReturn + ? "try await withCheckedThrowingContinuation { continuation in" + : "await withCheckedContinuation { continuation in" + + let newBody: ExprSyntax = + """ + \(raw: continuationExpr) + \(raw: function.identifier)(\(raw: callArguments.joined(separator: ", "))) { returnValue in + \(isResultReturn ? switchBody : "continuation.resume(returning: returnValue)") + } + } + """ + + // TODO: Make better codeblock init + let newFunc = + function._syntax + .withParameters(newParameters) + .withReturnType(successReturnType) + .withAsyncModifier() + .withThrowsModifier(isResultReturn) + .withBody(CodeBlockSyntax([newBody])) + .withAttributes(filteredAttributes) + .withLeadingBlankLine() + + return DeclSyntax(newFunc) + } +} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift new file mode 100644 index 0000000..ec91658 --- /dev/null +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift @@ -0,0 +1,14 @@ +import SwiftSyntax +import MacroToolkit +import SwiftSyntaxMacros + +public enum AddAsyncImplementationMacro: PeerMacro { + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + let decl = try AddAsyncImplementationCore.expansion(of: node, providingFunctionOf: declaration) + return [decl] + } +} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift new file mode 100644 index 0000000..c47467a --- /dev/null +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift @@ -0,0 +1,16 @@ +import SwiftSyntax +import MacroToolkit +import SwiftSyntaxMacros + +public enum AddAsyncInterfaceAllMembersMacro: MemberMacro { + public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { + guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else { + throw MacroError("Macro `AddAsyncInterfaceToAllMemberMacro` can only be applied to a protocol") + } + + let methods = protocolDecl.memberBlock.members.map(\.decl).compactMap { + try? AddAsyncInterfaceCore.expansion(of: nil, providingFunctionOf: $0) + } + return methods + } +} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift new file mode 100644 index 0000000..44bf3a4 --- /dev/null +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift @@ -0,0 +1,74 @@ +import SwiftSyntax +import MacroToolkit +import SwiftSyntaxMacros + +enum AddAsyncInterfaceCore { + static func expansion(of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol) throws -> DeclSyntax { + // Only on functions at the moment. + guard let function = Function(declaration) else { + throw MacroError("@AddAsync only works on functions") + } + + // This only makes sense for non async functions. + guard !function.isAsync else { + throw MacroError("@AddAsync requires a non async function") + } + + // This only makes sense void functions + guard function.returnsVoid else { + throw MacroError("@AddAsync requires a function that returns void") + } + + // Requires a completion handler block as last parameter + guard + let completionHandlerType = function.parameters.last?.type.asFunctionType + else { + throw MacroError( + "@AddAsync requires a function that has a completion handler as last parameter") + } + + // Completion handler needs to return Void + guard completionHandlerType.returnType.isVoid else { + throw MacroError( + "@AddAsync requires a function that has a completion handler that returns Void") + } + + guard let returnType = completionHandlerType.parameters.first else { + throw MacroError( + "@AddAsync requires a function that has a completion handler that has one parameter" + ) + } + + // Destructure return type + let successReturnType: Type + let isResultReturn: Bool + if case let .simple("Result", (successType, _)) = destructure(returnType) { + isResultReturn = true + successReturnType = successType + } else { + isResultReturn = false + successReturnType = returnType + } + + // Remove completionHandler and comma from the previous parameter + let newParameters = function.parameters.dropLast() + + // Drop the @AddAsync attribute from the new declaration. + var filteredAttributes = function.attributes + if let node { + filteredAttributes = filteredAttributes.removing(node) + } + + // TODO: Make better codeblock init + let newFunc = + function._syntax + .withParameters(newParameters) + .withReturnType(successReturnType) + .withAsyncModifier() + .withThrowsModifier(isResultReturn) + .withAttributes(filteredAttributes) + .withLeadingBlankLine() + + return DeclSyntax(newFunc) + } +} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift new file mode 100644 index 0000000..75d3e5b --- /dev/null +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift @@ -0,0 +1,14 @@ +import SwiftSyntax +import MacroToolkit +import SwiftSyntaxMacros + +public enum AddAsyncInterfaceMacro: PeerMacro { + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + let decl = try AddAsyncInterfaceCore.expansion(of: node, providingFunctionOf: declaration) + return [decl] + } +} diff --git a/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift b/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift index be4da19..81b106d 100644 --- a/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift +++ b/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift @@ -4,6 +4,18 @@ import SwiftSyntaxMacros @main struct MacroToolkitExamplePlugin: CompilerPlugin { let providingMacros: [Macro.Type] = [ - AddAsyncMacro.self + AddAsyncMacro.self, + AddCompletionHandlerMacro.self, + CaseDetectionMacro.self, + AddBlockerMacro.self, + OptionSetMacro.self, + MetaEnumMacro.self, + CustomCodableMacro.self, + CodableKeyMacro.self, + DictionaryStorageMacro.self, + AddAsyncInterfaceMacro.self, + AddAsyncInterfaceAllMembersMacro.self, + AddAsyncImplementationMacro.self, + AddAsyncImplementationAllMembersMacro.self, ] } diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index 925de2f..a868a81 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -16,6 +16,10 @@ let testMacros: [String: Macro.Type] = [ "CustomCodable": CustomCodableMacro.self, "CodableKey": CodableKeyMacro.self, "DictionaryStorage": DictionaryStorageMacro.self, + "AddAsyncInterface": AddAsyncInterfaceMacro.self, + "AddAsyncInterfaceAllMembers": AddAsyncInterfaceAllMembersMacro.self, + "AddAsyncImplementation": AddAsyncImplementationMacro.self, + "AddAsyncImplementationAllMembers": AddAsyncImplementationAllMembersMacro.self, ] final class MacroToolkitTests: XCTestCase { @@ -561,4 +565,120 @@ final class MacroToolkitTests: XCTestCase { XCTAssertEqual(n.initialValue?._syntax.description, "[1, 2.5]") XCTAssertEqual(n.isLazy, true) } + + func testAsyncInterfaceMacro() throws { + assertMacroExpansion( + """ + protocol API { + @AddAsyncInterface + func request(completion: (Int) -> Void) + } + """, + expandedSource: + """ + protocol API { + func request(completion: (Int) -> Void) + + func request() async -> Int + } + """, + macros: testMacros + ) + } + + func testAsyncInterfaceAllMembersMacro() throws { + assertMacroExpansion( + """ + @AddAsyncInterfaceAllMembers + protocol API { + func request1(completion: (Int) -> Void) + func request2(completion: (String) -> Void) + } + """, + expandedSource: + """ + protocol API { + func request1(completion: (Int) -> Void) + func request2(completion: (String) -> Void) + + func request1() async -> Int + + func request2() async -> String + } + """, + macros: testMacros + ) + } + func testAsyncImplementationMacro() throws { + assertMacroExpansion( + """ + struct Client { + @AddAsyncImplementation + func request1(completion: (Int) -> Void) { + completion(0) + } + } + """, + expandedSource: + """ + struct Client { + func request1(completion: (Int) -> Void) { + completion(0) + } + + func request1() async -> Int { + await withCheckedContinuation { continuation in + request1() { returnValue in + continuation.resume(returning: returnValue) + } + } + } + } + """, + macros: testMacros + ) + } + func testAsyncImplementationAllMembersMacro() throws { + assertMacroExpansion( + """ + @AddAsyncImplementationAllMembers + struct Client { + func request1(completion: (Int) -> Void) { + completion(0) + } + func request2(completion: (String) -> Void) { + completion("") + } + } + """, + expandedSource: + """ + struct Client { + func request1(completion: (Int) -> Void) { + completion(0) + } + func request2(completion: (String) -> Void) { + completion("") + } + + func request1() async -> Int { + await withCheckedContinuation { continuation in + request1() { returnValue in + continuation.resume(returning: returnValue) + } + } + } + + func request2() async -> String { + await withCheckedContinuation { continuation in + request2() { returnValue in + continuation.resume(returning: returnValue) + } + } + } + } + """, + macros: testMacros + ) + } } From 22de7451d2ef82993baab01c9975454b4b968d5f Mon Sep 17 00:00:00 2001 From: JH Lai Date: Thu, 8 Feb 2024 12:02:28 +0800 Subject: [PATCH 3/3] Remove duplicate code and optimize AddAsync macros --- .../MacroToolkitExample.swift | 16 +--- ...ro.swift => AddAsyncAllMembersMacro.swift} | 4 +- .../AddAsyncImplementationMacro.swift | 14 --- .../AddAsyncInterfaceAllMemberMacro.swift | 16 ---- .../AddAsyncInterfaceCore.swift | 74 --------------- .../AddAsyncInterfaceMacro.swift | 14 --- .../AddAsyncMacro.swift | 93 +------------------ ...tionCore.swift => AddAsyncMacroCore.swift} | 39 ++++---- .../MacroToolkitExamplePlugin.swift | 5 +- .../MacroToolkitTests/MacroToolkitTests.swift | 13 +-- 10 files changed, 34 insertions(+), 254 deletions(-) rename Sources/MacroToolkitExamplePlugin/{AddAsyncImplementationAllMembersMacro.swift => AddAsyncAllMembersMacro.swift} (68%) delete mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift delete mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift delete mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift delete mode 100644 Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift rename Sources/MacroToolkitExamplePlugin/{AddAsyncImplementationCore.swift => AddAsyncMacroCore.swift} (80%) diff --git a/Sources/MacroToolkitExample/MacroToolkitExample.swift b/Sources/MacroToolkitExample/MacroToolkitExample.swift index 74e7655..0b414c1 100644 --- a/Sources/MacroToolkitExample/MacroToolkitExample.swift +++ b/Sources/MacroToolkitExample/MacroToolkitExample.swift @@ -37,18 +37,6 @@ public macro CustomCodable() = public macro DictionaryStorage() = #externalMacro(module: "MacroToolkitExamplePlugin", type: "DictionaryStorageMacro") -@attached(peer, names: overloaded) -public macro AddAsyncInterface() = - #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncInterfaceMacro") - -@attached(member, names: arbitrary) -public macro AddAsyncInterfaceAllMembers() = - #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncInterfaceAllMembersMacro") - -@attached(peer, names: overloaded) -public macro AddAsyncImplementation() = - #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncImplementationMacro") - @attached(member, names: arbitrary) -public macro AddAsyncImplementationAllMembers() = - #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncImplementationAllMembersMacro") +public macro AddAsyncAllMembers() = + #externalMacro(module: "MacroToolkitExamplePlugin", type: "AddAsyncAllMembersMacro") diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift similarity index 68% rename from Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift rename to Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift index 5d08b1e..15cd34a 100644 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationAllMembersMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncAllMembersMacro.swift @@ -2,10 +2,10 @@ import SwiftSyntax import MacroToolkit import SwiftSyntaxMacros -public enum AddAsyncImplementationAllMembersMacro: MemberMacro { +public enum AddAsyncAllMembersMacro: MemberMacro { public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { declaration.memberBlock.members.map(\.decl).compactMap { - try? AddAsyncImplementationCore.expansion(of: nil, providingFunctionOf: $0) + try? AddAsyncMacroCore.expansion(of: nil, providingFunctionOf: $0) } } } diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift deleted file mode 100644 index ec91658..0000000 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationMacro.swift +++ /dev/null @@ -1,14 +0,0 @@ -import SwiftSyntax -import MacroToolkit -import SwiftSyntaxMacros - -public enum AddAsyncImplementationMacro: PeerMacro { - public static func expansion( - of node: AttributeSyntax, - providingPeersOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - let decl = try AddAsyncImplementationCore.expansion(of: node, providingFunctionOf: declaration) - return [decl] - } -} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift deleted file mode 100644 index c47467a..0000000 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceAllMemberMacro.swift +++ /dev/null @@ -1,16 +0,0 @@ -import SwiftSyntax -import MacroToolkit -import SwiftSyntaxMacros - -public enum AddAsyncInterfaceAllMembersMacro: MemberMacro { - public static func expansion(of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] { - guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else { - throw MacroError("Macro `AddAsyncInterfaceToAllMemberMacro` can only be applied to a protocol") - } - - let methods = protocolDecl.memberBlock.members.map(\.decl).compactMap { - try? AddAsyncInterfaceCore.expansion(of: nil, providingFunctionOf: $0) - } - return methods - } -} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift deleted file mode 100644 index 44bf3a4..0000000 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceCore.swift +++ /dev/null @@ -1,74 +0,0 @@ -import SwiftSyntax -import MacroToolkit -import SwiftSyntaxMacros - -enum AddAsyncInterfaceCore { - static func expansion(of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol) throws -> DeclSyntax { - // Only on functions at the moment. - guard let function = Function(declaration) else { - throw MacroError("@AddAsync only works on functions") - } - - // This only makes sense for non async functions. - guard !function.isAsync else { - throw MacroError("@AddAsync requires a non async function") - } - - // This only makes sense void functions - guard function.returnsVoid else { - throw MacroError("@AddAsync requires a function that returns void") - } - - // Requires a completion handler block as last parameter - guard - let completionHandlerType = function.parameters.last?.type.asFunctionType - else { - throw MacroError( - "@AddAsync requires a function that has a completion handler as last parameter") - } - - // Completion handler needs to return Void - guard completionHandlerType.returnType.isVoid else { - throw MacroError( - "@AddAsync requires a function that has a completion handler that returns Void") - } - - guard let returnType = completionHandlerType.parameters.first else { - throw MacroError( - "@AddAsync requires a function that has a completion handler that has one parameter" - ) - } - - // Destructure return type - let successReturnType: Type - let isResultReturn: Bool - if case let .simple("Result", (successType, _)) = destructure(returnType) { - isResultReturn = true - successReturnType = successType - } else { - isResultReturn = false - successReturnType = returnType - } - - // Remove completionHandler and comma from the previous parameter - let newParameters = function.parameters.dropLast() - - // Drop the @AddAsync attribute from the new declaration. - var filteredAttributes = function.attributes - if let node { - filteredAttributes = filteredAttributes.removing(node) - } - - // TODO: Make better codeblock init - let newFunc = - function._syntax - .withParameters(newParameters) - .withReturnType(successReturnType) - .withAsyncModifier() - .withThrowsModifier(isResultReturn) - .withAttributes(filteredAttributes) - .withLeadingBlankLine() - - return DeclSyntax(newFunc) - } -} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift deleted file mode 100644 index 75d3e5b..0000000 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncInterfaceMacro.swift +++ /dev/null @@ -1,14 +0,0 @@ -import SwiftSyntax -import MacroToolkit -import SwiftSyntaxMacros - -public enum AddAsyncInterfaceMacro: PeerMacro { - public static func expansion( - of node: AttributeSyntax, - providingPeersOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - let decl = try AddAsyncInterfaceCore.expansion(of: node, providingFunctionOf: declaration) - return [decl] - } -} diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncMacro.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncMacro.swift index e1cddee..b551df1 100644 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncMacro.swift +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncMacro.swift @@ -3,7 +3,7 @@ import SwiftSyntax import SwiftSyntaxMacros // Modified from: https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/AddAsyncMacro.swift -public struct AddAsyncMacro: PeerMacro { +public enum AddAsyncMacro: PeerMacro { public static func expansion< Context: MacroExpansionContext, Declaration: DeclSyntaxProtocol @@ -12,95 +12,6 @@ public struct AddAsyncMacro: PeerMacro { providingPeersOf declaration: Declaration, in context: Context ) throws -> [DeclSyntax] { - // Only on functions at the moment. - guard let function = Function(declaration) else { - throw MacroError("@AddAsync only works on functions") - } - - // This only makes sense for non async functions. - guard !function.isAsync else { - throw MacroError("@AddAsync requires a non async function") - } - - // This only makes sense void functions - guard function.returnsVoid else { - throw MacroError("@AddAsync requires a function that returns void") - } - - // Requires a completion handler block as last parameter - guard - let completionHandlerType = function.parameters.last?.type.asFunctionType - else { - throw MacroError( - "@AddAsync requires a function that has a completion handler as last parameter") - } - - // Completion handler needs to return Void - guard completionHandlerType.returnType.isVoid else { - throw MacroError( - "@AddAsync requires a function that has a completion handler that returns Void") - } - - guard let returnType = completionHandlerType.parameters.first else { - throw MacroError( - "@AddAsync requires a function that has a completion handler that has one parameter" - ) - } - - // Destructure return type - let successReturnType: Type - let isResultReturn: Bool - if case let .simple("Result", (successType, _)) = destructure(returnType) { - isResultReturn = true - successReturnType = successType - } else { - isResultReturn = false - successReturnType = returnType - } - - // Remove completionHandler and comma from the previous parameter - let newParameters = function.parameters.dropLast() - - // Drop the @AddAsync attribute from the new declaration. - let filteredAttributes = function.attributes.removing(node) - - let callArguments = newParameters.asPassthroughArguments - - let switchBody: ExprSyntax = - """ - switch returnValue { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - """ - - let continuationExpr = - isResultReturn - ? "try await withCheckedThrowingContinuation { continuation in" - : "await withCheckedContinuation { continuation in" - - let newBody: ExprSyntax = - """ - \(raw: continuationExpr) - \(raw: function.identifier)(\(raw: callArguments.joined(separator: ", "))) { returnValue in - \(isResultReturn ? switchBody : "continuation.resume(returning: returnValue)") - } - } - """ - - // TODO: Make better codeblock init - let newFunc = - function._syntax - .withParameters(newParameters) - .withReturnType(successReturnType) - .withAsyncModifier() - .withThrowsModifier(isResultReturn) - .withBody(CodeBlockSyntax([newBody])) - .withAttributes(filteredAttributes) - .withLeadingBlankLine() - - return [DeclSyntax(newFunc)] + [try AddAsyncMacroCore.expansion(of: node, providingFunctionOf: declaration)] } } diff --git a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift b/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift similarity index 80% rename from Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift rename to Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift index 17f9cbb..25e64eb 100644 --- a/Sources/MacroToolkitExamplePlugin/AddAsyncImplementationCore.swift +++ b/Sources/MacroToolkitExamplePlugin/AddAsyncMacroCore.swift @@ -4,7 +4,7 @@ import SwiftSyntaxMacros // Modified from: https://github.com/DougGregor/swift-macro-examples/blob/f61ac7cdca8dc3557e53f86e7e03df1353908d3e/MacroExamplesPlugin/AddAsyncMacro.swift -enum AddAsyncImplementationCore { +enum AddAsyncMacroCore { static func expansion(of node: AttributeSyntax?, providingFunctionOf declaration: some DeclSyntaxProtocol) throws -> DeclSyntax { // Only on functions at the moment. guard let function = Function(declaration) else { @@ -63,7 +63,8 @@ enum AddAsyncImplementationCore { let callArguments = newParameters.asPassthroughArguments - let switchBody: ExprSyntax = + let newBody = function._syntax.body.map { _ in + let switchBody: ExprSyntax = """ switch returnValue { case .success(let value): @@ -72,13 +73,13 @@ enum AddAsyncImplementationCore { continuation.resume(throwing: error) } """ - - let continuationExpr = + + let continuationExpr = isResultReturn - ? "try await withCheckedThrowingContinuation { continuation in" - : "await withCheckedContinuation { continuation in" - - let newBody: ExprSyntax = + ? "try await withCheckedThrowingContinuation { continuation in" + : "await withCheckedContinuation { continuation in" + + let newBody: ExprSyntax = """ \(raw: continuationExpr) \(raw: function.identifier)(\(raw: callArguments.joined(separator: ", "))) { returnValue in @@ -86,17 +87,21 @@ enum AddAsyncImplementationCore { } } """ - + return CodeBlockSyntax([newBody]) + } // TODO: Make better codeblock init - let newFunc = + var newFunc = function._syntax - .withParameters(newParameters) - .withReturnType(successReturnType) - .withAsyncModifier() - .withThrowsModifier(isResultReturn) - .withBody(CodeBlockSyntax([newBody])) - .withAttributes(filteredAttributes) - .withLeadingBlankLine() + .withParameters(newParameters) + .withReturnType(successReturnType) + .withAsyncModifier() + .withThrowsModifier(isResultReturn) + .withAttributes(filteredAttributes) + .withLeadingBlankLine() + + if let newBody { + newFunc = newFunc.withBody(newBody) + } return DeclSyntax(newFunc) } diff --git a/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift b/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift index 81b106d..be3e44d 100644 --- a/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift +++ b/Sources/MacroToolkitExamplePlugin/MacroToolkitExamplePlugin.swift @@ -13,9 +13,6 @@ struct MacroToolkitExamplePlugin: CompilerPlugin { CustomCodableMacro.self, CodableKeyMacro.self, DictionaryStorageMacro.self, - AddAsyncInterfaceMacro.self, - AddAsyncInterfaceAllMembersMacro.self, - AddAsyncImplementationMacro.self, - AddAsyncImplementationAllMembersMacro.self, + AddAsyncAllMembersMacro.self, ] } diff --git a/Tests/MacroToolkitTests/MacroToolkitTests.swift b/Tests/MacroToolkitTests/MacroToolkitTests.swift index a868a81..4b82a3e 100644 --- a/Tests/MacroToolkitTests/MacroToolkitTests.swift +++ b/Tests/MacroToolkitTests/MacroToolkitTests.swift @@ -16,10 +16,7 @@ let testMacros: [String: Macro.Type] = [ "CustomCodable": CustomCodableMacro.self, "CodableKey": CodableKeyMacro.self, "DictionaryStorage": DictionaryStorageMacro.self, - "AddAsyncInterface": AddAsyncInterfaceMacro.self, - "AddAsyncInterfaceAllMembers": AddAsyncInterfaceAllMembersMacro.self, - "AddAsyncImplementation": AddAsyncImplementationMacro.self, - "AddAsyncImplementationAllMembers": AddAsyncImplementationAllMembersMacro.self, + "AddAsyncAllMembers": AddAsyncAllMembersMacro.self, ] final class MacroToolkitTests: XCTestCase { @@ -570,7 +567,7 @@ final class MacroToolkitTests: XCTestCase { assertMacroExpansion( """ protocol API { - @AddAsyncInterface + @AddAsync func request(completion: (Int) -> Void) } """, @@ -589,7 +586,7 @@ final class MacroToolkitTests: XCTestCase { func testAsyncInterfaceAllMembersMacro() throws { assertMacroExpansion( """ - @AddAsyncInterfaceAllMembers + @AddAsyncAllMembers protocol API { func request1(completion: (Int) -> Void) func request2(completion: (String) -> Void) @@ -613,7 +610,7 @@ final class MacroToolkitTests: XCTestCase { assertMacroExpansion( """ struct Client { - @AddAsyncImplementation + @AddAsync func request1(completion: (Int) -> Void) { completion(0) } @@ -641,7 +638,7 @@ final class MacroToolkitTests: XCTestCase { func testAsyncImplementationAllMembersMacro() throws { assertMacroExpansion( """ - @AddAsyncImplementationAllMembers + @AddAsyncAllMembers struct Client { func request1(completion: (Int) -> Void) { completion(0)