Skip to content

Commit ce1dd43

Browse files
committed
[sending] Change CheckedContinuation/AsyncThrowingStream.Continuation APIs to use sending parameters and results.
Specifically: 1. CheckedContinuation.resume now takes a sending parameter. 2. Async{Throwing,}Stream.yield takes a sending parameter. 3. withCheckedContinuation returns a transferring parameter. Importantly due to the previous changes around mangling, this is a mangling neutral change. rdar://120420024
1 parent 88729b9 commit ce1dd43

File tree

6 files changed

+135
-23
lines changed

6 files changed

+135
-23
lines changed

lib/AST/ASTMangler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2981,7 +2981,7 @@ void ASTMangler::appendFunctionType(AnyFunctionType *fn, GenericSignature sig,
29812981
assert((DWARFMangling || fn->isCanonical()) &&
29822982
"expecting canonical types when not mangling for the debugger");
29832983

2984-
appendFunctionSignature(fn, sig, forDecl, NoFunctionMangling);
2984+
appendFunctionSignature(fn, sig, forDecl, NoFunctionMangling, isRecursedInto);
29852985

29862986
bool mangleClangType = fn->getASTContext().LangOpts.UseClangFunctionTypes &&
29872987
fn->hasNonDerivableClangType();

stdlib/public/Concurrency/AsyncStream.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ public struct AsyncStream<Element> {
192192
/// This can be called more than once and returns to the caller immediately
193193
/// without blocking for any awaiting consumption from the iteration.
194194
@discardableResult
195-
public func yield(_ value: __owned Element) -> YieldResult {
195+
public func yield(_ value: sending Element) -> YieldResult {
196196
storage.yield(value)
197197
}
198198

@@ -422,11 +422,11 @@ extension AsyncStream.Continuation {
422422
/// blocking for any awaiting consumption from the iteration.
423423
@discardableResult
424424
public func yield(
425-
with result: Result<Element, Never>
425+
with result: __shared sending Result<Element, Never>
426426
) -> YieldResult {
427427
switch result {
428-
case .success(let val):
429-
return storage.yield(val)
428+
case .success(let val):
429+
return storage.yield(val)
430430
}
431431
}
432432

@@ -501,7 +501,7 @@ public struct AsyncStream<Element> {
501501
@discardableResult
502502
@available(SwiftStdlib 5.1, *)
503503
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
504-
public func yield(_ value: __owned Element) -> YieldResult {
504+
public func yield(_ value: sending Element) -> YieldResult {
505505
fatalError("Unavailable in task-to-thread concurrency model")
506506
}
507507
@available(SwiftStdlib 5.1, *)
@@ -571,7 +571,7 @@ extension AsyncStream.Continuation {
571571
@available(SwiftStdlib 5.1, *)
572572
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
573573
public func yield(
574-
with result: Result<Element, Never>
574+
with result: __shared sending Result<Element, Never>
575575
) -> YieldResult {
576576
fatalError("Unavailable in task-to-thread concurrency model")
577577
}

stdlib/public/Concurrency/AsyncThrowingStream.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ public struct AsyncThrowingStream<Element, Failure: Error> {
214214
/// This can be called more than once and returns to the caller immediately
215215
/// without blocking for any awaiting consumption from the iteration.
216216
@discardableResult
217-
public func yield(_ value: __owned Element) -> YieldResult {
217+
public func yield(_ value: sending Element) -> YieldResult {
218218
storage.yield(value)
219219
}
220220

@@ -463,7 +463,7 @@ extension AsyncThrowingStream.Continuation {
463463
/// blocking for any awaiting consumption from the iteration.
464464
@discardableResult
465465
public func yield(
466-
with result: Result<Element, Failure>
466+
with result: __shared sending Result<Element, Failure>
467467
) -> YieldResult where Failure == Error {
468468
switch result {
469469
case .success(let val):
@@ -547,7 +547,7 @@ public struct AsyncThrowingStream<Element, Failure: Error> {
547547
@discardableResult
548548
@available(SwiftStdlib 5.1, *)
549549
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
550-
public func yield(_ value: __owned Element) -> YieldResult {
550+
public func yield(_ value: sending Element) -> YieldResult {
551551
fatalError("Unavailable in task-to-thread concurrency model")
552552
}
553553
@available(SwiftStdlib 5.1, *)
@@ -610,7 +610,7 @@ extension AsyncThrowingStream.Continuation {
610610
@available(SwiftStdlib 5.1, *)
611611
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
612612
public func yield(
613-
with result: Result<Element, Failure>
613+
with result: __shared sending Result<Element, Failure>
614614
) -> YieldResult where Failure == Error {
615615
fatalError("Unavailable in task-to-thread concurrency model")
616616
}

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public struct CheckedContinuation<T, E: Error>: Sendable {
159159
/// After `resume` enqueues the task, control immediately returns to
160160
/// the caller. The task continues executing when its executor is
161161
/// able to reschedule it.
162-
public func resume(returning value: __owned T) {
162+
public func resume(returning value: sending T) {
163163
if let c: UnsafeContinuation<T, E> = canary.takeContinuation() {
164164
c.resume(returning: value)
165165
} else {
@@ -206,7 +206,7 @@ extension CheckedContinuation {
206206
/// the caller. The task continues executing when its executor is
207207
/// able to reschedule it.
208208
@_alwaysEmitIntoClient
209-
public func resume<Er: Error>(with result: Result<T, Er>) where E == Error {
209+
public func resume<Er: Error>(with result: __shared sending Result<T, Er>) where E == Error {
210210
switch result {
211211
case .success(let val):
212212
self.resume(returning: val)
@@ -230,7 +230,7 @@ extension CheckedContinuation {
230230
/// the caller. The task continues executing when its executor is
231231
/// able to reschedule it.
232232
@_alwaysEmitIntoClient
233-
public func resume(with result: Result<T, E>) {
233+
public func resume(with result: __shared sending Result<T, E>) {
234234
switch result {
235235
case .success(let val):
236236
self.resume(returning: val)
@@ -291,9 +291,11 @@ public func withCheckedContinuation<T>(
291291
isolation: isolated (any Actor)? = #isolation,
292292
function: String = #function,
293293
_ body: (CheckedContinuation<T, Never>) -> Void
294-
) async -> T {
295-
return await withUnsafeContinuation {
296-
body(CheckedContinuation(continuation: $0, function: function))
294+
) async -> sending T {
295+
return await Builtin.withUnsafeContinuation {
296+
let unsafeContinuation = UnsafeContinuation<T, Never>($0)
297+
return body(CheckedContinuation(continuation: unsafeContinuation,
298+
function: function))
297299
}
298300
}
299301

@@ -350,9 +352,11 @@ public func withCheckedThrowingContinuation<T>(
350352
isolation: isolated (any Actor)? = #isolation,
351353
function: String = #function,
352354
_ body: (CheckedContinuation<T, Error>) -> Void
353-
) async throws -> T {
354-
return try await withUnsafeThrowingContinuation {
355-
body(CheckedContinuation(continuation: $0, function: function))
355+
) async throws -> sending T {
356+
return try await Builtin.withUnsafeThrowingContinuation {
357+
let unsafeContinuation = UnsafeContinuation<T, Error>($0)
358+
return body(CheckedContinuation(continuation: unsafeContinuation,
359+
function: function))
356360
}
357361
}
358362

@@ -393,7 +397,7 @@ internal func _createCheckedThrowingContinuation<T>(
393397
@_alwaysEmitIntoClient
394398
internal func _resumeCheckedContinuation<T>(
395399
_ continuation: CheckedContinuation<T, Never>,
396-
_ value: __owned T
400+
_ value: sending T
397401
) {
398402
continuation.resume(returning: value)
399403
}
@@ -402,7 +406,7 @@ internal func _resumeCheckedContinuation<T>(
402406
@_alwaysEmitIntoClient
403407
internal func _resumeCheckedThrowingContinuation<T>(
404408
_ continuation: CheckedContinuation<T, Error>,
405-
_ value: __owned T
409+
_ value: sending T
406410
) {
407411
continuation.resume(returning: value)
408412
}
@@ -411,7 +415,7 @@ internal func _resumeCheckedThrowingContinuation<T>(
411415
@_alwaysEmitIntoClient
412416
internal func _resumeCheckedThrowingContinuationWithError<T>(
413417
_ continuation: CheckedContinuation<T, Error>,
414-
_ error: __owned Error
418+
_ error: consuming Error
415419
) {
416420
continuation.resume(throwing: error)
417421
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// RUN: %target-swift-frontend -emit-sil -swift-version 6 -disable-availability-checking -enable-experimental-feature TransferringArgsAndResults -verify %s -o /dev/null -parse-as-library
2+
3+
// REQUIRES: asserts
4+
// REQUIRES: concurrency
5+
6+
////////////////////////
7+
// MARK: Declarations //
8+
////////////////////////
9+
10+
class NonSendableKlass {}
11+
12+
func useValue<T>(_ t: T) {}
13+
func useValueAsync<T>(_ t: T) async {}
14+
15+
/////////////////
16+
// MARK: Tests //
17+
/////////////////
18+
19+
@MainActor
20+
func withCheckedContinuation_1() async -> NonSendableKlass {
21+
await withCheckedContinuation { continuation in
22+
continuation.resume(returning: NonSendableKlass())
23+
}
24+
}
25+
26+
@MainActor
27+
func withCheckedContinuation_2() async -> NonSendableKlass {
28+
await withCheckedContinuation { continuation in
29+
let x = NonSendableKlass()
30+
continuation.resume(returning: x)
31+
// expected-error @-1 {{sending 'x' risks causing data races}}
32+
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
33+
useValue(x) // expected-note {{access can happen concurrently}}
34+
}
35+
}
36+
37+
@MainActor
38+
func withCheckedContinuation_3() async {
39+
// x is main actor isolated since withCheckedContinuation is #isolated.
40+
let x = await withCheckedContinuation { continuation in
41+
let x = NonSendableKlass()
42+
continuation.resume(returning: x)
43+
// expected-error @-1 {{sending 'x' risks causing data races}}
44+
// expected-note @-2 {{'x' used after being passed as a 'sending' parameter}}
45+
useValue(x) // expected-note {{access can happen concurrently}}
46+
}
47+
await useValueAsync(x)
48+
// expected-error @-1 {{sending 'x' risks causing data races}}
49+
// expected-note @-2 {{sending main actor-isolated 'x' to nonisolated global function 'useValueAsync' risks causing data races between nonisolated and main actor-isolated uses}}
50+
}
51+
52+
@MainActor
53+
func withCheckedContinuation_4() async {
54+
// x is main actor isolated since withCheckedContinuation is #isolated.
55+
let y = NonSendableKlass()
56+
let x = await withCheckedContinuation { continuation in
57+
continuation.resume(returning: y)
58+
// expected-error @-1 {{sending 'y' risks causing data races}}
59+
// expected-note @-2 {{main actor-isolated 'y' is passed as a 'sending' parameter}}
60+
useValue(y)
61+
}
62+
await useValueAsync(x)
63+
// expected-error @-1 {{sending 'x' risks causing data races}}
64+
// expected-note @-2 {{sending main actor-isolated 'x' to nonisolated global function 'useValueAsync' risks causing data races between nonisolated and main actor-isolated uses}}
65+
}
66+
67+
@MainActor func testAsyncStream() {
68+
let (_, continuation) = AsyncStream.makeStream(of: NonSendableKlass.self)
69+
70+
continuation.yield(NonSendableKlass())
71+
let x = NonSendableKlass()
72+
continuation.yield(x) // expected-error {{sending 'x' risks causing data races}}
73+
// expected-note @-1 {{'x' used after being passed as a 'sending' parameter; Later uses could race}}
74+
useValue(x) // expected-note {{access can happen concurrently}}
75+
}
76+
77+
@MainActor func testAsyncStreamContinuation() {
78+
let _ = AsyncStream(NonSendableKlass.self) { continuation in
79+
continuation.yield(NonSendableKlass())
80+
let x = NonSendableKlass()
81+
continuation.yield(x) // expected-error {{sending 'x' risks causing data races}}
82+
// expected-note @-1 {{'x' used after being passed as a 'sending' parameter; Later uses could race}}
83+
useValue(x) // expected-note {{access can happen concurrently}}
84+
}
85+
}
86+
87+
@MainActor func testAsyncThrowingStream() {
88+
let (_, continuation) = AsyncThrowingStream.makeStream(of: NonSendableKlass.self)
89+
90+
continuation.yield(NonSendableKlass())
91+
let x = NonSendableKlass()
92+
continuation.yield(x) // expected-error {{sending 'x' risks causing data races}}
93+
// expected-note @-1 {{'x' used after being passed as a 'sending' parameter; Later uses could race}}
94+
useValue(x) // expected-note {{access can happen concurrently}}
95+
}
96+
97+
@MainActor func testAsyncThrowingStreamContinuation() {
98+
let _ = AsyncThrowingStream(NonSendableKlass.self) { continuation in
99+
continuation.yield(NonSendableKlass())
100+
let x = NonSendableKlass()
101+
continuation.yield(x) // expected-error {{sending 'x' risks causing data races}}
102+
// expected-note @-1 {{'x' used after being passed as a 'sending' parameter; Later uses could race}}
103+
useValue(x) // expected-note {{access can happen concurrently}}
104+
}
105+
}

test/Concurrency/sending_mangling.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ func testNoRemoveFunctionResultImmediateTypedFunctionWithArg() -> ((sending NonS
3636
func testNoRemoveFunctionResultImmedateTypedFunctionWithResult() -> (() -> sending NonSendableKlass) { fatalError() }
3737

3838
struct MethodTest {
39+
// CHECK: sil hidden [ossa] @sending_mangling.MethodTest.init(__owned sending_mangling.NonSendableKlass) -> sending_mangling.MethodTest : $@convention(method) (@sil_sending @owned NonSendableKlass, @thin MethodTest.Type) -> MethodTest {
40+
init(_ x: sending NonSendableKlass) {}
41+
3942
// CHECK: sil hidden [ossa] @sending_mangling.MethodTest.testMethodRemoveFunctionArg(__owned sending_mangling.NonSendableKlass) -> () : $@convention(method) (@sil_sending @owned NonSendableKlass, MethodTest) -> () {
4043
func testMethodRemoveFunctionArg(_ x: sending NonSendableKlass) {}
4144

0 commit comments

Comments
 (0)