@@ -539,53 +539,126 @@ private final class _ContextInserter<C, M>: SyntaxRewriter where C: MacroExpansi
539539#endif
540540}
541541
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+ )
581650 }
582651 )
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+ )
587659
588- return ( result, rewrittenNodes, prefixCodeBlockItems)
660+ return ( closureExpr, rewrittenNodes)
661+ }
589662}
590663
591664// MARK: - Finding optional chains
@@ -668,57 +741,23 @@ private func _rewriteDollarIdentifier(_ token: TokenSyntax) -> TokenSyntax {
668741/// A syntax rewriter that replaces _numeric_ dollar identifiers (e.g. `$0`)
669742/// with normal (non-dollar) identifiers.
670743private 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 > ( )
691747
692748 override func visit( _ node: TokenSyntax ) -> TokenSyntax {
693749 if case let . dollarIdentifier( id) = node. tokenKind, id. dropFirst ( ) . allSatisfy ( \. isWholeNumber) {
694750 // This dollar identifier is numeric, so it's a closure argument.
695- dollarIdentifierTokens . insert ( node)
751+ dollarIdentifierTokenKinds . insert ( node. tokenKind )
696752 return _rewriteDollarIdentifier ( node)
697753 }
698754
699755 return node
700756 }
701- }
702757
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)
722762 }
723- return ( result, captureList)
724763}
0 commit comments