Skip to content

Commit 59b136c

Browse files
authored
Merge pull request #83054 from tshortli/unavailable-setters-require-read-only-key-paths
ConstraintSystem: Make key paths for properties with unavailable setters read-only
2 parents ef798d5 + 99380bf commit 59b136c

File tree

8 files changed

+186
-44
lines changed

8 files changed

+186
-44
lines changed

include/swift/AST/AvailabilityConstraint.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@ enum class AvailabilityConstraintFlag : uint8_t {
174174
/// Include constraints for all domains, regardless of whether they are active
175175
/// or relevant to type checking.
176176
IncludeAllDomains = 1 << 1,
177+
178+
/// By default, non-type declarations that are universally unavailable are
179+
/// always diagnosed, regardless of whether the context of the reference
180+
/// is also universally unavailable. If this flag is set, though, those
181+
/// references are allowed.
182+
AllowUniversallyUnavailableInCompatibleContexts = 1 << 2,
177183
};
178184
using AvailabilityConstraintFlags = OptionSet<AvailabilityConstraintFlag>;
179185

lib/AST/AvailabilityConstraint.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,16 @@ DeclAvailabilityConstraints::getPrimaryConstraint() const {
118118
}
119119

120120
static bool canIgnoreConstraintInUnavailableContexts(
121-
const Decl *decl, const AvailabilityConstraint &constraint) {
121+
const Decl *decl, const AvailabilityConstraint &constraint,
122+
const AvailabilityConstraintFlags flags) {
122123
auto domain = constraint.getDomain();
123124

124125
switch (constraint.getReason()) {
125126
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
127+
if (flags.contains(AvailabilityConstraintFlag::
128+
AllowUniversallyUnavailableInCompatibleContexts))
129+
return true;
130+
126131
// Always reject uses of universally unavailable declarations, regardless
127132
// of context, since there are no possible compilation configurations in
128133
// which they are available. However, make an exception for types and
@@ -162,11 +167,12 @@ static bool canIgnoreConstraintInUnavailableContexts(
162167
static bool
163168
shouldIgnoreConstraintInContext(const Decl *decl,
164169
const AvailabilityConstraint &constraint,
165-
const AvailabilityContext &context) {
170+
const AvailabilityContext &context,
171+
const AvailabilityConstraintFlags flags) {
166172
if (!context.isUnavailable())
167173
return false;
168174

169-
if (!canIgnoreConstraintInUnavailableContexts(decl, constraint))
175+
if (!canIgnoreConstraintInUnavailableContexts(decl, constraint, flags))
170176
return false;
171177

172178
return context.containsUnavailableDomain(constraint.getDomain());
@@ -256,7 +262,7 @@ static void getAvailabilityConstraintsForDecl(
256262
// declaration is unconditionally unavailable in a domain for which
257263
// the context is already unavailable.
258264
llvm::erase_if(constraints, [&](const AvailabilityConstraint &constraint) {
259-
return shouldIgnoreConstraintInContext(decl, constraint, context);
265+
return shouldIgnoreConstraintInContext(decl, constraint, context, flags);
260266
});
261267
}
262268

lib/Sema/ConstraintSystem.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4997,11 +4997,7 @@ bool ConstraintSystem::isReadOnlyKeyPathComponent(
49974997
// If the setter is unavailable, then the keypath ought to be read-only
49984998
// in this context.
49994999
if (auto setter = storage->getOpaqueAccessor(AccessorKind::Set)) {
5000-
// FIXME: [availability] Fully unavailable setters should cause the key path
5001-
// to be readonly too.
5002-
auto constraint =
5003-
getUnsatisfiedAvailabilityConstraint(setter, DC, referenceLoc);
5004-
if (constraint && constraint->isPotentiallyAvailable())
5000+
if (getUnsatisfiedAvailabilityConstraint(setter, DC, referenceLoc))
50055001
return true;
50065002
}
50075003

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1771,8 +1771,17 @@ std::optional<AvailabilityConstraint>
17711771
swift::getUnsatisfiedAvailabilityConstraint(const Decl *decl,
17721772
const DeclContext *referenceDC,
17731773
SourceLoc referenceLoc) {
1774+
AvailabilityConstraintFlags flags;
1775+
1776+
// In implicit code, allow references to universally unavailable declarations
1777+
// as long as the context is also universally unavailable.
1778+
if (referenceLoc.isInvalid())
1779+
flags |= AvailabilityConstraintFlag::
1780+
AllowUniversallyUnavailableInCompatibleContexts;
1781+
17741782
return getAvailabilityConstraintsForDecl(
1775-
decl, AvailabilityContext::forLocation(referenceLoc, referenceDC))
1783+
decl, AvailabilityContext::forLocation(referenceLoc, referenceDC),
1784+
flags)
17761785
.getPrimaryConstraint();
17771786
}
17781787

lib/Sema/TypeCheckStorage.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ getPropertyWrapperLValueness(VarDecl *var) {
10631063
/// - Wrapped: \c self._member.wrappedValue
10641064
/// - Composition: \c self._member.wrappedValue.wrappedValue….wrappedValue
10651065
/// - Projected: \c self._member.projectedValue
1066-
/// - Enclosed instance: \c Wrapper[_enclosedInstance: self, …]
1066+
/// - Enclosed instance: \c Wrapper[_enclosingInstance: self, …]
10671067
static Expr *buildStorageReference(AccessorDecl *accessor,
10681068
AbstractStorageDecl *storage,
10691069
TargetImpl target,

test/Availability/availability_accessors.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ struct BaseStruct<T> {
6363
var unavailableSetter: T {
6464
get { fatalError() }
6565
@available(*, unavailable)
66-
set { fatalError() } // expected-note 38 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
66+
set { fatalError() } // expected-note 33 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
6767
}
6868

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

@@ -318,17 +318,17 @@ func testKeyPathAssignments_Struct(_ someValue: StructValue) {
318318
a[keyPath: \.[takesInOut(&x.unavailableGetter.a.b)]] = 0 // expected-error {{getter for 'unavailableGetter' is unavailable}}
319319
a[keyPath: \.[takesInOut(&x.unavailableGetter[0].b)]] = 0 // expected-error {{getter for 'unavailableGetter' is unavailable}}
320320

321-
x[keyPath: \.unavailableSetter] = someValue // expected-error {{setter for 'unavailableSetter' is unavailable}}
322-
x[keyPath: \.unavailableSetter.a] = someValue.a // expected-error {{setter for 'unavailableSetter' is unavailable}}
323-
x[keyPath: \.unavailableSetter[0]] = someValue.a // expected-error {{setter for 'unavailableSetter' is unavailable}}
324-
x[keyPath: \.unavailableSetter[0].b] = 1 // expected-error {{setter for 'unavailableSetter' is unavailable}}
321+
x[keyPath: \.unavailableSetter] = someValue // expected-error {{cannot assign through subscript: key path is read-only}}
322+
x[keyPath: \.unavailableSetter.a] = someValue.a // expected-error {{cannot assign through subscript: key path is read-only}}
323+
x[keyPath: \.unavailableSetter[0]] = someValue.a // expected-error {{cannot assign through subscript: key path is read-only}}
324+
x[keyPath: \.unavailableSetter[0].b] = 1 // expected-error {{cannot assign through subscript: key path is read-only}}
325325
a[keyPath: \.[takesInOut(&x.unavailableSetter.a.b)]] = 0 // expected-error {{setter for 'unavailableSetter' is unavailable}}
326326
a[keyPath: \.[takesInOut(&x.unavailableSetter[0].b)]] = 0 // expected-error {{setter for 'unavailableSetter' is unavailable}}
327327

328-
x[keyPath: \.unavailableGetterAndSetter] = someValue // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
329-
x[keyPath: \.unavailableGetterAndSetter.a] = someValue.a // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
330-
x[keyPath: \.unavailableGetterAndSetter[0]] = someValue.a // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
331-
x[keyPath: \.unavailableGetterAndSetter[0].b] = 1 // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
328+
x[keyPath: \.unavailableGetterAndSetter] = someValue // expected-error {{cannot assign through subscript: key path is read-only}}
329+
x[keyPath: \.unavailableGetterAndSetter.a] = someValue.a // expected-error {{cannot assign through subscript: key path is read-only}}
330+
x[keyPath: \.unavailableGetterAndSetter[0]] = someValue.a // expected-error {{cannot assign through subscript: key path is read-only}}
331+
x[keyPath: \.unavailableGetterAndSetter[0].b] = 1 // expected-error {{cannot assign through subscript: key path is read-only}}
332332
a[keyPath: \.[takesInOut(&x.unavailableGetterAndSetter.a.b)]] = 0 // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}} expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
333333
a[keyPath: \.[takesInOut(&x.unavailableGetterAndSetter[0].b)]] = 0 // expected-error {{getter for 'unavailableGetterAndSetter' is unavailable}} expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
334334
}
@@ -352,7 +352,7 @@ func testKeyPathAssignments_Class(_ someValue: ClassValue) {
352352
a[keyPath: \.[takesInOut(&x.unavailableGetter.a.b)]] = 0 // expected-error {{getter for 'unavailableGetter' is unavailable}}
353353
a[keyPath: \.[takesInOut(&x.unavailableGetter[0].b)]] = 0 // expected-error {{getter for 'unavailableGetter' is unavailable}}
354354

355-
x[keyPath: \.unavailableSetter] = someValue // expected-error {{setter for 'unavailableSetter' is unavailable}}
355+
x[keyPath: \.unavailableSetter] = someValue // expected-error {{cannot assign through subscript: key path is read-only}}
356356
// FIXME: spurious unavailable setter error
357357
x[keyPath: \.unavailableSetter.a] = someValue.a // expected-error {{setter for 'unavailableSetter' is unavailable}}
358358
// FIXME: spurious unavailable setter error
@@ -362,7 +362,7 @@ func testKeyPathAssignments_Class(_ someValue: ClassValue) {
362362
a[keyPath: \.[takesInOut(&x.unavailableSetter.a.b)]] = 0
363363
a[keyPath: \.[takesInOut(&x.unavailableSetter[0].b)]] = 0
364364

365-
x[keyPath: \.unavailableGetterAndSetter] = someValue // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
365+
x[keyPath: \.unavailableGetterAndSetter] = someValue // expected-error {{cannot assign through subscript: key path is read-only}}
366366
x[keyPath: \.unavailableGetterAndSetter.a] = someValue.a // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
367367
x[keyPath: \.unavailableGetterAndSetter[0]] = someValue.a // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
368368
// FIXME: spurious unavailable setter error

test/Availability/property_wrapper_availability.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,36 @@ func unavailableOnMacOSFunc(
186186
@WrappedValueUnavailableOnMacOS var unavailableWrappedValueLocal = S()
187187
@WrappedValueAvailable51 var wrappedValueAavailable51 = S()
188188
}
189+
190+
@propertyWrapper
191+
struct Observable<Value> {
192+
private var stored: Value
193+
194+
init(wrappedValue: Value) {
195+
self.stored = wrappedValue
196+
}
197+
198+
var wrappedValue: Value {
199+
get { fatalError() }
200+
set { fatalError() }
201+
}
202+
203+
static subscript<EnclosingSelf>(
204+
_enclosingInstance observed: EnclosingSelf,
205+
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
206+
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
207+
) -> Value {
208+
get { fatalError() }
209+
set { fatalError() }
210+
}
211+
}
212+
213+
@available(macOS, unavailable)
214+
class UnavailableOnMacOSObserved {
215+
@Observable var observedProperty = 17
216+
}
217+
218+
@available(*, unavailable)
219+
class UniversallyUnavailableObserved {
220+
@Observable var observedProperty = 17
221+
}
Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,128 @@
1-
// RUN: %target-swift-frontend -target %target-cpu-apple-macosx10.9 -typecheck -verify %s
1+
// RUN: %target-swift-frontend -typecheck -verify %s
22
// REQUIRES: OS=macosx
33

44
struct Butt {
5-
var setter_conditionally_available: Int {
6-
get { fatalError() }
5+
var setter_conditionally_available: Int {
6+
get { fatalError() }
77

8-
@available(macOS 10.10, *)
9-
set { fatalError() }
10-
}
8+
@available(macOS 99, *)
9+
set { fatalError() }
10+
}
11+
12+
var setter_unavailable_on_macos: Int {
13+
get { fatalError() }
14+
15+
@available(macOS, unavailable)
16+
set { fatalError() }
17+
}
18+
19+
var setter_universally_unavailable: Int {
20+
get { fatalError() }
21+
22+
@available(*, unavailable)
23+
set { fatalError() }
24+
}
25+
}
26+
27+
@dynamicMemberLookup
28+
struct Lens<T> {
29+
var obj: T
30+
31+
init(_ obj: T) {
32+
self.obj = obj
33+
}
34+
35+
subscript<U>(dynamicMember member: KeyPath<T, U>) -> Lens<U> {
36+
get { return Lens<U>(obj[keyPath: member]) }
37+
}
38+
39+
subscript<U>(dynamicMember member: WritableKeyPath<T, U>) -> Lens<U> {
40+
get { return Lens<U>(obj[keyPath: member]) }
41+
set { obj[keyPath: member] = newValue.obj }
42+
}
1143
}
1244

1345
func assertExactType<T>(of _: inout T, is _: T.Type) {}
1446

1547
@available(macOS 10.9, *)
16-
public func unavailableSetterContext() {
17-
var kp = \Butt.setter_conditionally_available
18-
assertExactType(of: &kp, is: KeyPath<Butt, Int>.self)
48+
public func availableOnMacOS_10_9() {
49+
var kp = \Butt.setter_conditionally_available
50+
var lens = Lens(Butt())
51+
assertExactType(of: &kp, is: KeyPath<Butt, Int>.self)
52+
_ = lens.setter_conditionally_available
53+
lens.setter_conditionally_available = Lens(1) // expected-error {{cannot assign to property: 'lens' is immutable}}
54+
}
55+
56+
@available(macOS 99, *)
57+
public func availableOnMacOS_99() {
58+
var kp = \Butt.setter_conditionally_available
59+
var lens = Lens(Butt())
60+
assertExactType(of: &kp, is: WritableKeyPath<Butt, Int>.self)
61+
_ = lens.setter_conditionally_available
62+
lens.setter_conditionally_available = Lens(1)
1963
}
20-
@available(macOS 10.10, *)
21-
public func availableSetterContext() {
64+
65+
public func alwaysAvailableOnMacOS() {
66+
var lens = Lens(Butt())
67+
68+
if #available(macOS 99, *) {
2269
var kp = \Butt.setter_conditionally_available
2370
assertExactType(of: &kp, is: WritableKeyPath<Butt, Int>.self)
71+
_ = lens.setter_conditionally_available
72+
// FIXME: [availability] setter_conditionally_available should be writable in this branch
73+
lens.setter_conditionally_available = Lens(1) // expected-error {{cannot assign to property: 'lens' is immutable}}
74+
} else {
75+
var kp = \Butt.setter_conditionally_available
76+
assertExactType(of: &kp, is: KeyPath<Butt, Int>.self)
77+
_ = lens.setter_conditionally_available
78+
lens.setter_conditionally_available = Lens(1) // expected-error {{cannot assign to property: 'lens' is immutable}}
79+
}
80+
81+
var kp2 = \Butt.setter_unavailable_on_macos
82+
assertExactType(of: &kp2, is: KeyPath<Butt, Int>.self)
83+
_ = lens.setter_unavailable_on_macos
84+
lens.setter_unavailable_on_macos = Lens(1) // expected-error {{cannot assign to property: 'lens' is immutable}}
85+
86+
var kp3 = \Butt.setter_universally_unavailable
87+
assertExactType(of: &kp3, is: KeyPath<Butt, Int>.self)
88+
_ = lens.setter_universally_unavailable
89+
lens.setter_universally_unavailable = Lens(1) // expected-error {{cannot assign to property: 'lens' is immutable}}
2490
}
25-
@available(macOS 10.9, *)
26-
public func conditionalAvailableSetterContext() {
27-
if #available(macOS 10.10, *) {
28-
var kp = \Butt.setter_conditionally_available
29-
assertExactType(of: &kp, is: WritableKeyPath<Butt, Int>.self)
30-
} else {
31-
var kp = \Butt.setter_conditionally_available
32-
assertExactType(of: &kp, is: KeyPath<Butt, Int>.self)
33-
}
91+
92+
@available(macOS, unavailable)
93+
public func unvailableOnMacOS() {
94+
var lens = Lens(Butt())
95+
var kp = \Butt.setter_conditionally_available
96+
assertExactType(of: &kp, is: WritableKeyPath<Butt, Int>.self)
97+
_ = lens.setter_conditionally_available
98+
lens.setter_conditionally_available = Lens(1)
99+
100+
var kp2 = \Butt.setter_unavailable_on_macos
101+
assertExactType(of: &kp2, is: WritableKeyPath<Butt, Int>.self)
102+
_ = lens.setter_unavailable_on_macos
103+
lens.setter_unavailable_on_macos = Lens(1)
104+
105+
var kp3 = \Butt.setter_universally_unavailable
106+
assertExactType(of: &kp3, is: KeyPath<Butt, Int>.self)
107+
_ = lens.setter_universally_unavailable
108+
lens.setter_universally_unavailable = Lens(1) // expected-error {{cannot assign to property: 'lens' is immutable}}
34109
}
35110

36-
// FIXME: Check additional unavailability conditions
111+
@available(*, unavailable)
112+
public func universallyUnavailable() {
113+
var lens = Lens(Butt())
114+
var kp = \Butt.setter_conditionally_available
115+
assertExactType(of: &kp, is: WritableKeyPath<Butt, Int>.self)
116+
_ = lens.setter_conditionally_available
117+
lens.setter_conditionally_available = Lens(1)
118+
119+
var kp2 = \Butt.setter_unavailable_on_macos
120+
assertExactType(of: &kp2, is: WritableKeyPath<Butt, Int>.self)
121+
_ = lens.setter_unavailable_on_macos
122+
lens.setter_unavailable_on_macos = Lens(1)
123+
124+
var kp3 = \Butt.setter_universally_unavailable
125+
assertExactType(of: &kp3, is: KeyPath<Butt, Int>.self)
126+
_ = lens.setter_universally_unavailable
127+
lens.setter_universally_unavailable = Lens(1)
128+
}

0 commit comments

Comments
 (0)