diff --git a/include/swift/AST/Availability.h b/include/swift/AST/Availability.h index 81ec3204946e6..ba1715f84a243 100644 --- a/include/swift/AST/Availability.h +++ b/include/swift/AST/Availability.h @@ -350,15 +350,20 @@ class AvailabilityInference { static AvailabilityRange inferForType(Type t); - /// Returns the context where a declaration is available - /// We assume a declaration without an annotation is always available. + /// Returns the range of platform versions in which the decl is available. static AvailabilityRange availableRange(const Decl *D); + /// Returns the range of platform versions in which the decl is available and + /// the attribute which determined this range (which may be `nullptr` if the + /// declaration is always available. + static std::pair + availableRangeAndAttr(const Decl *D); + /// Returns true is the declaration is `@_spi_available`. static bool isAvailableAsSPI(const Decl *D); - /// Returns the availability context for a declaration with the given - /// @available attribute. + /// Returns the range of platform versions in which a declaration with the + /// given `@available` attribute is available. /// /// NOTE: The attribute must be active on the current platform. static AvailabilityRange availableRange(const AvailableAttr *attr, diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index cabd1fbb455f3..33cd1b26e229c 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -580,12 +580,18 @@ static const AvailableAttr *attrForAvailableRange(const Decl *D) { return nullptr; } -AvailabilityRange AvailabilityInference::availableRange(const Decl *D) { - if (auto attr = attrForAvailableRange(D)) - return availableRange(attr, D->getASTContext()); +std::pair +AvailabilityInference::availableRangeAndAttr(const Decl *D) { + if (auto attr = attrForAvailableRange(D)) { + return {availableRange(attr, D->getASTContext()), attr}; + } // Treat unannotated declarations as always available. - return AvailabilityRange::alwaysAvailable(); + return {AvailabilityRange::alwaysAvailable(), nullptr}; +} + +AvailabilityRange AvailabilityInference::availableRange(const Decl *D) { + return availableRangeAndAttr(D).first; } bool AvailabilityInference::isAvailableAsSPI(const Decl *D) { diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 1d0c3e5613faf..8191934f4ed47 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -315,6 +315,19 @@ ExportContext::getExportabilityReason() const { return std::nullopt; } +std::optional +UnmetAvailabilityRequirement::getRequiredNewerAvailabilityRange( + ASTContext &ctx) const { + switch (kind) { + case Kind::AlwaysUnavailable: + case Kind::RequiresVersion: + case Kind::Obsoleted: + return std::nullopt; + case Kind::IntroducedInNewerVersion: + return AvailabilityInference::availableRange(attr, ctx); + } +} + /// Returns the first availability attribute on the declaration that is active /// on the target platform. static const AvailableAttr *getActiveAvailableAttribute(const Decl *D, @@ -345,8 +358,9 @@ static bool computeContainedByDeploymentTarget(TypeRefinementContext *TRC, /// Returns true if the reference or any of its parents is an /// unconditional unavailable declaration for the same platform. static bool isInsideCompatibleUnavailableDeclaration( - const Decl *D, const ExportContext &where, const AvailableAttr *attr) { - auto referencedPlatform = where.getUnavailablePlatformKind(); + const Decl *D, AvailabilityContext availabilityContext, + const AvailableAttr *attr) { + auto referencedPlatform = availabilityContext.getUnavailablePlatformKind(); if (!referencedPlatform) return false; @@ -374,7 +388,7 @@ ExportContext::shouldDiagnoseDeclAsUnavailable(const Decl *D) const { if (!attr) return nullptr; - if (isInsideCompatibleUnavailableDeclaration(D, *this, attr)) + if (isInsideCompatibleUnavailableDeclaration(D, Availability, attr)) return nullptr; return attr; @@ -1496,7 +1510,8 @@ TypeChecker::checkDeclarationAvailability(const Decl *D, // Skip computing potential unavailability if the declaration is explicitly // unavailable and the context is also unavailable. if (const AvailableAttr *Attr = AvailableAttr::isUnavailable(D)) - if (isInsideCompatibleUnavailableDeclaration(D, Where, Attr)) + if (isInsideCompatibleUnavailableDeclaration(D, Where.getAvailability(), + Attr)) return std::nullopt; if (isDeclarationUnavailable(D, Where.getDeclContext(), [&Where] { @@ -3063,6 +3078,51 @@ bool diagnoseExplicitUnavailability( return true; } +std::optional +swift::checkDeclarationAvailability(const Decl *decl, + const DeclContext *declContext, + AvailabilityContext availabilityContext) { + auto &ctx = declContext->getASTContext(); + if (ctx.LangOpts.DisableAvailabilityChecking) + return std::nullopt; + + // Generic parameters are always available. + if (isa(decl)) + return std::nullopt; + + if (auto attr = AvailableAttr::isUnavailable(decl)) { + if (isInsideCompatibleUnavailableDeclaration(decl, availabilityContext, + attr)) + return std::nullopt; + + switch (attr->getVersionAvailability(ctx)) { + case AvailableVersionComparison::Available: + case AvailableVersionComparison::PotentiallyUnavailable: + llvm_unreachable("Decl should be unavailable"); + + case AvailableVersionComparison::Unavailable: + if ((attr->isLanguageVersionSpecific() || + attr->isPackageDescriptionVersionSpecific()) && + attr->Introduced.has_value()) + return UnmetAvailabilityRequirement::forRequiresVersion(attr); + + return UnmetAvailabilityRequirement::forAlwaysUnavailable(attr); + + case AvailableVersionComparison::Obsoleted: + return UnmetAvailabilityRequirement::forObsoleted(attr); + } + } + + // Check whether the declaration is available in a newer platform version. + auto rangeAndAttr = AvailabilityInference::availableRangeAndAttr(decl); + if (!availabilityContext.getPlatformRange().isContainedIn(rangeAndAttr.first)) + return UnmetAvailabilityRequirement::forIntroducedInNewerVersion( + rangeAndAttr.second); + + return std::nullopt; +} + + /// Check if this is a subscript declaration inside String or /// Substring that returns String, and if so return true. bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) { @@ -4055,8 +4115,20 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R, return false; } - if (diagnoseExplicitUnavailability(D, R, Where, call, Flags)) - return true; + auto *DC = Where.getDeclContext(); + auto &ctx = DC->getASTContext(); + auto unmetRequirement = + checkDeclarationAvailability(D, DC, Where.getAvailability()); + auto requiredRange = + unmetRequirement + ? unmetRequirement->getRequiredNewerAvailabilityRange(ctx) + : std::nullopt; + + if (unmetRequirement && !requiredRange) { + // FIXME: diagnoseExplicitUnavailability should take an unmet requirement + if (diagnoseExplicitUnavailability(D, R, Where, call, Flags)) + return true; + } if (diagnoseDeclAsyncAvailability(D, R, call, Where)) return true; @@ -4077,25 +4149,21 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R, return false; // Diagnose (and possibly signal) for potential unavailability - auto maybeUnavail = TypeChecker::checkDeclarationAvailability(D, Where); - if (!maybeUnavail.has_value()) + if (!requiredRange) return false; - auto requiredAvailability = maybeUnavail.value(); - auto *DC = Where.getDeclContext(); - auto &ctx = DC->getASTContext(); if (Flags.contains( DeclAvailabilityFlag:: AllowPotentiallyUnavailableAtOrBelowDeploymentTarget) && - requiresDeploymentTargetOrEarlier(requiredAvailability, ctx)) + requiresDeploymentTargetOrEarlier(*requiredRange, ctx)) return false; if (accessor) { bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout); - diagnosePotentialAccessorUnavailability(accessor, R, DC, - requiredAvailability, forInout); + diagnosePotentialAccessorUnavailability(accessor, R, DC, *requiredRange, + forInout); } else { - if (!diagnosePotentialUnavailability(D, R, DC, requiredAvailability)) + if (!diagnosePotentialUnavailability(D, R, DC, *requiredRange)) return false; } diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 268eb9ba75983..5b3876fff99cd 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -156,6 +156,8 @@ class ExportContext { DeclContext *getDeclContext() const { return DC; } + AvailabilityContext getAvailability() const { return Availability; } + AvailabilityRange getAvailabilityRange() const { return Availability.getPlatformRange(); } @@ -198,6 +200,74 @@ class ExportContext { const AvailableAttr *shouldDiagnoseDeclAsUnavailable(const Decl *decl) const; }; +/// Represents the reason a declaration is considered unavailable in a certain +/// context. +class UnmetAvailabilityRequirement { +public: + enum class Kind { + /// The declaration is referenced in a context in which it is + /// generally unavailable. For example, a reference to a declaration that is + /// unavailable on macOS from a context that may execute on macOS has this + /// unmet requirement. + AlwaysUnavailable, + + /// The declaration is referenced in a context in which it is considered + /// obsolete. For example, a reference to a declaration that is obsolete in + /// macOS 13 from a context that may execute on macOS 13 or later has this + /// unmet requirement. + Obsoleted, + + /// The declaration is only available in a different version. For example, + /// the declaration might only be introduced in the Swift 6 language mode + /// while the module is being compiled in the Swift 5 language mode. + RequiresVersion, + + /// The declaration is referenced in a context that does not have an + /// adequate minimum version constraint. For example, a reference to a + /// declaration that is introduced in macOS 13 from a context that may + /// execute on earlier versions of macOS has this unmet requirement. This + /// kind of unmet requirement can be addressed by tightening the minimum + /// version of the context with `if #available(...)` or by adding or + /// adjusting an `@available` attribute. + IntroducedInNewerVersion, + }; + +private: + Kind kind; + const AvailableAttr *attr; + + UnmetAvailabilityRequirement(Kind kind, const AvailableAttr *attr) + : kind(kind), attr(attr){}; + +public: + static UnmetAvailabilityRequirement + forAlwaysUnavailable(const AvailableAttr *attr) { + return UnmetAvailabilityRequirement(Kind::AlwaysUnavailable, attr); + } + + static UnmetAvailabilityRequirement forObsoleted(const AvailableAttr *attr) { + return UnmetAvailabilityRequirement(Kind::Obsoleted, attr); + } + + static UnmetAvailabilityRequirement + forRequiresVersion(const AvailableAttr *attr) { + return UnmetAvailabilityRequirement(Kind::RequiresVersion, attr); + } + + static UnmetAvailabilityRequirement + forIntroducedInNewerVersion(const AvailableAttr *attr) { + return UnmetAvailabilityRequirement(Kind::IntroducedInNewerVersion, attr); + } + + Kind getKind() const { return kind; } + const AvailableAttr *getAttr() const { return attr; } + + /// Returns the required range for `IntroducedInNewerVersion` requirements, or + /// `std::nullopt` otherwise. + std::optional + getRequiredNewerAvailabilityRange(ASTContext &ctx) const; +}; + /// Check if a declaration is exported as part of a module's external interface. /// This includes public and @usableFromInline decls. bool isExported(const ValueDecl *VD); @@ -248,6 +318,14 @@ bool diagnoseExplicitUnavailability(const ValueDecl *D, SourceRange R, const Expr *call, DeclAvailabilityFlags Flags = std::nullopt); +/// Checks whether a declaration should be considered unavailable when referred +/// to in the given declaration context and availability context and, if so, +/// returns a result that describes the unmet availability requirements. +/// Returns `std::nullopt` if the declaration is available. +std::optional +checkDeclarationAvailability(const Decl *decl, const DeclContext *declContext, + AvailabilityContext availabilityContext); + /// Diagnose uses of the runtime support of the given type, such as /// type metadata and dynamic casting. ///