From de9eb3b909fb1e713ac8e672658e420cf65a0e59 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Fri, 10 Jan 2025 09:24:48 +0100 Subject: [PATCH] Compute the lexical context of an accessor macro based on the pre-rewritten node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an accessor macro contains a closure, it gets rewritten to a new node in `MacroSystem` by calling `super.visit`. That rewritten node doesn’t have any parents (because `SyntaxRewriter` is expecting that node to get inserted into the tree when the node’s parent gets rewritten). This caused us to generate an empty lexical context in these situations. To fix this, generate the lexical context based on the node before we rewrite it. rdar://142639902 --- .../MacroSystem.swift | 49 +++++++++++++------ .../AccessorMacroTests.swift | 33 +++++++++++++ 2 files changed, 66 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index d858103cc0a..e4e923f794b 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -930,23 +930,30 @@ private class MacroApplication: SyntaxRewriter { } override func visit(_ node: VariableDeclSyntax) -> DeclSyntax { - var node = super.visit(node).cast(VariableDeclSyntax.self) + var rewrittenNode = super.visit(node).cast(VariableDeclSyntax.self) - guard !macroAttributes(attachedTo: DeclSyntax(node), ofType: AccessorMacro.Type.self).isEmpty else { - return DeclSyntax(node) + guard !macroAttributes(attachedTo: DeclSyntax(rewrittenNode), ofType: AccessorMacro.Type.self).isEmpty else { + return DeclSyntax(rewrittenNode) } - guard node.bindings.count == 1, - var binding = node.bindings.first + guard rewrittenNode.bindings.count == 1, + var binding = rewrittenNode.bindings.first else { contextGenerator(Syntax(node)).addDiagnostics( from: MacroApplicationError.accessorMacroOnVariableWithMultipleBindings, - node: node + node: rewrittenNode ) - return DeclSyntax(node) + return DeclSyntax(rewrittenNode) } - var expansion = expandAccessors(of: node, existingAccessors: binding.accessorBlock) + // Generate the context based on the node before it was rewritten by calling `super.visit`. If the node was modified + // by `super.visit`, it will not have any parents, which would cause the lexical context to be empty. + let context = contextGenerator(Syntax(node)) + var expansion = expandAccessors( + of: rewrittenNode, + context: context, + existingAccessors: binding.accessorBlock + ) if expansion.accessors != binding.accessorBlock { if binding.accessorBlock == nil { @@ -966,16 +973,25 @@ private class MacroApplication: SyntaxRewriter { binding.accessorBlock = expansion.accessors } - node.bindings = [binding] + rewrittenNode.bindings = [binding] } - return DeclSyntax(node) + return DeclSyntax(rewrittenNode) } override func visit(_ node: SubscriptDeclSyntax) -> DeclSyntax { - var node = super.visit(node).cast(SubscriptDeclSyntax.self) - node.accessorBlock = expandAccessors(of: node, existingAccessors: node.accessorBlock).accessors - return DeclSyntax(node) + var rewrittenNode = super.visit(node).cast(SubscriptDeclSyntax.self) + // Generate the context based on the node before it was rewritten by calling `super.visit`. If the node was modified + // by `super.visit`, it will not have any parents, which would cause the lexical context to be empty. + let context = contextGenerator(Syntax(node)) + rewrittenNode.accessorBlock = + expandAccessors( + of: rewrittenNode, + context: context, + existingAccessors: rewrittenNode.accessorBlock + ) + .accessors + return DeclSyntax(rewrittenNode) } } @@ -1160,6 +1176,7 @@ extension MacroApplication { /// removed). private func expandAccessors( of storage: some DeclSyntaxProtocol, + context: Context, existingAccessors: AccessorBlockSyntax? ) -> (accessors: AccessorBlockSyntax?, expandsGetSet: Bool) { let accessorMacros = macroAttributes(attachedTo: DeclSyntax(storage), ofType: AccessorMacro.Type.self) @@ -1184,7 +1201,7 @@ extension MacroApplication { definition: macro.definition, attributeNode: macro.attributeNode, attachedTo: DeclSyntax(storage), - in: contextGenerator(Syntax(storage)), + in: context, indentationWidth: indentationWidth ) { checkExpansions(newAccessors) @@ -1201,7 +1218,7 @@ extension MacroApplication { definition: macro.definition, attributeNode: macro.attributeNode, attachedTo: DeclSyntax(storage), - in: contextGenerator(Syntax(storage)), + in: context, indentationWidth: indentationWidth ) { guard case .accessors(let accessorList) = newAccessors.accessors else { @@ -1220,7 +1237,7 @@ extension MacroApplication { } } } catch { - contextGenerator(Syntax(storage)).addDiagnostics(from: error, node: macro.attributeNode) + context.addDiagnostics(from: error, node: macro.attributeNode) } } return (newAccessorsBlock, expandsGetSet) diff --git a/Tests/SwiftSyntaxMacroExpansionTest/AccessorMacroTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/AccessorMacroTests.swift index bd5858389c1..cf1c0b6267a 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/AccessorMacroTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/AccessorMacroTests.swift @@ -532,4 +532,37 @@ final class AccessorMacroTests: XCTestCase { indentationWidth: indentationWidth ) } + + func testClosureInAccessorMacro() { + enum PropertyWrapperMacro: AccessorMacro { + public static func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AccessorDeclSyntax] { + guard let structDecl = context.lexicalContext.first?.as(StructDeclSyntax.self) else { + return [] + } + + return ["get { \(literal: structDecl.name.text) }"] + } + } + assertMacroExpansion( + """ + struct Foo { + @TestWrapper(b: { a in 1 }) var test3: Thing + } + """, + expandedSource: """ + struct Foo { + var test3: Thing { + get { + "Foo" + } + } + } + """, + macros: ["TestWrapper": PropertyWrapperMacro.self] + ) + } }