You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
*[Capturing the withSpan pattern in another macro role](#capturing-the-withspan-pattern-in-another-macro-role)
@@ -123,12 +121,12 @@ When using the shorthand syntax for get-only properties, a function body macro c
123
121
124
122
### Declaring function body macros
125
123
126
-
Function body macros are declared with the `body`or `preamble`role, which indicate that they can be attached to any kind of function, and can produce the contents of a function body. For example, here are declarations for the macros used above:
124
+
Function body macros are declared with the `body` role, which indicate that they can be attached to any kind of function, and can produce the contents of a function body. For example, here are declarations for the macros used above:
@@ -158,99 +156,10 @@ public protocol BodyMacro: AttachedMacro {
158
156
159
157
That function may have a function body, which will be replaced by the code items produced from the macro implementation.
160
158
161
-
Preamble macros are implemented with a type that conforms to the `PreambleMacro` protocol:
162
-
163
-
```swift
164
-
/// Describes a macro that can introduce "preamble" code into an existing
165
-
/// function body.
166
-
publicprotocolPreambleMacro: AttachedMacro {
167
-
/// Expand a macro described by the given custom attribute and
168
-
/// attached to the given declaration and evaluated within a
169
-
/// particular expansion context.
170
-
///
171
-
/// The macro expansion can introduce code items that form a preamble to
172
-
/// the body of the given function. The code items produced by this macro
173
-
/// expansion will be inserted at the beginning of the function body.
174
-
staticfuncexpansion(
175
-
ofnode: AttributeSyntax,
176
-
providingPreambleFordeclaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
177
-
incontext: some MacroExpansionContext
178
-
) throws-> [CodeBlockItemSyntax]
179
-
}
180
-
```
181
-
182
-
Preamble macros don't provide a complete function body. Rather, they provide code items that will be introduced at the beginning of the existing function body. Preamble macros are useful for injecting code without changing anything else about the function body, for example to introduce logging or simple tracing facilities. The `defer` statement can be used to trigger code that will run once the function returns.
183
-
184
159
### Composing function body macros
185
160
186
161
At most one `body` macro can be applied to a given function. It receives the function declaration to which it is attached as it was written in the source code and produces a new function body.
187
162
188
-
Any number of `preamble` macros can be applied to a given function. Each sees the function declaration to which it is attached as it was written in the source code, and produces code items that will be introduced at the beginning of the function body. The code items produced by the `preamble` macros will be applied in the order that the preamble macro attributes are applied in the source.
189
-
190
-
For example, given:
191
-
192
-
```swift
193
-
@A@B@Cfuncf() { print("hello") }
194
-
```
195
-
196
-
Where `A` and `B` are preamble macros and `C` is a function body macro, each macro will see the same declaration of `f`. The resulting function body for `f` will involve the code items produced by all three macros as follows:
197
-
198
-
```swift
199
-
{
200
-
// code items produced by preamble macro A
201
-
// code items produced by preamble macro B
202
-
// code items produced by function body macro C
203
-
}
204
-
```
205
-
206
-
Preamble macros may introduce new local declarations, which will be visible to later preamble macros (e.g., names introduced by `@A` will be visible in `@B` and `@C` as well) and the remainder of the function body. As with all macros that introduce new names into existing code, those names must be documented with a [`names` clause](https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md#specifying-newly-introduced-names). For example, a tracing macro for [swift-distributed-tracing](https://swiftpackageindex.com/apple/swift-distributed-tracing) might want to introduce a local variable `span` that can be used by the rest of the function body to add more information to the trace. Such a macro could be declared as a preamble macro as follows:
It is possible that the newly-introduced `span` variable could shadow a variable from an outer scope, for example if `prepareDinner` was inside an actor with `span` property:
print("Arm span is \(span) meters") // refers to 'span' from the Traced macro expansion
249
-
// ...
250
-
}
251
-
}
252
-
```
253
-
254
163
### Type checking of functions involving function body macros
255
164
256
165
When a function body macro is applied, the macro-expanded function body will need to be type checked when it is incorporated into the program. However, the function might already have a body that was written by the developer, which can be inspected by the macro implementation. The function body as written must be syntactically well-formed (i.e., it must conform to the [Swift grammar](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/summaryofthegrammar/)) but will *not* be type-checked, so it need not be semantically well-formed.
The requirement for syntactic wellformedness should help rein in the more outlandish uses of function body macros, as well as making sure that existing tools that operate on source code will continue to work well even in the presence of body macros.
283
192
284
-
### Function body macros and implicit returns
285
-
286
-
Swift functions that contain only a single expression will implicitly return that expression, e.g.,
287
-
288
-
```swift
289
-
funcg(a: Int, b: Int) ->Int {
290
-
a + b // return is implicit
291
-
}
292
-
```
293
-
294
-
The application of preamble macros to the function body does not affect the implicit return, so the following is well-formed
295
-
296
-
```swift
297
-
@Logged
298
-
funcg(a: Int, b: Int) ->Int {
299
-
a + b // return is implicit
300
-
}
301
-
```
302
-
303
-
and the expansion of the macro is treated like the following code:
304
-
305
-
```swift
306
-
funcg(a: Int, b: Int) ->Int {
307
-
log("Entering g(a: \(a), b: \(b))")
308
-
defer {
309
-
log("Exiting g")
310
-
}
311
-
return a + b
312
-
}
313
-
```
314
-
315
-
Body macros are different: because they replace the entire function body, whether the original function body as written would have had an implicit return is not considered.
316
-
317
193
## Source compatibility
318
194
319
-
Function body macros introduce new macro roles into the existing attached macro syntax, and therefore do not have an impact on source compatibility.
195
+
Function body macros introduce a new macro role into the existing attached macro syntax, and therefore does not have an impact on source compatibility.
320
196
321
197
## Effect on ABI stability
322
198
@@ -338,7 +214,7 @@ Function body macros as presented in this proposal are limited to declared funct
338
214
}
339
215
```
340
216
341
-
This extension would involve extending the `PreambleMacro` and `BodyMacro`protocols with another `expansion` method that accepts closure syntax. The primary challenge with applying function body macros to closures is the interaction with type inference, because closures generally occur within an expression and some of the macro arguments themselves might be part of the expression. In the example above, the `z` value could come from an outer scope and be the subject of type inference:
217
+
This extension would involve extending the `BodyMacro`protocol with another `expansion` method that accepts closure syntax. The primary challenge with applying function body macros to closures is the interaction with type inference, because closures generally occur within an expression and some of the macro arguments themselves might be part of the expression. In the example above, the `z` value could come from an outer scope and be the subject of type inference:
342
218
343
219
```swift
344
220
f(0) { z in
@@ -350,21 +226,23 @@ f(0) { z in
350
226
351
227
Macros are designed to avoid [multiply instantiating the same macro](https://github.com/apple/swift-evolution/blob/main/proposals/0382-expression-macros.md#macro-expansion), and have existing limitations in place to prevent the type checker from getting into a position where it is not obvious which macro to expand or the same macro needs to be expanded multiple times. To extend function body macros to closures will require a solution to this type-checking issue, and might be paired with lifting other restrictions on (e.g.) freestanding declaration macros.
352
228
353
-
##Alternatives considered
229
+
### Preamble macros
354
230
355
-
### Eliminating preamble macros
356
-
357
-
Preamble macros aren't technically necessary, because one could always write a function body macro that injects the preamble code into an existing body. However, preamble macros provide several end-user benefits over function body macros for the cases where they apply:
231
+
The first reviewed revision of this proposal contained *preamble* macros, which let a macro introduce code at the beginning of a function without changing the rest of the function body. Preamble macros aren't technically necessary, because one could always write a function body macro that injects the preamble code into an existing body. However, preamble macros provide several end-user benefits over function body macros for the cases where they apply:
358
232
359
233
* Preamble macros can be composed, whereas function body macros cannot.
360
234
* Preamble macros don't change the code as written by the user, so they provide a better user experience (e.g., for diagnostics, code completion, and so on).
361
235
362
-
### Capturing the `withSpan` pattern in another macro role
236
+
Preamble macros would be expressed as its own attached macro role (`preamble`), implemented with a type that conforms to the `PreambleMacro` protocol. Details are available in [the prior revision](https://github.com/apple/swift-evolution/blob/f1b9da80315578666352a7d6d40a9f6cc936f69a/proposals/0415-function-body-macros.md).
237
+
238
+
Preamble macros have been moved out to Future Directions because they represent a possible future, but not an obviously right one: preamble macros might not add sufficient expressivity to cover the cost of the complexity they introduce, and another kind of macro (like the "wrapper" macro below) might provide a more reasonable tradeoff between expressivity and complexity.
363
239
364
-
An alternative formulation of the `Traced` macro (call it `@TracedBody`) makes use of its existing [`withSpan` API](https://swiftpackageindex.com/apple/swift-distributed-tracing/1.0.1/documentation/tracing) in a `body` macro, such that a function such as:
240
+
### Wrapper macros
241
+
242
+
A number of use cases for body macros involve "wrapping" the existing body in additional logic. For example, consider an alternative formulation of the `Traced` macro (let's call it `@TracedWithSpan`) could make use of the [`withSpan` API](https://swiftpackageindex.com/apple/swift-distributed-tracing/1.0.1/documentation/tracing) such that a function such as:
The`withSpan` function used here is one instance of a fairly general pattern in Swift, where a `with<something>`function accepts a closure argument and runs it with some extra contextual parameters. As we did with the `preamble` macro role, we could introduce a special macro role that describes this pattern: the macro would not see the function body that was written by the developer at all, but would instead have a function value representing the body that it could call opaquely. For example, the `TracedBody` example function `h` would expand to:
261
+
This`withSpan` function used here is one instance of a fairly general pattern in Swift, where a function accepts a closure argument and runs it with some extra contextual parameters. As we with the `preamble` macro role mentioned above, we could introduce a special macro role that describes this pattern: the macro would not see the function body that was written by the developer at all, but would instead have a function value representing the body that it could call opaquely. For example, the `TracedWithSpan` example function `h` would expand to:
With this approach, the original function body for `h` would be type-checked prior to macro expansion, and then would be handed off to the macro as an opaque value `h-impl` to be called by `withSpan`. The macro could introduce its own closure wrapping that body as needed, e.g.,
392
270
393
271
```swift
394
-
@TracedBody("Doing complicated math", { span in
272
+
@TracedWithSpan("Doing complicated math", { span in
The advantage of this approach over allowing a `body` macro to replace a body is that we can type-check the function body as it was written, and only need to do so once---then it becomes a value of function type that's passed along to the underying macro. Also like preamble macros, this approach can compose, because the result of one macro could produce another value of function type that can be passed along to another macro.
291
+
The advantage of this approach over allowing a `body` macro to replace a body is that we can type-check the function body as it was written, and only need to do so once---then it becomes a value of function type that's passed along to the underying macro. Also like preamble macros, this approach can compose, because the result of one macro could produce another value of function type that can be passed along to another macro.[Python decorators](https://www.datacamp.com/tutorial/decorators-python) have been successful in that language for customizing the behavior of functions in a similar manner.
414
292
415
-
On the other hand, having a third kind of macro role for function body macros adds yet more language complexity, and introducing this role in lieu of allowing function body macros to replace an existing function body might be overfitting to today's use cases. Moreover, this only works for very specific examples. The `@AssumeMainActor` macro would not be able to use this feature, because the intent is that the code as written (in a `nonisolated` function) is processed in a different actor isolation context (the `@MainActor` closure).
293
+
## Alternatives considered
416
294
417
295
### Type-checking bodies as they were written
418
296
@@ -422,6 +300,9 @@ On the other hand, type-checking the function bodies before macro expansion has
422
300
423
301
## Revision history
424
302
303
+
* Revision 3:
304
+
* Narrowed the focus down to `body` macros.
305
+
* Moved preamble macros into Future Directions, added discussion of wrapper macros.
425
306
* Revision 2:
426
307
* Clarify that preamble macro-introduced local names can shadow names from outer scopes
427
308
* Clarify the effect of function body macros on single-expression functions and implicit returns
0 commit comments