Skip to content

[5.9] [Macros] Pass macro role for freestanding macro expansion #1763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
24 changes: 18 additions & 6 deletions Sources/SwiftCompilerPluginMessageHandling/Diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down
13 changes: 11 additions & 2 deletions Sources/SwiftCompilerPluginMessageHandling/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
)
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
97 changes: 84 additions & 13 deletions Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,74 @@ 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()`
/// throws) returns `nil`, and the diagnostics representing the `Error` are
/// 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) {
Expand All @@ -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)
}
Expand All @@ -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:
Expand Down Expand Up @@ -213,7 +284,7 @@ public func expandAttachedMacro<Context: MacroExpansionContext>(
}

default:
throw MacroExpansionError.unmathedMacroRole
throw MacroExpansionError.unmatchedMacroRole(definition, macroRole)
}
} catch {
context.addDiagnostics(from: error, node: attributeNode)
Expand Down