@@ -539,53 +539,126 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
539
539
#endif
540
540
}
541
541
542
- /// Insert calls to an expression context into a syntax tree.
543
- ///
544
- /// - Parameters:
545
- /// - expressionContextName: The name of the instance of
546
- /// `__ExpectationContext` to call.
547
- /// - node: The root of a syntax tree to rewrite. This node may not itself be
548
- /// the root of the overall syntax tree—it's just the root of the subtree
549
- /// that we're rewriting.
550
- /// - macro: The macro expression.
551
- /// - effectiveRootNode: The node to treat as the root of the syntax tree for
552
- /// the purposes of generating expression ID values.
553
- /// - context: The macro context in which the expression is being parsed.
554
- ///
555
- /// - Returns: A tuple containing the rewritten copy of `node`, a list of all
556
- /// the nodes within `node` (possibly including `node` itself) that were
557
- /// rewritten, and a code block containing code that should be inserted into
558
- /// the lexical scope of `node` _before_ its rewritten equivalent.
559
- func insertCalls(
560
- toExpressionContextNamed expressionContextName: TokenSyntax ,
561
- into node: some SyntaxProtocol ,
562
- for macro: some FreestandingMacroExpansionSyntax ,
563
- rootedAt effectiveRootNode: some SyntaxProtocol ,
564
- in context: some MacroExpansionContext
565
- ) -> ( Syntax , rewrittenNodes: Set < Syntax > , prefixCodeBlockItems: CodeBlockItemListSyntax ) {
566
- if let node = node. as ( ExprSyntax . self) {
567
- _diagnoseTrivialBooleanValue ( from: node, for: macro, in: context)
568
- }
569
-
570
- let contextInserter = _ContextInserter ( in: context, for: macro, rootedAt: Syntax ( effectiveRootNode) , expressionContextName: expressionContextName)
571
- let result = contextInserter. rewrite ( node)
572
- let rewrittenNodes = contextInserter. rewrittenNodes
573
-
574
- let prefixCodeBlockItems = CodeBlockItemListSyntax {
575
- if !contextInserter. teardownItems. isEmpty {
576
- CodeBlockItemSyntax (
577
- item: . stmt(
578
- StmtSyntax (
579
- DeferStmtSyntax {
580
- contextInserter. teardownItems
542
+ extension ConditionMacro {
543
+ /// Rewrite and expand upon an expression node.
544
+ ///
545
+ /// - Parameters:
546
+ /// - node: The root of a syntax tree to rewrite. This node may not itself
547
+ /// be the root of the overall syntax tree—it's just the root of the
548
+ /// subtree that we're rewriting.
549
+ /// - expressionContextName: The name of the instance of
550
+ /// `__ExpectationContext` to call at runtime.
551
+ /// - macro: The macro expression.
552
+ /// - effectiveRootNode: The node to treat as the root of the syntax tree
553
+ /// for the purposes of generating expression ID values.
554
+ /// - effectKeywordsToApply: The set of effect keywords in the expanded
555
+ /// expression or its lexical context that may apply to `node`.
556
+ /// - context: The macro context in which the expression is being parsed.
557
+ ///
558
+ /// - Returns: A tuple containing the rewritten copy of `node`, a list of all
559
+ /// the nodes within `node` (possibly including `node` itself) that were
560
+ /// rewritten, and a code block containing code that should be inserted into
561
+ /// the lexical scope of `node` _before_ its rewritten equivalent.
562
+ static func rewrite(
563
+ _ node: some ExprSyntaxProtocol ,
564
+ usingExpressionContextNamed expressionContextName: TokenSyntax ,
565
+ for macro: some FreestandingMacroExpansionSyntax ,
566
+ rootedAt effectiveRootNode: some SyntaxProtocol ,
567
+ effectKeywordsToApply: Set < Keyword > ,
568
+ in context: some MacroExpansionContext
569
+ ) -> ( ClosureExprSyntax , rewrittenNodes: Set < Syntax > ) {
570
+ _diagnoseTrivialBooleanValue ( from: ExprSyntax ( node) , for: macro, in: context)
571
+
572
+ let contextInserter = _ContextInserter ( in: context, for: macro, rootedAt: Syntax ( effectiveRootNode) , expressionContextName: expressionContextName)
573
+ var expandedExpr = contextInserter. rewrite ( node) . cast ( ExprSyntax . self)
574
+ let rewrittenNodes = contextInserter. rewrittenNodes
575
+
576
+ // Insert additional effect keywords as needed. Use the helper functions so
577
+ // we don't need to worry about the precise structure of the expression
578
+ // being rewritten.
579
+ if effectKeywordsToApply. contains ( . await ) {
580
+ expandedExpr = " await Testing.__requiringAwait( \( expandedExpr) ) "
581
+ }
582
+ if isThrowing || effectKeywordsToApply. contains ( . try ) {
583
+ expandedExpr = " try Testing.__requiringTry( \( expandedExpr) ) "
584
+ }
585
+
586
+ // Construct the body of the closure that we'll pass to the expanded
587
+ // function.
588
+ var codeBlockItems = CodeBlockItemListSyntax {
589
+ if contextInserter. teardownItems. isEmpty {
590
+ expandedExpr. with ( \. trailingTrivia, . newline)
591
+ } else {
592
+ // Insert a defer statement that runs any teardown items.
593
+ DeferStmtSyntax {
594
+ for teardownItem in contextInserter. teardownItems {
595
+ teardownItem. with ( \. trailingTrivia, . newline)
596
+ }
597
+ } . with ( \. trailingTrivia, . newline)
598
+
599
+ // If we're inserting any additional code into the closure before
600
+ // the rewritten argument, we can't elide the return keyword.
601
+ ReturnStmtSyntax (
602
+ expression: expandedExpr. with ( \. leadingTrivia, . space)
603
+ ) . with ( \. trailingTrivia, . newline)
604
+ }
605
+ }
606
+
607
+ // Replace any dollar identifiers in the code block, then construct a
608
+ // capture list for the closure (if needed.)
609
+ var captureList : ClosureCaptureClauseSyntax ?
610
+ do {
611
+ let dollarIDReplacer = _DollarIdentifierReplacer ( )
612
+ codeBlockItems = dollarIDReplacer. rewrite ( codeBlockItems) . cast ( CodeBlockItemListSyntax . self)
613
+ if !dollarIDReplacer. dollarIdentifierTokenKinds. isEmpty {
614
+ let dollarIdentifierTokens = dollarIDReplacer. dollarIdentifierTokenKinds. map { tokenKind in
615
+ TokenSyntax ( tokenKind, presence: . present)
616
+ }
617
+ captureList = ClosureCaptureClauseSyntax {
618
+ for token in dollarIdentifierTokens {
619
+ ClosureCaptureSyntax ( name: _rewriteDollarIdentifier ( token) , expression: DeclReferenceExprSyntax ( baseName: token) )
620
+ }
621
+ }
622
+ }
623
+ }
624
+
625
+ // Enclose the code block in the final closure.
626
+ let closureExpr = ClosureExprSyntax (
627
+ signature: ClosureSignatureSyntax (
628
+ capture: captureList,
629
+ parameterClause: . parameterClause(
630
+ ClosureParameterClauseSyntax (
631
+ parameters: ClosureParameterListSyntax {
632
+ ClosureParameterSyntax (
633
+ firstName: expressionContextName,
634
+ colon: . colonToken( ) . with ( \. trailingTrivia, . space) ,
635
+ type: TypeSyntax (
636
+ AttributedTypeSyntax (
637
+ specifiers: [
638
+ TypeSpecifierListSyntax . Element (
639
+ SimpleTypeSpecifierSyntax ( specifier: . keyword( . inout) )
640
+ . with ( \. trailingTrivia, . space)
641
+ )
642
+ ] ,
643
+ baseType: MemberTypeSyntax (
644
+ baseType: IdentifierTypeSyntax ( name: . identifier( " Testing " ) ) ,
645
+ name: . identifier( " __ExpectationContext " )
646
+ )
647
+ )
648
+ )
649
+ )
581
650
}
582
651
)
583
- )
584
- )
585
- }
586
- } . formatted ( ) . with ( \. trailingTrivia, . newline) . cast ( CodeBlockItemListSyntax . self)
652
+ ) ,
653
+ inKeyword: . keyword( . in)
654
+ . with ( \. leadingTrivia, . space)
655
+ . with ( \. trailingTrivia, . newline)
656
+ ) ,
657
+ statements: codeBlockItems
658
+ )
587
659
588
- return ( result, rewrittenNodes, prefixCodeBlockItems)
660
+ return ( closureExpr, rewrittenNodes)
661
+ }
589
662
}
590
663
591
664
// MARK: - Finding optional chains
@@ -668,57 +741,23 @@ private func _rewriteDollarIdentifier(_ token: TokenSyntax) -> TokenSyntax {
668
741
/// A syntax rewriter that replaces _numeric_ dollar identifiers (e.g. `$0`)
669
742
/// with normal (non-dollar) identifiers.
670
743
private final class _DollarIdentifierReplacer : SyntaxRewriter {
671
- /// The dollar identifier tokens that have been rewritten.
672
- var dollarIdentifierTokens = Set < TokenSyntax > ( )
673
-
674
- /// The node to treat as the root node when expanding expressions.
675
- var effectiveRootNode : Syntax
676
-
677
- init ( rootedAt effectiveRootNode: Syntax ) {
678
- self . effectiveRootNode = effectiveRootNode
679
- }
680
-
681
- override func visitAny( _ node: Syntax ) -> Syntax ? {
682
- // Do not recurse into closure expressions (except the root node) because
683
- // they will have their own argument/capture lists that won't conflict with
684
- // the enclosing scope's.
685
- if node. is ( ClosureExprSyntax . self) && node != effectiveRootNode {
686
- return Syntax ( node)
687
- }
688
-
689
- return nil
690
- }
744
+ /// The `tokenKind` properties of any dollar identifier tokens that have been
745
+ /// rewritten.
746
+ var dollarIdentifierTokenKinds = Set < TokenKind > ( )
691
747
692
748
override func visit( _ node: TokenSyntax ) -> TokenSyntax {
693
749
if case let . dollarIdentifier( id) = node. tokenKind, id. dropFirst ( ) . allSatisfy ( \. isWholeNumber) {
694
750
// This dollar identifier is numeric, so it's a closure argument.
695
- dollarIdentifierTokens . insert ( node)
751
+ dollarIdentifierTokenKinds . insert ( node. tokenKind )
696
752
return _rewriteDollarIdentifier ( node)
697
753
}
698
754
699
755
return node
700
756
}
701
- }
702
757
703
- /// Rewrite any implicit closure arguments (dollar identifiers such as `$0`) in
704
- /// the given node as normal (non-dollar) identifiers.
705
- ///
706
- /// - Parameters:
707
- /// - node: The syntax node to rewrite.
708
- ///
709
- /// - Returns: A rewritten copy of `node` as well as a closure capture list that
710
- /// can be used to transform the original dollar identifiers to their
711
- /// rewritten counterparts in a nested closure invocation.
712
- func rewriteClosureArguments( in node: some SyntaxProtocol ) -> ( rewrittenNode: Syntax , captureList: ClosureCaptureClauseSyntax ) ? {
713
- let replacer = _DollarIdentifierReplacer ( rootedAt: Syntax ( node) )
714
- let result = replacer. rewrite ( node)
715
- if replacer. dollarIdentifierTokens. isEmpty {
716
- return nil
717
- }
718
- let captureList = ClosureCaptureClauseSyntax {
719
- for token in replacer. dollarIdentifierTokens {
720
- ClosureCaptureSyntax ( name: _rewriteDollarIdentifier ( token) , expression: DeclReferenceExprSyntax ( baseName: token) )
721
- }
758
+ override func visit( _ node: ClosureExprSyntax ) -> ExprSyntax {
759
+ // Do not recurse into closure expressions because they will have their own
760
+ // argument lists that won't conflict with the enclosing scope's.
761
+ return ExprSyntax ( node)
722
762
}
723
- return ( result, captureList)
724
763
}
0 commit comments