Skip to content

Commit 3f6a0cc

Browse files
committed
Allow a global-actor to be dropped for some non-async functions.
It's ok to drop the global-actor qualifier `@G` from a function's type if: - the cast is happening in a context isolated to global-actor `G` - the function value will not be `@Sendable` - the function value is not `async` It's primarily safe to drop the attribute because we're already in the same isolation domain. So it's OK to simply drop the global-actor if we prevent the value from later leaving that isolation domain. This means we no longer need to warn about code like this: ``` @mainactor func doIt(_ x: [Int], _ f: @mainactor (Int) -> ()) { x.forEach(f) // warning: converting function value of type '@mainactor (Int) -> ()' to '(Int) throws -> Void' loses global actor 'MainActor' } ``` NOTE: this implementation is a bit gross in that the constraint solver might emit false warnings about casts it introduced that are actually safe. This is mainly because closure isolation is only fully determined after constraint solving. See the FIXME's for more details. resolves rdar://94462333
1 parent 5e59444 commit 3f6a0cc

File tree

11 files changed

+416
-13
lines changed

11 files changed

+416
-13
lines changed

include/swift/AST/ActorIsolation.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,26 @@ bool isSameActorIsolated(ValueDecl *value, DeclContext *dc);
238238
/// Determines whether this function's body uses flow-sensitive isolation.
239239
bool usesFlowSensitiveIsolation(AbstractFunctionDecl const *fn);
240240

241+
/// Check if it is safe for the \c globalActor qualifier to be removed from
242+
/// \c ty, when the function value of that type is isolated to that actor.
243+
///
244+
/// In general this is safe in a narrow but common case: a global actor
245+
/// qualifier can be dropped from a function type while in a DeclContext
246+
/// isolated to that same actor, as long as the value is not Sendable.
247+
///
248+
/// \param dc the innermost context in which the cast to remove the global actor
249+
/// is happening.
250+
/// \param globalActor global actor that was dropped from \c ty.
251+
/// \param ty a function type where \c globalActor was removed from it.
252+
/// \param getClosureActorIsolation function that knows how to produce accurate
253+
/// information about the isolation of a closure.
254+
/// \return true if it is safe to drop the global-actor qualifier.
255+
bool safeToDropGlobalActor(
256+
DeclContext *dc, Type globalActor, Type ty,
257+
llvm::function_ref<ClosureActorIsolation(AbstractClosureExpr *)>
258+
getClosureActorIsolation =
259+
_getRef__AbstractClosureExpr_getActorIsolation());
260+
241261
void simple_display(llvm::raw_ostream &out, const ActorIsolation &state);
242262

243263
} // end namespace swift

include/swift/Sema/CSFix.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ class MarkGlobalActorFunction final : public ContextualMismatch {
858858
}
859859

860860
public:
861-
std::string getName() const override { return "add @escaping"; }
861+
std::string getName() const override { return "add global actor"; }
862862

863863
bool diagnose(const Solution &solution, bool asNote = false) const override;
864864

include/swift/Sema/ConstraintSystem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2750,7 +2750,7 @@ struct GetClosureType {
27502750
Type operator()(const AbstractClosureExpr *expr) const;
27512751
};
27522752

