Skip to content

[509] Fix issue that caused errors thrown from macro expansion to show up twice in MacroSystem #2135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -485,10 +485,14 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
// Note that 'MacroExpansionExpr'/'MacroExpansionExprDecl' at code item
// position are handled by 'visit(_:CodeBlockItemListSyntax)'.
// Only expression expansions inside other syntax nodes is handled here.
if let expanded = expandExpr(node: node) {
switch expandExpr(node: node) {
case .success(let expanded):
return Syntax(visit(expanded))
case .failure:
return Syntax(node)
case .notAMacro:
break
}

if let declSyntax = node.as(DeclSyntax.self),
let attributedNode = node.asProtocol(WithAttributesSyntax.self),
!attributedNode.attributes.isEmpty
Expand All @@ -510,16 +514,21 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
var newItems: [CodeBlockItemSyntax] = []
func addResult(_ node: CodeBlockItemSyntax) {
// Expand freestanding macro.
if let expanded = expandCodeBlockItem(node: node) {
switch expandCodeBlockItem(node: node) {
case .success(let expanded):
for item in expanded {
addResult(item)
}
return
case .failure:
// Expanding the macro threw an error. We don't have an expanded source.
// Retain the macro node as-is.
newItems.append(node)
case .notAMacro:
// Recurse on the child node
newItems.append(visit(node))
}

// Recurse on the child node
newItems.append(visit(node))

// Expand any peer macro on this item.
if case .decl(let decl) = node.item {
for peer in expandCodeBlockPeers(of: decl) {
Expand Down Expand Up @@ -552,16 +561,19 @@ private class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {

func addResult(_ node: MemberBlockItemSyntax) {
// Expand freestanding macro.
if let expanded = expandMemberDecl(node: node) {
switch expandMemberDecl(node: node) {
case .success(let expanded):
for item in expanded {
addResult(item)
}
return
case .failure:
newItems.append(node)
case .notAMacro:
// Recurse on the child node.
newItems.append(visit(node))
}

// Recurse on the child node.
newItems.append(visit(node))

// Expand any peer macro on this member.
for peer in expandMemberDeclPeers(of: node.decl) {
addResult(peer)
Expand Down Expand Up @@ -842,20 +854,36 @@ extension MacroApplication {
// MARK: Freestanding macro expansion

extension MacroApplication {
enum MacroExpansionResult<ResultType> {
/// Expansion of the macro succeeded.
case success(ResultType)

/// Macro system found the macro to expand but running the expansion threw
/// an error and thus no expansion result exists.
case failure

/// The node that should be expanded was not a macro known to the macro system.
case notAMacro
}

private func expandFreestandingMacro<ExpandedMacroType: SyntaxProtocol>(
_ node: (any FreestandingMacroExpansionSyntax)?,
expandMacro: (_ macro: Macro.Type, _ node: any FreestandingMacroExpansionSyntax) throws -> ExpandedMacroType?
) -> ExpandedMacroType? {
) -> MacroExpansionResult<ExpandedMacroType> {
guard let node,
let macro = macroSystem.lookup(node.macro.text)
else {
return nil
return .notAMacro
}
do {
return try expandMacro(macro, node)
if let expanded = try expandMacro(macro, node) {
return .success(expanded)
} else {
return .failure
}
} catch {
context.addDiagnostics(from: error, node: node)
return nil
return .failure
}
}

Expand All @@ -867,7 +895,7 @@ extension MacroApplication {
/// #foo
/// }
/// ```
func expandCodeBlockItem(node: CodeBlockItemSyntax) -> CodeBlockItemListSyntax? {
func expandCodeBlockItem(node: CodeBlockItemSyntax) -> MacroExpansionResult<CodeBlockItemListSyntax> {
return expandFreestandingMacro(node.item.asProtocol(FreestandingMacroExpansionSyntax.self)) { macro, node in
return try expandFreestandingCodeItemList(
definition: macro,
Expand All @@ -886,7 +914,7 @@ extension MacroApplication {
/// #foo
/// }
/// ```
func expandMemberDecl(node: MemberBlockItemSyntax) -> MemberBlockItemListSyntax? {
func expandMemberDecl(node: MemberBlockItemSyntax) -> MacroExpansionResult<MemberBlockItemListSyntax> {
return expandFreestandingMacro(node.decl.as(MacroExpansionDeclSyntax.self)) { macro, node in
return try expandFreestandingMemberDeclList(
definition: macro,
Expand All @@ -904,7 +932,7 @@ extension MacroApplication {
/// ```swift
/// let a = #foo
/// ```
func expandExpr(node: Syntax) -> ExprSyntax? {
func expandExpr(node: Syntax) -> MacroExpansionResult<ExprSyntax> {
return expandFreestandingMacro(node.as(MacroExpansionExprSyntax.self)) { macro, node in
return try expandFreestandingExpr(
definition: macro,
Expand Down
119 changes: 119 additions & 0 deletions Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1775,4 +1775,123 @@ final class MacroSystemTests: XCTestCase {
)
}

func testThrowErrorFromExpressionMacro() {
struct MyError: Error, CustomStringConvertible {
let description: String = "my error"
}

struct TestMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
throw MyError()
}
}

assertMacroExpansion(
"#test",
expandedSource: "#test",
diagnostics: [
DiagnosticSpec(message: "my error", line: 1, column: 1)
],
macros: ["test": TestMacro.self]
)

assertMacroExpansion(
"1 + #test",
expandedSource: "1 + #test",
diagnostics: [
DiagnosticSpec(message: "my error", line: 1, column: 5)
],
macros: ["test": TestMacro.self]
)
}

func testThrowErrorFromDeclMacro() {
struct MyError: Error, CustomStringConvertible {
let description: String = "my error"
}

struct TestMacro: DeclarationMacro {
static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
throw MyError()
}
}

assertMacroExpansion(
"#test",
expandedSource: "#test",
diagnostics: [
DiagnosticSpec(message: "my error", line: 1, column: 1)
],
macros: ["test": TestMacro.self]
)

assertMacroExpansion(
"""
struct Foo {
#test
}
""",
expandedSource: """
struct Foo {
#test
}
""",
diagnostics: [
DiagnosticSpec(message: "my error", line: 2, column: 3)
],
macros: ["test": TestMacro.self]
)
}

func testAttachedMacroOnFreestandingMacro() {
struct DeclMacro: DeclarationMacro {
static func expansion(of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) throws -> [DeclSyntax] {
return ["var x: Int"]
}
}

struct MyPeerMacro: PeerMacro {
static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws
-> [DeclSyntax]
{
return ["var peer: Int"]
}
}

assertMacroExpansion(
"""
struct Foo {
@Peer
#decl
}
""",
expandedSource: """
struct Foo {
var x: Int

var peer: Int
}
""",
macros: ["decl": DeclMacro.self, "Peer": MyPeerMacro.self]
)

assertMacroExpansion(
"""
@Peer
#decl
""",
expandedSource: """
var x: Int

var peer: Int
""",
macros: ["decl": DeclMacro.self, "Peer": MyPeerMacro.self]
)
}
}