Skip to content

Commit 47b0e91

Browse files
authored
Merge pull request #83328 from tshortli/refactor-availability-constraint
AST: Simplify and clarify `AvailabilityConstraint`
2 parents ffc4e50 + 0fa2ca3 commit 47b0e91

11 files changed

+199
-141
lines changed

include/swift/AST/Attr.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3927,11 +3927,11 @@ class SemanticAvailableAttr final {
39273927

39283928
/// Returns the effective obsoletion range indicated by this attribute, along
39293929
/// with the domain that it applies to (which may be different than the domain
3930-
/// which the attribute was written with if a remap is required). This always
3931-
/// corresponds to the version specified by the `obsoleted:` component
3932-
/// (remapped or canonicalized if necessary). Returns `std::nullopt` if the
3933-
/// attribute does not indicate obsoletion (note that unavailability is
3934-
/// separate from obsoletion.
3930+
/// which the attribute was written with if a remap is required). This may
3931+
/// correspond to the version specified by the `obsoleted:` component
3932+
/// (remapped or canonicalized if necessary) or it may be "always" if the
3933+
/// attribute represents unconditional unavailability. Returns `std::nullopt`
3934+
/// if the attribute does not indicate obsoletion.
39353935
std::optional<AvailabilityDomainAndRange>
39363936
getObsoletedDomainAndRange(const ASTContext &ctx) const;
39373937

include/swift/AST/AvailabilityConstraint.h

Lines changed: 102 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -31,52 +31,96 @@ class ASTContext;
3131
class AvailabilityContext;
3232
class Decl;
3333

34-
/// Represents the reason a declaration could be considered unavailable in a
35-
/// certain context.
34+
/// Represents the reason a declaration is considered not available in a
35+
/// specific `AvailabilityContext`.
3636
class AvailabilityConstraint {
3737
public:
38-
/// The reason that the availability constraint is unsatisfied.
38+
/// The reason that a declaration is not available in a context. Broadly, the
39+
/// declaration may either be "unintroduced" or "unavailable" depending on its
40+
/// `@available` attributes. A declaration that is unintroduced can become
41+
/// available if availability constraints are added to the context. For
42+
/// unavailable declarations, on the other hand, either changing the
43+
/// deployment target or making the context itself unavailable are necessary
44+
/// to satisfy the constraint.
45+
///
46+
/// For example, take the following declaration `f()`:
47+
///
48+
/// @available(macOS, introduced: 11.0, obsoleted: 14.0)
49+
/// func f() { ... }
50+
///
51+
/// In contexts that may run on earlier OSes, references to `f()` yield
52+
/// an `Unintroduced` constraint:
53+
///
54+
/// @available(macOS 10.15, *)
55+
/// func g() {
56+
/// f() // error: 'f()' is only available in macOS 11.0 or newer
57+
/// }
58+
///
59+
/// macOS ... 11.0 14.0 ...
60+
/// f() |----------------[=======================)-----------------|
61+
/// g() |-----------[==============================================)
62+
/// ^
63+
/// Unintroduced
64+
///
65+
/// On the other hand, in contexts where deployment target is high enough to
66+
/// make `f()` obsolete, references to it yield an `UnavailableObsolete`
67+
/// constraint:
68+
///
69+
/// // compiled with -target arm64-apple-macos14
70+
/// func h() {
71+
/// f() // error: 'f()' is unavailable in macOS
72+
/// }
73+
///
74+
/// macOS ... 11.0 14.0 ...
75+
/// f() |----------------[=======================)-----------------|
76+
/// h() |----------------------------------------[=================)
77+
/// ^
78+
/// UnavailableObsolete
79+
///
80+
/// References to declarations that are unavailable in all versions of a
81+
/// domain generate `UnavailableUnconditional` constraints unless the context
82+
/// is also unavailable under the same conditions:
83+
///
84+
/// @available(macOS, unavailable)
85+
/// func foo() { ... }
86+
///
87+
/// func bar() {
88+
/// foo() // error: 'foo()' is unavailable in macOS
89+
/// }
90+
///
91+
/// @available(macOS, unavailable)
92+
/// func baz() {
93+
/// foo() // OK
94+
/// }
95+
///
96+
/// @available(*, unavailable)
97+
/// func qux() {
98+
/// foo() // also OK
99+
/// }
39100
///
40101
/// NOTE: The order of this enum matters. Reasons are defined in descending
41102
/// priority order.
42103
enum class Reason {
43-
/// The declaration is referenced in a context in which it is generally
44-
/// unavailable. For example, a reference to a declaration that is
45-
/// unavailable on macOS from a context that may execute on macOS has this
46-
/// constraint.
47-
UnconditionallyUnavailable,
48-
49-
/// The declaration is referenced in a context in which it is considered
50-
/// obsolete. For example, a reference to a declaration that is obsolete in
51-
/// macOS 13 from a context that may execute on macOS 13 or later has this
52-
/// constraint.
53-
Obsoleted,
54-
55-
/// The declaration is not available in the deployment configuration
56-
/// specified for this compilation. For example, the declaration might only
57-
/// be introduced in the Swift 6 language mode while the module is being
58-
/// compiled in the Swift 5 language mode. These availability constraints
59-
/// cannot be satisfied by adding constraining contextual availability using
60-
/// `@available` attributes or `if #available` queries.
61-
UnavailableForDeployment,
62-
63-
/// The declaration is referenced in a context that does not have adequate
64-
/// availability constraints. For example, a reference to a declaration that
65-
/// was introduced in macOS 13 from a context that may execute on earlier
66-
/// versions of macOS cannot satisfy this constraint. The constraint
67-
/// can be satisfied, though, by introducing an `@available` attribute or an
68-
/// `if #available(...)` query.
69-
PotentiallyUnavailable,
70-
};
71-
72-
/// Classifies constraints into different high level categories.
73-
enum class Kind {
74-
/// There are no contexts in which the declaration would be available.
75-
Unavailable,
76-
77-
/// There are some contexts in which the declaration would be available if
78-
/// additional constraints were added.
79-
PotentiallyAvailable,
104+
/// The declaration is unconditionally unavailable, e.g. because of
105+
/// `@available(macOS, unavailable)`.
106+
UnavailableUnconditionally,
107+
108+
/// The declaration is obsolete, e.g. because of
109+
/// `@available(macOS, obsolete: 14.0)` in a program with a deployment
110+
/// target of `macOS 14` or later.
111+
UnavailableObsolete,
112+
113+
/// The declaration is only available for later deployment configurations,
114+
/// e.g. because of `@available(swift 6)` in a program compiled with
115+
/// `-swift-version 5`.
116+
UnavailableUnintroduced,
117+
118+
/// The declaration has not yet been introduced, e.g. because of
119+
/// `@available(macOS 14, *)` in a context that may run on macOS 13 or
120+
/// later. The constraint may be satisfied adding an `@available` attribute
121+
/// or an `if #available(...)` query with sufficient introduction
122+
/// constraints to the context.
123+
Unintroduced,
80124
};
81125

82126
private:
@@ -87,49 +131,42 @@ class AvailabilityConstraint {
87131

88132
public:
89133
static AvailabilityConstraint
90-
unconditionallyUnavailable(SemanticAvailableAttr attr) {
91-
return AvailabilityConstraint(Reason::UnconditionallyUnavailable, attr);
134+
unavailableUnconditionally(SemanticAvailableAttr attr) {
135+
return AvailabilityConstraint(Reason::UnavailableUnconditionally, attr);
92136
}
93137

94-
static AvailabilityConstraint obsoleted(SemanticAvailableAttr attr) {
95-
return AvailabilityConstraint(Reason::Obsoleted, attr);
138+
static AvailabilityConstraint
139+
unavailableObsolete(SemanticAvailableAttr attr) {
140+
return AvailabilityConstraint(Reason::UnavailableObsolete, attr);
96141
}
97142

98143
static AvailabilityConstraint
99-
unavailableForDeployment(SemanticAvailableAttr attr) {
100-
return AvailabilityConstraint(Reason::UnavailableForDeployment, attr);
144+
unavailableUnintroduced(SemanticAvailableAttr attr) {
145+
return AvailabilityConstraint(Reason::UnavailableUnintroduced, attr);
101146
}
102147

103-
static AvailabilityConstraint
104-
potentiallyUnavailable(SemanticAvailableAttr attr) {
105-
return AvailabilityConstraint(Reason::PotentiallyUnavailable, attr);
148+
static AvailabilityConstraint unintroduced(SemanticAvailableAttr attr) {
149+
return AvailabilityConstraint(Reason::Unintroduced, attr);
106150
}
107151

108152
Reason getReason() const { return attrAndReason.getInt(); }
109153
SemanticAvailableAttr getAttr() const {
110154
return static_cast<SemanticAvailableAttr>(attrAndReason.getPointer());
111155
}
112156

113-
Kind getKind() const {
157+
/// Returns true if the constraint cannot be satisfied using a runtime
158+
/// availability query (`if #available(...)`).
159+
bool isUnavailable() const {
114160
switch (getReason()) {
115-
case Reason::UnconditionallyUnavailable:
116-
case Reason::Obsoleted:
117-
case Reason::UnavailableForDeployment:
118-
return Kind::Unavailable;
119-
case Reason::PotentiallyUnavailable:
120-
return Kind::PotentiallyAvailable;
161+
case Reason::UnavailableUnconditionally:
162+
case Reason::UnavailableObsolete:
163+
case Reason::UnavailableUnintroduced:
164+
return true;
165+
case Reason::Unintroduced:
166+
return false;
121167
}
122168
}
123169

124-
/// Returns true if the constraint cannot be satisfied at runtime.
125-
bool isUnavailable() const { return getKind() == Kind::Unavailable; }
126-
127-
/// Returns true if the constraint is unsatisfied but could be satisfied at
128-
/// runtime in a more constrained context.
129-
bool isPotentiallyAvailable() const {
130-
return getKind() == Kind::PotentiallyAvailable;
131-
}
132-
133170
/// Returns the domain that the constraint applies to.
134171
AvailabilityDomain getDomain() const { return getAttr().getDomain(); }
135172

lib/AST/Availability.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -581,14 +581,14 @@ static bool constraintIndicatesRuntimeUnavailability(
581581
customDomainKind = customDomain->getKind();
582582

583583
switch (constraint.getReason()) {
584-
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
584+
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
585585
if (customDomainKind)
586586
return customDomainKind == CustomAvailabilityDomain::Kind::Enabled;
587587
return true;
588-
case AvailabilityConstraint::Reason::Obsoleted:
589-
case AvailabilityConstraint::Reason::UnavailableForDeployment:
588+
case AvailabilityConstraint::Reason::UnavailableObsolete:
589+
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
590590
return false;
591-
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
591+
case AvailabilityConstraint::Reason::Unintroduced:
592592
if (customDomainKind)
593593
return customDomainKind == CustomAvailabilityDomain::Kind::Disabled;
594594
return false;
@@ -998,13 +998,18 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
998998
std::optional<AvailabilityDomainAndRange>
999999
SemanticAvailableAttr::getObsoletedDomainAndRange(const ASTContext &Ctx) const {
10001000
auto *attr = getParsedAttr();
1001+
AvailabilityDomain domain = getDomain();
1002+
1003+
if (!attr->getRawObsoleted().has_value()) {
1004+
// An "unavailable" attribute effectively means obsolete in all versions.
1005+
if (attr->isUnconditionallyUnavailable())
1006+
return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx),
1007+
AvailabilityRange::alwaysAvailable());
10011008

1002-
// Obsoletion always requires a version.
1003-
if (!attr->getRawObsoleted().has_value())
10041009
return std::nullopt;
1010+
}
10051011

10061012
llvm::VersionTuple obsoletedVersion = getObsoleted().value();
1007-
AvailabilityDomain domain = getDomain();
10081013
llvm::VersionTuple remappedVersion;
10091014
if (AvailabilityInference::updateObsoletedAvailabilityDomainForFallback(
10101015
*this, Ctx, domain, remappedVersion))

lib/AST/AvailabilityConstraint.cpp

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,11 @@ using namespace swift;
2020
AvailabilityDomainAndRange
2121
AvailabilityConstraint::getDomainAndRange(const ASTContext &ctx) const {
2222
switch (getReason()) {
23-
case Reason::UnconditionallyUnavailable:
24-
// Technically, unconditional unavailability doesn't have an associated
25-
// range. However, if you view it as a special case of obsoletion, then an
26-
// unconditionally unavailable declaration is "always obsoleted."
27-
return AvailabilityDomainAndRange(getDomain().getRemappedDomain(ctx),
28-
AvailabilityRange::alwaysAvailable());
29-
case Reason::Obsoleted:
23+
case Reason::UnavailableUnconditionally:
24+
case Reason::UnavailableObsolete:
3025
return getAttr().getObsoletedDomainAndRange(ctx).value();
31-
case Reason::UnavailableForDeployment:
32-
case Reason::PotentiallyUnavailable:
26+
case Reason::UnavailableUnintroduced:
27+
case Reason::Unintroduced:
3328
return getAttr().getIntroducedDomainAndRange(ctx).value();
3429
}
3530
}
@@ -50,14 +45,14 @@ void AvailabilityConstraint::print(llvm::raw_ostream &os) const {
5045
os << ", ";
5146

5247
switch (getReason()) {
53-
case Reason::UnconditionallyUnavailable:
48+
case Reason::UnavailableUnconditionally:
5449
os << "unavailable";
5550
break;
56-
case Reason::Obsoleted:
51+
case Reason::UnavailableObsolete:
5752
os << "obsoleted: " << getAttr().getObsoleted().value();
5853
break;
59-
case Reason::UnavailableForDeployment:
60-
case Reason::PotentiallyUnavailable:
54+
case Reason::UnavailableUnintroduced:
55+
case Reason::Unintroduced:
6156
os << "introduced: " << getAttr().getIntroduced().value();
6257
break;
6358
}
@@ -75,18 +70,20 @@ static bool constraintIsStronger(const AvailabilityConstraint &lhs,
7570
return lhs.getReason() < rhs.getReason();
7671

7772
switch (lhs.getReason()) {
78-
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
73+
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
7974
// Just keep the first.
8075
return false;
8176

82-
case AvailabilityConstraint::Reason::Obsoleted:
77+
case AvailabilityConstraint::Reason::UnavailableObsolete:
8378
// Pick the larger obsoleted range.
84-
return *lhs.getAttr().getObsoleted() < *rhs.getAttr().getObsoleted();
79+
return lhs.getAttr().getObsoleted().value() <
80+
rhs.getAttr().getObsoleted().value();
8581

86-
case AvailabilityConstraint::Reason::UnavailableForDeployment:
87-
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
82+
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
83+
case AvailabilityConstraint::Reason::Unintroduced:
8884
// Pick the smaller introduced range.
89-
return *lhs.getAttr().getIntroduced() > *rhs.getAttr().getIntroduced();
85+
return lhs.getAttr().getIntroduced().value_or(llvm::VersionTuple()) >
86+
rhs.getAttr().getIntroduced().value_or(llvm::VersionTuple());
9087
}
9188
}
9289

@@ -154,7 +151,7 @@ static bool canIgnoreConstraintInUnavailableContexts(
154151
auto domain = constraint.getDomain();
155152

156153
switch (constraint.getReason()) {
157-
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
154+
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
158155
if (flags.contains(AvailabilityConstraintFlag::
159156
AllowUniversallyUnavailableInCompatibleContexts))
160157
return true;
@@ -169,7 +166,7 @@ static bool canIgnoreConstraintInUnavailableContexts(
169166
}
170167
return true;
171168

172-
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
169+
case AvailabilityConstraint::Reason::Unintroduced:
173170
switch (domain.getKind()) {
174171
case AvailabilityDomain::Kind::Universal:
175172
case AvailabilityDomain::Kind::SwiftLanguage:
@@ -189,8 +186,8 @@ static bool canIgnoreConstraintInUnavailableContexts(
189186
}
190187
return constraint.getDomain().isPlatform();
191188

192-
case AvailabilityConstraint::Reason::Obsoleted:
193-
case AvailabilityConstraint::Reason::UnavailableForDeployment:
189+
case AvailabilityConstraint::Reason::UnavailableObsolete:
190+
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
194191
return false;
195192
}
196193
}
@@ -217,7 +214,7 @@ getAvailabilityConstraintForAttr(const Decl *decl,
217214
const AvailabilityContext &context) {
218215
// Is the decl unconditionally unavailable?
219216
if (attr.isUnconditionallyUnavailable())
220-
return AvailabilityConstraint::unconditionallyUnavailable(attr);
217+
return AvailabilityConstraint::unavailableUnconditionally(attr);
221218

222219
auto &ctx = decl->getASTContext();
223220
auto domain = attr.getDomain();
@@ -226,25 +223,24 @@ getAvailabilityConstraintForAttr(const Decl *decl,
226223
// Is the decl obsoleted in the deployment context?
227224
if (auto obsoletedRange = attr.getObsoletedRange(ctx)) {
228225
if (deploymentRange && deploymentRange->isContainedIn(*obsoletedRange))
229-
return AvailabilityConstraint::obsoleted(attr);
226+
return AvailabilityConstraint::unavailableObsolete(attr);
230227
}
231228

232229
// Is the decl not yet introduced in the local context?
233230
if (auto introducedRange = attr.getIntroducedRange(ctx)) {
234231
if (domain.supportsContextRefinement()) {
235232
auto availableRange = context.getAvailabilityRange(domain, ctx);
236233
if (!availableRange || !availableRange->isContainedIn(*introducedRange))
237-
return AvailabilityConstraint::potentiallyUnavailable(attr);
234+
return AvailabilityConstraint::unintroduced(attr);
238235

239236
return std::nullopt;
240237
}
241238

242239
// Is the decl not yet introduced in the deployment context?
243240
if (deploymentRange && !deploymentRange->isContainedIn(*introducedRange))
244-
return AvailabilityConstraint::unavailableForDeployment(attr);
241+
return AvailabilityConstraint::unavailableUnintroduced(attr);
245242
}
246243

247-
// FIXME: [availability] Model deprecation as an availability constraint.
248244
return std::nullopt;
249245
}
250246

0 commit comments

Comments
 (0)