Skip to content

Commit 677560c

Browse files
committed
add method, annotation and test cases
1 parent 0e60d36 commit 677560c

File tree

10 files changed

+92
-4
lines changed

10 files changed

+92
-4
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,7 @@ class Definitions {
10341034
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
10351035
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
10361036
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
1037+
@tu lazy val RuntimeCheckedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.RuntimeChecked")
10371038
@tu lazy val SourceFileAnnot: ClassSymbol = requiredClass("scala.annotation.internal.SourceFile")
10381039
@tu lazy val ScalaSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaSignature")
10391040
@tu lazy val ScalaLongSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaLongSignature")

compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,15 @@ object ExtractSemanticDB:
458458
def unapply(tree: ValDef)(using Context): Option[(Tree, Tree)] = tree.rhs match
459459

460460
case Match(Typed(selected: Tree, tpt: TypeTree), CaseDef(pat: Tree, _, _) :: Nil)
461-
if tpt.span.exists && !tpt.span.hasLength && tpt.tpe.isAnnotatedByUnchecked =>
461+
if tpt.span.exists && !tpt.span.hasLength && tpt.tpe.isAnnotatedByUncheckedOrRuntimeChecked =>
462462
Some((pat, selected))
463463

464464
case _ => None
465465

466466
extension (tpe: Types.Type)
467-
private inline def isAnnotatedByUnchecked(using Context) = tpe match
468-
case Types.AnnotatedType(_, annot) => annot.symbol == defn.UncheckedAnnot
467+
private inline def isAnnotatedByUncheckedOrRuntimeChecked(using Context) = tpe match
468+
case Types.AnnotatedType(_, annot) =>
469+
annot.symbol == defn.UncheckedAnnot || annot.symbol == defn.RuntimeCheckedAnnot
469470
case _ => false
470471

471472
def collectPats(pat: Tree): List[Tree] =

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ object SpaceEngine {
794794
}
795795

796796
!sel.tpe.hasAnnotation(defn.UncheckedAnnot)
797+
&& !sel.tpe.hasAnnotation(defn.RuntimeCheckedAnnot)
797798
&& {
798799
ctx.settings.YcheckAllPatmat.value
799800
|| isCheckable(sel.tpe)
@@ -903,7 +904,7 @@ object SpaceEngine {
903904
def checkMatch(m: Match)(using Context): Unit =
904905
checkMatchExhaustivityOnly(m)
905906
if reachabilityCheckable(m.selector) then checkReachability(m)
906-
907+
907908
def checkMatchExhaustivityOnly(m: Match)(using Context): Unit =
908909
if exhaustivityCheckable(m.selector) then checkExhaustivity(m)
909910
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala.annotation.internal
2+
3+
import scala.annotation.Annotation
4+
import scala.annotation.experimental
5+
6+
/**An annotation marking an intention that all checks on a value can be reliably performed at runtime.
7+
*
8+
* The compiler will remove certain static checks except those that can't be performed at runtime.
9+
*/
10+
@experimental
11+
final class RuntimeChecked() extends Annotation

library/src/scala/runtime/stdLibPatches/Predef.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala.runtime.stdLibPatches
22

33
import scala.annotation.experimental
4+
import scala.annotation.internal.RuntimeChecked
45

56
object Predef:
67
import compiletime.summonFrom
@@ -80,4 +81,16 @@ object Predef:
8081
@experimental
8182
infix type is[A <: AnyKind, B <: Any{type Self <: AnyKind}] = B { type Self = A }
8283

84+
extension (x: Any)
85+
/**Asserts that a term should be exempt from static checks that can be reliably checked at runtime.
86+
* @example {{{
87+
* val xs: Option[Int] = Some(1)
88+
* xs.runtimeChecked match {
89+
* case is: Some[Int] => is.get
90+
* } // no warning about exhaustiveness, as all patterns can be checked at runtime.
91+
* }}}
92+
*/
93+
@experimental
94+
inline def runtimeChecked: x.type @RuntimeChecked = x: @RuntimeChecked
95+
8396
end Predef

tests/neg/runtimeChecked-2.check

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- [E030] Match case Unreachable Warning: tests/neg/runtimeChecked-2.scala:13:11 ---------------------------------------
2+
13 | case is: Some[t] => ??? // unreachable
3+
| ^^^^^^^^^^^
4+
| Unreachable case
5+
No warnings can be incurred under -Werror (or -Xfatal-warnings)

tests/neg/runtimeChecked-2.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using options -Werror -source:future
2+
3+
import annotation.experimental
4+
5+
@experimental
6+
object Foo {
7+
8+
val xs: Option[Int] = Some(1)
9+
10+
def test: Int =
11+
xs.runtimeChecked match { // this test asserts that reachability is not avoided by runtimeChecked
12+
case is: Some[t] => is.get
13+
case is: Some[t] => ??? // unreachable
14+
}
15+
}
16+
// nopos-error: No warnings can be incurred under -Werror (or -Xfatal-warnings)

tests/neg/runtimeChecked.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E092] Pattern Match Unchecked Warning: tests/neg/runtimeChecked.scala:14:11 ----------------------------------------
2+
14 | case is: ::[Int/* can not be checked so still err */] => is.head
3+
| ^
4+
|the type test for ::[Int] cannot be checked at runtime because its type arguments can't be determined from List[Any]
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
No warnings can be incurred under -Werror (or -Xfatal-warnings)

tests/neg/runtimeChecked.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//> using options -Werror -source:future
2+
3+
import annotation.experimental
4+
5+
@experimental
6+
object Foo {
7+
8+
val xs: List[Any] = List(1: Any)
9+
10+
def test: Int =
11+
xs.runtimeChecked match { // this test asserts that unsound type tests still require @unchecked
12+
// tests/run/runtimeChecked.scala adds @unchecked to the
13+
// unsound type test to avoid the warning.
14+
case is: ::[Int/* can not be checked so still err */] => is.head
15+
}
16+
}
17+
// nopos-error: No warnings can be incurred under -Werror (or -Xfatal-warnings)

tests/run/runtimeChecked.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using options -Werror -source:future
2+
3+
import annotation.experimental
4+
5+
6+
val xs: List[Any] = List(1: Any)
7+
8+
@experimental
9+
@main
10+
def Test: Unit =
11+
val head = xs.runtimeChecked match {
12+
// tests/neg/runtimeChecked.scala asserts that @unchecked is
13+
// still needed for unsound type tests.
14+
case is: ::[Int @unchecked] => is.head
15+
}
16+
assert(head == 1)

0 commit comments

Comments
 (0)