Skip to content

Commit e357422

Browse files
committed
Enforce stricter pattern binding rules by default
1 parent cd7f528 commit e357422

File tree

12 files changed

+64
-180
lines changed

12 files changed

+64
-180
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,10 +1579,10 @@ object desugar {
15791579
case _ => false
15801580

15811581
def needsNoFilter(gen: GenFrom): Boolean =
1582-
if (gen.checkMode == GenCheckMode.FilterAlways) // pattern was prefixed by `case`
1582+
if gen.checkMode == GenCheckMode.FilterAlways then // pattern was prefixed by `case`
15831583
false
15841584
else
1585-
gen.checkMode != GenCheckMode.FilterNow
1585+
gen.checkMode == GenCheckMode.Ignore
15861586
|| isVarBinding(gen.pat)
15871587
|| isIrrefutable(gen.pat, gen.expr)
15881588

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
168168
enum GenCheckMode {
169169
case Ignore // neither filter nor check since filtering was done before
170170
case Check // check that pattern is irrefutable
171-
case FilterNow // filter out non-matching elements since we are not yet in 3.x
172171
case FilterAlways // filter out non-matching elements since pattern is prefixed by `case`
173172
}
174173

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2515,9 +2515,8 @@ object Parsers {
25152515
def generatorRest(pat: Tree, casePat: Boolean): GenFrom =
25162516
atSpan(startOffset(pat), accept(LARROW)) {
25172517
val checkMode =
2518-
if (casePat) GenCheckMode.FilterAlways
2519-
else if sourceVersion.isAtLeast(future) then GenCheckMode.Check
2520-
else GenCheckMode.FilterNow // filter for now, to keep backwards compat
2518+
if casePat then GenCheckMode.FilterAlways
2519+
else GenCheckMode.Check
25212520
GenFrom(pat, subExpr(), checkMode)
25222521
}
25232522

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,6 @@ trait Checking {
814814
def check(pat: Tree, pt: Type): Boolean = (pt <:< pat.tpe) || fail(pat, pt)
815815

816816
def recur(pat: Tree, pt: Type): Boolean =
817-
!sourceVersion.isAtLeast(future) || // only for 3.x for now since mitigations work only after this PR
818817
pt.hasAnnotation(defn.UncheckedAnnot) || {
819818
patmatch.println(i"check irrefutable $pat: ${pat.tpe} against $pt")
820819
pat match {

compiler/test/dotty/tools/dotc/CompilationTests.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ class CompilationTests {
202202
aggregateTests(
203203
compileFile("tests/run-custom-args/tuple-cons.scala", allowDeepSubtypes),
204204
compileFile("tests/run-custom-args/i5256.scala", allowDeepSubtypes),
205-
compileFile("tests/run-custom-args/fors.scala", defaultOptions.and("-source", "future")),
206205
compileFile("tests/run-custom-args/no-useless-forwarders.scala", defaultOptions and "-Xmixin-force-forwarders:false"),
207206
compileFile("tests/run-custom-args/defaults-serizaliable-no-forwarders.scala", defaultOptions and "-Xmixin-force-forwarders:false"),
208207
compileFilesInDir("tests/run-custom-args/erased", defaultOptions.and("-language:experimental.erasedDefinitions")),

docs/_docs/reference/changed-features/pattern-bindings.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ movedTo: https://docs.scala-lang.org/scala3/reference/changed-features/pattern-b
77
In Scala 2, pattern bindings in `val` definitions and `for` expressions are
88
loosely typed. Potentially failing matches are still accepted at compile-time,
99
but may influence the program's runtime behavior.
10-
From Scala 3.1 on, type checking rules will be tightened so that warnings are reported at compile-time instead.
10+
From Scala 3.2 on, type checking rules will be tightened so that warnings are reported at compile-time instead.
1111

1212
## Bindings in Pattern Definitions
1313

@@ -16,7 +16,7 @@ val xs: List[Any] = List(1, 2, 3)
1616
val (x: String) :: _ = xs // error: pattern's type String is more specialized
1717
// than the right-hand side expression's type Any
1818
```
19-
This code gives a compile-time warning in Scala 3.1 (and also in Scala 3.0 under the `-source future` setting) whereas it will fail at runtime with a `ClassCastException` in Scala 2. In Scala 3.1, a pattern binding is only allowed if the pattern is _irrefutable_, that is, if the right-hand side's type conforms to the pattern's type. For instance, the following is OK:
19+
This code gives a compile-time warning in Scala 3.2 (and also earlier Scala 3.x under the `-source future` setting) whereas it will fail at runtime with a `ClassCastException` in Scala 2. In Scala 3.2, a pattern binding is only allowed if the pattern is _irrefutable_, that is, if the right-hand side's type conforms to the pattern's type. For instance, the following is OK:
2020
```scala
2121
val pair = (1, true)
2222
val (x, y) = pair
@@ -25,7 +25,7 @@ Sometimes one wants to decompose data anyway, even though the pattern is refutab
2525
```scala
2626
val first :: rest = elems // error
2727
```
28-
This works in Scala 2. In fact it is a typical use case for Scala 2's rules. But in Scala 3.1 it will give a warning. One can avoid the warning by marking the right-hand side with an [`@unchecked`](https://scala-lang.org/api/3.x/scala/unchecked.html) annotation:
28+
This works in Scala 2. In fact it is a typical use case for Scala 2's rules. But in Scala 3.2 it will give a warning. One can avoid the warning by marking the right-hand side with an [`@unchecked`](https://scala-lang.org/api/3.x/scala/unchecked.html) annotation:
2929
```scala
3030
val first :: rest = elems: @unchecked // OK
3131
```
@@ -40,7 +40,7 @@ val elems: List[Any] = List((1, 2), "hello", (3, 4))
4040
for (x, y) <- elems yield (y, x) // error: pattern's type (Any, Any) is more specialized
4141
// than the right-hand side expression's type Any
4242
```
43-
This code gives a compile-time warning in Scala 3.1 whereas in Scala 2 the list `elems`
43+
This code gives a compile-time warning in Scala 3.2 whereas in Scala 2 the list `elems`
4444
is filtered to retain only the elements of tuple type that match the pattern `(x, y)`.
4545
The filtering functionality can be obtained in Scala 3 by prefixing the pattern with `case`:
4646
```scala
@@ -56,4 +56,4 @@ Generator ::= [‘case’] Pattern1 ‘<-’ Expr
5656

5757
## Migration
5858

59-
The new syntax is supported in Scala 3.0. However, to enable smooth cross compilation between Scala 2 and Scala 3, the changed behavior and additional type checks are only enabled under the `-source future` setting. They will be enabled by default in version 3.1 of the language.
59+
The new syntax is supported in Scala 3.0. However, to enable smooth cross compilation between Scala 2 and Scala 3, the changed behavior and additional type checks are only enabled under the `-source future` setting. They will be enabled by default in version 3.2 of the language.

tests/run-custom-args/fors.check

Lines changed: 0 additions & 46 deletions
This file was deleted.

tests/run-custom-args/fors.scala

Lines changed: 0 additions & 117 deletions
This file was deleted.

tests/run/fors.check

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,21 @@ testNew
2626
0 2 4 6 8
2727
0 2 4 6 8
2828
a b c
29+
30+
testFiltering
31+
hello world
32+
hello world
33+
hello world
34+
1~2 3~4
35+
(empty)
36+
hello world
37+
hello/1~2 hello/3~4 /1~2 /3~4 world/1~2 world/3~4
38+
(2,1) (4,3)
39+
hello world
40+
hello world
41+
hello world
42+
1~2 3~4
43+
(empty)
44+
hello world
45+
hello/1~2 hello/3~4 /1~2 /3~4 world/1~2 world/3~4
46+
(2,1) (4,3)

tests/run/fors.scala

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ object Test extends App {
1414

1515
/////////////////// old syntax ///////////////////
1616

17-
def testOld: Unit = {
17+
def testOld(): Unit = {
1818
println("\ntestOld")
1919

2020
// lists
@@ -46,7 +46,7 @@ object Test extends App {
4646

4747
/////////////////// new syntax ///////////////////
4848

49-
def testNew: Unit = {
49+
def testNew(): Unit = {
5050
println("\ntestNew")
5151

5252
// lists
@@ -77,8 +77,41 @@ object Test extends App {
7777

7878
}
7979

80+
/////////////////// filtering with case ///////////////////
81+
82+
def testFiltering(): Unit = {
83+
println("\ntestFiltering")
84+
85+
val xs: List[Any] = List((1, 2), "hello", (3, 4), "", "world")
86+
87+
for (case x: String <- xs) do print(s"$x "); println()
88+
for (case (x: String) <- xs) do print(s"$x "); println()
89+
for (case y@ (x: String) <- xs) do print(s"$y "); println()
90+
91+
for (case (x, y) <- xs) do print(s"$x~$y "); println()
92+
93+
for (case (x: String) <- xs if x.isEmpty) do print("(empty)"); println()
94+
for (case (x: String) <- xs; y = x) do print(s"$y "); println()
95+
for (case (x: String) <- xs; case (y, z) <- xs) do print(s"$x/$y~$z "); println()
96+
97+
for (case (x, y) <- xs) do print(s"${(y, x)} "); println()
98+
99+
for case x: String <- xs do print(s"$x "); println()
100+
for case (x: String) <- xs do print(s"$x "); println()
101+
for case y@ (x: String) <- xs do print(s"$y "); println()
102+
103+
for case (x, y) <- xs do print(s"$x~$y "); println()
104+
105+
for case (x: String) <- xs if x.isEmpty do print("(empty)"); println()
106+
for case (x: String) <- xs; y = x do print(s"$y "); println()
107+
for case (x: String) <- xs; case (y, z) <- xs do print(s"$x/$y~$z "); println()
108+
109+
for case (x, y) <- xs do print(s"${(y, x)} "); println()
110+
}
111+
80112
////////////////////////////////////////////////////
81113

82-
testOld
83-
testNew
114+
testOld()
115+
testNew()
116+
testFiltering()
84117
}

0 commit comments

Comments
 (0)