@@ -667,6 +667,12 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
667667 /// added to top-level 'CodeBlockItemList'.
668668 var extensions : [ CodeBlockItemSyntax ] = [ ]
669669
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+
670676 init (
671677 macroSystem: MacroSystem ,
672678 contextGenerator: @escaping ( Syntax ) -> Context ,
@@ -683,7 +689,7 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
683689 }
684690
685691 override func visitAny( _ node: Syntax ) -> Syntax ? {
686- if skipVisitAnyHandling. contains ( node) {
692+ guard ! skipVisitAnyHandling. contains ( node) else {
687693 return nil
688694 }
689695
@@ -692,8 +698,10 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
692698 // position are handled by 'visit(_:CodeBlockItemListSyntax)'.
693699 // Only expression expansions inside other syntax nodes is handled here.
694700 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+ }
697705 case . failure:
698706 return Syntax ( node)
699707 case . notAMacro:
@@ -794,9 +802,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
794802 func addResult( _ node: CodeBlockItemSyntax ) {
795803 // Expand freestanding macro.
796804 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+ }
800810 }
801811 return
802812 case . failure:
@@ -839,9 +849,11 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
839849 func addResult( _ node: MemberBlockItemSyntax ) {
840850 // Expand freestanding macro.
841851 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+ }
845857 }
846858 return
847859 case . failure:
@@ -1205,9 +1217,36 @@ extension MacroApplication {
12051217// MARK: Freestanding macro expansion
12061218
12071219extension 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+
12081247 enum MacroExpansionResult < ResultType> {
12091248 /// Expansion of the macro succeeded.
1210- case success( ResultType )
1249+ case success( expansion : MacroExpansion < ResultType > )
12111250
12121251 /// Macro system found the macro to expand but running the expansion threw
12131252 /// an error and thus no expansion result exists.
@@ -1217,18 +1256,37 @@ extension MacroApplication {
12171256 case notAMacro
12181257 }
12191258
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.
12201271 private func expandFreestandingMacro< ExpandedMacroType: SyntaxProtocol > (
12211272 _ node: ( any FreestandingMacroExpansionSyntax ) ? ,
1222- expandMacro: ( _ macro: Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
1273+ expandMacro: ( _ macro: any Macro . Type , _ node: any FreestandingMacroExpansionSyntax ) throws -> ExpandedMacroType ?
12231274 ) -> MacroExpansionResult < ExpandedMacroType > {
12241275 guard let node,
12251276 let macro = macroSystem. lookup ( node. macroName. text) ? . type
12261277 else {
12271278 return . notAMacro
12281279 }
1280+
12291281 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+
12301288 if let expanded = try expandMacro ( macro, node) {
1231- return . success( expanded)
1289+ return . success( expansion : MacroExpansion ( expandedNode : expanded, macro : macro , macroApplication : self ) )
12321290 } else {
12331291 return . failure
12341292 }
0 commit comments