Skip to content

Commit 5d4028e

Browse files
committed
Permit indent at width after colon arrow eol
1 parent 9cb97ec commit 5d4028e

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
@@ -637,9 +641,13 @@ object Scanners {
637641
insert(OUTDENT, offset)
638642
else if r.isInstanceOf[InBraces] && !closingRegionTokens.contains(token) then
639643
report.warning("Line is indented too far to the left, or a `}` is missing", sourcePos())
640-
641644
else if lastWidth < nextWidth
642-
|| lastWidth == nextWidth && (lastToken == MATCH || lastToken == CATCH) && token == CASE then
645+
|| lastWidth == nextWidth
646+
&& lastToken.match
647+
case MATCH | CATCH => token == CASE
648+
case ARROWeol => true
649+
case _ => false
650+
then
643651
if canStartIndentTokens.contains(lastToken) then
644652
currentRegion = Indented(nextWidth, lastToken, currentRegion)
645653
insert(INDENT, offset)
@@ -657,7 +665,7 @@ object Scanners {
657665
def spaceTabMismatchMsg(lastWidth: IndentWidth, nextWidth: IndentWidth): Message =
658666
em"""Incompatible combinations of tabs and spaces in indentation prefixes.
659667
|Previous indent : $lastWidth
660-
|Latest indent : $nextWidth"""
668+
|Latest indent : $nextWidth"""
661669

662670
def observeColonEOL(inTemplate: Boolean): Unit =
663671
val enabled =
@@ -671,6 +679,13 @@ object Scanners {
671679
reset()
672680
if atEOL then token = COLONeol
673681

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

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

1102-
class LookaheadScanner(val allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
1116+
class LookaheadScanner(allowIndent: Boolean = false) extends Scanner(source, offset, allowIndent = allowIndent) {
11031117
override protected def initialCharBufferSize = 8
11041118
override def languageImportContext = Scanner.this.languageImportContext
11051119
}
@@ -1651,7 +1665,7 @@ object Scanners {
16511665
case class InCase(outer: Region) extends Region(OUTDENT)
16521666

16531667
/** A class describing an indentation region.
1654-
* @param width The principal indendation width
1668+
* @param width The principal indentation width
16551669
* @param prefix The token before the initial <indent> of the region
16561670
*/
16571671
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)