AST: Refactor availability version remapping.

Availability version remapping currently only applies to code built for
visionOS. We plan to introduce more platform kinds and standalone availability
domains that will require version remapping, though, so it's time to
rearchitect and simplify the code to make it easier to generalize.

`AvailabilityDomain` is now responsible for version remapping and much of the
previously duplicated utilities have been consolidated.
This commit is contained in:
Allan Shortlidge
2025-10-24 17:13:40 -07:00
parent 88bc79f6af
commit 05f7cb7017
6 changed files with 147 additions and 263 deletions

View File

@@ -33,12 +33,21 @@
namespace swift {
class ASTContext;
class AvailabilityDomainAndRange;
class CustomAvailabilityDomain;
class DeclContext;
class FuncDecl;
class ModuleDecl;
class ValueDecl;
/// Discriminates whether a version tuple represents the `introduced:`,
/// `deprecated:`, or `obsoleted:` version of an `@available` attribute.
enum class AvailabilityVersionKind {
Introduced,
Deprecated,
Obsoleted,
};
/// Represents a dimension of availability (e.g. macOS platform or Swift
/// language mode).
class AvailabilityDomain final {
@@ -131,6 +140,9 @@ private:
: std::nullopt;
}
std::optional<AvailabilityDomain>
getRemappedDomainOrNull(const ASTContext &ctx) const;
public:
AvailabilityDomain() {}
@@ -294,20 +306,23 @@ public:
/// descendants of the iOS domain.
AvailabilityDomain getRootDomain() const;
/// Returns the canonical domain that versions in this domain must be remapped
/// to before making availability comparisons in the current compilation
/// context. Sets \p didRemap to `true` if a remap was required.
const AvailabilityDomain getRemappedDomain(const ASTContext &ctx,
bool &didRemap) const;
/// Returns the canonical domain that versions in this domain must be remapped
/// to before making availability comparisons in the current compilation
/// context.
const AvailabilityDomain getRemappedDomain(const ASTContext &ctx) const {
bool unused;
return getRemappedDomain(ctx, unused);
auto remappedDomain = getRemappedDomainOrNull(ctx);
return remappedDomain ? *remappedDomain : *this;
}
/// Converts the domain and the given version into a canonical domain and
/// range that can be used for availability comparisons in the current current
/// compilation context. If no conversion is necessary or possible, the domain
/// and range are returned unmodified.
AvailabilityDomainAndRange
getRemappedDomainAndRange(const llvm::VersionTuple &version,
AvailabilityVersionKind versionKind,
const ASTContext &ctx) const;
/// Returns true for a domain that is permanently always available, and
/// therefore availability constraints in the domain are effectively the same
/// as constraints in the `*` domain. This is used to diagnose unnecessary

View File

@@ -25,7 +25,6 @@
namespace swift {
class ASTContext;
class AvailabilityDomain;
class BackDeployedAttr;
class Decl;
class SemanticAvailableAttr;
@@ -49,37 +48,8 @@ public:
static std::optional<AvailabilityRange>
annotatedAvailableRange(const Decl *D);
static AvailabilityRange
annotatedAvailableRangeForAttr(const Decl *D, const AbstractSpecializeAttr *attr,
ASTContext &ctx);
/// For the attribute's introduction version, update the platform and version
/// values to the re-mapped platform's, if using a fallback platform.
/// Returns `true` if a remap occured.
static bool updateIntroducedAvailabilityDomainForFallback(
const SemanticAvailableAttr &attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer);
/// For the attribute's deprecation version, update the platform and version
/// values to the re-mapped platform's, if using a fallback platform.
/// Returns `true` if a remap occured.
static bool updateDeprecatedAvailabilityDomainForFallback(
const SemanticAvailableAttr &attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer);
/// For the attribute's obsoletion version, update the platform and version
/// values to the re-mapped platform's, if using a fallback platform.
/// Returns `true` if a remap occured.
static bool updateObsoletedAvailabilityDomainForFallback(
const SemanticAvailableAttr &attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer);
/// For the attribute's before version, update the platform and version
/// values to the re-mapped platform's, if using a fallback platform.
/// Returns `true` if a remap occured.
static bool updateBeforeAvailabilityDomainForFallback(
const BackDeployedAttr *attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer);
static AvailabilityRange annotatedAvailableRangeForAttr(
const Decl *D, const AbstractSpecializeAttr *attr, ASTContext &ctx);
};
} // end namespace swift

