import SwiftDiagnostics import SwiftOperators import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros /// 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{ if tuple.isEmpty { return tuple } return tuple.with(\.[tuple.startIndex].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 } } public struct FileIDMacro: ExpressionMacro { public static func expansion< Node: FreestandingMacroExpansionSyntax, Context: MacroExpansionContext >( of macro: Node, in context: Context ) throws -> ExprSyntax { guard let sourceLoc = context.location(of: macro) else { throw CustomError.message("can't find location for macro") } let fileLiteral: ExprSyntax = "\(sourceLoc.file)" return fileLiteral } } public struct AssertMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { guard let argument = macro.argumentList.first?.expression else { fatalError("boom") } return "assert(\(argument))" } } public struct StringifyMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { guard let argument = macro.argumentList.first?.expression else { fatalError("boom") } return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))" } } public struct ExprAndDeclMacro: ExpressionMacro, DeclarationMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { guard let argument = macro.argumentList.first?.expression else { fatalError("boom") } return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))" } public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> [DeclSyntax] { return [] } } public struct StringifyAndTryMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { guard let argument = macro.argumentList.first?.expression else { fatalError("boom") } return "(try \(argument), \(StringLiteralExprSyntax(content: argument.description)))" } } struct SimpleDiagnosticMessage: DiagnosticMessage { let message: String let diagnosticID: MessageID let severity: DiagnosticSeverity } extension SimpleDiagnosticMessage: FixItMessage { var fixItID: MessageID { diagnosticID } } public enum AddBlocker: ExpressionMacro { class AddVisitor: SyntaxRewriter { var diagnostics: [Diagnostic] = [] override func visit( _ node: InfixOperatorExprSyntax ) -> ExprSyntax { if let binOp = node.operator.as(BinaryOperatorExprSyntax.self) { if binOp.operator.text == "+" { let messageID = MessageID(domain: "silly", id: "addblock") diagnostics.append( Diagnostic( node: Syntax(node.operator), message: SimpleDiagnosticMessage( message: "blocked an add; did you mean to subtract?", diagnosticID: messageID, severity: .error ), highlights: [ Syntax(node.leftOperand), Syntax(node.rightOperand) ], fixIts: [ FixIt( message: SimpleDiagnosticMessage( message: "use '-'", diagnosticID: messageID, severity: .error ), changes: [ FixIt.Change.replace( oldNode: Syntax(binOp.operator), newNode: Syntax( TokenSyntax( .binaryOperator("-"), presence: .present ) ) ) ] ), ] ) ) let minusOperator = binOp.with(\.operator.tokenKind, .binaryOperator("-")) return ExprSyntax(node.with(\.operator, ExprSyntax(minusOperator))) } } return ExprSyntax(node) } } public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { let visitor = AddVisitor() let result = visitor.rewrite(Syntax(node)) for diag in visitor.diagnostics { context.diagnose(diag) } return result.asProtocol(FreestandingMacroExpansionSyntax.self)!.argumentList.first!.expression } } public class RecursiveMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { guard let argument = macro.argumentList.first?.expression, argument.description == "false" else { return "\(macro)" } return "()" } } public class NestedDeclInExprMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { return """ { () -> Void in struct Foo { } return () } """ } } enum CustomError: Error, CustomStringConvertible { case message(String) var description: String { switch self { case .message(let text): return text } } } public struct EmptyDeclarationMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [] } } public struct DefineBitwidthNumberedStructsMacro: 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(prefix) = stringLiteral.segments.first else { throw CustomError.message("#bitwidthNumberedStructs macro requires a string literal") } if prefix.content.text == "BUG" { return [ """ struct \(raw: prefix) { func \(context.makeUniqueName("method"))() { return 0 } func \(context.makeUniqueName("method"))() { return 1 } } """ ] } return [8, 16, 32, 64].map { bitwidth in """ struct \(raw: prefix)\(raw: String(bitwidth)) { func \(context.makeUniqueName("method"))() { } func \(context.makeUniqueName("method"))() { } } """ } } } public struct DefineDeclsWithKnownNamesMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ """ struct A { func \(context.makeUniqueName("method"))() { } func \(context.makeUniqueName("method"))() { } } """, """ struct B { func \(context.makeUniqueName("method"))() { } func \(context.makeUniqueName("method"))() { } } """, """ var foo: Int { 1 } """, """ var addOne: (Int) -> Int { { $0 + 1 } } """ // FIXME: // 1. Stored properties are not visited in IRGen // let addTwo: (Int) -> Int = { $0 + 2 } // 2. Curry thunk at call sites // func foo() // Foo2.foo // AutoClosureExpr with invalid discriminator ] } } public struct VarDeclMacro: CodeItemMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [CodeBlockItemSyntax] { let name = context.makeUniqueName("fromMacro") return [ "let \(name) = 23", "use(\(name))", """ if true { let \(name) = "string" use(\(name)) } """ ] } } public struct WarningMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { guard let firstElement = macro.argumentList.first, let stringLiteral = firstElement.expression .as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(messageString)? = stringLiteral.segments.first else { throw CustomError.message("#myWarning macro requires a string literal") } context.diagnose( Diagnostic( node: Syntax(macro), message: SimpleDiagnosticMessage( message: messageString.content.description, diagnosticID: MessageID(domain: "test", id: "error"), severity: .warning ) ) ) return "()" } } public struct ErrorMacro: ExpressionMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { guard let firstElement = macro.argumentList.first, let stringLiteral = firstElement.expression.as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(messageString)? = stringLiteral.segments.first else { let errorNode: Syntax if let firstElement = macro.argumentList.first { errorNode = Syntax(firstElement) } else { errorNode = Syntax(macro) } let messageID = MessageID(domain: "silly", id: "error") let diag = Diagnostic( node: errorNode, message: SimpleDiagnosticMessage( message: "#myError macro requires a string literal", diagnosticID: messageID, severity: .error ) ) throw DiagnosticsError(diagnostics: [diag]) } context.diagnose( Diagnostic( node: Syntax(macro), message: SimpleDiagnosticMessage( message: messageString.content.description, diagnosticID: MessageID(domain: "test", id: "error"), severity: .error ) ) ) return "()" } } public struct PropertyWrapperMacro {} extension PropertyWrapperMacro: AccessorMacro, Macro { 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 else { return [] } return [ """ get { _\(identifier).wrappedValue } """, """ set { _\(identifier).wrappedValue = newValue } """, ] } } extension PropertyWrapperMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let varDecl = declaration.as(VariableDeclSyntax.self), let binding = varDecl.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, binding.accessorBlock == nil, let type = binding.typeAnnotation?.type else { return [] } return [ """ var _\(raw: identifier.trimmedDescription): MyWrapperThingy<\(type)> """ ] } } extension AccessorBlockSyntax { var hasGetter: Bool { switch self.accessors { case .accessors(let accessors): for accessor in accessors { if accessor.accessorSpecifier.text == "get" { return true } } return false case .getter: return true } } } public struct PropertyWrapperSkipsComputedMacro {} extension PropertyWrapperSkipsComputedMacro: AccessorMacro, Macro { 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?.hasGetter ?? false) else { return [] } return [ """ get { _\(identifier).wrappedValue } """, """ set { _\(identifier).wrappedValue = newValue } """, ] } } public struct WillSetMacro: 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 else { return [] } return [ """ willSet { } """ ] } } public struct WrapAllProperties: MemberAttributeMacro { public static func expansion( of node: AttributeSyntax, attachedTo parent: some DeclGroupSyntax, providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [AttributeSyntax] { guard member.is(VariableDeclSyntax.self) else { return [] } let wrapperTypeName: String if parent.is(ClassDeclSyntax.self) { wrapperTypeName = "EnclosingSelfWrapper" } else { wrapperTypeName = "Wrapper" } let propertyWrapperAttr = AttributeSyntax( attributeName: IdentifierTypeSyntax( name: .identifier(wrapperTypeName) ) ) return [propertyWrapperAttr] } } public struct TypeWrapperMacro {} extension TypeWrapperMacro: MemberAttributeMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [AttributeSyntax] { guard let varDecl = member.as(VariableDeclSyntax.self), let binding = varDecl.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, binding.accessorBlock == nil else { return [] } if identifier.text == "_storage" { return [] } let customAttr = AttributeSyntax( attributeName: IdentifierTypeSyntax( name: .identifier("accessViaStorage") ) ) return [customAttr] } } extension TypeWrapperMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { let storageVariable: DeclSyntax = """ private var _storage = _Storage() """ return [ storageVariable, ] } } public struct AccessViaStorageMacro: 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 [] } if identifier.text == "_storage" { return [] } return [ "get { _storage.\(identifier) }", "set { _storage.\(identifier) = newValue }", ] } } public struct AddMembers: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { let uniqueClassName = context.makeUniqueName("uniqueClass") let storageStruct: DeclSyntax = """ struct Storage {} """ let storageVariable: DeclSyntax = """ private var storage = Storage() """ let instanceMethod: DeclSyntax = """ func getStorage() -> Storage { print("synthesized method") return storage } """ let staticMethod: DeclSyntax = """ static func method() {} """ let initDecl: DeclSyntax = """ init() { _ = \(uniqueClassName)() } """ let classDecl: DeclSyntax = """ class \(uniqueClassName) { } """ return [ storageStruct, storageVariable, instanceMethod, staticMethod, initDecl, classDecl, ] } } public struct AddExtMembers: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { let uniqueClassName = context.makeUniqueName("uniqueClass") let instanceMethod: DeclSyntax = """ func extInstanceMethod() {} """ let staticMethod: DeclSyntax = """ static func extStaticMethod() {} """ let classDecl: DeclSyntax = """ class \(uniqueClassName) { } """ return [ instanceMethod, staticMethod, classDecl, ] } } public struct AddArbitraryMembers: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let identified = decl.asProtocol(NamedDeclSyntax.self) else { return [] } let parentName = identified.name.trimmed return [ "struct \(parentName)1 {}", "struct \(parentName)2 {}", "struct \(parentName)3 {}", ] } } /// Implementation of the `wrapStoredProperties` macro, which can be /// used to apply an attribute to all of the stored properties of a type. /// /// This macro demonstrates member-attribute macros. public struct WrapStoredPropertiesMacro: MemberAttributeMacro { public static func expansion< Declaration: DeclGroupSyntax, Context: MacroExpansionContext >( of node: AttributeSyntax, attachedTo decl: Declaration, providingAttributesFor member: some DeclSyntaxProtocol, in context: Context ) throws -> [AttributeSyntax] { guard let property = member.as(VariableDeclSyntax.self), property.isStoredProperty else { return [] } guard case let .argumentList(arguments) = node.arguments, let firstElement = arguments.first, let stringLiteral = firstElement.expression .as(StringLiteralExprSyntax.self), stringLiteral.segments.count == 1, case let .stringSegment(wrapperName)? = stringLiteral.segments.first else { throw CustomError.message("macro requires a string literal containing the name of an attribute") } return [ AttributeSyntax( attributeName: IdentifierTypeSyntax( name: .identifier(wrapperName.content.text) ) ) ] } } extension VariableDeclSyntax { /// Determine whether this variable has the syntax of a stored property. /// /// This syntactic check cannot account for semantic adjustments due to, /// e.g., accessor macros or property wrappers. var isStoredProperty: Bool { if bindings.count != 1 { return false } let binding = bindings.first! switch binding.accessorBlock?.accessors { case .none: return true case .accessors(let node): for accessor in node { switch accessor.accessorSpecifier.tokenKind { case .keyword(.willSet), .keyword(.didSet): // Observers can occur on a stored property. break default: // Other accessors make it a computed property. return false } } return true case .getter: return false @unknown default: return false } } } extension DeclGroupSyntax { /// Enumerate the stored properties that syntactically occur in this /// declaration. func storedProperties() -> [VariableDeclSyntax] { return memberBlock.members.compactMap { member in guard let variable = member.decl.as(VariableDeclSyntax.self), variable.isStoredProperty else { return nil } return variable } } } public enum LeftHandOperandFinderMacro: ExpressionMacro { class Visitor: SyntaxVisitor { let context: Context init(context: Context) { self.context = context super.init(viewMode: .sourceAccurate) } override func visit( _ node: InfixOperatorExprSyntax ) -> SyntaxVisitorContinueKind { guard let lhsStartLoc = context.location(of: node.leftOperand), let lhsEndLoc = context.location( of: node.leftOperand, at: .beforeTrailingTrivia, filePathMode: .fileID ) else { fatalError("missing source location information") } print("Source range for LHS is \(lhsStartLoc.file): \(lhsStartLoc.line):\(lhsStartLoc.column)-\(lhsEndLoc.line):\(lhsEndLoc.column)") return .visitChildren } } public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { let visitor = Visitor(context: context) visitor.walk(node) return node.argumentList.first!.expression } } 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 CustomError.message("@addCompletionHandler only works on functions") } // This only makes sense for async functions. if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil { throw CustomError.message( "@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(), type: "@escaping (\(resultType ?? "")) -> Void" as TypeSyntax ) // Add the completion handler parameter to the parameter list. let parameterList = funcDecl.signature.parameterClause.parameters var newParameterList = parameterList if !newParameterList.isEmpty { // We need to add a trailing comma to the preceding list. let lastIndex = newParameterList.index(before: newParameterList.endIndex) newParameterList[lastIndex].trailingComma = .commaToken() } newParameterList.append(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, let attributeType = attribute.attributeName.as(IdentifierTypeSyntax.self), let nodeType = node.attributeName.as(IdentifierTypeSyntax.self) else { return true } return attributeType.name.text != nodeType.name.text } 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( leftBrace: .leftBraceToken(), statements: CodeBlockItemListSyntax( [CodeBlockItemSyntax(item: .expr(newBody))] ), rightBrace: .rightBraceToken() ) newFunc.attributes = newAttributeList return [DeclSyntax(newFunc)] } } public struct InvalidMacro: PeerMacro, DeclarationMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ "import Swift", "precedencegroup MyPrecedence {}", "@attached(member) macro myMacro()", "extension Int {}", """ @main struct MyMain { static func main() {} } """, "typealias Array = Void", "typealias Dictionary = Void", "typealias BooleanLiteralType = Void", "typealias ExtendedGraphemeClusterType = Void", "typealias FloatLiteralType = Void", "typealias IntegerLiteralType = Void", "typealias StringLiteralType = Void", "typealias UnicodeScalarType = Void", "typealias _ColorLiteralType = Void", "typealias _ImageLiteralType = Void", "typealias _FileReferenceLiteralType = Void", ] } public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ "var value: Int" ] } } public struct CoerceToIntMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { "\(node.argumentList.first!.expression) as Int" } } public struct WrapInType: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { throw CustomError.message("@wrapInType only applies to functions") } // Build a new function with the same signature that forwards arguments // to the the original function. let parameterList = funcDecl.signature.parameterClause.parameters 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: ", "))) """ // Drop the peer macro attribute from the new declaration. let newAttributeList = funcDecl.attributes.filter { guard case let .attribute(attribute) = $0, let attributeType = attribute.attributeName.as(IdentifierTypeSyntax.self), let nodeType = node.attributeName.as(IdentifierTypeSyntax.self) else { return true } return attributeType.name.text != nodeType.name.text } var method = funcDecl method.name = "\(context.makeUniqueName(funcDecl.name.text))" method.signature = funcDecl.signature method.body = CodeBlockSyntax( leftBrace: .leftBraceToken(), statements: CodeBlockItemListSyntax( [CodeBlockItemSyntax(item: .expr(call))] ), rightBrace: .rightBraceToken() ) method.attributes = newAttributeList let structType: DeclSyntax = """ struct \(context.makeUniqueName(funcDecl.name.text)) { \(method) } """ return [structType] } } private extension DeclSyntaxProtocol { var isObservableStoredProperty: Bool { if let property = self.as(VariableDeclSyntax.self), let binding = property.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, identifier.text != "_registrar", identifier.text != "_storage", binding.accessorBlock == nil { return true } return false } } public struct ObservableMacro: MemberMacro, MemberAttributeMacro { // MARK: - MemberMacro public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let identified = declaration.asProtocol(NamedDeclSyntax.self) else { return [] } let parentName = identified.name let registrar: DeclSyntax = """ let _registrar = ObservationRegistrar<\(parentName)>() """ let addObserver: DeclSyntax = """ public nonisolated func addObserver(_ observer: some Observer<\(parentName)>) { _registrar.addObserver(observer) } """ let removeObserver: DeclSyntax = """ public nonisolated func removeObserver(_ observer: some Observer<\(parentName)>) { _registrar.removeObserver(observer) } """ let withTransaction: DeclSyntax = """ private func withTransaction(_ apply: () throws -> T) rethrows -> T { _registrar.beginAccess() defer { _registrar.endAccess() } return try apply() } """ let memberList = declaration.memberBlock.members.filter { $0.decl.isObservableStoredProperty } let storageStruct: DeclSyntax = """ private struct Storage { \(memberList) } """ let storage: DeclSyntax = """ private var _storage = Storage() """ return [ registrar, addObserver, removeObserver, withTransaction, storageStruct, storage, ] } // MARK: - MemberAttributeMacro public static func expansion( of node: AttributeSyntax, attachedTo declaration: some DeclGroupSyntax, providingAttributesFor member: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [SwiftSyntax.AttributeSyntax] { guard member.isObservableStoredProperty else { return [] } return [ AttributeSyntax( attributeName: IdentifierTypeSyntax( name: .identifier("ObservableProperty") ) ) ] } } extension ObservableMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { if (protocols.isEmpty) { return [] } let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): Observable { } """ return [ decl.cast(ExtensionDeclSyntax.self) ] } } public struct ObservablePropertyMacro: AccessorMacro { public 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 [] } let getAccessor: AccessorDeclSyntax = """ get { _registrar.beginAccess(\\.\(identifier)) defer { _registrar.endAccess() } return _storage.\(identifier) } """ let setAccessor: AccessorDeclSyntax = """ set { _registrar.beginAccess(\\.\(identifier)) _registrar.register(observable: self, willSet: \\.\(identifier), to: newValue) defer { _registrar.register(observable: self, didSet: \\.\(identifier)) _registrar.endAccess() } _storage.\(identifier) = newValue } """ return [getAccessor, setAccessor] } } extension DeclModifierSyntax { fileprivate var isNeededAccessLevelModifier: Bool { switch self.name.tokenKind { case .keyword(.public): return true default: return false } } } extension SyntaxStringInterpolation { fileprivate mutating func appendInterpolation(_ node: Node?) { if let node { appendInterpolation(node) } } } public struct NewTypeMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let type = node.attributeName.as(IdentifierTypeSyntax.self), let genericArguments = type.genericArgumentClause?.arguments, genericArguments.count == 1, let rawType = genericArguments.first else { throw CustomError.message(#"@NewType requires the raw type as an argument, in the form ""."#) } guard let declaration = declaration.as(StructDeclSyntax.self) else { throw CustomError.message("@NewType can only be applied to a struct declarations.") } let access = declaration.modifiers.first(where: \.isNeededAccessLevelModifier) return [ "\(access)typealias RawValue = \(rawType)", "\(access)var rawValue: RawValue", "\(access)init(_ rawValue: RawValue) { self.rawValue = rawValue }", ] } } public struct EmptyMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [] } } public struct EmptyPeerMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [] } } public struct EquatableMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let ext: DeclSyntax = "extension \(type.trimmed): Equatable {}" return [ext.cast(ExtensionDeclSyntax.self)] } } public struct EquatableViaMembersMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let comparisons: [String] = decl.storedProperties().map { property in guard let binding = property.bindings.first, let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else { return "true" } return "lhs.\(identifier) == rhs.\(identifier)" } let condition = comparisons.joined(separator: " && ") let equalOperator: DeclSyntax = """ static func ==(lhs: \(type.trimmed), rhs: \(type.trimmed)) -> Bool { return \(raw: condition) } """ let ext: DeclSyntax = """ extension \(type.trimmed): Equatable { \(equalOperator) } """ return [ext.cast(ExtensionDeclSyntax.self)] } } public struct ConformanceViaExtensionMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { if (protocols.isEmpty) { return [] } let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): MyProtocol { } """ return [ decl.cast(ExtensionDeclSyntax.self) ] } } public struct HashableMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let ext: DeclSyntax = "extension \(type.trimmed): Hashable {}" return [ext.cast(ExtensionDeclSyntax.self)] } } public struct ImpliesHashableMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let ext: DeclSyntax = "extension \(type.trimmed): ImpliesHashable {}" return [ext.cast(ExtensionDeclSyntax.self)] } } public struct DelegatedConformanceMacro: ExtensionMacro, MemberMacro { public static func expansion( of node: AttributeSyntax, attachedTo: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let conformance: DeclSyntax = """ extension \(type.trimmed): P where Element: P {} """ guard let extensionDecl = conformance.as(ExtensionDeclSyntax.self) else { return [] } return [extensionDecl] } public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { let requirement: DeclSyntax = """ static func requirement() where Element : P { Element.requirement() } """ return [requirement] } } public struct DelegatedConformanceViaExtensionMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { if (protocols.isEmpty) { return [] } let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): P where Element: P { static func requirement() { Element.requirement() } } """ guard let extensionDecl = decl.as(ExtensionDeclSyntax.self) else { return [] } return [ extensionDecl ] } } public struct AlwaysAddConformance: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): P where Element: P { static func requirement() { Element.requirement() } } """ return [ decl.cast(ExtensionDeclSyntax.self) ] } } public struct ConditionallyAvailableConformance: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let decl: DeclSyntax = """ @available(macOS 99, *) extension \(raw: type.trimmedDescription): Equatable {} """ return [ decl.cast(ExtensionDeclSyntax.self) ] } } public struct AddAllConformancesMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { protocols.map { proto in let decl: DeclSyntax = """ extension \(type): \(proto) {} """ return decl.cast(ExtensionDeclSyntax.self) } } } public struct AlwaysAddCodable: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo decl: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let decl: DeclSyntax = """ extension \(raw: type.trimmedDescription): Codable { } """ return [ decl.cast(ExtensionDeclSyntax.self) ] } } public struct ExtendableEnum: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf declaration: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { let unknownDecl: DeclSyntax = """ func unknown() -> Int { 34 } // or something like: `case unknown` """ return [unknownDecl] } } public struct DefineStructWithUnqualifiedLookupMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [""" struct StructWithUnqualifiedLookup { let hello = 1 func foo() -> Int { hello + world // looks up "world" in the parent scope } } """] } } public struct AddMemberWithFixIt: MemberMacro { public static func expansion< Declaration: DeclGroupSyntax, Context: MacroExpansionContext >( of node: AttributeSyntax, providingMembersOf declaration: Declaration, in context: Context ) throws -> [DeclSyntax] { [ """ func foo() { var x = 0 _ = x } """ ] } } extension LabeledExprListSyntax { /// Retrieve the first element with the given label. func first(labeled name: String) -> Element? { return first { element in if let label = element.label, label.text == name { return true } return false } } } public struct DefineAnonymousTypesMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let body = node.trailingClosure else { throw CustomError.message("#anonymousTypes macro requires a trailing closure") } let accessSpecifier: String if let _ = node.argumentList.first(labeled: "public") { accessSpecifier = "public " } else { accessSpecifier = "" } var results: [DeclSyntax] = [ """ \(raw:accessSpecifier)class \(context.makeUniqueName("name")) { \(raw:accessSpecifier)func hello() -> String { \(body.statements) } \(raw:accessSpecifier)func getSelf() -> Any.Type { return Self.self } } """, """ enum \(context.makeUniqueName("name")) { case apple case banana func hello() -> String { \(body.statements) } } """, """ struct \(context.makeUniqueName("name")): Equatable { static func == (lhs: Self, rhs: Self) -> Bool { false } } """ ] if let _ = node.argumentList.first(labeled: "causeErrors") { results += [""" struct \(context.makeUniqueName("name")) where T == Equatable { // expect error: need 'any' #introduceTypeCheckingErrors // make sure we get nested errors } """] } return results } } public struct IntroduceTypeCheckingErrorsMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> [DeclSyntax] { return [ """ struct \(context.makeUniqueName("name")) { struct \(context.makeUniqueName("name")) where T == Hashable { // expect error: need 'any' } } """ ] } } public struct AddClassReferencingSelfMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else { throw CustomError.message("Macro can only be applied to a protocol declarations.") } let className = "\(protocolDecl.name.text)Builder" return [ """ struct \(raw: className) { init(_ build: (_ builder: Self) -> Self) { _ = build(self) } } """ ] } } public struct SimpleCodeItemMacro: CodeItemMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [CodeBlockItemSyntax] { [ .init(item: .decl(""" struct \(context.makeUniqueName("foo")) { var x: Int } """)), .init(item: .stmt(""" if true { print("from stmt") usedInExpandedStmt() } """)), .init(item: .stmt(""" if false { print("impossible") } """)), .init(item: .expr(""" print("from expr") """)), ] } } public struct MultiStatementClosure: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> ExprSyntax { return """ { let temp = 10 let result = temp return result }() """ } } public struct VarValueMacro: DeclarationMacro, PeerMacro { public static func expansion( of macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> [DeclSyntax] { return [ "var value: Int { 1 }" ] } public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ "var value: Int { 1 }" ] } } public struct SingleMemberMacro: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { let member: DeclSyntax = """ var expandedMember: Int = 10 """ return [ member, ] } } public struct UseIdentifierMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let argument = node.argumentList.first?.expression.as(StringLiteralExprSyntax.self) else { fatalError("boom") } return [ """ func \(context.makeUniqueName("name"))() { _ = \(raw: argument.segments.first!) } """, """ struct Foo { var \(context.makeUniqueName("name")): Int { _ = \(raw: argument.segments.first!) return 0 } } """ ] } } public struct StaticFooFuncMacro: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ "static func foo() {}", ] } } public struct SelfAlwaysEqualOperator: DeclarationMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ "static func ==(lhs: Self, rhs: Bool) -> Bool { true }", ] } } extension SelfAlwaysEqualOperator: MemberMacro { public static func expansion( of node: AttributeSyntax, providingMembersOf decl: some DeclGroupSyntax, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ "static func ==(lhs: Self, rhs: Bool) -> Bool { true }", ] } } public struct AddPeerStoredPropertyMacro: PeerMacro, Sendable { public static func expansion( of attribute: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { return [ """ private var _foo: Int = 100 """ ] } } public struct InitializableMacro: ExtensionMacro { public static func expansion( of node: AttributeSyntax, attachedTo: some DeclGroupSyntax, providingExtensionsOf type: some TypeSyntaxProtocol, conformingTo protocols: [TypeSyntax], in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { let ext: DeclSyntax = """ extension \(type.trimmed): Initializable { init(value: Int) {} } """ return [ext.cast(ExtensionDeclSyntax.self)] } } public struct PeerValueWithSuffixNameMacro: PeerMacro { public static func expansion( of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext ) throws -> [DeclSyntax] { guard let identified = declaration.asProtocol(NamedDeclSyntax.self) else { throw CustomError.message("Macro can only be applied to an identified declarations.") } return ["var \(raw: identified.name.text)_peer: Int { 1 }"] } } public struct MagicFileMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { return "#file" } } public struct MagicLineMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { return "(#line)" } } public struct NestedMagicLiteralMacro: ExpressionMacro { public static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { return """ { print(#MagicFile) print(#MagicLine) }() """ } }