From f0d76ca2b6b02d2f960931a74b87d945b12d8909 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 3 Nov 2023 17:08:03 +0100 Subject: [PATCH 1/7] error for refutable for-generator patterns in 3.4 --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index c845ea8f74c7..b8d39186bc50 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2771,7 +2771,7 @@ object Parsers { atSpan(startOffset(pat), accept(LARROW)) { val checkMode = if casePat then GenCheckMode.FilterAlways - else if sourceVersion.isAtLeast(`future`) then GenCheckMode.Check + else if sourceVersion.isAtLeast(`3.4`) then GenCheckMode.Check else if sourceVersion.isAtLeast(`3.2`) then GenCheckMode.CheckAndFilter else GenCheckMode.FilterNow // filter on source version < 3.2, for backward compat GenFrom(pat, subExpr(), checkMode) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c8026ad5784b..dc0f4d729789 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -923,7 +923,13 @@ trait Checking { | |If $usage is intentional, this can be communicated by $fix, |which $addendum.$rewriteMsg"""), - pos, warnFrom = `3.2`, errorFrom = `future`) + pos, + warnFrom = `3.2`, + // we tighten for-comprehension without `case` to error in 3.4, + // but we keep pat-defs as warnings for now ("@unchecked"), + // until we propose an alternative way to assert exhaustivity to the typechecker. + errorFrom = if isPatDef then `future` else `3.4` + ) false } From 3aaa98ccda2d746568ddce82521c20e551daa812 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 3 Nov 2023 19:23:36 +0100 Subject: [PATCH 2/7] fix bootstrapped tests --- tests/neg/irrefutable.check | 12 ++++++ tests/neg/irrefutable.scala | 42 +++++++++++++++++++ .../refutable-pattern-binding-messages.check | 32 +++++++------- tests/pos/irrefutable.scala | 22 ---------- tests/pos/t6205.scala | 2 +- tests/run/ReplacementMatching.scala | 4 +- tests/run/irrefutable.check | 5 +++ tests/run/irrefutable.scala | 36 ++++++++++++++++ tests/run/patmat-bind-typed.scala | 2 +- tests/run/quoted-sematics-1.scala | 8 ++-- tests/run/t6406-regextract.scala | 4 +- tests/run/t6646.scala | 6 +-- tests/run/t6968.scala | 2 +- 13 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 tests/neg/irrefutable.check create mode 100644 tests/neg/irrefutable.scala delete mode 100644 tests/pos/irrefutable.scala create mode 100644 tests/run/irrefutable.check create mode 100644 tests/run/irrefutable.scala diff --git a/tests/neg/irrefutable.check b/tests/neg/irrefutable.check new file mode 100644 index 000000000000..01baff685cbc --- /dev/null +++ b/tests/neg/irrefutable.check @@ -0,0 +1,12 @@ +-- [E008] Not Found Error: tests/neg/irrefutable.scala:27:29 ----------------------------------------------------------- +27 | for (case Foo(x: Int) <- xs) yield x // error + | ^^ + | value withFilter is not a member of Lst[Foo[Any]] +-- Error: tests/neg/irrefutable.scala:30:16 ---------------------------------------------------------------------------- +30 | for (Foo(x: Int) <- xs) yield x // error + | ^^^ + | pattern's type Int is more specialized than the right hand side expression's type Any + | + | If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern, + | which will result in a filtering for expression (using `withFilter`). + | This patch can be rewritten automatically under -rewrite -source 3.2-migration. diff --git a/tests/neg/irrefutable.scala b/tests/neg/irrefutable.scala new file mode 100644 index 000000000000..b4f2736998e6 --- /dev/null +++ b/tests/neg/irrefutable.scala @@ -0,0 +1,42 @@ +// This tests that A.f1 is recognized as an irrefutable pattern and A.f2_nocase is not, and therefore A.f2 solves this +// by adding a case to the pattern, which results in withFilter being inserted. +// see also: tests/run/irrefutable.scala for an example that exercises the insertion of withFilter. + +class Lst[+T](val id: String, val underlying: List[T]) { + def map[U](f: T => U): Lst[U] = new Lst(id, underlying.map(f)) + + // hide the withFilter so that there is a compile error + // def withFilter(f: T => Boolean): Lst.WithFilter[T] = new Lst.WithFilter(this, f) +} + +// object Lst: +// class WithFilter[+T](lst: Lst[T], filter: T => Boolean): +// def forwardingFilter[T1](filter: T1 => Boolean): T1 => Boolean = t => +// println(s"filtering $t in ${lst.id}") +// filter(t) + +// def map[U](f: T => U): Lst[U] = Lst(lst.id, lst.underlying.withFilter(forwardingFilter(filter)).map(f)) + +case class Foo[T](x: T) + +object A { + def f1(xs: Lst[Foo[Int]]): Lst[Int] = { + for (Foo(x: Int) <- xs) yield x + } + def f2(xs: Lst[Foo[Any]]): Lst[Int] = { + for (case Foo(x: Int) <- xs) yield x // error + } + def f2_nocase(xs: Lst[Foo[Any]]): Lst[Int] = { + for (Foo(x: Int) <- xs) yield x // error + } +} + +@main def Test = + val xs = new Lst("xs", List(Foo(1), Foo(2), Foo(3))) + println("=== mapping xs with A.f1 ===") + val xs1 = A.f1(xs) + assert(xs1.underlying == List(1, 2, 3)) + val ys = new Lst("ys", List(Foo(1: Any), Foo(2: Any), Foo(3: Any))) + println("=== mapping ys with A.f2 ===") + val ys1 = A.f2(ys) + assert(ys1.underlying == List(1, 2, 3)) diff --git a/tests/neg/refutable-pattern-binding-messages.check b/tests/neg/refutable-pattern-binding-messages.check index b1b8866e174f..5a9d85fd4447 100644 --- a/tests/neg/refutable-pattern-binding-messages.check +++ b/tests/neg/refutable-pattern-binding-messages.check @@ -1,11 +1,3 @@ --- Error: tests/neg/refutable-pattern-binding-messages.scala:5:14 ------------------------------------------------------ -5 | val Positive(p) = 5 // error: refutable extractor - | ^^^^^^^^^^^^^^^ - | pattern binding uses refutable extractor `Test.Positive` - | - | If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression, - | which may result in a MatchError at runtime. - | This patch can be rewritten automatically under -rewrite -source 3.2-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:6:14 ------------------------------------------------------ 6 | for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor | ^^^^^^^^^^^ @@ -14,14 +6,6 @@ | If this usage is intentional, this can be communicated by adding the `case` keyword before the full pattern, | which will result in a filtering for expression (using `withFilter`). | This patch can be rewritten automatically under -rewrite -source 3.2-migration. --- Error: tests/neg/refutable-pattern-binding-messages.scala:10:20 ----------------------------------------------------- -10 | val i :: is = List(1, 2, 3) // error: pattern type more specialized - | ^^^^^^^^^^^^^ - | pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int] - | - | If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression, - | which may result in a MatchError at runtime. - | This patch can be rewritten automatically under -rewrite -source 3.2-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:11:11 ----------------------------------------------------- 11 | for ((x: String) <- xs) do () // error: pattern type more specialized | ^^^^^^ @@ -38,6 +22,22 @@ | If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern, | which will result in a filtering for expression (using `withFilter`). | This patch can be rewritten automatically under -rewrite -source 3.2-migration. +-- Error: tests/neg/refutable-pattern-binding-messages.scala:5:14 ------------------------------------------------------ +5 | val Positive(p) = 5 // error: refutable extractor + | ^^^^^^^^^^^^^^^ + | pattern binding uses refutable extractor `Test.Positive` + | + | If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression, + | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source 3.2-migration. +-- Error: tests/neg/refutable-pattern-binding-messages.scala:10:20 ----------------------------------------------------- +10 | val i :: is = List(1, 2, 3) // error: pattern type more specialized + | ^^^^^^^^^^^^^ + | pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int] + | + | If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression, + | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source 3.2-migration. -- Error: tests/neg/refutable-pattern-binding-messages.scala:16:10 ----------------------------------------------------- 16 | val 1 = 2 // error: pattern type does not match | ^ diff --git a/tests/pos/irrefutable.scala b/tests/pos/irrefutable.scala deleted file mode 100644 index 0a792b644a09..000000000000 --- a/tests/pos/irrefutable.scala +++ /dev/null @@ -1,22 +0,0 @@ -// The test which this should perform but does not -// is that f1 is recognized as irrefutable and f2 is not -// This can be recognized via the generated classes: -// -// A$$anonfun$f1$1.class -// A$$anonfun$f2$1.class -// A$$anonfun$f2$2.class -// -// The extra one in $f2$ is the filter. -// -// !!! Marking with exclamation points so maybe someday -// this test will be finished. -class A { - case class Foo[T](x: T) - - def f1(xs: List[Foo[Int]]) = { - for (Foo(x: Int) <- xs) yield x - } - def f2(xs: List[Foo[Any]]) = { - for (Foo(x: Int) <- xs) yield x - } -} diff --git a/tests/pos/t6205.scala b/tests/pos/t6205.scala index 52078bd5f46f..a50350d20376 100644 --- a/tests/pos/t6205.scala +++ b/tests/pos/t6205.scala @@ -2,7 +2,7 @@ class A[T] class Test1 { def x(backing: Map[A[_], Any]) = - for( (k: A[kt], v) <- backing) + for(case (k: A[kt], v) <- backing) yield (k: A[kt]) } diff --git a/tests/run/ReplacementMatching.scala b/tests/run/ReplacementMatching.scala index 846f1c0a0966..b233709a7cae 100644 --- a/tests/run/ReplacementMatching.scala +++ b/tests/run/ReplacementMatching.scala @@ -32,12 +32,12 @@ object Test { def groupsMatching: Unit = { val Date = """(\d+)/(\d+)/(\d+)""".r - for (Regex.Groups(a, b, c) <- Date findFirstMatchIn "1/1/2001 marks the start of the millennium. 31/12/2000 doesn't.") { + for (case Regex.Groups(a, b, c) <- Date findFirstMatchIn "1/1/2001 marks the start of the millennium. 31/12/2000 doesn't.") { assert(a == "1") assert(b == "1") assert(c == "2001") } - for (Regex.Groups(a, b, c) <- (Date findAllIn "1/1/2001 marks the start of the millennium. 31/12/2000 doesn't.").matchData) { + for (case Regex.Groups(a, b, c) <- (Date findAllIn "1/1/2001 marks the start of the millennium. 31/12/2000 doesn't.").matchData) { assert(a == "1" || a == "31") assert(b == "1" || b == "12") assert(c == "2001" || c == "2000") diff --git a/tests/run/irrefutable.check b/tests/run/irrefutable.check new file mode 100644 index 000000000000..f0ac130d17af --- /dev/null +++ b/tests/run/irrefutable.check @@ -0,0 +1,5 @@ +=== mapping xs with A.f1 === +=== mapping ys with A.f2 === +filtering Foo(1) in ys +filtering Foo(2) in ys +filtering Foo(3) in ys diff --git a/tests/run/irrefutable.scala b/tests/run/irrefutable.scala new file mode 100644 index 000000000000..c62dcebaf8fb --- /dev/null +++ b/tests/run/irrefutable.scala @@ -0,0 +1,36 @@ +// This tests that A.f1 does not filter its inputs, whereas A.f2 does. +// see also: tests/neg/irrefutable.scala for an example that exercises the requirement to insert case. + +class Lst[+T](val id: String, val underlying: List[T]) { + def map[U](f: T => U): Lst[U] = new Lst(id, underlying.map(f)) + def withFilter(f: T => Boolean): Lst.WithFilter[T] = new Lst.WithFilter(this, f) +} + +object Lst: + class WithFilter[+T](lst: Lst[T], filter: T => Boolean): + def forwardingFilter[T1](filter: T1 => Boolean): T1 => Boolean = t => + println(s"filtering $t in ${lst.id}") + filter(t) + + def map[U](f: T => U): Lst[U] = Lst(lst.id, lst.underlying.withFilter(forwardingFilter(filter)).map(f)) + +case class Foo[T](x: T) + +object A { + def f1(xs: Lst[Foo[Int]]): Lst[Int] = { + for (Foo(x: Int) <- xs) yield x + } + def f2(xs: Lst[Foo[Any]]): Lst[Int] = { + for (case Foo(x: Int) <- xs) yield x + } +} + +@main def Test = + val xs = new Lst("xs", List(Foo(1), Foo(2), Foo(3))) + println("=== mapping xs with A.f1 ===") + val xs1 = A.f1(xs) + assert(xs1.underlying == List(1, 2, 3)) + val ys = new Lst("ys", List(Foo(1: Any), Foo(2: Any), Foo(3: Any))) + println("=== mapping ys with A.f2 ===") + val ys1 = A.f2(ys) + assert(ys1.underlying == List(1, 2, 3)) diff --git a/tests/run/patmat-bind-typed.scala b/tests/run/patmat-bind-typed.scala index 10de921c5190..065babc8216c 100644 --- a/tests/run/patmat-bind-typed.scala +++ b/tests/run/patmat-bind-typed.scala @@ -1,5 +1,5 @@ object Test { - def f(xs: List[Any]) = for (key @ (dummy: String) <- xs) yield key + def f(xs: List[Any]) = for (case key @ (dummy: String) <- xs) yield key def main(args: Array[String]): Unit = { f("abc" :: Nil) foreach println diff --git a/tests/run/quoted-sematics-1.scala b/tests/run/quoted-sematics-1.scala index 84bf754dbc36..4f94c8f3c32c 100644 --- a/tests/run/quoted-sematics-1.scala +++ b/tests/run/quoted-sematics-1.scala @@ -82,7 +82,7 @@ def typeChecks(g: Gamma)(level: 0 | 1)(term: Term): Option[Type] = yield LambdaType(t, res) case App(fun, arg) => // T-App for - LambdaType(t1, t2) <- typeChecks(g)(level)(fun) + case LambdaType(t1, t2) <- typeChecks(g)(level)(fun) `t1` <- typeChecks(g)(level)(arg) yield t2 case Box(body) if level == 0 => // T-Box @@ -90,16 +90,16 @@ def typeChecks(g: Gamma)(level: 0 | 1)(term: Term): Option[Type] = case Lift(body) if level == 0 => // T-Lift for NatType <- typeChecks(g)(0)(body) yield BoxType(NatType) case Splice(body) if level == 1 => // T-Unbox - for BoxType(t) <- typeChecks(g)(0)(body) yield t + for case BoxType(t) <- typeChecks(g)(0)(body) yield t case Match(scrutinee, pat, thenp, elsep) => // T-Pat for - BoxType(t1) <- typeChecks(g)(0)(scrutinee) + case BoxType(t1) <- typeChecks(g)(0)(scrutinee) delta <- typePatChecks(g, t1)(pat) t <- typeChecks(g ++ delta)(0)(thenp) `t` <- typeChecks(g)(0)(elsep) yield t case Fix(t) if level == 0 => - for LambdaType(t1, t2) <- typeChecks(g)(0)(t) yield t2 // T-Fix + for case LambdaType(t1, t2) <- typeChecks(g)(0)(t) yield t2 // T-Fix case _ => None if res.isEmpty then println(s"Failed to type $term at level $level with environment $g") diff --git a/tests/run/t6406-regextract.scala b/tests/run/t6406-regextract.scala index 18cf28865aba..4d10d3f8775c 100644 --- a/tests/run/t6406-regextract.scala +++ b/tests/run/t6406-regextract.scala @@ -20,10 +20,10 @@ object Test extends App { val t = "Last modified 2011-07-15" val p1 = """(\d\d\d\d)-(\d\d)-(\d\d)""".r val y1: Option[String] = for { - p1(year, month, day) <- p1 findFirstIn t + case p1(year, month, day) <- p1 findFirstIn t } yield year val y2: Option[String] = for { - p1(year, month, day) <- p1 findFirstMatchIn t + case p1(year, month, day) <- p1 findFirstMatchIn t } yield year println(s"$y1 $y2") diff --git a/tests/run/t6646.scala b/tests/run/t6646.scala index b96851077bf9..d1c84455c216 100644 --- a/tests/run/t6646.scala +++ b/tests/run/t6646.scala @@ -8,9 +8,9 @@ object Test { val l = List(PrimaryKey, NoNull, lower) // withFilter must be generated in these - for (option @ NoNull <- l) println("Found " + option) - for (option @ `lower` <- l) println("Found " + option) - for ((`lower`, i) <- l.zipWithIndex) println("Found " + i) + for (case option @ NoNull <- l) println("Found " + option) + for (case option @ `lower` <- l) println("Found " + option) + for (case (`lower`, i) <- l.zipWithIndex) println("Found " + i) // no withFilter for (X <- List("A single ident is always a pattern")) println(X) diff --git a/tests/run/t6968.scala b/tests/run/t6968.scala index c4e47ba0eda8..84a0969c0872 100644 --- a/tests/run/t6968.scala +++ b/tests/run/t6968.scala @@ -1,7 +1,7 @@ object Test { def main(args: Array[String]): Unit = { val mixedList = List(1,(1,2),4,(3,1),(5,4),6) - val as = for((a,b) <- mixedList) yield a + val as = for(case (a,b) <- mixedList) yield a println(as.mkString(", ")) } } From fd079c8ac358acb9ccd2d9c1d9de7cbaa35901ae Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 3 Nov 2023 20:46:20 +0100 Subject: [PATCH 3/7] ignore problematic scala.js sources --- project/Build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/Build.scala b/project/Build.scala index 13ebe9c028ae..fe9720464786 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1490,6 +1490,7 @@ object Build { ( (dir / "shared/src/test/scala" ** (("*.scala": FileFilter) -- "ReflectiveCallTest.scala" // uses many forms of structural calls that are not allowed in Scala 3 anymore + -- "UTF16Test.scala" // refutable pattern match )).get ++ (dir / "shared/src/test/require-sam" ** "*.scala").get From 763ce729734c8383951341e9076bb615cdd790e5 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 11 Jan 2023 16:20:10 +0100 Subject: [PATCH 4/7] patch scala-xml for refutable patterns --- community-build/community-projects/scala-xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scala-xml b/community-build/community-projects/scala-xml index ba33a89bdeee..e5175a666bd9 160000 --- a/community-build/community-projects/scala-xml +++ b/community-build/community-projects/scala-xml @@ -1 +1 @@ -Subproject commit ba33a89bdeee67089ff486c66ead93ab35f9250a +Subproject commit e5175a666bd9e63fcd8a61266d136773aa5b700a From 440ff3250337e4e11dd07c873025d1df90e312b3 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 11 Jan 2023 17:25:36 +0100 Subject: [PATCH 5/7] patch scalatest for refutable patterns --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 39370e391342..d430625d9621 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 39370e391342eb3d3ecfa847be16734f2fb1f3a2 +Subproject commit d430625d96218c9031b1434cc0c2110f3740fa1c From 050af10859dd2e14090990adc8f6c833145b108f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 28 Sep 2023 17:15:04 +0200 Subject: [PATCH 6/7] patch scala-xml again --- community-build/community-projects/scala-xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scala-xml b/community-build/community-projects/scala-xml index e5175a666bd9..105c3dac8835 160000 --- a/community-build/community-projects/scala-xml +++ b/community-build/community-projects/scala-xml @@ -1 +1 @@ -Subproject commit e5175a666bd9e63fcd8a61266d136773aa5b700a +Subproject commit 105c3dac883549eca1182b04fc5a18fe4f5ad51a From 0bfd343794850a450d5df1b4f62c2d6a9b911f43 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 3 Nov 2023 20:59:21 +0100 Subject: [PATCH 7/7] patch scalaz for refutable for --- community-build/community-projects/scalaz | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalaz b/community-build/community-projects/scalaz index 6e7f3d9caf64..97cccf3b3fcb 160000 --- a/community-build/community-projects/scalaz +++ b/community-build/community-projects/scalaz @@ -1 +1 @@ -Subproject commit 6e7f3d9caf64d8ad1c82804cf418882345f41930 +Subproject commit 97cccf3b3fcb71885a32b2e567171c0f70b06104