Skip to content

Commit 98a260b

Browse files
authored
Merge pull request #951 from hamishknight/static-dot
2 parents 583e3d8 + d117cd1 commit 98a260b

File tree

5 files changed

+154
-73
lines changed

5 files changed

+154
-73
lines changed

Sources/SwiftParser/Diagnostics/DiagnosticExtensions.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ import SwiftDiagnostics
1414
import SwiftSyntax
1515

1616
extension FixIt {
17-
init(message: StaticParserFixIt, changes: Changes) {
18-
self.init(message: message as FixItMessage, changes: changes)
19-
}
20-
2117
public init(message: FixItMessage, changes: [Changes]) {
2218
self.init(message: message, changes: FixIt.Changes(combining: changes))
2319
}
2420

21+
// These overloads shouldn't be needed, but are currently required for the
22+
// Swift 5.5 compiler to handle non-trivial FixIt initializations using
23+
// leading-dot syntax.
24+
// TODO: These can be dropped once we require a minimum of Swift 5.6 to
25+
// compile the library.
26+
init(message: StaticParserFixIt, changes: Changes) {
27+
self.init(message: message as FixItMessage, changes: changes)
28+
}
2529
init(message: StaticParserFixIt, changes: [Changes]) {
2630
self.init(message: message as FixItMessage, changes: FixIt.Changes(combining: changes))
2731
}

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
) {
@@ -147,10 +126,10 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
147126

148127
/// If `unexpected` only contains a single token that satisfies `predicate`,
149128
/// emits a diagnostic with `message` that removes this token.
150-
public func removeToken(
129+
public func removeToken<Message: DiagnosticMessage>(
151130
_ unexpected: UnexpectedNodesSyntax?,
152131
where predicate: (TokenSyntax) -> Bool,
153-
message: (TokenSyntax) -> DiagnosticMessage
132+
message: (TokenSyntax) -> Message
154133
) {
155134
guard let unexpected = unexpected,
156135
let misplacedToken = unexpected.onlyToken(where: predicate)
@@ -509,7 +488,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
509488
unexpected: node.output?.unexpectedBetweenArrowAndReturnType,
510489
unexpectedTokenCondition: { $0.tokenKind == .throwsKeyword },
511490
correctTokens: [node.throwsOrRethrowsKeyword],
512-
message: { _ in StaticParserError.throwsInReturnPosition },
491+
message: { _ in .throwsInReturnPosition },
513492
moveFixIt: { MoveTokensInFrontOfFixIt(movedTokens: $0, inFrontOf: .arrow) }
514493
)
515494
return .visitChildren
@@ -577,7 +556,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
577556
unexpected: node.unexpectedBeforeReturnKeyword,
578557
unexpectedTokenCondition: { $0.tokenKind == .tryKeyword },
579558
correctTokens: [node.expression?.as(TryExprSyntax.self)?.tryKeyword],
580-
message: { _ in StaticParserError.tryMustBePlacedOnReturnedExpr },
559+
message: { _ in .tryMustBePlacedOnReturnedExpr },
581560
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .returnKeyword) }
582561
)
583562
}
@@ -616,7 +595,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
616595
unexpected: node.unexpectedBeforeThrowKeyword,
617596
unexpectedTokenCondition: { $0.tokenKind == .tryKeyword },
618597
correctTokens: [node.expression.as(TryExprSyntax.self)?.tryKeyword],
619-
message: { _ in StaticParserError.tryMustBePlacedOnThrownExpr },
598+
message: { _ in .tryMustBePlacedOnThrownExpr },
620599
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .throwKeyword) }
621600
)
622601
return .visitChildren
@@ -701,7 +680,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
701680
removeToken(
702681
node.unexpectedBetweenIdentifierAndInheritanceClause,
703682
where: { $0.tokenKind == .ellipsis },
704-
message: { _ in StaticParserError.associatedTypeCannotUsePack }
683+
message: { _ in .associatedTypeCannotUsePack }
705684
)
706685
return .visitChildren
707686
}
@@ -717,7 +696,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
717696
unexpected: node.unexpectedBetweenModifiersAndLetOrVarKeyword,
718697
unexpectedTokenCondition: { $0.tokenKind == .tryKeyword },
719698
correctTokens: missingTries,
720-
message: { _ in StaticParserError.tryOnInitialValueExpression },
699+
message: { _ in .tryOnInitialValueExpression },
721700
moveFixIt: { MoveTokensAfterFixIt(movedTokens: $0, after: .equal) },
722701
removeRedundantFixIt: { RemoveRedundantFixIt(removeTokens: $0) }
723702
)

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 124 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -64,42 +64,102 @@ 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 classConstraintCanOnlyBeUsedInProtocol = "'class' constraint can only appear on protocol declarations"
72-
case consecutiveDeclarationsOnSameLine = "consecutive declarations on a line must be separated by ';'"
73-
case consecutiveStatementsOnSameLine = "consecutive statements on a line must be separated by ';'"
74-
case cStyleForLoop = "C-style for statement has been removed in Swift 3"
75-
case defaultCannotBeUsedWithWhere = "'default' cannot be used with a 'where' guard expression"
76-
case defaultOutsideOfSwitch = "'default' label can only appear inside a 'switch' statement"
77-
case deinitCannotHaveName = "deinitializers cannot have a name"
78-
case deinitCannotHaveParameters = "deinitializers cannot have parameters"
79-
case editorPlaceholderInSourceFile = "editor placeholder in source file"
80-
case expectedExpressionAfterTry = "expected expression after 'try'"
81-
case invalidFlagAfterPrecedenceGroupAssignment = "expected 'true' or 'false' after 'assignment'"
82-
case missingColonAndExprInTernaryExpr = "expected ':' and expression after '? ...' in ternary expression"
83-
case missingColonInTernaryExpr = "expected ':' after '? ...' in ternary expression"
84-
case operatorShouldBeDeclaredWithoutBody = "operator should not be declared with body"
85-
case standaloneSemicolonStatement = "standalone ';' statements are not allowed"
86-
case subscriptsCannotHaveNames = "subscripts cannot have a name"
87-
case throwsInReturnPosition = "'throws' may only occur before '->'"
88-
case tryMustBePlacedOnReturnedExpr = "'try' must be placed on the returned expression"
89-
case tryMustBePlacedOnThrownExpr = "'try' must be placed on the thrown expression"
90-
case tryOnInitialValueExpression = "'try' must be placed on the initial value expression"
91-
case unexpectedSemicolon = "unexpected ';' separator"
92-
case associatedTypeCannotUsePack = "associated types cannot be variadic"
93-
94-
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+
}
9579

