@@ -667,6 +667,9 @@ 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
+ var expandingFreestandingMacros : [ any Macro . Type ] = [ ]
672
+
670
673
init (
671
674
macroSystem: MacroSystem ,
672
675
contextGenerator: @escaping ( Syntax ) -> Context ,
@@ -683,7 +686,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
683
686
}
684
687
685
688
override func visitAny( _ node: Syntax ) -> Syntax ? {
686
- if skipVisitAnyHandling. contains ( node) {
689
+ guard ! skipVisitAnyHandling. contains ( node) else {
687
690
return nil
688
691
}
689
692
@@ -692,8 +695,10 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
692
695
// position are handled by 'visit(_:CodeBlockItemListSyntax)'.
693
696
// Only expression expansions inside other syntax nodes is handled here.
694
697
switch expandExpr ( node: node) {
695
- case . success( let expanded) :
696
- return Syntax ( visit ( expanded) )
698
+ case . success( let expansion) :
699
+ return expansion. consume { expanded in
700
+ Syntax ( visit ( expanded) )
701
+ }
697
702
case . failure:
698
703
return Syntax ( node)
699
704
case . notAMacro:
@@ -794,9 +799,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
794
799
func addResult( _ node: CodeBlockItemSyntax ) {
795
800
// Expand freestanding macro.
796
801
switch expandCodeBlockItem ( node: node) {
797
- case . success( let expanded) :
798
- for item in expanded {
799
- addResult ( item)
802
+ case . success( let expansion) :
803
+ expansion. consume { expanded in
804
+ for item in expanded {
805
+ addResult ( item)
806
+ }
800
807
}
801
808
return
802
809
case . failure:
@@ -839,9 +846,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
839
846
func addResult( _ node: MemberBlockItemSyntax ) {
840
847
// Expand freestanding macro.
841
848
switch expandMemberDecl ( node: node) {
842
- case . success( let expanded) :
843
- for item in expanded {
844
- addResult ( item)
849
+ case . success( let expansion) :
850
+ expansion. consume { expanded in
851
+ for item in expanded {
852
+ addResult ( item)
853
+ }
845
854
}
846
855
return
847
856
case . failure:
@@ -1205,9 +1214,39 @@ extension MacroApplication {
1205
1214
// MARK: Freestanding macro expansion
1206
1215
1207
1216
extension MacroApplication {
1217
+ class MacroExpansion < ResultType> {
1218
+ private let expandedNode : ResultType
1219
+ private let macro : any Macro . Type
1220
+ private unowned let macroApplication : MacroApplication
1221
+ private var isConsumed = false
1222
+
1223
+ fileprivate init ( expandedNode: ResultType , macro: any Macro . Type , macroApplication: MacroApplication ) {
1224
+ self . expandedNode = expandedNode
1225
+ self . macro = macro
1226
+ self . macroApplication = macroApplication
1227
+ }
1228
+
1229
+ /// Invokes the given closure with the node resulting from a macro expansion.
1230
+ ///
1231
+ /// - Precondition: This method has never been called for this instance.
1232
+ ///
1233
+ /// This method inserts a pair of push and pop operations immediately around the closure invocation to maintain an
1234
+ /// exact stack of expanding freestanding macros to detect circluar macro expansion. Callers should only call this
1235
+ /// method once and perform all further expansions on `expanded` only within the scope of `body`.
1236
+ func consume< T> ( _ body: ( _ expanded: ResultType ) throws -> T ) rethrows -> T {
1237
+ precondition ( !isConsumed)
1238
+ isConsumed = true
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.
@@ -1219,16 +1258,21 @@ extension MacroApplication {
1219
1258
1220
1259
private func expandFreestandingMacro< ExpandedMacroType: SyntaxProtocol > (
1221
1260
_ node: ( any FreestandingMacroExpansionSyntax ) ? ,
1222
- expandMacro: ( _ macro: Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
1261
+ expandMacro: ( _ macro: any Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
1223
1262
) -> MacroExpansionResult < ExpandedMacroType > {
1224
1263
guard let node,
1225
1264
let macro = macroSystem. lookup ( node. macroName. text) ? . type
1226
1265
else {
1227
1266
return . notAMacro
1228
1267
}
1268
+
1229
1269
do {
1270
+ guard expandingFreestandingMacros. allSatisfy ( { $0 != macro } ) else {
1271
+ throw MacroExpansionError . circularExpansion ( macro, node)
1272
+ }
1273
+
1230
1274
if let expanded = try expandMacro ( macro, node) {
1231
- return . success( expanded)
1275
+ return . success( expansion : MacroExpansion ( expandedNode : expanded, macro : macro , macroApplication : self ) )
1232
1276
} else {
1233
1277
return . failure
1234
1278
}
0 commit comments