Skip to content

Commit e476389

Browse files
committed
AST: Introduce swift::getAvailabilityConstraintsForDecl().
This new query is designed to become the canonical source of information regarding whether a declaration is available to use in a given `AvailabilityContext`. It should be adopted as the foundational building block for all other queries that answer more specific questions about the availability of a specific delcaration. The implementation of this query has been copied from a variety of sources which should eventually be deleted once the new query has been fully adopted. NFC.
1 parent cedb4d9 commit e476389

File tree

3 files changed

+194
-4
lines changed

3 files changed

+194
-4
lines changed

include/swift/AST/AvailabilityConstraint.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
namespace swift {
2727

2828
class ASTContext;
29+
class AvailabilityContext;
30+
class Decl;
2931

3032
/// Represents the reason a declaration could be considered unavailable in a
3133
/// certain context.
@@ -110,6 +112,37 @@ class AvailabilityConstraint {
110112
bool isActiveForRuntimeQueries(ASTContext &ctx) const;
111113
};
112114

115+
/// Represents a set of availability constraints that restrict use of a
116+
/// declaration in a particular context.
117+
class DeclAvailabilityConstraints {
118+
using Storage = llvm::SmallVector<AvailabilityConstraint, 4>;
119+
Storage constraints;
120+
121+
public:
122+
DeclAvailabilityConstraints() {}
123+
124+
void addConstraint(const AvailabilityConstraint &constraint) {
125+
constraints.emplace_back(constraint);
126+
}
127+
128+
using const_iterator = Storage::const_iterator;
129+
const_iterator begin() const { return constraints.begin(); }
130+
const_iterator end() const { return constraints.end(); }
131+
};
132+
133+
/// Returns the `AvailabilityConstraint` that describes how \p attr restricts
134+
/// use of \p decl in \p context or `std::nullopt` if there is no restriction.
135+
std::optional<AvailabilityConstraint>
136+
getAvailabilityConstraintForAttr(const Decl *decl,
137+
const SemanticAvailableAttr &attr,
138+
const AvailabilityContext &context);
139+
140+
/// Returns the set of availability constraints that restrict use of \p decl
141+
/// when it is referenced from the given context. In other words, it is the
142+
/// collection of of `@available` attributes with unsatisfied conditions.
143+
DeclAvailabilityConstraints
144+
getAvailabilityConstraintsForDecl(const Decl *decl,
145+
const AvailabilityContext &context);
113146
} // end namespace swift
114147

115148
#endif

lib/AST/AvailabilityConstraint.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212

1313
#include "swift/AST/AvailabilityConstraint.h"
1414
#include "swift/AST/ASTContext.h"
15+
#include "swift/AST/AvailabilityContext.h"
16+
#include "swift/AST/AvailabilityInference.h"
17+
#include "swift/AST/Decl.h"
1518

1619
using namespace swift;
1720

@@ -51,3 +54,136 @@ bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const {
5154
/*forTargetVariant=*/false,
5255
/*forRuntimeQuery=*/true);
5356
}
57+
58+
static bool
59+
isInsideCompatibleUnavailableDeclaration(const Decl *decl,
60+
const SemanticAvailableAttr &attr,
61+
const AvailabilityContext &context) {
62+
if (!context.isUnavailable())
63+
return false;
64+
65+
if (!attr.isUnconditionallyUnavailable())
66+
return false;
67+
68+
// Refuse calling universally unavailable functions from unavailable code,
69+
// but allow the use of types.
70+
auto domain = attr.getDomain();
71+
if (!isa<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
72+
if (domain.isUniversal() || domain.isSwiftLanguage())
73+
return false;
74+
}
75+
76+
return context.containsUnavailableDomain(domain);
77+
}
78+
79+
std::optional<AvailabilityConstraint>
80+
swift::getAvailabilityConstraintForAttr(const Decl *decl,
81+
const SemanticAvailableAttr &attr,
82+
const AvailabilityContext &context) {
83+
if (isInsideCompatibleUnavailableDeclaration(decl, attr, context))
84+
return std::nullopt;
85+
86+
if (attr.isUnconditionallyUnavailable())
87+
return AvailabilityConstraint::forAlwaysUnavailable(attr);
88+
89+
auto &ctx = decl->getASTContext();
90+
auto deploymentVersion = attr.getActiveVersion(ctx);
91+
auto deploymentRange =
92+
AvailabilityRange(VersionRange::allGTE(deploymentVersion));
93+
std::optional<llvm::VersionTuple> obsoletedVersion = attr.getObsoleted();
94+
95+
{
96+
StringRef obsoletedPlatform;
97+
llvm::VersionTuple remappedObsoletedVersion;
98+
if (AvailabilityInference::updateObsoletedPlatformForFallback(
99+
attr, ctx, obsoletedPlatform, remappedObsoletedVersion))
100+
obsoletedVersion = remappedObsoletedVersion;
101+
}
102+
103+
if (obsoletedVersion && *obsoletedVersion <= deploymentVersion)
104+
return AvailabilityConstraint::forObsoleted(attr);
105+
106+
AvailabilityRange introducedRange = attr.getIntroducedRange(ctx);
107+
108+
// FIXME: [availability] Expand this to cover custom versioned domains
109+
if (attr.isPlatformSpecific()) {
110+
if (!context.getPlatformRange().isContainedIn(introducedRange))
111+
return AvailabilityConstraint::forIntroducedInNewerVersion(attr);
112+
} else if (!deploymentRange.isContainedIn(introducedRange)) {
113+
return AvailabilityConstraint::forRequiresVersion(attr);
114+
}
115+
116+
return std::nullopt;
117+
}
118+
119+
/// Returns the most specific platform domain from the availability attributes
120+
/// attached to \p decl or `std::nullopt` if there are none. Platform specific
121+
/// `@available` attributes for other platforms should be ignored. For example,
122+
/// if a declaration has attributes for both iOS and macCatalyst, only the
123+
/// macCatalyst attributes take effect when compiling for a macCatalyst target.
124+
static std::optional<AvailabilityDomain>
125+
activePlatformDomainForDecl(const Decl *decl) {
126+
std::optional<AvailabilityDomain> activeDomain;
127+
for (auto attr :
128+
decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
129+
auto domain = attr.getDomain();
130+
if (!domain.isPlatform())
131+
continue;
132+
133+
if (activeDomain && domain.contains(*activeDomain))
134+
continue;
135+
136+
activeDomain.emplace(domain);
137+
}
138+
139+
return activeDomain;
140+
}
141+
142+
static void
143+
getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints,
144+
const Decl *decl,
145+
const AvailabilityContext &context) {
146+
auto activePlatformDomain = activePlatformDomainForDecl(decl);
147+
148+
for (auto attr :
149+
decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
150+
auto domain = attr.getDomain();
151+
if (domain.isPlatform() && activePlatformDomain &&
152+
!activePlatformDomain->contains(domain))
153+
continue;
154+
155+
if (auto constraint =
156+
swift::getAvailabilityConstraintForAttr(decl, attr, context))
157+
constraints.addConstraint(*constraint);
158+
}
159+
}
160+
161+
DeclAvailabilityConstraints
162+
swift::getAvailabilityConstraintsForDecl(const Decl *decl,
163+
const AvailabilityContext &context) {
164+
DeclAvailabilityConstraints constraints;
165+
166+
// Generic parameters are always available.
167+
if (isa<GenericTypeParamDecl>(decl))
168+
return constraints;
169+
170+
decl = abstractSyntaxDeclForAvailableAttribute(decl);
171+
172+
getAvailabilityConstraintsForDecl(constraints, decl, context);
173+
174+
// If decl is an extension member, query the attributes of the extension, too.
175+
//
176+
// Skip decls imported from Clang, though, as they could be associated to the
177+
// wrong extension and inherit unavailability incorrectly. ClangImporter
178+
// associates Objective-C protocol members to the first category where the
179+
// protocol is directly or indirectly adopted, no matter its availability
180+
// and the availability of other categories. rdar://problem/53956555
181+
if (decl->getClangNode())
182+
return constraints;
183+
184+
auto parent = AvailabilityInference::parentDeclForInferredAvailability(decl);
185+
if (auto extension = dyn_cast_or_null<ExtensionDecl>(parent))
186+
getAvailabilityConstraintsForDecl(constraints, extension, context);
187+
188+
return constraints;
189+
}