2753-
/// Retrieve the closure type from the constraint system.
2753+
/// Retrieve the closure's preconcurrency status from the constraint system.
27542754
struct ClosureIsolatedByPreconcurrency {
27552755
ConstraintSystem &cs;
27562756

lib/Sema/CSSimplify.cpp

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,68 @@ bool ConstraintSystem::hasPreconcurrencyCallee(
29202920
return calleeOverload->choice.getDecl()->preconcurrency();
29212921
}
29222922

2923+
/// Determines whether a DeclContext is preconcurrency, using information
2924+
/// tracked by the solver to aid in answering that.
2925+
static bool isPreconcurrency(ConstraintSystem &cs, DeclContext *dc) {
2926+
if (auto *decl = dc->getAsDecl())
2927+
return decl->preconcurrency();
2928+
2929+
if (auto *ce = dyn_cast<ClosureExpr>(dc)) {
2930+
return ClosureIsolatedByPreconcurrency{cs}(ce);
2931+
}
2932+
2933+
if (auto *autoClos = dyn_cast<AutoClosureExpr>(dc)) {
2934+
return isPreconcurrency(cs, autoClos->getParent());
2935+
}
2936+
2937+
llvm_unreachable("unhandled DeclContext kind in isPreconcurrency");
2938+
}
2939+
2940+
/// A thin wrapper around \c swift::safeToDropGlobalActor that provides the
2941+
/// ability to infer the isolation of a closure. Not perfect but mostly works.
2942+
static bool okToRemoveGlobalActor(ConstraintSystem &cs,
2943+
DeclContext *dc,
2944+
Type globalActor, Type ty) {
2945+
auto findGlobalActorForClosure = [&](AbstractClosureExpr *ace) -> ClosureActorIsolation {
2946+
// FIXME: Because the actor isolation checking happens after constraint
2947+
// solving, the closure expression does not yet have its actor isolation
2948+
// set, i.e., the code that would call
2949+
// `AbstractClosureExpr::setActorIsolation` hasn't run yet.
2950+
// So, I expect the existing isolation to always be set to the default.
2951+
// If the assertion below starts tripping, then this ad-hoc inference
2952+
// is no longer needed!
2953+
auto existingIso = ace->getActorIsolation();
2954+
if (existingIso != ClosureActorIsolation()) {
2955+
assert(false && "somebody set the closure's isolation already?");
2956+
return existingIso;
2957+
}
2958+
2959+
// Otherwise, do an ad-hoc inference of the closure's isolation.
2960+
2961+
// see if the closure's type has isolation.
2962+
if (auto closType = GetClosureType{cs}(ace)) {
2963+
if (auto fnTy = closType->getAs<AnyFunctionType>()) {
2964+
if (auto globActor = fnTy->getGlobalActor()) {
2965+
return ClosureActorIsolation::forGlobalActor(globActor,
2966+
isPreconcurrency(cs, ace));
2967+
}
2968+
}
2969+
}
2970+
2971+
// otherwise, check for an explicit annotation
2972+
if (auto *ce = dyn_cast<ClosureExpr>(ace)) {
2973+
if (auto globActor = getExplicitGlobalActor(ce)) {
2974+
return ClosureActorIsolation::forGlobalActor(globActor,
2975+
isPreconcurrency(cs, ce));
2976+
}
2977+
}
2978+
2979+
return existingIso;
2980+
};
2981+
2982+
return safeToDropGlobalActor(dc, globalActor, ty, findGlobalActorForClosure);
2983+
}
2984+
29232985
ConstraintSystem::TypeMatchResult
29242986
ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
29252987
ConstraintKind kind, TypeMatchOptions flags,
@@ -2999,10 +3061,30 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
29993061
ConstraintKind::Equal, subflags, locator);
30003062
if (result == SolutionKind::Error)
30013063
return getTypeMatchFailure(locator);
3064+
30023065
} else if (func1->getGlobalActor() && !func2->isAsync()) {
3003-
// Cannot remove a global actor from a synchronous function.
3004-
if (MarkGlobalActorFunction::attempt(*this, kind, func1, func2, locator))
3066+
// Cannot remove a global actor from a synchronous function in mismatched
3067+
// DeclContext.
3068+
//
3069+
// FIXME: the ConstraintSystem's DeclContext is not always precise enough
3070+
// to give an accurate answer. We want the innermost DeclContext that
3071+
// contains the expression associated with the constraint locator.
3072+
// Sometimes the ConstraintSystem only has the enclosing function, and not
3073+
// the inner closure containing the expression. To workaround this,
3074+
// `ActorIsolationChecker::checkFunctionConversion`, has extra checking of
3075+
// function conversions specifically to account for this false positive.
3076+
// This means we may have duplicate diagnostics emitted.
3077+
if (okToRemoveGlobalActor(*this, DC, func1->getGlobalActor(), func2)) {
3078+
// FIXME: this is a bit of a hack to workaround multiple solutions
3079+
// because in these contexts it's valid to both add or remove the actor
3080+
// from these function types. At least with the score increases, we
3081+
// can bias the solver to pick the solution with fewer conversions.
3082+
increaseScore(SK_FunctionConversion);
3083+
3084+
} else if (MarkGlobalActorFunction::attempt(*this, kind, func1, func2, locator)) {
30053085
return getTypeMatchFailure(locator);
3086+
}
3087+
30063088
} else if (kind < ConstraintKind::Subtype) {
30073089
return getTypeMatchFailure(locator);
30083090
} else {
@@ -3011,6 +3093,7 @@ ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
30113093
// is a function conversion going on here, let's increase the score to
30123094
// avoid ambiguity when solver can also match a global actor matching
30133095
// function type.
3096+
// FIXME: there may be a better way. see https://github.com/apple/swift/pull/62514
30143097
increaseScore(SK_FunctionConversion);
30153098
}
30163099
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,41 @@ bool swift::isAsyncDecl(ConcreteDeclRef declRef) {
15261526
return false;
15271527
}
15281528

