Skip to content

Commit c32dca9

Browse files
committed
Fix issue that caused errors thrown from macro expansion to show up twice in MacroSystem
If the macro expansion of a freestanding expression macro throws an error, `expandCodeBlockItem` returned `nil` while adding the thrown error to the macro expansion context. `visit(_:CodeBlockItemListSyntax).addResult` took the `nil` return value as an indicator that the macro wasn’t expanded because its macro definition wasn’t found and ended up calling the expansion again in ```swift // Recurse on the child node newItems.append(visit(node)) ``` Change the return value of `expandCodeBlockItem` to an enum that indicates whether the macro was not found or if the expansion failed. If the macro was found but the expansion threw an error, we just just retain the macro as-is without calling into `visit` again. Fixes #2111 rdar://114592410
1 parent fe6ce4e commit c32dca9

File tree

4 files changed

+166
-17
lines changed

4 files changed

+166
-17
lines changed

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,14 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
485485
// Note that 'MacroExpansionExpr'/'MacroExpansionExprDecl' at code item
486486
// position are handled by 'visit(_:CodeBlockItemListSyntax)'.
487487
// Only expression expansions inside other syntax nodes is handled here.
488-
if let expanded = expandExpr(node: node) {
488+
switch expandExpr(node: node) {
489+
case .success(let expanded):
489490
return Syntax(visit(expanded))
491+
case .failure:
492+
return Syntax(node)
493+
case .notAMacro:
494+
break
490495
}
491-
492496
if let declSyntax = node.as(DeclSyntax.self),
493497
let attributedNode = node.asProtocol(WithAttributesSyntax.self),
494498
!attributedNode.attributes.isEmpty
@@ -510,16 +514,21 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
510514
var newItems: [CodeBlockItemSyntax] = []
511515
func addResult(_ node: CodeBlockItemSyntax) {
512516
// Expand freestanding macro.
513-
if let expanded = expandCodeBlockItem(node: node) {
517+
switch expandCodeBlockItem(node: node) {
518+
case .success(let expanded):
514519
for item in expanded {
515520
addResult(item)
516521
}
517522
return
523+
case .failure:
524+
// Expanding the macro threw an error. We don't have an expanded source.
525+
// Retain the macro node as-is.
526+
newItems.append(node)
527+
case .notAMacro:
528+
// Recurse on the child node
529+
newItems.append(visit(node))
518530
}
519531

520-
// Recurse on the child node
521-
newItems.append(visit(node))
522-
523532
// Expand any peer macro on this item.
524533
if case .decl(let decl) = node.item {
525534
for peer in expandCodeBlockPeers(of: decl) {
@@ -552,16 +561,19 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
552561

553562
func addResult(_ node: MemberBlockItemSyntax) {
554563
// Expand freestanding macro.
555-
if let expanded = expandMemberDecl(node: node) {
564+
switch expandMemberDecl(node: node) {
565+
case .success(let expanded):
556566
for item in expanded {
557567
addResult(item)
558568
}
559569
return
570+
case .failure:
571+
newItems.append(node)
572+
case .notAMacro:
573+
// Recurse on the child node.
574+
newItems.append(visit(node))
560575
}
561576

562-
// Recurse on the child node.
563-
newItems.append(visit(node))
564-
565577
// Expand any peer macro on this member.
566578
for peer in expandMemberDeclPeers(of: node.decl) {
567579
addResult(peer)
@@ -842,20 +854,36 @@ extension MacroApplication {
842854
// MARK: Freestanding macro expansion
843855

844856
extension MacroApplication {
857+
enum MacroExpansionResult<ResultType> {
858+
/// Expansion of the macro succeeded.
859+
case success(ResultType)
860+
861+
/// Macro system found the macro to expand but running the expansion threw
862+
/// an error and thus no expansion result exists.
863+
case failure
864+
865+
/// The node that should be expanded was not a macro known to the macro system.
866+
case notAMacro
867+
}
868+
845869
private func expandFreestandingMacro<ExpandedMacroType: SyntaxProtocol>(
846870
_ node: (any FreestandingMacroExpansionSyntax)?,
847871
expandMacro: (_ macro: Macro.Type, _ node: any FreestandingMacroExpansionSyntax) throws -> ExpandedMacroType?
848-
) -> ExpandedMacroType? {
872+
) -> MacroExpansionResult<ExpandedMacroType> {
849873
guard let node,
850874
let macro = macroSystem.lookup(node.macro.text)
851875
else {
852-
return nil
876+
return .notAMacro
853877
}
854878
do {
855-
return try expandMacro(macro, node)
879+
if let expanded = try expandMacro(macro, node) {
880+
return .success(expanded)
881+
} else {
882+
return .failure
883+
}
856884
} catch {
857885
context.addDiagnostics(from: error, node: node)
858-
return nil
886+
return .failure
859887
}
860888
}
861889

@@ -867,7 +895,7 @@ extension MacroApplication {
867895
/// #foo
868896
/// }
869897
/// ```
870-
func expandCodeBlockItem(node: CodeBlockItemSyntax) -> CodeBlockItemListSyntax? {
898+
func expandCodeBlockItem(node: CodeBlockItemSyntax) -> MacroExpansionResult<CodeBlockItemListSyntax> {
871899
return expandFreestandingMacro(node.item.asProtocol(FreestandingMacroExpansionSyntax.self)) { macro, node in
872900
return try expandFreestandingCodeItemList(
873901
definition: macro,
@@ -886,7 +914,7 @@ extension MacroApplication {
886914
/// #foo
887915
/// }
888916
/// ```
889-
func expandMemberDecl(node: MemberBlockItemSyntax) -> MemberBlockItemListSyntax? {
917+
func expandMemberDecl(node: MemberBlockItemSyntax) -> MacroExpansionResult<MemberBlockItemListSyntax> {
890918
return expandFreestandingMacro(node.decl.as(MacroExpansionDeclSyntax.self)) { macro, node in
891919
return try expandFreestandingMemberDeclList(
892920
definition: macro,
@@ -904,7 +932,7 @@ extension MacroApplication {
904932
/// ```swift
905933
/// let a = #foo
906934
/// ```
907-
func expandExpr(node: Syntax) -> ExprSyntax? {
935+
func expandExpr(node: Syntax) -> MacroExpansionResult<ExprSyntax> {
908936
return expandFreestandingMacro(node.as(MacroExpansionExprSyntax.self)) { macro, node in
909937
return try expandFreestandingExpr(
910938
definition: macro,

Tests/SwiftSyntaxMacroExpansionTest/DeclarationMacroTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,46 @@ final class DeclarationMacroTests: XCTestCase {
347347
indentationWidth: indentationWidth
348348
)
349349
}
350+
351+
func testThrowErrorFromDeclMacro() {
352+
struct MyError: Error, CustomStringConvertible {
353+
let description: String = "my error"
354+
}
355+
356+
struct TestMacro: DeclarationMacro {
357+
static func expansion(
358+
of node: some FreestandingMacroExpansionSyntax,
359+
in context: some MacroExpansionContext
360+
) throws -> [DeclSyntax] {
361+
throw MyError()
362+
}
363+
}
364+
365+
assertMacroExpansion(
366+
"#test",
367+
expandedSource: "#test",
368+
diagnostics: [
369+
DiagnosticSpec(message: "my error", line: 1, column: 1)
370+
],
371+
macros: ["test": TestMacro.self]
372+
)
373+
374+
assertMacroExpansion(
375+
"""
376+
struct Foo {
377+
#test
378+
}
379+
""",
380+
expandedSource: """
381+
struct Foo {
382+
#test
383+
}
384+
""",
385+
diagnostics: [
386+
DiagnosticSpec(message: "my error", line: 2, column: 3)
387+
],
388+
macros: ["test": TestMacro.self]
389+
)
390+
}
391+
350392
}

Tests/SwiftSyntaxMacroExpansionTest/ExpressionMacroTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,38 @@ final class ExpressionMacroTests: XCTestCase {
200200
indentationWidth: indentationWidth
201201
)
202202
}
203+
204+
func testThrowErrorFromExpressionMacro() {
205+
struct MyError: Error, CustomStringConvertible {
206+
let description: String = "my error"
207+
}
208+
209+
struct TestMacro: ExpressionMacro {
210+
public static func expansion(
211+
of node: some FreestandingMacroExpansionSyntax,
212+
in context: some MacroExpansionContext
213+
) throws -> ExprSyntax {
214+
throw MyError()
215+
}
216+
}
217+
218+
assertMacroExpansion(
219+
"#test",
220+
expandedSource: "#test",
221+
diagnostics: [
222+
DiagnosticSpec(message: "my error", line: 1, column: 1)
223+
],
224+
macros: ["test": TestMacro.self]
225+
)
226+
227+
assertMacroExpansion(
228+
"1 + #test",
229+
expandedSource: "1 + #test",
230+
diagnostics: [
231+
DiagnosticSpec(message: "my error", line: 1, column: 5)
232+
],
233+
macros: ["test": TestMacro.self]
234+
)
235+
}
236+
203237
}

Tests/SwiftSyntaxMacroExpansionTest/MultiRoleMacroTests.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,51 @@ final class MultiRoleMacroTests: XCTestCase {
204204
macros: ["customTypeWrapper": CustomTypeWrapperMacro.self],
205205
indentationWidth: indentationWidth
206206
)
207+
}
208+
209+
func testAttachedMacroOnFreestandingMacro() {
210+
struct DeclMacro: DeclarationMacro {
211+
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
212+
return ["var x: Int"]
213+
}
214+
}
215+
216+
struct MyPeerMacro: PeerMacro {
217+
static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws
218+
-> [DeclSyntax]
219+
{
220+
return ["var peer: Int"]
221+
}
222+
}
223+
224+
assertMacroExpansion(
225+
"""
226+
struct Foo {
227+
@Peer
228+
#decl
229+
}
230+
""",
231+
expandedSource: """
232+
struct Foo {
233+
var x: Int
207234
235+
var peer: Int
236+
}
237+
""",
238+
macros: ["decl": DeclMacro.self, "Peer": MyPeerMacro.self]
239+
)
240+
241+
assertMacroExpansion(
242+
"""
243+
@Peer
244+
#decl
245+
""",
246+
expandedSource: """
247+
var x: Int
248+
249+
var peer: Int
250+
""",
251+
macros: ["decl": DeclMacro.self, "Peer": MyPeerMacro.self]
252+
)
208253
}
209254
}

0 commit comments

Comments
 (0)