Skip to content

[Concurrency] Perform Sendable checking on parameter and result types for async function conversions that change actor isolation. #72691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5611,6 +5611,14 @@ ERROR(non_sendable_param_type,none,
"in parameter of superclass method overridden by %3 %kind2|"
"in parameter of %3 '@objc' %kind2}1 cannot cross actor boundary",
(Type, unsigned, const ValueDecl *, ActorIsolation))
ERROR(non_sendable_param_reference,none,
"non-sendable parameter type %0 in conversion from %1 function to "
"%2 function",
(Type, ActorIsolation, ActorIsolation))
ERROR(non_sendable_result_reference,none,
"non-sendable result type %0 in conversion from %1 function to "
"%2 function",
(Type, ActorIsolation, ActorIsolation))
ERROR(non_sendable_call_argument,none,
"passing argument of non-sendable type %0 %select{into %2 context|"
"outside of %2 context}1 may introduce data races",
Expand Down
38 changes: 30 additions & 8 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1917,7 +1917,8 @@ bool swift::isAsyncDecl(ConcreteDeclRef declRef) {
/// \param ty a function type where \c globalActor was removed from it.
/// \return true if it is safe to drop the global-actor qualifier.
static bool safeToDropGlobalActor(
DeclContext *dc, Type globalActor, Type ty, ApplyExpr *call) {
DeclContext *dc, Type globalActor, Type ty,
ApplyExpr *call, SourceLoc loc) {
auto funcTy = ty->getAs<AnyFunctionType>();
if (!funcTy)
return false;
Expand All @@ -1936,11 +1937,30 @@ static bool safeToDropGlobalActor(
if (otherIsolation.isErased())
return true;

// We currently allow unconditional dropping of global actors from
// async function types, despite this confusing Sendable checking
// in light of SE-338.
if (funcTy->isAsync())
// Converting from a global actor isolated type to an async function
// type is okay as long as the parameter and result types are 'Sendable',
// because the argument and result values will cross an isolation boundary
// in the conversion thunk.
if (funcTy->isAsync()) {
auto sourceIsolation = ActorIsolation::forGlobalActor(globalActor);
for (auto param : funcTy->getParams()) {
auto paramType = param.getPlainType();
diagnoseNonSendableTypes(
paramType, dc, /*inDerivedConformance*/Type(),
loc, diag::non_sendable_param_reference,
sourceIsolation,
ActorIsolation::forNonisolated(/*unsafe*/false));
}

auto resultType = funcTy->getResult();
diagnoseNonSendableTypes(
resultType, dc, /*inDerivedConformance*/Type(),
loc, diag::non_sendable_result_reference,
sourceIsolation,
ActorIsolation::forNonisolated(/*unsafe*/false));

return true;
}

// If the argument is passed over an isolation boundary, it's not
// safe to erase actor isolation, because the callee can call the
Expand Down Expand Up @@ -2365,13 +2385,15 @@ namespace {
if (auto fromActor = fromFnType->getGlobalActor()) {
if (auto toFnType = toType->getAs<FunctionType>()) {

// ignore some kinds of casts, as they're diagnosed elsewhere.
if (toFnType->hasGlobalActor() || toFnType->isAsync())
// The conversion is okay if the global actors match.
if (toFnType->hasGlobalActor() &&
fromActor->isEqual(toFnType->getGlobalActor()))
return;

auto dc = const_cast<DeclContext*>(getDeclContext());
if (!safeToDropGlobalActor(dc, fromActor, toType,
getImmediateApply())) {
getImmediateApply(),
funcConv->getLoc())) {
// otherwise, it's not a safe cast.
dc->getASTContext()
.Diags
Expand Down
14 changes: 14 additions & 0 deletions test/Concurrency/global_actor_function_types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,17 @@ extension GlobalType {
rhs: GlobalType
) -> Bool { true }
}

// expected-complete-tns-note@+1 2 {{class 'NotSendable' does not conform to the 'Sendable' protocol}}
class NotSendable {}

func testUnsafeAsyncFunctionConversions(
f: @escaping @MainActor (NotSendable) async -> Void,
g: @escaping @MainActor () async -> NotSendable
) {
// expected-complete-tns-warning@+1 {{non-sendable parameter type 'NotSendable' in conversion from main actor-isolated function to nonisolated function; this is an error in the Swift 6 language mode}}
let _: (NotSendable) async -> Void = f

// expected-complete-tns-warning@+1 {{non-sendable result type 'NotSendable' in conversion from main actor-isolated function to nonisolated function; this is an error in the Swift 6 language mode}}
let _: () async -> NotSendable = g
}