|
| 1 | +--- |
| 2 | +layout: doc-page |
| 3 | +title: "The runtimeChecked method" |
| 4 | +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/runtimeChecked.html |
| 5 | +--- |
| 6 | + |
| 7 | +The `runtimeChecked` method is an extension method, defined in `scala.Predef`. It can be called on any expression. An expression marked as `runtimeChecked` is exempt from certain static checks in the compiler, for example pattern match exhaustivity. It is intended to replace `: @unchecked` type ascription in these cases. |
| 8 | + |
| 9 | +## Example |
| 10 | + |
| 11 | +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. |
| 12 | + |
| 13 | +e.g. looking up an expected entry in a dynamically loaded dictionary-like structure |
| 14 | +```scala |
| 15 | +// example 1 |
| 16 | +trait AppConfig: |
| 17 | + def get(key: String): Option[String] |
| 18 | + |
| 19 | +val config: AppConfig = ??? |
| 20 | + |
| 21 | +val Some(appVersion) = config.get("appVersion").runtimeChecked |
| 22 | +``` |
| 23 | + |
| 24 | +or to assert that a value can only match some specific patterns: |
| 25 | +```scala |
| 26 | +// example 2 |
| 27 | +enum Day: |
| 28 | + case Mon, Tue, Wed, Thu, Fri, Sat, Sun |
| 29 | + |
| 30 | +val weekDay: Option[Day] = ??? |
| 31 | + |
| 32 | +weekDay.runtimeChecked match |
| 33 | + case Some(Mon | Tue | Wed | Thu | Fri) => println("got weekday") |
| 34 | +// case Some(Sat | Sun) => // weekend should not appear |
| 35 | + case None => |
| 36 | +``` |
| 37 | + |
| 38 | +In both of these cases, without `runtimeChecked` then 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. |
| 39 | + |
| 40 | +```scala |
| 41 | +// warning in example 2 when we don't add `.runtimeChecked`. |
| 42 | +-- [E029] Pattern Match Exhaustivity Warning: ---------------------------------- |
| 43 | +6 |weekDay match |
| 44 | + |^^^^^^^ |
| 45 | + |match may not be exhaustive. |
| 46 | + | |
| 47 | + |It would fail on pattern case: Some(Sat), Some(Sun) |
| 48 | +``` |
| 49 | + |
| 50 | +## Safety |
| 51 | + |
| 52 | +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: |
| 53 | +```scala |
| 54 | +scala> val xs = List(1: Any) |
| 55 | + | xs.runtimeChecked match { |
| 56 | + | case is: ::[Int] => is.head |
| 57 | + | } |
| 58 | +1 warning found |
| 59 | +-- Unchecked Warning: ---------------------------------------------------------- |
| 60 | +3 | case is: ::[Int] => is.head |
| 61 | + | ^ |
| 62 | + |the type test for ::[Int] cannot be checked at runtime because its type arguments can't be determined from List[Any] |
| 63 | +val res0: Int = 1 |
| 64 | +``` |
| 65 | +As the warning hints, `::[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. |
| 66 | + |
| 67 | +To fully avoid warnings, as with previous Scala versions, `@unchecked` should be put on the type argument: |
| 68 | +```scala |
| 69 | +scala> xs.runtimeChecked match { |
| 70 | + | case is: ::[Int @unchecked] => is.head |
| 71 | + | } |
| 72 | +val res1: Int = 1 |
| 73 | +``` |
| 74 | + |
| 75 | + |
| 76 | +## Specification |
| 77 | + |
| 78 | +We add a new annotation `scala.internal.RuntimeChecked`, this is part of the standard Scala 3 library. A programmer is not expected to use this annotation directly. |
| 79 | + |
| 80 | +```scala |
| 81 | +package scala.annotation.internal |
| 82 | + |
| 83 | +@experimental |
| 84 | +final class RuntimeChecked extends Annotation |
| 85 | +``` |
| 86 | + |
| 87 | +Any term that is the scrutinee of a pattern match, that has a type annotated with `RuntimeChecked`, is exempt from pattern match exhaustivity checking. |
| 88 | + |
| 89 | + |
| 90 | +The user facing API is provided by a new extension method `scala.Predef.runtimeChecked`, qualified for any value: |
| 91 | +```scala |
| 92 | +extension (x: Any) |
| 93 | + @experimental |
| 94 | + inline def runtimeChecked: x.type @RuntimeChecked = x: @RuntimeChecked |
| 95 | +``` |
| 96 | + |
| 97 | +The `runtimeChecked` method returns its argument, refining its type with the `RuntimeChecked` annotation. |
| 98 | + |
| 99 | +## Motivation |
| 100 | + |
| 101 | +As described in [Pattern Bindings](../changed-features/pattern-bindings.md), under `-source:future` it is an error for a pattern definition to be refutable. For instance, consider: |
| 102 | +```scala |
| 103 | +def xs: List[Any] = ??? |
| 104 | +val y :: ys = xs |
| 105 | +``` |
| 106 | + |
| 107 | +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. |
| 108 | +As an escape hatch in 3.2 we recommended to use a type ascription of `: @unchecked`: |
| 109 | +``` |
| 110 | +-- Warning: ../../new/test.scala:6:16 ------------------------------------------ |
| 111 | +6 | val y :: ys = xs |
| 112 | + | ^^ |
| 113 | + |pattern's type ::[Any] is more specialized than the right hand side expression's type List[Any] |
| 114 | + | |
| 115 | + |If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression, |
| 116 | + |which may result in a MatchError at runtime. |
| 117 | +``` |
| 118 | + |
| 119 | +We suggest that `: @unchecked` is syntactically awkward, and also a misnomer - in fact in this case the the pattern is fully checked, but the necessary checks occur at runtime. The `runtimeChecked` method is then a successor to `@unchecked` for this purpose. |
| 120 | + |
| 121 | +We propose that `@unchecked` will still be necessary for silencing warnings on unsound type tests. |
| 122 | + |
| 123 | +### Restoring Scala 2.13 semantics with runtimeChecked |
| 124 | + |
| 125 | +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. |
0 commit comments