From f4b4dc9889f1b2a665ec3d850064a769ab747d56 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Mon, 2 Jun 2025 11:20:58 -0700 Subject: [PATCH] AST/Sema: Fix remapping of iOS availability in diagnostics for visionOS. When compiling for visionOS, iOS availability attributes are remapped into the visionOS availability domain automatically. While the version remapping was being performed correctly, there was a regression that caused the platform name to be printed incorrectly in many diagnostics. Whenever an iOS version is remapped to a visionOS version, availability diagnostics will now present those versions as visionOS versions instead of iOS versions. Resolves rdar://146293165. --- include/swift/AST/Attr.h | 66 ++++++-- include/swift/AST/AvailabilityConstraint.h | 8 +- include/swift/AST/AvailabilityDomain.h | 28 ++++ include/swift/AST/AvailabilityInference.h | 5 - lib/AST/Availability.cpp | 154 ++++++++++-------- lib/AST/AvailabilityConstraint.cpp | 14 +- lib/AST/AvailabilityDomain.cpp | 12 ++ .../DerivedConformanceRawRepresentable.cpp | 10 +- lib/Sema/TypeCheckAvailability.cpp | 48 +++--- ...ilability_ios_to_visionos_decl_remap.swift | 106 ++++++++++++ 10 files changed, 317 insertions(+), 134 deletions(-) create mode 100644 test/attr/attr_availability_ios_to_visionos_decl_remap.swift diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 9f7b04e17d5d4..0e0bdb693cb51 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -3757,13 +3757,23 @@ class SemanticAvailableAttr final { /// The source range of the `introduced:` version component. SourceRange getIntroducedSourceRange() const { return attr->IntroducedRange; } - /// Returns the effective introduction range indicated by this attribute. - /// This may correspond to the version specified by the `introduced:` - /// component (remapped or canonicalized if necessary) or it may be "always" - /// for an attribute indicating availability in a version-less domain. Returns - /// `std::nullopt` if the attribute does not indicate introduction. + /// See `getIntroducedDomainAndRange()`. std::optional - getIntroducedRange(const ASTContext &Ctx) const; + getIntroducedRange(const ASTContext &ctx) const { + if (auto domainAndRange = getIntroducedDomainAndRange(ctx)) + return domainAndRange->getRange(); + return std::nullopt; + } + + /// Returns the effective introduction range indicated by this attribute, + /// along with the domain that it applies to (which may be different than the + /// domain which the attribute was written with if a remap is required). This + /// may correspond to the version specified by the `introduced:` component + /// (remapped or canonicalized if necessary) or it may be "always" for an + /// attribute indicating availability in a version-less domain. Returns + /// `std::nullopt` if the attribute does not indicate introduction. + std::optional + getIntroducedDomainAndRange(const ASTContext &ctx) const; /// The version tuple for the `deprecated:` component. std::optional getDeprecated() const; @@ -3771,13 +3781,23 @@ class SemanticAvailableAttr final { /// The source range of the `deprecated:` version component. SourceRange getDeprecatedSourceRange() const { return attr->DeprecatedRange; } - /// Returns the effective deprecation range indicated by this attribute. - /// This may correspond to the version specified by the `deprecated:` - /// component (remapped or canonicalized if necessary) or it may be "always" - /// for an unconditional deprecation attribute. Returns `std::nullopt` if the - /// attribute does not indicate deprecation. + /// See `getDeprecatedDomainAndRange()`. std::optional - getDeprecatedRange(const ASTContext &Ctx) const; + getDeprecatedRange(const ASTContext &ctx) const { + if (auto domainAndRange = getDeprecatedDomainAndRange(ctx)) + return domainAndRange->getRange(); + return std::nullopt; + } + + /// Returns the effective deprecation range indicated by this attribute, along + /// with the domain that it applies to (which may be different than the domain + /// which the attribute was written with if a remap is required). This may + /// correspond to the version specified by the `deprecated:` component + /// (remapped or canonicalized if necessary) or it may be "always" for an + /// unconditional deprecation attribute. Returns `std::nullopt` if the + /// attribute does not indicate deprecation. + std::optional + getDeprecatedDomainAndRange(const ASTContext &ctx) const; /// The version tuple for the `obsoleted:` component. std::optional getObsoleted() const; @@ -3785,13 +3805,23 @@ class SemanticAvailableAttr final { /// The source range of the `obsoleted:` version component. SourceRange getObsoletedSourceRange() const { return attr->ObsoletedRange; } - /// Returns the effective obsoletion range indicated by this attribute. - /// This always corresponds to the version specified by the `obsoleted:` - /// component (remapped or canonicalized if necessary). Returns `std::nullopt` - /// if the attribute does not indicate obsoletion (note that unavailability is - /// separate from obsoletion. + /// See `getObsoletedDomainAndRange()`. std::optional - getObsoletedRange(const ASTContext &Ctx) const; + getObsoletedRange(const ASTContext &ctx) const { + if (auto domainAndRange = getObsoletedDomainAndRange(ctx)) + return domainAndRange->getRange(); + return std::nullopt; + } + + /// Returns the effective obsoletion range indicated by this attribute, along + /// with the domain that it applies to (which may be different than the domain + /// which the attribute was written with if a remap is required). This always + /// corresponds to the version specified by the `obsoleted:` component + /// (remapped or canonicalized if necessary). Returns `std::nullopt` if the + /// attribute does not indicate obsoletion (note that unavailability is + /// separate from obsoletion. + std::optional + getObsoletedDomainAndRange(const ASTContext &ctx) const; /// Returns the `message:` field of the attribute, or an empty string. StringRef getMessage() const { return attr->Message; } diff --git a/include/swift/AST/AvailabilityConstraint.h b/include/swift/AST/AvailabilityConstraint.h index ba9e70230dfc1..3505a76bb7c47 100644 --- a/include/swift/AST/AvailabilityConstraint.h +++ b/include/swift/AST/AvailabilityConstraint.h @@ -132,10 +132,10 @@ class AvailabilityConstraint { /// Returns the domain that the constraint applies to. AvailabilityDomain getDomain() const { return getAttr().getDomain(); } - /// Returns the required range for `IntroducedInNewerVersion` requirements, or - /// `std::nullopt` otherwise. - std::optional - getPotentiallyUnavailableRange(const ASTContext &ctx) const; + /// Returns the domain and range (remapped if necessary) in which the + /// constraint must be satisfied. How the range should be interpreted depends + /// on the reason for the constraint. + AvailabilityDomainAndRange getDomainAndRange(const ASTContext &ctx) const; /// Some availability constraints are active for type-checking but cannot /// be translated directly into an `if #available(...)` runtime query. diff --git a/include/swift/AST/AvailabilityDomain.h b/include/swift/AST/AvailabilityDomain.h index 06e5ee3cc54ea..87b5fb46c429a 100644 --- a/include/swift/AST/AvailabilityDomain.h +++ b/include/swift/AST/AvailabilityDomain.h @@ -263,6 +263,20 @@ class AvailabilityDomain final { /// 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); + } + bool operator==(const AvailabilityDomain &other) const { return storage.getOpaqueValue() == other.storage.getOpaqueValue(); } @@ -420,6 +434,20 @@ class AvailabilityDomainOrIdentifier { void print(llvm::raw_ostream &os) const; }; +/// Represents an `AvailabilityRange` paired with the `AvailabilityDomain` that +/// the range applies to. +class AvailabilityDomainAndRange { + AvailabilityDomain domain; + AvailabilityRange range; + +public: + AvailabilityDomainAndRange(AvailabilityDomain domain, AvailabilityRange range) + : domain(domain), range(range) {}; + + AvailabilityDomain getDomain() const { return domain; } + AvailabilityRange getRange() const { return range; } +}; + } // end namespace swift namespace llvm { diff --git a/include/swift/AST/AvailabilityInference.h b/include/swift/AST/AvailabilityInference.h index 86dd46657c672..7ab2c5c92907d 100644 --- a/include/swift/AST/AvailabilityInference.h +++ b/include/swift/AST/AvailabilityInference.h @@ -81,11 +81,6 @@ class AvailabilityInference { const SemanticAvailableAttr &attr, const ASTContext &ctx, AvailabilityDomain &domain, llvm::VersionTuple &platformVer); - static void - updateAvailabilityDomainForFallback(const SemanticAvailableAttr &attr, - const ASTContext &ctx, - AvailabilityDomain &domain); - /// 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. diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 1098553821fa4..a502cb76934d8 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -297,18 +297,20 @@ bool AvailabilityInference::updateIntroducedAvailabilityDomainForFallback( const SemanticAvailableAttr &attr, const ASTContext &ctx, AvailabilityDomain &domain, llvm::VersionTuple &platformVer) { std::optional introducedVersion = attr.getIntroduced(); - if (attr.getPlatform() == PlatformKind::iOS && - introducedVersion.has_value() && - isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) { - // We re-map the iOS introduced version to the corresponding visionOS version - auto potentiallyRemappedIntroducedVersion = - getRemappedIntroducedVersionForFallbackPlatform(ctx, - *introducedVersion); - if (potentiallyRemappedIntroducedVersion.has_value()) { - domain = AvailabilityDomain::forPlatform(PlatformKind::visionOS); - platformVer = potentiallyRemappedIntroducedVersion.value(); - return true; - } + 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; } @@ -317,18 +319,21 @@ bool AvailabilityInference::updateDeprecatedAvailabilityDomainForFallback( const SemanticAvailableAttr &attr, const ASTContext &ctx, AvailabilityDomain &domain, llvm::VersionTuple &platformVer) { std::optional deprecatedVersion = attr.getDeprecated(); - if (attr.getPlatform() == PlatformKind::iOS && - deprecatedVersion.has_value() && - isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) { - // We re-map the iOS deprecated version to the corresponding visionOS version - auto potentiallyRemappedDeprecatedVersion = - getRemappedDeprecatedObsoletedVersionForFallbackPlatform( - ctx, *deprecatedVersion); - if (potentiallyRemappedDeprecatedVersion.has_value()) { - domain = AvailabilityDomain::forPlatform(PlatformKind::visionOS); - platformVer = potentiallyRemappedDeprecatedVersion.value(); - return true; - } + 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; } @@ -337,44 +342,40 @@ bool AvailabilityInference::updateObsoletedAvailabilityDomainForFallback( const SemanticAvailableAttr &attr, const ASTContext &ctx, AvailabilityDomain &domain, llvm::VersionTuple &platformVer) { std::optional obsoletedVersion = attr.getObsoleted(); - if (attr.getPlatform() == PlatformKind::iOS && obsoletedVersion.has_value() && - isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) { - // We re-map the iOS obsoleted version to the corresponding visionOS version - auto potentiallyRemappedObsoletedVersion = - getRemappedDeprecatedObsoletedVersionForFallbackPlatform( - ctx, *obsoletedVersion); - if (potentiallyRemappedObsoletedVersion.has_value()) { - domain = AvailabilityDomain::forPlatform(PlatformKind::visionOS); - platformVer = potentiallyRemappedObsoletedVersion.value(); - return true; - } - } - return false; -} + if (!obsoletedVersion.has_value()) + return false; -void AvailabilityInference::updateAvailabilityDomainForFallback( - const SemanticAvailableAttr &attr, const ASTContext &Ctx, - AvailabilityDomain &domain) { - if (attr.getPlatform() == PlatformKind::iOS && - isPlatformActive(PlatformKind::visionOS, Ctx.LangOpts)) { - domain = AvailabilityDomain::forPlatform(PlatformKind::visionOS); + 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 = domain.getRemappedDomain(ctx, hasRemap); + if (!hasRemap) + return false; + auto beforeVersion = attr->Version; - if (attr->Platform == PlatformKind::iOS && - isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) { - // We re-map the iOS before version to the corresponding visionOS version - auto PotentiallyRemappedIntroducedVersion = - getRemappedIntroducedVersionForFallbackPlatform(ctx, beforeVersion); - if (PotentiallyRemappedIntroducedVersion.has_value()) { - domain = AvailabilityDomain::forPlatform(PlatformKind::visionOS); - platformVer = PotentiallyRemappedIntroducedVersion.value(); - return true; - } + auto potentiallyRemappedIntroducedVersion = + getRemappedIntroducedVersionForFallbackPlatform(ctx, beforeVersion); + if (potentiallyRemappedIntroducedVersion.has_value()) { + domain = AvailabilityDomain::forPlatform(PlatformKind::visionOS); + platformVer = potentiallyRemappedIntroducedVersion.value(); + return true; } return false; } @@ -913,13 +914,16 @@ std::optional SemanticAvailableAttr::getIntroduced() const { return std::nullopt; } -std::optional -SemanticAvailableAttr::getIntroducedRange(const ASTContext &Ctx) const { +std::optional +SemanticAvailableAttr::getIntroducedDomainAndRange( + const ASTContext &Ctx) const { auto *attr = getParsedAttr(); + auto domain = getDomain(); + if (!attr->getRawIntroduced().has_value()) { // For versioned domains, an "introduced:" version is always required to // indicate introduction. - if (getDomain().isVersioned()) + if (domain.isVersioned()) return std::nullopt; // For version-less domains, an attribute that does not indicate some other @@ -927,7 +931,8 @@ SemanticAvailableAttr::getIntroducedRange(const ASTContext &Ctx) const { // the decl is available in all versions of the domain. switch (attr->getKind()) { case AvailableAttr::Kind::Default: - return AvailabilityRange::alwaysAvailable(); + return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx), + AvailabilityRange::alwaysAvailable()); case AvailableAttr::Kind::Deprecated: case AvailableAttr::Kind::Unavailable: case AvailableAttr::Kind::NoAsync: @@ -936,13 +941,13 @@ SemanticAvailableAttr::getIntroducedRange(const ASTContext &Ctx) const { } llvm::VersionTuple introducedVersion = getIntroduced().value(); - AvailabilityDomain unusedDomain; llvm::VersionTuple remappedVersion; if (AvailabilityInference::updateIntroducedAvailabilityDomainForFallback( - *this, Ctx, unusedDomain, remappedVersion)) + *this, Ctx, domain, remappedVersion)) introducedVersion = remappedVersion; - return AvailabilityRange{introducedVersion}; + return AvailabilityDomainAndRange(domain, + AvailabilityRange{introducedVersion}); } std::optional SemanticAvailableAttr::getDeprecated() const { @@ -951,27 +956,31 @@ std::optional SemanticAvailableAttr::getDeprecated() const { return std::nullopt; } -std::optional -SemanticAvailableAttr::getDeprecatedRange(const ASTContext &Ctx) const { +std::optional +SemanticAvailableAttr::getDeprecatedDomainAndRange( + const ASTContext &Ctx) const { auto *attr = getParsedAttr(); + AvailabilityDomain domain = getDomain(); + 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 AvailabilityRange::alwaysAvailable(); + return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx), + AvailabilityRange::alwaysAvailable()); return std::nullopt; } llvm::VersionTuple deprecatedVersion = getDeprecated().value(); - AvailabilityDomain unusedDomain; llvm::VersionTuple remappedVersion; if (AvailabilityInference::updateDeprecatedAvailabilityDomainForFallback( - *this, Ctx, unusedDomain, remappedVersion)) + *this, Ctx, domain, remappedVersion)) deprecatedVersion = remappedVersion; - return AvailabilityRange{deprecatedVersion}; + return AvailabilityDomainAndRange(domain, + AvailabilityRange{deprecatedVersion}); } std::optional SemanticAvailableAttr::getObsoleted() const { @@ -980,8 +989,8 @@ std::optional SemanticAvailableAttr::getObsoleted() const { return std::nullopt; } -std::optional -SemanticAvailableAttr::getObsoletedRange(const ASTContext &Ctx) const { +std::optional +SemanticAvailableAttr::getObsoletedDomainAndRange(const ASTContext &Ctx) const { auto *attr = getParsedAttr(); // Obsoletion always requires a version. @@ -989,13 +998,14 @@ SemanticAvailableAttr::getObsoletedRange(const ASTContext &Ctx) const { return std::nullopt; llvm::VersionTuple obsoletedVersion = getObsoleted().value(); - AvailabilityDomain unusedDomain; + AvailabilityDomain domain = getDomain(); llvm::VersionTuple remappedVersion; if (AvailabilityInference::updateObsoletedAvailabilityDomainForFallback( - *this, Ctx, unusedDomain, remappedVersion)) + *this, Ctx, domain, remappedVersion)) obsoletedVersion = remappedVersion; - return AvailabilityRange{obsoletedVersion}; + return AvailabilityDomainAndRange(domain, + AvailabilityRange{obsoletedVersion}); } namespace { diff --git a/lib/AST/AvailabilityConstraint.cpp b/lib/AST/AvailabilityConstraint.cpp index 5377057ad94ec..02fe22fe7f71b 100644 --- a/lib/AST/AvailabilityConstraint.cpp +++ b/lib/AST/AvailabilityConstraint.cpp @@ -18,16 +18,20 @@ using namespace swift; -std::optional -AvailabilityConstraint::getPotentiallyUnavailableRange( - const ASTContext &ctx) const { +AvailabilityDomainAndRange +AvailabilityConstraint::getDomainAndRange(const ASTContext &ctx) const { switch (getReason()) { case Reason::UnconditionallyUnavailable: + // Technically, unconditional unavailability doesn't have an associated + // range. However, if you view it as a special case of obsoletion, then an + // unconditionally unavailable declaration is "always obsoleted." + return AvailabilityDomainAndRange(getDomain().getRemappedDomain(ctx), + AvailabilityRange::alwaysAvailable()); case Reason::Obsoleted: + return getAttr().getObsoletedDomainAndRange(ctx).value(); case Reason::UnavailableForDeployment: - return std::nullopt; case Reason::PotentiallyUnavailable: - return getAttr().getIntroducedRange(ctx); + return getAttr().getIntroducedDomainAndRange(ctx).value(); } } diff --git a/lib/AST/AvailabilityDomain.cpp b/lib/AST/AvailabilityDomain.cpp index d364cea5c931c..96e5d48d07cdd 100644 --- a/lib/AST/AvailabilityDomain.cpp +++ b/lib/AST/AvailabilityDomain.cpp @@ -279,6 +279,18 @@ AvailabilityDomain AvailabilityDomain::getRootDomain() const { return *this; } +const AvailabilityDomain +AvailabilityDomain::getRemappedDomain(const ASTContext &ctx, + bool &didRemap) const { + if (getPlatformKind() == PlatformKind::iOS && + isPlatformActive(PlatformKind::visionOS, ctx.LangOpts)) { + didRemap = true; + return AvailabilityDomain::forPlatform(PlatformKind::visionOS); + } + + return *this; +} + void AvailabilityDomain::print(llvm::raw_ostream &os) const { os << getNameForAttributePrinting(); } diff --git a/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp b/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp index a56b54b79bcf8..a12152bf884b0 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp @@ -260,18 +260,16 @@ checkAvailability(const EnumElementDecl *elt, if (!constraint->isActiveForRuntimeQueries(C)) return true; - auto domain = constraint->getDomain(); + auto domainAndRange = constraint->getDomainAndRange(C); // Only platform version constraints are supported currently. // FIXME: [availability] Support non-platform domain availability checks - if (!domain.isPlatform()) + if (!domainAndRange.getDomain().isPlatform()) return true; // It's conditionally available; create a version constraint and return true. - auto range = constraint->getPotentiallyUnavailableRange(C); - - ASSERT(range); - versionCheck.emplace(domain.getPlatformKind(), range->getRawMinimumVersion()); + versionCheck.emplace(domainAndRange.getDomain().getPlatformKind(), + domainAndRange.getRange().getRawMinimumVersion()); return true; } diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index d8abc8e26cc27..d2df3648c01b3 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -1727,7 +1727,7 @@ bool diagnoseExplicitUnavailability(SourceLoc loc, auto type = rootConf->getType(); auto proto = rootConf->getProtocol()->getDeclaredInterfaceType(); - auto domain = constraint.getDomain(); + auto domainAndRange = constraint.getDomainAndRange(ctx); auto attr = constraint.getAttr(); // Downgrade unavailable Sendable conformance diagnostics where @@ -1738,8 +1738,8 @@ bool diagnoseExplicitUnavailability(SourceLoc loc, EncodedDiagnosticMessage EncodedMessage(attr.getMessage()); diags .diagnose(loc, diag::conformance_availability_unavailable, type, proto, - shouldHideDomainNameForConstraintDiagnostic(constraint), domain, - EncodedMessage.Message) + shouldHideDomainNameForConstraintDiagnostic(constraint), + domainAndRange.getDomain(), EncodedMessage.Message) .limitBehaviorWithPreconcurrency(behavior, preconcurrency) .warnUntilSwiftVersionIf(warnIfConformanceUnavailablePreSwift6, 6); @@ -1752,12 +1752,13 @@ bool diagnoseExplicitUnavailability(SourceLoc loc, break; case AvailabilityConstraint::Reason::UnavailableForDeployment: diags.diagnose(ext, diag::conformance_availability_introduced_in_version, - type, proto, domain, attr.getIntroducedRange(ctx).value()); + type, proto, domainAndRange.getDomain(), + domainAndRange.getRange()); break; case AvailabilityConstraint::Reason::Obsoleted: diags .diagnose(ext, diag::conformance_availability_obsoleted, type, proto, - domain, attr.getObsoletedRange(ctx).value()) + domainAndRange.getDomain(), domainAndRange.getRange()) .highlight(attr.getParsedAttr()->getRange()); break; case AvailabilityConstraint::Reason::PotentiallyUnavailable: @@ -2115,7 +2116,7 @@ bool diagnoseExplicitUnavailability( SourceLoc Loc = R.Start; ASTContext &ctx = D->getASTContext(); auto &diags = ctx.Diags; - auto domain = constraint.getDomain(); + auto domainAndRange = constraint.getDomainAndRange(ctx); // TODO: Consider removing this. // ObjC keypaths components weren't checked previously, so errors are demoted @@ -2151,14 +2152,11 @@ bool diagnoseExplicitUnavailability( // Skip the note emitted below. return true; } else { - auto unavailableDiagnosticDomain = domain; - AvailabilityInference::updateAvailabilityDomainForFallback( - Attr, ctx, unavailableDiagnosticDomain); EncodedDiagnosticMessage EncodedMessage(message); diags .diagnose(Loc, diag::availability_decl_unavailable, D, shouldHideDomainNameForConstraintDiagnostic(constraint), - unavailableDiagnosticDomain, EncodedMessage.Message) + domainAndRange.getDomain(), EncodedMessage.Message) .highlight(R) .limitBehavior(limit); } @@ -2171,14 +2169,14 @@ bool diagnoseExplicitUnavailability( break; case AvailabilityConstraint::Reason::UnavailableForDeployment: diags - .diagnose(D, diag::availability_introduced_in_version, D, domain, - Attr.getIntroducedRange(ctx).value()) + .diagnose(D, diag::availability_introduced_in_version, D, + domainAndRange.getDomain(), domainAndRange.getRange()) .highlight(sourceRange); break; case AvailabilityConstraint::Reason::Obsoleted: diags - .diagnose(D, diag::availability_obsoleted, D, domain, - Attr.getObsoletedRange(ctx).value()) + .diagnose(D, diag::availability_obsoleted, D, + domainAndRange.getDomain(), domainAndRange.getRange()) .highlight(sourceRange); break; case AvailabilityConstraint::Reason::PotentiallyUnavailable: @@ -2891,23 +2889,25 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R, return false; // Diagnose (and possibly signal) for potential unavailability - auto domain = constraint->getDomain(); - auto requiredRange = constraint->getPotentiallyUnavailableRange(ctx); - if (!requiredRange) + if (!constraint->isPotentiallyAvailable()) return false; + auto domainAndRange = constraint->getDomainAndRange(ctx); + auto domain = domainAndRange.getDomain(); + auto requiredRange = domainAndRange.getRange(); + if (Flags.contains( DeclAvailabilityFlag:: AllowPotentiallyUnavailableAtOrBelowDeploymentTarget) && - requiresDeploymentTargetOrEarlier(domain, *requiredRange, ctx)) + requiresDeploymentTargetOrEarlier(domain, requiredRange, ctx)) return false; if (accessor) { bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout); diagnosePotentialAccessorUnavailability(accessor, R, DC, domain, - *requiredRange, forInout); + requiredRange, forInout); } else { - if (!diagnosePotentialUnavailability(D, R, DC, domain, *requiredRange)) + if (!diagnosePotentialUnavailability(D, R, DC, domain, requiredRange)) return false; } @@ -3412,11 +3412,11 @@ swift::diagnoseConformanceAvailability(SourceLoc loc, } // Diagnose (and possibly signal) for potential unavailability - if (auto requiredRange = - constraint->getPotentiallyUnavailableRange(ctx)) { + if (constraint->isPotentiallyAvailable()) { + auto domainAndRange = constraint->getDomainAndRange(ctx); if (diagnosePotentialUnavailability(rootConf, ext, loc, DC, - constraint->getDomain(), - *requiredRange)) { + domainAndRange.getDomain(), + domainAndRange.getRange())) { maybeEmitAssociatedTypeNote(); return true; } diff --git a/test/attr/attr_availability_ios_to_visionos_decl_remap.swift b/test/attr/attr_availability_ios_to_visionos_decl_remap.swift new file mode 100644 index 0000000000000..2e74a0ab58ec1 --- /dev/null +++ b/test/attr/attr_availability_ios_to_visionos_decl_remap.swift @@ -0,0 +1,106 @@ +// RUN: %empty-directory(%t/mock-sdk) +// RUN: cp %S/../Inputs/MockPlatformRemapSDKConfig/SDKSettings.json %t/mock-sdk/SDKSettings.json +// RUN: %swift -typecheck -verify -parse-stdlib -target arm64-apple-xros1.0 %s -sdk %t/mock-sdk + +@available(iOS 17.4, *) +public func doSomething() { } + +@available(iOS 99.0, *) +public func doSomethingFarFuture() { } + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "you don't want to do that anyway") +public func doSomethingElse() { } +// expected-note@-1 3 {{'doSomethingElse()' was obsoleted in visionOS 1.0}} + +@available(iOS, introduced: 16.0, deprecated: 17.0, message: "please don't") +public func doSomethingInadvisable() { } + +@available(iOS 17.0, *) +public func doSomethingGood() { } + +@available(iOS 13.0, *) +public func doSomethingOld() { } + +public func takesSomeProto(_ p: some SomeProto) { } + +public protocol SomeProto { } + +public struct ConformsToProtoIniOS17_4 { } + +@available(iOS 17.4, *) +extension ConformsToProtoIniOS17_4: SomeProto { } + +public struct ConformsToProtoIniOS99 { } + +@available(iOS 99, *) +extension ConformsToProtoIniOS99: SomeProto { } + +public struct ConformsToProtoDeprecatedIniOS17 { } + +@available(iOS, introduced: 16.0, deprecated: 17.0, message: "please don't") +extension ConformsToProtoDeprecatedIniOS17: SomeProto { } + +public struct ConformsToProtoObsoletedIniOS17 { } + +@available(iOS, introduced: 16.0, obsoleted: 17.0, message: "you don't want to do that anyway") +extension ConformsToProtoObsoletedIniOS17: SomeProto { } +// expected-note@-1 {{conformance of 'ConformsToProtoObsoletedIniOS17' to 'SomeProto' was obsoleted in visionOS 1.0}} + + +func testDeploymentTarget() { + // expected-note@-1 6 {{add '@available' attribute to enclosing global function}} + doSomething() // expected-error {{'doSomething()' is only available in visionOS 1.1 or newer}} + // expected-note@-1 {{add 'if #available' version check}}{{3-16=if #available(visionOS 1.1, *) {\n doSomething()\n \} else {\n // Fallback on earlier versions\n \}}} + doSomethingFarFuture() // expected-error {{'doSomethingFarFuture()' is only available in iOS 99.0 or newer}} + // expected-note@-1 {{add 'if #available' version check}}{{3-25=if #available(visionOS 99.0, *) {\n doSomethingFarFuture()\n \} else {\n // Fallback on earlier versions\n \}}} + doSomethingElse() // expected-error{{'doSomethingElse()' is unavailable in visionOS: you don't want to do that anyway}} + doSomethingInadvisable() // expected-warning {{'doSomethingInadvisable()' was deprecated in iOS 1.0: please don't}} + doSomethingGood() + doSomethingOld() + + takesSomeProto(ConformsToProtoIniOS17_4()) // expected-warning {{conformance of 'ConformsToProtoIniOS17_4' to 'SomeProto' is only available in visionOS 1.1 or newer; this is an error in the Swift 6 language mode}} + // expected-note@-1 {{add 'if #available' version check}}{{3-45=if #available(visionOS 1.1, *) {\n takesSomeProto(ConformsToProtoIniOS17_4())\n \} else {\n // Fallback on earlier versions\n \}}} + takesSomeProto(ConformsToProtoIniOS99()) // expected-warning {{conformance of 'ConformsToProtoIniOS99' to 'SomeProto' is only available in iOS 99 or newer; this is an error in the Swift 6 language mode}} + // expected-note@-1 {{add 'if #available' version check}}{{3-43=if #available(visionOS 99, *) {\n takesSomeProto(ConformsToProtoIniOS99())\n \} else {\n // Fallback on earlier versions\n \}}} + takesSomeProto(ConformsToProtoDeprecatedIniOS17()) // expected-warning {{conformance of 'ConformsToProtoDeprecatedIniOS17' to 'SomeProto' was deprecated in iOS 1.0: please don't}} + takesSomeProto(ConformsToProtoObsoletedIniOS17()) // expected-error {{conformance of 'ConformsToProtoObsoletedIniOS17' to 'SomeProto' is unavailable in visionOS: you don't want to do that anyway}} + + if #available(iOS 17.4, *) { + // #available(iOS ...) is not matched when compiling for visionOS + doSomething() // expected-error {{'doSomething()' is only available in visionOS 1.1 or newer}} + // expected-note@-1 {{add 'if #available' version check}}{{5-18=if #available(visionOS 1.1, *) {\n doSomething()\n \} else {\n // Fallback on earlier versions\n \}}} + + takesSomeProto(ConformsToProtoIniOS17_4()) // expected-warning {{conformance of 'ConformsToProtoIniOS17_4' to 'SomeProto' is only available in visionOS 1.1 or newer; this is an error in the Swift 6 language mode}} + // expected-note@-1 {{add 'if #available' version check}}{{5-47=if #available(visionOS 1.1, *) {\n takesSomeProto(ConformsToProtoIniOS17_4())\n \} else {\n // Fallback on earlier versions\n \}}} + + if #available(iOS 17.1, *) { } + } + + if #available(visionOS 2.0, *) { + doSomething() + } +} + +@available(iOS 17.4, *) +func testAfterDeployment_iOS() { + doSomething() + doSomethingElse() // expected-error {{'doSomethingElse()' is unavailable in visionOS: you don't want to do that anyway}} + doSomethingFarFuture() // expected-error {{'doSomethingFarFuture()' is only available in iOS 99.0 or newer}} + // expected-note@-1 {{add 'if #available' version check}} + doSomethingInadvisable() // expected-warning {{'doSomethingInadvisable()' was deprecated in iOS 1.0: please don't}} + doSomethingGood() + doSomethingOld() + + takesSomeProto(ConformsToProtoIniOS17_4()) +} + +@available(visionOS 2.0, *) +func testAfterDeployment_visionOS() { + doSomething() + doSomethingElse() // expected-error{{'doSomethingElse()' is unavailable in visionOS: you don't want to do that anyway}} + doSomethingInadvisable() // expected-warning {{'doSomethingInadvisable()' was deprecated in iOS 1.0: please don't}} + doSomethingGood() + doSomethingOld() + + takesSomeProto(ConformsToProtoIniOS17_4()) +}