Skip to content

Commit c9bf402

Browse files
Backport "Check trailing blank line at EOF for OUTDENT" to 3.7.0 (#22942)
Backports #22855 to 3.7.0-RC2
2 parents 2c38464 + bee65d9 commit c9bf402

File tree

3 files changed

+32
-6
lines changed

3 files changed

+32
-6
lines changed

Diff for: compiler/src/dotty/tools/dotc/parsing/Scanners.scala

+21-5
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,20 @@ object Scanners {
603603
lastWidth = r.knownWidth
604604
newlineIsSeparating = r.isInstanceOf[InBraces]
605605

606+
// can emit OUTDENT if line is not non-empty blank line at EOF
607+
inline def isTrailingBlankLine: Boolean =
608+
token == EOF && {
609+
val end = buf.length - 1 // take terminal NL as empty last line
610+
val prev = buf.lastIndexWhere(!isWhitespace(_), end = end)
611+
prev < 0 || end - prev > 0 && isLineBreakChar(buf(prev))
612+
}
613+
614+
inline def canDedent: Boolean =
615+
lastToken != INDENT
616+
&& !isLeadingInfixOperator(nextWidth)
617+
&& !statCtdTokens.contains(lastToken)
618+
&& !isTrailingBlankLine
619+
606620
if newlineIsSeparating
607621
&& canEndStatTokens.contains(lastToken)
608622
&& canStartStatTokens.contains(token)
@@ -615,9 +629,8 @@ object Scanners {
615629
|| nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then
616630
if currentRegion.isOutermost then
617631
if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth)
618-
else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) && lastToken != INDENT then
632+
else if canDedent then
619633
currentRegion match
620-
case _ if token == EOF => // no OUTDENT at EOF
621634
case r: Indented =>
622635
insert(OUTDENT, offset)
623636
handleNewIndentWidth(r.enclosing, ir =>
@@ -671,13 +684,16 @@ object Scanners {
671684
reset()
672685
if atEOL then token = COLONeol
673686

674-
// consume => and insert <indent> if applicable
687+
// consume => and insert <indent> if applicable. Used to detect colon arrow: x =>
675688
def observeArrowIndented(): Unit =
676689
if isArrow && indentSyntax then
677690
peekAhead()
678-
val atEOL = isAfterLineEnd || token == EOF
691+
val atEOL = isAfterLineEnd
692+
val atEOF = token == EOF
679693
reset()
680-
if atEOL then
694+
if atEOF then
695+
token = EOF
696+
else if atEOL then
681697
val nextWidth = indentWidth(next.offset)
682698
val lastWidth = currentRegion.indentWidth
683699
if lastWidth < nextWidth then

Diff for: compiler/src/dotty/tools/dotc/util/Chars.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ object Chars:
5050
}
5151

5252
/** Is character a whitespace character (but not a new line)? */
53-
def isWhitespace(c: Char): Boolean =
53+
inline def isWhitespace(c: Char): Boolean =
5454
c == ' ' || c == '\t' || c == CR
5555

5656
/** Can character form part of a doc comment variable $xxx? */

Diff for: compiler/test/dotty/tools/repl/ReplCompilerTests.scala

+10
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,16 @@ class ReplCompilerTests extends ReplTest:
511511
val all = lines()
512512
assertTrue(hints.forall(hint => all.exists(_.contains(hint))))
513513

514+
@Test def `i22844 regression colon eol`: Unit = initially:
515+
run:
516+
"""|println:
517+
| "hello, world"
518+
|""".stripMargin // outdent, but this test does not exercise the bug
519+
assertEquals(List("hello, world"), lines())
520+
521+
@Test def `i22844b regression colon arrow eol`: Unit = contextually:
522+
assertTrue(ParseResult.isIncomplete("List(42).map: x =>"))
523+
514524
object ReplCompilerTests:
515525

516526
private val pattern = Pattern.compile("\\r[\\n]?|\\n");

0 commit comments

Comments
 (0)