Skip to content

Commit 4457ad6

Browse files
authored
Merge pull request #74793 from tshortli/subscript-setter-availability
Sema: Fix accessor availability checking in argument lists
2 parents 39cb996 + 0dbbb68 commit 4457ad6

File tree

2 files changed

+143
-86
lines changed

2 files changed

+143
-86
lines changed

lib/Sema/TypeCheckAvailability.cpp

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

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

33313338
ASTContext &Context;
3332-
MemberAccessContext AccessContext = MemberAccessContext::Getter;
3339+
MemberAccessContext AccessContext = MemberAccessContext::Default;
33333340
SmallVector<const Expr *, 16> ExprStack;
33343341
const ExportContext &Where;
33353342

@@ -3346,6 +3353,13 @@ class ExprAvailabilityWalker : public ASTWalker {
33463353
return MacroWalking::Arguments;
33473354
}
33483355

3356+
PreWalkAction walkToArgumentPre(const Argument &Arg) override {
3357+
// Arguments should be walked in their own member access context which
3358+
// starts out read-only by default.
3359+
walkInContext(Arg.getExpr(), MemberAccessContext::Default);
3360+
return Action::SkipChildren();
3361+
}
3362+
33493363
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
33503364
auto *DC = Where.getDeclContext();
33513365

@@ -3468,6 +3482,11 @@ class ExprAvailabilityWalker : public ASTWalker {
34683482
ME->getMacroRef(), ME->getMacroNameLoc().getSourceRange());
34693483
}
34703484

3485+
if (auto LE = dyn_cast<LoadExpr>(E)) {
3486+
walkLoadExpr(LE);
3487+
return Action::SkipChildren(E);
3488+
}
3489+
34713490
return Action::Continue(E);
34723491
}
34733492

@@ -3524,26 +3543,6 @@ class ExprAvailabilityWalker : public ASTWalker {
35243543
return call;
35253544
}
35263545