9680
public var diagnosticID: MessageID {
97-
MessageID(domain: diagnosticDomain, id: "\(type(of: self)).\(self)")
81+
MessageID(domain: diagnosticDomain, id: "\(type(of: self)).\(messageID)")
9882
}
9983

10084
public var severity: DiagnosticSeverity { .error }
10185
}
10286

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 classConstraintCanOnlyBeUsedInProtocol: Self {
99+
.init("'class' constraint can only appear on protocol declarations")
100+
}
101+
public static var consecutiveDeclarationsOnSameLine: Self {
102+
.init("consecutive declarations on a line must be separated by ';'")
103+
}
104+
public static var consecutiveStatementsOnSameLine: Self {
105+
.init("consecutive statements on a line must be separated by ';'")
106+
}
107+
public static var cStyleForLoop: Self {
108+
.init("C-style for statement has been removed in Swift 3")
109+
}
110+
public static var defaultCannotBeUsedWithWhere: Self {
111+
.init("'default' cannot be used with a 'where' guard expression")
112+
}
113+
public static var defaultOutsideOfSwitch: Self {
114+
.init("'default' label can only appear inside a 'switch' statement")
115+
}
116+
public static var deinitCannotHaveName: Self {
117+
.init("deinitializers cannot have a name")
118+
}
119+
public static var deinitCannotHaveParameters: Self {
120+
.init("deinitializers cannot have parameters")
121+
}
122+
public static var editorPlaceholderInSourceFile: Self {
123+
.init("editor placeholder in source file")
124+
}
125+
public static var expectedExpressionAfterTry: Self {
126+
.init("expected expression after 'try'")
127+
}
128+
public static var invalidFlagAfterPrecedenceGroupAssignment: Self {
129+
.init("expected 'true' or 'false' after 'assignment'")
130+
}
131+
public static var missingColonAndExprInTernaryExpr: Self {
132+
.init("expected ':' and expression after '? ...' in ternary expression")
133+
}
134+
public static var missingColonInTernaryExpr: Self {
135+
.init("expected ':' after '? ...' in ternary expression")
136+
}
137+
public static var operatorShouldBeDeclaredWithoutBody: Self {
138+
.init("operator should not be declared with body")
139+
}
140+
public static var standaloneSemicolonStatement: Self {
141+
.init("standalone ';' statements are not allowed")
142+
}
143+
public static var subscriptsCannotHaveNames: Self {
144+
.init("subscripts cannot have a name")
145+
}
146+
public static var throwsInReturnPosition: Self {
147+
.init("'throws' may only occur before '->'")
148+
}
149+
public static var tryMustBePlacedOnReturnedExpr: Self {
150+
.init("'try' must be placed on the returned expression")
151+
}
152+
public static var tryMustBePlacedOnThrownExpr: Self {
153+
.init("'try' must be placed on the thrown expression")
154+
}
155+
public static var tryOnInitialValueExpression: Self {
156+
.init("'try' must be placed on the initial value expression")
157+
}
158+
public static var unexpectedSemicolon: Self {
159+
.init("unexpected ';' separator")
160+
}
161+
}
162+
103163
// MARK: - Diagnostics (please sort alphabetically)
104164

