Skip to content

Commit 05b48c6

Browse files
committed
[Macros] Fix and test diagnostic propagation through macro expansion.
1 parent ac34246 commit 05b48c6

File tree

2 files changed

+99
-12
lines changed

2 files changed

+99
-12
lines changed

Sources/_SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,14 @@ class MacroApplication: SyntaxRewriter {
101101
}
102102
}
103103

104-
105104
extension SyntaxProtocol {
106105
/// Expand all uses of the given set of macros within this syntax
107106
/// node.
108107
public func expand(
109-
macros: [String : Macro.Type],
108+
macros: [String: Macro.Type],
110109
in context: inout MacroExpansionContext
111110
) -> Syntax {
112-
// Build the macro system.
111+
// Build the macro system.
113112
var system = MacroSystem()
114113
for (macroName, macroType) in macros {
115114
try! system.add(macroType, name: macroName)
@@ -119,6 +118,11 @@ extension SyntaxProtocol {
119118
macroSystem: system,
120119
context: context
121120
)
121+
122+
defer {
123+
context = applier.context
124+
}
125+
122126
return applier.visit(Syntax(self))
123127
}
124128
}

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import SwiftDiagnostics
1314
import SwiftParser
1415
import SwiftSyntax
1516
import SwiftSyntaxBuilder
@@ -132,6 +133,58 @@ struct CheckContextIndependenceMacro: ExpressionMacro {
132133
}
133134
}
134135

136+
enum CustomError: Error, CustomStringConvertible {
137+
case message(String)
138+
139+
var description: String {
140+
switch self {
141+
case .message(let text):
142+
return text
143+
}
144+
}
145+
}
146+
147+
struct SimpleDiagnosticMessage: DiagnosticMessage {
148+
let message: String
149+
let diagnosticID: MessageID
150+
let severity: DiagnosticSeverity
151+
}
152+
153+
extension SimpleDiagnosticMessage: FixItMessage {
154+
var fixItID: MessageID { diagnosticID }
155+
}
156+
157+
public struct ErrorMacro: ExpressionMacro {
158+
public static func expansion(
159+
of macro: MacroExpansionExprSyntax,
160+
in context: inout MacroExpansionContext
161+
) throws -> ExprSyntax {
162+
guard let firstElement = macro.argumentList.first,
163+
let stringLiteral = firstElement.expression
164+
.as(StringLiteralExprSyntax.self),
165+
stringLiteral.segments.count == 1,
166+
case let .stringSegment(messageString) = stringLiteral.segments[0]
167+
else {
168+
throw CustomError.message("#error macro requires a string literal")
169+
}
170+
171+
context.diagnose(
172+
Diagnostic(
173+
node: Syntax(macro),
174+
message: SimpleDiagnosticMessage(
175+
message: messageString.content.description,
176+
diagnosticID: MessageID(domain: "test", id: "error"),
177+
severity: .error
178+
)
179+
)
180+
)
181+
182+
return "()"
183+
}
184+
}
185+
186+
// MARK: Assertion helper functions
187+
135188
/// Assert that expanding the given macros in the original source produces
136189
/// the given expanded source code.
137190
///
@@ -146,11 +199,12 @@ struct CheckContextIndependenceMacro: ExpressionMacro {
146199
/// - expandedSource: The source code that we expect to see after performing
147200
/// macro expansion on the original source.
148201
public func AssertMacroExpansion(
149-
macros: [String : Macro.Type],
202+
macros: [String: Macro.Type],
150203
testModuleName: String = "TestModule",
151204
testFileName: String = "test.swift",
152205
_ originalSource: String,
153206
_ expandedSource: String,
207+
diagnosticStrings: [String] = [],
154208
file: StaticString = #file,
155209
line: UInt = #line
156210
) {
@@ -170,15 +224,25 @@ public func AssertMacroExpansion(
170224
file: file,
171225
line: line
172226
)
227+
228+
let diags = context.diagnostics
229+
XCTAssertEqual(diags.count, diagnosticStrings.count)
230+
for (actualDiag, expectedDiag) in zip(diags, diagnosticStrings) {
231+
let actualMessage = actualDiag.message
232+
XCTAssertEqual(actualMessage, expectedDiag)
233+
}
173234
}
174235

236+
// MARK: Tests
237+
175238
/// The set of test macros we use here.
176-
public let testMacros: [String : Macro.Type] = [
177-
"checkContext" : CheckContextIndependenceMacro.self,
178-
"colorLiteral" : ColorLiteralMacro.self,
179-
"fileID" : FileIDMacro.self,
180-
"imageLiteral" : ImageLiteralMacro.self,
181-
"stringify" : StringifyMacro.self,
239+
public let testMacros: [String: Macro.Type] = [
240+
"checkContext": CheckContextIndependenceMacro.self,
241+
"colorLiteral": ColorLiteralMacro.self,
242+
"fileID": FileIDMacro.self,
243+
"imageLiteral": ImageLiteralMacro.self,
244+
"stringify": StringifyMacro.self,
245+
"myError": ErrorMacro.self,
182246
]
183247

184248
final class MacroSystemTests: XCTestCase {
@@ -198,7 +262,7 @@ final class MacroSystemTests: XCTestCase {
198262

199263
func testStringifyExpression() {
200264
AssertMacroExpansion(
201-
macros: ["stringify" : StringifyMacro.self],
265+
macros: ["stringify": StringifyMacro.self],
202266
"""
203267
_ = #stringify({ () -> Bool in
204268
print("hello")
@@ -242,7 +306,7 @@ final class MacroSystemTests: XCTestCase {
242306

243307
func testContextIndependence() {
244308
AssertMacroExpansion(
245-
macros: ["checkContext" : CheckContextIndependenceMacro.self],
309+
macros: ["checkContext": CheckContextIndependenceMacro.self],
246310
"""
247311
let b = #checkContext
248312
""",
@@ -251,4 +315,23 @@ final class MacroSystemTests: XCTestCase {
251315
"""
252316
)
253317
}
318+
319+
func testErrorExpansion() {
320+
AssertMacroExpansion(
321+
macros: testMacros,
322+
"""
323+
_ = #myError("please don't do that")
324+
_ = #myError(bad)
325+
""",
326+
"""
327+
_ = ()
328+
_ = #myError(bad)
329+
""",
330+
diagnosticStrings: [
331+
"please don't do that",
332+
"#error macro requires a string literal",
333+
]
334+
)
335+
}
336+
254337
}

0 commit comments

Comments
 (0)