Skip to content

Commit f594699

Browse files
authored
Merge pull request #62153 from kavon/lose-mainactor-94462333
Allow a global-actor to be dropped for some non-async functions.
2 parents 7080f67 + 3f6a0cc commit f594699

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)