Skip to content

Commit e9c5d1a

Browse files
authored
Merge pull request #2019 from ahoppen/ahoppen/simpler-diag-generation
Add declarations to make construction of diagnostics in macros easier
2 parents d3d4c93 + d3b9518 commit e9c5d1a

File tree

7 files changed

+134
-39
lines changed

7 files changed

+134
-39
lines changed

Sources/SwiftDiagnostics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_swift_host_library(SwiftDiagnostics
10+
Convenience.swift
1011
Diagnostic.swift
1112
DiagnosticsFormatter.swift
1213
FixIt.swift
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
import SwiftSyntax
14+
15+
extension Diagnostic {
16+
/// Construct a new diagnostic that has exactly one Fix-It.
17+
public init(
18+
node: some SyntaxProtocol,
19+
position: AbsolutePosition? = nil,
20+
message: DiagnosticMessage,
21+
highlights: [Syntax]? = nil,
22+
notes: [Note] = [],
23+
fixIt: FixIt
24+
) {
25+
self.init(
26+
node: node,
27+
position: position,
28+
message: message,
29+
highlights: highlights,
30+
notes: notes,
31+
fixIts: [fixIt]
32+
)
33+
}
34+
}
35+
36+
extension FixIt {
37+
/// A Fix-It that replaces `oldNode` by `newNode`.
38+
public static func replace(
39+
message: FixItMessage,
40+
oldNode: some SyntaxProtocol,
41+
newNode: some SyntaxProtocol
42+
) -> Self {
43+
return FixIt(
44+
message: message,
45+
changes: [
46+
.replace(oldNode: Syntax(oldNode), newNode: Syntax(newNode))
47+
]
48+
)
49+
}
50+
}

Sources/SwiftDiagnostics/Diagnostic.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ public struct Diagnostic: CustomDebugStringConvertible {
3636
/// If `highlights` is `nil` then `node` will be highlighted. This is a
3737
/// reasonable default for almost all diagnostics.
3838
public init(
39-
node: Syntax,
39+
node: some SyntaxProtocol,
4040
position: AbsolutePosition? = nil,
4141
message: DiagnosticMessage,
4242
highlights: [Syntax]? = nil,
4343
notes: [Note] = [],
4444
fixIts: [FixIt] = []
4545
) {
46-
self.node = node
46+
self.node = Syntax(node)
4747
self.position = position ?? node.positionAfterSkippingLeadingTrivia
4848
self.diagMessage = message
49-
self.highlights = highlights ?? [node]
49+
self.highlights = highlights ?? [Syntax(node)]
5050
self.notes = notes
5151
self.fixIts = fixIts
5252
}

Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ add_swift_host_library(SwiftSyntaxMacroExpansion
33
FunctionParameterUtils.swift
44
IndentationUtils.swift
55
MacroExpansion.swift
6+
MacroExpansionDiagnosticMessages.swift
67
MacroReplacement.swift
78
MacroSystem.swift
89
)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
import SwiftDiagnostics
14+
15+
/// An error during macro expansion that is described by its message.
16+
///
17+
/// This type allows macro authors to quickly generate error messages based on a
18+
/// string. For any non-trivial error messages, it is encouraged to define a
19+
/// custom type that conforms to `DiagnosticMessage`.
20+
public struct MacroExpansionErrorMessage: Error, DiagnosticMessage {
21+
public let message: String
22+
23+
public var severity: SwiftDiagnostics.DiagnosticSeverity { .error }
24+
25+
public var diagnosticID: SwiftDiagnostics.MessageID {
26+
.init(domain: diagnosticDomain, id: "\(Self.self)")
27+
}
28+
29+
public init(_ message: String) {
30+
self.message = message
31+
}
32+
}
33+
34+
/// An warning during macro expansion that is described by its message.
35+
///
36+
/// This type allows macro authors to quickly generate warning messages based on
37+
/// a string. For any non-trivial warning messages, it is encouraged to define a
38+
/// custom type that conforms to `DiagnosticMessage`.
39+
public struct MacroExpansionWarningMessage: DiagnosticMessage {
40+
public let message: String
41+
42+
public var severity: SwiftDiagnostics.DiagnosticSeverity { .warning }
43+
44+
public var diagnosticID: SwiftDiagnostics.MessageID {
45+
.init(domain: diagnosticDomain, id: "\(Self.self)")
46+
}
47+
48+
public init(_ message: String) {
49+
self.message = message
50+
}
51+
}
52+
53+
/// The message of a Fix-It that is specified by a string literal
54+
///
55+
/// This type allows macro authors to quickly generate Fix-It messages based on
56+
/// a string. For any non-trivial Fix-It messages, it is encouraged to define a
57+
/// custom type that conforms to `FixItMessage`.
58+
public struct MacroExpansionFixItMessage: FixItMessage {
59+
public var message: String
60+
61+
public var fixItID: SwiftDiagnostics.MessageID {
62+
.init(domain: diagnosticDomain, id: "\(Self.self)")
63+
}
64+
65+
public init(_ message: String) {
66+
self.message = message
67+
}
68+
}

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ private class AttributeRemover: SyntaxRewriter {
420420
}
421421
}
422422

423-
private let diagnosticDomain: String = "SwiftSyntaxMacroExpansion"
423+
let diagnosticDomain: String = "SwiftSyntaxMacroExpansion"
424424

425425
private enum MacroApplicationError: DiagnosticMessage, Error {
426426
case accessorMacroOnVariableWithMultipleBindings

Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,14 @@ import SwiftSyntaxMacroExpansion
2020
import SwiftSyntaxMacrosTestSupport
2121
import XCTest
2222

23-
enum CustomError: Error, CustomStringConvertible {
24-
case message(String)
25-
26-
var description: String {
27-
switch self {
28-
case .message(let text):
29-
return text
30-
}
31-
}
32-
}
33-
3423
// MARK: Example macros
3524
public struct StringifyMacro: ExpressionMacro {
3625
public static func expansion(
3726
of macro: some FreestandingMacroExpansionSyntax,
3827
in context: some MacroExpansionContext
3928
) throws -> ExprSyntax {
4029
guard let argument = macro.argumentList.first?.expression else {
41-
throw CustomError.message("missing argument")
30+
throw MacroExpansionErrorMessage("missing argument")
4231
}
4332

4433
return "(\(argument), \(StringLiteralExprSyntax(content: argument.description)))"
@@ -110,7 +99,7 @@ public struct ColumnMacro: ExpressionMacro {
11099
) throws -> ExprSyntax {
111100
guard let sourceLoc: AbstractSourceLocation = context.location(of: macro)
112101
else {
113-
throw CustomError.message("can't find location for macro")
102+
throw MacroExpansionErrorMessage("can't find location for macro")
114103
}
115104
return sourceLoc.column.with(\.leadingTrivia, macro.leadingTrivia)
116105
}
@@ -123,7 +112,7 @@ public struct FileIDMacro: ExpressionMacro {
123112
) throws -> ExprSyntax {
124113
guard let sourceLoc: AbstractSourceLocation = context.location(of: macro)
125114
else {
126-
throw CustomError.message("can't find location for macro")
115+
throw MacroExpansionErrorMessage("can't find location for macro")
127116
}
128117
return sourceLoc.file.with(\.leadingTrivia, macro.leadingTrivia)
129118
}
@@ -147,16 +136,6 @@ struct CheckContextIndependenceMacro: ExpressionMacro {
147136
}
148137
}
149138

150-
struct SimpleDiagnosticMessage: DiagnosticMessage {
151-
let message: String
152-
let diagnosticID: MessageID
153-
let severity: DiagnosticSeverity
154-
}
155-
156-
extension SimpleDiagnosticMessage: FixItMessage {
157-
var fixItID: MessageID { diagnosticID }
158-
}
159-
160139
public struct ErrorMacro: DeclarationMacro {
161140
public static func expansion(
162141
of node: some FreestandingMacroExpansionSyntax,
@@ -168,17 +147,13 @@ public struct ErrorMacro: DeclarationMacro {
168147
stringLiteral.segments.count == 1,
169148
case let .stringSegment(messageString) = stringLiteral.segments[0]
170149
else {
171-
throw CustomError.message("#error macro requires a string literal")
150+
throw MacroExpansionErrorMessage("#error macro requires a string literal")
172151
}
173152

174153
context.diagnose(
175154
Diagnostic(
176155
node: Syntax(node),
177-
message: SimpleDiagnosticMessage(
178-
message: messageString.content.description,
179-
diagnosticID: MessageID(domain: "test", id: "error"),
180-
severity: .error
181-
)
156+
message: MacroExpansionErrorMessage(messageString.content.description)
182157
)
183158
)
184159

@@ -197,7 +172,7 @@ struct DefineBitwidthNumberedStructsMacro: DeclarationMacro {
197172
stringLiteral.segments.count == 1,
198173
case let .stringSegment(prefix) = stringLiteral.segments[0]
199174
else {
200-
throw CustomError.message(
175+
throw MacroExpansionErrorMessage(
201176
"#bitwidthNumberedStructs macro requires a string literal"
202177
)
203178
}
@@ -304,12 +279,12 @@ public struct AddCompletionHandler: PeerMacro {
304279
// Only on functions at the moment. We could handle initializers as well
305280
// with a bit of work.
306281
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
307-
throw CustomError.message("@addCompletionHandler only works on functions")
282+
throw MacroExpansionErrorMessage("@addCompletionHandler only works on functions")
308283
}
309284

310285
// This only makes sense for async functions.
311286
if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil {
312-
throw CustomError.message(
287+
throw MacroExpansionErrorMessage(
313288
"@addCompletionHandler requires an async function"
314289
)
315290
}
@@ -559,14 +534,14 @@ public struct UnwrapMacro: CodeItemMacro {
559534
in context: some MacroExpansionContext
560535
) throws -> [CodeBlockItemSyntax] {
561536
guard !node.argumentList.isEmpty else {
562-
throw CustomError.message("'#unwrap' requires arguments")
537+
throw MacroExpansionErrorMessage("'#unwrap' requires arguments")
563538
}
564539
let errorThrower = node.trailingClosure
565540
let identifiers = try node.argumentList.map { argument in
566541
guard let tupleElement = argument.as(LabeledExprSyntax.self),
567542
let declReferenceExpr = tupleElement.expression.as(DeclReferenceExprSyntax.self)
568543
else {
569-
throw CustomError.message("Arguments must be identifiers")
544+
throw MacroExpansionErrorMessage("Arguments must be identifiers")
570545
}
571546
return declReferenceExpr.baseName
572547
}

0 commit comments

Comments
 (0)