@@ -111,6 +111,10 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
111111 /// The nodes in this array are the _original_ nodes, not the rewritten nodes.
112112 var rewrittenNodes = Set < Syntax > ( )
113113
114+ /// Any postflight code the caller should insert into the closure containing
115+ /// the rewritten syntax tree.
116+ var teardownItems = [ CodeBlockItemSyntax] ( )
117+
114118 init ( in context: C , for macro: M , rootedAt effectiveRootNode: Syntax , expressionContextName: TokenSyntax ) {
115119 self . context = context
116120 self . macro = macro
@@ -123,12 +127,13 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
123127 /// (or rather, its `callAsFunction(_:_:)` member).
124128 ///
125129 /// - Parameters:
126- /// - functionNameExpr: If not `nil`, the name of the function to call (as a
127- /// member of the expression context.)
128130 /// - node: The node to rewrite.
129131 /// - originalNode: The original node in the original syntax tree, if `node`
130132 /// has already been partially rewritten or substituted. If `node` has not
131133 /// been rewritten, this argument should equal it.
134+ /// - functionName: If not `nil`, the name of the function to call (as a
135+ /// member function of the expression context.)
136+ /// - additionalArguments: Any additional arguments to pass to the function.
132137 ///
133138 /// - Returns: A rewritten copy of `node` that calls into the expression
134139 /// context when it is evaluated at runtime.
@@ -379,8 +384,24 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
379384 }
380385
381386 override func visit( _ node: InOutExprSyntax ) -> ExprSyntax {
382- // inout arguments cannot be forwarded through functions. In the future, we
383- // could experiment with unsafe mutable pointers?
387+ // Swift's Law of Exclusivity means that only one subexpression in the
388+ // expectation ought to be interacting with `value` when it is passed
389+ // `inout`, so it should be sufficient to capture it in a `defer` statement
390+ // that runs after the expression is evaluated.
391+
392+ let teardownItem = CodeBlockItemSyntax (
393+ item: . expr(
394+ _rewrite (
395+ node. expression,
396+ originalWas: node,
397+ calling: . identifier( " __inoutAfter " )
398+ )
399+ )
400+ )
401+ teardownItems. append ( teardownItem)
402+
403+ // The argument should not be expanded in-place as we can't return an
404+ // argument passed `inout` and expect it to remain semantically correct.
384405 return ExprSyntax ( node)
385406 }
386407
@@ -525,23 +546,42 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
525546/// the purposes of generating expression ID values.
526547/// - context: The macro context in which the expression is being parsed.
527548///
528- /// - Returns: A tuple containing the rewritten copy of `node` as well as a list
529- /// of all the nodes within `node` (possibly including `node` itself) that
530- /// were rewritten.
549+ /// - Returns: A tuple containing the rewritten copy of `node`, a list of all
550+ /// the nodes within `node` (possibly including `node` itself) that were
551+ /// rewritten, and a code block containing code that should be inserted into
552+ /// the lexical scope of `node` _before_ its rewritten equivalent.
531553func insertCalls(
532554 toExpressionContextNamed expressionContextName: TokenSyntax ,
533555 into node: some SyntaxProtocol ,
534556 for macro: some FreestandingMacroExpansionSyntax ,
535557 rootedAt effectiveRootNode: some SyntaxProtocol ,
536558 in context: some MacroExpansionContext
537- ) -> ( Syntax , rewrittenNodes: Set < Syntax > ) {
559+ ) -> ( Syntax , rewrittenNodes: Set < Syntax > , prefixCodeBlockItems : CodeBlockItemListSyntax ) {
538560 if let node = node. as ( ExprSyntax . self) {
539561 _diagnoseTrivialBooleanValue ( from: node, for: macro, in: context)
540562 }
541563
542564 let contextInserter = _ContextInserter ( in: context, for: macro, rootedAt: Syntax ( effectiveRootNode) , expressionContextName: expressionContextName)
543565 let result = contextInserter. rewrite ( node)
544- return ( result, contextInserter. rewrittenNodes)
566+ let rewrittenNodes = contextInserter. rewrittenNodes
567+
568+ let prefixCodeBlockItems = CodeBlockItemListSyntax {
569+ if !contextInserter. teardownItems. isEmpty {
570+ CodeBlockItemSyntax (
571+ item: . stmt(
572+ StmtSyntax (
573+ DeferStmtSyntax {
574+ for teardownItem in contextInserter. teardownItems {
575+ teardownItem
576+ }
577+ }
578+ )
579+ )
580+ )
581+ }
582+ } . formatted ( ) . with ( \. trailingTrivia, . newline) . cast ( CodeBlockItemListSyntax . self)
583+
584+ return ( result, rewrittenNodes, prefixCodeBlockItems)
545585}
546586
547587// MARK: - Finding optional chains
0 commit comments