775 lines
26 KiB
Markdown
775 lines
26 KiB
Markdown
# Macros
|
|
|
|
Use macros to generate code at compile time.
|
|
|
|
Macros transform your source code when you compile it,
|
|
letting you avoid writing repetitive code by hand.
|
|
During compilation,
|
|
Swift expands any macros in your code before building your code as usual.
|
|
|
|

|
|
|
|
Expanding a macro is always an additive operation:
|
|
Macros add new code,
|
|
but they never delete or modify existing code.
|
|
|
|
Both the input to a macro and the output of macro expansion
|
|
are checked to ensure they're syntactically valid Swift code.
|
|
Likewise, the values you pass to a macro
|
|
and the values in code generated by a macro
|
|
are checked to ensure they have the correct types.
|
|
In addition,
|
|
if the macro's implementation encounters an error when expanding that macro,
|
|
the compiler treats this as a compilation error.
|
|
These guarantees make it easier to reason about code that uses macros,
|
|
and they make it easier to identify issues
|
|
like using a macro incorrectly
|
|
or a macro implementation that has a bug.
|
|
|
|
Swift has two kinds of macros:
|
|
|
|
- *Freestanding macros* appear on their own,
|
|
without being attached to a declaration.
|
|
|
|
- *Attached macros* modify the declaration that they're attached to.
|
|
|
|
You call attached and freestanding macros slightly differently,
|
|
but they both follow the same model for macro expansion,
|
|
and you implement them both using the same approach.
|
|
The following sections describe both kinds of macros in more detail.
|
|
|
|
## Freestanding Macros
|
|
|
|
To call a freestanding macro,
|
|
you write a number sign (`#`) before its name,
|
|
and you write any arguments to the macro in parentheses after its name.
|
|
For example:
|
|
|
|
```swift
|
|
func myFunction() {
|
|
print("Currently running \(#function)")
|
|
#warning("Something's wrong")
|
|
}
|
|
```
|
|
|
|
In the first line,
|
|
`#function` calls the [`function()`][] macro from the Swift standard library.
|
|
When you compile this code,
|
|
Swift calls that macro's implementation,
|
|
which replaces `#function` with the name of the current function.
|
|
When you run this code and call `myFunction()`,
|
|
it prints "Currently running myFunction()".
|
|
In the second line,
|
|
`#warning` calls the [`warning(_:)`][] macro from the Swift standard library
|
|
to produce a custom compile-time warning.
|
|
|
|
[`function()`]: https://developer.apple.com/documentation/swift/function()
|
|
[`warning(_:)`]: https://developer.apple.com/documentation/swift/warning(_:)
|
|
|
|
Freestanding macros can produce a value, like `#function` does,
|
|
or they can perform an action at compile time, like `#warning` does.
|
|
<!-- SE-0397: or they can generate new declarations. -->
|
|
|
|
## Attached Macros
|
|
|
|
To call an attached macro,
|
|
you write an at sign (`@`) before its name,
|
|
and you write any arguments to the macro in parentheses after its name.
|
|
|
|
Attached macros modify the declaration that they're attached to.
|
|
They add code to that declaration,
|
|
like defining a new method or adding conformance to a protocol.
|
|
|
|
For example, consider the following code
|
|
that doesn't use macros:
|
|
|
|
```swift
|
|
struct SundaeToppings: OptionSet {
|
|
let rawValue: Int
|
|
static let nuts = SundaeToppings(rawValue: 1 << 0)
|
|
static let cherry = SundaeToppings(rawValue: 1 << 1)
|
|
static let fudge = SundaeToppings(rawValue: 1 << 2)
|
|
}
|
|
```
|
|
|
|
In this code,
|
|
each of the options in the `SundaeToppings` option set
|
|
includes a call to the initializer,
|
|
which is repetitive and manual.
|
|
It would be easy to make a mistake when adding a new option,
|
|
like typing the wrong number at the end of the line.
|
|
|
|
Here's a version of this code that uses a macro instead:
|
|
|
|
```swift
|
|
@OptionSet<Int>
|
|
struct SundaeToppings {
|
|
private enum Options: Int {
|
|
case nuts
|
|
case cherry
|
|
case fudge
|
|
}
|
|
}
|
|
```
|
|
|
|
This version of `SundaeToppings` calls an `@OptionSet` macro.
|
|
The macro reads the list of cases in the private enumeration,
|
|
generates the list of constants for each option,
|
|
and adds a conformance to the [`OptionSet`][] protocol.
|
|
|
|
[`OptionSet`]: https://developer.apple.com/documentation/swift/optionset
|
|
|
|
<!--
|
|
When the @OptionSet macro comes back, change both links back:
|
|
|
|
[`@OptionSet`]: https://developer.apple.com/documentation/swift/optionset-swift.macro
|
|
[`OptionSet`]: https://developer.apple.com/documentation/swift/optionset-swift.protocol
|
|
-->
|
|
|
|
For comparison,
|
|
here's what the expanded version of the `@OptionSet` macro looks like.
|
|
You don't write this code,
|
|
and you would see it only if you specifically asked Swift
|
|
to show the macro's expansion.
|
|
|
|
```swift
|
|
struct SundaeToppings {
|
|
private enum Options: Int {
|
|
case nuts
|
|
case cherry
|
|
case fudge
|
|
}
|
|
|
|
typealias RawValue = Int
|
|
var rawValue: RawValue
|
|
init() { self.rawValue = 0 }
|
|
init(rawValue: RawValue) { self.rawValue = rawValue }
|
|
static let nuts: Self = Self(rawValue: 1 << Options.nuts.rawValue)
|
|
static let cherry: Self = Self(rawValue: 1 << Options.cherry.rawValue)
|
|
static let fudge: Self = Self(rawValue: 1 << Options.fudge.rawValue)
|
|
}
|
|
extension SundaeToppings: OptionSet { }
|
|
```
|
|
|
|
All of the code after the private enumeration
|
|
comes from the `@OptionSet` macro.
|
|
The version of `SundaeToppings`
|
|
that uses a macro to generate all of the static variables
|
|
is easier to read and easier to maintain
|
|
than the manually coded version, earlier.
|
|
|
|
## Macro Declarations
|
|
|
|
In most Swift code,
|
|
when you implement a symbol, like a function or type,
|
|
there's no separate declaration.
|
|
However, for macros, the declaration and implementation are separate.
|
|
A macro's declaration contains its name,
|
|
the parameters it takes,
|
|
where it can be used,
|
|
and what kind of code it generates.
|
|
A macro's implementation contains the code
|
|
that expands the macro by generating Swift code.
|
|
|
|
You introduce a macro declaration with the `macro` keyword.
|
|
For example,
|
|
here's part of the declaration for
|
|
the `@OptionSet` macro used in the previous example:
|
|
|
|
```swift
|
|
public macro OptionSet<RawType>() =
|
|
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
|
|
```
|
|
|
|
The first line
|
|
specifies the macro's name and its arguments ---
|
|
the name is `OptionSet`, and it doesn't take any arguments.
|
|
The second line
|
|
uses the [`externalMacro(module:type:)`][] macro from the Swift standard library
|
|
to tell Swift where the macro's implementation is located.
|
|
In this case,
|
|
the `SwiftMacros` module
|
|
contains a type named `OptionSetMacro`,
|
|
which implements the `@OptionSet` macro.
|
|
|
|
[`externalMacro(module:type:)`]: https://developer.apple.com/documentation/swift/externalmacro(module:type:)
|
|
|
|
Because `OptionSet` is an attached macro,
|
|
its name uses upper camel case,
|
|
like the names for structures and classes.
|
|
Freestanding macros have lower camel case names,
|
|
like the names for variables and functions.
|
|
|
|
> Note:
|
|
> Macros are always declared as `public`.
|
|
> Because the code that declares a macro
|
|
> is in a different module from code that uses that macro,
|
|
> there isn't anywhere you could apply a nonpublic macro.
|
|
|
|
A macro declaration defines the macro's *roles* ---
|
|
the places in source code where that macro can be called,
|
|
and the kinds of code the macro can generate.
|
|
Every macro has one or more roles,
|
|
which you write as part of the attributes
|
|
at the beginning of the macro declaration.
|
|
Here's a bit more of the declaration for `@OptionSet`,
|
|
including the attributes for its roles:
|
|
|
|
```swift
|
|
@attached(member)
|
|
@attached(extension, conformances: OptionSet)
|
|
public macro OptionSet<RawType>() =
|
|
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
|
|
```
|
|
|
|
The `@attached` attribute appears twice in this declaration,
|
|
once for each macro role.
|
|
The first use, `@attached(member)`, indicates that the macro
|
|
adds new members to the type you apply it to.
|
|
The `@OptionSet` macro adds an `init(rawValue:)` initializer
|
|
that's required by the `OptionSet` protocol,
|
|
as well as some additional members.
|
|
The second use, `@attached(extension, conformances: OptionSet)`,
|
|
tells you that `@OptionSet`
|
|
adds conformance to the `OptionSet` protocol.
|
|
The `@OptionSet` macro
|
|
extends the type that you apply the macro to,
|
|
to add conformance to the `OptionSet` protocol.
|
|
|
|
For a freestanding macro,
|
|
you write the `@freestanding` attribute to specify its role:
|
|
|
|
```swift
|
|
@freestanding(expression)
|
|
public macro line<T: ExpressibleByIntegerLiteral>() -> T =
|
|
/* ... location of the macro implementation... */
|
|
```
|
|
|
|
<!--
|
|
Elided the implementation of #line above
|
|
because it's a compiler built-in:
|
|
|
|
public macro line<T: ExpressibleByIntegerLiteral>() -> T = Builtin.LineMacro
|
|
-->
|
|
|
|
The `#line` macro above has the `expression` role.
|
|
An expression macro produces a value,
|
|
or performs a compile-time action like generating a warning.
|
|
|
|
In addition to the macro's role,
|
|
a macro's declaration provides information about
|
|
the names of the symbols that the macro generates.
|
|
When a macro declaration provides a list of names,
|
|
it's guaranteed to produce only declarations that use those names,
|
|
which helps you understand and debug the generated code.
|
|
Here's the full declaration of `@OptionSet`:
|
|
|
|
```swift
|
|
@attached(member, names: named(RawValue), named(rawValue),
|
|
named(`init`), arbitrary)
|
|
@attached(extension, conformances: OptionSet)
|
|
public macro OptionSet<RawType>() =
|
|
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
|
|
```
|
|
|
|
In the declaration above,
|
|
the `@attached(member)` macro includes arguments after the `names:` label
|
|
for each of the symbols that the `@OptionSet` macro generates.
|
|
The macro adds declarations for symbols named
|
|
`RawValue`, `rawValue`, and `init` ---
|
|
because those names are known ahead of time,
|
|
the macro declaration lists them explicitly.
|
|
|
|
The macro declaration also includes `arbitrary` after the list of names,
|
|
allowing the macro to generate declarations
|
|
whose names aren't known until you use the macro.
|
|
For example,
|
|
when the `@OptionSet` macro is applied to the `SundaeToppings` above,
|
|
it generates type properties that correspond to the enumeration cases,
|
|
`nuts`, `cherry`, and `fudge`.
|
|
|
|
For more information,
|
|
including a full list of macro roles,
|
|
see <doc:Attributes#attached> and <doc:Attributes#freestanding>
|
|
in <doc:Attributes>.
|
|
|
|
## Macro Expansion
|
|
|
|
When building Swift code that uses macros,
|
|
the compiler calls the macros' implementation to expand them.
|
|
|
|

