Skip to content

Commit d9097a3

Browse files
authored
[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
1 parent f358d79 commit d9097a3

File tree

1 file changed

+61
-60
lines changed

1 file changed

+61
-60
lines changed

proposals/0413-typed-throws.md

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
* Proposal: [SE-0413](0413-typed-throws.md)
44
* Authors: [Jorge Revuelta (@minuscorp)](https://github.com/minuscorp), [Torsten Lehmann](https://github.com/torstenlehmann), [Doug Gregor](https://github.com/DougGregor)
55
* Review Manager: [Steve Canon](https://github.com/stephentyrone)
6-
* Status: **Accepted**
7-
* Upcoming Feature Flag: `FullTypedThrows`
6+
* Status: **Implemented (Swift 6)**
87
* Review: [latest pitch](https://forums.swift.org/t/pitch-n-1-typed-throws/67496), [review](https://forums.swift.org/t/se-0413-typed-throws/68507), [acceptance](https://forums.swift.org/t/accepted-se-0413-typed-throws/69099)
98

109
## Introduction
@@ -13,6 +12,8 @@ Swift's error handling model allows functions and closures marked `throws` to no
1312

1413
This proposal introduces the ability to specify that functions and closures only throw errors of a particular concrete type.
1514

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+
1617
## Table of Contents
1718

1819
[Typed throws](#typed-throws)
@@ -46,7 +47,6 @@ This proposal introduces the ability to specify that functions and closures only
4647
* [Protocol conformance](#protocol-conformance)
4748
* [Override checking](#override-checking)
4849
* [Type inference](#type-inference)
49-
* [Closure thrown type inference](#closure-thrown-type-inference)
5050
* [Associated type inference](#associated-type-inference)
5151
* [Standard library adoption](#standard-library-adoption)
5252
* [Converting between throws and Result](#converting-between-throws-and-result)
@@ -55,6 +55,7 @@ This proposal introduces the ability to specify that functions and closures only
5555
* [Effect on API resilience](#effect-on-api-resilience)
5656
* [Effect on ABI stability](#effect-on-abi-stability)
5757
* [Future directions](#future-directions)
58+
* [Closure thrown type inference](#closure-thrown-type-inference)
5859
* [Standard library operations that rethrow](#standard-library-operations-that-rethrow)
5960
* [Concurrency library adoption](#concurrency-library-adoption)
6061
* [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 */ {
687688
}
688689
```
689690

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.
691692

692693
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`:
693694

@@ -787,7 +788,7 @@ The standard `rethrows` checking rejects the call to `filter` because, technical
787788
2. Has no protocol requirements on `E` other than that it conform to the `Error` protocol, and
788789
3. Any parameters of throwing function type throw the specific error type `E`.
789790

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.
791792

792793
#### Opaque thrown error types
793794

@@ -924,56 +925,6 @@ class Subsubclass: Subclass {
924925

925926
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.
926927

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-
{ throw E() } // throws
933-
934-
{ try call() } // throws
935-
936-
{
937-
do {
938-
try call()
939-
} catch let e as CatError {
940-
// ...
941-
}
942-
} // throws, the do...catch is not exhaustive
943-
944-
{
945-
do {
946-
try call()
947-
} catch e {}
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-
> if Int.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 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.
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 for throw statements:
968-
>
969-
> ```swift
970-
> { () throws(CatError) in
971-
> if Int.random(in: 0..<24) < 20 {
972-
> throw .asleep
973-
> }
974-
> }
975-
> ```
976-
977928
#### Associated type inference
978929

979930
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
10641015

10651016
## Source compatibility
10661017

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.
10721019

10731020
## Effect on API resilience
10741021

@@ -1184,6 +1131,58 @@ This way, clients compiled against the updated standard library will always use
11841131

11851132
## Future directions
11861133

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+
{ throw E() } // throws
1140+
1141+
{ try call() } // throws
1142+
1143+
{
1144+
do {
1145+
try call()
1146+
} catch let e as CatError {
1147+
// ...
1148+
}
1149+
} // throws, the do...catch is not exhaustive
1150+
1151+
{
1152+
do {
1153+
try call()
1154+
} catch e {}
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+
if Int.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+
if Int.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+
11871186
### Concurrency library adoption
11881187

11891188
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
13741373

13751374
## Revision history
13761375

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.
13771378
* Revision 5 (first review):
13781379
* 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!
13791380
* Revision 4:

0 commit comments

Comments
 (0)