You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
would also allow throwing `E1` or `E2` inside the method's body but might cause problems when someone tried to call this method
104
+
from another method declaring its `CanThrow` capabilities like in the earlier examples.
105
+
This is because `CanThrow` has a contravariant type parameter so `CanThrow[E1 | E2]` is a subtype of both `CanThrow[E1]` and `CanThrow[E2]`.
106
+
Hence the presence of a given instance of `CanThrow[E1 | E2]` in scope satisfies the requirement for `CanThrow[E1]` and `CanThrow[E2]`
107
+
but given instances of `CanThrow[E1]` and `CanThrow[E2]` cannot be combined to provide and instance of `CanThrow[E1 | E2]`.
108
+
109
+
**Note 2:** One should keep in mind that `|` binds its left and right arguments more tightly than `throws` so `A | B throws E1 | E2` means `(A | B) throws (Ex1 | Ex2)`, not `A | (B throws E1) | E2`.
110
+
98
111
The `CanThrow`/`throws` combo essentially propagates the `CanThrow` requirement outwards. But where are these capabilities created in the first place? That's in the `try` expression. Given a `try` like this:
99
112
100
113
```scala
@@ -105,17 +118,29 @@ catch
105
118
...
106
119
caseexN: ExN=> handlerN
107
120
```
108
-
the compiler generates capabilities for `CanThrow[Ex1]`, ..., `CanThrow[ExN]` that are in scope as givens in`body`. It does this by augmenting the `try` roughly as follows:
121
+
the compiler generates an accumulated capability of type `CanThrow[Ex1 | ... | Ex2]` that is available as a given in the scope of`body`. It does this by augmenting the `try` roughly as follows:
109
122
```scala
110
123
try
111
-
erased givenCanThrow[Ex1] =???
112
-
...
113
-
erased givenCanThrow[ExN] =???
124
+
erased givenCanThrow[Ex1| ... |ExN] =???
114
125
body
115
126
catch ...
116
127
```
117
-
Note that the right-hand side of all givens is `???` (undefined). This is OK since
118
-
these givens are erased; they will not be executed at runtime.
128
+
Note that the right-hand side of the synthesized given is `???` (undefined). This is OK since
129
+
this given is erased; it will not be executed at runtime.
130
+
131
+
**Note 1:** The `saferExceptions` feature is designed to work only with checked exceptions. An exception type is _checked_ if it is a subtype of
132
+
`Exception` but not of `RuntimeException`. The signature of `CanThrow` still admits `RuntimeException`s since `RuntimeException` is a proper subtype of its bound, `Exception`. But no capabilities will be generated for `RuntimeException`s. Furthermore, `throws` clauses
133
+
also may not refer to `RuntimeException`s.
134
+
135
+
**Note 2:** To keep things simple, the compiler will currently only generate capabilities
136
+
for catch clauses of the form
137
+
```scala
138
+
caseex: Ex=>
139
+
```
140
+
where `ex` is an arbitrary variable name (`_` is also allowed), and `Ex` is an arbitrary
141
+
checked exception type. Constructor patterns such as `Ex(...)` or patterns with guards
142
+
are not allowed. The compiler will issue an error if one of these is used to catch
143
+
a checked exception and `saferExceptions` is enabled.
119
144
120
145
## An Example
121
146
@@ -133,17 +158,17 @@ def f(x: Double): Double =
133
158
```
134
159
You'll get this error message:
135
160
```
136
-
9 | if x < limit then x * x else throw LimitExceeded()
137
-
| ^^^^^^^^^^^^^^^^^^^^^
138
-
|The capability to throw exception LimitExceeded is missing.
139
-
|The capability can be provided by one of the following:
140
-
| - A using clause `(using CanThrow[LimitExceeded])`
141
-
| - A `throws` clause in a result type such as `X throws LimitExceeded`
142
-
| - an enclosing `try` that catches LimitExceeded
143
-
|
144
-
|The following import might fix the problem:
145
-
|
146
-
| import unsafeExceptions.canThrowAny
161
+
if x < limit then x * x else throw LimitExceeded()
162
+
^^^^^^^^^^^^^^^^^^^^^
163
+
The capability to throw exception LimitExceeded is missing.
164
+
The capability can be provided by one of the following:
165
+
- Adding a using clause `(using CanThrow[LimitExceeded])` to the definition of the enclosing method
166
+
- Adding `throws LimitExceeded` clause after the result type of the enclosing method
167
+
- Wrapping this piece of code with a `try` block that catches LimitExceeded
168
+
169
+
The following import might fix the problem:
170
+
171
+
import unsafeExceptions.canThrowAny
147
172
```
148
173
As the error message implies, you have to declare that `f` needs the capability to throw a `LimitExceeded` exception. The most concise way to do so is to add a `throws` clause:
149
174
```scala
@@ -179,20 +204,6 @@ closure may refer to capabilities in its free variables. This means that `map` i
179
204
already effect polymorphic even though we did not change its signature at all.
180
205
So the takeaway is that the effects as capabilities model naturally provides for effect polymorphism whereas this is something that other approaches struggle with.
181
206
182
-
**Note 1:** The compiler will only treat checked exceptions that way. An exception type is _checked_ if it is a subtype of
183
-
`Exception` but not of `RuntimeException`. The signature of `CanThrow` still admits `RuntimeException`s since `RuntimeException` is a proper subtype of its bound, `Exception`. But no capabilities will be generated for `RuntimeException`s. Furthermore, `throws` clauses
184
-
also may not refer to `RuntimeException`s.
185
-
186
-
**Note 2:** To keep things simple, the compiler will currently only generate capabilities
187
-
for catch clauses of the form
188
-
```scala
189
-
caseex: Ex=>
190
-
```
191
-
where `ex` is an arbitrary variable name (`_` is also allowed), and `Ex` is an arbitrary
192
-
checked exception type. Constructor patterns such as `Ex(...)` or patterns with guards
193
-
are not allowed. The compiler will issue an error if one of these is used to catch
194
-
a checked exception and `saferExceptions` is enabled.
195
-
196
207
## Gradual Typing Via Imports
197
208
198
209
Another advantage is that the model allows a gradual migration from current unchecked exceptions to safer exceptions. Imagine for a moment that `experimental.saferExceptions` is turned on everywhere. There would be lots of code that breaks since functions have not yet been properly annotated with `throws`. But it's easy to create an escape hatch that lets us ignore the breakages for a while: simply add the import
* a given of class `CanThrow[Ex]` to be available.
8
8
*/
9
9
@experimental
10
-
@implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - A using clause `(using CanThrow[${E}])`\n - A `throws` clause in a result type such as `X throws ${E}`\n - an enclosing `try` that catches ${E}")
10
+
@implicitNotFound("The capability to throw exception ${E} is missing.\nThe capability can be provided by one of the following:\n - Adding a using clause `(using CanThrow[${E}])` to the definition of the enclosing method\n - Adding `throws ${E}` clause after the result type of the enclosing method\n - Wrapping this piece of code with a `try` block that catches ${E}")
0 commit comments