Skip to content

Commit 8732b28

Browse files
committed
Allow single-line lambdas after :
Previously, we need to indent after the error, e.g. ```scala xs.map: x => x + 1 ``` We now also allow to write the lambda on a single line: ```scala xs.map: x => x + 1 ``` The lambda extends to the end of the line.
1 parent 0a7f843 commit 8732b28

File tree

11 files changed

+178
-81
lines changed

11 files changed

+178
-81
lines changed

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

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,23 +1089,31 @@ object Parsers {
10891089
* something enclosed in (...) or [...], and this is followed by a `=>` or `?=>`
10901090
* and an INDENT.
10911091
*/
1092-
def followingIsLambdaAfterColon(): Boolean =
1092+
def followingIsLambdaAfterColon(): Option[() => Tree] =
10931093
val lookahead = in.LookaheadScanner(allowIndent = true)
10941094
.tap(_.currentRegion.knownWidth = in.currentRegion.indentWidth)
1095-
def isArrowIndent() =
1096-
lookahead.isArrow
1097-
&& {
1095+
def isArrowIndent(): Option[() => Tree] =
1096+
if lookahead.isArrow then
10981097
lookahead.observeArrowIndented()
1099-
lookahead.token == INDENT || lookahead.token == EOF
1100-
}
1098+
if lookahead.token == INDENT || lookahead.token == EOF then
1099+
Some(() => expr(Location.InColonArg))
1100+
else if !in.currentRegion.isInstanceOf[InParens] then
1101+
Some: () =>
1102+
val t = inSepRegion(SingleLineLambda(_)):
1103+
expr(Location.InColonArg)
1104+
accept(ENDLAMBDA)
1105+
t
1106+
else None
1107+
else None
11011108
lookahead.nextToken()
11021109
if lookahead.isIdent || lookahead.token == USCORE then
11031110
lookahead.nextToken()
11041111
isArrowIndent()
11051112
else if lookahead.token == LPAREN || lookahead.token == LBRACKET then
11061113
lookahead.skipParens()
11071114
isArrowIndent()
1108-
else false
1115+
else
1116+
None
11091117

11101118
/** Can the next lookahead token start an operand as defined by
11111119
* leadingOperandTokens, or is postfix ops enabled?
@@ -1174,8 +1182,10 @@ object Parsers {
11741182
* : (params) =>
11751183
* body
11761184
*/
1177-
def isColonLambda =
1178-
sourceVersion.enablesFewerBraces && in.token == COLONfollow && followingIsLambdaAfterColon()
1185+
def isColonLambda: Option[() => Tree] =
1186+
if sourceVersion.enablesFewerBraces && in.token == COLONfollow
1187+
then followingIsLambdaAfterColon()
1188+
else None
11791189

11801190
/** operand { infixop operand | MatchClause } [postfixop],
11811191
*
@@ -1199,17 +1209,19 @@ object Parsers {
11991209
opStack = OpInfo(top1, op, in.offset) :: opStack
12001210
colonAtEOLOpt()
12011211
newLineOptWhenFollowing(canStartOperand)
1202-
if isColonLambda then
1203-
in.nextToken()
1204-
recur(expr(Location.InColonArg))
1205-
else if maybePostfix && !canStartOperand(in.token) then
1206-
val topInfo = opStack.head
1207-
opStack = opStack.tail
1208-
val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType)
1209-
atSpan(startOffset(od), topInfo.offset) {
1210-
PostfixOp(od, topInfo.operator)
1211-
}
1212-
else recur(operand(location))
1212+
isColonLambda match
1213+
case Some(parseExpr) =>
1214+
in.nextToken()
1215+
recur(parseExpr())
1216+
case _ =>
1217+
if maybePostfix && !canStartOperand(in.token) then
1218+
val topInfo = opStack.head
1219+
opStack = opStack.tail
1220+
val od = reduceStack(base, topInfo.operand, 0, true, in.name, isType)
1221+
atSpan(startOffset(od), topInfo.offset) {
1222+
PostfixOp(od, topInfo.operator)
1223+
}
1224+
else recur(operand(location))
12131225
else
12141226
val t = reduceStack(base, top, minPrec, leftAssoc = true, in.name, isType)
12151227
if !isType && in.token == MATCH then recurAtMinPrec(matchClause(t))
@@ -2848,12 +2860,14 @@ object Parsers {
28482860
makeParameter(name.asTermName, typedOpt(), Modifiers(), isBackquoted = isBackquoted(id))
28492861
}
28502862
case _ => t
2851-
else if isColonLambda then
2852-
val app = atSpan(startOffset(t), in.skipToken()) {
2853-
Apply(t, expr(Location.InColonArg) :: Nil)
2854-
}
2855-
simpleExprRest(app, location, canApply = true)
2856-
else t
2863+
else isColonLambda match
2864+
case Some(parseExpr) =>
2865+
val app =
2866+
atSpan(startOffset(t), in.skipToken()):
2867+
Apply(t, parseExpr() :: Nil)
2868+
simpleExprRest(app, location, canApply = true)
2869+
case None =>
2870+
t
28572871
end simpleExprRest
28582872

28592873
/** SimpleExpr ::= ‘new’ ConstrApp {`with` ConstrApp} [TemplateBody]

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,9 @@ object Scanners {
617617
&& !statCtdTokens.contains(lastToken)
618618
&& !isTrailingBlankLine
619619

620-
if newlineIsSeparating
620+
if currentRegion.closedBy == ENDLAMBDA then
621+
insert(ENDLAMBDA, lineOffset)
622+
else if newlineIsSeparating
621623
&& canEndStatTokens.contains(lastToken)
622624
&& canStartStatTokens.contains(token)
623625
&& !isLeadingInfixOperator(nextWidth)
@@ -1599,6 +1601,8 @@ object Scanners {
15991601
* InParens a pair of parentheses (...) or brackets [...]
16001602
* InBraces a pair of braces { ... }
16011603
* Indented a pair of <indent> ... <outdent> tokens
1604+
* InCase a case of a match
1605+
* SingleLineLambda the rest of a line following a `:`
16021606
*/
16031607
abstract class Region(val closedBy: Token):
16041608

@@ -1667,6 +1671,7 @@ object Scanners {
16671671
case _: InBraces => "}"
16681672
case _: InCase => "=>"
16691673
case _: Indented => "UNDENT"
1674+
case _: SingleLineLambda => "end of single-line lambda"
16701675

16711676
/** Show open regions as list of lines with decreasing indentations */
16721677
def visualize: String =
@@ -1680,6 +1685,7 @@ object Scanners {
16801685
case class InParens(prefix: Token, outer: Region) extends Region(prefix + 1)
16811686
case class InBraces(outer: Region) extends Region(RBRACE)
16821687
case class InCase(outer: Region) extends Region(OUTDENT)
1688+
case class SingleLineLambda(outer: Region) extends Region(ENDLAMBDA)
16831689

16841690
/** A class describing an indentation region.
16851691
* @param width The principal indentation width

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,10 @@ object Tokens extends TokensCommon {
203203
// A `:` recognized as starting an indentation block
204204
inline val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type
205205

206+
inline val ENDLAMBDA = 99; enter(ENDLAMBDA, "end of single-line lambda")
207+
206208
/** XML mode */
207-
inline val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
209+
inline val XMLSTART = 100; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
208210

209211
final val alphaKeywords: TokenSet = tokenRange(IF, END)
210212
final val symbolicKeywords: TokenSet = tokenRange(USCORE, CTXARROW)
@@ -267,7 +269,7 @@ object Tokens extends TokensCommon {
267269
final val canStartStatTokens3: TokenSet = canStartExprTokens3 | mustStartStatTokens | BitSet(
268270
AT, CASE, END)
269271

270-
final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(TYPE, GIVEN, RPAREN, RBRACE, RBRACKET, OUTDENT)
272+
final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(TYPE, GIVEN, RPAREN, RBRACE, RBRACKET, OUTDENT, ENDLAMBDA)
271273

272274
/** Tokens that stop a lookahead scan search for a `<-`, `then`, or `do`.
273275
* Used for disambiguating between old and new syntax.

tests/neg/closure-args.check

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
-- [E040] Syntax Error: tests/neg/closure-args.scala:2:25 --------------------------------------------------------------
2+
2 |val x = List(1).map: (x: => Int) => // error
3+
| ^^
4+
| an identifier expected, but '=>' found
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
-- Error: tests/neg/closure-args.scala:14:0 ----------------------------------------------------------------------------
8+
14 | y => y > 0 // error // error
9+
|^
10+
|indented definitions expected, end of single-line lambda found
11+
-- [E103] Syntax Error: tests/neg/closure-args.scala:14:4 --------------------------------------------------------------
12+
14 | y => y > 0 // error // error
13+
| ^
14+
| Illegal start of toplevel definition
15+
|
16+
| longer explanation available when compiling with `-explain`
17+
-- [E018] Syntax Error: tests/neg/closure-args.scala:18:46 -------------------------------------------------------------
18+
18 |val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error
19+
| ^^^^
20+
| expression expected but case found
21+
|
22+
| longer explanation available when compiling with `-explain`
23+
-- [E008] Not Found Error: tests/neg/closure-args.scala:10:4 -----------------------------------------------------------
24+
8 |val b: Int = xs
25+
9 | .map: x => x
26+
10 | * x // error
27+
| ^
28+
| value * is not a member of List[Int].
29+
| Note that `*` is treated as an infix operator in Scala 3.
30+
| If you do not want that, insert a `;` or empty line in front
31+
| or drop any spaces behind the operator.
32+
-- [E006] Not Found Error: tests/neg/closure-args.scala:16:21 ----------------------------------------------------------
33+
16 |val c = List(xs.map: y => y + y) // error // error // error // error
34+
| ^
35+
| Not found: type y
36+
|
37+
| longer explanation available when compiling with `-explain`
38+
-- [E006] Not Found Error: tests/neg/closure-args.scala:16:28 ----------------------------------------------------------
39+
16 |val c = List(xs.map: y => y + y) // error // error // error // error
40+
| ^
41+
| Not found: type +
42+
|
43+
| longer explanation available when compiling with `-explain`
44+
-- [E006] Not Found Error: tests/neg/closure-args.scala:16:26 ----------------------------------------------------------
45+
16 |val c = List(xs.map: y => y + y) // error // error // error // error
46+
| ^
47+
| Not found: type y
48+
|
49+
| longer explanation available when compiling with `-explain`
50+
-- [E006] Not Found Error: tests/neg/closure-args.scala:16:30 ----------------------------------------------------------
51+
16 |val c = List(xs.map: y => y + y) // error // error // error // error
52+
| ^
53+
| Not found: type y
54+
|
55+
| longer explanation available when compiling with `-explain`

tests/neg/closure-args.scala

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
1-
import language.`3.3`
21

32
val x = List(1).map: (x: => Int) => // error
43
???
54
val z = List(1).map: + => // ok
65
???
76

87
val xs = List(1)
9-
val b: Int = xs // error
10-
.map: x => x * x // error
11-
.filter: y => y > 0 // error
12-
(0)
13-
val d = xs // error
8+
val b: Int = xs
9+
.map: x => x
10+
* x // error
11+
12+
val d = xs
1413
.map: x => x.toString + xs.dropWhile:
15-
y => y > 0
14+
y => y > 0 // error // error
1615

1716
val c = List(xs.map: y => y + y) // error // error // error // error
18-
val d2: String = xs // error
19-
.map: x => x.toString + xs.dropWhile: y => y > 0 // error // error
20-
.filter: z => !z.isEmpty // error
21-
(0)
2217

23-
val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error // error
18+
val fs: List[List[Int] => Int] = xs.map: x => case y :: ys => y case Nil => -1 // error

tests/neg/i22193.check

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- [E018] Syntax Error: tests/neg/i22193.scala:15:68 -------------------------------------------------------------------
2+
15 | arg2 = "the quick brown fox jumped over the lazy dog"): env => // error
3+
| ^
4+
| expression expected but end of single-line lambda found
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
-- Error: tests/neg/i22193.scala:22:2 ----------------------------------------------------------------------------------
8+
22 | env => // error indented definitions expected, identifier env found
9+
| ^^^
10+
| indented definitions expected, identifier env found
11+
-- Error: tests/neg/i22193.scala:31:2 ----------------------------------------------------------------------------------
12+
31 | val x = "Hello" // error
13+
| ^^^
14+
| indented definitions expected, val found
15+
-- [E006] Not Found Error: tests/neg/i22193.scala:16:10 ----------------------------------------------------------------
16+
16 | val x = env // error
17+
| ^^^
18+
| Not found: env
19+
|
20+
| longer explanation available when compiling with `-explain`
21+
-- [E178] Type Error: tests/neg/i22193.scala:28:2 ----------------------------------------------------------------------
22+
28 | fn3( // error missing argument list for value of type (=> Unit) => Unit
23+
| ^
24+
| missing argument list for value of type (=> Unit) => Unit
25+
29 | arg = "blue sleeps faster than tuesday",
26+
30 | arg2 = "the quick brown fox jumped over the lazy dog"):
27+
|
28+
| longer explanation available when compiling with `-explain`
29+
-- [E006] Not Found Error: tests/neg/i22193.scala:32:10 ----------------------------------------------------------------
30+
32 | println(x) // error
31+
| ^
32+
| Not found: x
33+
|
34+
| longer explanation available when compiling with `-explain`

tests/neg/i22193.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ def test1() =
1010
val x = env
1111
println(x)
1212

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

tests/neg/i22906.check

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Flag -indent set repeatedly
2-
-- Error: tests/neg/i22906.scala:6:15 ----------------------------------------------------------------------------------
3-
6 | {`1`: Int => 5} // error
4-
| ^
5-
| parentheses are required around the parameter of a lambda
6-
| This construct can be rewritten automatically under -rewrite -source 3.0-migration.
2+
-- [E040] Syntax Error: tests/neg/i22906.scala:6:20 --------------------------------------------------------------------
3+
6 | {`1`: Int => 5} // error // error
4+
| ^
5+
| end of single-line lambda expected, but '}' found
6+
-- [E006] Not Found Error: tests/neg/i22906.scala:6:5 ------------------------------------------------------------------
7+
6 | {`1`: Int => 5} // error // error
8+
| ^^^
9+
| Not found: 1
10+
|
11+
| longer explanation available when compiling with `-explain`

tests/neg/i22906.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
// does not reproduce under "vulpix" test rig, which enforces certain flag sets?
44

55
def program: Int => Int =
6-
{`1`: Int => 5} // error
6+
{`1`: Int => 5} // error // error

tests/pos/change-lambda.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
def foo(x: Any) = ???
2+
3+
def test(xs: List[Int]) =
4+
xs.map: x => x
5+
foo:
6+
xs.map: x => x + 1
7+

0 commit comments

Comments
 (0)