Edit this page on GitHub

The runtimeChecked method

The runtimeChecked method is an extension method, defined in scala.Predef. It can be called on any expression. An expression ending in .runtimeChecked is exempt from certain static checks in the compiler, for example pattern match exhaustivity. The idiom is intended to replace a : @unchecked type ascription in these cases.

Example

A common use case for runtimeChecked is to assert that a pattern will always match, either for convenience, or because there is a known invariant that the types can not express.

E.g. looking up an expected entry in a dynamically loaded dictionary-like structure:

// example 1
trait AppConfig:
  def get(key: String): Option[String]

val config: AppConfig = ???

val Some(appVersion) = config.get("appVersion").runtimeChecked

or to assert that a value can only match some specific patterns:

// example 2
enum Day:
  case Mon, Tue, Wed, Thu, Fri, Sat, Sun

val weekDay: Option[Day] = ???

weekDay.runtimeChecked match
  case Some(Mon | Tue | Wed | Thu | Fri) => println("got weekday")
// case Some(Sat | Sun) => // weekend should not appear
  case None =>

In both of these cases, without runtimeChecked there would either be an error (example 1), or a warning (example 2), because statically, the compiler knows that there could be other cases at runtime - so is right to caution the programmer.

// warning in example 2 when we don't add `.runtimeChecked`.
-- [E029] Pattern Match Exhaustivity Warning: ----------------------------------
6 |weekDay match
  |^^^^^^^
  |match may not be exhaustive.
  |
  |It would fail on pattern case: Some(Sat), Some(Sun)

Safety

The runtimeChecked method only turns off static checks that can be soundly performed at runtime. This means that patterns with unchecked type-tests will still generate warnings. For example:

scala> val xs = List(1: Any)
     | xs.runtimeChecked match {
     |   case is: ::[Int] => is.head
     | }
1 warning found
-- Unchecked Warning: ---------------------------------------
3 |  case is: ::[Int] => is.head
  |       ^
  |the type test for ::[Int] cannot be checked at runtime
  |because its type arguments can't be determined from List[Any]
val res0: Int = 1

As the warning hints, the type ::[Int] can not be tested at runtime on a value of type List[Any], so using runtimeChecked still protects the user against assertions that can not be validated.

To fully avoid warnings, as with previous Scala versions, @unchecked should be put on the type argument:

scala> xs.runtimeChecked match {
     |   case is: ::[Int @unchecked] => is.head
     | }
val res1: Int = 1

Specification

We add a new annotation scala.internal.RuntimeChecked as a part of the standard Scala 3 library. A programmer is not expected to use this annotation directly.

package scala.annotation.internal

final class RuntimeChecked extends Annotation

Any term that is the scrutinee of a pattern match, and that has a type annotated with RuntimeChecked, is exempt from pattern match exhaustivity checking.

The user facing API is augmented with a new extension method scala.Predef.runtimeChecked, qualified for any value:

package scala

import scala.annotation.internal.RuntimeChecked

object Predef:
  ...
  extension [T](x: T)
    inline def runtimeChecked: x.type @RuntimeChecked =
      x: @RuntimeChecked

The runtimeChecked method returns its argument, refining its type with the RuntimeChecked annotation.

Motivation

As described in Pattern Bindings, under -source:future it is an error for a pattern definition to be refutable. For instance, consider:

def xs: List[Any] = ???
val y :: ys = xs

This compiled without warning in 3.0, became a warning in 3.2, and we would like to make it an error by default in a future 3.x version. As an escape hatch in 3.2 we recommended to use a type ascription of : @unchecked:

-- Warning: ../../new/test.scala:6:16 -----------------------
6 |  val y :: ys = xs
  |                ^^
  |pattern's type ::[Any] is more specialized than the right
  |hand side expression's type List[Any]
  |
  |If the narrowing is intentional, this can be communicated
  |by adding `: @unchecked` after the expression,
  |which may result in a MatchError at runtime.

However, : @unchecked is syntactically awkward, and is also a misnomer - in fact in this case the pattern is fully checked, but the necessary checks occur at runtime. The runtimeChecked method is intended to replace @unchecked for this purpose.

The @unchecked annotation is still retained for silencing warnings on unsound type tests.

Restoring Scala 2.13 semantics with runtimeChecked

In Scala 3, the : @unchecked type ascription has the effect of turning off all pattern-match warnings on the match scrutinee - this differs from 2.13 in which it strictly turns off only pattern exhaustivity checking. runtimeChecked restores the semantics of Scala 2.13.