diff --git a/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift b/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift index c01ad205b27..5a764af2b3b 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift @@ -87,9 +87,10 @@ extension CompilerPluginMessageHandler { ) try self.sendMessage(.getCapabilityResult(capability: capability)) - case .expandFreestandingMacro(let macro, let discriminator, let expandingSyntax): + case .expandFreestandingMacro(let macro, let macroRole, let discriminator, let expandingSyntax): try expandFreestandingMacro( macro: macro, + macroRole: macroRole, discriminator: discriminator, expandingSyntax: expandingSyntax ) diff --git a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift index 72b9df65586..a00f1d2e7b2 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift @@ -14,16 +14,28 @@ import SwiftDiagnostics import SwiftSyntax /// Errors in macro handing. -enum MacroExpansionError: String { - case macroTypeNotFound = "macro expanding type not found" - case unmathedMacroRole = "macro doesn't conform to required macro role" - case freestandingMacroSyntaxIsNotMacro = "macro syntax couldn't be parsed" - case invalidExpansionMessage = "internal message error; please file a bug report" +enum MacroExpansionError { + case macroTypeNotFound(PluginMessage.MacroReference) + case freestandingMacroSyntaxIsNotMacro + case invalidExpansionMessage + case invalidMacroRole(PluginMessage.MacroRole) } extension MacroExpansionError: DiagnosticMessage { var message: String { - self.rawValue + switch self { + case .macroTypeNotFound(let ref): + return "macro type '\(ref.moduleName).\(ref.typeName)' not found when expanding macro '\(ref.name)'" + + case .freestandingMacroSyntaxIsNotMacro: + return "macro syntax couldn't be parsed" + + case .invalidExpansionMessage: + return "internal message error; please file a bug report" + + case .invalidMacroRole(let role): + return "invalid macro role '\(role)' for expansion" + } } var diagnosticID: SwiftDiagnostics.MessageID { .init(domain: "SwiftCompilerPlugin", id: "\(type(of: self)).\(self)") diff --git a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift index 7731e7b39fd..d2edb7562fa 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift @@ -26,6 +26,7 @@ extension CompilerPluginMessageHandler { /// Expand `@freestainding(XXX)` macros. func expandFreestandingMacro( macro: PluginMessage.MacroReference, + macroRole pluginMacroRole: PluginMessage.MacroRole?, discriminator: String, expandingSyntax: PluginMessage.Syntax ) throws { @@ -43,11 +44,19 @@ extension CompilerPluginMessageHandler { throw MacroExpansionError.freestandingMacroSyntaxIsNotMacro } guard let macroDefinition = resolveMacro(macro) else { - throw MacroExpansionError.macroTypeNotFound + throw MacroExpansionError.macroTypeNotFound(macro) + } + + let macroRole: MacroRole + if let pluginMacroRole { + macroRole = MacroRole(messageMacroRole: pluginMacroRole) + } else { + macroRole = try inferFreestandingMacroRole(definition: macroDefinition) } expandedSource = SwiftSyntaxMacroExpansion.expandFreestandingMacro( definition: macroDefinition, + macroRole: macroRole, node: macroSyntax, in: context ) @@ -89,7 +98,7 @@ extension CompilerPluginMessageHandler { let expandedSources: [String]? do { guard let macroDefinition = resolveMacro(macro) else { - throw MacroExpansionError.macroTypeNotFound + throw MacroExpansionError.macroTypeNotFound(macro) } expandedSources = SwiftSyntaxMacroExpansion.expandAttachedMacro( diff --git a/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift b/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift index 414598a184d..02b350bf8e3 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift @@ -20,6 +20,7 @@ internal enum HostToPluginMessage: Codable { /// Expand a '@freestanding' macro. case expandFreestandingMacro( macro: PluginMessage.MacroReference, + macroRole: PluginMessage.MacroRole? = nil, discriminator: String, syntax: PluginMessage.Syntax ) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift index 18af9926cce..7df14081107 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -12,19 +12,55 @@ public enum MacroRole { case codeItem } +extension MacroRole { + var protocolName: String { + switch self { + case .expression: return "ExpressionMacro" + case .declaration: return "DeclarationMacro" + case .accessor: return "AccessorMacro" + case .memberAttribute: return "MemberAttributeMacro" + case .member: return "MemberMacro" + case .peer: return "PeerMacro" + case .conformance: return "ConformanceMacro" + case .codeItem: return "CodeItemMacro" + } + } +} + /// Simple diagnostic message -private enum MacroExpansionError: String, Error, CustomStringConvertible { - case unmathedMacroRole = "macro doesn't conform to required macro role" - case parentDeclGroupNil = "parent decl group is nil" - case declarationNotDeclGroup = "declaration is not a decl group syntax" - case declarationNotIdentified = "declaration is not a 'Identified' syntax" - var description: String { self.rawValue } +private enum MacroExpansionError: Error, CustomStringConvertible { + case unmatchedMacroRole(Macro.Type, MacroRole) + case parentDeclGroupNil + case declarationNotDeclGroup + case declarationNotIdentified + case noFreestandingMacroRoles(Macro.Type) + + var description: String { + switch self { + case .unmatchedMacroRole(let type, let role): + return "macro implementation type '\(type)' doesn't conform to required protocol '\(role.protocolName)'" + + case .parentDeclGroupNil: + return "parent decl group is nil" + + case .declarationNotDeclGroup: + return "declaration is not a decl group syntax" + + case .declarationNotIdentified: + return "declaration is not a 'Identified' syntax" + + case .noFreestandingMacroRoles(let type): + return "macro implementation type '\(type)' does not conform to any freestanding macro protocol" + + } + } } /// Expand `@freestanding(XXX)` macros. /// /// - Parameters: /// - definition: a type conforms to one of freestanding `Macro` protocol. +/// - macroRole: indicates which `Macro` protocol expansion should be performed /// - node: macro expansion syntax node (e.g. `#macroName(argument)`). /// - in: context of the expansion. /// - Returns: expanded source text. Upon failure (i.e. `defintion.expansion()` @@ -32,17 +68,18 @@ private enum MacroExpansionError: String, Error, CustomStringConvertible { /// guaranteed to be added to context. public func expandFreestandingMacro( definition: Macro.Type, + macroRole: MacroRole, node: FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> String? { do { func _expand(node: some FreestandingMacroExpansionSyntax) throws -> String { let expandedSyntax: Syntax - switch definition { - case let exprMacroDef as ExpressionMacro.Type: + switch (macroRole, definition) { + case (.expression, let exprMacroDef as ExpressionMacro.Type): expandedSyntax = try Syntax(exprMacroDef.expansion(of: node, in: context)) - case let declMacroDef as DeclarationMacro.Type: + case (.declaration, let declMacroDef as DeclarationMacro.Type): var rewritten = try declMacroDef.expansion(of: node, in: context) // Copy attributes and modifiers to the generated decls. if let expansionDecl = node.as(MacroExpansionDeclSyntax.self) { @@ -60,12 +97,13 @@ public func expandFreestandingMacro( ) ) - case let codeItemMacroDef as CodeItemMacro.Type: + case (.codeItem, let codeItemMacroDef as CodeItemMacro.Type): let rewritten = try codeItemMacroDef.expansion(of: node, in: context) expandedSyntax = Syntax(CodeBlockItemListSyntax(rewritten)) - default: - throw MacroExpansionError.unmathedMacroRole + case (.accessor, _), (.memberAttribute, _), (.member, _), (.peer, _), (.conformance, _), (.expression, _), (.declaration, _), + (.codeItem, _): + throw MacroExpansionError.unmatchedMacroRole(definition, macroRole) } return expandedSyntax.formattedExpansion(definition.formatMode) } @@ -76,6 +114,39 @@ public func expandFreestandingMacro( } } +/// Try to infer the freestanding macro role from the type definition itself. +/// +/// This is a workaround for older compilers with a newer plugin +public func inferFreestandingMacroRole(definition: Macro.Type) throws -> MacroRole { + switch definition { + case is ExpressionMacro.Type: return .expression + case is DeclarationMacro.Type: return .declaration + case is CodeItemMacro.Type: return .codeItem + + default: + throw MacroExpansionError.noFreestandingMacroRoles(definition) + } +} + +@available(*, deprecated, message: "pass a macro role, please!") +public func expandFreestandingMacro( + definition: Macro.Type, + node: FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext +) -> String? { + do { + return expandFreestandingMacro( + definition: definition, + macroRole: try inferFreestandingMacroRole(definition: definition), + node: node, + in: context + ) + } catch { + context.addDiagnostics(from: error, node: node) + return nil + } +} + /// Expand `@attached(XXX)` macros. /// /// - Parameters: @@ -213,7 +284,7 @@ public func expandAttachedMacro( } default: - throw MacroExpansionError.unmathedMacroRole + throw MacroExpansionError.unmatchedMacroRole(definition, macroRole) } } catch { context.addDiagnostics(from: error, node: attributeNode)