Skip to content

Commit 6007a19

Browse files
committed
AST: Teach AvailabilityContext to represent version-less availability.
This enables potential unavailability diagnostics to be emitted for decls that are only available in a version-less domain.
1 parent ad2845f commit 6007a19

File tree

7 files changed

+270
-54
lines changed

7 files changed

+270
-54
lines changed

include/swift/AST/AvailabilityContext.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,15 @@ class AvailabilityContext {
6464

6565
/// Returns the range of platform versions which may execute code in the
6666
/// availability context, starting at its introduction version.
67+
// FIXME: [availability] Remove; superseded by getAvailableRange().
6768
AvailabilityRange getPlatformRange() const;
6869

70+
/// Returns the range which is available for `domain` in this context. If
71+
/// there are not any constraints established for `domain`, returns
72+
/// `std::nullopt`.
73+
std::optional<AvailabilityRange>
74+
getAvailabilityRange(AvailabilityDomain domain, const ASTContext &ctx) const;
75+
6976
/// Returns true if this context contains any unavailable domains.
7077
bool isUnavailable() const;
7178

@@ -80,9 +87,15 @@ class AvailabilityContext {
8087
const ASTContext &ctx);
8188

8289
/// Constrain the platform availability range with `platformRange`.
90+
// FIXME: [availability] Remove; superseded by constrainWithAvailableRange().
8391
void constrainWithPlatformRange(const AvailabilityRange &platformRange,
8492
const ASTContext &ctx);
8593

94+
/// Constrain the available range for `domain` by `range`.
95+
void constrainWithAvailabilityRange(const AvailabilityRange &range,
96+
AvailabilityDomain domain,
97+
const ASTContext &ctx);
98+
8699
/// Constrain the context by adding \p domain to the set of unavailable
87100
/// domains.
88101
void constrainWithUnavailableDomain(AvailabilityDomain domain,

include/swift/AST/AvailabilityContextStorage.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class AvailabilityContext::DomainInfo final {
4343
AvailabilityRange getRange() const { return range; }
4444
bool isUnavailable() const { return range.isKnownUnreachable(); }
4545

46+
bool constrainRange(const AvailabilityRange &range);
47+
4648
void Profile(llvm::FoldingSetNodeID &ID) const {
4749
ID.AddPointer(domain.getOpaqueValue());
4850
range.getRawVersionRange().Profile(ID);

include/swift/AST/AvailabilityDomain.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,24 @@ class AvailabilityDomain final {
209209
/// version ranges.
210210
bool isVersioned() const;
211211

212+
/// Returns true if availability of the domain can be refined using
213+
/// `@available` attributes and `if #available` queries. If not, then the
214+
/// domain's availability is fixed by compilation settings. For example,
215+
/// macOS platform availability supports contextual refinement, whereas Swift
216+
/// language availability does not.
217+
bool supportsContextRefinement() const;
218+
212219
/// Returns true if the domain supports `#available`/`#unavailable` queries.
213220
bool supportsQueries() const;
214221

215222
/// Returns true if this domain is considered active in the current
216223
/// compilation context.
217224
bool isActive(const ASTContext &ctx) const;
218225

226+
/// Returns true if this domain is a platform domain that is contained by the
227+
/// set of active platform-specific domains.
228+
bool isActiveForTargetPlatform(const ASTContext &ctx) const;
229+
219230
/// Returns the minimum available range for the attribute's domain. For
220231
/// example, for the domain of the platform that compilation is targeting,
221232
/// this will be the deployment target. For the Swift language domain, this

lib/AST/AvailabilityConstraint.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,27 +140,38 @@ static std::optional<AvailabilityConstraint>
140140
getAvailabilityConstraintForAttr(const Decl *decl,
141141
const SemanticAvailableAttr &attr,
142142
const AvailabilityContext &context) {
143+
// Is the decl unconditionally unavailable?
143144
if (attr.isUnconditionallyUnavailable())
144145
return AvailabilityConstraint::unconditionallyUnavailable(attr);
145146

147+
// FIXME: [availability] Model deprecation as an availability constraint.
148+
if (attr.isUnconditionallyDeprecated() && !attr.isVersionSpecific())
149+
return std::nullopt;
150+
146151
auto &ctx = decl->getASTContext();
147-
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
152+
auto domain = attr.getDomain();
153+
auto deploymentRange = domain.getDeploymentRange(ctx);
148154

155+
// Is the decl obsoleted in the deployment context?
149156
auto obsoletedRange = attr.getObsoletedRange(ctx);
150157
if (deploymentRange && deploymentRange->isContainedIn(obsoletedRange))
151158
return AvailabilityConstraint::obsoleted(attr);
152159

153160
AvailabilityRange introducedRange = attr.getIntroducedRange(ctx);
154161

155-
// FIXME: [availability] Expand this to cover custom versioned domains
156-
if (attr.isPlatformSpecific()) {
157-
if (!context.getPlatformRange().isContainedIn(introducedRange))
162+
// Is the decl not yet introduced in the local context?
163+
if (domain.supportsContextRefinement()) {
164+
auto availableRange = context.getAvailabilityRange(domain, ctx);
165+
if (!availableRange || !availableRange->isContainedIn(introducedRange))
158166
return AvailabilityConstraint::potentiallyUnavailable(attr);
159-
} else if (deploymentRange &&
160-
!deploymentRange->isContainedIn(introducedRange)) {
161-
return AvailabilityConstraint::unavailableForDeployment(attr);
167+
168+
return std::nullopt;
162169
}
163170

171+
// Is the decl not yet introduced in the deployment context?
172+
if (deploymentRange && !deploymentRange->isContainedIn(introducedRange))
173+
return AvailabilityConstraint::unavailableForDeployment(attr);
174+
164175
return std::nullopt;
165176
}
166177

lib/AST/AvailabilityContext.cpp

Lines changed: 138 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,60 +44,96 @@ static bool constrainRange(AvailabilityRange &existing,
4444
return true;
4545
}
4646

47-
/// Returns true if `domain` is not already contained in `domainInfos` as an
48-
/// unavailable domain. Also, removes domains from `unavailableDomains` that are
49-
/// contained in `domain`.
50-
static bool shouldConstrainUnavailableDomains(
51-
AvailabilityDomain domain,
52-
llvm::SmallVectorImpl<AvailabilityContext::DomainInfo> &domainInfos) {
53-
bool didRemove = false;
54-
for (auto iter = domainInfos.rbegin(), end = domainInfos.rend(); iter != end;
55-
++iter) {
56-
auto const &domainInfo = *iter;
57-
auto existingDomain = domainInfo.getDomain();
58-
59-
if (!domainInfo.isUnavailable())
47+
/// Returns true if `domainInfos` will be constrained by merging the domain
48+
/// availability represented by `otherDomainInfo`. Additionally, this function
49+
/// has a couple of side-effects:
50+
///
51+
/// - If any existing domain availability ought to be constrained by
52+
/// `otherDomainInfo` then that value will be updated in place.
53+
/// - If any existing value in `domainInfos` should be replaced when
54+
/// `otherDomainInfo` is added, then that existing value is removed
55+
/// and `otherDomainInfo` is appended to `domainInfosToAdd`.
56+
///
57+
static bool constrainDomainInfos(
58+
AvailabilityContext::DomainInfo otherDomainInfo,
59+
llvm::SmallVectorImpl<AvailabilityContext::DomainInfo> &domainInfos,
60+
llvm::SmallVectorImpl<AvailabilityContext::DomainInfo> &domainInfosToAdd) {
61+
bool isConstrained = false;
62+
bool shouldAdd = true;
63+
auto otherDomain = otherDomainInfo.getDomain();
64+
auto end = domainInfos.rend();
65+
66+
// Iterate over domainInfos in reverse order to allow items to be removed
67+
// during iteration.
68+
for (auto iter = domainInfos.rbegin(); iter != end; ++iter) {
69+
auto &domainInfo = *iter;
70+
auto domain = domainInfo.getDomain();
71+
72+
// We found an existing available range for the domain. Constrain it if
73+
// necessary.
74+
if (domain == otherDomain) {
75+
shouldAdd = false;
76+
isConstrained |= domainInfo.constrainRange(otherDomainInfo.getRange());
6077
continue;
78+
}
6179

62-
// Check if the domain is already unavailable.
63-
if (existingDomain.contains(domain)) {
64-
ASSERT(!didRemove); // This would indicate that the context is malformed.
80+
// Check whether an existing unavailable domain contains the domain that
81+
// would be added. If so, there's nothing to do because the availability of
82+
// the domain is already as constrained as it can be.
83+
if (domainInfo.isUnavailable() && domain.contains(otherDomain)) {
84+
DEBUG_ASSERT(!isConstrained);
6585
return false;
6686
}
6787

68-
// Check if the existing domain would be absorbed by the new domain.
69-
if (domain.contains(existingDomain)) {
88+
// If the domain that will be added is unavailable, check whether the
89+
// existing domain is contained within it. If it is, availability for the
90+
// existing domain should be removed because it has been superseded.
91+
if (otherDomainInfo.isUnavailable() && otherDomain.contains(domain)) {
7092
domainInfos.erase((iter + 1).base());
71-
didRemove = true;
93+
isConstrained = true;
7294
}
7395
}
7496

75-
return true;
97+
// If the new domain availability isn't already covered by an item in
98+
// `domainInfos`, then it needs to be added. Defer adding the new domain
99+
// availability until later when the entire set of domain infos can be
100+
// re-sorted once.
101+
if (shouldAdd) {
102+
domainInfosToAdd.push_back(otherDomainInfo);
103+
return true;
104+
}
105+
106+
return isConstrained;
76107
}
77108

109+
/// Constrains `domainInfos` by merging them with `otherDomainInfos`. Returns
110+
/// true if any changes were made to `domainInfos`.
78111
static bool constrainDomainInfos(
79112
llvm::SmallVectorImpl<AvailabilityContext::DomainInfo> &domainInfos,
80113
llvm::ArrayRef<AvailabilityContext::DomainInfo> otherDomainInfos) {
114+
bool isConstrained = false;
81115
llvm::SmallVector<AvailabilityContext::DomainInfo, 4> domainInfosToAdd;
82-
83116
for (auto otherDomainInfo : otherDomainInfos) {
84-
if (otherDomainInfo.isUnavailable())
85-
if (shouldConstrainUnavailableDomains(otherDomainInfo.getDomain(),
86-
domainInfos))
87-
domainInfosToAdd.push_back(otherDomainInfo);
117+
isConstrained |=
118+
constrainDomainInfos(otherDomainInfo, domainInfos, domainInfosToAdd);
88119
}
89120

90-
if (domainInfosToAdd.size() < 1)
121+
if (!isConstrained)
91122
return false;
92123

93-
// Add the candidate domain and then re-sort.
124+
// Add the new domains and then re-sort.
94125
for (auto domainInfo : domainInfosToAdd)
95126
domainInfos.push_back(domainInfo);
96127

97128
llvm::sort(domainInfos, AvailabilityDomainInfoComparator());
98129
return true;
99130
}
100131

132+
bool AvailabilityContext::DomainInfo::constrainRange(
133+
const AvailabilityRange &otherRange) {
134+
return ::constrainRange(range, otherRange);
135+
}
136+
101137
AvailabilityContext
102138
AvailabilityContext::forPlatformRange(const AvailabilityRange &range,
103139
const ASTContext &ctx) {
@@ -121,6 +157,24 @@ AvailabilityRange AvailabilityContext::getPlatformRange() const {
121157
return storage->platformRange;
122158
}
123159

160+
std::optional<AvailabilityRange>
161+
AvailabilityContext::getAvailabilityRange(AvailabilityDomain domain,
162+
const ASTContext &ctx) const {
163+
DEBUG_ASSERT(domain.supportsContextRefinement());
164+
165+
if (domain.isPlatform()) {
166+
DEBUG_ASSERT(domain.isActiveForTargetPlatform(ctx));
167+
return storage->platformRange;
168+
}
169+
170+
for (auto domainInfo : storage->getDomainInfos()) {
171+
if (domain == domainInfo.getDomain() && !domainInfo.isUnavailable())
172+
return domainInfo.getRange();
173+
}
174+
175+
return std::nullopt;
176+
}
177+
124178
bool AvailabilityContext::isUnavailable() const {
125179
for (auto domainInfo : storage->getDomainInfos()) {
126180
if (domainInfo.isUnavailable())
@@ -162,16 +216,33 @@ void AvailabilityContext::constrainWithContext(const AvailabilityContext &other,
162216
}
163217

164218
void AvailabilityContext::constrainWithPlatformRange(
165-
const AvailabilityRange &otherPlatformRange, const ASTContext &ctx) {
166-
219+
const AvailabilityRange &range, const ASTContext &ctx) {
167220
auto platformRange = storage->platformRange;
168-
if (!constrainRange(platformRange, otherPlatformRange))
221+
if (!constrainRange(platformRange, range))
169222
return;
170223

171224
storage = Storage::get(platformRange, storage->isDeprecated,
172225
storage->getDomainInfos(), ctx);
173226
}
174227

228+
void AvailabilityContext::constrainWithAvailabilityRange(
229+
const AvailabilityRange &range, AvailabilityDomain domain,
230+
const ASTContext &ctx) {
231+
232+
if (domain.isPlatform()) {
233+
DEBUG_ASSERT(domain.isActiveForTargetPlatform(ctx));
234+
constrainWithPlatformRange(range, ctx);
235+
return;
236+
}
237+
238+
auto domainInfos = storage->copyDomainInfos();
239+
if (!constrainDomainInfos(domainInfos, {DomainInfo(domain, range)}))
240+
return;
241+
242+
storage = Storage::get(storage->platformRange, storage->isDeprecated,
243+
domainInfos, ctx);
244+
}
245+
175246
void AvailabilityContext::constrainWithUnavailableDomain(
176247
AvailabilityDomain domain, const ASTContext &ctx) {
177248
auto domainInfos = storage->copyDomainInfos();
@@ -196,6 +267,9 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange(
196267
bool isDeprecated = storage->isDeprecated;
197268
isConstrained |= constrainBool(isDeprecated, decl->isDeprecated());
198269

270+
// Compute the availability constraints for the decl when used in this context
271+
// and then map those constraints to domain infos. The result will be merged
272+
// into the existing domain infos for this context.
199273
llvm::SmallVector<DomainInfo, 4> declDomainInfos;
200274
AvailabilityConstraintFlags flags =
201275
AvailabilityConstraintFlag::SkipEnclosingExtension;
@@ -211,12 +285,12 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange(
211285
declDomainInfos.push_back(DomainInfo::unavailable(domain));
212286
break;
213287
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
214-
DEBUG_ASSERT(domain.isPlatform());
215-
if (domain.isPlatform())
216-
isConstrained |=
217-
constrainRange(platformRange, attr.getIntroducedRange(ctx));
218-
// FIXME: [availability] Store other potentially unavailable domains in
219-
// domainInfos.
288+
auto range = attr.getIntroducedRange(ctx);
289+
if (domain.isPlatform()) {
290+
isConstrained |= constrainRange(platformRange, range);
291+
} else {
292+
declDomainInfos.push_back({domain, range});
293+
}
220294
break;
221295
}
222296
}
@@ -284,16 +358,38 @@ void AvailabilityContext::print(llvm::raw_ostream &os) const {
284358

285359
void AvailabilityContext::dump() const { print(llvm::errs()); }
286360

287-
bool AvailabilityContext::verify(const ASTContext &ctx) const {
288-
// Domain infos must be sorted to ensure folding set node lookups yield
289-
// consistent results.
290-
if (!llvm::is_sorted(storage->getDomainInfos(),
291-
AvailabilityDomainInfoComparator()))
292-
return false;
361+
bool verifyDomainInfos(
362+
llvm::ArrayRef<AvailabilityContext::DomainInfo> domainInfos) {
363+
// Checks that the following invariants hold:
364+
// - The domain infos are sorted using AvailabilityDomainInfoComparator.
365+
// - There is not more than one info per-domain.
366+
if (domainInfos.empty())
367+
return true;
368+
369+
AvailabilityDomainInfoComparator compare;
370+
auto prev = domainInfos.begin();
371+
auto next = prev;
372+
auto end = domainInfos.end();
373+
for (++next; next != end; prev = next, ++next) {
374+
const auto &prevInfo = *prev;
375+
const auto &nextInfo = *next;
376+
377+
if (compare(nextInfo, prevInfo))
378+
return false;
379+
380+
// Since the infos are sorted by domain, infos with the same domain should
381+
// be adjacent.
382+
if (prevInfo.getDomain() == nextInfo.getDomain())
383+
return false;
384+
}
293385

294386
return true;
295387
}
296388

389+
bool AvailabilityContext::verify(const ASTContext &ctx) const {
390+
return verifyDomainInfos(storage->getDomainInfos());
391+
}
392+
297393
void AvailabilityContext::Storage::Profile(
298394
llvm::FoldingSetNodeID &ID, const AvailabilityRange &platformRange,
299395
bool isDeprecated,

0 commit comments

Comments
 (0)