Skip to content

Commit b112b47

Browse files
committed
Allow leading dot syntax for StaticParserError
Convert StaticParserError into a struct, and define the diagnostics in a `DiagnosticMessage where Self == StaticParserError` extension. This allows us to use leading dot syntax to pass a StaticParserError anywhere a DiagnosticMessage is expected. To compute the diagnostic ID, we can (ab)use `#function` to pick up the name of the property.
1 parent bf12c45 commit b112b47

File tree

2 files changed

+86
-55
lines changed

2 files changed

+86
-55
lines changed

Sources/SwiftParser/Diagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -65,27 +65,6 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
6565
self.handledNodes.append(contentsOf: handledNodes)
6666
}
6767

68-
/// Produce a diagnostic.
69-
func addDiagnostic<T: SyntaxProtocol>(
70-
_ node: T,
71-
position: AbsolutePosition? = nil,
72-
_ message: StaticParserError,
73-
highlights: [Syntax] = [],
74-
notes: [Note] = [],
75-
fixIts: [FixIt] = [],
76-
handledNodes: [SyntaxIdentifier] = []
77-
) {
78-
addDiagnostic(
79-
node,
80-
position: position,
81-
message as DiagnosticMessage,
82-
highlights: highlights,
83-
notes: notes,
84-
fixIts: fixIts,
85-
handledNodes: handledNodes
86-
)
87-
}
88-
8968
/// Whether the node should be skipped for diagnostic emission.
9069
/// Every visit method must check this at the beginning.
9170
func shouldSkip<T: SyntaxProtocol>(_ node: T) -> Bool {
@@ -99,11 +78,11 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
9978
///
10079
/// If `incorrectContainer` contains only tokens that satisfy `unexpectedTokenCondition`, emit a diagnostic with message `message` that marks this token as misplaced.
10180
/// If `correctTokens` contains missing tokens, also emit a Fix-It with message `fixIt` that marks the unexpected token as missing and instead inserts `correctTokens`.
102-
public func exchangeTokens(
81+
public func exchangeTokens<Message: DiagnosticMessage>(
10382
unexpected: UnexpectedNodesSyntax?,
10483
unexpectedTokenCondition: (TokenSyntax) -> Bool,
10584
correctTokens: [TokenSyntax?],
106-
message: (_ misplacedTokens: [TokenSyntax]) -> DiagnosticMessage,
85+
message: (_ misplacedTokens: [TokenSyntax]) -> Message,
10786
moveFixIt: (_ misplacedTokens: [TokenSyntax]) -> FixItMessage,
10887
removeRedundantFixIt: (_ misplacedTokens: [TokenSyntax]) -> FixItMessage? = { _ in nil }
10988
) {
@@ -146,10 +125,10 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
146125
}
147126

148127
/// Utility function to remove a misplaced token with a custom error message.
149-
public func removeToken(
128+
public func removeToken<Message: DiagnosticMessage>(
150129
_ unexpected: UnexpectedNodesSyntax?,
151130
where predicate: (TokenSyntax) -> Bool,
152-
message: (TokenSyntax) -> DiagnosticMessage
131+
message: (TokenSyntax) -> Message
153132
) {
154133
guard let unexpected = unexpected,
155134
let misplacedToken = unexpected.onlyToken(where: predicate)
@@ -462,7 +441,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
462441
unexpected: node.output?.unexpectedBetweenArrowAndReturnType,
463442
unexpectedTokenCondition: { $0.tokenKind == .throwsKeyword },
464443
correctTokens: [node.throwsOrRethrowsKeyword],
465-
message: { _ in StaticParserError.throwsInReturnPosition },
444+
message: { _ in .throwsInReturnPosition },
466445
moveFixIt: { MoveTokensInFrontOfFixIt(movedTokens: $0, inFrontOf: .arrow) }
467446
)
468447
return .visitChildren
@@ -514,7 +493,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
514493
unexpected: node.unexpectedBeforeReturnKeyword,
515494
unexpectedTokenCondition: { $0.tokenKind == .tryKeyword },
516495
correctTokens: [node.expression?.as(TryExprSyntax.self)?.tryKeyword],
517-
message: { _ in StaticParserError.tryMustBePlacedOnReturnedExpr },
496+
message: { _ in .tryMustBePlacedOnReturnedExpr },
518497
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .returnKeyword) }
519498
)
520499
}
@@ -553,7 +532,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
553532
unexpected: node.unexpectedBeforeThrowKeyword,
554533
unexpectedTokenCondition: { $0.tokenKind == .tryKeyword },
555534
correctTokens: [node.expression.as(TryExprSyntax.self)?.tryKeyword],
556-
message: { _ in StaticParserError.tryMustBePlacedOnThrownExpr },
535+
message: { _ in .tryMustBePlacedOnThrownExpr },
557536
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .throwKeyword) }
558537
)
559538
return .visitChildren
@@ -623,7 +602,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
623602
removeToken(
624603
node.unexpectedBetweenIdentifierAndInheritanceClause,
625604
where: { $0.tokenKind == .ellipsis },
626-
message: { _ in StaticParserError.associatedTypeCannotUsePack }
605+
message: { _ in .associatedTypeCannotUsePack }
627606
)
628607
return .visitChildren
629608
}
@@ -639,7 +618,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
639618
unexpected: node.unexpectedBetweenModifiersAndLetOrVarKeyword,
640619
unexpectedTokenCondition: { $0.tokenKind == .tryKeyword },
641620
correctTokens: missingTries,
642-
message: { _ in StaticParserError.tryOnInitialValueExpression },
621+
message: { _ in .tryOnInitialValueExpression },
643622
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .equal) },
644623
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
645624
)

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -64,38 +64,90 @@ public extension ParserFixIt {
6464

6565
// MARK: - Errors (please sort alphabetically)
6666

67-
/// Please order the cases in this enum alphabetically by case name.
68-
public enum StaticParserError: String, DiagnosticMessage {
69-
case allStatmentsInSwitchMustBeCoveredByCase = "all statements inside a switch must be covered by a 'case' or 'default' label"
70-
case caseOutsideOfSwitchOrEnum = "'case' can only appear inside a 'switch' statement or 'enum' declaration"
71-
case consecutiveDeclarationsOnSameLine = "consecutive declarations on a line must be separated by ';'"
72-
case consecutiveStatementsOnSameLine = "consecutive statements on a line must be separated by ';'"
73-
case cStyleForLoop = "C-style for statement has been removed in Swift 3"
74-
case defaultCannotBeUsedWithWhere = "'default' cannot be used with a 'where' guard expression"
75-
case defaultOutsideOfSwitch = "'default' label can only appear inside a 'switch' statement"
76-
case editorPlaceholderInSourceFile = "editor placeholder in source file"
77-
case expectedExpressionAfterTry = "expected expression after 'try'"
78-
case invalidFlagAfterPrecedenceGroupAssignment = "expected 'true' or 'false' after 'assignment'"
79-
case missingColonInTernaryExprDiagnostic = "expected ':' after '? ...' in ternary expression"
80-
case operatorShouldBeDeclaredWithoutBody = "operator should not be declared with body"
81-
case standaloneSemicolonStatement = "standalone ';' statements are not allowed"
82-
case subscriptsCannotHaveNames = "subscripts cannot have a name"
83-
case throwsInReturnPosition = "'throws' may only occur before '->'"
84-
case tryMustBePlacedOnReturnedExpr = "'try' must be placed on the returned expression"
85-
case tryMustBePlacedOnThrownExpr = "'try' must be placed on the thrown expression"
86-
case tryOnInitialValueExpression = "'try' must be placed on the initial value expression"
87-
case unexpectedSemicolon = "unexpected ';' separator"
88-
case associatedTypeCannotUsePack = "associated types cannot be variadic"
89-
90-
public var message: String { self.rawValue }
67+
/// A parser error with a static message.
68+
public struct StaticParserError: DiagnosticMessage {
69+
public let message: String
70+
private let messageID: String
71+
72+
/// This should only be called within a static var on DiagnosticMessage, such
73+
/// as the examples below. This allows us to pick up the messageID from the
74+
/// var name.
75+
fileprivate init(_ message: String, messageID: String = #function) {
76+
self.message = message
77+
self.messageID = messageID
78+
}
9179

9280
public var diagnosticID: MessageID {
93-
MessageID(domain: diagnosticDomain, id: "\(type(of: self)).\(self)")
81+
MessageID(domain: diagnosticDomain, id: "\(type(of: self)).\(messageID)")
9482
}
9583

9684
public var severity: DiagnosticSeverity { .error }
9785
}
9886

87+
extension DiagnosticMessage where Self == StaticParserError {
88+
/// Please order the diagnostics alphabetically by property name.
89+
public static var allStatmentsInSwitchMustBeCoveredByCase: Self {
90+
.init("all statements inside a switch must be covered by a 'case' or 'default' label")
91+
}
92+
public static var associatedTypeCannotUsePack: Self {
93+
.init("associated types cannot be variadic")
94+
}
95+
public static var caseOutsideOfSwitchOrEnum: Self {
96+
.init("'case' can only appear inside a 'switch' statement or 'enum' declaration")
97+
}
98+
public static var consecutiveDeclarationsOnSameLine: Self {
99+
.init("consecutive declarations on a line must be separated by ';'")
100+
}
101+
public static var consecutiveStatementsOnSameLine: Self {
102+
.init("consecutive statements on a line must be separated by ';'")
103+
}
104+
public static var cStyleForLoop: Self {
105+
.init("C-style for statement has been removed in Swift 3")
106+
}
107+
public static var defaultCannotBeUsedWithWhere: Self {
108+
.init("'default' cannot be used with a 'where' guard expression")
109+
}
110+
public static var defaultOutsideOfSwitch: Self {
111+
.init("'default' label can only appear inside a 'switch' statement")
112+
}
113+
public static var editorPlaceholderInSourceFile: Self {
114+
.init("editor placeholder in source file")
115+
}
116+
public static var expectedExpressionAfterTry: Self {
117+
.init("expected expression after 'try'")
118+
}
119+
public static var invalidFlagAfterPrecedenceGroupAssignment: Self {
120+
.init("expected 'true' or 'false' after 'assignment'")
121+
}
122+
public static var missingColonInTernaryExprDiagnostic: Self {
123+
.init("expected ':' after '? ...' in ternary expression")
124+
}
125+
public static var operatorShouldBeDeclaredWithoutBody: Self {
126+
.init("operator should not be declared with body")
127+
}
128+
public static var standaloneSemicolonStatement: Self {
129+
.init("standalone ';' statements are not allowed")
130+
}
131+
public static var subscriptsCannotHaveNames: Self {
132+
.init("subscripts cannot have a name")
133+
}
134+
public static var throwsInReturnPosition: Self {
135+
.init("'throws' may only occur before '->'")
136+
}
137+
public static var tryMustBePlacedOnReturnedExpr: Self {
138+
.init("'try' must be placed on the returned expression")
139+
}
140+
public static var tryMustBePlacedOnThrownExpr: Self {
141+
.init("'try' must be placed on the thrown expression")
142+
}
143+
public static var tryOnInitialValueExpression: Self {
144+
.init("'try' must be placed on the initial value expression")
145+
}
146+
public static var unexpectedSemicolon: Self {
147+
.init("unexpected ';' separator")
148+
}
149+
}
150+
99151
// MARK: - Diagnostics (please sort alphabetically)
100152

101153
public struct EffectsSpecifierAfterArrow: ParserError {

0 commit comments

Comments
 (0)