Skip to content

Commit a92e8e0

Browse files
committed
Produce meaningful diagnostic for "#elif" typo
1 parent 5594b98 commit a92e8e0

File tree

3 files changed

+90
-15
lines changed

3 files changed

+90
-15
lines changed

Sources/SwiftParser/Directives.swift

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@ extension Parser {
1616
private enum IfConfigContinuationClauseStartKeyword: TokenSpecSet {
1717
case poundElseifKeyword
1818
case poundElseKeyword
19+
case pound
1920

2021
var spec: TokenSpec {
2122
switch self {
2223
case .poundElseifKeyword: return .poundElseifKeyword
2324
case .poundElseKeyword: return .poundElseKeyword
25+
case .pound: return TokenSpec(.pound, recoveryPrecedence: .openingPoundIf)
2426
}
2527
}
2628

2729
init?(lexeme: Lexer.Lexeme) {
2830
switch PrepareForKeywordMatch(lexeme) {
2931
case TokenSpec(.poundElseifKeyword): self = .poundElseifKeyword
3032
case TokenSpec(.poundElseKeyword): self = .poundElseKeyword
33+
case TokenSpec(.pound): self = .pound
3134
default: return nil
3235
}
3336
}
@@ -106,29 +109,54 @@ extension Parser {
106109
while let poundIfHandle = firstIteration ? self.canRecoverTo(.poundIfKeyword) : self.canRecoverTo(anyIn: IfConfigContinuationClauseStartKeyword.self)?.handle,
107110
loopProgress.evaluate(self.currentToken)
108111
{
109-
var (unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle)
110-
firstIteration = false
111-
// Parse the condition.
112+
var unexpectedBeforePoundIf: RawUnexpectedNodesSyntax?
113+
var poundIf: Parser.Token
112114
let condition: RawExprSyntax?
113-
switch poundIf.tokenKind {
114-
case .poundIfKeyword, .poundElseifKeyword:
115+
var atElifTypo: Bool {
116+
guard self.at(.pound) else {
117+
return false
118+
}
119+
let identifierSpec = TokenSpec(.identifier, allowAtStartOfLine: false)
120+
var lookahead = self.lookahead()
121+
lookahead.consumeAnyToken()
122+
guard lookahead.at(identifierSpec), lookahead.currentToken.tokenText == "elif" else {
123+
return false
124+
}
125+
lookahead.consumeAnyToken()
126+
return lookahead.at(identifierSpec)
127+
}
128+
129+
if atElifTypo {
130+
(unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle)
131+
guard let identifier = self.consume(if: TokenSpec(.identifier, allowAtStartOfLine: false)) else {
132+
preconditionFailure("The current token should be an identifier, guaranteed by the previous if statement")
133+
}
134+
unexpectedBeforePoundIf = RawUnexpectedNodesSyntax(combining: unexpectedBeforePoundIf, poundIf, identifier, arena: self.arena)
135+
poundIf = self.missingToken(.poundElseifKeyword)
115136
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
116-
case .poundElseKeyword:
117-
if let ifToken = self.consume(if: .init(.if, allowAtStartOfLine: false)) {
118-
unexpectedBeforePoundIf = RawUnexpectedNodesSyntax(combining: unexpectedBeforePoundIf, poundIf, ifToken, arena: self.arena)
119-
poundIf = self.missingToken(.poundElseifKeyword)
137+
} else {
138+
(unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle)
139+
firstIteration = false
140+
switch poundIf.tokenKind {
141+
case .poundIfKeyword, .poundElseifKeyword:
120142
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
121-
} else {
122-
condition = nil
143+
case .poundElseKeyword:
144+
if let ifToken = self.consume(if: .init(.if, allowAtStartOfLine: false)) {
145+
unexpectedBeforePoundIf = RawUnexpectedNodesSyntax(combining: unexpectedBeforePoundIf, poundIf, ifToken, arena: self.arena)
146+
poundIf = self.missingToken(.poundElseifKeyword)
147+
condition = RawExprSyntax(self.parseSequenceExpression(.basic, forDirective: true))
148+
} else {
149+
condition = nil
150+
}
151+
default:
152+
preconditionFailure("The loop condition should guarantee that we are at one of these tokens")
123153
}
124-
default:
125-
preconditionFailure("The loop condition should guarantee that we are at one of these tokens")
126154
}
127155

128156
var elements = [Element]()
129157
do {
130158
var elementsProgress = LoopProgressCondition()
131-
while !self.at(.eof) && !self.at(.poundElseKeyword, .poundElseifKeyword, .poundEndifKeyword) && elementsProgress.evaluate(currentToken) {
159+
while !self.at(.eof) && !self.at(.poundElseKeyword, .poundElseifKeyword, .poundEndifKeyword) && !atElifTypo && elementsProgress.evaluate(currentToken) {
132160
let newItemAtStartOfLine = self.currentToken.isAtStartOfLine
133161
guard let element = parseElement(&self, elements.isEmpty), !element.isEmpty else {
134162
break

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,18 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
678678
unexpectedBeforePoundKeyword
679679
.suffix(2)
680680
.compactMap { $0.as(TokenSyntax.self) }
681+
var diagnosticMessage: DiagnosticMessage?
682+
681683
if unexpectedTokens.map(\.tokenKind) == [.poundElseKeyword, .keyword(.if)] {
684+
diagnosticMessage = StaticParserError.unexpectedPoundElseSpaceIf
685+
} else if unexpectedTokens.first?.tokenKind == .pound, unexpectedTokens.last?.text == "elif" {
686+
diagnosticMessage = UnknownDirectiveError(unexpected: unexpectedBeforePoundKeyword)
687+
}
688+
689+
if let diagnosticMessage = diagnosticMessage {
682690
addDiagnostic(
683691
unexpectedBeforePoundKeyword,
684-
StaticParserError.unexpectedPoundElseSpaceIf,
692+
diagnosticMessage,
685693
fixIts: [
686694
FixIt(
687695
message: ReplaceTokensFixIt(replaceTokens: unexpectedTokens, replacement: clause.poundKeyword),

Tests/SwiftParserTest/translated/IfconfigExprTests.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,45 @@ final class IfconfigExprTests: XCTestCase {
455455
)
456456
}
457457

458+
func testIfConfigExpr32() {
459+
AssertParse(
460+
"""
461+
#if arch(x86_64)
462+
debugPrint("x86_64")
463+
1️⃣#elif arch(arm64)
464+
debugPrint("arm64")
465+
#else
466+
debugPrint("Some other architecture.")
467+
#endif
468+
""",
469+
diagnostics: [
470+
DiagnosticSpec(
471+
message: "use of unknown directive '#elif'",
472+
fixIts: ["replace '#elif' with '#elseif'"]
473+
)
474+
],
475+
fixedSource: """
476+
#if arch(x86_64)
477+
debugPrint("x86_64")
478+
#elseif arch(arm64)
479+
debugPrint("arm64")
480+
#else
481+
debugPrint("Some other architecture.")
482+
#endif
483+
"""
484+
)
485+
}
486+
487+
func testIfConfigExpr33() {
488+
AssertParse(
489+
"""
490+
#if arch(x86_64)
491+
#line
492+
#endif
493+
"""
494+
)
495+
}
496+
458497
func testUnknownPlatform1() {
459498
AssertParse(
460499
"""

0 commit comments

Comments
 (0)