Skip to content

Commit e7ef347

Browse files
committed
Sema: Fix accessor availability checking in argument lists.
The changes in swiftlang#72410 caused a regression when checking availability in the following example: ``` // warning: Setter for 'hasDeprecatedSetter' is deprecated: ... x[y.hasDeprecatedSetter] = ... ``` The result of `y.hasDeprecatedSetter` is being passed as an argument to the subscript and its setter will not be called. To fix this, `ExprAvailabilityWalker` now consistently creates a new default `MemberAccessContext` when descending into any `Argument`, since the access context for the expressions surrounding the call should not affect the arguments to the call. Additionally, `MemberAccessContext` has been refactored to better model context state transitions. Instead of only modeling which accessors will be called, the enumeration's members now reflect the possible states that `ExprAvailabilityWalker` can be in during its traversal. This should hopefully make it easier to follow the logic for traversal of `LoadExpr`s and arguments. Resolves rdar://130487998.
1 parent 78112a6 commit e7ef347

File tree

2 files changed

+78
-74
lines changed

2 files changed

+78
-74
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 69 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3319,25 +3319,32 @@ bool swift::diagnoseExplicitUnavailability(
33193319

33203320
namespace {
33213321
class ExprAvailabilityWalker : public ASTWalker {
3322-
/// Describes how the next member reference will be treated as we traverse
3323-
/// the AST.
3322+
/// Models how member references will translate to accessor usage. This is
3323+
/// used to diagnose the availability of individual accessors that may be
3324+
/// called by the expression being checked.
33243325
enum class MemberAccessContext : unsigned {
3325-
/// The member reference is in a context where an access will call
3326-
/// the getter.
3327-
Getter,
3328-
3329-
/// The member reference is in a context where an access will call
3330-
/// the setter.
3331-
Setter,
3332-
3333-
/// The member reference is in a context where it will be turned into
3334-
/// an inout argument. (Once this happens, we have to conservatively assume
3335-
/// that both the getter and setter could be called.)
3336-
InOut
3326+
/// The starting access context for the root of any expression tree. In this
3327+
/// context, a member access will call the get accessor only.
3328+
Default,
3329+
3330+
/// The access context for expressions rooted in a LoadExpr. A LoadExpr
3331+
/// coerces l-values to r-values and thus member access inside of a LoadExpr
3332+
/// will only invoke get accessors.
3333+
Load,
3334+
3335+
/// The access context for the outermost member accessed in the expression
3336+
/// tree on the left-hand side of an assignment. Only the set accessor will
3337+
/// be invoked on this member.
3338+
Assignment,
3339+
3340+
/// The access context for expressions in which member is being read and
3341+
/// then written back to. For example, a writeback will occur inside of an
3342+
/// InOutExpr. Both the get and set accessors may be called in this context.
3343+
Writeback
33373344
};
33383345

33393346
ASTContext &Context;
3340-
MemberAccessContext AccessContext = MemberAccessContext::Getter;
3347+
MemberAccessContext AccessContext = MemberAccessContext::Default;
33413348
SmallVector<const Expr *, 16> ExprStack;
33423349
const ExportContext &Where;
33433350

@@ -3354,6 +3361,13 @@ class ExprAvailabilityWalker : public ASTWalker {
33543361
return MacroWalking::Arguments;
33553362
}
33563363

3364+
PreWalkAction walkToArgumentPre(const Argument &Arg) override {
3365+
// Arguments should be walked in their own member access context which
3366+
// starts out read-only by default.
3367+
walkInContext(Arg.getExpr(), MemberAccessContext::Default);
3368+
return Action::SkipChildren();
3369+
}
3370+
33573371
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
33583372
auto *DC = Where.getDeclContext();
33593373

@@ -3476,6 +3490,11 @@ class ExprAvailabilityWalker : public ASTWalker {
34763490
ME->getMacroRef(), ME->getMacroNameLoc().getSourceRange());
34773491
}
34783492

3493+
if (auto LE = dyn_cast<LoadExpr>(E)) {
3494+
walkLoadExpr(LE);
3495+
return Action::SkipChildren(E);
3496+
}
3497+
34793498
return Action::Continue(E);
34803499
}
34813500

@@ -3532,26 +3551,6 @@ class ExprAvailabilityWalker : public ASTWalker {
35323551
return call;
35333552
}
35343553

3535-
/// Walks up from a potential member reference to the first LoadExpr that would
3536-
/// make the member reference an r-value instead of an l-value.
3537-
const LoadExpr *getEnclosingLoadExpr() const {
3538-
assert(!ExprStack.empty() && "must be called while visiting an expression");
3539-
ArrayRef<const Expr *> stack = ExprStack;
3540-
stack = stack.drop_back();
3541-
3542-
for (auto expr : llvm::reverse(stack)) {
3543-
// Do not search past the first enclosing ApplyExpr. Any enclosing
3544-
// LoadExpr from this point only applies to the result of the call.
3545-
if (auto applyExpr = dyn_cast<ApplyExpr>(expr))
3546-
return nullptr;
3547-
3548-
if (auto loadExpr = dyn_cast<LoadExpr>(expr))
3549-
return loadExpr;
3550-
}
3551-
3552-
return nullptr;
3553-
}
3554-
35553554
/// Walk an assignment expression, checking for availability.
35563555
void walkAssignExpr(AssignExpr *E) {
35573556
// We take over recursive walking of assignment expressions in order to
@@ -3567,32 +3566,38 @@ class ExprAvailabilityWalker : public ASTWalker {
35673566
// encountered walking (pre-order) is the Dest is the destination of the
35683567
// write. For the moment this is fine -- but future syntax might violate
35693568
// this assumption.
3570-
walkInContext(E, Dest, MemberAccessContext::Setter);
3569+
walkInContext(Dest, MemberAccessContext::Assignment);
35713570

35723571
// Check RHS in getter context
35733572
Expr *Source = E->getSrc();
35743573
if (!Source) {
35753574
return;
35763575
}
3577-
walkInContext(E, Source, MemberAccessContext::Getter);
3576+
walkInContext(Source, MemberAccessContext::Default);
35783577
}
3579-
3578+
3579+
/// Walk a load expression, checking for availability.
3580+
void walkLoadExpr(LoadExpr *E) {
3581+
walkInContext(E->getSubExpr(), MemberAccessContext::Load);
3582+
}
3583+
35803584
/// Walk a member reference expression, checking for availability.
35813585
void walkMemberRef(MemberRefExpr *E) {
3582-
// Walk the base. If the access context is currently `Setter`, then we must
3583-
// be diagnosing the destination of an assignment. When recursing, diagnose
3584-
// any remaining member refs as if they were in an InOutExpr, since there is
3585-
// a writeback occurring through them as a result of the assignment.
3586+
// Walk the base. If the access context is currently `Assignment`, then we
3587+
// must be diagnosing the destination of an assignment. When recursing,
3588+
// diagnose any remaining member refs in a `Writeback` context, since
3589+
// there is a writeback occurring through them as a result of the
3590+
// assignment.
35863591
//
35873592
// someVar.x.y = 1
3588-
// │ ╰─ MemberAccessContext::Setter
3589-
// ╰─── MemberAccessContext::InOut
3593+
// │ ╰─ MemberAccessContext::Assignment
3594+
// ╰─── MemberAccessContext::Writeback
35903595
//
35913596
MemberAccessContext accessContext =
3592-
(AccessContext == MemberAccessContext::Setter)
3593-
? MemberAccessContext::InOut
3597+
(AccessContext == MemberAccessContext::Assignment)
3598+
? MemberAccessContext::Writeback
35943599
: AccessContext;
3595-
walkInContext(E, E->getBase(), accessContext);
3600+
walkInContext(E->getBase(), accessContext);
35963601

35973602
ConcreteDeclRef DR = E->getMember();
35983603
// Diagnose for the member declaration itself.
@@ -3645,7 +3650,13 @@ class ExprAvailabilityWalker : public ASTWalker {
36453650

36463651
/// Walk an inout expression, checking for availability.
36473652
void walkInOutExpr(InOutExpr *E) {
3648-
walkInContext(E, E->getSubExpr(), MemberAccessContext::InOut);
3653+
// Typically an InOutExpr should begin a `Writeback` context. However,
3654+
// inside a LoadExpr this transition is suppressed since the entire
3655+
// expression is being coerced to an r-value.
3656+
auto accessContext = AccessContext != MemberAccessContext::Load
3657+
? MemberAccessContext::Writeback
3658+
: AccessContext;
3659+
walkInContext(E->getSubExpr(), accessContext);
36493660
}
36503661

36513662
bool shouldWalkIntoClosure(AbstractClosureExpr *closure) const {
@@ -3667,8 +3678,7 @@ class ExprAvailabilityWalker : public ASTWalker {
36673678
}
36683679

36693680
/// Walk the given expression in the member access context.
3670-
void walkInContext(Expr *baseExpr, Expr *E,
3671-
MemberAccessContext AccessContext) {
3681+
void walkInContext(Expr *E, MemberAccessContext AccessContext) {
36723682
llvm::SaveAndRestore<MemberAccessContext>
36733683
C(this->AccessContext, AccessContext);
36743684
E->walk(*this);
@@ -3695,28 +3705,25 @@ class ExprAvailabilityWalker : public ASTWalker {
36953705
// this probably needs to be refined to not assume that the accesses are
36963706
// specifically using the getter/setter.
36973707
switch (AccessContext) {
3698-
case MemberAccessContext::Getter:
3708+
case MemberAccessContext::Default:
3709+
case MemberAccessContext::Load:
36993710
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Get),
37003711
ReferenceRange, ReferenceDC, std::nullopt);
37013712
break;
37023713

3703-
case MemberAccessContext::Setter:
3704-
if (!getEnclosingLoadExpr()) {
3705-
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3706-
ReferenceRange, ReferenceDC, std::nullopt);
3707-
}
3714+
case MemberAccessContext::Assignment:
3715+
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3716+
ReferenceRange, ReferenceDC, std::nullopt);
37083717
break;
37093718

3710-
case MemberAccessContext::InOut:
3719+
case MemberAccessContext::Writeback:
37113720
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Get),
37123721
ReferenceRange, ReferenceDC,
37133722
DeclAvailabilityFlag::ForInout);
37143723

3715-
if (!getEnclosingLoadExpr()) {
3716-
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3717-
ReferenceRange, ReferenceDC,
3718-
DeclAvailabilityFlag::ForInout);
3719-
}
3724+
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3725+
ReferenceRange, ReferenceDC,
3726+
DeclAvailabilityFlag::ForInout);
37203727
break;
37213728
}
37223729
}

