Files
swift-mirror/lib/AST/Availability.cpp
2025-12-04 16:51:08 -08:00

1067 lines
37 KiB
C++

//===--- Availability.cpp - Swift Availability Structures -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//
//
// This file defines data structures for API availability.
//
//===----------------------------------------------------------------------===//
#include "swift/AST/ASTContext.h"
#include "swift/AST/Attr.h"
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/AvailabilityContext.h"
#include "swift/AST/AvailabilityDomain.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilityRange.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DeclExportabilityVisitor.h"
// FIXME: [availability] Remove this when possible
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/PlatformKindUtils.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/TypeWalker.h"
#include "swift/AST/Types.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Platform.h"
#include "swift/ClangImporter/ClangModule.h"
#include <map>
using namespace swift;
void VersionRange::Profile(llvm::FoldingSetNodeID &id) const {
id.AddBoolean(hasLowerEndpoint());
if (!hasLowerEndpoint()) {
id.AddBoolean(isAll());
return;
}
auto profileVersionComponent = [&id](std::optional<unsigned> component) {
id.AddBoolean(component.has_value());
if (component)
id.AddInteger(*component);
};
auto lowerEndpoint = getLowerEndpoint();
id.AddInteger(lowerEndpoint.getMajor());
profileVersionComponent(lowerEndpoint.getMinor());
profileVersionComponent(lowerEndpoint.getSubminor());
profileVersionComponent(lowerEndpoint.getBuild());
}
AvailabilityRange
AvailabilityRange::forDeploymentTarget(const ASTContext &Ctx) {
return AvailabilityRange(Ctx.LangOpts.getMinPlatformVersion());
}
AvailabilityRange AvailabilityRange::forInliningTarget(const ASTContext &Ctx) {
return AvailabilityRange(Ctx.LangOpts.MinimumInliningTargetVersion);
}
AvailabilityRange AvailabilityRange::forRuntimeTarget(const ASTContext &Ctx) {
return AvailabilityRange(Ctx.LangOpts.RuntimeVersion);
}
namespace {
/// The inferred availability required to access a group of declarations
/// on a single platform.
struct InferredAvailability {
AvailableAttr::Kind Kind = AvailableAttr::Kind::Default;
std::optional<llvm::VersionTuple> Introduced;
std::optional<llvm::VersionTuple> Deprecated;
std::optional<llvm::VersionTuple> Obsoleted;
StringRef Message;
StringRef Rename;
bool IsSPI = false;
};
/// The type of a function that merges two version tuples.
typedef const llvm::VersionTuple &(*MergeFunction)(
const llvm::VersionTuple &, const llvm::VersionTuple &);
} // end anonymous namespace
/// Apply a merge function to two optional versions, returning the result
/// in Inferred.
static bool
mergeIntoInferredVersion(const std::optional<llvm::VersionTuple> &Version,
std::optional<llvm::VersionTuple> &Inferred,
MergeFunction Merge) {
if (Version.has_value()) {
if (Inferred.has_value()) {
Inferred = Merge(Inferred.value(), Version.value());
return *Inferred == *Version;
} else {
Inferred = Version;
return true;
}
}
return false;
}
/// Merge an attribute's availability with an existing inferred availability
/// so that the new inferred availability is at least as available as
/// the attribute requires.
static void mergeWithInferredAvailability(SemanticAvailableAttr Attr,
InferredAvailability &Inferred) {
auto *ParsedAttr = Attr.getParsedAttr();
Inferred.Kind = static_cast<AvailableAttr::Kind>(
std::max(static_cast<unsigned>(Inferred.Kind),
static_cast<unsigned>(ParsedAttr->getKind())));
// The merge of two introduction versions is the maximum of the two versions.
if (mergeIntoInferredVersion(Attr.getIntroduced(), Inferred.Introduced,
std::max)) {
Inferred.IsSPI = Attr.isSPI();
}
// The merge of deprecated and obsoleted versions takes the minimum.
mergeIntoInferredVersion(Attr.getDeprecated(), Inferred.Deprecated, std::min);
mergeIntoInferredVersion(Attr.getObsoleted(), Inferred.Obsoleted, std::min);
if (Inferred.Message.empty() && !Attr.getMessage().empty())
Inferred.Message = Attr.getMessage();
if (Inferred.Rename.empty() && !Attr.getRename().empty())
Inferred.Rename = Attr.getRename();
}
/// Create an implicit availability attribute for the given domain
/// and with the inferred availability.
static AvailableAttr *createAvailableAttr(AvailabilityDomain Domain,
const InferredAvailability &Inferred,
ASTContext &Context) {
// If there is no information that would go into the availability attribute,
// don't create one.
if (Inferred.Kind == AvailableAttr::Kind::Default && !Inferred.Introduced &&
!Inferred.Deprecated && !Inferred.Obsoleted && Inferred.Message.empty() &&
Inferred.Rename.empty())
return nullptr;
llvm::VersionTuple Introduced =
Inferred.Introduced.value_or(llvm::VersionTuple());
llvm::VersionTuple Deprecated =
Inferred.Deprecated.value_or(llvm::VersionTuple());
llvm::VersionTuple Obsoleted =
Inferred.Obsoleted.value_or(llvm::VersionTuple());
return new (Context) AvailableAttr(
SourceLoc(), SourceRange(), Domain, SourceLoc(), Inferred.Kind,
Inferred.Message, Inferred.Rename, Introduced, SourceRange(), Deprecated,
SourceRange(), Obsoleted, SourceRange(), /*Implicit=*/true,
Inferred.IsSPI);
}
void AvailabilityInference::applyInferredAvailableAttrs(
Decl *ToDecl, ArrayRef<const Decl *> InferredFromDecls) {
auto &Context = ToDecl->getASTContext();
// Iterate over the declarations and infer required availability on
// a per-domain basis.
std::map<AvailabilityDomain, InferredAvailability,
StableAvailabilityDomainComparator>
Inferred;
for (const Decl *D : InferredFromDecls) {
llvm::SmallVector<SemanticAvailableAttr, 8> MergedAttrs;
do {
llvm::SmallVector<SemanticAvailableAttr, 8> PendingAttrs;
for (auto AvAttr : D->getSemanticAvailableAttrs()) {
// Skip an attribute from an outer declaration if it is for a platform
// that was already handled implicitly by an attribute from an inner
// declaration.
if (llvm::any_of(MergedAttrs,
[&AvAttr](SemanticAvailableAttr MergedAttr) {
return inheritsAvailabilityFromPlatform(
AvAttr.getPlatform(), MergedAttr.getPlatform());
}))
continue;
mergeWithInferredAvailability(AvAttr, Inferred[AvAttr.getDomain()]);
PendingAttrs.push_back(AvAttr);
}
MergedAttrs.append(PendingAttrs);
// Walk up the enclosing declaration hierarchy to make sure we aren't
// missing any inherited attributes.
D = D->parentDeclForAvailability();
} while (D);
}
// Create an availability attribute for each observed platform and add
// to ToDecl.
for (auto &Pair : Inferred) {
if (auto Attr = createAvailableAttr(Pair.first, Pair.second, Context))
ToDecl->addAttribute(Attr);
}
}
/// Returns the decl that should be considered the parent decl of the given decl
/// when looking for inherited availability annotations.
const Decl *Decl::parentDeclForAvailability() const {
if (auto *AD = dyn_cast<AccessorDecl>(this))
return AD->getStorage();
if (auto *ED = dyn_cast<ExtensionDecl>(this)) {
if (auto *NTD = ED->getExtendedNominal())
return NTD;
}
if (auto *PBD = dyn_cast<PatternBindingDecl>(this)) {
if (PBD->getNumPatternEntries() < 1)
return nullptr;
return PBD->getAnchoringVarDecl(0);
}
if (auto *OTD = dyn_cast<OpaqueTypeDecl>(this))
return OTD->getNamingDecl();
// Clang decls may be inaccurately parented rdar://53956555
if (hasClangNode())
return nullptr;
// Availability is inherited from the enclosing context.
return getDeclContext()->getInnermostDeclarationDeclContext();
}
/// Returns true if the introduced version in \p newAttr should be used instead
/// of the introduced version in \p prevAttr when both are attached to the same
/// declaration and refer to the active platform.
static bool isBetterThan(const SemanticAvailableAttr &newAttr,
const std::optional<SemanticAvailableAttr> &prevAttr) {
// If there is no prevAttr, newAttr of course wins.
if (!prevAttr)
return true;
// If they belong to the same platform, the one that introduces later wins.
if (prevAttr->getPlatform() == newAttr.getPlatform())
return prevAttr->getIntroduced().value() < newAttr.getIntroduced().value();
// If the new attribute's platform inherits from the old one, it wins.
return inheritsAvailabilityFromPlatform(newAttr.getPlatform(),
prevAttr->getPlatform());
}
static std::optional<SemanticAvailableAttr>
getDeclAvailableAttrForPlatformIntroduction(const Decl *D) {
std::optional<SemanticAvailableAttr> bestAvailAttr;
D = D->getAbstractSyntaxDeclForAttributes();
for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (!attr.isPlatformSpecific() || !attr.getIntroduced())
continue;
if (isBetterThan(attr, bestAvailAttr))
bestAvailAttr.emplace(attr);
}
return bestAvailAttr;
}
std::optional<AvailabilityRange>
AvailabilityInference::annotatedAvailableRange(const Decl *D) {
auto bestAvailAttr = D->getAvailableAttrForPlatformIntroduction();
if (!bestAvailAttr)
return std::nullopt;
return bestAvailAttr->getIntroducedRange(D->getASTContext());
}
bool Decl::isAvailableAsSPI() const {
if (auto attr = getAvailableAttrForPlatformIntroduction())
return attr->isSPI();
return false;
}
SemanticAvailableAttributes
Decl::getSemanticAvailableAttrs(bool includeInactive) const {
// A decl in an @abi gets its availability from the decl it's attached to.
auto abiRole = ABIRoleInfo(this);
if (!abiRole.providesAPI() && abiRole.getCounterpart())
return abiRole.getCounterpart()->getSemanticAvailableAttrs(includeInactive);
return SemanticAvailableAttributes(getAttrs(), this, includeInactive);
}
std::optional<SemanticAvailableAttr>
Decl::getSemanticAvailableAttr(const AvailableAttr *attr) const {
return evaluateOrDefault(getASTContext().evaluator,
SemanticAvailableAttrRequest{attr, this},
std::nullopt);
}
std::optional<SemanticAvailableAttr>
Decl::getActiveAvailableAttrForCurrentPlatform() const {
std::optional<SemanticAvailableAttr> bestAttr;
for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (!attr.isPlatformSpecific())
continue;
// We have an attribute that is active for the platform, but is it more
// specific than our current best?
if (!bestAttr || inheritsAvailabilityFromPlatform(
attr.getPlatform(), bestAttr->getPlatform())) {
bestAttr.emplace(attr);
}
}
return bestAttr;
}
std::optional<SemanticAvailableAttr> Decl::getDeprecatedAttr() const {
auto &ctx = getASTContext();
std::optional<SemanticAvailableAttr> result;
auto bestActive = getActiveAvailableAttrForCurrentPlatform();
for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (attr.isPlatformSpecific() && (!bestActive || attr != bestActive))
continue;
// Unconditional deprecated.
if (attr.isUnconditionallyDeprecated())
return attr;
auto deprecatedRange = attr.getDeprecatedRange(ctx);
if (!deprecatedRange)
continue;
// We treat the declaration as deprecated if it is deprecated on
// all deployment targets.
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
if (deploymentRange && deploymentRange->isContainedIn(*deprecatedRange))
result.emplace(attr);
}
return result;
}
std::optional<SemanticAvailableAttr> Decl::getSoftDeprecatedAttr() const {
auto &ctx = getASTContext();
std::optional<SemanticAvailableAttr> result;
auto bestActive = getActiveAvailableAttrForCurrentPlatform();
for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (attr.isPlatformSpecific() && (!bestActive || attr != bestActive))
continue;
auto deprecatedRange = attr.getDeprecatedRange(ctx);
if (!deprecatedRange)
continue;
// We treat the declaration as soft-deprecated if it is deprecated in a
// future version.
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
if (!deploymentRange || !deploymentRange->isContainedIn(*deprecatedRange))
result.emplace(attr);
}
return result;
}
std::optional<SemanticAvailableAttr> Decl::getNoAsyncAttr() const {
std::optional<SemanticAvailableAttr> bestAttr;
for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (!attr.isNoAsync())
continue;
if (!bestAttr) {
// If there is no best attr selected and the attr either has an active
// platform, or doesn't have one at all, select it.
bestAttr.emplace(attr);
} else if (bestAttr && attr.isPlatformSpecific() &&
bestAttr->isPlatformSpecific() &&
inheritsAvailabilityFromPlatform(attr.getPlatform(),
bestAttr->getPlatform())) {
// if they both have a viable platform, use the better one
bestAttr.emplace(attr);
} else if (attr.isPlatformSpecific() && !bestAttr->isPlatformSpecific()) {
// Use the one more specific
bestAttr.emplace(attr);
}
}
return bestAttr;
}
bool Decl::isUnavailableInCurrentSwiftVersion() const {
llvm::VersionTuple vers = getASTContext().LangOpts.EffectiveLanguageVersion;
for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
if (attr.isSwiftLanguageModeSpecific()) {
auto introduced = attr.getIntroduced();
if (introduced && *introduced > vers)
return true;
auto obsoleted = attr.getObsoleted();
if (obsoleted && *obsoleted <= vers)
return true;
}
}
return false;
}
std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
auto context = AvailabilityContext::forDeploymentTarget(getASTContext());
if (auto constraint = getAvailabilityConstraintsForDecl(this, context)
.getPrimaryConstraint()) {
if (constraint->isUnavailable())
return constraint->getAttr();
}
return std::nullopt;
}
/// Returns the mutually exclusive root platform domains that must all be
/// unavailable in order for a declaration to be unavailable at runtime.
static llvm::SmallSetVector<AvailabilityDomain, 2>
getRootTargetDomains(const ASTContext &ctx) {
llvm::SmallSetVector<AvailabilityDomain, 2> domains;
// Regardless of target platform, binaries built for Embedded do not require
// compatibility.
if (ctx.LangOpts.hasFeature(Feature::Embedded))
return domains;
auto targetPlatform = swift::targetPlatform(ctx.LangOpts);
if (targetPlatform != PlatformKind::none)
domains.insert(
AvailabilityDomain::forPlatform(targetPlatform).getRootDomain());
auto targetVariantPlatform = swift::targetVariantPlatform(ctx.LangOpts);
if (targetVariantPlatform != PlatformKind::none)
domains.insert(
AvailabilityDomain::forPlatform(targetVariantPlatform).getRootDomain());
return domains;
}
/// Returns true if a decl that is unavailable in the given domain must still be
/// emitted to preserve load time ABI compatibility.
static bool
domainRequiresABICompatibleUnavailableDecls(AvailabilityDomain domain,
const ASTContext &ctx) {
// FIXME: [availability] Restrict ABI compatible unavailable decls to modules
// compiled with macOS, iOS, watchOS, tvOS, or visionOS target triples. For
// other targets, unavailable code should always be stripped from binaries.
return domain.isUniversal() || domain.isPlatform();
}
/// Computes the `DeclRuntimeAvailability` value for `decl` in isolation.
static DeclRuntimeAvailability
computeDeclRuntimeAvailability(const Decl *decl) {
// Don't trust unavailability on declarations from Clang modules.
if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
return DeclRuntimeAvailability::PotentiallyAvailable;
auto &ctx = decl->getASTContext();
auto rootTargetDomains = getRootTargetDomains(ctx);
auto remainingTargetDomains = rootTargetDomains;
AvailabilityConstraintFlags flags;
// Semantic availability was already computed separately for any enclosing
// extension.
flags |= AvailabilityConstraintFlag::SkipEnclosingExtension;
// FIXME: [availability] Replace IncludeAllDomains with a RuntimeAvailability
// flag that includes the target variant constraints and keeps all constraints
// from active platforms.
flags |= AvailabilityConstraintFlag::IncludeAllDomains;
auto constraints = getAvailabilityConstraintsForDecl(
decl, AvailabilityContext::forInliningTarget(ctx), flags);
// First, collect the unavailable domains from the constraints.
llvm::SmallVector<AvailabilityDomain, 8> unavailableDomains;
getRuntimeUnavailableDomains(constraints, unavailableDomains, ctx);
// Check whether there are any available attributes that would make the
// decl available in descendants of the unavailable domains.
for (auto attr :
decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
auto domain = attr.getDomain();
if (llvm::is_contained(unavailableDomains, domain))
continue;
llvm::erase_if(unavailableDomains, [domain](auto unavailableDomain) {
// Unavailability in '*' cannot be superseded by an @available attribute
// for a more specific availability domain.
if (unavailableDomain.isUniversal())
return false;
return unavailableDomain.contains(domain);
});
}
// Check the remaining unavailable domains to see if the requirements for
// runtime unreachability are met.
auto result = DeclRuntimeAvailability::PotentiallyAvailable;
for (auto domain : unavailableDomains) {
// Check whether the constraint is from a relevant domain.
bool isTargetDomain = rootTargetDomains.contains(domain);
if (!domain.isActive(ctx) && !isTargetDomain)
continue;
// FIXME: [runtime availability] Update this?
if (!domain.isRoot())
continue;
// We've found an unavailable target domain. If all the target domains are
// unavailable then the decl is unreachable at runtime.
if (isTargetDomain) {
remainingTargetDomains.remove(domain);
if (remainingTargetDomains.empty())
result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
continue;
}
// We've found a single unavailable domain that alone proves the decl is
// unreachable at runtime. It may still be required at load time, though.
if (domainRequiresABICompatibleUnavailableDecls(domain, ctx)) {
result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
continue;
}
return DeclRuntimeAvailability::AlwaysUnavailable;
}
return result;
}
/// Determines the `DeclRuntimeAvailability` value for `decl` via
/// `DeclRuntimeAvailabilityRequest`.
static DeclRuntimeAvailability getDeclRuntimeAvailability(const Decl *decl) {
return evaluateOrDefault(decl->getASTContext().evaluator,
DeclRuntimeAvailabilityRequest{decl},
DeclRuntimeAvailability::PotentiallyAvailable);
}
DeclRuntimeAvailability
DeclRuntimeAvailabilityRequest::evaluate(Evaluator &evaluator,
const Decl *decl) const {
auto inherited = DeclRuntimeAvailability::PotentiallyAvailable;
if (auto *parent = decl->parentDeclForAvailability())
inherited = getDeclRuntimeAvailability(parent);
// If the inherited runtime availability is already maximally unavailable
// then skip computing unavailability for this declaration.
if (inherited == DeclRuntimeAvailability::AlwaysUnavailable)
return DeclRuntimeAvailability::AlwaysUnavailable;
auto availability = computeDeclRuntimeAvailability(decl);
return std::max(inherited, availability);
}
bool Decl::isUnreachableAtRuntime() const {
return getDeclRuntimeAvailability(this) >=
DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
}
static UnavailableDeclOptimization
getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())
return *ctx.LangOpts.UnavailableDeclOptimizationMode;
return UnavailableDeclOptimization::None;
}
bool Decl::isAvailableDuringLowering() const {
auto availability = getDeclRuntimeAvailability(this);
if (getEffectiveUnavailableDeclOptimization(getASTContext()) !=
UnavailableDeclOptimization::Complete)
return availability < DeclRuntimeAvailability::AlwaysUnavailable;
// All unreachable declarations should be skipped during lowering
// when -unavailable-decl-optimization=complete is specified.
return availability < DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
}
bool Decl::requiresUnavailableDeclABICompatibilityStubs() const {
// Code associated with unavailable declarations should trap at runtime if
// -unavailable-decl-optimization=stub is specified.
if (getEffectiveUnavailableDeclOptimization(getASTContext()) !=
UnavailableDeclOptimization::Stub)
return false;
return isUnreachableAtRuntime();
}
AvailabilityRange AvailabilityInference::annotatedAvailableRangeForAttr(
const Decl *D, const AbstractSpecializeAttr *attr, ASTContext &ctx) {
std::optional<SemanticAvailableAttr> bestAvailAttr;
for (auto *availAttr : attr->getAvailableAttrs()) {
auto semanticAttr = D->getSemanticAvailableAttr(availAttr);
if (!semanticAttr)
continue;
if (!semanticAttr->getIntroduced() || !semanticAttr->isActive(ctx) ||
!semanticAttr->isPlatformSpecific()) {
continue;
}
if (isBetterThan(*semanticAttr, bestAvailAttr))
bestAvailAttr.emplace(*semanticAttr);
}
if (bestAvailAttr)
return bestAvailAttr->getIntroducedRange(ctx).value_or(
AvailabilityRange::alwaysAvailable());
return AvailabilityRange::alwaysAvailable();
}
std::optional<SemanticAvailableAttr>
Decl::getAvailableAttrForPlatformIntroduction(bool checkExtension) const {
if (auto attr = getDeclAvailableAttrForPlatformIntroduction(this))
return attr;
// Unlike other declarations, extensions can be used without referring to them
// by name (they don't have one) in the source. For this reason, when checking
// the available range of a declaration we also need to check to see if it is
// immediately contained in an extension and use the extension's availability
// if the declaration does not have an explicit @available attribute
// itself. This check relies on the fact that we cannot have nested
// extensions.
if (!checkExtension)
return std::nullopt;
if (auto parent = parentDeclForAvailability()) {
if (auto *ED = dyn_cast<ExtensionDecl>(parent)) {
if (auto attr = getDeclAvailableAttrForPlatformIntroduction(ED))
return attr;
}
}
return std::nullopt;
}
AvailabilityRange AvailabilityInference::availableRange(const Decl *D) {
if (auto attr = D->getAvailableAttrForPlatformIntroduction())
return attr->getIntroducedRange(D->getASTContext())
.value_or(AvailabilityRange::alwaysAvailable());
return AvailabilityRange::alwaysAvailable();
}
std::optional<SemanticAvailableAttr>
SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
const AvailableAttr *attr,
const Decl *decl) const {
if (attr->getDomainOrIdentifier().isDomain())
return SemanticAvailableAttr(attr);
auto &ctx = decl->getASTContext();
auto &diags = ctx.Diags;
auto attrLoc = attr->getLocation();
auto domainLoc = attr->getDomainLoc();
auto declContext = decl->getInnermostDeclContext();
auto mutableAttr = const_cast<AvailableAttr *>(attr);
auto domain = mutableAttr->DomainOrIdentifier.resolveInDeclContext(
domainLoc, declContext);
if (!domain)
return std::nullopt;
auto checkVersion = [&](std::optional<llvm::VersionTuple> version,
SourceRange sourceRange) {
if (!version)
return false;
auto diagLoc = sourceRange.isValid() ? sourceRange.Start : attrLoc;
if (!VersionRange::isValidVersion(*version)) {
diags
.diagnose(diagLoc, diag::availability_unsupported_version_number,
*version)
.highlight(sourceRange);
return true;
}
// Warn if the version is not a valid one for the domain. For example, macOS
// 17 will never exist.
if (domain->isVersioned() && !domain->isVersionValid(*version)) {
diags
.diagnose(diagLoc,
diag::availability_invalid_version_number_for_domain,
*version, *domain)
.highlight(sourceRange);
}
return false;
};
if (checkVersion(attr->getRawIntroduced(), attr->IntroducedRange))
return std::nullopt;
if (checkVersion(attr->getRawDeprecated(), attr->DeprecatedRange))
return std::nullopt;
if (checkVersion(attr->getRawObsoleted(), attr->ObsoletedRange))
return std::nullopt;
bool hasIntroduced = attr->getRawIntroduced().has_value();
bool hasDeprecated = attr->getRawDeprecated().has_value();
auto hasObsoleted = attr->getRawObsoleted().has_value();
bool hasVersionSpec = (hasIntroduced || hasDeprecated || hasObsoleted);
if (!domain->isVersioned() && hasVersionSpec) {
SourceRange versionSourceRange;
if (hasIntroduced)
versionSourceRange = attr->IntroducedRange;
else if (hasDeprecated)
versionSourceRange = attr->DeprecatedRange;
else if (hasObsoleted)
versionSourceRange = attr->ObsoletedRange;
diags.diagnose(attrLoc, diag::availability_unexpected_version, *domain)
.limitBehaviorIf(domain->isUniversal(), DiagnosticBehavior::Warning)
.highlight(versionSourceRange);
return std::nullopt;
}
// Diagnose unsupported unconditional availability (deprecated, unavailable,
// noasync).
switch (domain->getKind()) {
case AvailabilityDomain::Kind::Universal:
case AvailabilityDomain::Kind::Embedded:
case AvailabilityDomain::Kind::Custom:
break;
case AvailabilityDomain::Kind::Platform:
// FIXME: [runtime availability] Diagnose Swift runtime platform, too.
break;
case AvailabilityDomain::Kind::SwiftLanguageMode:
case AvailabilityDomain::Kind::StandaloneSwiftRuntime:
case AvailabilityDomain::Kind::PackageDescription:
switch (attr->getKind()) {
case AvailableAttr::Kind::Deprecated:
diags.diagnose(attrLoc,
diag::attr_availability_expected_deprecated_version, attr,
*domain);
return std::nullopt;
case AvailableAttr::Kind::Unavailable:
diags.diagnose(attrLoc, diag::attr_availability_cannot_be_used_for_domain,
"unavailable", attr, *domain);
return std::nullopt;
case AvailableAttr::Kind::NoAsync:
diags.diagnose(attrLoc, diag::attr_availability_cannot_be_used_for_domain,
"noasync", attr, *domain);
return std::nullopt;
case AvailableAttr::Kind::Default:
break;
}
break;
}
if (!hasVersionSpec && domain->isVersioned()) {
switch (attr->getKind()) {
case AvailableAttr::Kind::Default:
diags.diagnose(domainLoc, diag::attr_availability_expected_version_spec,
attr, *domain);
return std::nullopt;
case AvailableAttr::Kind::Deprecated:
case AvailableAttr::Kind::Unavailable:
case AvailableAttr::Kind::NoAsync:
break;
}
}
return SemanticAvailableAttr(attr);
}
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {
if (auto version = attr->getRawIntroduced())
return canonicalizePlatformVersion(getPlatform(), *version);
return std::nullopt;
}
std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getIntroducedDomainAndRange(
const ASTContext &Ctx) const {
auto domain = getDomain();
if (domain.isUniversal())
return std::nullopt;
if (auto introduced = getIntroduced())
return domain.getRemappedDomainAndRange(
*introduced, AvailabilityVersionKind::Introduced, Ctx);
// For versioned domains, an "introduced:" version is always required to
// indicate introduction.
if (domain.isVersioned())
return std::nullopt;
// For version-less domains, an attribute that does not indicate some other
// kind of unconditional availability constraint implicitly specifies that
// the decl is available in all versions of the domain.
switch (attr->getKind()) {
case AvailableAttr::Kind::Default:
return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx),
AvailabilityRange::alwaysAvailable());
case AvailableAttr::Kind::Deprecated:
case AvailableAttr::Kind::Unavailable:
case AvailableAttr::Kind::NoAsync:
return std::nullopt;
}
}
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getDeprecated() const {
if (auto version = attr->getRawDeprecated())
return canonicalizePlatformVersion(getPlatform(), *version);
return std::nullopt;
}
std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getDeprecatedDomainAndRange(
const ASTContext &Ctx) const {
if (auto deprecated = getDeprecated())
return getDomain().getRemappedDomainAndRange(
*deprecated, AvailabilityVersionKind::Deprecated, Ctx);
// Regardless of the whether the domain supports versions or not, an
// unconditional deprecation attribute indicates the decl is always
// deprecated.
if (isUnconditionallyDeprecated())
return AvailabilityDomainAndRange(getDomain().getRemappedDomain(Ctx),
AvailabilityRange::alwaysAvailable());
return std::nullopt;
}
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
if (auto version = attr->getRawObsoleted())
return canonicalizePlatformVersion(getPlatform(), *version);
return std::nullopt;
}
std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getObsoletedDomainAndRange(const ASTContext &Ctx) const {
if (auto obsoleted = getObsoleted())
return getDomain().getRemappedDomainAndRange(
*obsoleted, AvailabilityVersionKind::Obsoleted, Ctx);
// An "unavailable" attribute effectively means obsolete in all versions.
if (isUnconditionallyUnavailable())
return AvailabilityDomainAndRange(getDomain().getRemappedDomain(Ctx),
AvailabilityRange::alwaysAvailable());
return std::nullopt;
}
namespace {
/// Infers the availability required to access a type.
class AvailabilityInferenceTypeWalker : public TypeWalker {
public:
AvailabilityRange AvailabilityInfo = AvailabilityRange::alwaysAvailable();
Action walkToTypePre(Type ty) override {
if (auto *nominalDecl = ty->getAnyNominal()) {
AvailabilityInfo.intersectWith(
AvailabilityInference::availableRange(nominalDecl));
}
return Action::Continue;
}
};
} // end anonymous namespace
AvailabilityRange AvailabilityInference::inferForType(Type t) {
AvailabilityInferenceTypeWalker walker;
t.walk(walker);
return walker.AvailabilityInfo;
}
AvailabilityRange ASTContext::getSwiftFutureAvailability() const {
auto target = LangOpts.Target;
auto getFutureAvailabilityRange = []() -> AvailabilityRange {
return AvailabilityRange(llvm::VersionTuple(99, 99, 0));
};
if (target.isMacOSX()) {
return getFutureAvailabilityRange();
} else if (target.isiOS()) {
return getFutureAvailabilityRange();
} else if (target.isWatchOS()) {
return getFutureAvailabilityRange();
} else if (target.isXROS()) {
return getFutureAvailabilityRange();
} else {
return AvailabilityRange::alwaysAvailable();
}
}
AvailabilityRange ASTContext::getSwiftAvailability(unsigned major,
unsigned minor) const {
auto target = LangOpts.Target;
// Deal with special cases for Swift 5.3 and lower
if (major == 5 && minor <= 3) {
if (target.getArchName() == "arm64e")
return AvailabilityRange::alwaysAvailable();
if (target.isMacOSX() && target.isAArch64())
return AvailabilityRange::alwaysAvailable();
if (target.isiOS() && target.isAArch64()
&& (target.isSimulatorEnvironment()
|| target.isMacCatalystEnvironment()))
return AvailabilityRange::alwaysAvailable();
if (target.isWatchOS() && target.isArch64Bit())
return AvailabilityRange::alwaysAvailable();
}
switch (major) {
#define MAJOR_VERSION(V) case V: switch (minor) {
#define END_MAJOR_VERSION(V) } break;
#define PLATFORM(P, V) \
if (IS_PLATFORM(P)) \
return AvailabilityRange(VersionRange::allGTE(llvm::VersionTuple V));
#define IS_PLATFORM(P) PLATFORM_TEST_##P
#define FUTURE return getSwiftFutureAvailability();
#define PLATFORM_TEST_macOS target.isMacOSX()
#define PLATFORM_TEST_iOS target.isiOS()
#define PLATFORM_TEST_watchOS target.isWatchOS()
#define PLATFORM_TEST_visionOS target.isXROS()
#define _SECOND(A, B) B
#define SECOND(T) _SECOND T
#define RUNTIME_VERSION(V, PLATFORMS) \
case SECOND(V): \
PLATFORMS \
return AvailabilityRange::alwaysAvailable();
#include "swift/AST/RuntimeVersions.def"
#undef PLATFORM_TEST_macOS
#undef PLATFORM_TEST_iOS
#undef PLATFORM_TEST_watchOS
#undef PLATFORM_TEST_visionOS
#undef _SECOND
#undef SECOND
case 99:
if (minor == 99)
return getSwiftFutureAvailability();
break;
}
llvm::report_fatal_error(
Twine("Missing runtime version data for Swift ") +
Twine(major) + Twine('.') + Twine(minor));
}
bool ASTContext::supportsVersionedAvailability() const {
return minimumAvailableOSVersionForTriple(LangOpts.Target).has_value();
}
ExportedLevel swift::isExported(const Decl *D) {
if (auto *VD = dyn_cast<ValueDecl>(D)) {
return isExported(VD);
}
if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
for (unsigned i = 0, e = PBD->getNumPatternEntries(); i < e; ++i) {
if (auto *VD = PBD->getAnchoringVarDecl(i))
return isExported(VD);
}
return ExportedLevel::None;
}
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
return isExported(ED);
}
return ExportedLevel::Exported;
}
ExportedLevel swift::isExported(const ValueDecl *VD) {
if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return ExportedLevel::None;
if (VD->isObjCMemberImplementation())
return ExportedLevel::None;
// Is this part of the module's API or ABI?
AccessScope accessScope =
VD->getFormalAccessScope(nullptr,
/*treatUsableFromInlineAsPublic*/ true);
if (accessScope.isPublic())
return ExportedLevel::Exported;
// Is this a stored property in a @frozen struct or class?
if (auto *property = dyn_cast<VarDecl>(VD))
return property->isLayoutExposedToClients();
// Case of an enum not marked @_implementationOnly in a non-resilient module?
if (auto *EED = dyn_cast<EnumElementDecl>(VD))
return isExported(EED->getParentEnum());
// Is this a type exposed by default in a non-resilient module?
if (isa<NominalTypeDecl>(VD) &&
VD->getASTContext().LangOpts.hasFeature(
Feature::CheckImplementationOnly) &&
VD->getDeclContext()->getParentModule()->getResilienceStrategy() !=
ResilienceStrategy::Resilient &&
!VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
return ExportedLevel::ImplicitlyExported;
return ExportedLevel::None;
}
bool swift::hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
auto nominal = ED->getExtendedNominal();
if (!nominal)
return false;
// Extensions of protocols cannot introduce additional conformances.
if (isa<ProtocolDecl>(nominal))
return false;
auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
for (const ProtocolDecl *PD : protocols) {
AccessScope scope =
PD->getFormalAccessScope(/*useDC*/ nullptr,
/*treatUsableFromInlineAsPublic*/ true);
if (scope.isPublic())
return true;
}
return false;
}
ExportedLevel swift::isExported(const ExtensionDecl *ED) {
// An extension can only be exported if it extends an exported type.
if (auto *NTD = ED->getExtendedNominal()) {
if (isExported(NTD) == ExportedLevel::None)
return ExportedLevel::None;
}
// If the extension declares a conformance to a public protocol then the
// extension is exported.
if (hasConformancesToPublicProtocols(ED))
return ExportedLevel::Exported;
// If there are any exported members then the extension is exported.
ExportedLevel exported = ExportedLevel::None;
for (const Decl *D : ED->getMembers())
exported = std::max(exported, isExported(D));
return exported;
}