Skip to content

Commit

Permalink
add method, annotation and test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Jul 3, 2024
1 parent 594306d commit 5cb3301
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 4 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,7 @@ class Definitions {
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
@tu lazy val RuntimeCheckedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.RuntimeChecked")
@tu lazy val SourceFileAnnot: ClassSymbol = requiredClass("scala.annotation.internal.SourceFile")
@tu lazy val ScalaSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaSignature")
@tu lazy val ScalaLongSignatureAnnot: ClassSymbol = requiredClass("scala.reflect.ScalaLongSignature")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,14 +458,15 @@ object ExtractSemanticDB:
def unapply(tree: ValDef)(using Context): Option[(Tree, Tree)] = tree.rhs match

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

case _ => None

extension (tpe: Types.Type)
private inline def isAnnotatedByUnchecked(using Context) = tpe match
case Types.AnnotatedType(_, annot) => annot.symbol == defn.UncheckedAnnot
private inline def isAnnotatedByUncheckedOrRuntimeChecked(using Context) = tpe match
case Types.AnnotatedType(_, annot) =>
annot.symbol == defn.UncheckedAnnot || annot.symbol == defn.RuntimeCheckedAnnot
case _ => false

def collectPats(pat: Tree): List[Tree] =
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ object SpaceEngine {
}

!sel.tpe.hasAnnotation(defn.UncheckedAnnot)
&& !sel.tpe.hasAnnotation(defn.RuntimeCheckedAnnot)
&& {
ctx.settings.YcheckAllPatmat.value
|| isCheckable(sel.tpe)
Expand Down Expand Up @@ -903,7 +904,7 @@ object SpaceEngine {
def checkMatch(m: Match)(using Context): Unit =
checkMatchExhaustivityOnly(m)
if reachabilityCheckable(m.selector) then checkReachability(m)

def checkMatchExhaustivityOnly(m: Match)(using Context): Unit =
if exhaustivityCheckable(m.selector) then checkExhaustivity(m)
}
11 changes: 11 additions & 0 deletions library/src/scala/annotation/internal/RuntimeChecked.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package scala.annotation.internal

import scala.annotation.Annotation
import scala.annotation.experimental

/**An annotation marking an intention that all checks on a value can be reliably performed at runtime.
*
* The compiler will remove certain static checks except those that can't be performed at runtime.
*/
@experimental
final class RuntimeChecked() extends Annotation
13 changes: 13 additions & 0 deletions library/src/scala/runtime/stdLibPatches/Predef.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scala.runtime.stdLibPatches

import scala.annotation.experimental
import scala.annotation.internal.RuntimeChecked

object Predef:
import compiletime.summonFrom
Expand Down Expand Up @@ -80,4 +81,16 @@ object Predef:
@experimental
infix type is[A <: AnyKind, B <: Any{type Self <: AnyKind}] = B { type Self = A }

extension (x: Any)
/**Asserts that a term should be exempt from static checks that can be reliably checked at runtime.
* @example {{{
* val xs: Option[Int] = Some(1)
* xs.runtimeChecked match {
* case is: Some[Int] => is.get
* } // no warning about exhaustiveness, as all patterns can be checked at runtime.
* }}}
*/
@experimental
inline def runtimeChecked: x.type @RuntimeChecked = x: @RuntimeChecked

end Predef
5 changes: 5 additions & 0 deletions tests/neg/runtimeChecked-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- [E030] Match case Unreachable Warning: tests/neg/runtimeChecked-2.scala:13:11 ---------------------------------------
13 | case is: Some[t] => ??? // unreachable
| ^^^^^^^^^^^
| Unreachable case
No warnings can be incurred under -Werror (or -Xfatal-warnings)
16 changes: 16 additions & 0 deletions tests/neg/runtimeChecked-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//> using options -Werror -source:future

import annotation.experimental

@experimental
object Foo {

val xs: Option[Int] = Some(1)

def test: Int =
xs.runtimeChecked match { // this test asserts that reachability is not avoided by runtimeChecked
case is: Some[t] => is.get
case is: Some[t] => ??? // unreachable
}
}
// nopos-error: No warnings can be incurred under -Werror (or -Xfatal-warnings)
7 changes: 7 additions & 0 deletions tests/neg/runtimeChecked.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- [E092] Pattern Match Unchecked Warning: tests/neg/runtimeChecked.scala:14:11 ----------------------------------------
14 | case is: ::[Int/* can not be checked so still err */] => is.head
| ^
|the type test for ::[Int] cannot be checked at runtime because its type arguments can't be determined from List[Any]
|
| longer explanation available when compiling with `-explain`
No warnings can be incurred under -Werror (or -Xfatal-warnings)
17 changes: 17 additions & 0 deletions tests/neg/runtimeChecked.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//> using options -Werror -source:future

import annotation.experimental

@experimental
object Foo {

val xs: List[Any] = List(1: Any)

def test: Int =
xs.runtimeChecked match { // this test asserts that unsound type tests still require @unchecked
// tests/run/runtimeChecked.scala adds @unchecked to the
// unsound type test to avoid the warning.
case is: ::[Int/* can not be checked so still err */] => is.head
}
}
// nopos-error: No warnings can be incurred under -Werror (or -Xfatal-warnings)
16 changes: 16 additions & 0 deletions tests/run/runtimeChecked.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//> using options -Werror -source:future

import annotation.experimental


val xs: List[Any] = List(1: Any)

@experimental
@main
def Test: Unit =
val head = xs.runtimeChecked match {
// tests/neg/runtimeChecked.scala asserts that @unchecked is
// still needed for unsound type tests.
case is: ::[Int @unchecked] => is.head
}
assert(head == 1)

0 comments on commit 5cb3301

Please sign in to comment.