View File

@@ -256,128 +256,6 @@ static bool isBetterThan(const SemanticAvailableAttr &newAttr,
prevAttr->getPlatform());
}
static const clang::DarwinSDKInfo::RelatedTargetVersionMapping *
getFallbackVersionMapping(const ASTContext &Ctx,
clang::DarwinSDKInfo::OSEnvPair Kind) {
auto *SDKInfo = Ctx.getDarwinSDKInfo();
if (SDKInfo)
return SDKInfo->getVersionMapping(Kind);
return Ctx.getAuxiliaryDarwinPlatformRemapInfo(Kind);
}
static std::optional<clang::VersionTuple>
getRemappedIntroducedVersionForFallbackPlatform(
const ASTContext &Ctx, const llvm::VersionTuple &Version) {
const auto *Mapping = getFallbackVersionMapping(
Ctx, clang::DarwinSDKInfo::OSEnvPair(
llvm::Triple::IOS, llvm::Triple::UnknownEnvironment,
llvm::Triple::XROS, llvm::Triple::UnknownEnvironment));
if (!Mapping)
return std::nullopt;
return Mapping->mapIntroducedAvailabilityVersion(Version);
}
static std::optional<clang::VersionTuple>
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(
const ASTContext &Ctx, const llvm::VersionTuple &Version) {
const auto *Mapping = getFallbackVersionMapping(
Ctx, clang::DarwinSDKInfo::OSEnvPair(
llvm::Triple::IOS, llvm::Triple::UnknownEnvironment,
llvm::Triple::XROS, llvm::Triple::UnknownEnvironment));
if (!Mapping)
return std::nullopt;
return Mapping->mapDeprecatedObsoletedAvailabilityVersion(Version);
}
bool AvailabilityInference::updateIntroducedAvailabilityDomainForFallback(
const SemanticAvailableAttr &attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer) {
std::optional<llvm::VersionTuple> introducedVersion = attr.getIntroduced();
if (!introducedVersion.has_value())
return false;
bool hasRemap = false;
auto remappedDomain = attr.getDomain().getRemappedDomain(ctx, hasRemap);
if (!hasRemap)
return false;
auto potentiallyRemappedIntroducedVersion =
getRemappedIntroducedVersionForFallbackPlatform(ctx, *introducedVersion);
if (potentiallyRemappedIntroducedVersion.has_value()) {
domain = remappedDomain;
platformVer = potentiallyRemappedIntroducedVersion.value();
return true;
}
return false;
}
bool AvailabilityInference::updateDeprecatedAvailabilityDomainForFallback(
const SemanticAvailableAttr &attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer) {
std::optional<llvm::VersionTuple> deprecatedVersion = attr.getDeprecated();
if (!deprecatedVersion.has_value())
return false;
bool hasRemap = false;
auto remappedDomain = attr.getDomain().getRemappedDomain(ctx, hasRemap);
if (!hasRemap)
return false;
auto potentiallyRemappedDeprecatedVersion =
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(
ctx, *deprecatedVersion);
if (potentiallyRemappedDeprecatedVersion.has_value()) {
domain = remappedDomain;
platformVer = potentiallyRemappedDeprecatedVersion.value();
return true;
}
return false;
}
bool AvailabilityInference::updateObsoletedAvailabilityDomainForFallback(
const SemanticAvailableAttr &attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer) {
std::optional<llvm::VersionTuple> obsoletedVersion = attr.getObsoleted();
if (!obsoletedVersion.has_value())
return false;
bool hasRemap = false;
auto remappedDomain = attr.getDomain().getRemappedDomain(ctx, hasRemap);
if (!hasRemap)
return false;
auto potentiallyRemappedObsoletedVersion =
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(
ctx, *obsoletedVersion);
if (potentiallyRemappedObsoletedVersion.has_value()) {
domain = remappedDomain;
platformVer = potentiallyRemappedObsoletedVersion.value();
return true;
}
return false;
}
bool AvailabilityInference::updateBeforeAvailabilityDomainForFallback(
const BackDeployedAttr *attr, const ASTContext &ctx,
AvailabilityDomain &domain, llvm::VersionTuple &platformVer) {
bool hasRemap = false;
auto remappedDomain =
attr->getAvailabilityDomain().getRemappedDomain(ctx, hasRemap);
if (!hasRemap)
return false;
auto beforeVersion = attr->getVersion();
auto potentiallyRemappedIntroducedVersion =
getRemappedIntroducedVersionForFallbackPlatform(ctx, beforeVersion);
if (potentiallyRemappedIntroducedVersion.has_value()) {
domain = remappedDomain;
platformVer = potentiallyRemappedIntroducedVersion.value();
return true;
}
return false;
}
static std::optional<SemanticAvailableAttr>
getDeclAvailableAttrForPlatformIntroduction(const Decl *D) {
std::optional<SemanticAvailableAttr> bestAvailAttr;
@@ -917,40 +795,31 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {
std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getIntroducedDomainAndRange(
const ASTContext &Ctx) const {
auto *attr = getParsedAttr();
auto domain = getDomain();
if (domain.isUniversal())
return std::nullopt;
if (!attr->getRawIntroduced().has_value()) {
// For versioned domains, an "introduced:" version is always required to
// indicate introduction.
if (domain.isVersioned())
return std::nullopt;
if (auto introduced = getIntroduced())
return domain.getRemappedDomainAndRange(
*introduced, AvailabilityVersionKind::Introduced, Ctx);
// 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;
}
// 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;
}
llvm::VersionTuple introducedVersion = getIntroduced().value();
llvm::VersionTuple remappedVersion;
if (AvailabilityInference::updateIntroducedAvailabilityDomainForFallback(
*this, Ctx, domain, remappedVersion))
introducedVersion = remappedVersion;
return AvailabilityDomainAndRange(domain,
AvailabilityRange{introducedVersion});
}
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getDeprecated() const {
@@ -962,28 +831,18 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getDeprecated() const {
std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getDeprecatedDomainAndRange(
const ASTContext &Ctx) const {
auto *attr = getParsedAttr();
AvailabilityDomain domain = getDomain();
if (auto deprecated = getDeprecated())
return getDomain().getRemappedDomainAndRange(
*deprecated, AvailabilityVersionKind::Deprecated, Ctx);
if (!attr->getRawDeprecated().has_value()) {
// Regardless of the whether the domain supports versions or not, an
// unconditional deprecation attribute indicates the decl is always
// deprecated.
if (isUnconditionallyDeprecated())
return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx),
AvailabilityRange::alwaysAvailable());
// 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;
}
llvm::VersionTuple deprecatedVersion = getDeprecated().value();
llvm::VersionTuple remappedVersion;
if (AvailabilityInference::updateDeprecatedAvailabilityDomainForFallback(
*this, Ctx, domain, remappedVersion))
deprecatedVersion = remappedVersion;
return AvailabilityDomainAndRange(domain,
AvailabilityRange{deprecatedVersion});
return std::nullopt;
}
std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
@@ -994,26 +853,16 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getObsoletedDomainAndRange(const ASTContext &Ctx) const {
auto *attr = getParsedAttr();
AvailabilityDomain domain = getDomain();
if (auto obsoleted = getObsoleted())
return getDomain().getRemappedDomainAndRange(
*obsoleted, AvailabilityVersionKind::Obsoleted, Ctx);
if (!attr->getRawObsoleted().has_value()) {
// An "unavailable" attribute effectively means obsolete in all versions.
if (attr->isUnconditionallyUnavailable())
return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx),
AvailabilityRange::alwaysAvailable());
// An "unavailable" attribute effectively means obsolete in all versions.
if (isUnconditionallyUnavailable())
return AvailabilityDomainAndRange(getDomain().getRemappedDomain(Ctx),
AvailabilityRange::alwaysAvailable());
return std::nullopt;
}
llvm::VersionTuple obsoletedVersion = getObsoleted().value();
llvm::VersionTuple remappedVersion;
if (AvailabilityInference::updateObsoletedAvailabilityDomainForFallback(
*this, Ctx, domain, remappedVersion))
obsoletedVersion = remappedVersion;
return AvailabilityDomainAndRange(domain,
AvailabilityRange{obsoletedVersion});
return std::nullopt;
}
namespace {

View File

@@ -400,16 +400,73 @@ AvailabilityDomain AvailabilityDomain::getRootDomain() const {
return *this;
}
const AvailabilityDomain
AvailabilityDomain::getRemappedDomain(const ASTContext &ctx,
bool &didRemap) const {
std::optional<AvailabilityDomain>
AvailabilityDomain::getRemappedDomainOrNull(const ASTContext &ctx) const {
if (getPlatformKind() == PlatformKind::iOS &&
isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) {
didRemap = true;
isPlatformActive(PlatformKind::visionOS, ctx.LangOpts))
return AvailabilityDomain::forPlatform(PlatformKind::visionOS);
return std::nullopt;
}
static const clang::DarwinSDKInfo::RelatedTargetVersionMapping *
getFallbackVersionMapping(const ASTContext &Ctx,
clang::DarwinSDKInfo::OSEnvPair Kind) {
auto *SDKInfo = Ctx.getDarwinSDKInfo();
if (SDKInfo)
return SDKInfo->getVersionMapping(Kind);
return Ctx.getAuxiliaryDarwinPlatformRemapInfo(Kind);
}
static std::optional<clang::VersionTuple>
getRemappedIntroducedVersionForFallbackPlatform(
const ASTContext &Ctx, const llvm::VersionTuple &Version) {
const auto *Mapping = getFallbackVersionMapping(
Ctx, clang::DarwinSDKInfo::OSEnvPair(
llvm::Triple::IOS, llvm::Triple::UnknownEnvironment,
llvm::Triple::XROS, llvm::Triple::UnknownEnvironment));
if (!Mapping)
return std::nullopt;
return Mapping->mapIntroducedAvailabilityVersion(Version);
}
static std::optional<clang::VersionTuple>
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(
const ASTContext &Ctx, const llvm::VersionTuple &Version) {
const auto *Mapping = getFallbackVersionMapping(
Ctx, clang::DarwinSDKInfo::OSEnvPair(
llvm::Triple::IOS, llvm::Triple::UnknownEnvironment,
llvm::Triple::XROS, llvm::Triple::UnknownEnvironment));
if (!Mapping)
return std::nullopt;
return Mapping->mapDeprecatedObsoletedAvailabilityVersion(Version);
}
AvailabilityDomainAndRange AvailabilityDomain::getRemappedDomainAndRange(
const llvm::VersionTuple &version, AvailabilityVersionKind versionKind,
const ASTContext &ctx) const {
auto remappedDomain = getRemappedDomainOrNull(ctx);
if (!remappedDomain)
return {*this, AvailabilityRange{version}};
std::optional<clang::VersionTuple> remappedVersion;
switch (versionKind) {
case AvailabilityVersionKind::Introduced:
remappedVersion =
getRemappedIntroducedVersionForFallbackPlatform(ctx, version);
break;
case AvailabilityVersionKind::Deprecated:
case AvailabilityVersionKind::Obsoleted:
remappedVersion =
getRemappedDeprecatedObsoletedVersionForFallbackPlatform(ctx, version);
break;
}
return *this;
if (!remappedVersion)
return {*this, AvailabilityRange{version}};
return {*remappedDomain, AvailabilityRange{*remappedVersion}};
}
bool IsCustomAvailabilityDomainPermanentlyEnabled::evaluate(

View File

@@ -600,17 +600,18 @@ std::optional<std::pair<const BackDeployedAttr *, AvailabilityRange>>
Decl::getBackDeployedAttrAndRange(bool forTargetVariant) const {
auto &ctx = getASTContext();
if (auto *attr = getAttrs().getBackDeployed(ctx, forTargetVariant)) {
auto version = attr->getVersion();
AvailabilityDomain ignoredDomain;
AvailabilityInference::updateBeforeAvailabilityDomainForFallback(
attr, ctx, ignoredDomain, version);
auto remappedDomainAndRange =
attr->getAvailabilityDomain().getRemappedDomainAndRange(
attr->getVersion(), AvailabilityVersionKind::Introduced, ctx);
// If the remap for fallback resulted in 1.0, then the
// backdeployment prior to that is not meaningful.
if (version == llvm::VersionTuple(1, 0, 0, 0))
// If the before version was remapped to '1.0', then this decl has
// effectively always been available on the current platform and does not
// qualify as back deployed.
if (remappedDomainAndRange.getRange().getRawMinimumVersion() ==
llvm::VersionTuple(1, 0, 0, 0))
return std::nullopt;
return std::make_pair(attr, AvailabilityRange(version));
return std::make_pair(attr, remappedDomainAndRange.getRange());
}
// Accessors may inherit `@backDeployed`.

View File

@@ -27,7 +27,6 @@
#include "swift/AST/ASTVisitor.h"
#include "swift/AST/Attr.h"
#include "swift/AST/AttrKind.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/ClangModuleLoader.h"
#include "swift/AST/ConformanceLookup.h"
#include "swift/AST/Decl.h"
@@ -5366,14 +5365,8 @@ void AttributeChecker::checkBackDeployedAttrs(
// Unavailable decls cannot be back deployed.
if (availability.containsUnavailableDomain(Domain)) {
auto domainForDiagnostics = Domain;
llvm::VersionTuple ignoredVersion;
AvailabilityInference::updateBeforeAvailabilityDomainForFallback(
Attr, Ctx, domainForDiagnostics, ignoredVersion);
diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr, VD,
domainForDiagnostics);
Domain.getRemappedDomain(Ctx));
// Find the attribute that makes the declaration unavailable.
const Decl *attrDecl = D;
@@ -5396,23 +5389,22 @@ void AttributeChecker::checkBackDeployedAttrs(
// fallback could never be executed at runtime.
if (auto availableRangeAttrPair =
getSemanticAvailableRangeDeclAndAttr(VD, Domain)) {
auto beforeDomain = Domain;
auto beforeVersion = Attr->getVersion();
auto availableAttr = availableRangeAttrPair.value().first;
auto introVersion = availableAttr.getIntroduced().value();
AvailabilityDomain introDomain = availableAttr.getDomain();
auto availableAttr = availableRangeAttrPair->first;
auto introDomainAndRange = availableAttr.getIntroducedDomainAndRange(Ctx);
if (!introDomainAndRange)
continue;
AvailabilityInference::updateBeforeAvailabilityDomainForFallback(
Attr, Ctx, beforeDomain, beforeVersion);
AvailabilityInference::updateIntroducedAvailabilityDomainForFallback(
availableAttr, Ctx, introDomain, introVersion);
auto beforeDomainAndRange = Domain.getRemappedDomainAndRange(
Attr->getVersion(), AvailabilityVersionKind::Introduced, Ctx);
if (beforeVersion <= introVersion) {
auto introRange = introDomainAndRange->getRange();
auto beforeRange = beforeDomainAndRange.getRange();
if (introRange.isContainedIn(beforeRange)) {
diagnose(AtLoc, diag::attr_has_no_effect_decl_not_available_before,
Attr, VD, beforeDomain, AvailabilityRange(beforeVersion));
Attr, VD, beforeDomainAndRange.getDomain(), beforeRange);
diagnose(availableAttr.getParsedAttr()->AtLoc,
diag::availability_introduced_in_version, VD, introDomain,
AvailabilityRange(introVersion))
diag::availability_introduced_in_version, VD,
introDomainAndRange->getDomain(), introRange)
.highlight(availableAttr.getParsedAttr()->getRange());
continue;
}