diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 8573127d6f27..b6a3d818ccd4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1054,8 +1054,9 @@ trait Checking { } /** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`. - * This means `sel` is either marked @unchecked or `sel.tpe` conforms to the - * pattern's type. If pattern is an UnApply, also check that the extractor is + * This means `sel` is either marked `: @RuntimeChecked`, `: @unchecked` (old style), + * or `sel.tpe` conforms to the pattern's type. If pattern is an Unapply, + * also check that the extractor is * irrefutable, and do the check recursively. */ def checkIrrefutable(sel: Tree, pat: Tree, isPatDef: Boolean)(using Context): Boolean = { @@ -1068,7 +1069,7 @@ trait Checking { import Reason.* val message = reason match case NonConforming => - var reportedPt = pt.dropAnnot(defn.UncheckedAnnot) + var reportedPt = pt.dropAnnot(defn.UncheckedAnnot).dropAnnot(defn.RuntimeCheckedAnnot) if !pat.tpe.isSingleton then reportedPt = reportedPt.widen val problem = if pat.tpe <:< reportedPt then "is more specialized than" else "does not match" em"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt" @@ -1084,7 +1085,10 @@ trait Checking { else em"pattern binding uses refutable extractor `$extractor`" val fix = - if isPatDef then "adding `: @unchecked` after the expression" + if isPatDef then + val patchText = + if sourceVersion.isAtLeast(`3.8`) then ".runtimeChecked" else ": @unchecked" + s"adding `$patchText` after the expression" else "adding the `case` keyword before the full pattern" val addendum = if isPatDef then "may result in a MatchError at runtime" @@ -1097,7 +1101,9 @@ trait Checking { case NonConforming => sel.srcPos case RefutableExtractor => pat.source.atSpan(pat.span `union` sel.span) else pat.srcPos - def rewriteMsg = Message.rewriteNotice("This patch", `3.2-migration`) + def rewriteMsg = Message.rewriteNotice("This patch", + if isPatDef && sourceVersion.isAtLeast(`3.8`) then `3.8-migration` else `3.2-migration` + ) report.errorOrMigrationWarning( message.append( i"""| @@ -1106,7 +1112,7 @@ trait Checking { |which $addendum.$rewriteMsg"""), pos, // we tighten for-comprehension without `case` to error in 3.4, - // but we keep pat-defs as warnings for now ("@unchecked"), + // but we keep pat-defs as warnings for now (".runtimeChecked"), // until we propose an alternative way to assert exhaustivity to the typechecker. if isPatDef then MigrationVersion.ForComprehensionUncheckedPathDefs else MigrationVersion.ForComprehensionPatternWithoutCase diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 389cd306fc02..10c1035167ab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2213,10 +2213,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedMatchFinish(tree, sel1, selType, tree.cases, pt) } - /** Are some form of brackets necessary to annotate the tree `sel` as `@unchecked`? + /** Are some form of brackets necessary to annotate the tree `sel` as `.runtimeChecked`? * If so, return a Some(opening bracket, closing bracket), otherwise None. */ - def uncheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match + def runtimeCheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match case _: untpd.If | _: untpd.Match | _: untpd.ForYield @@ -2235,20 +2235,24 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer && sourceVersion.isAtLeast(`3.2`) && sourceVersion.isMigrating then - if isPatDef then uncheckedBrackets(tree.selector) match + if isPatDef then + val patchText = + if sourceVersion.isAtLeast(`3.8`) then ".runtimeChecked" + else ": @unchecked" + runtimeCheckedBrackets(tree.selector) match case None => - patch(Span(tree.selector.span.end), ": @unchecked") + patch(Span(tree.selector.span.end), patchText) case Some(bl, br) => patch(Span(tree.selector.span.start), s"$bl") - patch(Span(tree.selector.span.end), s"$br: @unchecked") + patch(Span(tree.selector.span.end), s"$br$patchText") else patch(Span(tree.span.start), "case ") // skip exhaustivity check in later phase // TODO: move the check above to patternMatcher phase - val uncheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.UncheckedAnnot, tree.selector.span)) + val runtimeCheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.RuntimeCheckedAnnot, tree.selector.span)) tpd.cpy.Match(result)( - selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe, inferred = true)), + selector = tpd.Typed(sel, tpd.TypeTree(runtimeCheckedTpe, inferred = true)), cases = result.cases ) case _ => diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 85793e69b9cb..d2f6b8265c6f 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -70,7 +70,8 @@ class CompilationTests { compileFile("tests/rewrites/private-this.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/alphanumeric-infix-operator.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/filtering-fors.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")), - compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")), + compileFile("tests/rewrites/refutable-pattern-bindings-old.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")), + compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "3.8-migration")), compileFile("tests/rewrites/i8982.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i9632.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i11895.scala", defaultOptions.and("-indent", "-rewrite")), diff --git a/tests/neg/i11118.check b/tests/neg/i11118.check index 0af98c7f580a..6c89f8420cbf 100644 --- a/tests/neg/i11118.check +++ b/tests/neg/i11118.check @@ -1,11 +1,11 @@ -- Warning: tests/neg/i11118.scala:2:12 -------------------------------------------------------------------------------- 2 |val (a,b) = (1,2,3) // error // warning | ^^^^^^^ - | pattern's type (Any, Any) does not match the right hand side expression's type (Int, Int, Int) + | pattern's type (Any, Any) does not match the right hand side expression's type (Int, Int, 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. + | If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression, + | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source 3.8-migration. -- Error: tests/neg/i11118.scala:2:4 ----------------------------------------------------------------------------------- 2 |val (a,b) = (1,2,3) // error // warning | ^ diff --git a/tests/neg/refutable-pattern-binding-messages-old.check b/tests/neg/refutable-pattern-binding-messages-old.check new file mode 100644 index 000000000000..12da938246a9 --- /dev/null +++ b/tests/neg/refutable-pattern-binding-messages-old.check @@ -0,0 +1,48 @@ +-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:6:14 -------------------------------------------------- +6 | for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor + | ^^^^^^^^^^^ + | pattern binding uses refutable extractor `Test.Positive` + | + | 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-old.scala:11:11 ------------------------------------------------- +11 | for ((x: String) <- xs) do () // error: pattern type more specialized + | ^^^^^^ + | pattern's type String is more specialized than the right hand side expression's type AnyRef + | + | 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-old.scala:15:13 ------------------------------------------------- +15 | for none @ None <- ys do () // error: pattern type does not match + | ^^^^ + | pattern's type None.type does not match the right hand side expression's type (x$1 : Option[?]) + | + | 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. +-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:5:14 ------------------------------------------------ +5 | val Positive(p) = 5 // warn: 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. +-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:10:20 ----------------------------------------------- +10 | val i :: is = List(1, 2, 3) // warn: 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. +-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:16:10 ----------------------------------------------- +16 | val 1 = 2 // warn: pattern type does not match + | ^ + | pattern's type (1 : Int) does not match the right hand side expression's type (2 : 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. diff --git a/tests/neg/refutable-pattern-binding-messages-old.scala b/tests/neg/refutable-pattern-binding-messages-old.scala new file mode 100644 index 000000000000..85a6cce9936d --- /dev/null +++ b/tests/neg/refutable-pattern-binding-messages-old.scala @@ -0,0 +1,17 @@ +//> using options -source 3.7 +object Test { + // refutable extractor + object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) } + val Positive(p) = 5 // warn: refutable extractor + for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor + + // more specialized + val xs: List[AnyRef] = ??? + val i :: is = List(1, 2, 3) // warn: pattern type more specialized + for ((x: String) <- xs) do () // error: pattern type more specialized + + // does not match + val ys: List[Option[?]] = ??? + for none @ None <- ys do () // error: pattern type does not match + val 1 = 2 // warn: pattern type does not match +} diff --git a/tests/neg/refutable-pattern-binding-messages.check b/tests/neg/refutable-pattern-binding-messages.check index 4a8f895264a3..6f3c4762fe4f 100644 --- a/tests/neg/refutable-pattern-binding-messages.check +++ b/tests/neg/refutable-pattern-binding-messages.check @@ -27,22 +27,22 @@ | ^^^^^^^^^^^^^^^ | pattern binding uses refutable extractor `Test.Positive` | - | If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression, + | If this usage is intentional, this can be communicated by adding `.runtimeChecked` after the expression, | which may result in a MatchError at runtime. - | This patch can be rewritten automatically under -rewrite -source 3.2-migration. + | This patch can be rewritten automatically under -rewrite -source 3.8-migration. -- Warning: tests/neg/refutable-pattern-binding-messages.scala:10:20 --------------------------------------------------- 10 | val i :: is = List(1, 2, 3) // warn: pattern type more specialized | ^^^^^^^^^^^^^ - | pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int] + | 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. + | If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression, + | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source 3.8-migration. -- Warning: tests/neg/refutable-pattern-binding-messages.scala:16:10 --------------------------------------------------- 16 | val 1 = 2 // warn: pattern type does not match | ^ - | pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int) + | pattern's type (1 : Int) does not match the right hand side expression's type (2 : 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. + | If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression, + | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source 3.8-migration. diff --git a/tests/neg/refutable-pattern-binding-messages.scala b/tests/neg/refutable-pattern-binding-messages.scala index e71371785d15..319d916f882a 100644 --- a/tests/neg/refutable-pattern-binding-messages.scala +++ b/tests/neg/refutable-pattern-binding-messages.scala @@ -1,4 +1,4 @@ - +//> using options -source 3.8 object Test { // refutable extractor object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) } diff --git a/tests/neg/t5702-neg-bad-and-wild.check b/tests/neg/t5702-neg-bad-and-wild.check index a70c6db1cef9..96392a9389cd 100644 --- a/tests/neg/t5702-neg-bad-and-wild.check +++ b/tests/neg/t5702-neg-bad-and-wild.check @@ -57,8 +57,8 @@ -- Warning: tests/neg/t5702-neg-bad-and-wild.scala:22:20 --------------------------------------------------------------- 22 | val K(x @ _*) = k | ^ - | pattern's type Int* does not match the right hand side expression's type Int + | pattern's type Int* does not match the right hand side expression's type 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. + | If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression, + | which may result in a MatchError at runtime. + | This patch can be rewritten automatically under -rewrite -source 3.8-migration. diff --git a/tests/rewrites/refutable-pattern-bindings-old.check b/tests/rewrites/refutable-pattern-bindings-old.check new file mode 100644 index 000000000000..21302524d469 --- /dev/null +++ b/tests/rewrites/refutable-pattern-bindings-old.check @@ -0,0 +1,29 @@ +// NOTE: this file is compiled under -source 3.2-migration (i.e. it will rewrite to `: @unchecked`) +// see refutable-pattern-bindings.scala for the version compiled under -source 3.8-migration (which rewrites to `.runtimeChecked`) +val xs: List[Any] = ??? + +val hd :: tl = (xs match + case Nil => null :: xs + case _ => xs): @unchecked + +val h :: t = xs: @unchecked + +val a :: b = + (if xs.isEmpty then null :: xs + else xs): @unchecked + +val c :: d = + (try xs.head :: xs + catch case _: NoSuchElementException => null :: xs): @unchecked + +val e :: f = + {val zero = null :: Nil + if xs.isEmpty then zero + else xs}: @unchecked + +val j :: k = + (for + case (x: String) <- xs + yield x): @unchecked + +val (_: Int | _: AnyRef) = (??? : AnyRef): @unchecked diff --git a/tests/rewrites/refutable-pattern-bindings-old.scala b/tests/rewrites/refutable-pattern-bindings-old.scala new file mode 100644 index 000000000000..c6f7c974fa4c --- /dev/null +++ b/tests/rewrites/refutable-pattern-bindings-old.scala @@ -0,0 +1,29 @@ +// NOTE: this file is compiled under -source 3.2-migration (i.e. it will rewrite to `: @unchecked`) +// see refutable-pattern-bindings.scala for the version compiled under -source 3.8-migration (which rewrites to `.runtimeChecked`) +val xs: List[Any] = ??? + +val hd :: tl = xs match + case Nil => null :: xs + case _ => xs + +val h :: t = xs + +val a :: b = + if xs.isEmpty then null :: xs + else xs + +val c :: d = + try xs.head :: xs + catch case _: NoSuchElementException => null :: xs + +val e :: f = + val zero = null :: Nil + if xs.isEmpty then zero + else xs + +val j :: k = + for + (x: String) <- xs + yield x + +val (_: Int | _: AnyRef) = ??? : AnyRef diff --git a/tests/rewrites/refutable-pattern-bindings.check b/tests/rewrites/refutable-pattern-bindings.check index de61b29ebdf7..7d90d0c4ece6 100644 --- a/tests/rewrites/refutable-pattern-bindings.check +++ b/tests/rewrites/refutable-pattern-bindings.check @@ -2,26 +2,26 @@ val xs: List[Any] = ??? val hd :: tl = (xs match case Nil => null :: xs - case _ => xs): @unchecked + case _ => xs).runtimeChecked -val h :: t = xs: @unchecked +val h :: t = xs.runtimeChecked val a :: b = (if xs.isEmpty then null :: xs - else xs): @unchecked + else xs).runtimeChecked val c :: d = (try xs.head :: xs - catch case _: NoSuchElementException => null :: xs): @unchecked + catch case _: NoSuchElementException => null :: xs).runtimeChecked val e :: f = {val zero = null :: Nil if xs.isEmpty then zero - else xs}: @unchecked + else xs}.runtimeChecked val j :: k = (for case (x: String) <- xs - yield x): @unchecked + yield x).runtimeChecked -val (_: Int | _: AnyRef) = (??? : AnyRef): @unchecked +val (_: Int | _: AnyRef) = (??? : AnyRef).runtimeChecked diff --git a/tests/rewrites/refutable-pattern-bindings.scala b/tests/rewrites/refutable-pattern-bindings.scala index eb796662e207..ce6ad437fc64 100644 --- a/tests/rewrites/refutable-pattern-bindings.scala +++ b/tests/rewrites/refutable-pattern-bindings.scala @@ -21,7 +21,7 @@ val e :: f = val j :: k = for - (x: String) <- xs + case (x: String) <- xs yield x val (_: Int | _: AnyRef) = ??? : AnyRef diff --git a/tests/warn/i16649-refutable.check b/tests/warn/i16649-refutable.check index a6cacfc691ee..53bd7f943fb2 100644 --- a/tests/warn/i16649-refutable.check +++ b/tests/warn/i16649-refutable.check @@ -3,6 +3,6 @@ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | pattern binding uses refutable extractor `'{...}` | - | If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression, + | If this usage is intentional, this can be communicated by adding `.runtimeChecked` after the expression, | which may result in a MatchError at runtime. - | This patch can be rewritten automatically under -rewrite -source 3.2-migration. + | This patch can be rewritten automatically under -rewrite -source 3.8-migration.