Skip to content

Check trailing blank line at EOF for OUTDENT #22855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,20 @@ object Scanners {
lastWidth = r.knownWidth
newlineIsSeparating = r.isInstanceOf[InBraces]

// can emit OUTDENT if line is not non-empty blank line at EOF
inline def isTrailingBlankLine: Boolean =
token == EOF && {
val end = buf.length - 1 // take terminal NL as empty last line
val prev = buf.lastIndexWhere(!isWhitespace(_), end = end)
prev < 0 || end - prev > 0 && isLineBreakChar(buf(prev))
}

inline def canDedent: Boolean =
lastToken != INDENT
&& !isLeadingInfixOperator(nextWidth)
&& !statCtdTokens.contains(lastToken)
&& !isTrailingBlankLine

if newlineIsSeparating
&& canEndStatTokens.contains(lastToken)
&& canStartStatTokens.contains(token)
Expand All @@ -615,9 +629,8 @@ object Scanners {
|| nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then
if currentRegion.isOutermost then
if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth)
else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) && lastToken != INDENT then
else if canDedent then
currentRegion match
case _ if token == EOF => // no OUTDENT at EOF
case r: Indented =>
insert(OUTDENT, offset)
handleNewIndentWidth(r.enclosing, ir =>
Expand Down Expand Up @@ -671,13 +684,16 @@ object Scanners {
reset()
if atEOL then token = COLONeol

// consume => and insert <indent> if applicable
// consume => and insert <indent> if applicable. Used to detect colon arrow: x =>
def observeArrowIndented(): Unit =
if isArrow && indentSyntax then
peekAhead()
val atEOL = isAfterLineEnd || token == EOF
val atEOL = isAfterLineEnd
val atEOF = token == EOF
reset()
if atEOL then
if atEOF then
token = EOF
else if atEOL then
val nextWidth = indentWidth(next.offset)
val lastWidth = currentRegion.indentWidth
if lastWidth < nextWidth then
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/util/Chars.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object Chars:
}

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

/** Can character form part of a doc comment variable $xxx? */
Expand Down
10 changes: 10 additions & 0 deletions compiler/test/dotty/tools/repl/ReplCompilerTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,16 @@ class ReplCompilerTests extends ReplTest:
val all = lines()
assertTrue(hints.forall(hint => all.exists(_.contains(hint))))

@Test def `i22844 regression colon eol`: Unit = initially:
run:
"""|println:
| "hello, world"
|""".stripMargin // outdent, but this test does not exercise the bug
assertEquals(List("hello, world"), lines())

@Test def `i22844b regression colon arrow eol`: Unit = contextually:
assertTrue(ParseResult.isIncomplete("List(42).map: x =>"))

object ReplCompilerTests:

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