Generating Code using SwiftSyntax

I'd like to know what types I can use to generate the following syntax using SwiftSyntax package:

let array = [1, 2, 3]
let newArray = array.map {
  $0 * $0
}

I am kind of okay making the assignment statements. The one I am puzzled with is map { /* ... */ } syntax. What kind of declaration builder do I need to use to create trailing closure expressions?

Unless I'm missing something - that syntax looks entirely like native Swift code to me.

map has the following declaration:

// Element being the type of the element in the current sequence
// Result being the new type of element created by the transform closure

func map<Result>(_ transform: (Element) throws -> Result) -> [Result]

In Swift, you can write what's referred to as "Trailing Closures" which has rules applied to determine which function parameter will receive the trailing closure as it's value. See SE-0286.

let document = SyntaxFactory.makeSourceFile(
  statements: SyntaxFactory.makeCodeBlockItemList([
    SyntaxFactory.makeCodeBlockItem(
      item: Syntax(
        SyntaxFactory.makeVariableDecl(
          attributes: nil,
          modifiers: nil,
          letOrVarKeyword: SyntaxFactory.makeLetKeyword(trailingTrivia: .spaces(1)),
          bindings: SyntaxFactory.makePatternBindingList([
            SyntaxFactory.makePatternBinding(
              pattern: PatternSyntax(
                SyntaxFactory.makeIdentifierPattern(
                  identifier: SyntaxFactory.makeIdentifier(
                    "array",
                    trailingTrivia: .spaces(1)
                  )
                )
              ),
              typeAnnotation: nil,
              initializer: SyntaxFactory.makeInitializerClause(
                equal: SyntaxFactory.makeEqualToken(trailingTrivia: .spaces(1)),
                value: ExprSyntax(
                  SyntaxFactory.makeArrayExpr(
                    leftSquare: SyntaxFactory.makeLeftSquareBracketToken(),
                    elements: SyntaxFactory.makeArrayElementList([
                      SyntaxFactory.makeArrayElement(
                        expression: ExprSyntax(
                          SyntaxFactory.makeIntegerLiteralExpr(
                            digits: SyntaxFactory.makeIntegerLiteral("1")
                          )
                        ),
                        trailingComma: SyntaxFactory.makeCommaToken(
                          trailingTrivia: .spaces(1)
                        )
                      ),
                      SyntaxFactory.makeArrayElement(
                        expression: ExprSyntax(
                          SyntaxFactory.makeIntegerLiteralExpr(
                            digits: SyntaxFactory.makeIntegerLiteral("2")
                          )
                        ),
                        trailingComma: SyntaxFactory.makeCommaToken(
                          trailingTrivia: .spaces(1)
                        )
                      ),
                      SyntaxFactory.makeArrayElement(
                        expression: ExprSyntax(
                          SyntaxFactory.makeIntegerLiteralExpr(
                            digits: SyntaxFactory.makeIntegerLiteral("3")
                          )
                        ),
                        trailingComma: nil
                      ),
                    ]),
                    rightSquare: SyntaxFactory.makeRightSquareBracketToken()
                  )
                )
              ),
              accessor: nil,
              trailingComma: nil
            )
          ])
        )
      ),
      semicolon: nil,
      errorTokens: nil
    ),
    SyntaxFactory.makeCodeBlockItem(
      item: Syntax(
        SyntaxFactory.makeVariableDecl(
          attributes: nil,
          modifiers: nil,
          letOrVarKeyword: SyntaxFactory.makeLetKeyword(
            leadingTrivia: .newlines(1),
            trailingTrivia: .spaces(1)
          ),
          bindings: SyntaxFactory.makePatternBindingList([
            SyntaxFactory.makePatternBinding(
              pattern: PatternSyntax(
                SyntaxFactory.makeIdentifierPattern(
                  identifier: SyntaxFactory.makeIdentifier(
                    "newArray",
                    trailingTrivia: .spaces(1)
                  )
                )
              ),
              typeAnnotation: nil,
              initializer: SyntaxFactory.makeInitializerClause(
                equal: SyntaxFactory.makeEqualToken(trailingTrivia: .spaces(1)),
                value: ExprSyntax(
                  SyntaxFactory.makeFunctionCallExpr(
                    calledExpression: ExprSyntax(
                      SyntaxFactory.makeMemberAccessExpr(
                        base: ExprSyntax(
                          SyntaxFactory.makeIdentifierExpr(
                            identifier: SyntaxFactory.makeIdentifier("array"),
                            declNameArguments: nil
                          )
                        ),
                        dot: SyntaxFactory.makePeriodToken(),
                        name: SyntaxFactory.makeIdentifier("map", trailingTrivia: .spaces(1)),
                        declNameArguments: nil
                      )
                    ),
                    leftParen: nil,
                    argumentList: SyntaxFactory.makeTupleExprElementList([]),
                    rightParen: nil,
                    trailingClosure: SyntaxFactory.makeClosureExpr(
                      leftBrace: SyntaxFactory.makeLeftBraceToken(),
                      signature: nil,
                      statements: SyntaxFactory.makeCodeBlockItemList([
                        SyntaxFactory.makeCodeBlockItem(
                          item: Syntax(
                            SyntaxFactory.makeSequenceExpr(
                              elements: SyntaxFactory.makeExprList([
                                ExprSyntax(
                                  SyntaxFactory.makeIdentifierExpr(
                                    identifier: SyntaxFactory.makeDollarIdentifier(
                                      "$0",
                                      leadingTrivia: [.newlines(1), .spaces(2)],
                                      trailingTrivia: .spaces(1)
                                    ),
                                    declNameArguments: nil
                                  )
                                ),
                                ExprSyntax(
                                  SyntaxFactory.makeBinaryOperatorExpr(
                                    operatorToken: SyntaxFactory.makeBinaryOperator(
                                      "*",
                                      trailingTrivia: .spaces(1)
                                    )
                                  )
                                ),
                                ExprSyntax(
                                  SyntaxFactory.makeIdentifierExpr(
                                    identifier: SyntaxFactory.makeDollarIdentifier(
                                      "$0",
                                      trailingTrivia: .spaces(1)
                                    ),
                                    declNameArguments: nil
                                  )
                                ),
                              ])
                            )
                          ),
                          semicolon: nil,
                          errorTokens: nil
                        )
                      ]),
                      rightBrace: SyntaxFactory.makeRightBraceToken(
                        leadingTrivia: .newlines(1)
                      )
                    ),
                    additionalTrailingClosures: nil
                  )
                )
              ),
              accessor: nil,
              trailingComma: nil
            )
          ])
        )
      ),
      semicolon: nil,
      errorTokens: nil
    ),
  ]),
  eofToken: SyntaxFactory.makeToken(.eof)
)

