Skip to content

Commit c149eeb

Browse files
authored
Merge pull request #10861 from dotty-staging/with-templates
Parsing indentation cleanup
2 parents eb44b34 + 625f754 commit c149eeb

File tree

7 files changed

+104
-60
lines changed

7 files changed

+104
-60
lines changed

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,7 @@ object Parsers {
12831283

12841284
def possibleTemplateStart(isNew: Boolean = false): Unit =
12851285
in.observeColonEOL()
1286-
if in.token == COLONEOL || in.token == WITHEOL then
1286+
if in.token == COLONEOL || in.token == WITH then
12871287
if in.lookahead.isIdent(nme.end) then in.token = NEWLINE
12881288
else
12891289
in.nextToken()
@@ -3545,7 +3545,7 @@ object Parsers {
35453545
ValDef(name, parents.head, subExpr())
35463546
else
35473547
DefDef(name, tparams, vparamss, parents.head, subExpr())
3548-
else if in.token != WITH && in.token != WITHEOL && parentsIsType then
3548+
else if in.token != WITH && parentsIsType then
35493549
if name.isEmpty then
35503550
syntaxError(em"anonymous given cannot be abstract")
35513551
DefDef(name, tparams, vparamss, parents.head, EmptyTree)
@@ -3633,12 +3633,12 @@ object Parsers {
36333633
/** `{`with` ConstrApp} but no EOL allowed after `with`.
36343634
*/
36353635
def withConstrApps(): List[Tree] =
3636-
if in.token == WITH then
3637-
in.observeWithEOL() // converts token to WITHEOL if at end of line
3638-
if in.token == WITH && in.lookahead.token != LBRACE then
3639-
in.nextToken()
3640-
constrApp() :: withConstrApps()
3641-
else Nil
3636+
def isTemplateStart =
3637+
val la = in.lookahead
3638+
la.isAfterLineEnd || la.token == LBRACE
3639+
if in.token == WITH && !isTemplateStart then
3640+
in.nextToken()
3641+
constrApp() :: withConstrApps()
36423642
else Nil
36433643

36443644
/** Template ::= InheritClauses [TemplateBody]
@@ -3708,8 +3708,8 @@ object Parsers {
37083708

37093709
/** with Template, with EOL <indent> interpreted */
37103710
def withTemplate(constr: DefDef, parents: List[Tree]): Template =
3711-
if in.token != WITHEOL then accept(WITH)
3712-
possibleTemplateStart() // consumes a WITHEOL token
3711+
if in.token != WITH then syntaxError(em"`with` expected")
3712+
possibleTemplateStart() // consumes a WITH token
37133713
val (self, stats) = templateBody()
37143714
Template(constr, parents, Nil, self, stats)
37153715
.withSpan(Span(constr.span.orElse(parents.head.span).start, in.lastOffset))

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ object Scanners {
7474

7575
def isNestedStart = token == LBRACE || token == INDENT
7676
def isNestedEnd = token == RBRACE || token == OUTDENT
77+
78+
/** Is current token first one after a newline? */
79+
def isAfterLineEnd: Boolean = lineOffset >= 0
7780
}
7881

7982
abstract class ScannerCommon(source: SourceFile)(using Context) extends CharArrayReader with TokenData {
@@ -512,15 +515,12 @@ object Scanners {
512515
|Previous indent : $lastWidth
513516
|Latest indent : $nextWidth"""
514517

515-
private def switchAtEOL(testToken: Token, eolToken: Token): Unit =
516-
if token == testToken then
518+
def observeColonEOL(): Unit =
519+
if token == COLON then
517520
lookAhead()
518521
val atEOL = isAfterLineEnd || token == EOF
519522
reset()
520-
if atEOL then token = eolToken
521-
522-
def observeColonEOL(): Unit = switchAtEOL(COLON, COLONEOL)
523-
def observeWithEOL(): Unit = switchAtEOL(WITH, WITHEOL)
523+
if atEOL then token = COLONEOL
524524

525525
def observeIndented(): Unit =
526526
if indentSyntax && isNewLine then
@@ -610,9 +610,6 @@ object Scanners {
610610
}
611611
}
612612

613-
/** Is current token first one after a newline? */
614-
def isAfterLineEnd: Boolean = lineOffset >= 0
615-
616613
/** Is there a blank line between the current token and the last one?
617614
* A blank line consists only of characters <= ' '.
618615
* @pre afterLineEnd().

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ object Tokens extends TokensCommon {
204204
final val QUOTE = 87; enter(QUOTE, "'")
205205

206206
final val COLONEOL = 88; enter(COLONEOL, ":", ": at eol")
207-
final val WITHEOL = 89; enter(WITHEOL, "with", "with at eol")
208-
final val SELFARROW = 90; enter(SELFARROW, "=>") // reclassified ARROW following self-type
207+
final val SELFARROW = 89; enter(SELFARROW, "=>") // reclassified ARROW following self-type
209208

210209
/** XML mode */
211210
final val XMLSTART = 99; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate
@@ -277,8 +276,7 @@ object Tokens extends TokensCommon {
277276
final val closingRegionTokens = BitSet(RBRACE, RPAREN, RBRACKET, CASE) | statCtdTokens
278277

279278
final val canStartIndentTokens: BitSet =
280-
statCtdTokens | BitSet(COLONEOL, WITHEOL, EQUALS, ARROW, LARROW, WHILE, TRY, FOR, IF)
281-
// `if` is excluded because it often comes after `else` which makes for awkward indentation rules TODO: try to do without the exception
279+
statCtdTokens | BitSet(COLONEOL, WITH, EQUALS, ARROW, CTXARROW, LARROW, WHILE, TRY, FOR, IF, THROW)
282280

283281
/** Faced with the choice between a type and a formal parameter, the following
284282
* tokens determine it's a formal parameter.
@@ -287,7 +285,7 @@ object Tokens extends TokensCommon {
287285

288286
final val scala3keywords = BitSet(ENUM, ERASED, GIVEN)
289287

290-
final val endMarkerTokens = identifierTokens | BitSet(IF, WHILE, FOR, MATCH, TRY, NEW, GIVEN, VAL, THIS)
288+
final val endMarkerTokens = identifierTokens | BitSet(IF, WHILE, FOR, MATCH, TRY, NEW, THROW, GIVEN, VAL, THIS)
291289

292290
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent, nme.infix)
293291
}

docs/docs/reference/changed-features/match-syntax.md

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,41 @@ title: Match Expressions
66
The syntactical precedence of match expressions has been changed.
77
`match` is still a keyword, but it is used like an alphabetical operator. This has several consequences:
88

9-
1. `match` expressions can be chained:
10-
11-
```scala
12-
xs match {
13-
case Nil => "empty"
14-
case x :: xs1 => "nonempty"
15-
} match {
16-
case "empty" => 0
17-
case "nonempty" => 1
18-
}
19-
```
20-
21-
(or, dropping the optional braces)
22-
23-
```scala
24-
xs match
25-
case Nil => "empty"
26-
case x :: xs1 => "nonempty"
27-
match
28-
case "empty" => 0
29-
case "nonempty" => 1
30-
```
31-
32-
2. `match` may follow a period:
9+
1. `match` expressions can be chained:
3310

3411
```scala
35-
if xs.match
36-
case Nil => false
37-
case _ => true
38-
then "nonempty"
39-
else "empty"
12+
xs match {
13+
case Nil => "empty"
14+
case x :: xs1 => "nonempty"
15+
} match {
16+
case "empty" => 0
17+
case "nonempty" => 1
18+
}
4019
```
4120

42-
3. The scrutinee of a match expression must be an `InfixExpr`. Previously the scrutinee could be
43-
followed by a type ascription `: T`, but this is no longer supported. So `x : T match { ... }`
44-
now has to be written `(x: T) match { ... }`.
21+
(or, dropping the optional braces)
22+
23+
```scala
24+
xs match
25+
case Nil => "empty"
26+
case x :: xs1 => "nonempty"
27+
match
28+
case "empty" => 0
29+
case "nonempty" => 1
30+
```
31+
32+
2. `match` may follow a period:
33+
34+
```scala
35+
if xs.match
36+
case Nil => false
37+
case _ => true
38+
then "nonempty"
39+
else "empty"
40+
```
41+
42+
3. The scrutinee of a match expression must be an `InfixExpr`. Previously the scrutinee could be followed by a type ascription `: T`, but this is no longer supported. So `x : T match { ... }` now has to be
43+
written `(x: T) match { ... }`.
4544

4645
## Syntax
4746

docs/docs/reference/other-new-features/indentation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ There are two rules:
6464
- after one of the following tokens:
6565

6666
```
67-
= => <- catch do else finally for
68-
if match return then try while yield
67+
= => ?=> <- catch do else finally for
68+
if match return then throw try while yield
6969
```
7070

7171
If an `<indent>` is inserted, the indentation width of the token on the next line

docs/docs/reference/other-new-features/matchable.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,45 @@ class Object extends Any, Matchable
9494

9595
`Matchable` is currently a marker trait without any methods. Over time
9696
we might migrate methods `getClass` and `isInstanceOf` to it, since these are closely related to pattern-matching.
97+
98+
### `Matchable` and Universal Equality
99+
100+
Methods that pattern-match on selectors of type `Any` will need a cast once the
101+
Matchable warning is turned on. The most common such method is the universal
102+
`equals` method. It will have to be written as in the following example:
103+
104+
```scala
105+
class C(val x: String):
106+
107+
override def equals(that: Any): Boolean =
108+
that.asInstanceOf[Matchable] match
109+
case that: C => this.x == that.x
110+
case _ => false
111+
```
112+
The cast of `that` to `Matchable` serves as an indication that universal equality
113+
is unsafe in the presence of abstract types and opaque types since it cannot properly distinguish the meaning of a type from its representation. The cast
114+
is guaranteed to succeed at run-time since `Any` and `Matchable` both erase to
115+
`Object`.
116+
117+
For instance, consider the definitions
118+
```scala
119+
opaque type Meter = Double
120+
def Meter(x: Double) = x
121+
122+
opaque type Second = Double
123+
def Second(x: Double) = x
124+
```
125+
Here, universal `equals` will return true for
126+
```scala
127+
Meter(10).equals(Second(10))
128+
```
129+
even though this is clearly false mathematically. With [multiversal equality](../contextual/multiversal-equality.html) one can mitigate that problem somewhat by turning
130+
```scala
131+
Meter(10) == Second(10)
132+
```
133+
into a type error.
134+
135+
136+
137+
138+

tests/pos/indent.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ object Test:
5151
y * y
5252
}
5353

54+
val gg = (x: Int) ?=>
55+
val y = x + 1
56+
y
57+
5458
xs.map {
5559
x =>
5660
val y = x * x
@@ -80,11 +84,15 @@ object Test:
8084

8185
class Test2:
8286
self =>
83-
def foo = 1
87+
def foo(x: Int) =
88+
if x < 0 then throw
89+
val ex = new AssertionError()
90+
ex
91+
x
8492

8593
val x =
8694
new Test2 {
87-
override def foo = 2
95+
override def foo(x: Int) = 2
8896
}
8997
end x
9098
end Test2

0 commit comments

Comments
 (0)