@@ -668,6 +668,12 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
668
668
/// added to top-level 'CodeBlockItemList'.
669
669
var extensions : [ CodeBlockItemSyntax ] = [ ]
670
670
671
+ /// Stores the types of the freestanding macros that are currently expanding.
672
+ ///
673
+ /// As macros are expanded by DFS, `expandingFreestandingMacros` always represent the expansion path starting from
674
+ /// the root macro node to the last macro node currently expanding.
675
+ var expandingFreestandingMacros : [ any Macro . Type ] = [ ]
676
+
671
677
init (
672
678
macroSystem: MacroSystem ,
673
679
contextGenerator: @escaping ( Syntax ) -> Context ,
@@ -684,7 +690,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
684
690
}
685
691
686
692
override func visitAny( _ node: Syntax ) -> Syntax ? {
687
- if skipVisitAnyHandling. contains ( node) {
693
+ guard ! skipVisitAnyHandling. contains ( node) else {
688
694
return nil
689
695
}
690
696
@@ -693,8 +699,10 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
693
699
// position are handled by 'visit(_:CodeBlockItemListSyntax)'.
694
700
// Only expression expansions inside other syntax nodes is handled here.
695
701
switch expandExpr ( node: node) {
696
- case . success( let expanded) :
697
- return Syntax ( visit ( expanded) )
702
+ case . success( let expansion) :
703
+ return expansion. withExpandedNode { expandedNode in
704
+ Syntax ( visit ( expandedNode) )
705
+ }
698
706
case . failure:
699
707
return Syntax ( node)
700
708
case . notAMacro:
@@ -795,9 +803,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
795
803
func addResult( _ node: CodeBlockItemSyntax ) {
796
804
// Expand freestanding macro.
797
805
switch expandCodeBlockItem ( node: node) {
798
- case . success( let expanded) :
799
- for item in expanded {
800
- addResult ( item)
806
+ case . success( let expansion) :
807
+ expansion. withExpandedNode { expandedNode in
808
+ for item in expandedNode {
809
+ addResult ( item)
810
+ }
801
811
}
802
812
return
803
813
case . failure:
@@ -840,9 +850,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
840
850
func addResult( _ node: MemberBlockItemSyntax ) {
841
851
// Expand freestanding macro.
842
852
switch expandMemberDecl ( node: node) {
843
- case . success( let expanded) :
844
- for item in expanded {
845
- addResult ( item)
853
+ case . success( let expansion) :
854
+ expansion. withExpandedNode { expandedNode in
855
+ for item in expandedNode {
856
+ addResult ( item)
857
+ }
846
858
}
847
859
return
848
860
case . failure:
@@ -1218,9 +1230,36 @@ extension MacroApplication {
1218
1230
// MARK: Freestanding macro expansion
1219
1231
1220
1232
extension MacroApplication {
1233
+ /// Encapsulates an expanded node, the type of the macro from which the node was expanded, and the macro application,
1234
+ /// such that recursive macro expansion can be consistently detected.
1235
+ struct MacroExpansion < ResultType> {
1236
+ private let expandedNode : ResultType
1237
+ private let macro : any Macro . Type
1238
+ private unowned let macroApplication : MacroApplication
1239
+
1240
+ fileprivate init ( expandedNode: ResultType , macro: any Macro . Type , macroApplication: MacroApplication ) {
1241
+ self . expandedNode = expandedNode
1242
+ self . macro = macro
1243
+ self . macroApplication = macroApplication
1244
+ }
1245
+
1246
+ /// Invokes the given closure with the node resulting from a macro expansion.
1247
+ ///
1248
+ /// This method inserts a pair of push and pop operations immediately around the invocation of `body` to maintain
1249
+ /// an exact stack of expanding freestanding macros to detect recursive macro expansion. Callers should perform any
1250
+ /// further macro expansion on `expanded` only within the scope of `body`.
1251
+ func withExpandedNode< T> ( _ body: ( _ expandedNode: ResultType ) throws -> T ) rethrows -> T {
1252
+ macroApplication. expandingFreestandingMacros. append ( macro)
1253
+ defer {
1254
+ macroApplication. expandingFreestandingMacros. removeLast ( )
1255
+ }
1256
+ return try body ( expandedNode)
1257
+ }
1258
+ }
1259
+
1221
1260
enum MacroExpansionResult < ResultType> {
1222
1261
/// Expansion of the macro succeeded.
1223
- case success( ResultType )
1262
+ case success( expansion : MacroExpansion < ResultType > )
1224
1263
1225
1264
/// Macro system found the macro to expand but running the expansion threw
1226
1265
/// an error and thus no expansion result exists.
@@ -1230,18 +1269,37 @@ extension MacroApplication {
1230
1269
case notAMacro
1231
1270
}
1232
1271
1272
+ /// Expands the given freestanding macro node into a syntax node by invoking the given closure.
1273
+ ///
1274
+ /// Any error thrown by `expandMacro` and circular expansion error will be added to diagnostics.
1275
+ ///
1276
+ /// - Parameters:
1277
+ /// - node: The freestanding macro node to be expanded.
1278
+ /// - expandMacro: The closure that expands the given macro type and macro node into a syntax node.
1279
+ ///
1280
+ /// - Returns:
1281
+ /// Returns `.notAMacro` if `node` is `nil` or `node.macroName` isn't registered with any macro type.
1282
+ /// Returns `.failure` if `expandMacro` throws an error or returns `nil`, or recursive expansion is detected.
1283
+ /// Returns `.success` otherwise.
1233
1284
private func expandFreestandingMacro< ExpandedMacroType: SyntaxProtocol > (
1234
1285
_ node: ( any FreestandingMacroExpansionSyntax ) ? ,
1235
- expandMacro: ( _ macro: Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
1286
+ expandMacro: ( _ macro: any Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
1236
1287
) -> MacroExpansionResult < ExpandedMacroType > {
1237
1288
guard let node,
1238
1289
let macro = macroSystem. lookup ( node. macroName. text) ? . type
1239
1290
else {
1240
1291
return . notAMacro
1241
1292
}
1293
+
1242
1294
do {
1295
+ guard !expandingFreestandingMacros. contains ( where: { $0 == macro } ) else {
1296
+ // We may think of any ongoing macro expansion as a tree in which macro types being expanded are nodes.
1297
+ // Any macro type being expanded more than once will create a cycle which the compiler as of now doesn't allow.
1298
+ throw MacroExpansionError . recursiveExpansion ( macro)
1299
+ }
1300
+
1243
1301
if let expanded = try expandMacro ( macro, node) {
1244
- return . success( expanded)
1302
+ return . success( expansion : MacroExpansion ( expandedNode : expanded, macro : macro , macroApplication : self ) )
1245
1303
} else {
1246
1304
return . failure
1247
1305
}
0 commit comments