105165
public struct EffectsSpecifierAfterArrow: ParserError {
@@ -221,18 +281,43 @@ public struct UnknownDirectiveError: ParserError {
221281

222282
// MARK: - Fix-Its (please sort alphabetically)
223283

224-
public enum StaticParserFixIt: String, FixItMessage {
225-
case insertSemicolon = "insert ';'"
226-
case insertAttributeArguments = "insert attribute argument"
227-
case joinIdentifiers = "join the identifiers together"
228-
case joinIdentifiersWithCamelCase = "join the identifiers together with camel-case"
229-
case removeOperatorBody = "remove operator body"
230-
case wrapKeywordInBackticks = "if this name is unavoidable, use backticks to escape it"
231-
232-
public var message: String { self.rawValue }
284+
/// A parser fix-it with a static message.
285+
public struct StaticParserFixIt: FixItMessage {
286+
public let message: String
287+
private let messageID: String
288+
289+
/// This should only be called within a static var on FixItMessage, such
290+
/// as the examples below. This allows us to pick up the messageID from the
291+
/// var name.
292+
fileprivate init(_ message: String, messageID: String = #function) {
293+
self.message = message
294+
self.messageID = messageID
295+
}
233296

234297
public var fixItID: MessageID {
235-
MessageID(domain: diagnosticDomain, id: "\(type(of: self)).\(self)")
298+
MessageID(domain: diagnosticDomain, id: "\(type(of: self)).\(messageID)")
299+
}
300+
}
301+
302+
extension FixItMessage where Self == StaticParserFixIt {
303+
/// Please order alphabetically by property name.
304+
public static var insertSemicolon: Self {
305+
.init("insert ';'")
306+
}
307+
public static var insertAttributeArguments: Self {
308+
.init("insert attribute argument")
309+
}
310+
public static var joinIdentifiers: Self {
311+
.init("join the identifiers together")
312+
}
313+
public static var joinIdentifiersWithCamelCase: Self {
314+
.init("join the identifiers together with camel-case")
315+
}
316+
public static var removeOperatorBody: Self {
317+
.init("remove operator body")
318+
}
319+
public static var wrapKeywordInBackticks: Self {
320+
.init("if this name is unavoidable, use backticks to escape it")
236321
}
237322
}
238323

Tests/SwiftParserTest/DiagnosticInfrastructureTests.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,10 @@ public class DiagnosticInfrastructureTests: XCTestCase {
1515

1616
XCTAssertEqual(StaticParserError.throwsInReturnPosition.diagnosticID, MessageID(domain: "SwiftParser", id: "StaticParserError.throwsInReturnPosition"))
1717
XCTAssertEqual(StaticParserError.throwsInReturnPosition.severity, .error)
18+
19+
XCTAssertEqual(
20+
StaticParserFixIt.insertSemicolon.fixItID,
21+
MessageID(domain: "SwiftParser", id: "StaticParserFixIt.insertSemicolon")
22+
)
1823
}
1924
}

Tests/SwiftParserTest/Types.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,5 +354,13 @@ final class TypeParameterPackTests: XCTestCase {
354354
}
355355
""")
356356
}
357+
func testParameterPacks28() throws {
358+
// We allow whitespace between the generic parameter and the '...', this is
359+
// consistent with regular variadic parameters.
360+
AssertParse(
361+
"""
362+
func f1<T ...>(_ x: T ...) -> (T ...) {}
363+
""")
364+
}
357365
}
358366

0 commit comments

Comments
 (0)