|
|
|
|
Specifically, Swift expands macros in the following way:
|
|
|
|
1. The compiler reads the code,
|
|
creating an in-memory representation of the syntax.
|
|
|
|
1. The compiler sends part of the in-memory representation
|
|
to the macro implementation,
|
|
which expands the macro.
|
|
|
|
1. The compiler replaces the macro call with its expanded form.
|
|
|
|
1. The compiler continues with compilation,
|
|
using the expanded source code.
|
|
|
|
To go through the specific steps, consider the following:
|
|
|
|
```swift
|
|
let magicNumber = #fourCharacterCode("ABCD")
|
|
```
|
|
|
|
The `#fourCharacterCode` macro takes a string that's four characters long
|
|
and returns an unsigned 32-bit integer
|
|
that corresponds to the ASCII values in the string joined together.
|
|
Some file formats use integers like this to identify data
|
|
because they're compact but still readable in a debugger.
|
|
The <doc:Macros#Implementing-a-Macro> section below
|
|
shows how to implement this macro.
|
|
|
|
To expand the macros in the code above,
|
|
the compiler reads the Swift file
|
|
and creates an in-memory representation of that code
|
|
known as an *abstract syntax tree*, or AST.
|
|
The AST makes the code's structure explicit,
|
|
which makes it easier to write code that interacts with that structure ---
|
|
like a compiler or a macro implementation.
|
|
Here's a representation of the AST for the code above,
|
|
slightly simplified by omitting some extra detail:
|
|
|
|

