Skip to content

Commit 8506bde

Browse files
committed
Improve syntax error recovery
- Skip less on recovery by handling OUTDENT tokens more dicriminately. - Adjust region stack after a skip.
1 parent ab03bd0 commit 8506bde

File tree

5 files changed

+54
-2
lines changed

5 files changed

+54
-2
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,24 @@ object Parsers {
254254
*/
255255
protected def skip(): Unit =
256256
val lastRegion = in.currentRegion
257+
var prevRegion = lastRegion
258+
// the region before the last token
259+
// we need to keep track of this since OUTDENT immediately pops
260+
// the region whereas other closing tokens do that only when the
261+
// next token is encountered.
257262
in.skipping = true
258263
def atStop =
259264
in.token == EOF
260265
|| skipStopTokens.contains(in.token) && (in.currentRegion eq lastRegion)
266+
|| in.token == OUTDENT && (prevRegion eq lastRegion) && in.indentWidth(in.offset) < in.lastKnownIndentWidth
267+
// stop at OUTDENT if (1) currentRegion is the one that was just closed by the OUTDENT
268+
// and (2) the new indentwidth is smaller than the indent width of currentRegion.
269+
// The second condition corrects for the problem that sometimes we don't see an INDENT
270+
// when skipping and therefore might erroneously end up syncing on a nested OUTDENT.
261271
while !atStop do
272+
prevRegion = in.currentRegion
262273
in.nextToken()
274+
in.recoverSepRegions()
263275
lastErrorOffset = in.offset
264276
in.skipping = false
265277

compiler/src/dotty/tools/dotc/parsing/Scanners.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,13 @@ object Scanners {
276276
/** The current region. This is initially an Indented region with zero indentation width. */
277277
var currentRegion: Region = Indented(IndentWidth.Zero, Set(), EMPTY, null)
278278

279+
def lastKnownIndentWidth: IndentWidth =
280+
def recur(r: Region | Null): IndentWidth =
281+
if r == null then IndentWidth.Zero
282+
else if r.knownWidth == null then recur(r.outer)
283+
else r.knownWidth
284+
recur(currentRegion)
285+
279286
// Get next token ------------------------------------------------------------
280287

281288
/** Are we directly in a multiline string interpolation expression?
@@ -334,6 +341,27 @@ object Scanners {
334341
case _ =>
335342
}
336343

344+
private def dropUnclosed(r: Region): Option[Region] =
345+
def matches = r match
346+
case InParens(LPAREN, _) => token == RPAREN
347+
case InParens(LBRACKET, _) => token == RBRACKET
348+
case _: InBraces => token == RBRACE
349+
case _: Indented => token == OUTDENT
350+
case _ => false
351+
r.knownWidth match
352+
case width: IndentWidth if matches =>
353+
if indentWidth(lineOffset) < width then
354+
dropUnclosed(r.outer).orElse(Some(r))
355+
else
356+
None
357+
case _ =>
358+
if r.outer == null then None else dropUnclosed(r.outer)
359+
360+
def recoverSepRegions(): Unit =
361+
if isAfterLineEnd then
362+
for r <- dropUnclosed(currentRegion) do
363+
currentRegion = r
364+
337365
/** Read a token or copy it from `next` tokenData */
338366
private def getNextToken(lastToken: Token): Unit =
339367
if next.token == EMPTY then

compiler/src/dotty/tools/dotc/parsing/Tokens.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ object Tokens extends TokensCommon {
287287

288288
final val endMarkerTokens = identifierTokens | BitSet(IF, WHILE, FOR, MATCH, TRY, NEW, THROW, GIVEN, VAL, THIS)
289289

290-
final val skipStopTokens = BitSet(SEMI, COMMA, NEWLINE, NEWLINES, RBRACE, RPAREN, RBRACKET, OUTDENT)
290+
final val skipStopTokens = BitSet(SEMI, COMMA, NEWLINE, NEWLINES, RBRACE, RPAREN, RBRACKET)
291291

292292
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
293293
}

tests/neg/i12605.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
object Foo:
22
def joe(): List[(Int, Int)] =
33
List((2, 3), (3, 4)).filter case (a, b) => b > a // error // error
4-
// error

tests/neg/syntax-error-recovery.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object Test {
2+
def foo(x: Int) = {
3+
if (x < 0 then // error
4+
1
5+
else
6+
2
7+
}
8+
println(,) // error // error // error
9+
println()
10+
}
11+
12+
13+

0 commit comments

Comments
 (0)