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