Skip to content

Commit ecb1fd3

Browse files
authored
Suggest runtimeChecked in warning/error messages (#24672)
Now that `runtimeChecked` will be stable in 3.8.0, suggest `.runtimeChecked` instead of `: @unchecked` ~~also, change the patch applied under 3.2-migration.~~ Edit: pre 3.8, the warning and patch still suggest to use `: @unchecked`. From 3.8 onwards the warning and patch will apply `.runtimeChecked` instead fixes #23946
1 parent 0bb0433 commit ecb1fd3

14 files changed

+177
-43
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,8 +1054,9 @@ trait Checking {
10541054
}
10551055

10561056
/** Check that pattern `pat` is irrefutable for scrutinee type `sel.tpe`.
1057-
* This means `sel` is either marked @unchecked or `sel.tpe` conforms to the
1058-
* pattern's type. If pattern is an UnApply, also check that the extractor is
1057+
* This means `sel` is either marked `: @RuntimeChecked`, `: @unchecked` (old style),
1058+
* or `sel.tpe` conforms to the pattern's type. If pattern is an Unapply,
1059+
* also check that the extractor is
10591060
* irrefutable, and do the check recursively.
10601061
*/
10611062
def checkIrrefutable(sel: Tree, pat: Tree, isPatDef: Boolean)(using Context): Boolean = {
@@ -1068,7 +1069,7 @@ trait Checking {
10681069
import Reason.*
10691070
val message = reason match
10701071
case NonConforming =>
1071-
var reportedPt = pt.dropAnnot(defn.UncheckedAnnot)
1072+
var reportedPt = pt.dropAnnot(defn.UncheckedAnnot).dropAnnot(defn.RuntimeCheckedAnnot)
10721073
if !pat.tpe.isSingleton then reportedPt = reportedPt.widen
10731074
val problem = if pat.tpe <:< reportedPt then "is more specialized than" else "does not match"
10741075
em"pattern's type ${pat.tpe} $problem the right hand side expression's type $reportedPt"
@@ -1084,7 +1085,10 @@ trait Checking {
10841085
else em"pattern binding uses refutable extractor `$extractor`"
10851086

10861087
val fix =
1087-
if isPatDef then "adding `: @unchecked` after the expression"
1088+
if isPatDef then
1089+
val patchText =
1090+
if sourceVersion.isAtLeast(`3.8`) then ".runtimeChecked" else ": @unchecked"
1091+
s"adding `$patchText` after the expression"
10881092
else "adding the `case` keyword before the full pattern"
10891093
val addendum =
10901094
if isPatDef then "may result in a MatchError at runtime"
@@ -1097,7 +1101,9 @@ trait Checking {
10971101
case NonConforming => sel.srcPos
10981102
case RefutableExtractor => pat.source.atSpan(pat.span `union` sel.span)
10991103
else pat.srcPos
1100-
def rewriteMsg = Message.rewriteNotice("This patch", `3.2-migration`)
1104+
def rewriteMsg = Message.rewriteNotice("This patch",
1105+
if isPatDef && sourceVersion.isAtLeast(`3.8`) then `3.8-migration` else `3.2-migration`
1106+
)
11011107
report.errorOrMigrationWarning(
11021108
message.append(
11031109
i"""|
@@ -1106,7 +1112,7 @@ trait Checking {
11061112
|which $addendum.$rewriteMsg"""),
11071113
pos,
11081114
// we tighten for-comprehension without `case` to error in 3.4,
1109-
// but we keep pat-defs as warnings for now ("@unchecked"),
1115+
// but we keep pat-defs as warnings for now (".runtimeChecked"),
11101116
// until we propose an alternative way to assert exhaustivity to the typechecker.
11111117
if isPatDef then MigrationVersion.ForComprehensionUncheckedPathDefs
11121118
else MigrationVersion.ForComprehensionPatternWithoutCase

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,10 +2213,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22132213
typedMatchFinish(tree, sel1, selType, tree.cases, pt)
22142214
}
22152215

2216-
/** Are some form of brackets necessary to annotate the tree `sel` as `@unchecked`?
2216+
/** Are some form of brackets necessary to annotate the tree `sel` as `.runtimeChecked`?
22172217
* If so, return a Some(opening bracket, closing bracket), otherwise None.
22182218
*/
2219-
def uncheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match
2219+
def runtimeCheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match
22202220
case _: untpd.If
22212221
| _: untpd.Match
22222222
| _: untpd.ForYield
@@ -2235,20 +2235,24 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22352235
&& sourceVersion.isAtLeast(`3.2`)
22362236
&& sourceVersion.isMigrating
22372237
then
2238-
if isPatDef then uncheckedBrackets(tree.selector) match
2238+
if isPatDef then
2239+
val patchText =
2240+
if sourceVersion.isAtLeast(`3.8`) then ".runtimeChecked"
2241+
else ": @unchecked"
2242+
runtimeCheckedBrackets(tree.selector) match
22392243
case None =>
2240-
patch(Span(tree.selector.span.end), ": @unchecked")
2244+
patch(Span(tree.selector.span.end), patchText)
22412245
case Some(bl, br) =>
22422246
patch(Span(tree.selector.span.start), s"$bl")
2243-
patch(Span(tree.selector.span.end), s"$br: @unchecked")
2247+
patch(Span(tree.selector.span.end), s"$br$patchText")
22442248
else
22452249
patch(Span(tree.span.start), "case ")
22462250

22472251
// skip exhaustivity check in later phase
22482252
// TODO: move the check above to patternMatcher phase
2249-
val uncheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.UncheckedAnnot, tree.selector.span))
2253+
val runtimeCheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.RuntimeCheckedAnnot, tree.selector.span))
22502254
tpd.cpy.Match(result)(
2251-
selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe, inferred = true)),
2255+
selector = tpd.Typed(sel, tpd.TypeTree(runtimeCheckedTpe, inferred = true)),
22522256
cases = result.cases
22532257
)
22542258
case _ =>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ class CompilationTests {
7070
compileFile("tests/rewrites/private-this.scala", defaultOptions.and("-rewrite", "-source", "future-migration")),
7171
compileFile("tests/rewrites/alphanumeric-infix-operator.scala", defaultOptions.and("-rewrite", "-source", "future-migration")),
7272
compileFile("tests/rewrites/filtering-fors.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")),
73-
compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")),
73+
compileFile("tests/rewrites/refutable-pattern-bindings-old.scala", defaultOptions.and("-rewrite", "-source", "3.2-migration")),
74+
compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "3.8-migration")),
7475
compileFile("tests/rewrites/i8982.scala", defaultOptions.and("-indent", "-rewrite")),
7576
compileFile("tests/rewrites/i9632.scala", defaultOptions.and("-indent", "-rewrite")),
7677
compileFile("tests/rewrites/i11895.scala", defaultOptions.and("-indent", "-rewrite")),

