[rbi] Teach SendNonSendable how to more aggressively suppress sending errors around obfuscated Sendable functions

Specifically the type checker to work around interface types not having
isolation introduces casts into the AST that enrich the AST with isolation
information. Part of that information is Sendable. This means that we can
sometimes lose due to conversions that a function is actually Sendable. To work
around this, we today suppress those errors when they are emitted (post 6.2, we
should just change their classification as being Sendable... but I don't want to
make that change now).

This change just makes the pattern matching for these conversions handle more
cases so that transfernonsendable_closureliterals_isolationinference.swift now
passes.
This commit is contained in:
Michael Gottesman
2025-07-10 10:46:31 -07:00
parent ea4d0440f4
commit 73e34f020b
2 changed files with 31 additions and 6 deletions

View File

@@ -1601,9 +1601,32 @@ public:
} }
private: private:
bool isConvertFunctionFromSendableType(SILValue equivalenceClassRep) const { /// To work around not having isolation in interface types, the type checker
/// inserts casts and other AST nodes that are used to enrich the AST with
/// isolation information. This results in Sendable functions being
/// wrapped/converted/etc in ways that hide the Sendability. This helper looks
/// through these conversions/wrappers/thunks to see if the original
/// underlying function is Sendable.
///
/// The two ways this can happen is that we either get an actual function_ref
/// that is Sendable or we get a convert function with a Sendable operand.
bool isHiddenSendableFunctionType(SILValue equivalenceClassRep) const {
SILValue valueToTest = equivalenceClassRep; SILValue valueToTest = equivalenceClassRep;
while (true) { while (true) {
if (auto *pai = dyn_cast<PartialApplyInst>(valueToTest)) {
if (auto *calleeFunction = pai->getCalleeFunction()) {
if (pai->getNumArguments() >= 1 &&
pai->getArgument(0)->getType().isFunction() &&
calleeFunction->isThunk()) {
valueToTest = pai->getArgument(0);
continue;
}
if (calleeFunction->getLoweredFunctionType()->isSendable())
return true;
}
}
if (auto *i = dyn_cast<ThinToThickFunctionInst>(valueToTest)) { if (auto *i = dyn_cast<ThinToThickFunctionInst>(valueToTest)) {
valueToTest = i->getOperand(); valueToTest = i->getOperand();
continue; continue;
@@ -1615,6 +1638,9 @@ private:
break; break;
} }
if (auto *fn = dyn_cast<FunctionRefInst>(valueToTest))
return fn->getReferencedFunction()->getLoweredFunctionType()->isSendable();
auto *cvi = dyn_cast<ConvertFunctionInst>(valueToTest); auto *cvi = dyn_cast<ConvertFunctionInst>(valueToTest);
if (!cvi) if (!cvi)
return false; return false;
@@ -1647,7 +1673,7 @@ private:
// See if we have a convert function from a `@Sendable` type. In this // See if we have a convert function from a `@Sendable` type. In this
// case, we want to squelch the error. // case, we want to squelch the error.
if (isConvertFunctionFromSendableType(equivalenceClassRep)) if (isHiddenSendableFunctionType(equivalenceClassRep))
return; return;
} }
@@ -1692,7 +1718,7 @@ private:
// See if we have a convert function from a `@Sendable` type. In this // See if we have a convert function from a `@Sendable` type. In this
// case, we want to squelch the error. // case, we want to squelch the error.
if (isConvertFunctionFromSendableType(equivalenceClassRep)) if (isHiddenSendableFunctionType(equivalenceClassRep))
return; return;
} }
} }

View File

@@ -34,9 +34,8 @@ func useValue<T>(_ t: T) {}
@MainActor func testGlobalFakeInit() { @MainActor func testGlobalFakeInit() {
let ns = NonSendableKlass() let ns = NonSendableKlass()
// Will be resolved once @MainActor is @Sendable. Task.fakeInit { @MainActor in
Task.fakeInit { @MainActor in // expected-error {{passing closure as a 'sending' parameter risks causing data races between main actor-isolated code and concurrent execution of the closure}} print(ns)
print(ns) // expected-note {{closure captures 'ns' which is accessible to main actor-isolated code}}
} }
useValue(ns) useValue(ns)