8000 Correctly detect colon lambda eol indent for optional brace of argument by som-snytt · Pull Request #22477 · scala/scala3 · GitHub
[go: up one dir, main page]

Skip to content

Correctly detect colon lambda eol indent for optional brace of argument #22477

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
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Permit indent at width after colon arrow eol
  • Loading branch information
som-snytt committed Jan 29, 2025
commit fb9bc69ce7a159131afc38fa6eb59ea26ab4dc63
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,7 @@ object Parsers {
def isArrowIndent() =
lookahead.isArrow
&& {
lookahead.observeArrowEOL()
lookahead.nextToken()
lookahead.token == INDENT || lookahead.token == EOF
}
Expand Down Expand Up @@ -2654,10 +2655,14 @@ object Parsers {

def closureRest(start: Int, location: Location, params: List[Tree]): Tree =
atSpan(start, in.offset) {
if location == Location.InColonArg then
in.observeArrowEOL()
if in.token == CTXARROW then
if params.isEmpty then
syntaxError(em"context function literals require at least one formal parameter", Span(start, in.lastOffset))
in.nextToken()
else if in.token == ARROWeol then
in.nextToken()
else
accept(ARROW)
val body =
Expand Down
30 changes: 22 additions & 8 deletions compiler/src/dotty/tools/dotc/parsing/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object Scanners {
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1))

def isArrow =
token == ARROW || token == CTXARROW
token == ARROW || token == CTXARROW || token == ARROWeol
}

abstract class ScannerCommon(source: SourceFile)(using Context) extends CharArrayReader with TokenData {
Expand Down Expand Up @@ -612,7 +612,11 @@ object Scanners {
insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset)
else if indentIsSignificant then
if nextWidth < lastWidth
|| nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then
|| nextWidth == lastWidth
&& indentPrefix.match
case MATCH | CATCH => token != CASE
case _ => false
then
if currentRegion.isOutermost then
if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth)
else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) && lastToken != INDENT then
Expand All @@ -638,9 +642,13 @@ object Scanners {
insert(OUTDENT, offset)
else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then
report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos())

else if lastWidth < nextWidth
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then
|| lastWidth == nextWidth
&& lastToken.match
case MATCH | CATCH => token == CASE
case ARROWeol => true
case _ => false
then
if canStartIndentTokens.contains(lastToken) then
currentRegion = Indented(nextWidth, lastToken, currentRegion)
insert(INDENT, offset)
Expand All @@ -658,7 +666,7 @@ object Scanners {
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message =
em"""Incompatible combinations of tabs and spaces in indentation prefixes.
|Previous indent : $lastWidth
|Latest indent : $nextWidth"""
|Latest indent : $nextWidth"""

def observeColonEOL(inTemplate: Boolean): Unit =
val enabled =
Expand All @@ -672,6 +680,13 @@ object Scanners {
reset()
if atEOL then token = COLONeol

def observeArrowEOL(): Unit =
if indentSyntax && token == ARROW then
peekAhead()
val atEOL = isAfterLineEnd || token == EOF
reset()
if atEOL then token = ARROWeol

def observeIndented(): Unit =
if indentSyntax && isNewLine then
val nextWidth = indentWidth(next.offset)
Expand All @@ -680,7 +695,6 @@ object Scanners {
currentRegion = Indented(nextWidth, COLONeol, currentRegion)
offset = next.offset
token = INDENT
end observeIndented
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, I also don't like these end markers 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the method used to be longer and had a blank line. I must have deleted it during other edits while experimenting, but here it's no longer needed.


/** Insert an <outdent> token if next token closes an indentation region.
* Exception: continue if indentation region belongs to a `match` and next token is `case`.
Expand Down Expand Up @@ -1100,7 +1114,7 @@ object Scanners {
reset()
next

class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
override protected def initialCharBufferSize = 8
override def languageImportContext = Scanner.this.languageImportContext
}
Expand Down Expand Up @@ -1652,7 +1666,7 @@ object Scanners {
case class InCase(outer: Region) extends Region(OUTDENT)

/** A class describing an indentation region.
* @param width The principal indendation width
* @param width The principal indentation width
* @param prefix The token before the initial <indent> of the region
*/
case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT):
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,10 @@ object Tokens extends TokensCommon {
inline val COLONeol = 89; enter(COLONeol, ":", ": at eol")
// A `:` recognized as starting an indentation block
inline val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type
inline val ARROWeol = 99; enter(ARROWeol, "=>", "=> at eol") // lambda ARROW at eol followed by indent

/** XML mode */
inline val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
inline val XMLSTART = 100; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate

final val alphaKeywords: TokenSet = tokenRange(IF, END)
final val symbolicKeywords: TokenSet = tokenRange(USCORE, CTXARROW)
Expand Down Expand Up @@ -282,7 +283,7 @@ object Tokens extends TokensCommon {
final val closingRegionTokens = BitSet(RBRACE, RPAREN, RBRACKET, CASE) | statCtdTokens

final val canStartIndentTokens: BitSet =
statCtdTokens | BitSet(COLONeol, WITH, EQUALS, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW, RETURN)
statCtdTokens | BitSet(COLONeol, WITH, EQUALS, ARROWeol, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW, RETURN)

/** Faced with the choice between a type and a formal parameter, the following
* tokens determine it's a formal parameter.
Expand Down
6D47 44 changes: 44 additions & 0 deletions tests/neg/i22193.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg)

def fn3(arg: String, arg2: String)(f: => Unit): Unit = f

def test1() =

fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env
println(x)

fn2( // error not a legal formal parameter for a function literal
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env // error
println(x)

fn2( // error
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env // error
println(x)

fn2(
arg = "blue sleeps faster than tuesday",
< 9E81 /td> arg2 = "the quick brown fox jumped over the lazy dog"):
env => // error indented definitions expected, identifier env found
val x = env
println(x)

def test2() =

fn2(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"
): env =>
val x = env
println(x)

fn3( // error missing argument list for value of type (=> Unit) => Unit
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"):
val x = "Hello" // error
println(x) // error
57 changes: 57 additions & 0 deletions tests/pos/i22193.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg)

def fn3(arg: String, arg2: String)(f: => Unit): Unit = f

def test() =

fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env
println(x)

// doesn't compile
fn2(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env
println(x)

fn2(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
val x = env
println(x)

// does compile
fn2(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"):
env =>
val x = env
println(x)

// does compile
fn2(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"
): env =>
val x = env
println(x)

fn3(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"):
val x = "Hello"
println(x)

fn3(
arg = "blue sleeps faster than tuesday",
arg2 = "the quick brown fox jumped over the lazy dog"):
val x = "Hello"
println(x)

// don't turn innocent empty cases into functions
def regress(x: Int) =
x match
case 42 =>
case _ =>
0