tests/neg/i11118.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
-- Warning: tests/neg/i11118.scala:2:12 --------------------------------------------------------------------------------
22
2 |val (a,b) = (1,2,3) // error // warning
33
| ^^^^^^^
4-
| pattern's type (Any, Any) does not match the right hand side expression's type (Int, Int, Int)
4+
| pattern's type (Any, Any) does not match the right hand side expression's type (Int, Int, Int)
55
|
6-
| If the narrowing 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.
6+
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
7+
| which may result in a MatchError at runtime.
8+
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
99
-- Error: tests/neg/i11118.scala:2:4 -----------------------------------------------------------------------------------
1010
2 |val (a,b) = (1,2,3) // error // warning
1111
| ^
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:6:14 --------------------------------------------------
2+
6 | for Positive(i) <- List(1, 2, 3) do () // 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 the `case` keyword before the full pattern,
7+
| which will result in a filtering for expression (using `withFilter`).
8+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
9+
-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:11:11 -------------------------------------------------
10+
11 | for ((x: String) <- xs) do () // error: pattern type more specialized
11+
| ^^^^^^
12+
| pattern's type String is more specialized than the right hand side expression's type AnyRef
13+
|
14+
| If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
15+
| which will result in a filtering for expression (using `withFilter`).
16+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
17+
-- Error: tests/neg/refutable-pattern-binding-messages-old.scala:15:13 -------------------------------------------------
18+
15 | for none @ None <- ys do () // error: pattern type does not match
19+
| ^^^^
20+
| pattern's type None.type does not match the right hand side expression's type (x$1 : Option[?])
21+
|
22+
| If the narrowing is intentional, this can be communicated by adding the `case` keyword before the full pattern,
23+
| which will result in a filtering for expression (using `withFilter`).
24+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
25+
-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:5:14 ------------------------------------------------
26+
5 | val Positive(p) = 5 // warn: 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+
-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:10:20 -----------------------------------------------
34+
10 | val i :: is = List(1, 2, 3) // warn: 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.
41+
-- Warning: tests/neg/refutable-pattern-binding-messages-old.scala:16:10 -----------------------------------------------
42+
16 | val 1 = 2 // warn: pattern type does not match
43+
| ^
44+
| pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int)
45+
|
46+
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
47+
| which may result in a MatchError at runtime.
48+
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//> using options -source 3.7
2+
object Test {
3+
// refutable extractor
4+
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }
5+
val Positive(p) = 5 // warn: refutable extractor
6+
for Positive(i) <- List(1, 2, 3) do () // error: refutable extractor
7+
8+
// more specialized
9+
val xs: List[AnyRef] = ???
10+
val i :: is = List(1, 2, 3) // warn: pattern type more specialized
11+
for ((x: String) <- xs) do () // error: pattern type more specialized
12+
13+
// does not match
14+
val ys: List[Option[?]] = ???
15+
for none @ None <- ys do () // error: pattern type does not match
16+
val 1 = 2 // warn: pattern type does not match
17+
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,22 @@
2727
| ^^^^^^^^^^^^^^^
2828
| pattern binding uses refutable extractor `Test.Positive`
2929
|
30-
| If this usage is intentional, this can be communicated by adding `: @unchecked` after the expression,
30+
| If this usage is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
3131
| which may result in a MatchError at runtime.
32-
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
32+
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
3333
-- Warning: tests/neg/refutable-pattern-binding-messages.scala:10:20 ---------------------------------------------------
3434
10 | val i :: is = List(1, 2, 3) // warn: pattern type more specialized
3535
| ^^^^^^^^^^^^^
36-
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
36+
| pattern's type ::[Int] is more specialized than the right hand side expression's type List[Int]
3737
|
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.
38+
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
39+
| which may result in a MatchError at runtime.
40+
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
4141
-- Warning: tests/neg/refutable-pattern-binding-messages.scala:16:10 ---------------------------------------------------
4242
16 | val 1 = 2 // warn: pattern type does not match
4343
| ^
44-
| pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int)
44+
| pattern's type (1 : Int) does not match the right hand side expression's type (2 : Int)
4545
|
46-
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
47-
| which may result in a MatchError at runtime.
48-
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
46+
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
47+
| which may result in a MatchError at runtime.
48+
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
//> using options -source 3.8
22
object Test {
33
// refutable extractor
44
object Positive { def unapply(i: Int): Option[Int] = Some(i).filter(_ > 0) }

tests/neg/t5702-neg-bad-and-wild.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
-- Warning: tests/neg/t5702-neg-bad-and-wild.scala:22:20 ---------------------------------------------------------------
5858
22 | val K(x @ _*) = k
5959
| ^
60-
| pattern's type Int* does not match the right hand side expression's type Int
60+
| pattern's type Int* does not match the right hand side expression's type Int
6161
|
62-
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
63-
| which may result in a MatchError at runtime.
64-
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.
62+
| If the narrowing is intentional, this can be communicated by adding `.runtimeChecked` after the expression,
63+
| which may result in a MatchError at runtime.
64+
| This patch can be rewritten automatically under -rewrite -source 3.8-migration.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// NOTE: this file is compiled under -source 3.2-migration (i.e. it will rewrite to `: @unchecked`)
2+
// see refutable-pattern-bindings.scala for the version compiled under -source 3.8-migration (which rewrites to `.runtimeChecked`)
3+
val xs: List[Any] = ???
4+
5+
val hd :: tl = (xs match
6+
case Nil => null :: xs
7+
case _ => xs): @unchecked
8+
9+
val h :: t = xs: @unchecked
10+
11+
val a :: b =
12+
(if xs.isEmpty then null :: xs
13+
else xs): @unchecked
14+
15+
val c :: d =
16+
(try xs.head :: xs
17+
catch case _: NoSuchElementException => null :: xs): @unchecked
18+
19+
val e :: f =
20+
{val zero = null :: Nil
21+
if xs.isEmpty then zero
22+
else xs}: @unchecked
23+
24+
val j :: k =
25+
(for
26+
case (x: String) <- xs
27+
yield x): @unchecked
28+
29+
val (_: Int | _: AnyRef) = (??? : AnyRef): @unchecked

0 commit comments

Comments
 (0)