Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 29b3909

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 ebe241e commit 29b3909

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)

AltStyle によって変換されたページ (->オリジナル) /