|
|
|
|
The diagram above shows how the structure of this code
|
|
is represented in memory.
|
|
Each element in the AST
|
|
corresponds to a part of the source code.
|
|
The "Constant declaration" AST element
|
|
has two child elements under it,
|
|
which represent the two parts of a constant declaration:
|
|
its name and its value.
|
|
The "Macro call" element has child elements
|
|
that represent the macro's name
|
|
and the list of arguments being passed to the macro.
|
|
|
|
As part of constructing this AST,
|
|
the compiler checks that the source code is valid Swift.
|
|
For example, `#fourCharacterCode` takes a single argument,
|
|
which must be a string.
|
|
If you tried to pass an integer argument,
|
|
or forgot the quotation mark (`"`) at the end of the string literal,
|
|
you'd get an error at this point in the process.
|
|
|
|
The compiler finds the places in the code where you call a macro,
|
|
and loads the external binary that implements those macros.
|
|
For each macro call,
|
|
the compiler passes part of the AST to that macro's implementation.
|
|
Here's a representation of that partial AST:
|
|
|
|

|
|
|
|
The implementation of the `#fourCharacterCode` macro
|
|
reads this partial AST as its input when expanding the macro.
|
|
A macro's implementation
|
|
operates only on the partial AST that it receives as its input,
|
|
meaning a macro always expands the same way
|
|
regardless of what code comes before and after it.
|
|
This limitation helps make macro expansion easier to understand,
|
|
and helps your code build faster
|
|
because Swift can avoid expanding macros that haven't changed.
|
|
<!-- TODO TR: Confirm -->
|
|
Swift helps macro authors avoid accidentally reading other input
|
|
by restricting the code that implements macros:
|
|
|
|
- The AST passed to a macro implementation
|
|
contains only the AST elements that represent the macro,
|
|
not any of the code that comes before or after it.
|
|
|
|
- The macro implementation runs in a sandboxed environment
|
|
that prevents it from accessing the file system or the network.
|
|
|
|
In addition to these safeguards,
|
|
the macro's author is responsible for not reading or modifying anything
|
|
outside of the macro's inputs.
|
|
For example, a macro's expansion must not depend on the current time of day.
|
|
|
|
The implementation of `#fourCharacterCode`
|
|
generates a new AST containing the expanded code.
|
|
Here's what that code returns to the compiler:
|
|
|
|

