RFC: Generalize `expandAttachedMacro` APIs to accept `ClosureExprSyntax`

Summary: As part of supporting function body macros on closure expressions, I've added a new requirement to the BodyMacro protocol to accept a value of ClosureExprSyntax, and I generalized the public expandAttachedMacro APIs to accept some SyntaxProtocol instead of DeclSyntax to allow passing a value of ClosureExprSyntax as the node that the macro is attached to.

Link to PR: [Macros] Support function body macros on closures. by hborla · Pull Request #3016 · swiftlang/swift-syntax · GitHub

Swift Interface

My PR adds the following public API in SwiftSyntaxMacroExpansion:

/// Expand `@attached(XXX)` macros.
///
/// - Parameters:
///   - definition: a type that conforms to one or more attached `Macro` protocols.
///   - macroRole: indicates which `Macro` protocol expansion should be performed
///   - attributeNode: attribute syntax node (e.g. `@macroName(argument)`).
///   - node: target syntax node to apply the expansion. This is either a declaration
///     or a closure syntax node.
///   - parentDeclNode: Only used for `MacroRole.memberAttribute`. The parent
///     context node of `declarationNode`.
///   - context: context of the expansion.
///   - indentationWidth: The indentation that should be added for each additional
///     nesting level
/// - Returns: A list of expanded source text. Upon failure (i.e.
///   `definition.expansion()` throws) returns `nil`, and the diagnostics
///   representing the `Error` are guaranteed to be added to context.
public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>(
  definition: Macro.Type,
  macroRole: MacroRole,
  attributeNode: AttributeSyntax,
  node: some SyntaxProtocol,
  parentDeclNode: DeclSyntax?,
  extendedType: TypeSyntax?,
  conformanceList: InheritedTypeListSyntax?,
  in context: Context,
  indentationWidth: Trivia? = nil
) -> [String]?

/// Expand `@attached(XXX)` macros.
///
/// - Parameters:
///   - definition: a type that conforms to one or more attached `Macro` protocols.
///   - macroRole: indicates which `Macro` protocol expansion should be performed
///   - attributeNode: attribute syntax node (e.g. `@macroName(argument)`).
///   - node: target declaration syntax node to apply the expansion. This is either
///     a declaration or a closure syntax node.
///   - parentDeclNode: Only used for `MacroRole.memberAttribute`. The parent
///     context node of `declarationNode`.
///   - context: context of the expansion.
///   - indentationWidth: The indentation that should be added for each additional
///     nesting level
/// - 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 expandAttachedMacro<Context: MacroExpansionContext>(
  definition: Macro.Type,
  macroRole: MacroRole,
  attributeNode: AttributeSyntax,
  node: some SyntaxProtocol,
  parentDeclNode: DeclSyntax?,
  extendedType: TypeSyntax?,
  conformanceList: InheritedTypeListSyntax?,
  in context: Context,
  indentationWidth: Trivia? = nil
) -> String? {

I also deprecated the overloads that accept declarationNode: DeclSyntax, with a message to rename the declarationNode: argument label to node:. Because the new overloads accept some SyntaxProtocol, direct only require changing the argument label; an argument type of DeclSyntax is still valid.

I also added the following requirement to the BodyMacro protocol in SwiftSyntaxMacros:

  /// Expand a macro described by the given custom attribute and
  /// attached to the given closure and evaluated within a
  /// particular expansion context.
  ///
  /// The macro expansion can replace the body of the given closure.
  static func expansion(
    of node: AttributeSyntax,
    providingBodyFor closure: ClosureExprSyntax,
    in context: some MacroExpansionContext
  ) throws -> [CodeBlockItemSyntax]

For existing API: This change will cause warnings in existing callers of the expandAttachedMacro APIs and existing conformances to BodyMacro.

  • Migration: To facilitate migration, the old overloads of the expandAttachedMacro APIs are deprecated, and the default implementation of the new BodyMacro protocol requirement is deprecated. This will cause warnings in existing clients of these APIs. Existing callers of expandAttachedMacro{WithoutCollapsing} will get a warning to change the declarationNode argument label to node. Existing conformances of BodyMacro will get a warning to implement the new protocol requirement.
4 Likes

These changes look good to me. Thank you, Holly.