3527-
/// Walks up from a potential member reference to the first LoadExpr that would
3528-
/// make the member reference an r-value instead of an l-value.
3529-
const LoadExpr *getEnclosingLoadExpr() const {
3530-
assert(!ExprStack.empty() && "must be called while visiting an expression");
3531-
ArrayRef<const Expr *> stack = ExprStack;
3532-
stack = stack.drop_back();
3533-
3534-
for (auto expr : llvm::reverse(stack)) {
3535-
// Do not search past the first enclosing ApplyExpr. Any enclosing
3536-
// LoadExpr from this point only applies to the result of the call.
3537-
if (auto applyExpr = dyn_cast<ApplyExpr>(expr))
3538-
return nullptr;
3539-
3540-
if (auto loadExpr = dyn_cast<LoadExpr>(expr))
3541-
return loadExpr;
3542-
}
3543-
3544-
return nullptr;
3545-
}
3546-
35473546
/// Walk an assignment expression, checking for availability.
35483547
void walkAssignExpr(AssignExpr *E) {
35493548
// We take over recursive walking of assignment expressions in order to
@@ -3559,32 +3558,38 @@ class ExprAvailabilityWalker : public ASTWalker {
35593558
// encountered walking (pre-order) is the Dest is the destination of the
35603559
// write. For the moment this is fine -- but future syntax might violate
35613560
// this assumption.
3562-
walkInContext(E, Dest, MemberAccessContext::Setter);
3561+
walkInContext(Dest, MemberAccessContext::Assignment);
35633562

35643563
// Check RHS in getter context
35653564
Expr *Source = E->getSrc();
35663565
if (!Source) {
35673566
return;
35683567
}
3569-
walkInContext(E, Source, MemberAccessContext::Getter);
3568+
walkInContext(Source, MemberAccessContext::Default);
35703569
}
3571-
3570+
3571+
/// Walk a load expression, checking for availability.
3572+
void walkLoadExpr(LoadExpr *E) {
3573+
walkInContext(E->getSubExpr(), MemberAccessContext::Load);
3574+
}
3575+
35723576
/// Walk a member reference expression, checking for availability.
35733577
void walkMemberRef(MemberRefExpr *E) {
3574-
// Walk the base. If the access context is currently `Setter`, then we must
3575-
// be diagnosing the destination of an assignment. When recursing, diagnose
3576-
// any remaining member refs as if they were in an InOutExpr, since there is
3577-
// a writeback occurring through them as a result of the assignment.
3578+
// Walk the base. If the access context is currently `Assignment`, then we
3579+
// must be diagnosing the destination of an assignment. When recursing,
3580+
// diagnose any remaining member refs in a `Writeback` context, since
3581+
// there is a writeback occurring through them as a result of the
3582+
// assignment.
35783583
//
35793584
// someVar.x.y = 1
3580-
// │ ╰─ MemberAccessContext::Setter
3581-
// ╰─── MemberAccessContext::InOut
3585+
// │ ╰─ MemberAccessContext::Assignment
3586+
// ╰─── MemberAccessContext::Writeback
35823587
//
35833588
MemberAccessContext accessContext =
3584-
(AccessContext == MemberAccessContext::Setter)
3585-
? MemberAccessContext::InOut
3589+
(AccessContext == MemberAccessContext::Assignment)
3590+
? MemberAccessContext::Writeback
35863591
: AccessContext;
3587-
walkInContext(E, E->getBase(), accessContext);
3592+
walkInContext(E->getBase(), accessContext);
35883593

35893594
ConcreteDeclRef DR = E->getMember();
35903595
// Diagnose for the member declaration itself.
@@ -3637,7 +3642,13 @@ class ExprAvailabilityWalker : public ASTWalker {
36373642

36383643
/// Walk an inout expression, checking for availability.
36393644
void walkInOutExpr(InOutExpr *E) {
3640-
walkInContext(E, E->getSubExpr(), MemberAccessContext::InOut);
3645+
// Typically an InOutExpr should begin a `Writeback` context. However,
3646+
// inside a LoadExpr this transition is suppressed since the entire
3647+
// expression is being coerced to an r-value.
3648+
auto accessContext = AccessContext != MemberAccessContext::Load
3649+
? MemberAccessContext::Writeback
3650+
: AccessContext;
3651+
walkInContext(E->getSubExpr(), accessContext);
36413652
}
36423653

36433654
bool shouldWalkIntoClosure(AbstractClosureExpr *closure) const {
@@ -3659,8 +3670,7 @@ class ExprAvailabilityWalker : public ASTWalker {
36593670
}
36603671

36613672
/// Walk the given expression in the member access context.
3662-
void walkInContext(Expr *baseExpr, Expr *E,
3663-
MemberAccessContext AccessContext) {
3673+
void walkInContext(Expr *E, MemberAccessContext AccessContext) {
36643674
llvm::SaveAndRestore<MemberAccessContext>
36653675
C(this->AccessContext, AccessContext);
36663676
E->walk(*this);
@@ -3687,28 +3697,25 @@ class ExprAvailabilityWalker : public ASTWalker {
36873697
// this probably needs to be refined to not assume that the accesses are
36883698
// specifically using the getter/setter.
36893699
switch (AccessContext) {
3690-
case MemberAccessContext::Getter:
3700+
case MemberAccessContext::Default:
3701+
case MemberAccessContext::Load:
36913702
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Get),
36923703
ReferenceRange, ReferenceDC, std::nullopt);
36933704
break;
36943705

3695-
case MemberAccessContext::Setter:
3696-
if (!getEnclosingLoadExpr()) {
3697-
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3698-
ReferenceRange, ReferenceDC, std::nullopt);
3699-
}
3706+
case MemberAccessContext::Assignment:
3707+
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3708+
ReferenceRange, ReferenceDC, std::nullopt);
37003709
break;
37013710

3702-
case MemberAccessContext::InOut:
3711+
case MemberAccessContext::Writeback:
37033712
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Get),
37043713
ReferenceRange, ReferenceDC,
37053714
DeclAvailabilityFlag::ForInout);
37063715

3707-
if (!getEnclosingLoadExpr()) {
3708-
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3709-
ReferenceRange, ReferenceDC,
3710-
DeclAvailabilityFlag::ForInout);
3711-
}
3716+
diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
3717+
ReferenceRange, ReferenceDC,
3718+
DeclAvailabilityFlag::ForInout);
37123719
break;
37133720
}
37143721
}

test/Sema/availability_accessors.swift

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,7 @@ struct Nested {
1111
}
1212
}
1313

