@@ -668,6 +668,12 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
668668 /// added to top-level 'CodeBlockItemList'.
669669 var extensions : [ CodeBlockItemSyntax ] = [ ]
670670
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+
671677 init (
672678 macroSystem: MacroSystem ,
673679 contextGenerator: @escaping ( Syntax ) -> Context ,
@@ -684,7 +690,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
684690 }
685691
686692 override func visitAny( _ node: Syntax ) -> Syntax ? {
687- if skipVisitAnyHandling. contains ( node) {
693+ guard ! skipVisitAnyHandling. contains ( node) else {
688694 return nil
689695 }
690696
@@ -693,8 +699,10 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
693699 // position are handled by 'visit(_:CodeBlockItemListSyntax)'.
694700 // Only expression expansions inside other syntax nodes is handled here.
695701 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+ }
698706 case . failure:
699707 return Syntax ( node)
700708 case . notAMacro:
@@ -795,9 +803,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
795803 func addResult( _ node: CodeBlockItemSyntax ) {
796804 // Expand freestanding macro.
797805 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+ }
801811 }
802812 return
803813 case . failure:
@@ -840,9 +850,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
840850 func addResult( _ node: MemberBlockItemSyntax ) {
841851 // Expand freestanding macro.
842852 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+ }
846858 }
847859 return
848860 case . failure:
@@ -1218,9 +1230,36 @@ extension MacroApplication {
12181230// MARK: Freestanding macro expansion
12191231
12201232extension 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+
12211260 enum MacroExpansionResult < ResultType> {
12221261 /// Expansion of the macro succeeded.
1223- case success( ResultType )
1262+ case success( expansion : MacroExpansion < ResultType > )
12241263
12251264 /// Macro system found the macro to expand but running the expansion threw
12261265 /// an error and thus no expansion result exists.
@@ -1230,18 +1269,37 @@ extension MacroApplication {
12301269 case notAMacro
12311270 }
12321271
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.
12331284 private func expandFreestandingMacro< ExpandedMacroType: SyntaxProtocol > (
12341285 _ node: ( any FreestandingMacroExpansionSyntax ) ? ,
1235- expandMacro: ( _ macro: Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
1286+ expandMacro: ( _ macro: any Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
12361287 ) -> MacroExpansionResult < ExpandedMacroType > {
12371288 guard let node,
12381289 let macro = macroSystem. lookup ( node. macroName. text) ? . type
12391290 else {
12401291 return . notAMacro
12411292 }
1293+
12421294 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+
12431301 if let expanded = try expandMacro ( macro, node) {
1244- return . success( expanded)
1302+ return . success( expansion : MacroExpansion ( expandedNode : expanded, macro : macro , macroApplication : self ) )
12451303 } else {
12461304 return . failure
12471305 }
0 commit comments