Description
Issue
Swift's concurrency features cannot be fully utilized in macros without being able to implement expansion
function requirements of macro protocols as async
functions.
Cause
While the Swift Evolution proposals for expression and attached macros state that the expansion
function requirements of macro protocols should be async
, the macro protocol declarations in swift-syntax are missing the async
keyword from their expansion
function requirements (see comparison below), making it impossible to implement them as async
functions.
Solution
Changing the declarations in swift-syntax to async
shouldn't break existing macros, as async
function requirements can be implemented as non-async functions.
Example
protocol Macro {
func expansion()
}
struct SyncMacro : Macro {
func expansion() {} // OK ✅
}
struct AsyncMacro : Macro {
func expansion() async {} // ERROR ❌
}
protocol Macro {
func expansion() async
}
struct SyncMacro : Macro {
func expansion() {} // OK ✅
}
struct AsyncMacro : Macro {
func expansion() async {} // OK ✅
}
I acknowledge that additional changes would have to be made within the swift-syntax package (changes at the expansion function call sites and possible further structural changes to facilitate asynchronous code), but depending on how they are implemented, the changes don't have to affect the user-facing parts of the package in a source breaking way.
Differences between Swift Evolution proposals and swift-syntax
ExpressionMacro
public protocol ExpressionMacro: FreestandingMacro {
/// Expand a macro described by the given freestanding macro expansion
/// within the given context to produce a replacement expression.
static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) async throws -> ExprSyntax
}
swift-syntax
PeerMacro
public PeerMacro: AttachedMacro {
/// Expand a macro described by the given attribute to
/// produce "peer" declarations of the declaration to which it
/// is attached.
///
/// The macro expansion can introduce "peer" declarations that
/// go alongside the given declaration.
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) async throws -> [DeclSyntax]
}
swift-syntax
swift-syntax/Sources/SwiftSyntaxMacros/MacroProtocols/PeerMacro.swift
Lines 17 to 29 in 0b324f8
MemberMacro
protocol MemberMacro: AttachedMacro {
/// Expand a macro described by the given attribute to
/// produce additional members of the given declaration to which
/// the attribute is attached.
static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) async throws -> [DeclSyntax]
}
swift-syntax
AccessorMacro
protocol AccessorMacro: AttachedMacro {
/// Expand a macro described by the given attribute to
/// produce accessors for the given declaration to which
/// the attribute is attached.
static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) async throws -> [AccessorDeclSyntax]
}
swift-syntax
MemberAttributeMacro
protocol MemberAttributeMacro: AttachedMacro {
/// Expand a macro described by the given custom attribute to
/// produce additional attributes for the members of the type.
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingAttributesOf member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) async throws -> [AttributeSyntax]
}
swift-syntax
Additional quotes from Expression Macros proposal
* Make the
ExpressionMacro.expansion(of:in:)
requirementasync
.