Skip to content

Commit a0010cb

Browse files
authored
Merge pull request #79718 from tshortli/multiple-unavailable-domains
AST: Allow multiple unavailable domains in `AvailabilityContext`
2 parents 3370637 + f34e135 commit a0010cb

File tree

8 files changed

+187
-150
lines changed

8 files changed

+187
-150
lines changed

include/swift/AST/AvailabilityContext.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,6 @@ class AvailabilityContext {
4848
assert(storage);
4949
};
5050

51-
/// Retrieves an `AvailabilityContext` with the given platform availability
52-
/// parameters.
53-
static AvailabilityContext
54-
get(const AvailabilityRange &platformAvailability,
55-
std::optional<AvailabilityDomain> unavailableDomain, bool deprecated,
56-
ASTContext &ctx);
57-
5851
public:
5952
/// Retrieves an `AvailabilityContext` constrained by the given platform
6053
/// availability range.
@@ -119,6 +112,9 @@ class AvailabilityContext {
119112

120113
void print(llvm::raw_ostream &os) const;
121114
SWIFT_DEBUG_DUMP;
115+
116+
/// Returns true if all internal invariants are satisfied.
117+
bool verify(ASTContext &ctx) const;
122118
};
123119

124120
} // end namespace swift

include/swift/AST/AvailabilityContextStorage.h

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ class AvailabilityContext::Info {
3131
/// The introduction version.
3232
AvailabilityRange Range;
3333

34-
/// The broadest unavailable domain.
35-
std::optional<AvailabilityDomain> UnavailableDomain;
34+
/// A sorted collection of disjoint domains that are known to be
35+
/// unavailable in this context.
36+
llvm::SmallVector<AvailabilityDomain, 1> UnavailableDomains;
3637

3738
/// Whether or not the context is considered deprecated on the current
3839
/// platform.
@@ -49,20 +50,20 @@ class AvailabilityContext::Info {
4950
bool constrainWith(const DeclAvailabilityConstraints &constraints,
5051
ASTContext &ctx);
5152

52-
bool constrainUnavailability(std::optional<AvailabilityDomain> domain);
53+
bool constrainUnavailability(
54+
const llvm::SmallVectorImpl<AvailabilityDomain> &domains);
55+
bool constrainUnavailability(AvailabilityDomain domain) {
56+
return constrainUnavailability(
57+
llvm::SmallVector<AvailabilityDomain>{domain});
58+
}
5359

5460
/// Returns true if `other` is as available or is more available.
5561
bool isContainedIn(const Info &other) const;
5662

57-
void Profile(llvm::FoldingSetNodeID &ID) const {
58-
Range.getRawVersionRange().Profile(ID);
59-
if (UnavailableDomain) {
60-
UnavailableDomain->Profile(ID);
61-
} else {
62-
ID.AddPointer(nullptr);
63-
}
64-
ID.AddBoolean(IsDeprecated);
65-
}
63+
void Profile(llvm::FoldingSetNodeID &ID) const;
64+
65+
/// Returns true if all internal invariants are satisfied.
66+
bool verify(ASTContext &ctx) const;
6667
};
6768

6869
/// As an implementation detail, the values that make up an `Availability`

include/swift/AST/AvailabilityDomain.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,14 @@ inline void simple_display(llvm::raw_ostream &os,
265265
domain.print(os);
266266
}
267267

268+
/// A comparator that implements a stable, total ordering on
269+
/// `AvailabilityDomain` that can be used for sorting in contexts where the
270+
/// result must be stable and deterministic across compilations.
271+
struct StableAvailabilityDomainComparator {
272+
bool operator()(const AvailabilityDomain &lhs,
273+
const AvailabilityDomain &rhs) const;
274+
};
275+
268276
/// Represents an availability domain that has been defined in a module.
269277
class CustomAvailabilityDomain : public ASTAllocated<CustomAvailabilityDomain> {
270278
public:

lib/AST/Availability.cpp

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -168,46 +168,11 @@ void AvailabilityInference::applyInferredAvailableAttrs(
168168
Decl *ToDecl, ArrayRef<const Decl *> InferredFromDecls) {
169169
auto &Context = ToDecl->getASTContext();
170170

171-
/// A wrapper for AvailabilityDomain that implements a stable, total ordering for
172-
/// domains. This is needed to ensure that the inferred attributes are added to
173-
/// the declaration in a consistent order, preserving interface printing output
174-
/// stability across compilations.
175-
class OrderedAvailabilityDomain {
176-
public:
177-
AvailabilityDomain domain;
178-
179-
OrderedAvailabilityDomain(AvailabilityDomain domain) : domain(domain) {}
180-
181-
bool operator<(const OrderedAvailabilityDomain &other) const {
182-
auto kind = domain.getKind();
183-
auto otherKind = other.domain.getKind();
184-
if (kind != otherKind)
185-
return kind < otherKind;
186-
187-
switch (kind) {
188-
case AvailabilityDomain::Kind::Universal:
189-
case AvailabilityDomain::Kind::SwiftLanguage:
190-
case AvailabilityDomain::Kind::PackageDescription:
191-
case AvailabilityDomain::Kind::Embedded:
192-
return false;
193-
case AvailabilityDomain::Kind::Platform:
194-
return domain.getPlatformKind() < other.domain.getPlatformKind();
195-
case AvailabilityDomain::Kind::Custom: {
196-
auto mod = domain.getModule();
197-
auto otherMod = other.domain.getModule();
198-
if (mod != otherMod)
199-
return mod->getName() < otherMod->getName();
200-
201-
return domain.getNameForAttributePrinting() <
202-
other.domain.getNameForAttributePrinting();
203-
}
204-
}
205-
}
206-
};
207-
208171
// Iterate over the declarations and infer required availability on
209-
// a per-platform basis.
210-
std::map<OrderedAvailabilityDomain, InferredAvailability> Inferred;
172+
// a per-domain basis.
173+
std::map<AvailabilityDomain, InferredAvailability,
174+
StableAvailabilityDomainComparator>
175+
Inferred;
211176
for (const Decl *D : InferredFromDecls) {
212177
llvm::SmallVector<SemanticAvailableAttr, 8> MergedAttrs;
213178

@@ -242,8 +207,7 @@ void AvailabilityInference::applyInferredAvailableAttrs(
242207
// Create an availability attribute for each observed platform and add
243208
// to ToDecl.
244209
for (auto &Pair : Inferred) {
245-
if (auto Attr =
246-
createAvailableAttr(Pair.first.domain, Pair.second, Context))
210+
if (auto Attr = createAvailableAttr(Pair.first, Pair.second, Context))
247211
Attrs.add(Attr);
248212
}
249213
}

lib/AST/AvailabilityContext.cpp

Lines changed: 95 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,10 @@ static bool constrainRange(AvailabilityRange &existing,
3838
return true;
3939
}
4040

41-
static bool constrainUnavailableDomain(
42-
std::optional<AvailabilityDomain> &domain,
43-
const std::optional<AvailabilityDomain> &otherDomain) {
44-
// If the other domain is absent or is the same domain, it's a noop.
45-
if (!otherDomain || domain == otherDomain)
46-
return false;
47-
48-
// Check if the other domain is a superset and constrain to it if it is.
49-
if (!domain || otherDomain->contains(*domain)) {
50-
domain = otherDomain;
51-
return true;
52-
}
53-
54-
return false;
55-
}
56-
5741
bool AvailabilityContext::Info::constrainWith(const Info &other) {
5842
bool isConstrained = false;
5943
isConstrained |= constrainRange(Range, other.Range);
60-
if (other.UnavailableDomain)
61-
isConstrained |= constrainUnavailability(other.UnavailableDomain);
44+
isConstrained |= constrainUnavailability(other.UnavailableDomains);
6245
isConstrained |= CONSTRAIN_BOOL(IsDeprecated, other.IsDeprecated);
6346

6447
return isConstrained;
@@ -89,24 +72,72 @@ bool AvailabilityContext::Info::constrainWith(
8972
return isConstrained;
9073
}
9174

75+
/// Returns true if `domain` is not already contained in `unavailableDomains`.
76+
/// Also, removes domains from `unavailableDomains` that are contained in
77+
/// `domain`.
78+
static bool shouldConstrainUnavailableDomains(
79+
AvailabilityDomain domain,
80+
llvm::SmallVectorImpl<AvailabilityDomain> &unavailableDomains) {
81+
bool didRemove = false;
82+
for (auto iter = unavailableDomains.rbegin(), end = unavailableDomains.rend();
83+
iter != end; ++iter) {
84+
auto const &existingDomain = *iter;
85+
86+
// Check if the domain is already unavailable.
87+
if (existingDomain.contains(domain)) {
88+
ASSERT(!didRemove); // This would indicate that the context is malformed.
89+
return false;
90+
}
91+
92+
// Check if the existing domain would be absorbed by the new domain.
93+
if (domain.contains(existingDomain)) {
94+
unavailableDomains.erase((iter + 1).base());
95+
didRemove = true;
96+
}
97+
}
98+
99+
return true;
100+
}
101+
92102
bool AvailabilityContext::Info::constrainUnavailability(
93-
std::optional<AvailabilityDomain> domain) {
94-
return constrainUnavailableDomain(UnavailableDomain, domain);
103+
const llvm::SmallVectorImpl<AvailabilityDomain> &domains) {
104+
llvm::SmallVector<AvailabilityDomain, 2> domainsToAdd;
105+
106+
for (auto domain : domains) {
107+
if (shouldConstrainUnavailableDomains(domain, UnavailableDomains))
108+
domainsToAdd.push_back(domain);
109+
}
110+
111+
if (domainsToAdd.size() < 1)
112+
return false;
113+
114+
// Add the candidate domain and then re-sort.
115+
for (auto domain : domainsToAdd)
116+
UnavailableDomains.push_back(domain);
117+
118+
llvm::sort(UnavailableDomains, StableAvailabilityDomainComparator());
119+
return true;
95120
}
96121

97122
bool AvailabilityContext::Info::isContainedIn(const Info &other) const {
98123
// The available versions range be the same or smaller.
99124
if (!Range.isContainedIn(other.Range))
100125
return false;
101126

102-
// The set of unavailable domains should be the same or larger.
103-
if (auto otherUnavailableDomain = other.UnavailableDomain) {
104-
if (!UnavailableDomain)
105-
return false;
106-
107-
if (!UnavailableDomain->contains(otherUnavailableDomain.value()))
108-
return false;
109-
}
127+
// Every unavailable domain in the other context should be contained in some
128+
// unavailable domain in this context.
129+
bool disjointUnavailability = llvm::any_of(
130+
other.UnavailableDomains,
131+
[&](const AvailabilityDomain &otherUnavailableDomain) {
132+
return llvm::none_of(
133+
UnavailableDomains,
134+
[&otherUnavailableDomain](const AvailabilityDomain &domain) {
135+
return domain.contains(otherUnavailableDomain);
136+
});
137+
});
138+
139+
if (disjointUnavailability)
140+
return false;
110141

111142
// The set of deprecated domains should be the same or larger.
112143
if (!IsDeprecated && other.IsDeprecated)
@@ -115,10 +146,29 @@ bool AvailabilityContext::Info::isContainedIn(const Info &other) const {
115146
return true;
116147
}
117148

149+
void AvailabilityContext::Info::Profile(llvm::FoldingSetNodeID &ID) const {
150+
Range.getRawVersionRange().Profile(ID);
151+
ID.AddInteger(UnavailableDomains.size());
152+
for (auto domain : UnavailableDomains) {
153+
domain.Profile(ID);
154+
}
155+
ID.AddBoolean(IsDeprecated);
156+
}
157+
158+
bool AvailabilityContext::Info::verify(ASTContext &ctx) const {
159+
// Unavailable domains must be sorted to ensure folding set node lookups yield
160+
// consistent results.
161+
if (!llvm::is_sorted(UnavailableDomains,
162+
StableAvailabilityDomainComparator()))
163+
return false;
164+
165+
return true;
166+
}
167+
118168
AvailabilityContext
119169
AvailabilityContext::forPlatformRange(const AvailabilityRange &range,
120170
ASTContext &ctx) {
121-
Info info{range, /*UnavailableDomain*/ std::nullopt,
171+
Info info{range, /*UnavailableDomains*/ {},
122172
/*IsDeprecated*/ false};
123173
return AvailabilityContext(Storage::get(info, ctx));
124174
}
@@ -133,27 +183,20 @@ AvailabilityContext AvailabilityContext::forDeploymentTarget(ASTContext &ctx) {
133183
AvailabilityRange::forDeploymentTarget(ctx), ctx);
134184
}
135185

136-
AvailabilityContext
137-
AvailabilityContext::get(const AvailabilityRange &platformAvailability,
138-
std::optional<AvailabilityDomain> unavailableDomain,
139-
bool deprecated, ASTContext &ctx) {
140-
Info info{platformAvailability, unavailableDomain, deprecated};
141-
return AvailabilityContext(Storage::get(info, ctx));
142-
}
143-
144186
AvailabilityRange AvailabilityContext::getPlatformRange() const {
145187
return storage->info.Range;
146188
}
147189

148190
bool AvailabilityContext::isUnavailable() const {
149-
return storage->info.UnavailableDomain.has_value();
191+
return storage->info.UnavailableDomains.size() > 0;
150192
}
151193

152194
bool AvailabilityContext::containsUnavailableDomain(
153195
AvailabilityDomain domain) const {
154-
if (auto unavailableDomain = storage->info.UnavailableDomain)
155-
return unavailableDomain->contains(domain);
156-
196+
for (auto unavailableDomain : storage->info.UnavailableDomains) {
197+
if (unavailableDomain.contains(domain))
198+
return true;
199+
}
157200
return false;
158201
}
159202

@@ -217,10 +260,7 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange(
217260
}
218261

219262
bool AvailabilityContext::isContainedIn(const AvailabilityContext other) const {
220-
if (!storage->info.isContainedIn(other.storage->info))
221-
return false;
222-
223-
return true;
263+
return storage->info.isContainedIn(other.storage->info);
224264
}
225265

226266
static std::string
@@ -236,11 +276,19 @@ stringForAvailability(const AvailabilityRange &availability) {
236276
void AvailabilityContext::print(llvm::raw_ostream &os) const {
237277
os << "version=" << stringForAvailability(getPlatformRange());
238278

239-
if (auto unavailableDomain = storage->info.UnavailableDomain)
240-
os << " unavailable=" << unavailableDomain->getNameForAttributePrinting();
279+
if (storage->info.UnavailableDomains.size() > 0) {
280+
os << " unavailable=";
281+
llvm::interleave(
282+
storage->info.UnavailableDomains, os,
283+
[&](const AvailabilityDomain &domain) { domain.print(os); }, ",");
284+
}
241285

242286
if (isDeprecated())
243287
os << " deprecated";
244288
}
245289

246290
void AvailabilityContext::dump() const { print(llvm::errs()); }
291+
292+
bool AvailabilityContext::verify(ASTContext &ctx) const {
293+
return storage->info.verify(ctx);
294+
}

0 commit comments

Comments
 (0)