Skip to content

Commit 716ed0f

Browse files
committed
Mark syntax interpolation errors in macro expansion
- This commit prepends `SyntaxStringInterpolationError` message with `Internal macro error:` when adding it as a diagnostic in `MacroExpansionContext.addDiagnostics`, so that macro expansion clarifies the error as internal to the macro, not to how it was applied. - Any unrecognized errors raised and sent to `addDiagnostics` will be added as is, without prepending them with a message. That's consistent with how it works now. - Adds a basic test for `addDiagnostics` for that behavior.
1 parent a381b0d commit 716ed0f

File tree

3 files changed

+83
-3
lines changed

3 files changed

+83
-3
lines changed

Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,18 @@ where Self.StringInterpolation == SyntaxStringInterpolation {
161161
init(stringInterpolation: SyntaxStringInterpolation)
162162
}
163163

164-
enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
164+
// Used in `MacroExpansionContext` to make a nicer error message when a
165+
// macro expansion fails with SwiftSyntaxBuilder making a syntax node
166+
// with string interpolation.
167+
@_spi(Diagnostics) public enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
165168
case producedInvalidNodeType(expectedType: SyntaxProtocol.Type, actualType: SyntaxProtocol.Type)
166169
case diagnostics([Diagnostic], tree: Syntax)
167170

168171
static func producedInvalidNodeType<S: SyntaxProtocol>(expectedType: SyntaxProtocol.Type, actualNode: S) -> Self {
169172
return .producedInvalidNodeType(expectedType: expectedType, actualType: type(of: actualNode))
170173
}
171174

172-
var description: String {
175+
public var description: String {
173176
switch self {
174177
case .producedInvalidNodeType(expectedType: let expectedType, actualType: let actualType):
175178
return "Parsing the code snippet was expected to produce a \(expectedType) but produced a \(actualType)"

Sources/SwiftSyntaxMacros/MacroExpansionContext.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import SwiftDiagnostics
1414
import SwiftSyntax
15+
@_spi(Diagnostics) import SwiftSyntaxBuilder
1516

1617
/// Interface to extract information about the context in which a given
1718
/// macro is expanded.
@@ -77,14 +78,17 @@ private struct ThrownErrorDiagnostic: DiagnosticMessage {
7778
}
7879

7980
extension MacroExpansionContext {
80-
/// Add diagnostics from the error thrown during macro expansion.
81+
/// Adds diagnostics from the error thrown during a macro expansion.
8182
public func addDiagnostics(from error: Error, node: some SyntaxProtocol) {
8283
// Inspect the error to form an appropriate set of diagnostics.
8384
var diagnostics: [Diagnostic]
8485
if let diagnosticsError = error as? DiagnosticsError {
8586
diagnostics = diagnosticsError.diagnostics
8687
} else if let message = error as? DiagnosticMessage {
8788
diagnostics = [Diagnostic(node: Syntax(node), message: message)]
89+
} else if let error = error as? SyntaxStringInterpolationError {
90+
let diagnostic = Diagnostic(node: Syntax(node), message: ThrownErrorDiagnostic(message: "Internal macro error: \(error.description)"))
91+
diagnostics = [diagnostic]
8892
} else {
8993
diagnostics = [Diagnostic(node: Syntax(node), message: ThrownErrorDiagnostic(message: String(describing: error)))]
9094
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
//==========================================================================//
14+
// IMPORTANT: The macros defined in this file are intended to test the //
15+
// behavior of MacroSystem. Many of them do not serve as good examples of //
16+
// how macros should be written. In particular, they often lack error //
17+
// handling because it is not needed in the few test cases in which these //
18+
// macros are invoked. //
19+
//==========================================================================//
20+
21+
import SwiftSyntax
22+
import SwiftDiagnostics
23+
@_spi(Diagnostics) import SwiftSyntaxBuilder
24+
import SwiftSyntaxMacros
25+
import SwiftSyntaxMacroExpansion
26+
import SwiftSyntaxMacrosTestSupport
27+
import XCTest
28+
29+
//private class TestMacroExpansionContext: MacroExpansionContext {
30+
// public var diagnostics = [Diagnostic]()
31+
//
32+
// func diagnose(_ diagnostic: Diagnostic) {
33+
// diagnostics.append(diagnostic)
34+
// }
35+
//
36+
// func makeUniqueName(_ name: String) -> TokenSyntax {
37+
// return TokenSyntax.identifier(name)
38+
// }
39+
//
40+
// func location(
41+
// of node: some SyntaxProtocol,
42+
// at position: PositionInSyntaxNode,
43+
// filePathMode: SourceLocationFilePathMode
44+
// ) -> AbstractSourceLocation? {
45+
// return nil
46+
// }
47+
//}
48+
49+
private struct DummyError: Error {
50+
static let diagnosticTestError = DummyError()
51+
}
52+
53+
final class MacroExpansionContextTests: XCTestCase {
54+
55+
func testMacroExpansionContextAddDiagnosticsAddsSwiftSyntaxInterpolationErrorsWithWrappingMessage(){
56+
let context = BasicMacroExpansionContext()
57+
let error = SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: DeclSyntax.self, actualType: ExprSyntax.self)
58+
// Since we only care about the error switch inside of addDagnostics, we don't care about the particular node we're passing in.
59+
context.addDiagnostics(from: error, node: ExprSyntax("1"))
60+
61+
XCTAssertEqual(context.diagnostics.count, 1)
62+
XCTAssertTrue(context.diagnostics.first!.message.starts(with: "Internal macro error:"))
63+
}
64+
65+
func testMacroExpansionContextAddDiagnosticsUsesErrorDescriptionForDiagMessage() {
66+
let context = BasicMacroExpansionContext()
67+
let error = DummyError.diagnosticTestError
68+
69+
context.addDiagnostics(from: error, node: ExprSyntax("1"))
70+
XCTAssertEqual(context.diagnostics.count, 1)
71+
XCTAssertEqual(context.diagnostics.first!.message, String(describing: error))
72+
}
73+
}

0 commit comments

Comments
 (0)