Skip to content

Commit 02e7893

Browse files
committed
Add diagnostic for missing 0 in RawFloatLiteralExprSyntax
1 parent cef8815 commit 02e7893

File tree

5 files changed

+84
-4
lines changed

5 files changed

+84
-4
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,29 @@ extension Parser {
12631263
return RawExprSyntax(self.parseClosureExpression())
12641264
case (.period, let handle)?: // .foo
12651265
let dot = self.eat(handle)
1266+
1267+
// Special case ".<integer_literal>" like ".4". This isn't valid, but the
1268+
// developer almost certainly meant to use "0.4". Diagnose this, and
1269+
// recover as if they wrote that.
1270+
if let integerLiteral = self.consume(if: .integerLiteral) {
1271+
return RawExprSyntax(
1272+
RawFloatLiteralExprSyntax(
1273+
floatingDigits: RawTokenSyntax(
1274+
missing: .floatingLiteral,
1275+
arena: self.arena
1276+
),
1277+
RawUnexpectedNodesSyntax(
1278+
elements: [
1279+
RawSyntax(dot),
1280+
RawSyntax(integerLiteral),
1281+
],
1282+
arena: self.arena
1283+
),
1284+
arena: self.arena
1285+
)
1286+
)
1287+
}
1288+
12661289
let (name, args) = self.parseDeclNameRef([.keywords, .compoundNames])
12671290
return RawExprSyntax(
12681291
RawMemberAccessExprSyntax(

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,39 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
678678
return .visitChildren
679679
}
680680

681+
public override func visit(_ node: FloatLiteralExprSyntax) -> SyntaxVisitorContinueKind {
682+
if shouldSkip(node) {
683+
return .skipChildren
684+
}
685+
if node.floatingDigits.presence == .missing,
686+
let tokens = node.unexpectedAfterFloatingDigits?.onlyTokens(satisfying: { $0.tokenKind == .period || $0.tokenKind.isIntegerLiteral })
687+
{
688+
addDiagnostic(
689+
node,
690+
InvalidFloatLiteralMissingLeadingZero(integerLiteral: tokens[1]),
691+
fixIts: [
692+
FixIt(
693+
message: InsertFixIt(tokenToBeInserted: .integerLiteral("0")),
694+
changes: [
695+
FixIt.MultiNodeChange(
696+
FixIt.Change.replace(
697+
oldNode: Syntax(node.floatingDigits),
698+
newNode: Syntax(
699+
FloatLiteralExprSyntax(
700+
floatingDigits: .floatingLiteral("0.\(tokens[1].text)")
701+
)
702+
)
703+
)
704+
)
705+
] + tokens.map { FixIt.MultiNodeChange.makeMissing($0) }
706+
)
707+
],
708+
handledNodes: [node.floatingDigits.id] + tokens.map { $0.id }
709+
)
710+
}
711+
return .visitChildren
712+
}
713+
681714
public override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind {
682715
if shouldSkip(node) {
683716
return .skipChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,14 @@ public struct IdentifierNotAllowedInOperatorName: ParserError {
306306
}
307307
}
308308

309+
public struct InvalidFloatLiteralMissingLeadingZero: ParserError {
310+
public let integerLiteral: TokenSyntax
311+
312+
public var message: String {
313+
return "'.\(integerLiteral.text)' is not a valid floating point literal; it must be written '0.\(integerLiteral.text)'"
314+
}
315+
}
316+
309317
public struct InvalidIdentifierError: ParserError {
310318
public let invalidIdentifier: TokenSyntax
311319
public let missingIdentifier: TokenSyntax

Sources/SwiftParserDiagnostics/SyntaxExtensions.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,15 @@ extension TokenKind {
149149
return false
150150
}
151151
}
152+
153+
var isIntegerLiteral: Bool {
154+
switch self {
155+
case .integerLiteral:
156+
return true
157+
default:
158+
return false
159+
}
160+
}
152161
}
153162

154163
public extension TriviaPiece {

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,13 +1372,20 @@ final class RecoveryTests: XCTestCase {
13721372
assertParse(
13731373
"""
13741374
func exprPostfix2() {
1375-
_ = .1️⃣42
1375+
_ = 1️⃣.42
13761376
}
13771377
""",
13781378
diagnostics: [
1379-
// TODO: Old parser expected error on line 2: '.42' is not a valid floating point literal; it must be written '0.42', Fix-It replacements: 7 - 7 = '0'
1380-
DiagnosticSpec(message: "expected name in member access", fixIts: ["insert name"])
1381-
]
1379+
DiagnosticSpec(
1380+
message: "'.42' is not a valid floating point literal; it must be written '0.42'",
1381+
fixIts: ["insert '0'"]
1382+
)
1383+
],
1384+
fixedSource: """
1385+
func exprPostfix2() {
1386+
_ = 0.42
1387+
}
1388+
"""
13821389
)
13831390
}
13841391

0 commit comments

Comments
 (0)