14-
protocol ValueProto {
15-
static var defaultValue: Self { get }
16-
17-
var a: Nested { get set }
18-
19-
subscript(_ i: Int) -> Nested { get set }
20-
21-
@discardableResult mutating func setToZero() -> Int
22-
}
23-
24-
struct StructValue: ValueProto {
14+
struct StructValue {
2515
static var defaultValue: Self { .init(a: Nested(b: 1)) }
2616

2717
var a: Nested
@@ -39,9 +29,7 @@ struct StructValue: ValueProto {
3929
}
4030
}
4131

42-
class ClassValue: ValueProto {
43-
static var defaultValue: Self { .init(a: Nested(b: 1)) }
44-
32+
class ClassValue {
4533
var a: Nested
4634

4735
required init(a: Nested) {
@@ -60,29 +48,59 @@ class ClassValue: ValueProto {
6048
}
6149
}
6250

63-
struct BaseStruct<T: ValueProto> {
64-
var available: T = .defaultValue
51+
struct BaseStruct<T> {
52+
var available: T {
53+
get { fatalError() }
54+
set { }
55+
}
6556

6657
var unavailableGetter: T {
6758
@available(*, unavailable)
68-
get { fatalError() } // expected-note 62 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
59+
get { fatalError() } // expected-note 67 {{getter for 'unavailableGetter' has been explicitly marked unavailable here}}
6960
set {}
7061
}
7162

7263
var unavailableSetter: T {
73-
get { .defaultValue }
64+
get { fatalError() }
7465
@available(*, unavailable)
75-
set { fatalError() } // expected-note 37 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
66+
set { fatalError() } // expected-note 38 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
7667
}
7768

7869
var unavailableGetterAndSetter: T {
7970
@available(*, unavailable)
80-
get { fatalError() } // expected-note 62 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
71+
get { fatalError() } // expected-note 67 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
8172
@available(*, unavailable)
82-
set { fatalError() } // expected-note 37 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
73+
set { fatalError() } // expected-note 38 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
8374
}
8475
}
8576

77+
struct SubscriptHelper {
78+
subscript<T>(available t: T) -> () {
79+
get { }
80+
set { }
81+
}
82+
83+
subscript<T>(unavailableGetter t: T) -> () {
84+
@available(*, unavailable)
85+
get { } // expected-note {{getter for 'subscript(unavailableGetter:)' has been explicitly marked unavailable here}}
86+
set { }
87+
}
88+
89+
subscript<T>(unavailableSetter t: T) -> () {
90+
get { }
91+
@available(*, unavailable)
92+
set { } // expected-note {{setter for 'subscript(unavailableSetter:)' has been explicitly marked unavailable here}}
93+
}
94+
95+
subscript<T>(unavailableGetterAndSetter t: T) -> () {
96+
@available(*, unavailable)
97+
get { } // expected-note {{getter for 'subscript(unavailableGetterAndSetter:)' has been explicitly marked unavailable here}}
98+
@available(*, unavailable)
99+
set { } // expected-note {{setter for 'subscript(unavailableGetterAndSetter:)' has been explicitly marked unavailable here}}
100+
}
101+
102+
}
103+
86104
@discardableResult func takesInOut<T>(_ t: inout T) -> T {
87105
return t
88106
}
@@ -169,7 +187,7 @@ func testLValueAssignments_Class(_ someValue: ClassValue) {
169187

170188
x.unavailableGetter = someValue
171189
x.unavailableGetter.a = someValue.a // expected-error {{getter for 'unavailableGetter' is unavailable}}
172-
x.unavailableGetter[0] = someValue.a // FIXME: missing diagnostic for getter
190+
x.unavailableGetter[0] = someValue.a // expected-error {{getter for 'unavailableGetter' is unavailable}}
173191
x.unavailableGetter[0].b = 1 // expected-error {{getter for 'unavailableGetter' is unavailable}}
174192

175193
x.unavailableSetter = someValue // expected-error {{setter for 'unavailableSetter' is unavailable}}
@@ -179,11 +197,43 @@ func testLValueAssignments_Class(_ someValue: ClassValue) {
179197

180198
x.unavailableGetterAndSetter = someValue // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
181199
x.unavailableGetterAndSetter.a = someValue.a // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
182-
// FIXME: missing diagnostic for getter
183-
x.unavailableGetterAndSetter[0] = someValue.a
200+
x.unavailableGetterAndSetter[0] = someValue.a // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
184201
x.unavailableGetterAndSetter[0].b = 1 // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
185202
}
186203

204+
func testSubscripts(_ s: BaseStruct<StructValue>) {
205+
var x = BaseStruct<SubscriptHelper>()
206+
207+
// Available subscript, available member, varying argument availability
208+
x.available[available: s.available] = ()
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}}
212+
213+
_ = x.available[available: s.available]
214+
_ = x.available[available: s.unavailableGetter] // expected-error {{getter for 'unavailableGetter' is unavailable}}
215+
_ = x.available[available: s.unavailableSetter]
216+
_ = x.available[available: s.unavailableGetterAndSetter] // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
217+
218+
// Varying subscript availability, available member, available argument
219+
x.available[unavailableGetter: s.available] = ()
220+
x.available[unavailableSetter: s.available] = () // expected-error {{setter for 'subscript(unavailableSetter:)' is unavailable}}
221+
x.available[unavailableGetterAndSetter: s.available] = () // expected-error {{setter for 'subscript(unavailableGetterAndSetter:)' is unavailable}}
222+
223+
_ = x.available[unavailableGetter: s.available] // expected-error {{getter for 'subscript(unavailableGetter:)' is unavailable}}
224+
_ = x.available[unavailableSetter: s.available]
225+
_ = x.available[unavailableGetterAndSetter: s.available] // expected-error {{getter for 'subscript(unavailableGetterAndSetter:)' is unavailable}}
226+
227+
// Available subscript, varying member availability, available argument
228+
x.unavailableGetter[available: s.available] = () // expected-error {{getter for 'unavailableGetter' is unavailable}}
229+
x.unavailableSetter[available: s.available] = () // expected-error {{setter for 'unavailableSetter' is unavailable}}
230+
x.unavailableGetterAndSetter[available: s.available] = () // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}} expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
231+
232+
_ = x.unavailableGetter[available: s.available] // expected-error {{getter for 'unavailableGetter' is unavailable}}
233+
_ = x.unavailableSetter[available: s.available]
234+
_ = x.unavailableGetterAndSetter[available: s.available] // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}}
235+
}
236+
187237
func testDiscardedKeyPathLoads_Struct() {
188238
let a = [0]
189239
var x = BaseStruct<StructValue>()

0 commit comments

Comments
 (0)