From d0c01ca5f73951ce6d6e6979daf9df257de2292f Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Tue, 8 Apr 2025 08:47:27 -0700 Subject: [PATCH 1/2] Improve dentation at EOF Check trailing blank line at EOF for OUTDENT If EOF is preceded by only spaces on the last line, do not outdent because it will complain about alignment during an edit. Preserve EOF token when probing arrow EOL For REPL, `: x =>` at EOF sees an EOF in order to detect lambda eol. --- .../src/dotty/tools/dotc/parsing/Scanners.scala | 17 +++++++++++++++-- compiler/src/dotty/tools/dotc/util/Chars.scala | 2 +- .../dotty/tools/repl/ReplCompilerTests.scala | 10 ++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 96e9cd600363..1992ab32c9c6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -601,6 +601,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) @@ -613,9 +627,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 => diff --git a/compiler/src/dotty/tools/dotc/util/Chars.scala b/compiler/src/dotty/tools/dotc/util/Chars.scala index cde1a63f5293..4bdf6e464cd1 100644 --- a/compiler/src/dotty/tools/dotc/util/Chars.scala +++ b/compiler/src/dotty/tools/dotc/util/Chars.scala @@ -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? */ diff --git a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala index 9fe5ffcf072e..3d228e3721a2 100644 --- a/compiler/test/dotty/tools/repl/ReplCompilerTests.scala +++ b/compiler/test/dotty/tools/repl/ReplCompilerTests.scala @@ -400,6 +400,16 @@ class ReplCompilerTests extends ReplTest: assertTrue(all.head.startsWith("-- [E103] Syntax Error")) assertTrue(all.exists(_.trim().startsWith("| Illegal start of statement: this modifier is not allowed here"))) + @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"); From f143d1daacd14aad76dab9400062a0652edd356a Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Sun, 27 Apr 2025 23:06:26 +0200 Subject: [PATCH 2/2] Improve dentation at EOF Check trailing blank line at EOF for OUTDENT If EOF is preceded by only spaces on the last line, do not outdent because it will complain about alignment during an edit. Preserve EOF token when probing arrow EOL For REPL, `: x =>` at EOF sees an EOF in order to detect lambda eol. [Cherry-picked bc6cf47575469621bbee4e11dfd7d66544e6cfe8][modified]