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
[SE-0413] Shrink proposal to what was implemented in Swift 6.0 (#2535)
* [SE-0413] Shrink proposal to what was implemented in Swift 6.0
Error type inference for closures did not make it into Swift 6.0,
although the rest of this proposal did (as well as some of the future
directions, e.g., for `AsyncSequence`). Move this inference, and the
other changes behind the upcoming flag `FullTypedThrows`, out to
"Future Directions" and mark this proposal as (otherwise) implemented.
* Fix mangled sentence
@@ -13,6 +12,8 @@ Swift's error handling model allows functions and closures marked `throws` to no
13
12
14
13
This proposal introduces the ability to specify that functions and closures only throw errors of a particular concrete type.
15
14
15
+
> Note: the [originally accepted version](https://github.com/swiftlang/swift-evolution/blob/821970ae986219f88eb3f950ed787a55ce31d512/proposals/0413-typed-throws.md) of this proposal included type inference changes intended for Swift 6.0 that were behind the upcoming feature flag `FullTypedThrows`. These type inference changes did not get implemented in Swift 6.0, and have therefore been removed from this proposal and placed into "Future Directions" so they can be revisited once implemented.
16
+
16
17
## Table of Contents
17
18
18
19
[Typed throws](#typed-throws)
@@ -46,7 +47,6 @@ This proposal introduces the ability to specify that functions and closures only
46
47
*[Protocol conformance](#protocol-conformance)
47
48
*[Override checking](#override-checking)
48
49
*[Type inference](#type-inference)
49
-
*[Closure thrown type inference](#closure-thrown-type-inference)
50
50
*[Associated type inference](#associated-type-inference)
*[Specific thrown error types for distributed actors](#specific-thrown-error-types-for-distributed-actors)
@@ -687,7 +688,7 @@ do /*infers throws(CatError) in Swift 6 */ {
687
688
}
688
689
```
689
690
690
-
> **Swift 6**: To prevent this source compatibility issue, we can refine the rule slightly for Swift 5 code bases to specify that any `throw` statement always throws a value of type `any Error`. That way, one can only get a caught error type more specific than `any Error` when the both of the `do..catch` contains no `throw` statements and all of the `try` operations are using functions that make use of typed throws.
691
+
To prevent this source compatibility issue, we refine the rule slightly to specify that any `throw` statement always throws a value of type `any Error`. That way, one can only get a caught error type more specific than `any Error` when the both of the `do..catch` contains no `throw` statements and all of the `try` operations are using functions that make use of typed throws.
691
692
692
693
Note that the only way to write an exhaustive `do...catch` statement is to have an unconditional `catch` block. The dynamic checking provided by `is` or `as` patterns in the `catch` block cannot be used to make a catch exhaustive, even if the type specified is the same as the type thrown from the body of the `do`:
693
694
@@ -787,7 +788,7 @@ The standard `rethrows` checking rejects the call to `filter` because, technical
787
788
2. Has no protocol requirements on `E` other than that it conform to the `Error` protocol, and
788
789
3. Any parameters of throwing function type throw the specific error type `E`.
789
790
790
-
to be a rethrowing function for the purposes of `rethrows` checking in its caller. This compatibility feature introduces a small soundness hole in `rethrows` functions, so it is temporary: it is only available in Swift 5, and is removed when the `FullTypedThrows` upcoming feature is enabled.
791
+
to be a rethrowing function for the purposes of `rethrows` checking in its caller. This compatibility feature introduces a small soundness hole in `rethrows` functions that can only be removed with improvements to type inference behavior.
791
792
792
793
#### Opaque thrown error types
793
794
@@ -924,56 +925,6 @@ class Subsubclass: Subclass {
924
925
925
926
The type checker can infer thrown error types in a number of different places, making it easier to carry specific thrown type information through a program without additional annotation. This section covers the various ways in which thrown errors interact with type inference.
926
927
927
-
#### Closure thrown type inference
928
-
929
-
Function declarations must always explicitly specify whether they throw, optionally providing a specific thrown error type. For closures, whether they throw or not is inferred by the Swift compiler. Specifically, the Swift compiler looks at the structure of body of the closure. If the body of the closure contains a throwing site (either a `throw` statement or a `try` expression) that is not within an exhaustive `do...catch` (i.e., one that has an unconditional `catch` clause), then the closure is inferred to be `throws`. Otherwise, it is non-throwing. Here are some examples:
930
-
931
-
```swift
932
-
{ throwE() } // throws
933
-
934
-
{ trycall() } // throws
935
-
936
-
{
937
-
do {
938
-
trycall()
939
-
} catchlet e asCatError {
940
-
// ...
941
-
}
942
-
} // throws, the do...catch is not exhaustive
943
-
944
-
{
945
-
do {
946
-
trycall()
947
-
} catche {}
948
-
// ...
949
-
}
950
-
} // does not throw, the do...catch is exhaustive
951
-
```
952
-
953
-
With typed throws, the closure type could be inferred to have a typed error by considering all of the throwing sites that aren't caught (let each have a thrown type `Ei`) and then inferring the closure's thrown error type to be `errorUnion(E1, E2, ... EN)`.
954
-
955
-
> **Swift 6**: This inference rule will change the thrown error types of existing closures that throw concrete types. For example, the following closure:
956
-
>
957
-
> ```swift
958
-
> {
959
-
>ifInt.random(in: 0..<24) <20 {
960
-
>throw CatError.asleep
961
-
> }
962
-
> }
963
-
> ```
964
-
>
965
-
> will currently be inferred as `throws`. With the rule specified here, it will be inferred as `throws(CatError)`. This could break some code that depends on the precisely inferred type. To prevent this from becoming a source compatibility problem, we apply the same rule asfor `do...catch` statements to limit inference: `throw` statements within the closure body are treated as having the type `anyError` in Swift 5. This way, one can only infer a more specific thrown error type in a closure when the `try` operations are calling functions that make use of typed errors.
966
-
>
967
-
> Note that one can explicitly specify the thrown error type of a closure to disable this type inference, which has the nice effect of also providing a contextual type forthrow statements:
968
-
>
969
-
> ```swift
970
-
> { () throws(CatError) in
971
-
>ifInt.random(in: 0..<24) <20 {
972
-
>throw .asleep
973
-
> }
974
-
> }
975
-
> ```
976
-
977
928
#### Associated type inference
978
929
979
930
An associated type can be used as the thrown error type in other protocol requirements. For example:
@@ -1064,11 +1015,7 @@ This is a mechanical transformation that is applied throughout the standard libr
1064
1015
1065
1016
## Source compatibility
1066
1017
1067
-
This proposal has called out two specific places where the introduction of typed throws into the language will affect source compatibility. In both cases, the type inference behavior of the language will differ when there are `throw` statements that throw a specific concrete type.
1068
-
1069
-
To mitigate this source compatibility problem in Swift 5, `throw` statements will be treated as always throwing `any Error`. In Swift 6, they will be treated as throwing the type of their thrown expression. One can enable the Swift 6 behavior with the [upcoming feature flag](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0362-piecemeal-future-features.md) named `FullTypedThrows`.
1070
-
1071
-
Note that the source compatibility arguments in this proposal are there to ensure that Swift code that does not use typed throws will continue to work in the same way it always has. Once a function adopts typed throws, the effect of typed throws can then ripple to its callers.
1018
+
This proposal has called out a few specific places where the introduction of typed throws into the language could affect source compatibility. However, in those places, we have opted for semantics that ensure that existing Swift code that does not change behavior, to make this proposal act as a purely additive change to the language. Once a function adopts typed throws, the effect of typed throws can then ripple to its callers.
1072
1019
1073
1020
## Effect on API resilience
1074
1021
@@ -1184,6 +1131,58 @@ This way, clients compiled against the updated standard library will always use
1184
1131
1185
1132
## Future directions
1186
1133
1134
+
### Closure thrown type inference
1135
+
1136
+
Function declarations must always explicitly specify whether they throw, optionally providing a specific thrown error type. For closures, whether they throw or not is inferred by the Swift compiler. Specifically, the Swift compiler looks at the structure of body of the closure. If the body of the closure contains a throwing site (either a `throw` statement or a `try` expression) that is not within an exhaustive `do...catch` (i.e., one that has an unconditional `catch` clause), then the closure is inferred to be `throws`. Otherwise, it is non-throwing. Here are some examples:
1137
+
1138
+
```swift
1139
+
{ throwE() } // throws
1140
+
1141
+
{ trycall() } // throws
1142
+
1143
+
{
1144
+
do {
1145
+
trycall()
1146
+
} catchlet e asCatError {
1147
+
// ...
1148
+
}
1149
+
} // throws, the do...catch is not exhaustive
1150
+
1151
+
{
1152
+
do {
1153
+
trycall()
1154
+
} catche {}
1155
+
// ...
1156
+
}
1157
+
} // does not throw, the do...catch is exhaustive
1158
+
```
1159
+
1160
+
With typed throws, the closure type could be inferred to have a typed error by considering all of the throwing sites that aren't caught (let each have a thrown type `Ei`) and then inferring the closure's thrown error type to be `errorUnion(E1, E2, ... EN)`.
1161
+
1162
+
This inference rule will change the thrown error types of existing closures that throw concrete types. For example, the following closure:
1163
+
1164
+
```swift
1165
+
{
1166
+
ifInt.random(in: 0..<24) <20 {
1167
+
throw CatError.asleep
1168
+
}
1169
+
}
1170
+
```
1171
+
1172
+
will currently be inferred as `throws`. With the rule specified here, it will be inferred as `throws(CatError)`. This could break some code that depends on the precisely inferred type. To prevent this from becoming a source compatibility problem, we apply the same rule as for `do...catch` statements to limit inference: `throw` statements within the closure body are treated as having the type `any Error` in Swift 5. This way, one can only infer a more specific thrown error type in a closure when the `try` operations are calling functions that make use of typed errors.
1173
+
1174
+
Note that one can explicitly specify the thrown error type of a closure to disable this type inference, which has the nice effect of also providing a contextual type for throw statements:
1175
+
1176
+
```swift
1177
+
{ () throws(CatError) in
1178
+
ifInt.random(in: 0..<24) <20 {
1179
+
throw .asleep
1180
+
}
1181
+
}
1182
+
```
1183
+
1184
+
Such a change would need to be under an upcoming feature flag (e.g., `FullTypedThrows`) and should also involve inference from the actual thrown error type of `throw` statements as well as closing the minor semantic hole introduced for compatibility with `rethrows` functions.
1185
+
1187
1186
### Concurrency library adoption
1188
1187
1189
1188
The concurrency library has a number of places that could benefit from the adoption of typed throws, including `Task` creation and completion, continuations, task cancellation, task groups, and async sequences and streams.
@@ -1374,6 +1373,8 @@ Removing or changing the semantics of `rethrows` would be a source-incompatible
1374
1373
1375
1374
## Revision history
1376
1375
1376
+
* Revision 6 (post-review):
1377
+
* Closure type inference did not get implemented in Swift 6.0, so this proposal has been "shrunk" down to what actually got implemented in Swift 6.0.
1377
1378
* Revision 5 (first review):
1378
1379
* Add `do throws(MyError)` { ... } syntax to allow explicit specification of the thrown error type within the body of a `do..catch` block, suppressing type inference of the thrown error type. Thank you to Becca Royal-Gordon for the idea!
0 commit comments