test/embedded/availability.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public func unavailable_in_embedded() { }
1717

1818
@available(*, unavailable, message: "always unavailable")
1919
public func universally_unavailable() { }
20-
// expected-note@-1 3 {{'universally_unavailable()' has been explicitly marked unavailable here}}
20+
// expected-note@-1 4 {{'universally_unavailable()' has been explicitly marked unavailable here}}
2121

2222
@_unavailableInEmbedded
2323
public func unused() { } // no error
@@ -35,34 +35,55 @@ public func has_universally_unavailable_overload(_ s1: S1) { }
3535

3636
public func has_universally_unavailable_overload(_ s2: S2) { }
3737

38+
public struct Available {}
39+
40+
@_unavailableInEmbedded
41+
extension Available {
42+
public func unavailable_in_embedded_method( // expected-note {{'unavailable_in_embedded_method' has been explicitly marked unavailable here}}
43+
_ uie: UnavailableInEmbedded,
44+
_ uu: UniverallyUnavailable,
45+
_ a: Available,
46+
) {
47+
unavailable_in_embedded()
48+
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
49+
a.unavailable_in_embedded_method(uie, uu, a)
50+
}
51+
}
52+
3853
public func available(
3954
_ uie: UnavailableInEmbedded, // expected-error {{'UnavailableInEmbedded' is unavailable: unavailable in embedded Swift}}
40-
_ uu: UniverallyUnavailable // expected-error {{'UniverallyUnavailable' is unavailable: always unavailable}}
55+
_ uu: UniverallyUnavailable, // expected-error {{'UniverallyUnavailable' is unavailable: always unavailable}}
56+
_ a: Available,
4157
) {
4258
unavailable_in_embedded() // expected-error {{'unavailable_in_embedded()' is unavailable: unavailable in embedded Swift}}
4359
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
60+
a.unavailable_in_embedded_method(uie, uu, a) // expected-error {{'unavailable_in_embedded_method' is unavailable: unavailable in embedded Swift}}
4461
has_unavailable_in_embedded_overload(.init())
4562
has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload
4663
}
4764

4865
@_unavailableInEmbedded
4966
public func also_unavailable_in_embedded(
5067
_ uie: UnavailableInEmbedded, // OK
51-
_ uu: UniverallyUnavailable // OK
68+
_ uu: UniverallyUnavailable, // OK
69+
_ a: Available,
5270
) {
5371
unavailable_in_embedded() // OK
5472
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
73+
a.unavailable_in_embedded_method(uie, uu, a)
5574
has_unavailable_in_embedded_overload(.init()) // expected-error {{ambiguous use of 'init()'}}
5675
has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload
5776
}
5877

5978
@available(*, unavailable)
6079
public func also_universally_unavailable(
6180
_ uie: UnavailableInEmbedded, // OK
62-
_ uu: UniverallyUnavailable // OK
81+
_ uu: UniverallyUnavailable, // OK
82+
_ a: Available,
6383
) {
6484
unavailable_in_embedded()
6585
universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}}
86+
a.unavailable_in_embedded_method(uie, uu, a)
6687
has_unavailable_in_embedded_overload(.init()) // expected-error {{ambiguous use of 'init()'}}
6788
has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload
6889
}

0 commit comments

Comments
 (0)