Skip to content

Commit 5428bbc

Browse files
committed
Add a method Error.asDiagnostics(at:) to unpack errors into diagnostics
This is a useful convenience function to handle cases where we have a thrown error from some API and we want to translate it into a diagnostic that we need to emit at a particular location.
1 parent 00fb7a0 commit 5428bbc

File tree

3 files changed

+57
-14
lines changed

3 files changed

+57
-14
lines changed

Release Notes/601.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
- Description: Returns the node or the first ancestor that satisfies `condition`.
1212
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/2696
1313

14+
- `Error` protocol now has an `asDiagnostics(at:)` method.
15+
- Description: This method translates an error into one or more diagnostics, recognizing `DiagnosticsError` and `DiagnosticMessage` instances or providing its own `Diagnostic` as needed.
16+
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/1816
17+
1418
## API Behavior Changes
1519

1620
- `SyntaxProtocol.trimmed` detaches the node

Sources/SwiftDiagnostics/Diagnostic.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,38 @@ public struct DiagnosticsError: Error, Sendable {
9292
)
9393
}
9494
}
95+
96+
/// Diagnostic message used for thrown errors.
97+
private struct DiagnosticFromError: DiagnosticMessage {
98+
let error: Error
99+
let severity: DiagnosticSeverity = .error
100+
101+
var message: String {
102+
return String(describing: error)
103+
}
104+
105+
var diagnosticID: MessageID {
106+
.init(domain: "SwiftDiagnostics", id: "\(type(of: error))")
107+
}
108+
}
109+
110+
extension Error {
111+
/// Given an error, produce an array of diagnostics reporting the error,
112+
/// using the given syntax node as the location if it wasn't otherwise known.
113+
///
114+
/// This operation will look for diagnostics of known type, such as
115+
/// `DiagnosticsError` and `DiagnosticMessage` to retain information. If
116+
/// none of those apply, it will produce an `error` diagnostic whose message
117+
/// comes from rendering the error as a string.
118+
public func asDiagnostics(at node: some SyntaxProtocol) -> [Diagnostic] {
119+
if let diagnosticsError = self as? DiagnosticsError {
120+
return diagnosticsError.diagnostics
121+
}
122+
123+
if let message = self as? DiagnosticMessage {
124+
return [Diagnostic(node: Syntax(node), message: message)]
125+
}
126+
127+
return [Diagnostic(node: Syntax(node), message: DiagnosticFromError(error: self))]
128+
}
129+
}

Sources/SwiftSyntaxMacros/MacroExpansionContext.swift

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,23 @@ extension MacroExpansionContext {
102102
#endif
103103
}
104104

105-
/// Diagnostic message used for thrown errors.
106-
private struct ThrownErrorDiagnostic: DiagnosticMessage {
107-
let message: String
105+
private enum MacroExpansionContextError: DiagnosticMessage {
106+
case internalError(SyntaxStringInterpolationInvalidNodeTypeError)
107+
case missingError
108+
109+
var message: String {
110+
switch self {
111+
case .internalError(let error):
112+
return "Internal macro error: \(error.description)"
113+
case .missingError:
114+
return "macro expansion failed without generating an error"
115+
}
116+
}
108117

109118
var severity: DiagnosticSeverity { .error }
110119

111120
var diagnosticID: MessageID {
112-
.init(domain: "SwiftSyntaxMacros", id: "ThrownErrorDiagnostic")
121+
.init(domain: "SwiftDiagnostics", id: "MacroExpansionContextError")
113122
}
114123
}
115124

@@ -118,18 +127,15 @@ extension MacroExpansionContext {
118127
public func addDiagnostics(from error: Error, node: some SyntaxProtocol) {
119128
// Inspect the error to form an appropriate set of diagnostics.
120129
var diagnostics: [Diagnostic]
121-
if let diagnosticsError = error as? DiagnosticsError {
122-
diagnostics = diagnosticsError.diagnostics
123-
} else if let message = error as? DiagnosticMessage {
124-
diagnostics = [Diagnostic(node: Syntax(node), message: message)]
125-
} else if let error = error as? SyntaxStringInterpolationInvalidNodeTypeError {
130+
131+
if let error = error as? SyntaxStringInterpolationInvalidNodeTypeError {
126132
let diagnostic = Diagnostic(
127133
node: Syntax(node),
128-
message: ThrownErrorDiagnostic(message: "Internal macro error: \(error.description)")
134+
message: MacroExpansionContextError.internalError(error)
129135
)
130136
diagnostics = [diagnostic]
131137
} else {
132-
diagnostics = [Diagnostic(node: Syntax(node), message: ThrownErrorDiagnostic(message: String(describing: error)))]
138+
diagnostics = error.asDiagnostics(at: node)
133139
}
134140

135141
// Emit the diagnostics.
@@ -144,9 +150,7 @@ extension MacroExpansionContext {
144150
diagnose(
145151
Diagnostic(
146152
node: Syntax(node),
147-
message: ThrownErrorDiagnostic(
148-
message: "macro expansion failed without generating an error"
149-
)
153+
message: MacroExpansionContextError.missingError
150154
)
151155
)
152156
}

0 commit comments

Comments
 (0)