Skip to content

Commit 9604304

Browse files
committed
Downgrade more errors into warnings for actor inits.
In the replacement of the escaping-use restriction with flow-isolation, I hadn't accounted for all of the situations where the isolation changes would break backwards compatability with Swift 5.5 programs. The escaping-use restriction permitted a lot of very unsafe things with warnings that it would become an error in Swift 6. With the introduction of flow-isolation, it was a bit tricky to get the right warnings back in place, while not unnessecarily warning about property accesses that might actually be OK. There is a very careful coordination between the type-checker and the flow-isolation pass. While I had done these downgrades for deinits, I also needed to do them for inits as well, because member accesses to isolated methods within actor initializer were still getting rejected as an error. This patch should be pretty solid now. fixes rdar://90595278
1 parent 5afa1b1 commit 9604304

File tree

4 files changed

+189
-63
lines changed

4 files changed

+189
-63
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4517,17 +4517,13 @@ NOTE(note_distributed_actor_system_conformance_missing_adhoc_requirement,none,
45174517
ERROR(override_implicit_unowned_executor,none,
45184518
"cannot override an actor's 'unownedExecutor' property that wasn't "
45194519
"explicitly defined", ())
4520-
ERROR(actor_isolated_from_decl,none,
4521-
"actor-isolated %0 %1 can not be referenced from a non-isolated "
4522-
"%select{deinit|autoclosure|closure}2",
4523-
(DescriptiveDeclKind, DeclName, unsigned))
45244520
ERROR(actor_isolated_non_self_reference,none,
45254521
"actor-isolated %0 %1 can not be "
45264522
"%select{referenced|mutated|used 'inout'}2 "
45274523
"%select{on a non-isolated actor instance|"
45284524
"from a Sendable function|from a Sendable closure|"
45294525
"from an 'async let' initializer|from global actor %4|"
4530-
"from the main actor|from a non-isolated context}3",
4526+
"from the main actor|from a non-isolated context|from a non-isolated autoclosure}3",
45314527
(DescriptiveDeclKind, DeclName, unsigned, unsigned, Type))
45324528
ERROR(distributed_actor_isolated_non_self_reference,none,
45334529
"distributed actor-isolated %0 %1 can not be accessed from a "

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 135 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,10 @@ namespace {
12721272
// It is within a nonisolated context.
12731273
NonIsolatedContext,
12741274

1275+
// It is within a nonisolated autoclosure argument. This is primarily here
1276+
// to aid in giving specific diagnostics, because autoclosures are not
1277+
// always easy for programmers to notice.
1278+
NonIsolatedAutoclosure
12751279
};
12761280

12771281
VarDecl * const actor;
@@ -1351,7 +1355,7 @@ namespace {
13511355
///
13521356
/// @returns None if the context expression is either an InOutExpr,
13531357
/// not tracked, or if the decl is not a property or subscript
1354-
Optional<VarRefUseEnv> kindOfUsage(ValueDecl *decl, Expr *use) const {
1358+
Optional<VarRefUseEnv> kindOfUsage(ValueDecl const* decl, Expr *use) const {
13551359
// we need a use for lookup.
13561360
if (!use)
13571361
return None;
@@ -1751,6 +1755,16 @@ namespace {
17511755
auto var = getReferencedParamOrCapture(expr);
17521756
bool isPotentiallyIsolated = isPotentiallyIsolatedActor(var);
17531757

1758+
// helps aid in giving more informative diagnostics for autoclosure args.
1759+
auto specificNonIsoClosureKind =
1760+
[](DeclContext const* dc) -> ReferencedActor::Kind {
1761+
if (auto autoClos = dyn_cast<AutoClosureExpr>(dc))
1762+
if (autoClos->getThunkKind() == AutoClosureExpr::Kind::None)
1763+
return ReferencedActor::NonIsolatedAutoclosure;
1764+
1765+
return ReferencedActor::NonIsolatedContext;
1766+
};
1767+
17541768
// Walk the scopes between the variable reference and the variable
17551769
// declaration to determine whether it is still isolated.
17561770
auto dc = const_cast<DeclContext *>(getDeclContext());
@@ -1774,7 +1788,7 @@ namespace {
17741788
return ReferencedActor(var, isPotentiallyIsolated, ReferencedActor::SendableClosure);
17751789
}
17761790

1777-
return ReferencedActor(var, isPotentiallyIsolated, ReferencedActor::NonIsolatedContext);
1791+
return ReferencedActor(var, isPotentiallyIsolated, specificNonIsoClosureKind(dc));
17781792

17791793
case ClosureActorIsolation::ActorInstance:
17801794
// If the closure is isolated to the same variable, we're all set.
@@ -1786,7 +1800,7 @@ namespace {
17861800
return ReferencedActor(var, isPotentiallyIsolated, ReferencedActor::Isolated);
17871801
}
17881802

1789-
return ReferencedActor(var, isPotentiallyIsolated, ReferencedActor::NonIsolatedContext);
1803+
return ReferencedActor(var, isPotentiallyIsolated, specificNonIsoClosureKind(dc));
17901804

17911805
case ClosureActorIsolation::GlobalActor:
17921806
return ReferencedActor::forGlobalActor(
@@ -1939,7 +1953,7 @@ namespace {
19391953

19401954
/// Note that the given actor member is isolated.
19411955
/// @param context is allowed to be null if no context is appropriate.
1942-
void noteIsolatedActorMember(ValueDecl *decl, Expr *context) {
1956+
void noteIsolatedActorMember(ValueDecl const* decl, Expr *context) {
19431957
// detect if it is a distributed actor, to provide better isolation notes
19441958

19451959
auto nominal = decl->getDeclContext()->getSelfNominalTypeDecl();
@@ -2693,40 +2707,124 @@ namespace {
26932707
return false;
26942708
}
26952709

2696-
/// an ad-hoc check specific to member isolation checking.
2697-
static bool memberAccessWasAllowedInSwift5(DeclContext const *refCxt,
2698-
ValueDecl const *member,
2699-
SourceLoc memberLoc) {
2710+
/// Based on the former escaping-use restriction, which was replaced by
2711+
/// flow-isolation. We need this to support backwards compatability in the
2712+
/// type-checker for programs prior to Swift 6.
2713+
/// \param fn either a constructor or destructor of an actor.
2714+
static bool wasLegacyEscapingUseRestriction(AbstractFunctionDecl *fn) {
2715+
assert(fn->getDeclContext()->getSelfClassDecl()->isAnyActor());
2716+
assert(isa<ConstructorDecl>(fn) || isa<DestructorDecl>(fn));
2717+
2718+
// according to today's isolation, determine whether it use to have the
2719+
// escaping-use restriction
2720+
switch (getActorIsolation(fn).getKind()) {
2721+
case ActorIsolation::Independent:
2722+
case ActorIsolation::GlobalActor:
2723+
case ActorIsolation::GlobalActorUnsafe:
2724+
// convenience inits did not have the restriction.
2725+
if (auto *ctor = dyn_cast<ConstructorDecl>(fn))
2726+
if (ctor->isConvenienceInit())
2727+
return false;
2728+
2729+
break; // goto basic case
2730+
2731+
case ActorIsolation::ActorInstance:
2732+
// none of these had the restriction affect them.
2733+
assert(fn->hasAsync());
2734+
return false;
2735+
2736+
case ActorIsolation::Unspecified:
2737+
// this is basically just objc-marked inits.
2738+
break;
2739+
};
2740+
2741+
return !(fn->hasAsync()); // basic case: not async = had restriction.
2742+
}
2743+
2744+
/// An ad-hoc check specific to member isolation checking. assumed to be
2745+
/// queried when a self-member is being accessed in a context which is not
2746+
/// isolated to self. The "special permission" is part of a backwards
2747+
/// compatability with actor inits and deinits that maintains the
2748+
/// permissive nature of the escaping-use restriction, which was only
2749+
/// staged in as a warning. See implementation for more details.
2750+
///
2751+
/// \returns true if this access in the given context should be allowed
2752+
/// in Sema, with the side-effect of emitting a warning as needed.
2753+
/// If false is returned, then the "special permission" was not granted.
2754+
bool memberAccessHasSpecialPermissionInSwift5(DeclContext const *refCxt,
2755+
ReferencedActor &baseActor,
2756+
ValueDecl const *member,
2757+
SourceLoc memberLoc,
2758+
Expr *exprCxt) {
27002759
// no need for this in Swift 6+
27012760
if (refCxt->getASTContext().isSwiftVersionAtLeast(6))
27022761
return false;
27032762

2704-
// In Swift 5, we were allowing all members to be referenced from a
2705-
// deinit, nested within a wide variety of contexts.
2763+
// must be an access to an instance member.
2764+
if (!member->isInstanceMember())
2765+
return false;
2766+
2767+
// In the history of actor initializers prior to Swift 6, self-isolated
2768+
// members could be referenced from any init or deinit, even a synchronous
2769+
// one, with no diagnostics at all.
2770+
//
2771+
// When the escaping-use restriction came into place for the release of
2772+
// 5.5, it was implemented as a warning and only applied to initializers,
2773+
// which stated that it would become an error in Swift 6.
2774+
//
2775+
// Once 5.6 was released, we also added restrictions in the deinits of
2776+
// actors, at least for accessing members other than stored properties.
2777+
//
2778+
// Later on, for 5.7 we introduced flow-isolation as part of SE-327 for
2779+
// both inits and deinits. This meant that stored property accesses now
2780+
// are only sometimes going to be problematic. This change also brought
2781+
// official changes in isolation for the inits and deinits to handle the
2782+
// the non-stored-property members. Since those isolation changes are
2783+
// currently in place, the purpose of the code below is to override the
2784+
// isolation checking, so that the now-mismatched isolation on member
2785+
// access is still permitted, but with a warning stating that it will
2786+
// be rejected in Swift 6.
2787+
//
2788+
// In the checking below, we let stored-property accesses go ignored,
2789+
// so that flow-isolation can warn about them only if needed. This helps
2790+
// prevent needless warnings on property accesses that will actually be OK
2791+
// with flow-isolation in the future.
27062792
if (auto oldFn = isActorInitOrDeInitContext(refCxt)) {
2707-
if (isa<DestructorDecl>(oldFn) && member->isInstanceMember()) {
2708-
auto &diags = refCxt->getASTContext().Diags;
2709-
2710-
// if the context in which we consider the access matches between
2711-
// old and new, and its a stored property, then skip the warning
2712-
// because it will still be allowed in Swift 6.
2713-
if (!(refCxt == oldFn && isStoredProperty(member))) {
2714-
unsigned cxtKind = 0; // deinit
2715-
2716-
// try to get a better name for this context.
2717-
if (isa<AutoClosureExpr>(refCxt)) {
2718-
cxtKind = 1;
2719-
} else if (isa<AbstractClosureExpr>(refCxt)) {
2720-
cxtKind = 2;
2721-
}
2793+
auto oldFnMut = const_cast<AbstractFunctionDecl*>(oldFn);
27222794

2723-
diags.diagnose(memberLoc, diag::actor_isolated_from_decl,
2724-
member->getDescriptiveKind(),
2725-
member->getName(),
2726-
cxtKind).warnUntilSwiftVersion(6);
2727-
}
2795+
// If function did not have the escaping-use restriction, then it gets
2796+
// no special permissions here.
2797+
if (!wasLegacyEscapingUseRestriction(oldFnMut))
2798+
return false;
2799+
2800+
// At this point, the special permission will be granted. But, we
2801+
// need to warn now about this permission being taken away in Swift 6
2802+
// for specific kinds of non-stored-property member accesses:
2803+
2804+
// If the context in which we consider the access matches between the
2805+
// old (escaping-use restriction) and new (flow-isolation) contexts,
2806+
// and it is a stored property, then permit it here without any warning.
2807+
// Later, flow-isolation pass will check and emit a warning if needed.
2808+
if (refCxt == oldFn && isStoredProperty(member))
27282809
return true;
2729-
}
2810+
2811+
2812+
// Otherwise, it's definitely going to be illegal, so warn and permit.
2813+
auto &diags = refCxt->getASTContext().Diags;
2814+
auto useKind = static_cast<unsigned>(
2815+
kindOfUsage(member, exprCxt).getValueOr(VarRefUseEnv::Read));
2816+
2817+
diags.diagnose(
2818+
memberLoc, diag::actor_isolated_non_self_reference,
2819+
member->getDescriptiveKind(),
2820+
member->getName(),
2821+
useKind,
2822+
baseActor.kind - 1,
2823+
baseActor.globalActor)
2824+
.warnUntilSwiftVersion(6);
2825+
2826+
noteIsolatedActorMember(member, exprCxt);
2827+
return true;
27302828
}
27312829

27322830
return false;
@@ -2743,10 +2841,11 @@ namespace {
27432841
///
27442842
/// \returns true iff the member access is permitted in Sema because it will
27452843
/// be verified later by flow-isolation.
2746-
static bool checkedByFlowIsolation(DeclContext const *refCxt,
2844+
bool checkedByFlowIsolation(DeclContext const *refCxt,
27472845
ReferencedActor &baseActor,
27482846
ValueDecl const *member,
2749-
SourceLoc memberLoc) {
2847+
SourceLoc memberLoc,
2848+
Expr *exprCxt) {
27502849

27512850
// base of member reference must be `self`
27522851
if (!baseActor.isActorSelf())
@@ -2774,7 +2873,8 @@ namespace {
27742873
break;
27752874
}
27762875

2777-
if (memberAccessWasAllowedInSwift5(refCxt, member, memberLoc))
2876+
if (memberAccessHasSpecialPermissionInSwift5(refCxt, baseActor, member,
2877+
memberLoc, exprCxt))
27782878
return true; // then permit it now.
27792879

27802880
if (!usesFlowSensitiveIsolation(fnDecl))
@@ -2888,7 +2988,7 @@ namespace {
28882988
// access an isolated member on `self`. If that case applies, then we
28892989
// can skip checking.
28902990
if (checkedByFlowIsolation(getDeclContext(), isolatedActor,
2891-
member, memberLoc))
2991+
member, memberLoc, context))
28922992
return false;
28932993

28942994
// An escaping partial application of something that is part of

0 commit comments

Comments
 (0)