From fe05c669b5b985d840d7064f1303b71c2f469320 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 9 Jun 2023 12:14:22 -0700 Subject: [PATCH 1/3] [Macros] Pass macro role for freestanding macro expansion Macros can only have a single freestanding macro role, so that their expansion is unambiguous. However, that restriction is at the level of a macro declaration, and is not reflected in a similar restriction on the set of macro protocols to which a macro implementation can conform. Therefore, have the compiler pass a freestanding macro role through to macro expansion in the same way that we do for attached macros, and use that to determine which macro protocol to use. Part of fixing rdar://110418969. --- .../CompilerPluginMessageHandler.swift | 3 +- .../Diagnostics.swift | 1 + .../Macros.swift | 16 +++++++ .../PluginMessages.swift | 1 + .../MacroExpansion.swift | 48 +++++++++++++++++-- 5 files changed, 63 insertions(+), 6 deletions(-) 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..ab56c64c4f3 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift @@ -19,6 +19,7 @@ enum MacroExpansionError: String { 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" + case invalidMacroRole = "invalid macro role for expansion" } extension MacroExpansionError: DiagnosticMessage { diff --git a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift index 7731e7b39fd..f26cefd7430 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 { @@ -46,8 +47,23 @@ extension CompilerPluginMessageHandler { throw MacroExpansionError.macroTypeNotFound } + let macroRole: MacroRole + if let pluginMacroRole { + switch pluginMacroRole { + case .expression: macroRole = .expression + case .declaration: macroRole = .declaration + case .codeItem: macroRole = .codeItem + + case .accessor, .conformance, .member, .memberAttribute, .peer: + throw MacroExpansionError.invalidMacroRole + } + } else { + macroRole = try inferFreestandingMacroRole(definition: macroDefinition) + } + expandedSource = SwiftSyntaxMacroExpansion.expandFreestandingMacro( definition: macroDefinition, + macroRole: macroRole, node: macroSyntax, in: context ) 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..c0b6367d3ec 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -25,6 +25,7 @@ private enum MacroExpansionError: String, Error, CustomStringConvertible { /// /// - 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 +33,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 +62,15 @@ 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: + case (.expression, _), (.declaration, _), (.codeItem, _): throw MacroExpansionError.unmathedMacroRole + + case (.accessor, _), (.memberAttribute, _), (.member, _), (.peer, _), (.conformance, _), (.codeItem, _): + fatalError("macro role \(macroRole) is not a freestanding macro") } return expandedSyntax.formattedExpansion(definition.formatMode) } @@ -76,6 +81,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.unmathedMacroRole + } +} + +@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: From 4a8b665f7a7bf1c025098c5bb9f824943e1399a6 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 9 Jun 2023 12:57:26 -0700 Subject: [PATCH 2/3] [Macros] Improve error messages --- .../Diagnostics.swift | 29 ++++++--- .../Macros.swift | 6 +- .../MacroExpansion.swift | 59 +++++++++++++++---- 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift index ab56c64c4f3..acb6a5a7030 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift @@ -14,17 +14,32 @@ 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" - case invalidMacroRole = "invalid macro role for expansion" +enum MacroExpansionError { + case macroTypeNotFound(PluginMessage.MacroReference) + case unmatchedMacroRole + 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 .unmatchedMacroRole: + return "macro doesn't conform to required macro role" + + 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 f26cefd7430..9e2b787ff2f 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift @@ -44,7 +44,7 @@ extension CompilerPluginMessageHandler { throw MacroExpansionError.freestandingMacroSyntaxIsNotMacro } guard let macroDefinition = resolveMacro(macro) else { - throw MacroExpansionError.macroTypeNotFound + throw MacroExpansionError.macroTypeNotFound(macro) } let macroRole: MacroRole @@ -55,7 +55,7 @@ extension CompilerPluginMessageHandler { case .codeItem: macroRole = .codeItem case .accessor, .conformance, .member, .memberAttribute, .peer: - throw MacroExpansionError.invalidMacroRole + throw MacroExpansionError.invalidMacroRole(pluginMacroRole) } } else { macroRole = try inferFreestandingMacroRole(definition: macroDefinition) @@ -105,7 +105,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/SwiftSyntaxMacroExpansion/MacroExpansion.swift b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift index c0b6367d3ec..7df14081107 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -12,13 +12,48 @@ 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. @@ -66,11 +101,9 @@ public func expandFreestandingMacro( let rewritten = try codeItemMacroDef.expansion(of: node, in: context) expandedSyntax = Syntax(CodeBlockItemListSyntax(rewritten)) - case (.expression, _), (.declaration, _), (.codeItem, _): - throw MacroExpansionError.unmathedMacroRole - - case (.accessor, _), (.memberAttribute, _), (.member, _), (.peer, _), (.conformance, _), (.codeItem, _): - fatalError("macro role \(macroRole) is not a freestanding macro") + case (.accessor, _), (.memberAttribute, _), (.member, _), (.peer, _), (.conformance, _), (.expression, _), (.declaration, _), + (.codeItem, _): + throw MacroExpansionError.unmatchedMacroRole(definition, macroRole) } return expandedSyntax.formattedExpansion(definition.formatMode) } @@ -91,7 +124,7 @@ public func inferFreestandingMacroRole(definition: Macro.Type) throws -> MacroRo case is CodeItemMacro.Type: return .codeItem default: - throw MacroExpansionError.unmathedMacroRole + throw MacroExpansionError.noFreestandingMacroRoles(definition) } } @@ -251,7 +284,7 @@ public func expandAttachedMacro( } default: - throw MacroExpansionError.unmathedMacroRole + throw MacroExpansionError.unmatchedMacroRole(definition, macroRole) } } catch { context.addDiagnostics(from: error, node: attributeNode) From a992586c50fcfba019c446d628195a8ca5549721 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 9 Jun 2023 12:59:35 -0700 Subject: [PATCH 3/3] Prune unused macro expansion error case --- Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift index acb6a5a7030..a00f1d2e7b2 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift @@ -16,7 +16,6 @@ import SwiftSyntax /// Errors in macro handing. enum MacroExpansionError { case macroTypeNotFound(PluginMessage.MacroReference) - case unmatchedMacroRole case freestandingMacroSyntaxIsNotMacro case invalidExpansionMessage case invalidMacroRole(PluginMessage.MacroRole) @@ -28,9 +27,6 @@ extension MacroExpansionError: DiagnosticMessage { case .macroTypeNotFound(let ref): return "macro type '\(ref.moduleName).\(ref.typeName)' not found when expanding macro '\(ref.name)'" - case .unmatchedMacroRole: - return "macro doesn't conform to required macro role" - case .freestandingMacroSyntaxIsNotMacro: return "macro syntax couldn't be parsed"