1529+
bool swift::safeToDropGlobalActor(
1530+
DeclContext *dc, Type globalActor, Type ty,
1531+
llvm::function_ref<ClosureActorIsolation(AbstractClosureExpr *)>
1532+
getClosureActorIsolation) {
1533+
auto funcTy = ty->getAs<AnyFunctionType>();
1534+
if (!funcTy)
1535+
return false;
1536+
1537+
// can't add a different global actor
1538+
if (auto otherGA = funcTy->getGlobalActor()) {
1539+
assert(otherGA->getCanonicalType() != globalActor->getCanonicalType()
1540+
&& "not even dropping the actor?");
1541+
return false;
1542+
}
1543+
1544+
// We currently allow unconditional dropping of global actors from
1545+
// async function types, despite this confusing Sendable checking
1546+
// in light of SE-338.
1547+
if (funcTy->isAsync())
1548+
return true;
1549+
1550+
// fundamentally cannot be sendable if we want to drop isolation info
1551+
if (funcTy->isSendable())
1552+
return false;
1553+
1554+
// finally, must be in a context with matching isolation.
1555+
auto dcIsolation = getActorIsolationOfContext(dc, getClosureActorIsolation);
1556+
if (dcIsolation.isGlobalActor())
1557+
if (dcIsolation.getGlobalActor()->getCanonicalType()
1558+
== globalActor->getCanonicalType())
1559+
return true;
1560+
1561+
return false;
1562+
}
1563+
15291564
static FuncDecl *findAnnotatableFunction(DeclContext *dc) {
15301565
auto fn = dyn_cast<FuncDecl>(dc);
15311566
if (!fn) return nullptr;
@@ -1737,6 +1772,41 @@ namespace {
17371772
return false;
17381773
}
17391774

1775+
/// Some function conversions synthesized by the constraint solver may not
1776+
/// be correct AND the solver doesn't know, so we must emit a diagnostic.
1777+
void checkFunctionConversion(FunctionConversionExpr *funcConv) {
1778+
auto subExprType = funcConv->getSubExpr()->getType();
1779+
if (auto fromType = subExprType->getAs<FunctionType>()) {
1780+
if (auto fromActor = fromType->getGlobalActor()) {
1781+
if (auto toType = funcConv->getType()->getAs<FunctionType>()) {
1782+
1783+
// ignore some kinds of casts, as they're diagnosed elsewhere.
1784+
if (toType->hasGlobalActor() || toType->isAsync())
1785+
return;
1786+
1787+
auto dc = const_cast<DeclContext*>(getDeclContext());
1788+
if (!safeToDropGlobalActor(dc, fromActor, toType)) {
1789+
// FIXME: this diagnostic is sometimes a duplicate of one emitted
1790+
// by the constraint solver. Difference is the solver doesn't use
1791+
// warnUntilSwiftVersion, which appends extra text on the end.
1792+
// So, I'm making the messages exactly the same so IDEs will
1793+
// hopefully ignore the second diagnostic!
1794+
1795+
// otherwise, it's not a safe cast.
1796+
dc->getASTContext()
1797+
.Diags
1798+
.diagnose(funcConv->getLoc(),
1799+
diag::converting_func_loses_global_actor, fromType,
1800+
toType, fromActor)
1801+
.limitBehavior(dc->getASTContext().isSwiftVersionAtLeast(6)
1802+
? DiagnosticBehavior::Error
1803+
: DiagnosticBehavior::Warning);
1804+
}
1805+
}
1806+
}
1807+
}
1808+
}
1809+
17401810
/// Check closure captures for Sendable violations.
17411811
void checkClosureCaptures(AbstractClosureExpr *closure) {
17421812
SmallVector<CapturedValue, 2> captures;
@@ -1994,6 +2064,11 @@ namespace {
19942064
}
19952065
}
19962066

2067+
// The constraint solver may not have chosen legal casts.
2068+
if (auto funcConv = dyn_cast<FunctionConversionExpr>(expr)) {
2069+
checkFunctionConversion(funcConv);
2070+
}
2071+
19972072
return Action::Continue(expr);
19982073
}
19992074

test/Concurrency/actor_call_implicitly_async.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@ func blender(_ peeler : () -> Void) {
298298
// expected-warning@-1 3{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
299299

300300
blender((peelBanana))
301-
// expected-warning@-1{{converting function value of type '@BananaActor () -> ()' to '() -> Void' loses global actor 'BananaActor'}}
301+
// expected-warning@-1 2{{converting function value of type '@BananaActor () -> ()' to '() -> Void' loses global actor 'BananaActor'}}
302+
302303
await wisk(peelBanana)
303304
// expected-warning@-1{{non-sendable type 'Any' passed in call to global actor 'BananaActor'-isolated function cannot cross actor boundary}}
304305

test/Concurrency/actor_isolation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ func testGlobalActorClosures() {
463463
return 17
464464
}
465465

466-
acceptConcurrentClosure { @SomeGlobalActor in 5 } // expected-warning{{converting function value of type '@SomeGlobalActor @Sendable () -> Int' to '@Sendable () -> Int' loses global actor 'SomeGlobalActor'}}
466+
acceptConcurrentClosure { @SomeGlobalActor in 5 } // expected-warning 2{{converting function value of type '@SomeGlobalActor @Sendable () -> Int' to '@Sendable () -> Int' loses global actor 'SomeGlobalActor'}}
467467
}
468468

469469
@available(SwiftStdlib 5.1, *)

0 commit comments

Comments
 (0)