From a3357a206120f400f3d393a821fb2ed618cc4e29 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Sun, 24 May 2026 18:31:44 -0700 Subject: [PATCH] Allow peer macros on variables with multiple bindings in assertMacroExpansion assertMacroExpansion rejected peer macros attached to a variable declaration with more than one binding (such as `var a: Int, b: Int`) with the error "peer macro can only be applied to a single variable", before the macro was ever invoked. This restriction was added back when the compiler effectively disallowed this by invoking the macro once per binding with the same syntax node, which produced duplicate peers and a redeclaration error. That is no longer the case: the compiler invokes a peer macro on such declarations and only diagnoses a problem if the macro itself emits conflicting declarations. A peer macro that returns no peers, or peers with unique names, compiles cleanly. This was verified by building a peer macro against the Swift 6.3 toolchain. As a result, assertMacroExpansion produced a false-positive diagnostic for code that the compiler accepts, making it an unreliable test oracle for peer macros on multi-binding variables. Remove the binding-count guard from both expandPeerMacroMember and expandPeerMacroCodeItem so the peer macro runs and its peers are emitted, matching the compiler. The corresponding error case is dropped since it is no longer used. The accessor-macro restriction is left in place because the compiler genuinely does not allow accessor macros on multi-binding variables. --- .../MacroSystem.swift | 11 ------ .../PeerMacroTests.swift | 37 ++++++++++++++++--- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift index d3b356106c5..f742787c49a 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift @@ -246,10 +246,6 @@ private func expandPeerMacroMember( in context: some MacroExpansionContext, indentationWidth: Trivia ) throws -> MemberBlockItemListSyntax? { - if let variable = attachedTo.as(VariableDeclSyntax.self), variable.bindings.count > 1 { - throw MacroApplicationError.peerMacroOnVariableWithMultipleBindings - } - guard let expanded = expandAttachedMacro( definition: definition, @@ -278,10 +274,6 @@ private func expandPeerMacroCodeItem( in context: some MacroExpansionContext, indentationWidth: Trivia ) throws -> CodeBlockItemListSyntax? { - if let variable = attachedTo.as(VariableDeclSyntax.self), variable.bindings.count > 1 { - throw MacroApplicationError.peerMacroOnVariableWithMultipleBindings - } - guard let expanded = expandAttachedMacro( definition: definition, @@ -622,7 +614,6 @@ let diagnosticDomain: String = "SwiftSyntaxMacroExpansion" private enum MacroApplicationError: DiagnosticMessage, Error { case accessorMacroOnVariableWithMultipleBindings - case peerMacroOnVariableWithMultipleBindings case malformedAccessor var diagnosticID: MessageID { @@ -635,8 +626,6 @@ private enum MacroApplicationError: DiagnosticMessage, Error { switch self { case .accessorMacroOnVariableWithMultipleBindings: return "accessor macro can only be applied to a single variable" - case .peerMacroOnVariableWithMultipleBindings: - return "peer macro can only be applied to a single variable" case .malformedAccessor: return """ macro returned a malformed accessor. Accessors should start with an introducer like 'get' or 'set'. diff --git a/Tests/SwiftSyntaxMacroExpansionTest/PeerMacroTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/PeerMacroTests.swift index 0a9eaebcf6c..01a62ef7bc2 100644 --- a/Tests/SwiftSyntaxMacroExpansionTest/PeerMacroTests.swift +++ b/Tests/SwiftSyntaxMacroExpansionTest/PeerMacroTests.swift @@ -171,10 +171,9 @@ final class PeerMacroTests: XCTestCase { """, expandedSource: """ let a = 17, b = 12 + + var baz: Int = 0 """, - diagnostics: [ - DiagnosticSpec(message: "peer macro can only be applied to a single variable", line: 1, column: 1) - ], macros: ["Test": TestMacro.self] ) @@ -188,11 +187,37 @@ final class PeerMacroTests: XCTestCase { expandedSource: """ struct Foo { let a = 17, b = 12 + + var baz: Int = 0 + } + """, + macros: ["Test": TestMacro.self] + ) + } + + func testPeerMacroReturningNoPeersOnVariableWithMultipleBindings() { + struct TestMacro: PeerMacro { + static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + return [] + } + } + + assertMacroExpansion( + """ + struct Foo { + @Test + var a: Int, b: Int + } + """, + expandedSource: """ + struct Foo { + var a: Int, b: Int } """, - diagnostics: [ - DiagnosticSpec(message: "peer macro can only be applied to a single variable", line: 2, column: 3) - ], macros: ["Test": TestMacro.self] ) }