Skip to content

Commit b937e35

Browse files
committed
Permit indent at width after colon arrow eol
1 parent 8d8b5b9 commit b937e35

File tree

5 files changed

+131
-10
lines changed

5 files changed

+131
-10
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,7 @@ object Parsers {
10901090
def isArrowIndent() =
10911091
lookahead.isArrow
10921092
&& {
1093+
lookahead.observeArrowEOL()
10931094
lookahead.nextToken()
10941095
lookahead.token == INDENT || lookahead.token == EOF
10951096
}
@@ -2654,10 +2655,14 @@ object Parsers {
26542655

26552656
def closureRest(start: Int, location: Location, params: List[Tree]): Tree =
26562657
atSpan(start, in.offset) {
2658+
if location == Location.InColonArg then
2659+
in.observeArrowEOL()
26572660
if in.token == CTXARROW then
26582661
if params.isEmpty then
26592662
syntaxError(em"context function literals require at least one formal parameter", Span(start, in.lastOffset))
26602663
in.nextToken()
2664+
else if in.token == ARROWeol then
2665+
in.nextToken()
26612666
else
26622667
accept(ARROW)
26632668
val body =

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ object Scanners {
9292
|| token == IDENTIFIER && isOperatorPart(name(name.length - 1))
9393

9494
def isArrow =
95-
token == ARROW || token == CTXARROW
95+
token == ARROW || token == CTXARROW || token == ARROWeol
9696
}
9797

9898
abstract class ScannerCommon(source: SourceFile)(using Context) extends CharArrayReader with TokenData {
@@ -612,7 +612,11 @@ object Scanners {
612612
insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset)
613613
else if indentIsSignificant then
614614
if nextWidth < lastWidth
615-
|| nextWidth == lastWidth && (indentPrefix == MATCH || indentPrefix == CATCH) && token != CASE then
615+
|| nextWidth == lastWidth
616+
&& indentPrefix.match
617+
case MATCH | CATCH => token != CASE
618+
case _ => false
619+
then
616620
if currentRegion.isOutermost then
617621
if nextWidth < lastWidth then currentRegion = topLevelRegion(nextWidth)
618622
else if !isLeadingInfixOperator(nextWidth) && !statCtdTokens.contains(lastToken) && lastToken != INDENT then
@@ -638,9 +642,13 @@ object Scanners {
638642
insert(OUTDENT, offset)
639643
else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then
640644
report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos())
641-
642645
else if lastWidth < nextWidth
643-
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then
646+
|| lastWidth == nextWidth
647+
&& lastToken.match
648+
case MATCH | CATCH => token == CASE
649+
case ARROWeol => true
650+
case _ => false
651+
then
644652
if canStartIndentTokens.contains(lastToken) then
645653
currentRegion = Indented(nextWidth, lastToken, currentRegion)
646654
insert(INDENT, offset)
@@ -658,7 +666,7 @@ object Scanners {
658666
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message =
659667
em"""Incompatible combinations of tabs and spaces in indentation prefixes.
660668
|Previous indent : $lastWidth
661-
|Latest indent : $nextWidth"""
669+
|Latest indent : $nextWidth"""
662670

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

683+
def observeArrowEOL(): Unit =
684+
if indentSyntax && token == ARROW then
685+
peekAhead()
686+
val atEOL = isAfterLineEnd || token == EOF
687+
reset()
688+
if atEOL then token = ARROWeol
689+
675690
def observeIndented(): Unit =
676691
if indentSyntax && isNewLine then
677692
val nextWidth = indentWidth(next.offset)
@@ -680,7 +695,6 @@ object Scanners {
680695
currentRegion = Indented(nextWidth, COLONeol, currentRegion)
681696
offset = next.offset
682697
token = INDENT
683-
end observeIndented
684698

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

1103-
class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
1117+
class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
11041118
override protected def initialCharBufferSize = 8
11051119
override def languageImportContext = Scanner.this.languageImportContext
11061120
}
@@ -1652,7 +1666,7 @@ object Scanners {
16521666
case class InCase(outer: Region) extends Region(OUTDENT)
16531667

16541668
/** A class describing an indentation region.
1655-
* @param width The principal indendation width
1669+
* @param width The principal indentation width
16561670
* @param prefix The token before the initial <indent> of the region
16571671
*/
16581672
case class Indented(width: IndentWidth, prefix: Token, outer: Region | Null) extends Region(OUTDENT):

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,10 @@ object Tokens extends TokensCommon {
202202
inline val COLONeol = 89; enter(COLONeol, ":", ": at eol")
203203
// A `:` recognized as starting an indentation block
204204
inline val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type
205+
inline val ARROWeol = 99; enter(ARROWeol, "=>", "=> at eol") // lambda ARROW at eol followed by indent
205206

206207
/** XML mode */
207-
inline val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
208+
inline val XMLSTART = 100; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
208209

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

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

287288
/** Faced with the choice between a type and a formal parameter, the following
288289
* tokens determine it's a formal parameter.

tests/neg/i22193.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg)
3+
4+
def fn3(arg: String, arg2: String)(f: => Unit): Unit = f
5+
6+
def test1() =
7+
8+
fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env =>
9+
val x = env
10+
println(x)
11+
12+
fn2( // error not a legal formal parameter for a function literal
13+
arg = "blue sleeps faster than tuesday",
14+
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
15+
val x = env // error
16+
println(x)
17+
18+
fn2( // error
19+
arg = "blue sleeps faster than tuesday",
20+
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
21+
val x = env // error
22+
println(x)
23+
24+
fn2(
25+
arg = "blue sleeps faster than tuesday",
26+
arg2 = "the quick brown fox jumped over the lazy dog"):
27+
env => // error indented definitions expected, identifier env found
28+
val x = env
29+
println(x)
30+
31+
def test2() =
32+
33+
fn2(
34+
arg = "blue sleeps faster than tuesday",
35+
arg2 = "the quick brown fox jumped over the lazy dog"
36+
): env =>
37+
val x = env
38+
println(x)
39+
40+
fn3( // error missing argument list for value of type (=> Unit) => Unit
41+
arg = "blue sleeps faster than tuesday",
42+
arg2 = "the quick brown fox jumped over the lazy dog"):
43+
val x = "Hello" // error
44+
println(x) // error

tests/pos/i22193.scala

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
2+
def fn2(arg: String, arg2: String)(f: String => Unit): Unit = f(arg)
3+
4+
def fn3(arg: String, arg2: String)(f: => Unit): Unit = f
5+
6+
def test() =
7+
8+
fn2(arg = "blue sleeps faster than tuesday", arg2 = "the quick brown fox jumped over the lazy dog"): env =>
9+
val x = env
10+
println(x)
11+
12+
// doesn't compile
13+
fn2(
14+
arg = "blue sleeps faster than tuesday",
15+
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
16+
val x = env
17+
println(x)
18+
19+
fn2(
20+
arg = "blue sleeps faster than tuesday",
21+
arg2 = "the quick brown fox jumped over the lazy dog"): env =>
22+
val x = env
23+
println(x)
24+
25+
// does compile
26+
fn2(
27+
arg = "blue sleeps faster than tuesday",
28+
arg2 = "the quick brown fox jumped over the lazy dog"):
29+
env =>
30+
val x = env
31+
println(x)
32+
33+
// does compile
34+
fn2(
35+
arg = "blue sleeps faster than tuesday",
36+
arg2 = "the quick brown fox jumped over the lazy dog"
37+
): env =>
38+
val x = env
39+
println(x)
40+
41+
fn3(
42+
arg = "blue sleeps faster than tuesday",
43+
arg2 = "the quick brown fox jumped over the lazy dog"):
44+
val x = "Hello"
45+
println(x)
46+
47+
fn3(
48+
arg = "blue sleeps faster than tuesday",
49+
arg2 = "the quick brown fox jumped over the lazy dog"):
50+
val x = "Hello"
51+
println(x)
52+
53+
// don't turn innocent empty cases into functions
54+
def regress(x: Int) =
55+
x match
56+
case 42 =>
57+
case _ =>

0 commit comments

Comments
 (0)