Skip to content

Commit 2523b69

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 6ab32cf commit 2523b69

9 files changed

+293
-55
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: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,27 +140,33 @@ 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

146147
auto &ctx = decl->getASTContext();
147-
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
148+
auto domain = attr.getDomain();
149+
auto deploymentRange = domain.getDeploymentRange(ctx);
148150

149151
// Is the decl obsoleted in the deployment context?
150152
if (auto obsoletedRange = attr.getObsoletedRange(ctx)) {
151153
if (deploymentRange && deploymentRange->isContainedIn(*obsoletedRange))
152154
return AvailabilityConstraint::obsoleted(attr);
153155
}
154156

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

166172
// FIXME: [availability] Model deprecation as an availability constraint.

lib/AST/AvailabilityContext.cpp

Lines changed: 135 additions & 41 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,23 @@ 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.isActiveForTargetPlatform(ctx)) {
166+
return storage->platformRange;
167+
}
168+
169+
for (auto domainInfo : storage->getDomainInfos()) {
170+
if (domain == domainInfo.getDomain() && !domainInfo.isUnavailable())
171+
return domainInfo.getRange();
172+
}
173+
174+
return std::nullopt;
175+
}
176+
124177
bool AvailabilityContext::isUnavailable() const {
125178
for (auto domainInfo : storage->getDomainInfos()) {
126179
if (domainInfo.isUnavailable())
@@ -162,16 +215,32 @@ void AvailabilityContext::constrainWithContext(const AvailabilityContext &other,
162215
}
163216

164217
void AvailabilityContext::constrainWithPlatformRange(
165-
const AvailabilityRange &otherPlatformRange, const ASTContext &ctx) {
166-
218+
const AvailabilityRange &range, const ASTContext &ctx) {
167219
auto platformRange = storage->platformRange;
168-
if (!constrainRange(platformRange, otherPlatformRange))
220+
if (!constrainRange(platformRange, range))
169221
return;
170222

171223
storage = Storage::get(platformRange, storage->isDeprecated,
172224
storage->getDomainInfos(), ctx);
173225
}
174226

227+
void AvailabilityContext::constrainWithAvailabilityRange(
228+
const AvailabilityRange &range, AvailabilityDomain domain,
229+
const ASTContext &ctx) {
230+
231+
if (domain.isActiveForTargetPlatform(ctx)) {
232+
constrainWithPlatformRange(range, ctx);
233+
return;
234+
}
235+
236+
auto domainInfos = storage->copyDomainInfos();
237+
if (!constrainDomainInfos(domainInfos, {DomainInfo(domain, range)}))
238+
return;
239+
240+
storage = Storage::get(storage->platformRange, storage->isDeprecated,
241+
domainInfos, ctx);
242+
}
243+
175244
void AvailabilityContext::constrainWithUnavailableDomain(
176245
AvailabilityDomain domain, const ASTContext &ctx) {
177246
auto domainInfos = storage->copyDomainInfos();
@@ -196,6 +265,9 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange(
196265
bool isDeprecated = storage->isDeprecated;
197266
isConstrained |= constrainBool(isDeprecated, decl->isDeprecated());
198267

268+
// Compute the availability constraints for the decl when used in this context
269+
// and then map those constraints to domain infos. The result will be merged
270+
// into the existing domain infos for this context.
199271
llvm::SmallVector<DomainInfo, 4> declDomainInfos;
200272
AvailabilityConstraintFlags flags =
201273
AvailabilityConstraintFlag::SkipEnclosingExtension;
@@ -211,13 +283,13 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange(
211283
declDomainInfos.push_back(DomainInfo::unavailable(domain));
212284
break;
213285
case AvailabilityConstraint::Reason::PotentiallyUnavailable:
214-
DEBUG_ASSERT(domain.isPlatform());
215-
if (domain.isPlatform()) {
216-
if (auto introducedRange = attr.getIntroducedRange(ctx))
286+
if (auto introducedRange = attr.getIntroducedRange(ctx)) {
287+
if (domain.isActiveForTargetPlatform(ctx)) {
217288
isConstrained |= constrainRange(platformRange, *introducedRange);
289+
} else {
290+
declDomainInfos.push_back({domain, *introducedRange});
291+
}
218292
}
219-
// FIXME: [availability] Store other potentially unavailable domains in
220-
// domainInfos.
221293
break;
222294
}
223295
}
@@ -285,16 +357,38 @@ void AvailabilityContext::print(llvm::raw_ostream &os) const {
285357

286358
void AvailabilityContext::dump() const { print(llvm::errs()); }
287359

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

295385
return true;
296386
}
297387

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

0 commit comments

Comments
 (0)