@@ -14,61 +14,105 @@ import SwiftSyntaxMacros
1414
1515// MARK: - Finding effect keywords and expressions
1616
17+ /// Get the effect keyword corresponding to a given syntax node, if any.
18+ ///
19+ /// - Parameters:
20+ /// 	- expr: The syntax node that may represent an effectful expression.
21+ ///
22+ /// - Returns: The effect keyword corresponding to `expr`, if any.
23+ private  func  _effectKeyword( for expr:  ExprSyntax )  ->  Keyword ? { 
24+   switch  expr. kind { 
25+   case  . tryExpr: 
26+     return  . try 
27+   case . awaitExpr: 
28+     return  . await 
29+   case . consumeExpr: 
30+     return  . consume
31+   case  . borrowExpr: 
32+     return  . borrow
33+   case  . unsafeExpr: 
34+     return  . unsafe
35+   default : 
36+     return  nil 
37+   } 
38+ } 
39+ 
40+ /// Determine how to descend further into a syntax node tree from a given node.
41+ ///
42+ /// - Parameters:
43+ ///   - node: The syntax node currently being walked.
44+ ///
45+ /// - Returns: Whether or not to descend into `node` and visit its children.
46+ private  func  _continueKind( for node:  Syntax )  ->  SyntaxVisitorContinueKind  { 
47+   switch  node. kind { 
48+   case  . tryExpr,  . awaitExpr,  . consumeExpr,  . borrowExpr,  . unsafeExpr: 
49+     // If this node represents an effectful expression, look inside it for
50+     // additional such expressions.
51+     return  . visitChildren
52+   case  . closureExpr,  . functionDecl: 
53+     // Do not delve into closures or function declarations.
54+     return  . skipChildren
55+   case  . variableDecl: 
56+     // Delve into variable declarations.
57+     return  . visitChildren
58+   default : 
59+     // Do not delve into declarations other than variables.
60+     if  node. isProtocol ( ( any  DeclSyntaxProtocol ) . self)  { 
61+       return  . skipChildren
62+     } 
63+   } 
64+ 
65+   // Recurse into everything else.
66+   return  . visitChildren
67+ } 
68+ 
1769/// A syntax visitor class that looks for effectful keywords in a given
1870/// expression.
1971private  final  class  _EffectFinder :  SyntaxAnyVisitor  { 
2072  /// The effect keywords discovered so far.
2173  var  effectKeywords :  Set < Keyword >  =  [ ] 
2274
2375  override func  visitAny( _ node:  Syntax )  ->  SyntaxVisitorContinueKind  { 
24-     switch  node. kind { 
25-     case  . tryExpr: 
26-       effectKeywords. insert ( . try ) 
27-     case  . awaitExpr: 
28-       effectKeywords. insert ( . await ) 
29-     case  . consumeExpr: 
30-       effectKeywords. insert ( . consume) 
31-     case  . borrowExpr: 
32-       effectKeywords. insert ( . borrow) 
33-     case  . unsafeExpr: 
34-       effectKeywords. insert ( . unsafe) 
35-     case  . closureExpr,  . functionDecl: 
36-       // Do not delve into closures or function declarations.
37-       return  . skipChildren
38-     case  . variableDecl: 
39-       // Delve into variable declarations.
40-       return  . visitChildren
41-     default : 
42-       // Do not delve into declarations other than variables.
43-       if  node. isProtocol ( ( any  DeclSyntaxProtocol ) . self)  { 
44-         return  . skipChildren
45-       } 
76+     if  let  expr =  node. as ( ExprSyntax . self) ,  let  keyword =  _effectKeyword ( for:  expr)  { 
77+       effectKeywords. insert ( keyword) 
4678    } 
4779
48-     // Recurse into everything else.
49-     return  . visitChildren
80+     return  _continueKind ( for:  node) 
5081  } 
5182} 
5283
5384/// Find effectful keywords in a syntax node.
5485///
5586/// - Parameters:
5687///   - node: The node to inspect.
57- ///   - context: The macro context in which the expression is being parsed.
5888///
5989/// - Returns: A set of effectful keywords such as `await` that are present in
6090///   `node`.
6191///
6292/// This function does not descend into function declarations or closure
6393/// expressions because they represent distinct lexical contexts and their
6494/// effects are uninteresting in the context of `node` unless they are called.
65- func  findEffectKeywords( in node:  some  SyntaxProtocol ,  context:  some  MacroExpansionContext )  ->  Set < Keyword >  { 
66-   // TODO: gather any effects from the lexical context once swift-syntax-#3037 and related PRs land
95+ func  findEffectKeywords( in node:  some  SyntaxProtocol )  ->  Set < Keyword >  { 
6796  let  effectFinder  =  _EffectFinder ( viewMode:  . sourceAccurate) 
6897  effectFinder. walk ( node) 
6998  return  effectFinder. effectKeywords
7099} 
71100
101+ /// Find effectful keywords in a macro's lexical context.
102+ ///
103+ /// - Parameters:
104+ ///   - context: The macro context in which the expression is being parsed.
105+ ///
106+ /// - Returns: A set of effectful keywords such as `await` that are present in
107+ ///   `context` and would apply to an expression macro during its expansion.
108+ func  findEffectKeywords( in context:  some  MacroExpansionContext )  ->  Set < Keyword >  { 
109+   let  result  =  context. lexicalContext. reversed ( ) . lazy
110+     . prefix  {  _continueKind ( for:  $0)  ==  . visitChildren } 
111+     . compactMap  {  $0. as ( ExprSyntax . self)  } 
112+     . compactMap ( _effectKeyword ( for: ) ) 
113+   return  Set ( result) 
114+ } 
115+ 
72116extension  BidirectionalCollection < Syntax >  { 
73117  /// The suffix of syntax nodes in this collection which are effectful
74118  /// expressions, such as those for `try` or `await`.
@@ -128,10 +172,13 @@ private func _makeCallToEffectfulThunk(_ thunkName: TokenSyntax, passing expr: s
128172/// - Parameters:
129173///   - effectfulKeywords: The effectful keywords to apply.
130174///   - expr: The expression to apply the keywords and thunk functions to.
175+ ///   - insertThunkCalls: Whether or not to also insert calls to thunks to
176+ ///   	ensure the inserted keywords do not generate warnings. If you aren't
177+ ///     sure whether thunk calls are needed, pass `true`.
131178///
132179/// - Returns: A copy of `expr` if no changes are needed, or an expression that
133180///   adds the keywords in `effectfulKeywords` to `expr`.
134- func  applyEffectfulKeywords( _ effectfulKeywords:  Set < Keyword > ,  to expr:  some  ExprSyntaxProtocol )  ->  ExprSyntax  { 
181+ func  applyEffectfulKeywords( _ effectfulKeywords:  Set < Keyword > ,  to expr:  some  ExprSyntaxProtocol ,  insertThunkCalls :   Bool   =   true )  ->  ExprSyntax  { 
135182  let  originalExpr  =  expr
136183  var  expr  =  ExprSyntax ( expr. trimmed) 
137184
@@ -141,14 +188,16 @@ func applyEffectfulKeywords(_ effectfulKeywords: Set<Keyword>, to expr: some Exp
141188  let  needUnsafe  =  isUnsafeKeywordSupported && effectfulKeywords. contains ( . unsafe)  && !expr. is ( UnsafeExprSyntax . self) 
142189
143190  // First, add thunk function calls.
144-   if  needAwait { 
145-     expr =  _makeCallToEffectfulThunk ( . identifier( " __requiringAwait " ) ,  passing:  expr) 
146-   } 
147-   if  needTry { 
148-     expr =  _makeCallToEffectfulThunk ( . identifier( " __requiringTry " ) ,  passing:  expr) 
149-   } 
150-   if  needUnsafe { 
151-     expr =  _makeCallToEffectfulThunk ( . identifier( " __requiringUnsafe " ) ,  passing:  expr) 
191+   if  insertThunkCalls { 
192+     if  needAwait { 
193+       expr =  _makeCallToEffectfulThunk ( . identifier( " __requiringAwait " ) ,  passing:  expr) 
194+     } 
195+     if  needTry { 
196+       expr =  _makeCallToEffectfulThunk ( . identifier( " __requiringTry " ) ,  passing:  expr) 
197+     } 
198+     if  needUnsafe { 
199+       expr =  _makeCallToEffectfulThunk ( . identifier( " __requiringUnsafe " ) ,  passing:  expr) 
200+     } 
152201  } 
153202
154203  // Then add keyword expressions. (We do this separately so we end up writing
0 commit comments