Skip to content

Commit 3ffa580

Browse files
committed
Disallow attaching peer macro to variable with multiple bindings in assertMacroExpansion
The compiler currently invokes `expansion` twice with the exact same syntax node (rdar://114996981), which causes the same member to be generated twice, effectively disallowing the application of peer macro on variables with multiple bindings. Disallow them in `assertMacroExpansion` with a nice error message.
1 parent 9e798fa commit 3ffa580

File tree

3 files changed

+60
-10
lines changed

3 files changed

+60
-10
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ private func expandPeerMacroMember(
187187
in context: some MacroExpansionContext,
188188
indentationWidth: Trivia
189189
) throws -> MemberBlockItemListSyntax? {
190+
if let variable = attachedTo.as(VariableDeclSyntax.self), variable.bindings.count > 1 {
191+
throw MacroApplicationError.peerMacroOnVariableWithMultipleBindings
192+
}
193+
190194
guard
191195
let expanded = expandAttachedMacro(
192196
definition: definition,
@@ -215,6 +219,10 @@ private func expandPeerMacroCodeItem(
215219
in context: some MacroExpansionContext,
216220
indentationWidth: Trivia
217221
) throws -> CodeBlockItemListSyntax? {
222+
if let variable = attachedTo.as(VariableDeclSyntax.self), variable.bindings.count > 1 {
223+
throw MacroApplicationError.peerMacroOnVariableWithMultipleBindings
224+
}
225+
218226
guard
219227
let expanded = expandAttachedMacro(
220228
definition: definition,
@@ -424,6 +432,7 @@ let diagnosticDomain: String = "SwiftSyntaxMacroExpansion"
424432

425433
private enum MacroApplicationError: DiagnosticMessage, Error {
426434
case accessorMacroOnVariableWithMultipleBindings
435+
case peerMacroOnVariableWithMultipleBindings
427436
case malformedAccessor
428437

429438
var diagnosticID: MessageID {
@@ -435,15 +444,12 @@ private enum MacroApplicationError: DiagnosticMessage, Error {
435444
var message: String {
436445
switch self {
437446
case .accessorMacroOnVariableWithMultipleBindings:
438-
return """
439-
swift-syntax applies macros syntactically and there is no way to represent a variable \
440-
declaration with multiple bindings that have accessors syntactically. \
441-
While the compiler allows this expansion, swift-syntax cannot represent it and thus \
442-
disallows it.
443-
"""
447+
return "accessor macro can only be applied to a single variable"
448+
case .peerMacroOnVariableWithMultipleBindings:
449+
return "peer macro can only be applied to a single variable"
444450
case .malformedAccessor:
445451
return """
446-
Macro returned a malformed accessor. Accessors should start with an introducer like 'get' or 'set'.
452+
macro returned a malformed accessor. Accessors should start with an introducer like 'get' or 'set'.
447453
"""
448454
}
449455
}
@@ -606,11 +612,12 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
606612
}
607613

608614
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
615+
var node = super.visit(node).cast(VariableDeclSyntax.self)
616+
609617
guard !macroAttributes(attachedTo: DeclSyntax(node), ofType: AccessorMacro.Type.self).isEmpty else {
610-
return super.visit(node).cast(DeclSyntax.self)
618+
return DeclSyntax(node)
611619
}
612620

613-
var node = super.visit(node).cast(VariableDeclSyntax.self)
614621
guard node.bindings.count == 1, let binding = node.bindings.first else {
615622
context.addDiagnostics(from: MacroApplicationError.accessorMacroOnVariableWithMultipleBindings, node: node)
616623
return DeclSyntax(node)

Tests/SwiftSyntaxMacroExpansionTest/AccessorMacroTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ final class AccessorMacroTests: XCTestCase {
229229
diagnostics: [
230230
DiagnosticSpec(
231231
message:
232-
"swift-syntax applies macros syntactically and there is no way to represent a variable declaration with multiple bindings that have accessors syntactically. While the compiler allows this expansion, swift-syntax cannot represent it and thus disallows it.",
232+
"accessor macro can only be applied to a single variable",
233233
line: 1,
234234
column: 1,
235235
severity: .error

Tests/SwiftSyntaxMacroExpansionTest/PeerMacroTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,47 @@ final class PeerMacroTests: XCTestCase {
130130
)
131131
}
132132

133+
func testPeerMacroOnVariableWithMultipleBindings() {
134+
struct TestMacro: PeerMacro {
135+
static func expansion(
136+
of node: AttributeSyntax,
137+
providingPeersOf declaration: some DeclSyntaxProtocol,
138+
in context: some MacroExpansionContext
139+
) throws -> [DeclSyntax] {
140+
return ["var baz: Int = 0"]
141+
}
142+
}
143+
144+
assertMacroExpansion(
145+
"""
146+
@Test
147+
let a = 17, b = 12
148+
""",
149+
expandedSource: """
150+
let a = 17, b = 12
151+
""",
152+
diagnostics: [
153+
DiagnosticSpec(message: "peer macro can only be applied to a single variable", line: 1, column: 1)
154+
],
155+
macros: ["Test": TestMacro.self]
156+
)
157+
158+
assertMacroExpansion(
159+
"""
160+
struct Foo {
161+
@Test
162+
let a = 17, b = 12
163+
}
164+
""",
165+
expandedSource: """
166+
struct Foo {
167+
let a = 17, b = 12
168+
}
169+
""",
170+
diagnostics: [
171+
DiagnosticSpec(message: "peer macro can only be applied to a single variable", line: 2, column: 3)
172+
],
173+
macros: ["Test": TestMacro.self]
174+
)
175+
}
133176
}

0 commit comments

Comments
 (0)