Skip to content

Commit 38afb27

Browse files
committed
Add Proposal: Multi-Pattern and Conditionally Compiled Catch Clauses
1 parent d0f3525 commit 38afb27

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Multi-Pattern and Conditionally Compiled Catch Clauses
2+
3+
* Proposal: [SE-NNNN](NNNN-multi-pattern-and-conditionally-compiled-catches.md)
4+
* Authors: [Owen Voorhees](https://github.com/owenv)
5+
* Review Manager: TBD
6+
* Status: **Awaiting review**
7+
* Implementation:
8+
- [apple/swift#27776](https://github.com/apple/swift/pull/27776) (multi-pattern catch clauses)
9+
- [apple/swift#27905](https://github.com/apple/swift/pull/27905) (conditionally compiled catch clauses)
10+
11+
## Introduction
12+
13+
Currently, each catch clause in a do-catch statement may only contain a single pattern and where clause, and may not be conditionally compiled using a `#if` directive. This is inconsistent with the behavior of cases in switch statements which provide similar functionality. It also makes some error handling patterns awkward to express. This proposal extends the grammar of catch clauses to support `#if` and a comma-separated list of patterns (with optional where clauses), resolving this inconsistency.
14+
15+
Swift-evolution thread: [Thread](https://forums.swift.org/t/multi-pattern-and-conditionally-compiled-catch-clauses/30246)
16+
17+
## Motivation
18+
19+
Currently, Swift only allows up to one pattern and where clause for each catch clause in a do-catch statement, so the following code snippet is not allowed:
20+
21+
```swift
22+
do {
23+
try performTask()
24+
} catch TaskError.someRecoverableError { // OK
25+
recover()
26+
} catch TaskError.someFailure(let msg),
27+
TaskError.anotherFailure(let msg) { // Not currently valid
28+
showMessage(msg)
29+
}
30+
31+
```
32+
33+
Because the above snippet is not valid today, developers frequently end up duplicating code between catch clauses, or writing something like the following instead:
34+
35+
```swift
36+
do {
37+
try performTask()
38+
} catch {
39+
switch error {
40+
case TaskError.someRecoverableError:
41+
recover()
42+
case TaskError.someFailure(let msg),
43+
TaskError.anotherFailure(let msg):
44+
showMessage(msg)
45+
}
46+
}
47+
```
48+
49+
Nesting the switch inside of the catch clause is awkward and introduces additional nesting. It also defeats the purpose of supporting pattern matching in catch clauses. Splitting the code up into multiple catch clauses requires duplication, which is also undesirable. Supporting a multi-pattern catch clause would allow for code which is both clearer and more concise.
50+
51+
The following use of `#if` is also not allowed today and frequently leads to nesting of switch statements inside catch clauses:
52+
53+
```swift
54+
do {
55+
try performTask()
56+
}
57+
#if os(macOS)
58+
catch TaskError.macOSOnlyError {
59+
macOSRecovery()
60+
}
61+
#endif
62+
catch {
63+
crossPlatformRecovery()
64+
}
65+
66+
```
67+
68+
69+
## Proposed solution
70+
71+
Catch clauses should allow the user to specify a comma-separated list of patterns. If an error thrown at runtime in a do block matches any of the patterns in a corresponding catch clause, that catch clause should be executed. Similar to switch cases, a user should be able to bind a variable in all patterns of a catch clause and then use it in the body.
72+
73+
Additionally, conditional compilation directives should be permitted to appear surrounding catch clauses.
74+
75+
When used together, these additions allow writing code like the following:
76+
77+
```swift
78+
do {
79+
try performTask()
80+
}
81+
#if os(macOS)
82+
catch TaskError.macOSOnlyError { // A conditionally compiled catch clause
83+
macOSRecovery()
84+
}
85+
#endif
86+
catch TaskError.someOtherFailure(let msg), // Multiple patterns in a single catch clause
87+
TaskError.anotherFailure(let msg) {
88+
showMessage(msg)
89+
}
90+
```
91+
92+
## Detailed design
93+
94+
### Grammar Changes
95+
96+
The revised catch clause grammar is as follows:
97+
98+
```
99+
catch-clauses -> catch-clause catch-clauses?
100+
101+
catch-clause -> 'catch' catch-item-list? code-block |
102+
conditional-catch-clause
103+
104+
catch-item-list -> catch-item |
105+
catch-item ',' case-item-list
106+
107+
catch-item -> pattern where-clause? |
108+
where-clause
109+
110+
conditional-catch-clause -> catch-if-directive-clause catch-elseif-directive-clauses? switch-else-directive-clause? endif-directive
111+
112+
catch-if-directive-clause -> if-directive compilation-condition catch-clauses?
113+
114+
catch-elseif-directive-clauses -> catch-elseif-directive-clause catch-elseif-directive-clauses?
115+
116+
catch-elseif-directive-clause -> elseif-directive compilation-condition catch-clauses?
117+
118+
catch-else-directive-clause -> else-directive catch-clauses?
119+
```
120+
121+
Note: Where clause expressions with trailing closures are not allowed in any of a catch clause's patterns. This differs from the behavior of switch cases.
122+
123+
### Semantics
124+
125+
If a catch clause has multiple patterns, then its body will be executed if a thrown error matches any one of those patterns, and has not already matched a pattern from a preceding catch clause. Similar to switch cases, catch clauses with multiple patterns may still contain value bindings. However, those bindings must have the same name and type in each pattern.
126+
127+
Catch clauses within conditional compilation blocks work just like any other supported construct. The clause must parse whether or not the provided condition evaluates to true, so long as it does not include a compiler version condition. If the condition does not evaluate to true, the compiler will not consider it beyond the parsing stage.
128+
129+
## Source compatibility
130+
131+
This proposal maintains source compatibility. It will only result in code compiling which was considered invalid by older compiler versions.
132+
133+
## Effect on ABI stability
134+
135+
This feature has no ABI impact.
136+
137+
## Effect on API resilience
138+
139+
This proposal does not introduce any new features which could become part of a public API.
140+
141+
## Alternatives considered
142+
143+
### Do nothing
144+
145+
These features are relatively minor additions to the language, and arguably would see rather limited usage. However, in the cases where they are needed, they help the user avoid hard-to-maintain error handling code which either duplicates functionality or nests control flow statements in a confusing manner. This proposal also simplifies Swift's pattern matching and conditional compilation models by unifying some of the semantics of switch and do-catch statements.
146+
147+
## Future Directions
148+
149+
There are a number of possible future directions which could increase the expressiveness of catch clauses.
150+
151+
### Implicit `$error` or `error` binding for all catch clauses
152+
153+
Currently, only catch clauses without a pattern have an implicit `error: Error` binding. However, there are some cases where it would be useful to have this binding in all catch clauses to make, for example, re-throwing errors easier. However, using `error` as the identifier for this binding would be a medium-to-large source-breaking change. Instead, we could continue the trend of compiler defined identifiers and use `$error`. `error` in empty catch clauses could then be deprecated and eventually removed in future language versions, a smaller source break.
154+
155+
This change was not included in this proposal because it is source-breaking and orthogonal. If there is interest in this feature, we should probably consider it as an independent improvement which deserves its own proposal.
156+
157+
### `fallthrough` support in catch clauses
158+
159+
Allowing `fallthrough` statements in catch clauses would further unify the semantics of switch cases and catches. However, it is currently undesirable for a number of reasons. First, it would be a source-breaking change for existing do-catch statements which trigger `fallthrough`s in an enclosing switch. Also, there is no historical precedent from other languages for supporting `fallthrough`s in catch statements, unlike switches. Finally, there have not yet been any identified examples of code which would benefit from this functionality.

0 commit comments

Comments
 (0)