Skip to content

Commit 849ee9c

Browse files
authored
In 3.4 make refutable patterns in a for comprehension an error (#18842)
supersedes #16665 Only make refutable patterns in a for comprehension an error, here we have a clear set in stone solution: put `case` before the pattern. It is still in the air the ideal solution for pattern val definitions, see https://contributors.scala-lang.org/t/pre-sip-replace-non-sensical-unchecked-annotations/6342/85, so keep those as a warning for now
2 parents cee2610 + 0bfd343 commit 849ee9c

19 files changed

+137
-57
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2772,7 +2772,7 @@ object Parsers {
27722772
atSpan(startOffset(pat), accept(LARROW)) {
27732773
val checkMode =
27742774
if casePat then GenCheckMode.FilterAlways
2775-
else if sourceVersion.isAtLeast(`future`) then GenCheckMode.Check
2775+
else if sourceVersion.isAtLeast(`3.4`) then GenCheckMode.Check
27762776
else if sourceVersion.isAtLeast(`3.2`) then GenCheckMode.CheckAndFilter
27772777
else GenCheckMode.FilterNow // filter on source version < 3.2, for backward compat
27782778
GenFrom(pat, subExpr(), checkMode)

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,13 @@ trait Checking {
923923
|
924924
|If $usage is intentional, this can be communicated by $fix,
925925
|which $addendum.$rewriteMsg"""),
926-
pos, warnFrom = `3.2`, errorFrom = `future`)
926+
pos,
927+
warnFrom = `3.2`,
928+
// we tighten for-comprehension without `case` to error in 3.4,
929+
// but we keep pat-defs as warnings for now ("@unchecked"),
930+
// until we propose an alternative way to assert exhaustivity to the typechecker.
931+
errorFrom = if isPatDef then `future` else `3.4`
932+
)
927933
false
928934
}
929935

project/Build.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,7 @@ object Build {
14931493
(
14941494
(dir / "shared/src/test/scala" ** (("*.scala": FileFilter)
14951495
-- "ReflectiveCallTest.scala" // uses many forms of structural calls that are not allowed in Scala 3 anymore
1496+
-- "UTF16Test.scala" // refutable pattern match
14961497
)).get
14971498

14981499
++ (dir / "shared/src/test/require-sam" ** "*.scala").get

tests/neg/irrefutable.check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E008] Not Found Error: tests/neg/irrefutable.scala:27:29 -----------------------------------------------------------
2+
27 | for (case Foo(x: Int) <- xs) yield x // error
3+
| ^^
4+
| value withFilter is not a member of Lst[Foo[Any]]
5+
-- Error: tests/neg/irrefutable.scala:30:16 ----------------------------------------------------------------------------
6+
30 | for (Foo(x: Int) <- xs) yield x // error
7+
| ^^^
8+
| pattern's type Int is more specialized than the right hand side expression's type Any
9+
|
10+
| If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
11+
| which will result in a filtering for expression (using `withFilter`).
12+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.

tests/neg/irrefutable.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This tests that A.f1 is recognized as an irrefutable pattern and A.f2_nocase is not, and therefore A.f2 solves this
2+
// by adding a case to the pattern, which results in withFilter being inserted.
3+
// see also: tests/run/irrefutable.scala for an example that exercises the insertion of withFilter.
4+
5+
class Lst[+T](val id: String, val underlying: List[T]) {
6+
def map[U](f: T => U): Lst[U] = new Lst(id, underlying.map(f))
7+
8+
// hide the withFilter so that there is a compile error
9+
// def withFilter(f: T => Boolean): Lst.WithFilter[T] = new Lst.WithFilter(this, f)
10+
}
11+
12+
// object Lst:
13+
// class WithFilter[+T](lst: Lst[T], filter: T => Boolean):
14+
// def forwardingFilter[T1](filter: T1 => Boolean): T1 => Boolean = t =>
15+
// println(s"filtering $t in ${lst.id}")
16+
// filter(t)
17+
18+
// def map[U](f: T => U): Lst[U] = Lst(lst.id, lst.underlying.withFilter(forwardingFilter(filter)).map(f))
19+
20+
case class Foo[T](x: T)
21+
22+
object A {
23+
def f1(xs: Lst[Foo[Int]]): Lst[Int] = {
24+
for (Foo(x: Int) <- xs) yield x
25+
}
26+
def f2(xs: Lst[Foo[Any]]): Lst[Int] = {
27+
for (case Foo(x: Int) <- xs) yield x // error
28+
}
29+
def f2_nocase(xs: Lst[Foo[Any]]): Lst[Int] = {
30+
for (Foo(x: Int) <- xs) yield x // error
31+
}
32+
}
33+
34+
@main def Test =
35+
val xs = new Lst("xs", List(Foo(1), Foo(2), Foo(3)))
36+
println("=== mapping xs with A.f1 ===")
37+
val xs1 = A.f1(xs)
38+
assert(xs1.underlying == List(1, 2, 3))
39+
val ys = new Lst("ys", List(Foo(1: Any), Foo(2: Any), Foo(3: Any)))
40+
println("=== mapping ys with A.f2 ===")
41+
val ys1 = A.f2(ys)
42+
assert(ys1.underlying == List(1, 2, 3))

tests/neg/refutable-pattern-binding-messages.check

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
-- Error: tests/neg/refutable-pattern-binding-messages.scala:5:14 ------------------------------------------------------
2-
5 | val Positive(p) = 5 // error: refutable extractor
3-
| ^^^^^^^^^^^^^^^
4-
| pattern binding uses refutable extractor `Test.Positive`
5-
|
6-
| If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression,
7-
| which may result in a MatchError at runtime.
8-
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
91
-- Error: tests/neg/refutable-pattern-binding-messages.scala:6:14 ------------------------------------------------------
102
6 | for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor
113
| ^^^^^^^^^^^
@@ -14,14 +6,6 @@
146
| If this usage is intentional, this can be communicated by adding the `case` keyword before the full pattern,
157
| which will result in a filtering for expression (using `withFilter`).
168
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
17-
-- Error: tests/neg/refutable-pattern-binding-messages.scala:10:20 -----------------------------------------------------
18-
10 | val i :: is = List(1, 2, 3) // error: pattern type more specialized
19-
| ^^^^^^^^^^^^^
20-
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
21-
|
22-
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
23-
| which may result in a MatchError at runtime.
24-
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
259
-- Error: tests/neg/refutable-pattern-binding-messages.scala:11:11 -----------------------------------------------------
2610
11 | for ((x: String) <- xs) do () // error: pattern type more specialized
2711
| ^^^^^^
@@ -38,6 +22,22 @@
3822
| If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
3923
| which will result in a filtering for expression (using `withFilter`).
4024
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
25+
-- Error: tests/neg/refutable-pattern-binding-messages.scala:5:14 ------------------------------------------------------
26+
5 | val Positive(p) = 5 // error: refutable extractor
27+
| ^^^^^^^^^^^^^^^
28+
| pattern binding uses refutable extractor `Test.Positive`
29+
|
30+
| If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression,
31+
| which may result in a MatchError at runtime.
32+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
33+
-- Error: tests/neg/refutable-pattern-binding-messages.scala:10:20 -----------------------------------------------------
34+
10 | val i :: is = List(1, 2, 3) // error: pattern type more specialized
35+
| ^^^^^^^^^^^^^
36+
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
37+
|
38+
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
39+
| which may result in a MatchError at runtime.
40+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
4141
-- Error: tests/neg/refutable-pattern-binding-messages.scala:16:10 -----------------------------------------------------
4242
16 | val 1 = 2 // error: pattern type does not match
4343
| ^

tests/pos/irrefutable.scala

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

0 commit comments

Comments
 (0)