Files
swift-mirror/test/Concurrency/transfernonsendable_sending_results.swift
Michael Gottesman 14634b6847 [rbi] Begin tracking if a function argument is from a nonisolated(nonsending) parameter and adjust printing as appropriate.
Specifically in terms of printing, if NonisolatedNonsendingByDefault is enabled,
we print out things as nonisolated/task-isolated and @concurrent/@concurrent
task-isolated. If said feature is disabled, we print out things as
nonisolated(nonsending)/nonisolated(nonsending) task-isolated and
nonisolated/task-isolated. This ensures in the default case, diagnostics do not
change and we always print out things to match the expected meaning of
nonisolated depending on the mode.

I also updated the tests as appropriate/added some more tests/added to the
SendNonSendable education notes information about this.
2025-07-02 13:03:13 -07:00

323 lines
13 KiB
Swift

// RUN: %target-swift-frontend -emit-sil -parse-as-library -strict-concurrency=complete -target %target-swift-5.1-abi-triple -verify %s -o /dev/null -enable-upcoming-feature GlobalActorIsolatedTypesUsability -verify-additional-prefix ni-
// RUN: %target-swift-frontend -emit-sil -parse-as-library -strict-concurrency=complete -target %target-swift-5.1-abi-triple -verify %s -o /dev/null -enable-upcoming-feature GlobalActorIsolatedTypesUsability -enable-upcoming-feature NonisolatedNonsendingByDefault -verify-additional-prefix ni-ns-
// REQUIRES: concurrency
// REQUIRES: swift_feature_GlobalActorIsolatedTypesUsability
// REQUIRES: swift_feature_NonisolatedNonsendingByDefault
////////////////////////
// MARK: Declarations //
////////////////////////
class NonSendableKlass {} // expected-note 2{{}}
struct NonSendableStruct {
var first = NonSendableKlass()
var second = NonSendableKlass()
}
func getValue<T>() -> T { fatalError() }
func getValueAsync<T>() async -> T { fatalError() }
func getValueAsyncTransferring<T>() async -> sending T { fatalError() }
@MainActor func getMainActorValueAsync<T>() async -> T { fatalError() }
@MainActor func getMainActorValueAsyncTransferring<T>() async -> sending T { fatalError() }
func useValue<T>(_ t: T) {}
func getAny() -> Any { fatalError() }
actor Custom {
}
@globalActor
struct CustomActor {
static var shared: Custom {
return Custom()
}
}
@MainActor func transferToMainIndirect<T>(_ t: T) async {}
@CustomActor func transferToCustomIndirect<T>(_ t: T) async {}
@MainActor func transferToMainDirect(_ t: NonSendableKlass) async {}
@CustomActor func transferToCustomDirect(_ t: NonSendableKlass) async {}
func useValueIndirect<T>(_ t: T) {}
func useValueDirect(_ t: NonSendableKlass) {}
func transferValueDirect(_ x: sending NonSendableKlass) {}
func transferValueIndirect<T>(_ x: sending T) {}
func transferResult() -> sending NonSendableKlass { NonSendableKlass() }
func transferResultWithArg(_ x: NonSendableKlass) -> sending NonSendableKlass { NonSendableKlass() }
func transferResultWithTransferringArg(_ x: sending NonSendableKlass) -> sending NonSendableKlass { NonSendableKlass() }
func transferResultWithTransferringArg2(_ x: sending NonSendableKlass, _ y: NonSendableKlass) -> sending NonSendableKlass { NonSendableKlass() }
func transferResultWithTransferringArg2Throwing(_ x: sending NonSendableKlass, _ y: NonSendableKlass) throws -> sending NonSendableKlass { NonSendableKlass() }
func transferAsyncResult() async -> sending NonSendableKlass { NonSendableKlass() }
func transferAsyncResultWithArg(_ x: NonSendableKlass) async -> sending NonSendableKlass { NonSendableKlass() }
func transferAsyncResultWithTransferringArg(_ x: sending NonSendableKlass) async -> sending NonSendableKlass { NonSendableKlass() }
func transferAsyncResultWithTransferringArg2(_ x: sending NonSendableKlass, _ y: NonSendableKlass) async -> sending NonSendableKlass { NonSendableKlass() }
func transferAsyncResultWithTransferringArg2Throwing(_ x: sending NonSendableKlass, _ y: NonSendableKlass) async throws -> sending NonSendableKlass { NonSendableKlass() }
@MainActor func transferAsyncResultMainActor() async -> sending NonSendableKlass { NonSendableKlass() }
@MainActor var globalNonSendableKlass = NonSendableKlass()
@MainActor
struct MainActorIsolatedStruct {
let ns = NonSendableKlass()
}
@MainActor
enum MainActorIsolatedEnum {
case first
case second(NonSendableKlass)
}
struct GenericNonSendableStruct<T> {
var t: T
var t2: T?
var x: NonSendableKlass
}
class GenericNonSendableKlass<T> {
var t: T
var t2: T?
var x: NonSendableKlass?
init(_ inputT: T) {
t = inputT
t2 = nil
x = NonSendableKlass()
}
}
func sendParameter<T>(_ t: sending T) {}
actor MyActor {
private var ns = NonSendableKlass()
}
/////////////////
// MARK: Tests //
/////////////////
func simpleTest() async {
let x = NonSendableKlass()
let y = transferResultWithArg(x)
await transferToMainDirect(x)
useValue(y)
}
// Since y is transferred, we should emit the error on useValue(x). We generally
// emit the first seen error on a path, so if we were to emit an error on
// useValue(y), we would have emitted that error.
func simpleTest2() async {
let x = NonSendableKlass()
let y = transferResultWithArg(x)
await transferToMainDirect(x) // expected-warning {{sending 'x' risks causing data races}}
// expected-note @-1 {{sending 'x' to main actor-isolated global function 'transferToMainDirect' risks causing data races between main actor-isolated and local nonisolated uses}}
useValue(y)
useValue(x) // expected-note {{access can happen concurrently}}
}
// Make sure that later errors with y can happen.
func simpleTest3() async {
let x = NonSendableKlass()
let y = transferResultWithArg(x)
await transferToMainDirect(x)
await transferToMainDirect(y) // expected-warning {{sending 'y' risks causing data races}}
// expected-note @-1 {{sending 'y' to main actor-isolated global function 'transferToMainDirect' risks causing data races between main actor-isolated and local nonisolated uses}}
useValue(y) // expected-note {{access can happen concurrently}}
}
func transferResult() async -> sending NonSendableKlass {
let x = NonSendableKlass()
await transferToMainDirect(x) // expected-warning {{sending 'x' risks causing data races}}
// expected-note @-1 {{sending 'x' to main actor-isolated global function 'transferToMainDirect' risks causing data races between main actor-isolated and local nonisolated uses}}
return x // expected-note {{access can happen concurrently}}
}
func transferInAndOut(_ x: sending NonSendableKlass) -> sending NonSendableKlass {
x
}
func transferReturnArg(_ x: NonSendableKlass) -> sending NonSendableKlass {
return x // expected-warning {{sending 'x' risks causing data races}}
// expected-ni-note @-1 {{task-isolated 'x' cannot be a 'sending' result. task-isolated uses may race with caller uses}}
// expected-ni-ns-note @-2 {{task-isolated 'x' cannot be a 'sending' result. task-isolated uses may race with caller uses}}
}
// TODO: This will be fixed once I represent @MainActor on func types.
@MainActor func transferReturnArgMainActor(_ x: NonSendableKlass) -> sending NonSendableKlass {
return x // expected-warning {{sending 'x' risks causing data races}}
// expected-note @-1 {{main actor-isolated 'x' cannot be a 'sending' result. main actor-isolated uses may race with caller uses}}
}
// This is safe since we are returning the whole tuple fresh. In contrast,
// (transferring NonSendableKlass, sending NonSendableKlass) would not be
// safe if we ever support that.
func transferReturnArgTuple(_ x: sending NonSendableKlass) -> sending (NonSendableKlass, NonSendableKlass) {
return (x, x)
}
func useTransferredResultMainActor() async {
let _ = await transferAsyncResultMainActor()
}
func useTransferredResult() async {
let _ = await transferAsyncResult()
}
extension MainActorIsolatedStruct {
func testNonSendableErrorReturnWithTransfer() -> sending NonSendableKlass {
return ns // expected-warning {{sending 'self.ns' risks causing data races}}
// expected-note @-1 {{main actor-isolated 'self.ns' cannot be a 'sending' result. main actor-isolated uses may race with caller uses}}
}
func testNonSendableErrorReturnNoTransfer() -> NonSendableKlass {
return ns
}
}
extension MainActorIsolatedEnum {
func testSwitchReturn() -> sending NonSendableKlass? {
switch self {
case .first:
return nil
case .second(let ns):
return ns
}
} // expected-warning {{sending 'ns.some' risks causing data races}}
// expected-note @-1 {{main actor-isolated 'ns.some' cannot be a 'sending' result. main actor-isolated uses may race with caller uses}}
func testSwitchReturnNoTransfer() -> NonSendableKlass? {
switch self {
case .first:
return nil
case .second(let ns):
return ns
}
}
func testIfLetReturn() -> sending NonSendableKlass? {
if case .second(let ns) = self {
return ns // TODO: The error below should be here.
}
return nil
} // expected-warning {{sending 'ns.some' risks causing data races}}
// expected-note @-1 {{main actor-isolated 'ns.some' cannot be a 'sending' result. main actor-isolated uses may race with caller uses}}
func testIfLetReturnNoTransfer() -> NonSendableKlass? {
if case .second(let ns) = self {
return ns
}
return nil
}
}
///////////////////////////
// MARK: Async Let Tests //
///////////////////////////
//
// Move these tests to async let once strict-concurrency=complete requires
// transfer non sendable.
// Make sure that we can properly construct a reabstraction thunk since
// constructNonSendableKlassAsync doesn't return the value sending but
// async let wants it to be transferring.
//
// Importantly, we should only emit the sema error here saying that one cannot
// return a non-Sendable value here.
func asyncLetReabstractionThunkTest() async {
// With thunk.
async let newValue: NonSendableKlass = await getValueAsync()
let _ = await newValue
// Without thunk.
async let newValue2: NonSendableKlass = await getValueAsyncTransferring()
let _ = await newValue2
}
func asyncLetReabstractionThunkTest2() async {
// We emit the error here since we are returning a MainActor-isolated value.
async let newValue: NonSendableKlass = await getMainActorValueAsync()
// expected-ni-warning @-1 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'getMainActorValueAsync()' to nonisolated context}}
// expected-ni-ns-warning @-2 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'getMainActorValueAsync()' to @concurrent context}}
let _ = await newValue
// Without thunk.
async let newValue2: NonSendableKlass = await getMainActorValueAsyncTransferring()
let _ = await newValue2
}
@MainActor func asyncLetReabstractionThunkTestGlobalActor() async {
// With thunk we do not emit an error since our async let is not main actor
// isolated despite being in an @MainActor function.
async let newValue: NonSendableKlass = await getValueAsync()
let _ = await newValue
// Without thunk.
async let newValue2: NonSendableKlass = await getValueAsyncTransferring()
let _ = await newValue2
}
@MainActor func asyncLetReabstractionThunkTestGlobalActor2() async {
// We emit the error here since we are returning a MainActor-isolated value.
async let newValue: NonSendableKlass = await getMainActorValueAsync()
// expected-ni-warning @-1 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'getMainActorValueAsync()' to nonisolated context}}
// expected-ni-ns-warning @-2 {{non-Sendable 'NonSendableKlass'-typed result can not be returned from main actor-isolated global function 'getMainActorValueAsync()' to @concurrent context}}
let _ = await newValue
// Without thunk.
async let newValue2: NonSendableKlass = await getMainActorValueAsyncTransferring()
let _ = await newValue2
}
///////////////////////////////////
// MARK: Indirect Sending Result //
///////////////////////////////////
func indirectSending<T>(_ t: T) -> sending T {
return t // expected-warning {{returning task-isolated 't' as a 'sending' result risks causing data races}}
// expected-note @-1 {{returning task-isolated 't' risks causing data races since the caller assumes that 't' can be safely sent to other isolation domains}}
}
func indirectSendingStructField<T>(_ t: GenericNonSendableStruct<T>) -> sending T {
return t.t // expected-warning {{returning task-isolated 't.t' as a 'sending' result risks causing data races}}
// expected-note @-1 {{returning task-isolated 't.t' risks causing data races since the caller assumes that 't.t' can be safely sent to other isolation domains}}
}
func indirectSendingStructOptionalField<T>(_ t: GenericNonSendableStruct<T>) -> sending T {
return t.t2! // expected-warning {{returning task-isolated 't.t2.some' as a 'sending' result risks causing data races}}
// expected-note @-1 {{returning task-isolated 't.t2.some' risks causing data races since the caller assumes that 't.t2.some' can be safely sent to other isolation domains}}
}
func indirectSendingClassField<T>(_ t: GenericNonSendableKlass<T>) -> sending T {
return t.t // expected-warning {{returning task-isolated 't.t' as a 'sending' result risks causing data races}}
// expected-note @-1 {{returning task-isolated 't.t' risks causing data races since the caller assumes that 't.t' can be safely sent to other isolation domains}}
}
func indirectSendingOptionalClassField<T>(_ t: GenericNonSendableKlass<T>) -> sending T {
return t.t2! // expected-warning {{returning a task-isolated 'Optional<T>' value as a 'sending' result risks causing data races}}
// expected-note @-1 {{returning a task-isolated 'Optional<T>' value risks causing races since the caller assumes the value can be safely sent to other isolation domains}}
// expected-note @-2 {{'Optional<T>' is a non-Sendable type}}
}
func useBlock<T>(block: () throws -> T) throws -> sending T {
fatalError()
}
extension MyActor {
// This shouldn't emit any errors. We used to error on returning result.
public func withContext<T>(_ block: sending () throws -> T) async throws -> sending T {
let value: T = try useBlock {
_ = ns
return try block()
}
return value
}
}