Skip to content

Commit cc67eea

Browse files
committed
Always treat new-style fors as if enumerators are in braces
With the changes to infix operators this is now a pretty safe choice, and less surprising than the alternative.
1 parent f176385 commit cc67eea

File tree

3 files changed

+62
-26
lines changed

3 files changed

+62
-26
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,9 +2011,7 @@ object Parsers {
20112011
val leading = in.token
20122012
val enums =
20132013
if (leading == LBRACE || leading == LPAREN && parensEncloseGenerators) {
2014-
var enumsOnNewLine = in.isAfterLineEnd() // if true, enums start on new line after `for`, so newlines are significant under Pascal-style syntax
20152014
in.nextToken()
2016-
enumsOnNewLine |= in.isAfterLineEnd()
20172015
openParens.change(leading, 1)
20182016
val res =
20192017
if (leading == LBRACE || in.token == CASE)
@@ -2034,8 +2032,13 @@ object Parsers {
20342032
val closingOnNewLine = in.isAfterLineEnd()
20352033
accept(leading + 1)
20362034
openParens.change(leading, -1)
2037-
if (rewriteToNewSyntax(Span(start)) && enumsOnNewLine == (leading == LBRACE)) {
2038-
// Don't rewrite if that would change meaning of newlines
2035+
def hasMultiLineEnum =
2036+
res.exists { t =>
2037+
val pos = t.sourcePos
2038+
pos.startLine < pos.endLine
2039+
}
2040+
if (rewriteToNewSyntax(Span(start)) && (leading == LBRACE || !hasMultiLineEnum)) {
2041+
// Don't rewrite if that could change meaning of newlines
20392042
newLinesOpt()
20402043
dropParensOrBraces(start, if (in.token == YIELD || in.token == DO) "" else "do")
20412044
}
@@ -2044,14 +2047,11 @@ object Parsers {
20442047
}
20452048
else {
20462049
wrappedEnums = false
2047-
val impliedBraces = in.isAfterLineEnd()
20482050

20492051
/*if (in.token == INDENT) inBracesOrIndented(enumerators()) else*/
2050-
val ts = // If we use indent syntax, the distinction below should no longer be necessary
2051-
if (impliedBraces) enumerators()
2052-
else inSepRegion(LPAREN, RPAREN)(enumerators())
2052+
val ts = inSepRegion(LBRACE, RBRACE)(enumerators())
20532053
if (rewriteToOldSyntax(Span(start)) && ts.nonEmpty) {
2054-
if (impliedBraces) {
2054+
if (ts.length > 1 && ts.head.sourcePos.startLine != ts.last.sourcePos.startLine) {
20552055
patch(source, Span(forEnd), " {")
20562056
patch(source, Span(in.offset), "} ")
20572057
}

docs/docs/reference/changed-features/operators.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ layout: doc-page
33
title: Rules for Operators
44
---
55

6-
There are two annotations that regulate operators: `infix` and `alpha`.
6+
The rules for infix operators have changed. There are two annotations that regulate operators: `infix` and `alpha`.
7+
Furthermore, a syntax change allows infix operators to be written on the left in a multi-line expression.
78

89
## The @alpha Annotation
910

@@ -127,3 +128,53 @@ The purpose of the `@infix` annotation is to achieve consistency across a code b
127128

128129
5. To smooth migration to Scala 3.0, alphanumeric operations will only be deprecated from Scala 3.1 onwards,
129130
or if the `-strict` option is given in Dotty/Scala 3.
131+
132+
## Syntax Change
133+
134+
Infix operators can now appear at the start of lines in a multi-line expression. Examples:
135+
```scala
136+
val str = "hello"
137+
++ " world"
138+
++ "!"
139+
140+
def condition =
141+
x > 0
142+
|| xs.exists(_ > 0)
143+
|| xs.isEmpty
144+
```
145+
Previously, these expressions would have been rejected, since the compiler's semicolon inference
146+
would have treated the continuations `++ " world"` or `|| xs.isEmpty` as separate statements.
147+
148+
To make this syntax work, the rules are modified to not infer semicolons in front of leading infix operators.
149+
A _leading infix operator_ is
150+
- a symbolic identifier such as `+`, or `approx_==`, or an identifier in backticks,
151+
- that starts a new line,
152+
- that precedes a token on the same line that can start an expression,
153+
- and that is immediately followed by at least one space character `' '`.
154+
155+
Example:
156+
157+
```scala
158+
freezing
159+
| boiling
160+
```
161+
This is recognized as a single infix operation. Compare with:
162+
```scala
163+
freezing
164+
!boiling
165+
```
166+
This is seen as two statements, `freezing` and `!boiling`. The difference is that only the operator in the first example
167+
is followed by a space.
168+
169+
Another example:
170+
```scala
171+
println("hello")
172+
???
173+
??? match { case 0 => 1 }
174+
```
175+
This code is recognized as three different statements. `???` is syntactically a symbolic identifier, but
176+
neither of its occurrences is followed by a space and a token that can start an expression.
177+
178+
179+
180+

docs/docs/reference/other-new-features/control-syntax.md

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,7 @@ The rules in detail are:
3030
- Newline characters are not statement separators in a condition of an `if` or a `while`.
3131
So the meaning of newlines is the same no matter whether parentheses are present
3232
or absent.
33-
- Newline characters are statement separators in the enumerators of a `for`-expression if and only if the first generator of the `for` expression appears on a new line.
34-
35-
To illustrate the last rule above, compare
36-
```scala
37-
for x <- xs
38-
++ ys // newline not significant here
39-
++ zs // newline not significant here
40-
do println(x)
41-
```
42-
and
43-
```scala
44-
for
45-
x <- xs
46-
y <- xs // newline is significant here
47-
do println(x)
48-
```
33+
- Newline characters are potential statement separators in the enumerators of a `for`-expression.
4934

5035
### Rewrites
5136

0 commit comments

Comments
 (0)