swiftlang/swift-syntax

Space Omission in Macro-Expanded Code Using `TryExprSyntax` or `AwaitExprSyntax` within String Interpolation

Matejkob opened this issue · 2 comments

Description

When using TryExprSyntax or AwaitExprSyntax in a macro expansion context, the resulting code omits the space between the try (or await) keyword and the subsequent expression when these are incorporated via string interpolation.

Steps to Reproduce

  1. Define a macro expansion that produces an expression using TryExprSyntax, as demonstrated in the following code snippet:

    public static func expansion(
      of node: some FreestandingMacroExpansionSyntax,
      in context: some MacroExpansionContext
    ) -> ExprSyntax {
      let tryExpression = ExprSyntax(TryExprSyntax(expression: ExprSyntax("foo()")))
      return tryExpression
    }

    This code correctly expands to try foo() with a space between try and foo().

  2. Use the resulting tryExpression within another expression using string interpolation:

    public static func expansion(
      of node: some FreestandingMacroExpansionSyntax,
      in context: some MacroExpansionContext
    ) -> ExprSyntax {
      let tryExpression = ExprSyntax(TryExprSyntax(expression: ExprSyntax("foo()")))
    
      let ifExpression: ExprSyntax = """
      if true {
          \(tryExpression)
      }
      """
      return ifExpression
    }

    The expected expansion would be:

    if true {
        try foo()
    }

    However, the actual expansion is:

    if true {
        tryfoo()
    }

    Notice the missing space between try and foo().

Expected Behavior:
The macro expansion should maintain the space between the try keyword and the subsequent expression, even when used within string interpolation.

Actual Behavior:
The space between the try keyword and the expression is omitted in the macro-expanded code when used within string interpolation. The same issue is suspected to occur with AwaitExprSyntax.

Tracked in Apple’s issue tracker as rdar://118271505

This is expected behavior. Building up a string interpolation inserts the source code of the subtree and then re-parses it. ?Since tryExpression is not formatted, it doesn’t have a space between try and foo and you thus end up inserting tryfoo() in the string interpolation.

To fix this, build up the subtree using a result builder or format the subexpression i.e. either

  1. Use SwiftSyntaxBuilder to build the if expression
IfExprSyntax("if true") {
  tryExpression
}
  1. \(tryExpression, format: BasicFormat())
  2. \(tryExpression.formatted())

I would recommend approach (1).