From 54484fa480949d4cf198e3aefc50c2baf3f09768 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 29 Aug 2023 08:52:14 -0700 Subject: [PATCH 1/2] Refactor MacrosystemTests to make most macro definitions local to their test case Most of the macro definitions were only used in a single test case. Achieve a higher locality of functionality by moving the macro definitions inside these test cases. --- .../MacroSystemTests.swift | 1211 ++++++++--------- 1 file changed, 575 insertions(+), 636 deletions(-) diff --git a/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift index 85a97c541f0..e31880159f7 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift @@ -20,174 +20,10 @@ import SwiftSyntaxMacroExpansion import SwiftSyntaxMacrosTestSupport import XCTest -// MARK: Example macros -public struct StringifyMacro: ExpressionMacro { - public static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) throws -> ExprSyntax { - guard let argument = macro.argumentList.first?.expression else { - throw MacroExpansionErrorMessage("missing argument") - } +// MARK: Macros shared between multiple test cases - return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))" - } -} - -/// Replace the label of the first element in the tuple with the given -/// new label. -private func replaceFirstLabel( - of tuple: LabeledExprListSyntax, - with newLabel: String -) -> LabeledExprListSyntax { - guard let firstElement = tuple.first else { - return tuple - } - - return tuple.with( - \.[tuple.startIndex], - firstElement.with(\.label, .identifier(newLabel)) - ) -} - -public struct ColorLiteralMacro: ExpressionMacro { - public static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) -> ExprSyntax { - let argList = replaceFirstLabel( - of: macro.argumentList, - with: "_colorLiteralRed" - ) - let initSyntax: ExprSyntax = ".init(\(argList))" - return initSyntax.with(\.leadingTrivia, macro.leadingTrivia) - } -} - -public struct FileLiteralMacro: ExpressionMacro { - public static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) -> ExprSyntax { - let argList = replaceFirstLabel( - of: macro.argumentList, - with: "fileReferenceLiteralResourceName" - ) - let initSyntax: ExprSyntax = ".init(\(argList))" - return initSyntax.with(\.leadingTrivia, macro.leadingTrivia) - } -} - -public struct ImageLiteralMacro: ExpressionMacro { - public static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) -> ExprSyntax { - let argList = replaceFirstLabel( - of: macro.argumentList, - with: "imageLiteralResourceName" - ) - let initSyntax: ExprSyntax = ".init(\(argList))" - return initSyntax.with(\.leadingTrivia, macro.leadingTrivia) - } -} - -public struct ColumnMacro: ExpressionMacro { - public static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) throws -> ExprSyntax { - guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) - else { - throw MacroExpansionErrorMessage("can't find location for macro") - } - return sourceLoc.column.with(\.leadingTrivia, macro.leadingTrivia) - } -} - -public struct FileIDMacro: ExpressionMacro { - public static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) throws -> ExprSyntax { - guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) - else { - throw MacroExpansionErrorMessage("can't find location for macro") - } - return sourceLoc.file.with(\.leadingTrivia, macro.leadingTrivia) - } -} - -/// Macro whose only purpose is to ensure that we cannot see "out" of the -/// macro expansion syntax node we were given. -struct CheckContextIndependenceMacro: ExpressionMacro { - static func expansion( - of macro: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) -> ExprSyntax { - - // Should not have a parent. - XCTAssertNil(macro.parent) - - // Absolute starting position should be zero. - XCTAssertEqual(macro.position.utf8Offset, 0) - - return "()" - } -} - -public struct ErrorMacro: DeclarationMacro { - public static func expansion( - of node: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - guard let firstElement = node.argumentList.first, - let stringLiteral = firstElement.expression - .as(StringLiteralExprSyntax.self), - stringLiteral.segments.count == 1, - case let .stringSegment(messageString) = stringLiteral.segments.first - else { - throw MacroExpansionErrorMessage("#error macro requires a string literal") - } - - context.diagnose( - Diagnostic( - node: Syntax(node), - message: MacroExpansionErrorMessage(messageString.content.description) - ) - ) - - return [] - } -} - -struct DefineBitwidthNumberedStructsMacro: DeclarationMacro { +fileprivate struct ConstantOneGetter: AccessorMacro { static func expansion( - of node: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - guard let firstElement = node.argumentList.first, - let stringLiteral = firstElement.expression - .as(StringLiteralExprSyntax.self), - stringLiteral.segments.count == 1, - case let .stringSegment(prefix) = stringLiteral.segments.first - else { - throw MacroExpansionErrorMessage( - "#bitwidthNumberedStructs macro requires a string literal" - ) - } - - return [8, 16, 32, 64].map { bitwidth in - """ - - struct \(raw: prefix)\(raw: String(bitwidth)) { } - """ - } - } -} - -public struct ConstantOneGetter: AccessorMacro { - public static func expansion( of node: AttributeSyntax, providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext @@ -202,384 +38,7 @@ public struct ConstantOneGetter: AccessorMacro { } } -public struct PropertyWrapper {} - -extension PropertyWrapper: AccessorMacro { - public static func expansion( - of node: AttributeSyntax, - providingAccessorsOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [AccessorDeclSyntax] { - guard let varDecl = declaration.as(VariableDeclSyntax.self), - let binding = varDecl.bindings.first, - let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, - binding.accessorBlock == nil - else { - return [] - } - - return [ - """ - get { - _\(identifier).wrappedValue - } - """, - """ - set { - _\(identifier).wrappedValue = newValue - } - """, - ] - } -} - -extension PropertyWrapper: PeerMacro { - public static func expansion( - of node: AttributeSyntax, - providingPeersOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [SwiftSyntax.DeclSyntax] { - guard let varDecl = declaration.as(VariableDeclSyntax.self), - let binding = varDecl.bindings.first, - let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, - let type = binding.typeAnnotation?.type, - binding.accessorBlock == nil - else { - return [] - } - - guard case .argumentList(let arguments) = node.arguments, - let wrapperTypeNameExpr = arguments.first?.expression, - let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self), - stringLiteral.segments.count == 1, - case let .stringSegment(wrapperTypeNameSegment)? = stringLiteral.segments.first - else { - return [] - } - - let storageType: TypeSyntax = "\(wrapperTypeNameSegment.content)<\(type)>" - let storageName = "_\(identifier)" - - return [ - """ - - private var \(raw: storageName): \(storageType) - - """ - ] - } -} - -public struct AddCompletionHandler: PeerMacro { - public static func expansion( - of node: AttributeSyntax, - providingPeersOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - // Only on functions at the moment. We could handle initializers as well - // with a bit of work. - guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { - throw MacroExpansionErrorMessage("@addCompletionHandler only works on functions") - } - - // This only makes sense for async functions. - if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil { - throw MacroExpansionErrorMessage( - "@addCompletionHandler requires an async function" - ) - } - - // Form the completion handler parameter. - let resultType: TypeSyntax? = funcDecl.signature.returnClause?.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []) - - let completionHandlerParam = - FunctionParameterSyntax( - firstName: .identifier("completionHandler"), - colon: .colonToken(trailingTrivia: .space), - type: "(\(resultType ?? "")) -> Void" as TypeSyntax - ) - - // Add the completion handler parameter to the parameter list. - let parameterList = funcDecl.signature.parameterClause.parameters - let newParameterList: FunctionParameterListSyntax - if let lastParam = parameterList.last { - // We need to add a trailing comma to the preceding list. - let newParameterListElements = - parameterList.dropLast() - + [ - lastParam.with( - \.trailingComma, - .commaToken(trailingTrivia: .space) - ), - completionHandlerParam, - ] - newParameterList = FunctionParameterListSyntax(newParameterListElements) - } else { - newParameterList = parameterList + [completionHandlerParam] - } - - let callArguments: [String] = parameterList.map { param in - let argName = param.secondName ?? param.firstName - - if param.firstName.text != "_" { - return "\(param.firstName.text): \(argName.text)" - } - - return "\(argName.text)" - } - - let call: ExprSyntax = - "\(funcDecl.name)(\(raw: callArguments.joined(separator: ", ")))" - - // FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation, - // so that the full body could go here. - let newBody: ExprSyntax = - """ - - Task { - completionHandler(await \(call)) - } - - """ - - // Drop the @addCompletionHandler attribute from the new declaration. - let newAttributeList = funcDecl.attributes.filter { - guard case let .attribute(attribute) = $0 else { - return true - } - return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name == "addCompletionHandler" - } - - let newFunc = - funcDecl - .with( - \.signature, - funcDecl.signature - .with( - \.effectSpecifiers, - funcDecl.signature.effectSpecifiers?.with(\.asyncSpecifier, nil) // drop async - ) - .with(\.returnClause, nil) // drop result type - .with( - \.parameterClause, // add completion handler parameter - funcDecl.signature.parameterClause.with(\.parameters, newParameterList) - .with(\.trailingTrivia, []) - ) - ) - .with( - \.body, - CodeBlockSyntax( - leftBrace: .leftBraceToken(leadingTrivia: .space), - statements: CodeBlockItemListSyntax( - [CodeBlockItemSyntax(item: .expr(newBody))] - ), - rightBrace: .rightBraceToken(leadingTrivia: .newline) - ) - ) - .with(\.attributes, newAttributeList) - .with(\.leadingTrivia, .newlines(2)) - - return [DeclSyntax(newFunc)] - } -} - -public struct AddBackingStorage: MemberMacro { - public static func expansion( - of node: AttributeSyntax, - providingMembersOf decl: some DeclGroupSyntax, - in context: some MacroExpansionContext - ) - throws -> [DeclSyntax] - { - let storage: DeclSyntax = "var _storage: Storage" - return [ - storage.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] - } -} - -public struct WrapAllProperties: MemberAttributeMacro { - public static func expansion( - of node: AttributeSyntax, - attachedTo decl: some DeclGroupSyntax, - providingAttributesFor member: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [AttributeSyntax] { - guard member.is(VariableDeclSyntax.self) else { - return [] - } - - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("Wrapper") - ) - ) - .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] - } -} - -public struct WrapStoredProperties: MemberAttributeMacro { - public static func expansion( - of node: AttributeSyntax, - attachedTo decl: some DeclGroupSyntax, - providingAttributesFor member: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [AttributeSyntax] { - guard let property = member.as(VariableDeclSyntax.self), - property.bindings.count == 1 - else { - return [] - } - - let binding = property.bindings.first! - switch binding.accessorBlock?.accessors { - case .none: - break - case .accessors(let node): - for accessor in node { - switch accessor.accessorSpecifier.tokenKind { - case .keyword(.get), .keyword(.set): - return [] - default: - break - } - } - break - case .getter: - return [] - } - - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("Wrapper") - ) - ) - .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] - } -} - -struct CustomTypeWrapperMacro {} - -extension CustomTypeWrapperMacro: MemberMacro { - static func expansion< - Declaration: DeclGroupSyntax, - Context: MacroExpansionContext - >( - of node: AttributeSyntax, - providingMembersOf declaration: Declaration, - in context: Context - ) throws -> [DeclSyntax] { - return ["var _storage: Wrapper"] - } -} - -extension CustomTypeWrapperMacro: MemberAttributeMacro { - static func expansion( - of node: AttributeSyntax, - attachedTo declaration: some DeclGroupSyntax, - providingAttributesFor member: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [AttributeSyntax] { - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("customTypeWrapper") - ) - ) - .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] - } -} - -extension CustomTypeWrapperMacro: AccessorMacro { - static func expansion( - of node: AttributeSyntax, - providingAccessorsOf declaration: some DeclSyntaxProtocol, - in context: some MacroExpansionContext - ) throws -> [AccessorDeclSyntax] { - guard let property = declaration.as(VariableDeclSyntax.self), - let binding = property.bindings.first, - let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, - binding.accessorBlock == nil - else { - return [] - } - - if identifier.text == "_storage" { return [] } - - return [ - """ - get { - _storage[wrappedKeyPath: \\.\(identifier)] - } - """, - """ - set { - _storage[wrappedKeyPath: \\.\(identifier)] = newValue - } - """, - ] - } -} - -public struct UnwrapMacro: CodeItemMacro { - public static func expansion( - of node: some FreestandingMacroExpansionSyntax, - in context: some MacroExpansionContext - ) throws -> [CodeBlockItemSyntax] { - guard !node.argumentList.isEmpty else { - throw MacroExpansionErrorMessage("'#unwrap' requires arguments") - } - let errorThrower = node.trailingClosure - let identifiers = try node.argumentList.map { argument in - guard let tupleElement = argument.as(LabeledExprSyntax.self), - let declReferenceExpr = tupleElement.expression.as(DeclReferenceExprSyntax.self) - else { - throw MacroExpansionErrorMessage("Arguments must be identifiers") - } - return declReferenceExpr.baseName - } - - func elseBlock(_ token: TokenSyntax) -> CodeBlockSyntax { - let expr: ExprSyntax - if let errorThrower { - expr = """ - \(errorThrower)("\(raw: token.text)") - """ - } else { - expr = """ - fatalError("'\(raw: token.text)' is nil") - """ - } - return .init( - statements: .init([ - .init( - leadingTrivia: " ", - item: .expr(expr), - trailingTrivia: " " - ) - ]) - ) - } - - return identifiers.map { identifier in - CodeBlockItemSyntax( - item: CodeBlockItemSyntax.Item.stmt( - """ - - guard let \(raw: identifier.text) else \(elseBlock(identifier)) - """ - ) - ) - } - } -} - -public struct DeclsFromStringsMacro: DeclarationMacro, MemberMacro { +fileprivate struct DeclsFromStringsMacro: DeclarationMacro, MemberMacro { private static func decls(from arguments: LabeledExprListSyntax) -> [DeclSyntax] { var strings: [String] = [] for arg in arguments { @@ -596,14 +55,14 @@ public struct DeclsFromStringsMacro: DeclarationMacro, MemberMacro { } } - public static func expansion( + static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return decls(from: node.argumentList) } - public static func expansion( + static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext @@ -615,88 +74,78 @@ public struct DeclsFromStringsMacro: DeclarationMacro, MemberMacro { } } -public struct SendableExtensionMacro: ExtensionMacro { - public static func expansion( - of node: AttributeSyntax, - attachedTo: some DeclGroupSyntax, - providingExtensionsOf type: some TypeSyntaxProtocol, - conformingTo protocols: [TypeSyntax], +fileprivate struct StringifyMacro: ExpressionMacro { + static func expansion( + of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext - ) throws -> [ExtensionDeclSyntax] { - let sendableExtension: DeclSyntax = - """ - extension \(type.trimmed): Sendable {} - """ - - guard let extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self) else { - return [] + ) throws -> ExprSyntax { + guard let argument = macro.argumentList.first?.expression else { + throw MacroExpansionErrorMessage("missing argument") } - return [extensionDecl] + return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))" } } -public struct DeclsFromStringsMacroNoAttrs: DeclarationMacro { - public static var propagateFreestandingMacroAttributes: Bool { false } - public static var propagateFreestandingMacroModifiers: Bool { false } - - public static func expansion( - of node: some FreestandingMacroExpansionSyntax, +fileprivate struct WrapAllProperties: MemberAttributeMacro { + static func expansion( + of node: AttributeSyntax, + attachedTo decl: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext - ) throws -> [DeclSyntax] { - var strings: [String] = [] - for arg in node.argumentList { - guard - let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue - else { - continue - } - strings.append(value) + ) throws -> [AttributeSyntax] { + guard member.is(VariableDeclSyntax.self) else { + return [] } - return strings.map { - "\(raw: $0)" - } + return [ + AttributeSyntax( + attributeName: IdentifierTypeSyntax( + name: .identifier("Wrapper") + ) + ) + .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) + ] } } // MARK: Tests -/// The set of test macros we use here. -public let testMacros: [String: Macro.Type] = [ - "checkContext": CheckContextIndependenceMacro.self, - "colorLiteral": ColorLiteralMacro.self, - "column": ColumnMacro.self, - "constantOne": ConstantOneGetter.self, - "fileID": FileIDMacro.self, - "imageLiteral": ImageLiteralMacro.self, - "stringify": StringifyMacro.self, - "myError": ErrorMacro.self, - "bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self, - "wrapProperty": PropertyWrapper.self, - "addCompletionHandler": AddCompletionHandler.self, - "addBackingStorage": AddBackingStorage.self, - "wrapAllProperties": WrapAllProperties.self, - "wrapStoredProperties": WrapStoredProperties.self, - "customTypeWrapper": CustomTypeWrapperMacro.self, - "unwrap": UnwrapMacro.self, - "AddSendableExtension": SendableExtensionMacro.self, -] - final class MacroSystemTests: XCTestCase { private let indentationWidth: Trivia = .spaces(2) - func testExpressionExpansion() { + func testExpressionExpansion() { + struct ColorLiteralMacro: ExpressionMacro { + static func expansion( + of macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + var argList = macro.argumentList + argList[argList.startIndex].label = .identifier("_colorLiteralRed") + let initSyntax: ExprSyntax = ".init(\(argList))" + return initSyntax.with(\.leadingTrivia, macro.leadingTrivia) + } + } + assertMacroExpansion( """ let b = #stringify(x + y) - #colorLiteral(red: 0.5, green: 0.5, blue: 0.25, alpha: 1.0) """, expandedSource: """ let b = (x + y, "x + y") + """, + macros: ["stringify": StringifyMacro.self], + indentationWidth: indentationWidth + ) + + assertMacroExpansion( + """ + #colorLiteral(red: 0.5, green: 0.5, blue: 0.25, alpha: 1.0) + """, + expandedSource: """ .init(_colorLiteralRed: 0.5, green: 0.5, blue: 0.25, alpha: 1.0) """, - macros: testMacros, + macros: ["colorLiteral": ColorLiteralMacro.self], indentationWidth: indentationWidth ) } @@ -715,7 +164,7 @@ final class MacroSystemTests: XCTestCase { // Capture me (x, "x") """, - macros: testMacros, + macros: ["stringify": StringifyMacro.self], indentationWidth: indentationWidth ) } @@ -730,7 +179,7 @@ final class MacroSystemTests: XCTestCase { let b = /*leading */ (x + y, "x + y") /*trailing*/ """, - macros: testMacros, + macros: ["stringify": StringifyMacro.self], indentationWidth: indentationWidth ) } @@ -755,6 +204,32 @@ final class MacroSystemTests: XCTestCase { } func testLocationExpansions() { + struct ColumnMacro: ExpressionMacro { + static func expansion( + of macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> ExprSyntax { + guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) + else { + throw MacroExpansionErrorMessage("can't find location for macro") + } + return sourceLoc.column.with(\.leadingTrivia, macro.leadingTrivia) + } + } + + struct FileIDMacro: ExpressionMacro { + static func expansion( + of macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> ExprSyntax { + guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) + else { + throw MacroExpansionErrorMessage("can't find location for macro") + } + return sourceLoc.file.with(\.leadingTrivia, macro.leadingTrivia) + } + } + assertMacroExpansion( """ let b = #fileID @@ -764,7 +239,7 @@ final class MacroSystemTests: XCTestCase { let b = "MyModule/taylor.swift" let c = 9 """, - macros: testMacros, + macros: ["fileID": FileIDMacro.self, "column": ColumnMacro.self], testModuleName: "MyModule", testFileName: "taylor.swift", indentationWidth: indentationWidth @@ -782,6 +257,24 @@ final class MacroSystemTests: XCTestCase { } func testContextIndependence() { + /// Macro whose only purpose is to ensure that we cannot see "out" of the + /// macro expansion syntax node we were given. + struct CheckContextIndependenceMacro: ExpressionMacro { + static func expansion( + of macro: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + + // Should not have a parent. + XCTAssertNil(macro.parent) + + // Absolute starting position should be zero. + XCTAssertEqual(macro.position.utf8Offset, 0) + + return "()" + } + } + assertMacroExpansion( """ let b = #checkContext @@ -795,6 +288,31 @@ final class MacroSystemTests: XCTestCase { } func testErrorExpansion() { + struct ErrorMacro: DeclarationMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let firstElement = node.argumentList.first, + let stringLiteral = firstElement.expression + .as(StringLiteralExprSyntax.self), + stringLiteral.segments.count == 1, + case let .stringSegment(messageString) = stringLiteral.segments.first + else { + throw MacroExpansionErrorMessage("#error macro requires a string literal") + } + + context.diagnose( + Diagnostic( + node: Syntax(node), + message: MacroExpansionErrorMessage(messageString.content.description) + ) + ) + + return [] + } + } + assertMacroExpansion( """ #myError("please don't do that") @@ -819,12 +337,37 @@ final class MacroSystemTests: XCTestCase { DiagnosticSpec(message: "#error macro requires a string literal", line: 4, column: 3, highlight: #"#myError(bad)"#), DiagnosticSpec(message: "worse", line: 6, column: 5, highlight: #"#myError("worse")"#), ], - macros: testMacros, + macros: ["myError": ErrorMacro.self], indentationWidth: indentationWidth ) } func testBitwidthNumberedStructsExpansion() { + struct DefineBitwidthNumberedStructsMacro: DeclarationMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let firstElement = node.argumentList.first, + let stringLiteral = firstElement.expression + .as(StringLiteralExprSyntax.self), + stringLiteral.segments.count == 1, + case let .stringSegment(prefix) = stringLiteral.segments.first + else { + throw MacroExpansionErrorMessage( + "#bitwidthNumberedStructs macro requires a string literal" + ) + } + + return [8, 16, 32, 64].map { bitwidth in + """ + + struct \(raw: prefix)\(raw: String(bitwidth)) { } + """ + } + } + } + assertMacroExpansion( """ #bitwidthNumberedStructs("MyInt") @@ -839,12 +382,76 @@ final class MacroSystemTests: XCTestCase { struct MyInt64 { } """, - macros: testMacros, + macros: ["bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self], indentationWidth: indentationWidth ) } func testPropertyWrapper() { + struct PropertyWrapper: AccessorMacro, PeerMacro { + static func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AccessorDeclSyntax] { + guard let varDecl = declaration.as(VariableDeclSyntax.self), + let binding = varDecl.bindings.first, + let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, + binding.accessorBlock == nil + else { + return [] + } + + return [ + """ + get { + _\(identifier).wrappedValue + } + """, + """ + set { + _\(identifier).wrappedValue = newValue + } + """, + ] + } + + static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [SwiftSyntax.DeclSyntax] { + guard let varDecl = declaration.as(VariableDeclSyntax.self), + let binding = varDecl.bindings.first, + let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, + let type = binding.typeAnnotation?.type, + binding.accessorBlock == nil + else { + return [] + } + + guard case .argumentList(let arguments) = node.arguments, + let wrapperTypeNameExpr = arguments.first?.expression, + let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self), + stringLiteral.segments.count == 1, + case let .stringSegment(wrapperTypeNameSegment)? = stringLiteral.segments.first + else { + return [] + } + + let storageType: TypeSyntax = "\(wrapperTypeNameSegment.content)<\(type)>" + let storageName = "_\(identifier)" + + return [ + """ + + private var \(raw: storageName): \(storageType) + + """ + ] + } + } + assertMacroExpansion( """ @wrapProperty("MyWrapperType") @@ -862,7 +469,7 @@ final class MacroSystemTests: XCTestCase { private var _x: MyWrapperType """, - macros: testMacros, + macros: ["wrapProperty": PropertyWrapper.self], indentationWidth: indentationWidth ) } @@ -885,7 +492,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) @@ -910,7 +517,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) @@ -933,7 +540,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) } @@ -958,7 +565,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) } @@ -985,7 +592,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) @@ -1010,7 +617,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) @@ -1037,7 +644,7 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) } @@ -1045,13 +652,11 @@ final class MacroSystemTests: XCTestCase { func testAccessorOnVariableDeclWithMultipleBindings() { assertMacroExpansion( """ - @wrapProperty("MyWrapperType") + @constantOneGetter var x: Int, y: Int """, expandedSource: """ var x: Int, y: Int - - private var _x: MyWrapperType """, diagnostics: [ DiagnosticSpec( @@ -1062,7 +667,7 @@ final class MacroSystemTests: XCTestCase { severity: .error ) ], - macros: testMacros, + macros: ["constantOneGetter": ConstantOneGetter.self], indentationWidth: indentationWidth ) } @@ -1084,12 +689,125 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["constantOne": ConstantOneGetter.self], indentationWidth: indentationWidth ) } func testAddCompletionHandler() { + struct AddCompletionHandler: PeerMacro { + static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + // Only on functions at the moment. We could handle initializers as well + // with a bit of work. + guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { + throw MacroExpansionErrorMessage("@addCompletionHandler only works on functions") + } + + // This only makes sense for async functions. + if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil { + throw MacroExpansionErrorMessage( + "@addCompletionHandler requires an async function" + ) + } + + // Form the completion handler parameter. + let resultType: TypeSyntax? = funcDecl.signature.returnClause?.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []) + + let completionHandlerParam = + FunctionParameterSyntax( + firstName: .identifier("completionHandler"), + colon: .colonToken(trailingTrivia: .space), + type: "(\(resultType ?? "")) -> Void" as TypeSyntax + ) + + // Add the completion handler parameter to the parameter list. + let parameterList = funcDecl.signature.parameterClause.parameters + let newParameterList: FunctionParameterListSyntax + if let lastParam = parameterList.last { + // We need to add a trailing comma to the preceding list. + let newParameterListElements = + parameterList.dropLast() + + [ + lastParam.with( + \.trailingComma, + .commaToken(trailingTrivia: .space) + ), + completionHandlerParam, + ] + newParameterList = FunctionParameterListSyntax(newParameterListElements) + } else { + newParameterList = parameterList + [completionHandlerParam] + } + + let callArguments: [String] = parameterList.map { param in + let argName = param.secondName ?? param.firstName + + if param.firstName.text != "_" { + return "\(param.firstName.text): \(argName.text)" + } + + return "\(argName.text)" + } + + let call: ExprSyntax = + "\(funcDecl.name)(\(raw: callArguments.joined(separator: ", ")))" + + // FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation, + // so that the full body could go here. + let newBody: ExprSyntax = + """ + + Task { + completionHandler(await \(call)) + } + + """ + + // Drop the @addCompletionHandler attribute from the new declaration. + let newAttributeList = funcDecl.attributes.filter { + guard case let .attribute(attribute) = $0 else { + return true + } + return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name == "addCompletionHandler" + } + + let newFunc = + funcDecl + .with( + \.signature, + funcDecl.signature + .with( + \.effectSpecifiers, + funcDecl.signature.effectSpecifiers?.with(\.asyncSpecifier, nil) // drop async + ) + .with(\.returnClause, nil) // drop result type + .with( + \.parameterClause, // add completion handler parameter + funcDecl.signature.parameterClause.with(\.parameters, newParameterList) + .with(\.trailingTrivia, []) + ) + ) + .with( + \.body, + CodeBlockSyntax( + leftBrace: .leftBraceToken(leadingTrivia: .space), + statements: CodeBlockItemListSyntax( + [CodeBlockItemSyntax(item: .expr(newBody))] + ), + rightBrace: .rightBraceToken(leadingTrivia: .newline) + ) + ) + .with(\.attributes, newAttributeList) + .with(\.leadingTrivia, .newlines(2)) + + return [DeclSyntax(newFunc)] + } + } + assertMacroExpansion( """ @addCompletionHandler @@ -1104,12 +822,27 @@ final class MacroSystemTests: XCTestCase { } } """, - macros: testMacros, + macros: ["addCompletionHandler": AddCompletionHandler.self], indentationWidth: indentationWidth ) } func testAddBackingStorage() { + struct AddBackingStorage: MemberMacro { + static func expansion( + of node: AttributeSyntax, + providingMembersOf decl: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) + throws -> [DeclSyntax] + { + let storage: DeclSyntax = "var _storage: Storage" + return [ + storage.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) + ] + } + } + assertMacroExpansion( """ @addBackingStorage @@ -1124,16 +857,26 @@ final class MacroSystemTests: XCTestCase { var _storage: Storage } """, - macros: testMacros, + macros: ["addBackingStorage": AddBackingStorage.self], indentationWidth: indentationWidth ) } func testCommentAroundeAttachedMacro() { + struct TestMacro: MemberMacro { + static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + return [] + } + } + assertMacroExpansion( """ /// Some doc comment - @addBackingStorage /* trailing */ + @Test /* trailing */ struct S { var value: Int } @@ -1143,11 +886,9 @@ final class MacroSystemTests: XCTestCase { /* trailing */ struct S { var value: Int - - var _storage: Storage } """, - macros: testMacros, + macros: ["Test": TestMacro.self], indentationWidth: indentationWidth ) } @@ -1185,9 +926,53 @@ final class MacroSystemTests: XCTestCase { func test() {} } """, - macros: testMacros, + macros: ["wrapAllProperties": WrapAllProperties.self], indentationWidth: indentationWidth ) + } + + func testWrapStoredProperties() { + struct WrapStoredProperties: MemberAttributeMacro { + static func expansion( + of node: AttributeSyntax, + attachedTo decl: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AttributeSyntax] { + guard let property = member.as(VariableDeclSyntax.self), + property.bindings.count == 1 + else { + return [] + } + + let binding = property.bindings.first! + switch binding.accessorBlock?.accessors { + case .none: + break + case .accessors(let node): + for accessor in node { + switch accessor.accessorSpecifier.tokenKind { + case .keyword(.get), .keyword(.set): + return [] + default: + break + } + } + break + case .getter: + return [] + } + + return [ + AttributeSyntax( + attributeName: IdentifierTypeSyntax( + name: .identifier("Wrapper") + ) + ) + .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) + ] + } + } assertMacroExpansion( """ @@ -1223,7 +1008,7 @@ final class MacroSystemTests: XCTestCase { func test() {} } """, - macros: testMacros, + macros: ["wrapStoredProperties": WrapStoredProperties.self], indentationWidth: indentationWidth ) } @@ -1421,6 +1206,64 @@ final class MacroSystemTests: XCTestCase { } func testTypeWrapperTransform() { + struct CustomTypeWrapperMacro: MemberMacro, MemberAttributeMacro, AccessorMacro { + static func expansion< + Declaration: DeclGroupSyntax, + Context: MacroExpansionContext + >( + of node: AttributeSyntax, + providingMembersOf declaration: Declaration, + in context: Context + ) throws -> [DeclSyntax] { + return ["var _storage: Wrapper"] + } + + static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingAttributesFor member: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AttributeSyntax] { + return [ + AttributeSyntax( + attributeName: IdentifierTypeSyntax( + name: .identifier("customTypeWrapper") + ) + ) + .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) + ] + } + + static func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AccessorDeclSyntax] { + guard let property = declaration.as(VariableDeclSyntax.self), + let binding = property.bindings.first, + let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, + binding.accessorBlock == nil + else { + return [] + } + + if identifier.text == "_storage" { return [] } + + return [ + """ + get { + _storage[wrappedKeyPath: \\.\(identifier)] + } + """, + """ + set { + _storage[wrappedKeyPath: \\.\(identifier)] = newValue + } + """, + ] + } + } + assertMacroExpansion( """ @customTypeWrapper @@ -1452,13 +1295,66 @@ final class MacroSystemTests: XCTestCase { var _storage: Wrapper } """, - macros: testMacros, + macros: ["customTypeWrapper": CustomTypeWrapperMacro.self], indentationWidth: indentationWidth ) } func testUnwrap() { + struct UnwrapMacro: CodeItemMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + guard !node.argumentList.isEmpty else { + throw MacroExpansionErrorMessage("'#unwrap' requires arguments") + } + let errorThrower = node.trailingClosure + let identifiers = try node.argumentList.map { argument in + guard let tupleElement = argument.as(LabeledExprSyntax.self), + let declReferenceExpr = tupleElement.expression.as(DeclReferenceExprSyntax.self) + else { + throw MacroExpansionErrorMessage("Arguments must be identifiers") + } + return declReferenceExpr.baseName + } + + func elseBlock(_ token: TokenSyntax) -> CodeBlockSyntax { + let expr: ExprSyntax + if let errorThrower { + expr = """ + \(errorThrower)("\(raw: token.text)") + """ + } else { + expr = """ + fatalError("'\(raw: token.text)' is nil") + """ + } + return .init( + statements: .init([ + .init( + leadingTrivia: " ", + item: .expr(expr), + trailingTrivia: " " + ) + ]) + ) + } + + return identifiers.map { identifier in + CodeBlockItemSyntax( + item: CodeBlockItemSyntax.Item.stmt( + """ + + guard let \(raw: identifier.text) else \(elseBlock(identifier)) + """ + ) + ) + } + } + } + assertMacroExpansion( #""" let x: Int? = 1 @@ -1498,12 +1394,36 @@ final class MacroSystemTests: XCTestCase { }("z") } """#, - macros: testMacros, + macros: ["unwrap": UnwrapMacro.self], indentationWidth: indentationWidth ) } func testDeclsFromStringLiterals() { + struct DeclsFromStringsMacroNoAttrs: DeclarationMacro { + static var propagateFreestandingMacroAttributes: Bool { false } + static var propagateFreestandingMacroModifiers: Bool { false } + + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + var strings: [String] = [] + for arg in node.argumentList { + guard + let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue + else { + continue + } + strings.append(value) + } + + return strings.map { + "\(raw: $0)" + } + } + } + assertMacroExpansion( #""" #decls( @@ -1713,6 +1633,27 @@ final class MacroSystemTests: XCTestCase { } func testExtensionExpansion() { + struct SendableExtensionMacro: ExtensionMacro { + static func expansion( + of node: AttributeSyntax, + attachedTo: some DeclGroupSyntax, + providingExtensionsOf type: some TypeSyntaxProtocol, + conformingTo protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + let sendableExtension: DeclSyntax = + """ + extension \(type.trimmed): Sendable {} + """ + + guard let extensionDecl = sendableExtension.as(ExtensionDeclSyntax.self) else { + return [] + } + + return [extensionDecl] + } + } + assertMacroExpansion( """ @AddSendableExtension @@ -1727,12 +1668,10 @@ final class MacroSystemTests: XCTestCase { extension MyType: Sendable { } """, - macros: testMacros, + macros: ["AddSendableExtension": SendableExtensionMacro.self], indentationWidth: indentationWidth ) - } - func testNestedExtensionExpansion() { assertMacroExpansion( """ struct Wrapper { @@ -1750,7 +1689,7 @@ final class MacroSystemTests: XCTestCase { extension MyType: Sendable { } """, - macros: testMacros, + macros: ["AddSendableExtension": SendableExtensionMacro.self], indentationWidth: indentationWidth ) } @@ -1770,7 +1709,7 @@ final class MacroSystemTests: XCTestCase { var value = 1 } """, - macros: testMacros, + macros: ["wrapAllProperties": WrapAllProperties.self], indentationWidth: indentationWidth ) } From bc0fed423c842212c64543ce1e2cde1b82354cd4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 29 Aug 2023 09:19:57 -0700 Subject: [PATCH 2/2] Improvements to macros in MacroSystemTests Some general improvements to the usage of SwiftSyntax in the macros in MacroSystemTests and removal of trivia handling in these macros because trivia is being added by MacroSystem now. --- .../MacroSystemTests.swift | 180 +++++------------- 1 file changed, 46 insertions(+), 134 deletions(-) diff --git a/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift index e31880159f7..d96c8cd1385 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift @@ -10,6 +10,14 @@ // //===----------------------------------------------------------------------===// +//==========================================================================// +// IMPORTANT: The macros defined in this file are intended to test the // +// behavior of MacroSystem. Many of them do not serve as good examples of // +// how macros should be written. In particular, they often lack error // +// handling because it is not needed in the few test cases in which these // +// macros are invoked. // +//==========================================================================// + import _SwiftSyntaxTestSupport import SwiftDiagnostics import SwiftParser @@ -42,17 +50,13 @@ fileprivate struct DeclsFromStringsMacro: DeclarationMacro, MemberMacro { private static func decls(from arguments: LabeledExprListSyntax) -> [DeclSyntax] { var strings: [String] = [] for arg in arguments { - guard - let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue - else { + guard let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue else { continue } strings.append(value) } - return strings.map { - "\(raw: $0)" - } + return strings.map { "\(raw: $0)" } } static func expansion( @@ -98,14 +102,7 @@ fileprivate struct WrapAllProperties: MemberAttributeMacro { return [] } - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("Wrapper") - ) - ) - .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] + return ["@Wrapper"] } } @@ -123,7 +120,7 @@ final class MacroSystemTests: XCTestCase { var argList = macro.argumentList argList[argList.startIndex].label = .identifier("_colorLiteralRed") let initSyntax: ExprSyntax = ".init(\(argList))" - return initSyntax.with(\.leadingTrivia, macro.leadingTrivia) + return initSyntax } } @@ -209,11 +206,10 @@ final class MacroSystemTests: XCTestCase { of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { - guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) - else { + guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) else { throw MacroExpansionErrorMessage("can't find location for macro") } - return sourceLoc.column.with(\.leadingTrivia, macro.leadingTrivia) + return sourceLoc.column } } @@ -222,11 +218,10 @@ final class MacroSystemTests: XCTestCase { of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { - guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) - else { + guard let sourceLoc: AbstractSourceLocation = context.location(of: macro) else { throw MacroExpansionErrorMessage("can't find location for macro") } - return sourceLoc.file.with(\.leadingTrivia, macro.leadingTrivia) + return sourceLoc.file } } @@ -348,9 +343,7 @@ final class MacroSystemTests: XCTestCase { of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { - guard let firstElement = node.argumentList.first, - let stringLiteral = firstElement.expression - .as(StringLiteralExprSyntax.self), + guard let stringLiteral = node.argumentList.first?.expression.as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(prefix) = stringLiteral.segments.first else { @@ -394,8 +387,7 @@ final class MacroSystemTests: XCTestCase { providingAccessorsOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [AccessorDeclSyntax] { - guard let varDecl = declaration.as(VariableDeclSyntax.self), - let binding = varDecl.bindings.first, + guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, binding.accessorBlock == nil else { @@ -421,8 +413,7 @@ final class MacroSystemTests: XCTestCase { providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [SwiftSyntax.DeclSyntax] { - guard let varDecl = declaration.as(VariableDeclSyntax.self), - let binding = varDecl.bindings.first, + guard let binding = declaration.as(VariableDeclSyntax.self)?.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, let type = binding.typeAnnotation?.type, binding.accessorBlock == nil @@ -431,8 +422,7 @@ final class MacroSystemTests: XCTestCase { } guard case .argumentList(let arguments) = node.arguments, - let wrapperTypeNameExpr = arguments.first?.expression, - let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self), + let stringLiteral = arguments.first?.expression.as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(wrapperTypeNameSegment)? = stringLiteral.segments.first else { @@ -715,33 +705,23 @@ final class MacroSystemTests: XCTestCase { } // Form the completion handler parameter. - let resultType: TypeSyntax? = funcDecl.signature.returnClause?.type.with(\.leadingTrivia, []).with(\.trailingTrivia, []) + let resultType: TypeSyntax? = funcDecl.signature.returnClause?.type.trimmed let completionHandlerParam = FunctionParameterSyntax( firstName: .identifier("completionHandler"), colon: .colonToken(trailingTrivia: .space), - type: "(\(resultType ?? "")) -> Void" as TypeSyntax + type: TypeSyntax("(\(resultType ?? "")) -> Void") ) // Add the completion handler parameter to the parameter list. let parameterList = funcDecl.signature.parameterClause.parameters - let newParameterList: FunctionParameterListSyntax - if let lastParam = parameterList.last { + var newParameterList = parameterList + if !parameterList.isEmpty { // We need to add a trailing comma to the preceding list. - let newParameterListElements = - parameterList.dropLast() - + [ - lastParam.with( - \.trailingComma, - .commaToken(trailingTrivia: .space) - ), - completionHandlerParam, - ] - newParameterList = FunctionParameterListSyntax(newParameterListElements) - } else { - newParameterList = parameterList + [completionHandlerParam] + newParameterList[newParameterList.index(before: newParameterList.endIndex)].trailingComma = .commaToken(trailingTrivia: .space) } + newParameterList.append(completionHandlerParam) let callArguments: [String] = parameterList.map { param in let argName = param.secondName ?? param.firstName @@ -775,34 +755,13 @@ final class MacroSystemTests: XCTestCase { return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name == "addCompletionHandler" } - let newFunc = - funcDecl - .with( - \.signature, - funcDecl.signature - .with( - \.effectSpecifiers, - funcDecl.signature.effectSpecifiers?.with(\.asyncSpecifier, nil) // drop async - ) - .with(\.returnClause, nil) // drop result type - .with( - \.parameterClause, // add completion handler parameter - funcDecl.signature.parameterClause.with(\.parameters, newParameterList) - .with(\.trailingTrivia, []) - ) - ) - .with( - \.body, - CodeBlockSyntax( - leftBrace: .leftBraceToken(leadingTrivia: .space), - statements: CodeBlockItemListSyntax( - [CodeBlockItemSyntax(item: .expr(newBody))] - ), - rightBrace: .rightBraceToken(leadingTrivia: .newline) - ) - ) - .with(\.attributes, newAttributeList) - .with(\.leadingTrivia, .newlines(2)) + var newFunc = funcDecl + newFunc.signature.effectSpecifiers?.asyncSpecifier = nil // drop async + newFunc.signature.returnClause = nil // drop result type + newFunc.signature.parameterClause.parameters = newParameterList + newFunc.signature.parameterClause.trailingTrivia = [] + newFunc.body = CodeBlockSyntax { newBody } + newFunc.attributes = newAttributeList return [DeclSyntax(newFunc)] } @@ -833,13 +792,8 @@ final class MacroSystemTests: XCTestCase { of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext - ) - throws -> [DeclSyntax] - { - let storage: DeclSyntax = "var _storage: Storage" - return [ - storage.with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] + ) throws -> [DeclSyntax] { + return ["var _storage: Storage"] } } @@ -963,14 +917,7 @@ final class MacroSystemTests: XCTestCase { return [] } - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("Wrapper") - ) - ) - .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] + return ["@Wrapper"] } } @@ -1021,13 +968,7 @@ final class MacroSystemTests: XCTestCase { providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [AttributeSyntax] { - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("Wrapper") - ) - ) - ] + return ["@Wrapper"] } } @@ -1116,15 +1057,7 @@ final class MacroSystemTests: XCTestCase { providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [AttributeSyntax] { - return [ - AttributeSyntax( - leadingTrivia: .blockComment("/* start */"), - attributeName: IdentifierTypeSyntax( - name: .identifier("Wrapper") - ), - trailingTrivia: .blockComment("/* end */") - ) - ] + return ["/* start */@Wrapper/* end */"] } } @@ -1207,13 +1140,10 @@ final class MacroSystemTests: XCTestCase { func testTypeWrapperTransform() { struct CustomTypeWrapperMacro: MemberMacro, MemberAttributeMacro, AccessorMacro { - static func expansion< - Declaration: DeclGroupSyntax, - Context: MacroExpansionContext - >( + static func expansion( of node: AttributeSyntax, - providingMembersOf declaration: Declaration, - in context: Context + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return ["var _storage: Wrapper"] } @@ -1224,14 +1154,7 @@ final class MacroSystemTests: XCTestCase { providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [AttributeSyntax] { - return [ - AttributeSyntax( - attributeName: IdentifierTypeSyntax( - name: .identifier("customTypeWrapper") - ) - ) - .with(\.leadingTrivia, [.newlines(1), .spaces(2)]) - ] + return ["@customTypeWrapper"] } static func expansion( @@ -1342,15 +1265,8 @@ final class MacroSystemTests: XCTestCase { ) } - return identifiers.map { identifier in - CodeBlockItemSyntax( - item: CodeBlockItemSyntax.Item.stmt( - """ - - guard let \(raw: identifier.text) else \(elseBlock(identifier)) - """ - ) - ) + return identifiers.map { (identifier) -> CodeBlockItemSyntax in + "guard let \(raw: identifier.text) else \(elseBlock(identifier))" } } } @@ -1410,17 +1326,13 @@ final class MacroSystemTests: XCTestCase { ) throws -> [DeclSyntax] { var strings: [String] = [] for arg in node.argumentList { - guard - let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue - else { + guard let value = arg.expression.as(StringLiteralExprSyntax.self)?.representedLiteralValue else { continue } strings.append(value) } - return strings.map { - "\(raw: $0)" - } + return strings.map { "\(raw: $0)" } } }