Skip to content

Commit 5703424

Browse files
committed
Make runtimeChecked a standard feature
The runtimeChecked implementation was accepted in the SIP meeting of May 23, 2025. Since the the feature has been around for long, and is quite uncontroversial, I think we can just merge it for 3.8. No preview phase should be needed.
1 parent 6896fe3 commit 5703424

File tree

5 files changed

+135
-132
lines changed

5 files changed

+135
-132
lines changed

docs/_docs/reference/experimental/runtimeChecked.md

Lines changed: 1 addition & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,130 +4,4 @@ title: "The runtimeChecked method"
44
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/runtimeChecked.html
55
---
66

7-
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.
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` 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
63-
|because its type arguments can't be determined from List[Any]
64-
val res0: Int = 1
65-
```
66-
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.
67-
68-
To fully avoid warnings, as with previous Scala versions, `@unchecked` should be put on the type argument:
69-
```scala
70-
scala> xs.runtimeChecked match {
71-
| case is: ::[Int @unchecked] => is.head
72-
| }
73-
val res1: Int = 1
74-
```
75-
76-
77-
## Specification
78-
79-
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.
80-
81-
```scala
82-
package scala.annotation.internal
83-
84-
final class RuntimeChecked extends Annotation
85-
```
86-
87-
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.
88-
89-
90-
The user facing API is augmented with a new extension method `scala.Predef.runtimeChecked`, qualified for any value:
91-
```scala
92-
package scala
93-
94-
import scala.annotation.internal.RuntimeChecked
95-
96-
object Predef:
97-
...
98-
extension [T](x: T)
99-
inline def runtimeChecked: x.type @RuntimeChecked =
100-
x: @RuntimeChecked
101-
```
102-
103-
The `runtimeChecked` method returns its argument, refining its type with the `RuntimeChecked` annotation.
104-
105-
## Motivation
106-
107-
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:
108-
```scala
109-
def xs: List[Any] = ???
110-
val y :: ys = xs
111-
```
112-
113-
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.
114-
As an escape hatch in 3.2 we recommended to use a type ascription of `: @unchecked`:
115-
```
116-
-- Warning: ../../new/test.scala:6:16 -----------------------
117-
6 | val y :: ys = xs
118-
| ^^
119-
|pattern's type ::[Any] is more specialized than the right
120-
|hand side expression's type List[Any]
121-
|
122-
|If the narrowing is intentional, this can be communicated
123-
|by adding `: @unchecked` after the expression,
124-
|which may result in a MatchError at runtime.
125-
```
126-
127-
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.
128-
129-
The `@unchecked` annotation is still retained for silencing warnings on unsound type tests.
130-
131-
### Restoring Scala 2.13 semantics with runtimeChecked
132-
133-
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.
7+
This is now a standard Scala 3 feature. See [https://docs.scala-lang.org/scala3/reference/experimental/runtimeChecked.html] for an up-to-date doc page.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
layout: doc-page
3+
title: "The runtimeChecked method"
4+
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/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 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.
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` 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
63+
|because its type arguments can't be determined from List[Any]
64+
val res0: Int = 1
65+
```
66+
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.
67+
68+
To fully avoid warnings, as with previous Scala versions, `@unchecked` should be put on the type argument:
69+
```scala
70+
scala> xs.runtimeChecked match {
71+
| case is: ::[Int @unchecked] => is.head
72+
| }
73+
val res1: Int = 1
74+
```
75+
76+
77+
## Specification
78+
79+
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.
80+
81+
```scala
82+
package scala.annotation.internal
83+
84+
final class RuntimeChecked extends Annotation
85+
```
86+
87+
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.
88+
89+
90+
The user facing API is augmented with a new extension method `scala.Predef.runtimeChecked`, qualified for any value:
91+
```scala
92+
package scala
93+
94+
import scala.annotation.internal.RuntimeChecked
95+
96+
object Predef:
97+
...
98+
extension [T](x: T)
99+
inline def runtimeChecked: x.type @RuntimeChecked =
100+
x: @RuntimeChecked
101+
```
102+
103+
The `runtimeChecked` method returns its argument, refining its type with the `RuntimeChecked` annotation.
104+
105+
## Motivation
106+
107+
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:
108+
```scala
109+
def xs: List[Any] = ???
110+
val y :: ys = xs
111+
```
112+
113+
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.
114+
As an escape hatch in 3.2 we recommended to use a type ascription of `: @unchecked`:
115+
```
116+
-- Warning: ../../new/test.scala:6:16 -----------------------
117+
6 | val y :: ys = xs
118+
| ^^
119+
|pattern's type ::[Any] is more specialized than the right
120+
|hand side expression's type List[Any]
121+
|
122+
|If the narrowing is intentional, this can be communicated
123+
|by adding `: @unchecked` after the expression,
124+
|which may result in a MatchError at runtime.
125+
```
126+
127+
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.
128+
129+
The `@unchecked` annotation is still retained for silencing warnings on unsound type tests.
130+
131+
### Restoring Scala 2.13 semantics with runtimeChecked
132+
133+
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.

docs/sidebar.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ subsection:
8888
- page: reference/other-new-features/experimental-defs.md
8989
- page: reference/other-new-features/preview-defs.md
9090
- page: reference/other-new-features/binary-literals.md
91+
- page: reference/other-new-features/runtimeChecked.md
9192
- title: Other Changed Features
9293
directory: changed-features
9394
index: reference/changed-features/changed-features.md
@@ -168,7 +169,6 @@ subsection:
168169
- page: reference/experimental/tupled-function.md
169170
- page: reference/experimental/modularity.md
170171
- page: reference/experimental/typeclasses.md
171-
- page: reference/experimental/runtimeChecked.md
172172
- page: reference/experimental/unrolled-defs.md
173173
- page: reference/experimental/package-object-values.md
174174
- page: reference/syntax.md

library/src/scala/annotation/internal/RuntimeChecked.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ import scala.annotation.experimental
77
*
88
* The compiler will remove certain static checks except those that can't be performed at runtime.
99
*/
10-
@experimental
1110
final class RuntimeChecked() extends Annotation

tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,6 @@ val experimentalDefinitionInLibrary = Set(
101101
// Need quotedPatternsWithPolymorphicFunctions enabled.
102102
"scala.quoted.runtime.Patterns$.higherOrderHoleWithTypes",
103103

104-
// New feature: SIP 57 - runtimeChecked replacement of @unchecked
105-
"scala.Predef$.runtimeChecked", "scala.annotation.internal.RuntimeChecked",
106-
107104
// New feature: SIP 61 - @unroll annotation
108105
"scala.annotation.unroll"
109106
)

0 commit comments

Comments
 (0)