Skip to content

Macro protocols lack async from expansion function requirements despite proposals stating they should have it#2803

@roopekv

Description

@roopekv

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

protocolMacro{func expansion()}structSyncMacro:Macro{func expansion(){} // OK ✅ }structAsyncMacro:Macro{func expansion()async{} // ERROR ❌ }
protocolMacro{func expansion()async}structSyncMacro:Macro{func expansion(){} // OK ✅ }structAsyncMacro: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

Proposal

publicprotocolExpressionMacro:FreestandingMacro{ /// Expand a macro described by the given freestanding macro expansion /// within the given context to produce a replacement expression. staticfunc expansion( of node:someFreestandingMacroExpansionSyntax, in context:someMacroExpansionContext)asyncthrows->ExprSyntax}

swift-syntax

publicprotocolExpressionMacro:FreestandingMacro{
/// Expand a macro described by the given freestanding macro expansion
/// within the given context to produce a replacement expression.
staticfunc expansion(
of node:someFreestandingMacroExpansionSyntax,
in context:someMacroExpansionContext
)throws->ExprSyntax
}

PeerMacro

Proposal

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

publicprotocolPeerMacro:AttachedMacro{
/// Expand a macro described by the given custom attribute and
/// attached to the given declaration and evaluated within a
/// particular expansion context.
///
/// The macro expansion can introduce "peer" declarations that sit alongside
/// the given declaration.
staticfunc expansion(
of node:AttributeSyntax,
providingPeersOf declaration:someDeclSyntaxProtocol,
in context:someMacroExpansionContext
)throws->[DeclSyntax]
}

MemberMacro

Proposal

protocolMemberMacro:AttachedMacro{ /// Expand a macro described by the given attribute to /// produce additional members of the given declaration to which /// the attribute is attached. staticfunc expansion( of node:AttributeSyntax, providingMembersOf declaration:someDeclGroupSyntax, in context:someMacroExpansionContext)asyncthrows->[DeclSyntax]}

swift-syntax

publicprotocolMemberMacro:AttachedMacro{
/// Expand an attached declaration macro to produce a set of members.
///
/// - Parameters:
/// - node: The custom attribute describing the attached macro.
/// - declaration: The declaration the macro attribute is attached to.
/// - context: The context in which to perform the macro expansion.
///
/// - Returns: the set of member declarations introduced by this macro, which
/// are nested inside the `attachedTo` declaration.
///
/// - Warning: This is the legacy `expansion` function of `MemberMacro` that is provided for backwards-compatiblity.
/// Use ``expansion(of:providingMembersOf:conformingTo:in:)-1sxoe`` instead.
staticfunc expansion(
of node:AttributeSyntax,
providingMembersOf declaration:someDeclGroupSyntax,
in context:someMacroExpansionContext
)throws->[DeclSyntax]
/// Expand an attached declaration macro to produce a set of members.
///
/// - Parameters:
/// - node: The custom attribute describing the attached macro.
/// - declaration: The declaration the macro attribute is attached to.
/// - conformingTo: The set of protocols that were declared
/// in the set of conformances for the macro and to which the declaration
/// does not explicitly conform. The member macro itself cannot declare
/// conformances to these protocols (only an extension macro can do that),
/// but can provide supporting declarations, such as a required
/// initializer or stored property, that cannot be written in an
/// extension.
/// - context: The context in which to perform the macro expansion.
///
/// - Returns: the set of member declarations introduced by this macro, which
/// are nested inside the `attachedTo` declaration.
staticfunc expansion(
of node:AttributeSyntax,
providingMembersOf declaration:someDeclGroupSyntax,
conformingTo protocols:[TypeSyntax],
in context:someMacroExpansionContext
)throws->[DeclSyntax]
}
privatestructUnimplementedExpansionMethodError:Error,CustomStringConvertible{
vardescription:String{
"""
Types conforming to `MemberMacro` must implement either \
expansion(of:providingMembersOf:in:) or \
expansion(of:providingMembersOf:conformingTo:in:)
"""
}
}

AccessorMacro

Proposal

protocolAccessorMacro:AttachedMacro{ /// Expand a macro described by the given attribute to /// produce accessors for the given declaration to which /// the attribute is attached. staticfunc expansion( of node:AttributeSyntax, providingAccessorsOf declaration:someDeclSyntaxProtocol, in context:someMacroExpansionContext)asyncthrows->[AccessorDeclSyntax]}

swift-syntax

publicprotocolAccessorMacro:AttachedMacro{
/// Expand a macro that's expressed as a custom attribute attached to
/// the given declaration. The result is a set of accessors for the
/// declaration.
staticfunc expansion(
of node:AttributeSyntax,
providingAccessorsOf declaration:someDeclSyntaxProtocol,
in context:someMacroExpansionContext
)throws->[AccessorDeclSyntax]
}

MemberAttributeMacro

Proposal

protocolMemberAttributeMacro:AttachedMacro{ /// Expand a macro described by the given custom attribute to /// produce additional attributes for the members of the type. staticfunc expansion( of node:AttributeSyntax, attachedTo declaration:someDeclGroupSyntax, providingAttributesOf member:someDeclSyntaxProtocol, in context:someMacroExpansionContext)asyncthrows->[AttributeSyntax]}

swift-syntax

publicprotocolMemberAttributeMacro:AttachedMacro{
/// Expand an attached declaration macro to produce an attribute list for
/// a given member.
///
/// - Parameters:
/// - node: The custom attribute describing the attached macro.
/// - declaration: The declaration the macro attribute is attached to.
/// - member: The member declaration to attach the resulting attributes to.
/// - context: The context in which to perform the macro expansion.
///
/// - Returns: the set of attributes to apply to the given member.
staticfunc expansion(
of node:AttributeSyntax,
attachedTo declaration:someDeclGroupSyntax,
providingAttributesFor member:someDeclSyntaxProtocol,
in context:someMacroExpansionContext
)throws->[AttributeSyntax]
}

Additional quotes from Expression Macros proposal

The macro expansion operation is asynchronous, to account for potentially-asynchronous operations that will eventually be added to MacroExpansionContext. For example, operations that require additional communication with the compiler to get types of subexpressions, access files in the program, and so on.

* Make the ExpressionMacro.expansion(of:in:) requirement async.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions