A Possible Vision for Macros in Swift

The idea of AST to AST transformation by macro is excellent. It has very great potential. I want to show one practical example. I have created a proof of concept for the Power Assert library using currently publicly available code.

If you write a macro like the following:

let a = 4
let b = 7
let c = 12

#powerAssert(max(a, b) == c)
#powerAssert(a + b > c)

let john = Person(name: "John", age: 42)
let mike = Person(name: "Mike", age: 13)

#powerAssert(john.isTeenager)
#powerAssert(mike.isTeenager && john.age < mike.age)

It will generate the following code:

let a = 4
let b = 7
let c = 12

PowerAssert.Assertion(#"#powerAssert(max(a, b) == c)"#, line: 14).assert(max(a, b) == c).capture(expression: max(a, b), column: 13).capture(expression: a.self, column: 17).capture(expression: b.self, column: 20).capture(expression: max(a, b) == c, column: 23).capture(expression: c.self, column: 26).render()
PowerAssert.Assertion(#"#powerAssert(a + b > c)"#, line: 15).assert(a + b > c).capture(expression: a.self, column: 13).capture(expression: b.self, column: 17).capture(expression: a + b > c, column: 19).capture(expression: c.self, column: 21).render()

let john = Person(name: "John", age: 42)
let mike = Person(name: "Mike", age: 13)

PowerAssert.Assertion(#"#powerAssert(john.isTeenager)"#, line: 29).assert(john.isTeenager).capture(expression: john.isTeenager.self, column: 18).capture(expression: john.self, column: 13).render()
PowerAssert.Assertion(#"#powerAssert(mike.isTeenager && john.age < mike.age)"#, line: 30).assert(mike.isTeenager && john.age < mike.age).capture(expression: mike.isTeenager.self, column: 18).capture(expression: mike.self, column: 13).capture(expression: mike.isTeenager && john.age < mike.age, column: 29).capture(expression: john.age.self, column: 37).capture(expression: john.self, column: 32).capture(expression: mike.isTeenager && john.age < mike.age, column: 41).capture(expression: mike.age.self, column: 48).capture(expression: mike.self, column: 43).render()

Run this code produces the following output:

#powerAssert(max(a, b) == c)
             |   |  |  |  |
             7   4  7  |  12
                       false
#powerAssert(a + b > c)
             |   | | |
             4   7 | 12
                   false
#powerAssert(john.isTeenager)
             |    |
             |    false
             Person(name: "John", age: 42)
#powerAssert(mike.isTeenager && john.age < mike.age)
             |    |          |  |    |   | |    |
             |    false      |  |    42  | |    13
             |               |  |        | Person(name: "Mike", age: 13)
             |               |  |        false
             |               |  Person(name: "John", age: 42)
             |               false
             Person(name: "Mike", age: 13)

An executable project is available here.

Wouldn't it be great to use such rich assertions in the Swift testing framework? I look forward to a future where this kind of AST manipulation is in Swift.

Unfortunately, SwiftSyntax does not provide complete type information that does not appear in the source code, so there are some cases where code generation fails.

For example, the value cannot be captured if a type is omitted due to dot syntax or an anonymous closure argument is used.

It would be great if Swift macros could be passed the type information checked by the compiler as well as the source code structure to achieve this use case.

47 Likes