There are so many types in SwiftSyntax that looking at source or documentation tends to be like searching for a needle in a haystack. What I do instead to figure something like this out is work backwards:

func inspect(_ node: Syntax) {
  print(node.syntaxNodeType)
  print(node)
  for child in node.children {
    inspect(child)
  }
}

let needToFigureOut = """
  let array = [1, 2, 3]
  let newArray = array.map {
    $0 * $0
  }
  """
let parsed = try! SyntaxParser.parse(source: needToFigureOut)
inspect(Syntax(parsed))

The resulting output looks like this:

SourceFileSyntax
let array = [1, 2, 3]
let newArray = array.map {
  $0 * $0
}
CodeBlockItemListSyntax
let array = [1, 2, 3]
let newArray = array.map {
  $0 * $0
}
CodeBlockItemSyntax
let array = [1, 2, 3]
VariableDeclSyntax
let array = [1, 2, 3]
TokenSyntax
let 
PatternBindingListSyntax
array = [1, 2, 3]
PatternBindingSyntax
array = [1, 2, 3]
IdentifierPatternSyntax
array 
TokenSyntax
array 
InitializerClauseSyntax
= [1, 2, 3]
TokenSyntax
= 
ArrayExprSyntax
[1, 2, 3]
TokenSyntax
[
ArrayElementListSyntax
1, 2, 3
ArrayElementSyntax
1, 
IntegerLiteralExprSyntax
1
TokenSyntax
1
TokenSyntax
, 
ArrayElementSyntax
2, 
IntegerLiteralExprSyntax
2
TokenSyntax
2
TokenSyntax
, 
ArrayElementSyntax
3
IntegerLiteralExprSyntax
3
TokenSyntax
3
TokenSyntax
]
CodeBlockItemSyntax

let newArray = array.map {
  $0 * $0
}
VariableDeclSyntax

let newArray = array.map {
  $0 * $0
}
TokenSyntax

let 
PatternBindingListSyntax
newArray = array.map {
  $0 * $0
}
PatternBindingSyntax
newArray = array.map {
  $0 * $0
}
IdentifierPatternSyntax
newArray 
TokenSyntax
newArray 
InitializerClauseSyntax
= array.map {
  $0 * $0
}
TokenSyntax
= 
FunctionCallExprSyntax
array.map {
  $0 * $0
}
MemberAccessExprSyntax
array.map 
IdentifierExprSyntax
array
TokenSyntax
array
TokenSyntax
.
TokenSyntax
map 
TupleExprElementListSyntax

ClosureExprSyntax
{
  $0 * $0
}
TokenSyntax
{
CodeBlockItemListSyntax

  $0 * $0
CodeBlockItemSyntax

  $0 * $0
SequenceExprSyntax

  $0 * $0
ExprListSyntax

  $0 * $0
IdentifierExprSyntax

  $0 
TokenSyntax

  $0 
BinaryOperatorExprSyntax
* 
TokenSyntax
* 
IdentifierExprSyntax
$0
TokenSyntax
$0
TokenSyntax

}
TokenSyntax

8 Likes

The working backwards is indeed a great trick. Thanks @SDGGiesbrecht!!!

A bit late response :sweat_smile: but a convenient way to see the syntax structure is by running

swift -frontend -emit-syntax somefile.swift | python -m json.tool
9 Likes