$30 off During Our Annual Pro Sale. View Details »

Swift Expression Macros: a practical introduction

Swift Expression Macros: a practical introduction

Swift Expression Macros:
a practical introduction
-実践 Swiftマクロ入門-

Macro proposal timeline
Swift macro overview
How to implement macros
Swift macro essentials
Pros and cons
Various kinds of macros
Declaration macros
Practical Example: Power Assert
Summary

Kishikawa Katsumi

January 21, 2023
Tweet

More Decks by Kishikawa Katsumi

Other Decks in Programming

Transcript

  1. kishikawa katsumi / @[email protected]
    Swift Expression Macros:
    a practical introduction
    -࣮ફ SwiftϚΫϩೖ໳-

    View Slide

  2. Agenda
    • Macro proposal timeline

    • Swift macro overview

    • How to implement macros

    • Swift macro essentials

    • Pros and cons

    • Various kinds of macros

    • Declaration macros

    • Practical Example: Power Assert

    • Summary
    ໨࣍

    View Slide

  3. Macro proposal timeline

    View Slide

  4. • A Possible Vision for Macros in Swift

    https://forums.swift.org/t/a-possible-vision-for-macros-in-swift/60900

    https://gist.github.com/DougGregor/4f3ba5f4eadac474ae62eae836328b71

    • Swift project in 2023

    https://www.swift.org/blog/focus-areas-2023/
    Macro proposal timeline
    A Possible Vision for Macros in Swift

    View Slide

  5. • A Possible Vision for Macros in Swift

    https://forums.swift.org/t/a-possible-vision-for-macros-in-swift/60900

    • Swift project in 2023

    https://www.swift.org/blog/focus-areas-2023/

    ➡[Pitch] Expression macros

    https://forums.swift.org/t/pitch-expression-macros/61499

    • [Pitch #2] Expression macros

    https://forums.swift.org/t/pitch-2-expression-macros/61861

    • SE-0382: Expression Macros

    https://forums.swift.org/t/se-0382-expression-macros/62090
    ϚΫϩʢExpression Macros/ࣜϚΫϩʣͷػೳ͕ঝೝ͞ΕΔ·Ͱ
    Macro proposal timeline

    View Slide

  6. • A Possible Vision for Macros in Swift

    https://forums.swift.org/t/a-possible-vision-for-macros-in-swift/60900

    • Swift project in 2023

    https://www.swift.org/blog/focus-areas-2023/

    • [Pitch] Expression macros

    https://forums.swift.org/t/pitch-expression-macros/61499

    ➡[Pitch #2] Expression macros

    https://forums.swift.org/t/pitch-2-expression-macros/61861

    • SE-0382: Expression Macros

    https://forums.swift.org/t/se-0382-expression-macros/62090
    ϚΫϩʢExpression Macros/ࣜϚΫϩʣͷػೳ͕ঝೝ͞ΕΔ·Ͱ
    Macro proposal timeline

    View Slide

  7. • A Possible Vision for Macros in Swift

    https://forums.swift.org/t/a-possible-vision-for-macros-in-swift/60900

    • Swift project in 2023

    https://www.swift.org/blog/focus-areas-2023/

    • [Pitch] Expression macros

    https://forums.swift.org/t/pitch-expression-macros/61499

    • [Pitch #2] Expression macros

    https://forums.swift.org/t/pitch-2-expression-macros/61861

    ➡SE-0382: Expression Macros

    https://forums.swift.org/t/se-0382-expression-macros/62090
    ϚΫϩʢExpression Macros/ࣜϚΫϩʣͷػೳ͕ঝೝ͞ΕΔ·Ͱ
    Macro proposal timeline

    View Slide

  8. એݴϚΫϩʢDeclaration Macrosʣ
    • [Pitch] Declaration macros

    https://forums.swift.org/t/pitch-declaration-macros/62373/17

    • Declaration Macros

    https://github.com/DougGregor/swift-evolution/blob/declaration-macros/proposals/nnnn-declaration-macros.md
    Macro proposal timeline

    View Slide

  9. Swift macro overview

    View Slide

  10. Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    func f(a: Int, _: Double, c: Int) {


    print(#function)


    }

    View Slide

  11. func f(a: Int, _: Double, c: Int) {


    print(#function) // => f(a:_:c:)


    }
    Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ

    View Slide

  12. func f(a: Int, _: Double, c: Int) {


    print(#function) // => f(a:_:c:)


    }
    Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    func f(a: Int, _: Double, c: Int) {


    print("f(a:_:c:)")


    }

    View Slide

  13. Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    let color = #colorLiteral(red: 0.5, green: 0.5, blue: 0.25, alpha: 1.0)
    let color = .init(_colorLiteralRed: 0.5, green: 0.5, blue: 0.25, alpha: 1.0)

    View Slide

  14. func doSomething(_ a: Int, b: Int, c d: Int, e _: Int, _: Int, _ _: Int) {


    #printArguments()


    ...


    }
    Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    https://forums.swift.org/t/se-0382-expression-macros/62090/9

    View Slide

  15. func doSomething(_ a: Int, b: Int, c d: Int, e _: Int, _: Int, _ _: Int) {


    #printArguments() // => doSomething(42, b: 256, c: 512, e: _, _, _)


    ...


    }
    Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    https://forums.swift.org/t/se-0382-expression-macros/62090/9

    View Slide

  16. Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    func doSomething(_ a: Int, b: Int, c d: Int, e _: Int, _: Int, _ _: Int) {


    print("doSomething(\(a), b: \(b), c: \(d), e: _, _, _)")


    ...


    }
    https://forums.swift.org/t/se-0382-expression-macros/62090/9
    func doSomething(_ a: Int, b: Int, c d: Int, e _: Int, _: Int, _ _: Int) {


    #printArguments() // => doSomething(42, b: 256, c: 512, e: _, _, _)


    ...


    }

    View Slide

  17. Swift Macro Overview
    SwiftϚΫϩͷ֓ཁ
    #function "f(a:_:c:)"
    #colorLiteral(


    red: 0.5,


    green: 0.5,


    blue: 0.25,


    alpha: 1.0


    )
    .init(


    _colorLiteralRed: 0.5,


    green: 0.5,


    blue: 0.25,


    alpha: 1.0


    )
    #printArguments() print("doSomething(\(a), b: \(b), c: \(d), e: _, _, _)”)

    View Slide

  18. First impressions about macros
    Pros and cons
    • The code might be harder to read

    • Macros are hard to imagine what they do

    • Incorrect conversions cause unintended behavior

    • Macros are hard to debug

    View Slide

  19. First impressions about macros
    Pros and cons
    • The code might be harder to read

    • Macros are hard to imagine what they do

    • Incorrect conversions cause unintended behavior

    • Macros are hard to debug

    • Macro can eliminate boilerplate

    View Slide

  20. How to implement macros

    View Slide

  21. How to implement macros
    #colorLiteral()
    let color = #colorLiteral(red: 0.5, green: 0.5, blue: 0.25, alpha: 1.0)

    View Slide

  22. How to implement macros
    #colorLiteral()
    @expression public macro


    colorLiteral(


    red: Float, green: Float, blue: Float, alpha: Float


    ) -> _ColorLiteralType


    = #externalMacro(module: "MacroExamplesPlugin", type: "ColorLiteralMacro")

    View Slide

  23. How to implement macros
    #colorLiteral()
    @expression public macro


    colorLiteral(


    red: Float, green: Float, blue: Float, alpha: Float


    ) -> _ColorLiteralType


    = #externalMacro(module: "MacroExamplesPlugin", type: "ColorLiteralMacro")

    View Slide

  24. How to implement macros
    #colorLiteral()
    @expression public macro


    colorLiteral(


    red: Float, green: Float, blue: Float, alpha: Float


    ) -> _ColorLiteralType


    = #externalMacro(module: "MacroExamplesPlugin", type: "ColorLiteralMacro")

    View Slide

  25. How to implement macros
    #colorLiteral()
    @expression public macro


    colorLiteral(


    red: Float, green: Float, blue: Float, alpha: Float


    ) -> _ColorLiteralType


    = #externalMacro(module: "MacroExamplesPlugin", type: "ColorLiteralMacro")

    View Slide

  26. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  27. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  28. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  29. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  30. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  31. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  32. How to implement macros
    #colorLiteral()
    import SwiftSyntax


    import _SwiftSyntaxMacros


    public struct ColorLiteralMacro: ExpressionMacro {


    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }


    }

    View Slide

  33. Swift Syntax

    View Slide

  34. Swift Syntax
    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    let argList = replaceFirstLabel(


    of: macro.argumentList,


    with: "_colorLiteralRed"


    )


    let initSyntax: ExprSyntax = ".init(\(argList))"


    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax


    }

    View Slide

  35. How to implement macros
    #colorLiteral()
    /// Replace the label of the
    fi
    rst element in the tuple with the given


    /// new label.


    private func replaceFirstLabel(


    of tuple: TupleExprElementListSyntax,


    with newLabel: String


    ) -> TupleExprElementListSyntax {


    guard let firstElement = tuple.first else {


    return tuple


    }


    return tuple.replacing(


    childAt: 0,


    with: firstElement.withLabel(.identifier(newLabel))


    )


    }

    View Slide

  36. How to implement macros
    #colorLiteral()
    /// Replace the label of the
    fi
    rst element in the tuple with the given


    /// new label.


    private func replaceFirstLabel(


    of tuple: TupleExprElementListSyntax,


    with newLabel: String


    ) -> TupleExprElementListSyntax {


    guard let firstElement = tuple.first else {


    return tuple


    }


    return tuple.replacing(


    childAt: 0,


    with: firstElement.withLabel(.identifier(newLabel))


    )


    }

    View Slide

  37. How to implement macros
    #colorLiteral()
    /// Replace the label of the
    fi
    rst element in the tuple with the given


    /// new label.


    private func replaceFirstLabel(


    of tuple: TupleExprElementListSyntax,


    with newLabel: String


    ) -> TupleExprElementListSyntax {


    guard let firstElement = tuple.first else {


    return tuple


    }


    return tuple.replacing(


    childAt: 0,


    with: firstElement.withLabel(.identifier(newLabel))


    )


    }

    View Slide

  38. How to implement macros
    #printArguments()
    func doSomething(_ a: Int, b: Int, c d: Int, e _: Int, _: Int, _ _: Int) {


    #printArguments()


    ...


    }

    View Slide

  39. How to implement macros
    #printArguments()
    public struct PrintArgumentsMacro: ExpressionMacro {


    public static func expansion(


    of node: MacroExpansionExprSyntax, in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    var syntax = node.as(Syntax.self)


    while syntax != nil && syntax!.as(FunctionDeclSyntax.self) == nil {


    syntax = syntax!.parent


    }


    guard let functionSyntax = syntax!.as(FunctionDeclSyntax.self) else { return "" }


    let signature: FunctionSignatureSyntax = functionSyntax.signature


    let parameterList = signature.input.parameterList


    let parameters = parameterList.map { parameter -> String in


    let potentialLabel = parameter.firstName!.withoutTrivia().description


    let label = potentialLabel == "_" ? nil : potentialLabel


    let potentialName = parameter.secondName?.withoutTrivia().description ?? potentialLabel


    let name = potentialName == "_" ? nil : potentialName


    var string: String


    if let label {


    string = "\(label): "


    } else {


    string = ""


    }


    if let name {


    string += "\\(\(name))"


    } else {


    string += "_"


    }


    return string


    }


    let parametersString = parameters.joined(separator: ", ")


    return "print(\"\(raw: functionSyntax.identifier.description)(\(raw: parametersString))\")"


    }


    }

    View Slide

  40. How to implement macros
    #printArguments()
    public struct PrintArgumentsMacro: ExpressionMacro {


    public static func expansion(


    of node: MacroExpansionExprSyntax, in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    var syntax = node.as(Syntax.self)


    while syntax != nil && syntax!.as(FunctionDeclSyntax.self) == nil {


    syntax = syntax!.parent


    }


    guard let functionSyntax = syntax!.as(FunctionDeclSyntax.self) else { return "" }


    let signature: FunctionSignatureSyntax = functionSyntax.signature


    let parameterList = signature.input.parameterList


    ...


    }


    }

    View Slide

  41. How to implement macros
    #printArguments()
    public struct PrintArgumentsMacro: ExpressionMacro {


    public static func expansion(


    of node: MacroExpansionExprSyntax, in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    var syntax = node.as(Syntax.self)


    while syntax != nil && syntax!.as(FunctionDeclSyntax.self) == nil {


    syntax = syntax!.parent


    }


    guard let functionSyntax = syntax!.as(FunctionDeclSyntax.self) else { return "" }


    let signature: FunctionSignatureSyntax = functionSyntax.signature


    let parameterList = signature.input.parameterList


    ...


    }


    }

    View Slide

  42. How to implement macros
    #printArguments()
    public struct PrintArgumentsMacro: ExpressionMacro {


    public static func expansion(


    of node: MacroExpansionExprSyntax, in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    var syntax = node.as(Syntax.self)


    while syntax != nil && syntax!.as(FunctionDeclSyntax.self) == nil {


    syntax = syntax!.parent


    }


    guard let functionSyntax = syntax!.as(FunctionDeclSyntax.self) else { return "" }


    let signature: FunctionSignatureSyntax = functionSyntax.signature


    let parameterList = signature.input.parameterList


    ...


    }


    }

    View Slide

  43. public struct PrintArgumentsMacro: ExpressionMacro {


    public static func expansion(


    of node: MacroExpansionExprSyntax, in context: inout MacroExpansionContext


    ) -> ExprSyntax {


    ...


    let parameters = parameterList.map { parameter -> String in


    let potentialLabel = parameter.firstName!.withoutTrivia().description


    let label = potentialLabel == "_" ? nil : potentialLabel


    let potentialName = parameter.secondName?.withoutTrivia().description ?? potentialLabel


    let name = potentialName == "_" ? nil : potentialName


    var string: String


    if let label {


    string = "\(label): "


    } else {


    string = ""


    }


    if let name {


    string += "\\(\(name))"


    } else {


    string += "_"


    }


    return string


    }


    let parametersString = parameters.joined(separator: ", ")


    return "print(\"\(raw: functionSyntax.identifier.description)(\(raw: parametersString))\")"


    }


    }

    View Slide

  44. Swift macro essentials

    View Slide

  45. Swift macro essentials
    • Type safe
    • Syntactic transformations

    • Run at build time

    • Additional checks at build time

    • Sandboxed

    View Slide

  46. Swift macro essentials
    • Type safe

    • Syntactic transformations
    • Run at build time

    • Additional checks at build time

    • Sandboxed

    View Slide

  47. Swift macro essentials
    if let leadingTrivia = macro.leadingTrivia {


    return initSyntax.withLeadingTrivia(leadingTrivia)


    }


    return initSyntax

    View Slide

  48. Swift macro essentials
    • Type safe

    • Syntactic transformations

    • Run at build time
    • Additional checks at build time
    • Sandboxed

    View Slide

  49. Additional checks at build time
    let homepage = #url("https://tryswift.jp/")
    @expression public macro url(_ string: String) -> URL = ...
    @expression public macro sql(_ string: String) -> SQLQuery = ...
    let query = #sql("SELECT * FROM ...")
    More ideas

    View Slide

  50. #URL()
    Compile error if argument is an invalid URL
    public static func expansion(


    of macro: MacroExpansionExprSyntax,


    in context: inout MacroExpansionContext


    ) throws -> ExprSyntax {


    ...


    if !validate(macro.argumentList.first) {


    context.diagnose(


    Diagnostic(


    node: Syntax(macro),


    message: SimpleDiagnosticMessage(


    message: "...",


    diagnosticID: MessageID(domain: "...", id: "..."),


    severity: .error


    )


    )


    )


    }


    ...


    }

    View Slide

  51. #URL()
    Compile error if argument is an invalid URL

    View Slide

  52. Swift macro essentials
    • Type safe

    • Syntactic transformations

    • Run at build time

    • Additional checks at build time

    • Sandboxed

    View Slide

  53. Sandboxed
    Embed binaries in apps without bundles
    let data = #embed("images/icon.png")


    let data = Data([0x4d, 0x49, 0x54, ..., 0x65, 0x6e, 0x73])

    View Slide

  54. Sandboxed
    Can I access the
    fi
    le system from macros?
    https://forums.swift.org/t/pitch-2-expression-macros/61861/9

    View Slide

  55. Pros and cons

    View Slide

  56. Pros and cons
    👍 Macros are type safe

    👍 Macros can extend Swift

    👍 Macros can cooperate with the build process

    🤔 Shoot yourself in the foot with any language

    • Macros are hard to imagine what they do

    • Macros are hard to debug
    • Macros or Functions / Property Wrappers / Result Builders

    🤔 Requires knowledge of Swift Syntax

    🤔 Things not suited for macro

    View Slide

  57. Pros and cons
    Swift Syntax

    View Slide

  58. Things not suited for macro
    • Use functions in preference to macros

    • Use Result Builders and Property Wrappers

    • Macros are code generators
    Pros and cons

    View Slide

  59. Various kinds of macros

    View Slide

  60. Various kinds of macros
    Declaration macros (Freestanding macros, Attached macros)
    @addCompletionHandler


    func fetchAvatar(_ username: String) async -> Image? { ... }
    @declaration(peers: [.overloaded]) macro addCompletionHandler: Void
    /// Expansion of the macro produces the following.


    func fetchAvatar(_ username: String, completionHandler: @escaping (Image?) -> Void) {


    Task.detached {


    completionHandler(await fetchAvatar(username))


    }


    }

    View Slide

  61. Various kinds of macros
    Declaration macros
    @optionSetMembers


    struct MyOptions: OptionSet {


    enum Option: Int {


    case a


    case b


    case c


    }


    }
    // Expands to...


    struct MyOptions: OptionSet {


    enum Option: Int {


    case a


    case b


    case c


    }




    // Synthesized code below...


    var rawValue: Int = 0




    static var a = MyOptions(rawValue: 1 << Option.a.rawValue)


    static var b = MyOptions(rawValue: 1 << Option.b.rawValue)


    static var c = MyOptions(rawValue: 1 << Option.c.rawValue)


    }

    View Slide

  62. Various kinds of macros
    Declaration macros
    @traced(logLevel: 2)


    func myFunction(a: Int, b: Int) { ... }
    @declaration(body) macro traced(logLevel: Int = 0)
    if shouldLog(atLevel: 2) {


    log("Entering myFunction((a: \(a), b: \(b)))")


    }

    View Slide

  63. Various kinds of macros
    Declaration macros
    • Replace code generators with a macro
    @swiftCodeGen


    enum Asset {


    enum Images {


    static let banana = ImageAsset(value: "Exotic/Banana")


    static let mango = ImageAsset(value: "Exotic/Mango")


    }




    enum Colors {


    static let primary = ColorAsset(value: "Vengo/Primary")


    static let tint = ColorAsset(value: "Vengo/Tint")


    }


    enum Symbols {


    static let exclamationMark = SymbolAsset(name: "Exclamation Mark")


    static let plus = SymbolAsset(name: "Plus")


    }


    }
    @swiftCodeGen


    enum Asset {


    }

    View Slide

  64. Practical Example:

    Power Assert

    View Slide

  65. What is Power Assert?
    let john = Person(name: "John", age: 42)


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


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

    View Slide

  66. What is Power Assert?
    #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)
    let john = Person(name: "John", age: 42)


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


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

    View Slide

  67. Why Power Assert?
    Many di
    ff
    erent assertion functions
    public func XCTAssert()


    public func XCTAssertEqual()


    public func XCTAssertEqualWithAccuracy()


    public func XCTAssertFalse()


    public func XCTAssertGreaterThan()


    public func XCTAssertGreaterThanOrEqual()


    public func XCTAssertIdentical()


    public func XCTAssertLessThan()


    public func XCTAssertLessThanOrEqual()


    public func XCTAssertNil()


    public func XCTAssertNoThrow()


    public func XCTAssertNotEqual()


    public func XCTAssertNotEqualWithAccuracy()


    public func XCTAssertNotIdentical()


    public func XCTAssertNotNil()


    public func XCTAssertThrowsError()


    public func XCTAssertTrue()

    View Slide

  68. Why Power Assert?
    Many di
    ff
    erent assertion functions

    View Slide

  69. Why Power Assert?
    Many di
    ff
    erent assertion functions

    View Slide

  70. Why Power Assert?
    Many di
    ff
    erent assertion functions

    View Slide

  71. What is Power Assert?
    • Only simple assert function such as powerAssert(a == b) is su
    ff
    i
    cient

    • Display easy-to-understand information on assert failure (when the test does
    not pass)

    • Bene
    fi
    t of not having to use many di
    ff
    erent assertion functions
    https://azu.github.io/slide/sakurajs/power-assert.html#/2

    View Slide

  72. Why Power Assert?
    That's all you need
    @expression


    public macro powerAssert(_ expression: @autoclosure () throws -> Bool)

    View Slide

  73. Implement Power Assert with
    macros

    View Slide

  74. Implement Power Assert with macros
    #powerAssert(a * b == 91)

    View Slide

  75. Implement Power Assert with macros
    https://swift-ast-explorer.com/
    Swift AST Explorer

    View Slide

  76. Implement Power Assert with macros
    #powerAssert(a * b == 91)
    TupleExprElementList
    Inspect the macro syntax structure

    View Slide

  77. Implement Power Assert with macros
    #powerAssert(a * b == 91)
    TupleExprElementList
    Identi
    fi
    erExpr
    Inspect the macro syntax structure

    View Slide

  78. Implement Power Assert with macros
    #powerAssert(a * b == 91)
    TupleExprElementList
    Identi
    fi
    erExpr
    BinaryOperatorExpr
    Inspect the macro syntax structure

    View Slide

  79. Implement Power Assert with macros
    #powerAssert(a * b == 91)
    TupleExprElementList
    Identi
    fi
    erExpr
    BinaryOperatorExpr
    IntegerLiteralExpr
    Inspect the macro syntax structure

    View Slide

  80. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()

    View Slide

  81. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()

    View Slide

  82. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()

    View Slide

  83. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()

    View Slide

  84. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()

    View Slide

  85. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()
    // => #powerAssert(a * b == 91)


    | | | |


    10 9 | 91


    false

    View Slide

  86. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(a * b == 91)"#, line: 2)


    .assert(a * b == 91)


    .capture(expression: a.self, column: 13)


    .capture(expression: b.self, column: 17)


    .capture(expression: a * b == 91, column: 19)


    .capture(expression: 91, column: 22)


    .render()
    // => #powerAssert(a * b == 91)


    | | | |


    10 9 | 91


    false
    #powerAssert(a * b == 91)

    View Slide

  87. Implement Power Assert with macros
    #powerAssert(String(data: data, encoding: .utf8) == "test")
    FunctionCallExpr
    Identi
    fi
    erExpr MemberAccessExpr

    View Slide

  88. Implement Power Assert with macros
    #powerAssert(String(data: data, encoding: .utf8) == "test")
    FunctionCallExpr
    Identi
    fi
    erExpr MemberAccessExpr

    View Slide

  89. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(String(data: data, encoding: .utf8) == "test")"#, line: 2)


    .assert(String(data: data, encoding: .utf8) == "test")


    .capture(expression: String(data: data, encoding: .utf8), column: 13)


    .capture(expression: data.self, column: 26)


    .capture(expression: .utf8, column: 42)


    .capture(expression: String(data: data, encoding: .utf8) == "test", column: 49)


    .capture(expression: "test", column: 52)


    .render()

    View Slide

  90. Implement Power Assert with macros
    PowerAssert.Assertion(#"#powerAssert(String(data: data, encoding: .utf8) == "test")"#, line: 2)


    .assert(String(data: data, encoding: .utf8) == "test")


    .capture(expression: String(data: data, encoding: .utf8), column: 13)


    .capture(expression: data.self, column: 26)


    .capture(expression: .utf8, column: 42)


    .capture(expression: String(data: data, encoding: .utf8) == "test", column: 49)


    .capture(expression: "test", column: 52)


    .render()

    View Slide

  91. Implement Power Assert with macros
    .capture(expression: data.self, column: 26)


    .capture(expression: .utf8, column: 42)


    .capture(expression: String(data: data,
    No type information in syntax tree

    View Slide

  92. Implement Power Assert with macros
    https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md#macro-argument-type-information
    No type information in syntax tree

    View Slide

  93. Implement Power Assert with macros
    https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md#macro-argument-type-information

    View Slide

  94. Implement Power Assert with macros
    https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md#example-expression-macros

    View Slide

  95. https://github.com/kishikawakatsumi/swift-power-assert
    Implement Power Assert with macros

    View Slide

  96. Summary
    Macros are fun!
    • Macros are a very powerful feature that can extend Swift itself

    • Shoot yourself in the foot if used incorrectly

    • Swift Syntax becomes more important

    • Macros are hard to debug

    • Post your ideas!

    View Slide

  97. Easy? Difficult?
    https://forums.swift.org/t/pitch-declaration-macros/62373/21

    View Slide

  98. Easy? Difficult?
    https://forums.swift.org/t/pitch-declaration-macros/62373/25

    View Slide

  99. Summary
    Macros are fun!
    • Macros are a very powerful feature that can extend Swift itself

    • Shoot yourself in the foot if used incorrectly

    • Swift Syntax becomes more important

    • Macros are hard to debug

    • Post your ideas!

    View Slide

  100. References
    • A Possible Vision for Macros in Swift

    https://forums.swift.org/t/a-possible-vision-for-macros-in-swift/60900

    • Swift project in 2023

    https://www.swift.org/blog/focus-areas-2023/

    • [Pitch] Expression macros

    https://forums.swift.org/t/pitch-expression-macros/61499

    • [Pitch #2] Expression macros

    https://forums.swift.org/t/pitch-2-expression-macros/61861

    • SE-0382: Expression Macros

    https://forums.swift.org/t/se-0382-expression-macros/62090

    • [Pitch] Declaration macros

    https://forums.swift.org/t/pitch-declaration-macros/62373/36

    • 5෼͙Β͍ͰΘ͔Δpower assert

    https://azu.github.io/slide/sakurajs/power-assert.html

    View Slide