mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
aed3492d93
Respect availability for obsoleted API
467 lines
17 KiB
C++
467 lines
17 KiB
C++
//===--- AvailabilityConstraint.cpp - Swift Availability Constraints ------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/AST/AvailabilityConstraint.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/AvailabilityContext.h"
|
|
#include "swift/AST/Decl.h"
|
|
|
|
using namespace swift;
|
|
|
|
AvailabilityDomainAndRange
|
|
AvailabilityConstraint::getDomainAndRange(const ASTContext &ctx) const {
|
|
switch (getReason()) {
|
|
case Reason::UnavailableUnconditionally:
|
|
case Reason::UnavailableObsolete:
|
|
return getAttr().getObsoletedDomainAndRange(ctx).value();
|
|
case Reason::UnavailableUnintroduced:
|
|
case Reason::Unintroduced:
|
|
return getAttr().getIntroducedDomainAndRange(ctx).value();
|
|
}
|
|
}
|
|
|
|
AvailabilityDomainAndRange
|
|
AvailabilityConstraint::getFixItDomainAndRange(const ASTContext &ctx) const {
|
|
auto attrDomain = getAttr().getDomain();
|
|
if (attrDomain.contains(
|
|
AvailabilityDomain::forPlatform(PlatformKind::anyAppleOS))) {
|
|
switch (getReason()) {
|
|
case Reason::UnavailableUnconditionally:
|
|
case Reason::UnavailableObsolete:
|
|
return AvailabilityDomainAndRange(
|
|
attrDomain, AvailabilityRange(getAttr().getObsoleted().value()));
|
|
case Reason::UnavailableUnintroduced:
|
|
case Reason::Unintroduced:
|
|
return AvailabilityDomainAndRange(
|
|
attrDomain, AvailabilityRange(getAttr().getIntroduced().value()));
|
|
}
|
|
}
|
|
return getDomainAndRange(ctx);
|
|
}
|
|
|
|
bool AvailabilityConstraint::isActiveForRuntimeQueries(
|
|
const ASTContext &ctx) const {
|
|
if (getAttr().getPlatform() == PlatformKind::none)
|
|
return true;
|
|
|
|
return swift::isPlatformActive(getAttr().getPlatform(), ctx.LangOpts,
|
|
/*forTargetVariant=*/false,
|
|
/*forRuntimeQuery=*/true);
|
|
}
|
|
|
|
void AvailabilityConstraint::print(llvm::raw_ostream &os) const {
|
|
os << "AvailabilityConstraint(";
|
|
getAttr().getDomain().print(os);
|
|
os << ", ";
|
|
|
|
std::optional<llvm::VersionTuple> version;
|
|
switch (getReason()) {
|
|
case Reason::UnavailableUnconditionally:
|
|
os << "unavailable";
|
|
break;
|
|
case Reason::UnavailableObsolete:
|
|
os << "obsoleted";
|
|
version = getAttr().getObsoleted();
|
|
break;
|
|
case Reason::UnavailableUnintroduced:
|
|
case Reason::Unintroduced:
|
|
os << "introduced";
|
|
version = getAttr().getIntroduced();
|
|
break;
|
|
}
|
|
|
|
if (version)
|
|
os << ": " << *version;
|
|
os << ")";
|
|
}
|
|
|
|
static bool constraintIsStronger(const AvailabilityConstraint &lhs,
|
|
const AvailabilityConstraint &rhs) {
|
|
DEBUG_ASSERT(lhs.getDomain() == rhs.getDomain());
|
|
|
|
// If the constraints have matching domains but different reasons, the
|
|
// constraint with the lowest reason is "strongest".
|
|
if (lhs.getReason() != rhs.getReason())
|
|
return lhs.getReason() < rhs.getReason();
|
|
|
|
switch (lhs.getReason()) {
|
|
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
|
|
// Just keep the first.
|
|
return false;
|
|
|
|
case AvailabilityConstraint::Reason::UnavailableObsolete:
|
|
// Pick the larger obsoleted range.
|
|
return lhs.getAttr().getObsoleted().value() <
|
|
rhs.getAttr().getObsoleted().value();
|
|
|
|
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
|
|
case AvailabilityConstraint::Reason::Unintroduced:
|
|
// Pick the smaller introduced range.
|
|
return lhs.getAttr().getIntroduced().value_or(llvm::VersionTuple()) >
|
|
rhs.getAttr().getIntroduced().value_or(llvm::VersionTuple());
|
|
}
|
|
}
|
|
|
|
void addConstraint(llvm::SmallVector<AvailabilityConstraint, 4> &constraints,
|
|
const AvailabilityConstraint &constraint,
|
|
const ASTContext &ctx) {
|
|
|
|
auto iter = llvm::find_if(
|
|
constraints, [&constraint](AvailabilityConstraint &existing) {
|
|
return constraint.getDomain() == existing.getDomain();
|
|
});
|
|
|
|
// There's no existing constraint for the same domain so just add it.
|
|
if (iter == constraints.end()) {
|
|
constraints.emplace_back(constraint);
|
|
return;
|
|
}
|
|
|
|
if (constraintIsStronger(constraint, *iter)) {
|
|
constraints.erase(iter);
|
|
constraints.emplace_back(constraint);
|
|
}
|
|
}
|
|
|
|
std::optional<AvailabilityConstraint>
|
|
DeclAvailabilityConstraints::getPrimaryConstraint() const {
|
|
std::optional<AvailabilityConstraint> result;
|
|
|
|
auto isStrongerConstraint = [](const AvailabilityConstraint &lhs,
|
|
const AvailabilityConstraint &rhs) {
|
|
// Constraint reasons are defined in descending order of strength.
|
|
if (lhs.getReason() != rhs.getReason())
|
|
return lhs.getReason() < rhs.getReason();
|
|
|
|
if (lhs.getDomain() != rhs.getDomain()) {
|
|
// Constraints in the universal domain are the strongest.
|
|
if (rhs.getDomain().isUniversal())
|
|
return true;
|
|
|
|
// Otherwise, pick the constraint from the broader domain.
|
|
if (lhs.getDomain() != rhs.getDomain())
|
|
return rhs.getDomain().contains(lhs.getDomain());
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Pick the strongest constraint.
|
|
for (auto const &constraint : constraints) {
|
|
if (!result || isStrongerConstraint(constraint, *result))
|
|
result.emplace(constraint);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void DeclAvailabilityConstraints::print(llvm::raw_ostream &os) const {
|
|
os << "{\n";
|
|
llvm::interleave(
|
|
constraints,
|
|
[&os](const AvailabilityConstraint &constraint) {
|
|
os << " " << constraint;
|
|
},
|
|
[&os] { os << ",\n"; });
|
|
os << "\n}";
|
|
}
|
|
|
|
static bool canIgnoreConstraintInUnavailableContexts(
|
|
const Decl *decl, const AvailabilityConstraint &constraint,
|
|
const AvailabilityConstraintFlags flags) {
|
|
auto domain = constraint.getDomain();
|
|
|
|
// Always reject uses of universally unavailable declarations, regardless
|
|
// of context, since there are no possible compilation configurations in
|
|
// which they are available. However, make an exception for types and
|
|
// conformances, which can sometimes be awkward to avoid references to.
|
|
if (!flags.contains(AvailabilityConstraintFlag::
|
|
AllowUniversallyUnavailableInCompatibleContexts)) {
|
|
if (!isa<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
|
|
if (domain.isUniversal() || domain.isSwiftLanguageMode())
|
|
return false;
|
|
}
|
|
}
|
|
|
|
switch (constraint.getReason()) {
|
|
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
|
|
return true;
|
|
|
|
case AvailabilityConstraint::Reason::Unintroduced:
|
|
case AvailabilityConstraint::Reason::UnavailableObsolete:
|
|
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
|
|
return domain.isVersioned();
|
|
}
|
|
}
|
|
|
|
static bool
|
|
shouldIgnoreConstraintInContext(const Decl *decl,
|
|
const AvailabilityConstraint &constraint,
|
|
const AvailabilityContext &context,
|
|
const AvailabilityConstraintFlags flags) {
|
|
if (!context.isUnavailable())
|
|
return false;
|
|
|
|
if (!canIgnoreConstraintInUnavailableContexts(decl, constraint, flags))
|
|
return false;
|
|
|
|
// If the constraint's domain is a superset of the compilation's target
|
|
// availability domain, use the more specific target availability domain
|
|
// instead. This allows declarations that are @available(macOS, unavailable)
|
|
// to be used in contexts that are @available(macOSApplicationExtension,
|
|
// unavailable), for example.
|
|
auto &ctx = decl->getASTContext();
|
|
auto domain = constraint.getDomain();
|
|
auto targetDomain = ctx.getTargetAvailabilityDomain();
|
|
if (domain.isSupersetOf(targetDomain))
|
|
domain = targetDomain;
|
|
|
|
return context.isUnavailableForDomain(domain);
|
|
}
|
|
|
|
/// Returns the `AvailabilityConstraint` that describes how \p attr restricts
|
|
/// use of \p decl in \p context or `std::nullopt` if there is no restriction.
|
|
static std::optional<AvailabilityConstraint>
|
|
getAvailabilityConstraintForAttr(const Decl *decl,
|
|
const SemanticAvailableAttr &attr,
|
|
const AvailabilityContext &context,
|
|
const AvailabilityConstraintFlags flags) {
|
|
auto getConstraint = [&]() -> std::optional<AvailabilityConstraint> {
|
|
// Is the decl unconditionally unavailable?
|
|
if (attr.isUnconditionallyUnavailable())
|
|
return AvailabilityConstraint::unavailableUnconditionally(attr);
|
|
|
|
auto &ctx = decl->getASTContext();
|
|
auto domain = attr.getDomain();
|
|
bool domainSupportsRefinement = domain.supportsContextRefinement();
|
|
|
|
// Compute the available range in the given context. If there is no
|
|
// explicit range defined by the context, use the deployment range as
|
|
// fallback.
|
|
std::optional<AvailabilityRange> availableRange;
|
|
if (domainSupportsRefinement)
|
|
availableRange = context.getAvailabilityRange(domain, ctx);
|
|
if (!availableRange)
|
|
availableRange = domain.getDeploymentRange(ctx);
|
|
|
|
// Is the decl obsoleted in this context?
|
|
if (auto obsoletedRange = attr.getObsoletedRange(ctx)) {
|
|
if (availableRange && !availableRange->isKnownUnreachable() &&
|
|
availableRange->isContainedIn(*obsoletedRange))
|
|
return AvailabilityConstraint::unavailableObsolete(attr);
|
|
}
|
|
|
|
// Is the decl not yet introduced in this context?
|
|
if (auto introducedRange = attr.getIntroducedRange(ctx)) {
|
|
if (!availableRange || !availableRange->isContainedIn(*introducedRange))
|
|
return domainSupportsRefinement
|
|
? AvailabilityConstraint::unintroduced(attr)
|
|
: AvailabilityConstraint::unavailableUnintroduced(attr);
|
|
}
|
|
|
|
return std::nullopt;
|
|
};
|
|
|
|
auto constraint = getConstraint();
|
|
if (constraint &&
|
|
shouldIgnoreConstraintInContext(decl, *constraint, context, flags))
|
|
return std::nullopt;
|
|
|
|
return constraint;
|
|
}
|
|
|
|
/// Returns the most specific platform domain from the availability attributes
|
|
/// attached to \p decl or `std::nullopt` if there are none. Platform specific
|
|
/// `@available` attributes for other platforms should be ignored. For example,
|
|
/// if a declaration has attributes for both iOS and macCatalyst, only the
|
|
/// macCatalyst attributes take effect when compiling for a macCatalyst target.
|
|
static std::optional<AvailabilityDomain>
|
|
activePlatformDomainForDecl(const Decl *decl) {
|
|
std::optional<AvailabilityDomain> activeDomain;
|
|
for (auto attr :
|
|
decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
|
|
auto domain = attr.getDomain();
|
|
if (!domain.isPlatform())
|
|
continue;
|
|
|
|
if (activeDomain && domain.contains(*activeDomain))
|
|
continue;
|
|
|
|
activeDomain.emplace(domain);
|
|
}
|
|
|
|
return activeDomain;
|
|
}
|
|
|
|
static void getAvailabilityConstraintsForDecl(
|
|
llvm::SmallVector<AvailabilityConstraint, 4> &constraints, const Decl *decl,
|
|
const AvailabilityContext &context, AvailabilityConstraintFlags flags) {
|
|
auto &ctx = decl->getASTContext();
|
|
auto activePlatformDomain = activePlatformDomainForDecl(decl);
|
|
bool includeAllDomains =
|
|
flags.contains(AvailabilityConstraintFlag::IncludeAllDomains);
|
|
|
|
for (auto attr : decl->getSemanticAvailableAttrs(includeAllDomains)) {
|
|
auto domain = attr.getDomain();
|
|
if (!includeAllDomains && domain.isPlatform() && activePlatformDomain &&
|
|
!activePlatformDomain->contains(domain))
|
|
continue;
|
|
|
|
if (auto constraint =
|
|
getAvailabilityConstraintForAttr(decl, attr, context, flags))
|
|
addConstraint(constraints, *constraint, ctx);
|
|
}
|
|
}
|
|
|
|
DeclAvailabilityConstraints
|
|
swift::getAvailabilityConstraintsForDecl(const Decl *decl,
|
|
const AvailabilityContext &context,
|
|
AvailabilityConstraintFlags flags) {
|
|
llvm::SmallVector<AvailabilityConstraint, 4> constraints;
|
|
|
|
// Generic parameters are always available.
|
|
if (isa<GenericTypeParamDecl>(decl))
|
|
return DeclAvailabilityConstraints();
|
|
|
|
decl = decl->getAbstractSyntaxDeclForAttributes();
|
|
|
|
getAvailabilityConstraintsForDecl(constraints, decl, context, flags);
|
|
|
|
// For requirements of reparentable protocols, add constraints from the
|
|
// enclosing protocol itself. We don't need to do this for ordinary protocols
|
|
// because of the rule that a protocol P cannot inherit from Q if Q is less
|
|
// available than P. Thus, the availability of the most derived protocol
|
|
// already carries the same or stricter constraints than its ancestors.
|
|
if (auto *proto = decl->getDeclContext()->getSelfProtocolDecl()) {
|
|
if (proto->getAttrs().hasAttribute<ReparentableAttr>())
|
|
getAvailabilityConstraintsForDecl(constraints, proto, context, flags);
|
|
}
|
|
|
|
if (flags.contains(AvailabilityConstraintFlag::SkipEnclosingExtension))
|
|
return constraints;
|
|
|
|
// If decl is an extension member, query the attributes of the extension, too.
|
|
//
|
|
// Skip decls imported from Clang, though, as they could be associated to the
|
|
// wrong extension and inherit unavailability incorrectly. ClangImporter
|
|
// associates Objective-C protocol members to the first category where the
|
|
// protocol is directly or indirectly adopted, no matter its availability
|
|
// and the availability of other categories. rdar://problem/53956555
|
|
if (decl->getClangNode())
|
|
return constraints;
|
|
|
|
auto parent = decl->parentDeclForAvailability();
|
|
if (auto extension = dyn_cast_or_null<ExtensionDecl>(parent))
|
|
getAvailabilityConstraintsForDecl(constraints, extension, context, flags);
|
|
|
|
return constraints;
|
|
}
|
|
|
|
std::optional<AvailabilityConstraint>
|
|
swift::getAvailabilityConstraintForDeclInDomain(
|
|
const Decl *decl, const AvailabilityContext &context,
|
|
AvailabilityDomain domain, AvailabilityConstraintFlags flags) {
|
|
auto constraints = getAvailabilityConstraintsForDecl(decl, context, flags);
|
|
for (auto const &constraint : constraints) {
|
|
if (constraint.getDomain().isRelated(domain))
|
|
return constraint;
|
|
}
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// Returns true if unsatisfied `@available(..., unavailable)` constraints for
|
|
/// \p domain make code unreachable at runtime
|
|
static bool
|
|
domainCanBeUnconditionallyUnavailableAtRuntime(AvailabilityDomain domain,
|
|
const ASTContext &ctx) {
|
|
switch (domain.getKind()) {
|
|
case AvailabilityDomain::Kind::Universal:
|
|
return true;
|
|
|
|
case AvailabilityDomain::Kind::Platform:
|
|
if (ctx.LangOpts.TargetVariant &&
|
|
domain.isActive(ctx, /*forTargetVariant=*/true))
|
|
return true;
|
|
return domain.isActive(ctx);
|
|
|
|
case AvailabilityDomain::Kind::SwiftLanguageMode:
|
|
case AvailabilityDomain::Kind::StandaloneSwiftRuntime:
|
|
case AvailabilityDomain::Kind::PackageDescription:
|
|
return false;
|
|
|
|
case AvailabilityDomain::Kind::Embedded:
|
|
return ctx.LangOpts.hasFeature(Feature::Embedded);
|
|
|
|
case AvailabilityDomain::Kind::Custom:
|
|
switch (domain.getCustomDomain()->getKind()) {
|
|
case CustomAvailabilityDomain::Kind::Enabled:
|
|
case CustomAvailabilityDomain::Kind::AlwaysEnabled:
|
|
return true;
|
|
case CustomAvailabilityDomain::Kind::Disabled:
|
|
case CustomAvailabilityDomain::Kind::Dynamic:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns true if unsatisfied introduction constraints for \p domain make
|
|
/// code unreachable at runtime.
|
|
static bool
|
|
domainIsUnavailableAtRuntimeIfUnintroduced(AvailabilityDomain domain,
|
|
const ASTContext &ctx) {
|
|
switch (domain.getKind()) {
|
|
case AvailabilityDomain::Kind::Universal:
|
|
case AvailabilityDomain::Kind::Platform:
|
|
case AvailabilityDomain::Kind::SwiftLanguageMode:
|
|
case AvailabilityDomain::Kind::StandaloneSwiftRuntime:
|
|
case AvailabilityDomain::Kind::PackageDescription:
|
|
return false;
|
|
|
|
case AvailabilityDomain::Kind::Embedded:
|
|
return !ctx.LangOpts.hasFeature(Feature::Embedded);
|
|
|
|
case AvailabilityDomain::Kind::Custom:
|
|
switch (domain.getCustomDomain()->getKind()) {
|
|
case CustomAvailabilityDomain::Kind::Enabled:
|
|
case CustomAvailabilityDomain::Kind::AlwaysEnabled:
|
|
case CustomAvailabilityDomain::Kind::Dynamic:
|
|
return false;
|
|
case CustomAvailabilityDomain::Kind::Disabled:
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool constraintIndicatesRuntimeUnavailability(
|
|
const AvailabilityConstraint &constraint, const ASTContext &ctx) {
|
|
auto domain = constraint.getDomain();
|
|
switch (constraint.getReason()) {
|
|
case AvailabilityConstraint::Reason::UnavailableUnconditionally:
|
|
return domainCanBeUnconditionallyUnavailableAtRuntime(domain, ctx);
|
|
case AvailabilityConstraint::Reason::UnavailableObsolete:
|
|
case AvailabilityConstraint::Reason::UnavailableUnintroduced:
|
|
return false;
|
|
case AvailabilityConstraint::Reason::Unintroduced:
|
|
return domainIsUnavailableAtRuntimeIfUnintroduced(domain, ctx);
|
|
}
|
|
}
|
|
|
|
void swift::getRuntimeUnavailableDomains(
|
|
const DeclAvailabilityConstraints &constraints,
|
|
llvm::SmallVectorImpl<AvailabilityDomain> &domains, const ASTContext &ctx) {
|
|
for (auto constraint : constraints) {
|
|
if (constraintIndicatesRuntimeUnavailability(constraint, ctx))
|
|
domains.push_back(constraint.getDomain());
|
|
}
|
|
}
|