@@ -36,14 +36,7 @@ internal final class CheckedContinuationCanary {
36
36
return functionPtr. assumingMemoryBound ( to: String . self)
37
37
}
38
38
39
- internal static func create< T> ( continuation: UnsafeContinuation < T > ,
40
- function: String ) -> Self {
41
- return _create (
42
- continuation: unsafeBitCast ( continuation, to: UnsafeRawPointer . self) ,
43
- function: function)
44
- }
45
-
46
- internal static func create< T> ( continuation: UnsafeThrowingContinuation < T > ,
39
+ internal static func create< T, E> ( continuation: UnsafeContinuation < T , E > ,
47
40
function: String ) -> Self {
48
41
return _create (
49
42
continuation: unsafeBitCast ( continuation, to: UnsafeRawPointer . self) ,
@@ -56,23 +49,14 @@ internal final class CheckedContinuationCanary {
56
49
57
50
// Take the continuation away from the container, or return nil if it's
58
51
// already been taken.
59
- private func _takeContinuation ( ) -> UnsafeRawPointer ? {
52
+ internal func takeContinuation < T , E > ( ) -> UnsafeContinuation < T , E > ? {
60
53
// Atomically exchange the current continuation value with a null pointer.
61
54
let rawContinuationPtr = unsafeBitCast ( _continuationPtr,
62
55
to: Builtin . RawPointer. self)
63
56
let rawOld = Builtin . atomicrmw_xchg_seqcst_Word ( rawContinuationPtr,
64
57
0 . _builtinWordValue)
65
58
66
- return unsafeBitCast ( rawOld, to: UnsafeRawPointer ? . self)
67
- }
68
-
69
- internal func takeContinuation< T> ( ) -> UnsafeContinuation < T > ? {
70
- return unsafeBitCast ( _takeContinuation ( ) ,
71
- to: UnsafeContinuation< T>? . self )
72
- }
73
- internal func takeThrowingContinuation< T> ( ) -> UnsafeThrowingContinuation < T > ? {
74
- return unsafeBitCast ( _takeContinuation ( ) ,
75
- to: UnsafeThrowingContinuation< T>? . self )
59
+ return unsafeBitCast ( rawOld, to: UnsafeContinuation< T, E>? . self )
76
60
}
77
61
78
62
deinit {
@@ -97,27 +81,30 @@ internal final class CheckedContinuationCanary {
97
81
/// is abandoned without resuming the task, then the task will be stuck in
98
82
/// the suspended state forever, and conversely, if the same continuation is
99
83
/// resumed multiple times, it will put the task in an undefined state.
84
+ ///
100
85
/// `UnsafeContinuation` avoids enforcing these invariants at runtime because
101
86
/// it aims to be a low-overhead mechanism for interfacing Swift tasks with
102
87
/// event loops, delegate methods, callbacks, and other non-`async` scheduling
103
88
/// mechanisms. However, during development, being able to verify that the
104
89
/// invariants are being upheld in testing is important.
105
90
///
106
91
/// `CheckedContinuation` is designed to be a drop-in API replacement for
107
- /// `UnsafeContinuation` that can be used for testing purposes, at the cost
108
- /// of an extra allocation and indirection for the wrapper object.
109
- /// Changing a call of `withUnsafeContinuation` into a call of
110
- /// `withCheckedContinuation` should be enough to obtain the extra checking
111
- /// without further source modification in most circumstances.
112
- public struct CheckedContinuation < T> {
113
- let canary : CheckedContinuationCanary
92
+ /// `UnsafeContinuation` that can be used for testing purposes, at the cost of
93
+ /// an extra allocation and indirection for the wrapper object. Changing a call
94
+ /// of `withUnsafeContinuation` or `withUnsafeThrowingContinuation` into a call
95
+ /// of `withCheckedContinuation` or `withCheckedThrowingContinuation` should be
96
+ /// enough to obtain the extra checking without further source modification in
97
+ /// most circumstances.
98
+ public struct CheckedContinuation < T, E: Error > {
99
+ private let canary : CheckedContinuationCanary
114
100
115
101
/// Initialize a `CheckedContinuation` wrapper around an
116
102
/// `UnsafeContinuation`.
117
103
///
118
- /// In most cases, you should use `withCheckedContinuation` instead.
119
- /// You only need to initialize your own `CheckedContinuation<T>` if you
120
- /// already have an `UnsafeContinuation` you want to impose checking on.
104
+ /// In most cases, you should use `withCheckedContinuation` or
105
+ /// `withCheckedThrowingContinuation` instead. You only need to initialize
106
+ /// your own `CheckedContinuation<T, E>` if you already have an
107
+ /// `UnsafeContinuation` you want to impose checking on.
121
108
///
122
109
/// - Parameters:
123
110
/// - continuation: a fresh `UnsafeContinuation` that has not yet
@@ -126,7 +113,7 @@ public struct CheckedContinuation<T> {
126
113
/// - function: a string identifying the declaration that is the notional
127
114
/// source for the continuation, used to identify the continuation in
128
115
/// runtime diagnostics related to misuse of this continuation.
129
- public init ( continuation: UnsafeContinuation < T > , function: String = #function) {
116
+ public init ( continuation: UnsafeContinuation < T , E > , function: String = #function) {
130
117
canary = CheckedContinuationCanary . create (
131
118
continuation: continuation,
132
119
function: function)
@@ -135,6 +122,8 @@ public struct CheckedContinuation<T> {
135
122
/// Resume the task awaiting the continuation by having it return normally
136
123
/// from its suspension point.
137
124
///
125
+ /// - Parameter value: The value to return from the continuation.
126
+ ///
138
127
/// A continuation must be resumed exactly once. If the continuation has
139
128
/// already been resumed through this object, then the attempt to resume
140
129
/// the continuation again will trap.
@@ -143,107 +132,41 @@ public struct CheckedContinuation<T> {
143
132
/// the caller. The task will continue executing when its executor is
144
133
/// able to reschedule it.
145
134
public func resume( returning x: __owned T) {
146
- if let c: UnsafeContinuation < T > = canary. takeContinuation ( ) {
135
+ if let c: UnsafeContinuation < T , E > = canary. takeContinuation ( ) {
147
136
c. resume ( returning: x)
148
137
} else {
149
138
fatalError ( " SWIFT TASK CONTINUATION MISUSE: \( canary. function) tried to resume its continuation more than once, returning \( x) ! \n " )
150
139
}
151
140
}
152
- }
153
-
154
- extension CheckedContinuation where T == Void {
155
- /// Resume the task awaiting the continuation by having it return normally
156
- /// from its suspension point.
157
- ///
158
- /// A continuation must be resumed exactly once. If the continuation has
159
- /// already been resumed through this object, then the attempt to resume
160
- /// the continuation again will trap.
161
- ///
162
- /// After `resume` enqueues the task, control is immediately returned to
163
- /// the caller. The task will continue executing when its executor is
164
- /// able to reschedule it.
165
- @inlinable
166
- public func resume( ) {
167
- self . resume ( returning: ( ) )
168
- }
169
- }
170
-
171
- public func withCheckedContinuation< T> (
172
- function: String = #function,
173
- _ body: ( CheckedContinuation < T > ) -> Void
174
- ) async -> T {
175
- return await withUnsafeContinuation {
176
- body ( CheckedContinuation ( continuation: $0, function: function) )
177
- }
178
- }
179
-
180
- /// A wrapper class for `UnsafeThrowingContinuation` that logs misuses of the
181
- /// continuation, logging a message if the continuation is resumed
182
- /// multiple times, or if an object is destroyed without its continuation
183
- /// ever being resumed.
184
- ///
185
- /// Raw `UnsafeThrowingContinuation`, like other unsafe constructs, requires the
186
- /// user to apply it correctly in order to maintain invariants. The key
187
- /// invariant is that the continuation must be resumed exactly once,
188
- /// and bad things happen if this invariant is not upheld--if a continuation
189
- /// is abandoned without resuming the task, then the task will be stuck in
190
- /// the suspended state forever, and conversely, if the same continuation is
191
- /// resumed multiple times, it will put the task in an undefined state.
192
- /// `UnsafeThrowingContinuation` avoids enforcing these invariants at runtime because
193
- /// it aims to be a low-overhead mechanism for interfacing Swift tasks with
194
- /// event loops, delegate methods, callbacks, and other non-`async` scheduling
195
- /// mechanisms. However, during development, being able to verify that the
196
- /// invariants are being upheld in testing is important.
197
- ///
198
- /// `CheckedThrowingContinuation` is designed to be a drop-in API replacement for
199
- /// `UnsafeThrowingContinuation` that can be used for testing purposes, at the cost
200
- /// of an extra allocation and indirection for the wrapper object.
201
- /// Changing a call of `withUnsafeThrowingContinuation` into a call of
202
- /// `withCheckedThrowingContinuation` should be enough to obtain the extra checking
203
- /// without further source modification in most circumstances.
204
- public struct CheckedThrowingContinuation < T> {
205
- let canary : CheckedContinuationCanary
206
141
207
- /// Initialize a `CheckedThrowingContinuation` wrapper around an
208
- /// `UnsafeThrowingContinuation`.
209
- ///
210
- /// In most cases, you should use `withCheckedThrowingContinuation` instead.
211
- /// You only need to initialize your own `CheckedThrowingContinuation<T>` if you
212
- /// already have an `UnsafeThrowingContinuation` you want to impose checking on.
213
- ///
214
- /// - Parameters:
215
- /// - continuation: a fresh `UnsafeThrowingContinuation` that has not yet
216
- /// been resumed. The `UnsafeThrowingContinuation` must not be used outside of
217
- /// this object once it's been given to the new object.
218
- /// - function: a string identifying the declaration that is the notional
219
- /// source for the continuation, used to identify the continuation in
220
- /// runtime diagnostics related to misuse of this continuation.
221
- public init ( continuation: UnsafeThrowingContinuation < T > , function: String = #function) {
222
- canary = CheckedContinuationCanary . create (
223
- continuation: continuation,
224
- function: function)
225
- }
226
-
227
- /// Resume the task awaiting the continuation by having it return normally
142
+ /// Resume the task awaiting the continuation by having it throw an error
228
143
/// from its suspension point.
229
144
///
145
+ /// - Parameter error: The error to throw from the continuation.
146
+ ///
230
147
/// A continuation must be resumed exactly once. If the continuation has
231
148
/// already been resumed through this object, then the attempt to resume
232
149
/// the continuation again will trap.
233
150
///
234
151
/// After `resume` enqueues the task, control is immediately returned to
235
152
/// the caller. The task will continue executing when its executor is
236
153
/// able to reschedule it.
237
- public func resume( returning x: __owned T ) {
238
- if let c: UnsafeThrowingContinuation < T > = canary. takeThrowingContinuation ( ) {
239
- c. resume ( returning : x)
154
+ public func resume( throwing x: __owned E ) {
155
+ if let c: UnsafeContinuation < T , E > = canary. takeContinuation ( ) {
156
+ c. resume ( throwing : x)
240
157
} else {
241
- fatalError ( " SWIFT TASK CONTINUATION MISUSE: \( canary. function) tried to resume its continuation more than once, returning \( x) ! \n " )
158
+ fatalError ( " SWIFT TASK CONTINUATION MISUSE: \( canary. function) tried to resume its continuation more than once, throwing \( x) ! \n " )
242
159
}
243
160
}
244
-
245
- /// Resume the task awaiting the continuation by having it throw an error
246
- /// from its suspension point.
161
+ }
162
+
163
+ extension CheckedContinuation {
164
+ /// Resume the task awaiting the continuation by having it either
165
+ /// return normally or throw an error based on the state of the given
166
+ /// `Result` value.
167
+ ///
168
+ /// - Parameter result: A value to either return or throw from the
169
+ /// continuation.
247
170
///
248
171
/// A continuation must be resumed exactly once. If the continuation has
249
172
/// already been resumed through this object, then the attempt to resume
@@ -252,36 +175,40 @@ public struct CheckedThrowingContinuation<T> {
252
175
/// After `resume` enqueues the task, control is immediately returned to
253
176
/// the caller. The task will continue executing when its executor is
254
177
/// able to reschedule it.
255
- public func resume( throwing x: __owned Error) {
256
- if let c: UnsafeThrowingContinuation < T > = canary. takeThrowingContinuation ( ) {
257
- c. resume ( throwing: x)
258
- } else {
259
- fatalError ( " SWIFT TASK CONTINUATION MISUSE: \( canary. function) tried to resume its continuation more than once, throwing \( x) ! \n " )
178
+ @_alwaysEmitIntoClient
179
+ public func resume< Er: Error > ( with result: Result < T , Er > ) where E == Error {
180
+ switch result {
181
+ case . success( let val) :
182
+ self . resume ( returning: val)
183
+ case . failure( let err) :
184
+ self . resume ( throwing: err)
260
185
}
261
186
}
262
187
263
188
/// Resume the task awaiting the continuation by having it either
264
189
/// return normally or throw an error based on the state of the given
265
190
/// `Result` value.
266
191
///
192
+ /// - Parameter result: A value to either return or throw from the
193
+ /// continuation.
194
+ ///
267
195
/// A continuation must be resumed exactly once. If the continuation has
268
196
/// already been resumed through this object, then the attempt to resume
269
197
/// the continuation again will trap.
270
198
///
271
199
/// After `resume` enqueues the task, control is immediately returned to
272
200
/// the caller. The task will continue executing when its executor is
273
201
/// able to reschedule it.
274
- public func resume< E: Error > ( with x: __owned Result< T , E > ) {
275
- switch x {
276
- case . success( let s) :
277
- return resume ( returning: s)
278
- case . failure( let e) :
279
- return resume ( throwing: e)
202
+ @_alwaysEmitIntoClient
203
+ public func resume( with result: Result < T , E > ) {
204
+ switch result {
205
+ case . success( let val) :
206
+ self . resume ( returning: val)
207
+ case . failure( let err) :
208
+ self . resume ( throwing: err)
280
209
}
281
210
}
282
- }
283
211
284
- extension CheckedThrowingContinuation where T == Void {
285
212
/// Resume the task awaiting the continuation by having it return normally
286
213
/// from its suspension point.
287
214
///
@@ -292,18 +219,27 @@ extension CheckedThrowingContinuation where T == Void {
292
219
/// After `resume` enqueues the task, control is immediately returned to
293
220
/// the caller. The task will continue executing when its executor is
294
221
/// able to reschedule it.
295
- @inlinable
296
- public func resume( ) {
222
+ @_alwaysEmitIntoClient
223
+ public func resume( ) where T == Void {
297
224
self . resume ( returning: ( ) )
298
225
}
299
226
}
300
227
228
+ public func withCheckedContinuation< T> (
229
+ function: String = #function,
230
+ _ body: ( CheckedContinuation < T , Never > ) -> Void
231
+ ) async -> T {
232
+ return await withUnsafeContinuation {
233
+ body ( CheckedContinuation ( continuation: $0, function: function) )
234
+ }
235
+ }
236
+
301
237
public func withCheckedThrowingContinuation< T> (
302
238
function: String = #function,
303
- _ body: ( CheckedThrowingContinuation < T > ) -> Void
239
+ _ body: ( CheckedContinuation < T , Error > ) -> Void
304
240
) async throws -> T {
305
241
return try await withUnsafeThrowingContinuation {
306
- body ( CheckedThrowingContinuation ( continuation: $0, function: function) )
242
+ body ( CheckedContinuation ( continuation: $0, function: function) )
307
243
}
308
244
}
309
245
0 commit comments