|
|
|
|
When the compiler receives this expansion,
|
|
it replaces the AST element that contains the macro call
|
|
with the element that contains the macro's expansion.
|
|
After macro expansion,
|
|
the compiler checks again to ensure
|
|
the program is still syntactically valid Swift
|
|
and all the types are correct.
|
|
That produces a final AST that can be compiled as usual:
|
|
|
|

|
|
|
|
This AST corresponds to Swift code like this:
|
|
|
|
```swift
|
|
let magicNumber = 1145258561 as UInt32
|
|
```
|
|
|
|
In this example, the input source code has only one macro,
|
|
but a real program could have several instances of the same macro
|
|
and several calls to different macros.
|
|
The compiler expands macros one at a time.
|
|
|
|
If one macro appears inside another,
|
|
the outer macro is expanded first ---
|
|
this lets the outer macro modify the inner macro before it's expanded.
|
|
|
|
<!-- OUTLINE
|
|
|
|
- TR: Is there any limit to nesting?
|
|
TR: Is it valid to nest like this -- if so, anything to note about it?
|
|
|
|
```
|
|
let something = #someMacro {
|
|
struct A { }
|
|
@someMacro struct B { }
|
|
}
|
|
```
|
|
|
|
- Macro recursion is limited.
|
|
One macro can call another,
|
|
but a given macro can't directly or indirectly call itself.
|
|
The result of macro expansion can include other macros,
|
|
but it can't include a macro that uses this macro in its expansion
|
|
or declare a new macro.
|
|
(TR: Likely need to iterate on details here)
|
|
-->
|
|
|
|
## Implementing a Macro
|
|
|
|
To implement a macro, you make two components:
|
|
A type that performs the macro expansion,
|
|
and a library that declares the macro to expose it as API.
|
|
These parts are built separately from code that uses the macro,
|
|
even if you're developing the macro and its clients together,
|
|
because the macro implementation runs
|
|
as part of building the macro's clients.
|
|
|
|
To create a new macro using Swift Package Manager,
|
|
run `swift package init --type macro` ---
|
|
this creates several files,
|
|
including a template for a macro implementation and declaration.
|
|
|
|
To add macros to an existing project,
|
|
edit the beginning of your `Package.swift` file as follows:
|
|
|
|
- Set a Swift tools version of 5.9 or later in the `swift-tools-version` comment.
|
|
- Import the `CompilerPluginSupport` module.
|
|
- Include macOS 10.15 as a minimum deployment target in the `platforms` list.
|
|
|
|
The code below shows the beginning of an example `Package.swift` file.
|
|
|
|
```swift
|
|
// swift-tools-version: 5.9
|
|
|
|
import PackageDescription
|
|
import CompilerPluginSupport
|
|
|
|
let package = Package(
|
|
name: "MyPackage",
|
|
platforms: [ .iOS(.v17), .macOS(.v13)],
|
|
// ...
|
|
)
|
|
```
|
|
|
|
Next, add a target for the macro implementation
|
|
and a target for the macro library
|
|
to your existing `Package.swift` file.
|
|
For example,
|
|
you can add something like the following,
|
|
changing the names to match your project:
|
|
|
|
```swift
|
|
targets: [
|
|
// Macro implementation that performs the source transformations.
|
|
.macro(
|
|
name: "MyProjectMacros",
|
|
dependencies: [
|
|
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
|
|
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
|
|
]
|
|
),
|
|
|
|
// Library that exposes a macro as part of its API.
|
|
.target(name: "MyProject", dependencies: ["MyProjectMacros"]),
|
|
]
|
|
```
|
|
|
|
The code above defines two targets:
|
|
`MyProjectMacros` contains the implementation of the macros,
|
|
and `MyProject` makes those macros available.
|
|
|
|
The implementation of a macro
|
|
uses the [SwiftSyntax][] module to interact with Swift code
|
|
in a structured way, using an AST.
|
|
If you created a new macro package with Swift Package Manager,
|
|
the generated `Package.swift` file
|
|
automatically includes a dependency on SwiftSyntax.
|
|
If you're adding macros to an existing project,
|
|
add a dependency on SwiftSyntax in your `Package.swift` file:
|
|
|
|
[SwiftSyntax]: https://github.com/swiftlang/swift-syntax
|
|
|
|
```swift
|
|
dependencies: [
|
|
.package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0")
|
|
],
|
|
```
|
|
|
|
Depending on your macro's role,
|
|
there's a corresponding protocol from SwiftSyntax
|
|
that the macro implementation conforms to.
|
|
For example,
|
|
consider `#fourCharacterCode` from the previous section.
|
|
Here's a structure that implements that macro:
|
|
|
|
```swift
|
|
import SwiftSyntax
|
|
import SwiftSyntaxMacros
|
|
|
|
public struct FourCharacterCode: ExpressionMacro {
|
|
public static func expansion(
|
|
of node: some FreestandingMacroExpansionSyntax,
|
|
in context: some MacroExpansionContext
|
|
) throws -> ExprSyntax {
|
|
guard let argument = node.argumentList.first?.expression,
|
|
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
|
|
segments.count == 1,
|
|
case .stringSegment(let literalSegment)? = segments.first
|
|
else {
|
|
throw CustomError.message("Need a static string")
|
|
}
|
|
|
|
let string = literalSegment.content.text
|
|
guard let result = fourCharacterCode(for: string) else {
|
|
throw CustomError.message("Invalid four-character code")
|
|
}
|
|
|
|
return "\(raw: result) as UInt32"
|
|
}
|
|
}
|
|
|
|
private func fourCharacterCode(for characters: String) -> UInt32? {
|
|
guard characters.count == 4 else { return nil }
|
|
|
|
var result: UInt32 = 0
|
|
for character in characters {
|
|
result = result << 8
|
|
guard let asciiValue = character.asciiValue else { return nil }
|
|
result += UInt32(asciiValue)
|
|
}
|
|
return result
|
|
}
|
|
enum CustomError: Error { case message(String) }
|
|
```
|
|
|
|
If you're adding this macro to an existing Swift Package Manager project,
|
|
add a type that acts as the entry point for the macro target
|
|
and lists the macros that the target defines:
|
|
|
|
```swift
|
|
import SwiftCompilerPlugin
|
|
|
|
@main
|
|
struct MyProjectMacros: CompilerPlugin {
|
|
var providingMacros: [Macro.Type] = [FourCharacterCode.self]
|
|
}
|
|
```
|
|
|
|
The `#fourCharacterCode` macro
|
|
is a freestanding macro that produces an expression,
|
|
so the `FourCharacterCode` type that implements it
|
|
conforms to the `ExpressionMacro` protocol.
|
|
The `ExpressionMacro` protocol has one requirement,
|
|
an `expansion(of:in:)` method that expands the AST.
|
|
For the list of macro roles and their corresponding SwiftSyntax protocols,
|
|
see <doc:Attributes#attached> and <doc:Attributes#freestanding>
|
|
in <doc:Attributes>.
|
|
|
|
To expand the `#fourCharacterCode` macro,
|
|
Swift sends the AST for the code that uses this macro
|
|
to the library that contains the macro implementation.
|
|
Inside the library, Swift calls `FourCharacterCode.expansion(of:in:)`,
|
|
passing in the AST and the context as arguments to the method.
|
|
The implementation of `expansion(of:in:)`
|
|
finds the string that was passed as an argument to `#fourCharacterCode`
|
|
and calculates the corresponding 32-bit unsigned integer literal value.
|
|
|
|
In the example above,
|
|
the first `guard` block extracts the string literal from the AST,
|
|
assigning that AST element to `literalSegment`.
|
|
The second `guard` block
|
|
calls the private `fourCharacterCode(for:)` function.
|
|
Both of these blocks throw an error if the macro is used incorrectly ---
|
|
the error message becomes a compiler error
|
|
at the malformed call site.
|
|
For example,
|
|
if you try to call the macro as `#fourCharacterCode("AB" + "CD")`
|
|
the compiler shows the error "Need a static string".
|
|
|
|
The `expansion(of:in:)` method returns an instance of `ExprSyntax`,
|
|
a type from SwiftSyntax that represents an expression in an AST.
|
|
Because this type conforms to the `StringLiteralConvertible` protocol,
|
|
the macro implementation uses a string literal
|
|
as a lightweight syntax to create its result.
|
|
All of the SwiftSyntax types that you return from a macro implementation
|
|
conform to `StringLiteralConvertible`,
|
|
so you can use this approach when implementing any kind of macro.
|
|
|
|
<!-- TODO contrast the `\(raw:)` and non-raw version. -->
|
|
|
|
<!--
|
|
The return-a-string APIs come from here
|
|
|
|
https://github.com/swiftlang/swift-syntax/blob/main/Sources/SwiftSyntaxBuilder/Syntax%2BStringInterpolation.swift
|
|
-->
|
|
|
|
<!-- OUTLINE:
|
|
|
|
- Note:
|
|
Behind the scenes, Swift serializes and deserializes the AST,
|
|
to pass the data across process boundaries,
|
|
but your macro implementation doesn't need to deal with any of that.
|
|
|
|
- This method is also passed a macro-expansion context, which you use to:
|
|
|
|
+ Generate unique symbol names
|
|
+ Produce diagnostics (`Diagnostic` and `SimpleDiagnosticMessage`)
|
|
+ Find a node's location in source
|
|
|
|
- Macro expansion happens in their surrounding context.
|
|
A macro can affect that environment if it needs to ---
|
|
and a macro that has bugs can interfere with that environment.
|
|
(Give guidance on when you'd do this. It should be rare.)
|
|
|
|
- Generated symbol names let a macro
|
|
avoid accidentally interacting with symbols in that environment.
|
|
To generate a unique symbol name,
|
|
call the `MacroExpansionContext.makeUniqueName()` method.
|
|
|
|
- Ways to create a syntax node include
|
|
Making an instance of the `Syntax` struct,
|
|
or `SyntaxToken`
|
|
or `ExprSyntax`.
|
|
(Need to give folks some general ideas,
|
|
and enough guidance so they can sort through
|
|
all the various `SwiftSyntax` node types and find the right one.)
|
|
|
|
- Attached macros follow the same general model as expression macros,
|
|
but with more moving parts.
|
|
|
|
- Pick the subprotocol of `AttachedMacro` to conform to,
|
|
depending on which kind of attached macro you're making.
|
|
[This is probably a table]
|
|
|
|
+ `AccessorMacro` goes with `@attached(accessor)`
|
|
+ `ConformanceMacro` goes with `@attached(conformance)`
|
|
[missing from the list under Declaring a Macro]
|
|
+ `MemberMacro` goes with `@attached(member)`
|
|
+ `PeerMacro` goes with `@attached(peer)`
|
|
+ `MemberAttributeMacro` goes with `@member(memberAttribute)`
|
|
|
|
- Code example of conforming to `MemberMacro`.
|
|
|
|
```
|
|
static func expansion<
|
|
Declaration: DeclGroupSyntax,
|
|
Context: MacroExpansionContext
|
|
>(
|
|
of node: AttributeSyntax,
|
|
providingMembersOf declaration: Declaration,
|
|
in context: Context
|
|
) throws -> [DeclSyntax]
|
|
```
|
|
|
|
- Adding a new member by making an instance of `Declaration`,
|
|
and returning it as part of the `[DeclSyntax]` list.
|
|
|
|
-->
|
|
|
|
## Developing and Debugging Macros
|
|
|
|
Macros are well suited to development using tests:
|
|
They transform one AST into another AST
|
|
without depending on any external state,
|
|
and without making changes to any external state.
|
|
In addition, you can create syntax nodes from a string literal,
|
|
which simplifies setting up the input for a test.
|
|
You can also read the `description` property of an AST
|
|
to get a string to compare against an expected value.
|
|
For example,
|
|
here's a test of the `#fourCharacterCode` macro from previous sections:
|
|
|
|
```swift
|
|
let source: SourceFileSyntax =
|
|
"""
|
|
let abcd = #fourCharacterCode("ABCD")
|
|
"""
|
|
|
|
let file = BasicMacroExpansionContext.KnownSourceFile(
|
|
moduleName: "MyModule",
|
|
fullFilePath: "test.swift"
|
|
)
|
|
|
|
let context = BasicMacroExpansionContext(sourceFiles: [source: file])
|
|
|
|
let transformedSF = source.expand(
|
|
macros:["fourCharacterCode": FourCharacterCode.self],
|
|
in: context
|
|
)
|
|
|
|
let expectedDescription =
|
|
"""
|
|
let abcd = 1145258561 as UInt32
|
|
"""
|
|
|
|
precondition(transformedSF.description == expectedDescription)
|
|
```
|
|
|
|
The example above tests the macro using a precondition,
|
|
but you could use a testing framework instead.
|
|
|
|
<!-- OUTLINE:
|
|
|
|
- Ways to view the macro expansion while debugging.
|
|
The SE prototype provides `-Xfrontend -dump-macro-expansions` for this.
|
|
[TR: Is this flag what we should suggest folks use,
|
|
or will there be better command-line options coming?]
|
|
|
|
- Use diagnostics for macros that have constraints/requirements
|
|
so your code can give a meaningful error to users when those aren't met,
|
|
instead of letting the compiler try & fail to build the generated code.
|
|
|
|
Additional APIs and concepts to introduce in the future,
|
|
in no particular order:
|
|
|
|
- Using `SyntaxRewriter` and the visitor pattern for modifying the AST
|
|
|
|
- Adding a suggested correction using `FixIt`
|
|
|
|
- concept of trivia
|
|
|
|
- `TokenSyntax`
|
|
-->
|
|
|
|
<!--
|
|
This source file is part of the Swift.org open source project
|
|
|
|
Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
|
|
Licensed under Apache License v2.0 with Runtime Library Exception
|
|
|
|
See https://swift.org/LICENSE.txt for license information
|
|
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
-->
|