test/Sema/availability_accessors.swift

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,21 @@ struct BaseStruct<T> {
5656

5757
var unavailableGetter: T {
5858
@available(*, unavailable)
59-
get { fatalError() } // expected-note 65 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
59+
get { fatalError() } // expected-note 67 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
6060
set {}
6161
}
6262

6363
var unavailableSetter: T {
6464
get { fatalError() }
6565
@available(*, unavailable)
66-
set { fatalError() } // expected-note 39 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
66+
set { fatalError() } // expected-note 38 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
6767
}
6868

6969
var unavailableGetterAndSetter: T {
7070
@available(*, unavailable)
71-
get { fatalError() } // expected-note 65 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
71+
get { fatalError() } // expected-note 67 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
7272
@available(*, unavailable)
73-
set { fatalError() } // expected-note 39 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
73+
set { fatalError() } // expected-note 38 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
7474
}
7575
}
7676

@@ -187,7 +187,7 @@ func testLValueAssignments_Class(_ someValue: ClassValue) {
187187

188188
x.unavailableGetter = someValue
189189
x.unavailableGetter.a = someValue.a // expected-error {{getter for 'unavailableGetter' is unavailable}}
190-
x.unavailableGetter[0] = someValue.a // FIXME: missing diagnostic for getter
190+
x.unavailableGetter[0] = someValue.a // expected-error {{getter for 'unavailableGetter' is unavailable}}
191191
x.unavailableGetter[0].b = 1 // expected-error {{getter for 'unavailableGetter' is unavailable}}
192192

193193
x.unavailableSetter = someValue // expected-error {{setter for 'unavailableSetter' is unavailable}}
@@ -197,8 +197,7 @@ func testLValueAssignments_Class(_ someValue: ClassValue) {
197197

198198
x.unavailableGetterAndSetter = someValue // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
199199
x.unavailableGetterAndSetter.a = someValue.a // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
200-
// FIXME: missing diagnostic for getter
201-
x.unavailableGetterAndSetter[0] = someValue.a
200+
x.unavailableGetterAndSetter[0] = someValue.a // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
202201
x.unavailableGetterAndSetter[0].b = 1 // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
203202
}
204203

@@ -207,11 +206,9 @@ func testSubscripts(_ s: BaseStruct<StructValue>) {
207206

208207
// Available subscript, available member, varying argument availability
209208
x.available[available: s.available] = ()
210-
x.available[available: s.unavailableGetter] = () // FIXME: missing diagnostic for getter
211-
// FIXME: spurious diagnostic for setter
212-
x.available[available: s.unavailableSetter] = () // expected-error {{setter for 'unavailableSetter' is unavailable}}
213-
// FIXME: spurious diagnostic for setter
214-
x.available[available: s.unavailableGetterAndSetter] = () // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
209+
x.available[available: s.unavailableGetter] = () // expected-error {{getter for 'unavailableGetter' is unavailable}}
210+
x.available[available: s.unavailableSetter] = ()
211+
x.available[available: s.unavailableGetterAndSetter] = () // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
215212

216213
_ = x.available[available: s.available]
217214
_ = x.available[available: s.unavailableGetter] // expected-error {{getter for 'unavailableGetter' is unavailable}}

0 commit comments

Comments
 (0)