Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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"""|
Expand All @@ -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
Expand Down
18 changes: 11 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 _ =>
Expand Down
3 changes: 2 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/i11118.check
Original file line number Diff line number Diff line change
@@ -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
| ^
Expand Down
48 changes: 48 additions & 0 deletions tests/neg/refutable-pattern-binding-messages-old.check
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 17 additions & 0 deletions tests/neg/refutable-pattern-binding-messages-old.scala
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 10 additions & 10 deletions tests/neg/refutable-pattern-binding-messages.check
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion tests/neg/refutable-pattern-binding-messages.scala
Original file line number Diff line number Diff line change
@@ -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) }
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/t5702-neg-bad-and-wild.check
Original file line number Diff line number Diff line change
Expand Up @@ -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.
29 changes: 29 additions & 0 deletions tests/rewrites/refutable-pattern-bindings-old.check
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions tests/rewrites/refutable-pattern-bindings-old.scala
Original file line number Diff line number Diff line change
@@ -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
14 changes: 7 additions & 7 deletions tests/rewrites/refutable-pattern-bindings.check
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion tests/rewrites/refutable-pattern-bindings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ val e :: f =

val j :: k =
for
(x: String) <- xs
case (x: String) <- xs
yield x

val (_: Int | _: AnyRef) = ??? : AnyRef
4 changes: 2 additions & 2 deletions tests/warn/i16649-refutable.check
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading