diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 4f9a699b9dfc8..9d3bf148f8089 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -703,23 +703,6 @@ class SwiftNativeObjCRuntimeBaseAttr : public DeclAttribute { } }; -/// Determine the result of comparing an availability attribute to a specific -/// platform or language version. -enum class AvailableVersionComparison { - /// The entity is guaranteed to be available. - Available, - - /// The entity is never available. - Unavailable, - - /// The entity might be unavailable at runtime, because it was introduced - /// after the requested minimum platform version. - PotentiallyUnavailable, - - /// The entity has been obsoleted. - Obsoleted, -}; - /// Defines the @available attribute. class AvailableAttr : public DeclAttribute { public: @@ -3378,12 +3361,6 @@ class SemanticAvailableAttr final { /// version for PackageDescription version-specific availability. llvm::VersionTuple getActiveVersion(const ASTContext &ctx) const; - /// Compare this attribute's version information against the platform or - /// language version (assuming the this attribute pertains to the active - /// platform). - AvailableVersionComparison - getVersionAvailability(const ASTContext &ctx) const; - /// Returns true if this attribute is considered active in the current /// compilation context. bool isActive(ASTContext &ctx) const; diff --git a/include/swift/AST/AvailabilityConstraint.h b/include/swift/AST/AvailabilityConstraint.h index 6f6a8efbddd9f..b206d14f24b68 100644 --- a/include/swift/AST/AvailabilityConstraint.h +++ b/include/swift/AST/AvailabilityConstraint.h @@ -22,6 +22,7 @@ #include "swift/AST/AvailabilityRange.h" #include "swift/AST/PlatformKind.h" #include "swift/Basic/LLVM.h" +#include "swift/Basic/OptionSet.h" namespace swift { @@ -33,6 +34,10 @@ class Decl; /// certain context. class AvailabilityConstraint { public: + /// The reason that the availability constraint is unsatisfied. + /// + /// NOTE: The order of this enum matters. Reasons are defined in descending + /// priority order. enum class Reason { /// The declaration is referenced in a context in which it is generally /// unavailable. For example, a reference to a declaration that is @@ -133,44 +138,50 @@ class AvailabilityConstraint { /// Returns the required range for `IntroducedInNewerVersion` requirements, or /// `std::nullopt` otherwise. std::optional - getRequiredNewerAvailabilityRange(ASTContext &ctx) const; + getRequiredNewerAvailabilityRange(const ASTContext &ctx) const; /// Some availability constraints are active for type-checking but cannot /// be translated directly into an `if #available(...)` runtime query. - bool isActiveForRuntimeQueries(ASTContext &ctx) const; + bool isActiveForRuntimeQueries(const ASTContext &ctx) const; }; /// Represents a set of availability constraints that restrict use of a -/// declaration in a particular context. +/// declaration in a particular context. There can only be one active constraint +/// for a given `AvailabilityDomain`, but there may be multiple active +/// constraints from separate domains. class DeclAvailabilityConstraints { using Storage = llvm::SmallVector; Storage constraints; public: DeclAvailabilityConstraints() {} + DeclAvailabilityConstraints(const Storage &&constraints) + : constraints(constraints) {} - void addConstraint(const AvailabilityConstraint &constraint) { - constraints.emplace_back(constraint); - } + /// Returns the strongest availability constraint or `std::nullopt` if empty. + std::optional getPrimaryConstraint() const; using const_iterator = Storage::const_iterator; const_iterator begin() const { return constraints.begin(); } const_iterator end() const { return constraints.end(); } }; -/// Returns the `AvailabilityConstraint` that describes how \p attr restricts -/// use of \p decl in \p context or `std::nullopt` if there is no restriction. -std::optional -getAvailabilityConstraintForAttr(const Decl *decl, - const SemanticAvailableAttr &attr, - const AvailabilityContext &context); +enum class AvailabilityConstraintFlag : uint8_t { + /// By default, the availability constraints for the members of extensions + /// include the constraints for `@available` attributes that were written on + /// the enclosing extension, since these members can be referred to without + /// referencing the extension. When this flag is specified, though, only the + /// attributes directly attached to the declaration are considered. + SkipEnclosingExtension = 1 << 0, +}; +using AvailabilityConstraintFlags = OptionSet; /// Returns the set of availability constraints that restrict use of \p decl /// when it is referenced from the given context. In other words, it is the /// collection of of `@available` attributes with unsatisfied conditions. -DeclAvailabilityConstraints -getAvailabilityConstraintsForDecl(const Decl *decl, - const AvailabilityContext &context); +DeclAvailabilityConstraints getAvailabilityConstraintsForDecl( + const Decl *decl, const AvailabilityContext &context, + AvailabilityConstraintFlags flags = std::nullopt); } // end namespace swift #endif diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index f80e6e16c8ac4..faf30e60fd12d 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -2294,50 +2294,6 @@ SemanticAvailableAttr::getActiveVersion(const ASTContext &ctx) const { } } -AvailableVersionComparison -SemanticAvailableAttr::getVersionAvailability(const ASTContext &ctx) const { - - // Unconditional unavailability. - if (attr->isUnconditionallyUnavailable()) - return AvailableVersionComparison::Unavailable; - - llvm::VersionTuple queryVersion = getActiveVersion(ctx); - std::optional ObsoletedVersion = getObsoleted(); - - StringRef ObsoletedPlatform; - llvm::VersionTuple RemappedObsoletedVersion; - if (AvailabilityInference::updateObsoletedPlatformForFallback( - *this, ctx, ObsoletedPlatform, RemappedObsoletedVersion)) - ObsoletedVersion = RemappedObsoletedVersion; - - // If this entity was obsoleted before or at the query platform version, - // consider it obsolete. - if (ObsoletedVersion && *ObsoletedVersion <= queryVersion) - return AvailableVersionComparison::Obsoleted; - - std::optional IntroducedVersion = getIntroduced(); - StringRef IntroducedPlatform; - llvm::VersionTuple RemappedIntroducedVersion; - if (AvailabilityInference::updateIntroducedPlatformForFallback( - *this, ctx, IntroducedPlatform, RemappedIntroducedVersion)) - IntroducedVersion = RemappedIntroducedVersion; - - // If this entity was introduced after the query version and we're doing a - // platform comparison, true availability can only be determined dynamically; - // if we're doing a _language_ version check, the query version is a - // static requirement, so we treat "introduced later" as just plain - // unavailable. - if (IntroducedVersion && *IntroducedVersion > queryVersion) { - if (isSwiftLanguageModeSpecific() || isPackageDescriptionVersionSpecific()) - return AvailableVersionComparison::Unavailable; - else - return AvailableVersionComparison::PotentiallyUnavailable; - } - - // The entity is available. - return AvailableVersionComparison::Available; -} - SpecializeAttr::SpecializeAttr(SourceLoc atLoc, SourceRange range, TrailingWhereClause *clause, bool exported, SpecializationKind kind, diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 208ee716b8430..4cb496d164daf 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -573,49 +573,13 @@ bool Decl::isUnavailableInCurrentSwiftVersion() const { return false; } -std::optional getDeclUnavailableAttr(const Decl *D) { - auto &ctx = D->getASTContext(); - std::optional result; - auto bestActive = D->getActiveAvailableAttrForCurrentPlatform(); - - for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) { - // If this is a platform-specific attribute and it isn't the most - // specific attribute for the current platform, we're done. - if (attr.isPlatformSpecific() && (!bestActive || attr != bestActive)) - continue; - - // Unconditional unavailable. - if (attr.isUnconditionallyUnavailable()) - return attr; - - switch (attr.getVersionAvailability(ctx)) { - case AvailableVersionComparison::Available: - case AvailableVersionComparison::PotentiallyUnavailable: - break; - - case AvailableVersionComparison::Obsoleted: - case AvailableVersionComparison::Unavailable: - result.emplace(attr); - break; - } - } - return result; -} - std::optional Decl::getUnavailableAttr() const { - if (auto attr = getDeclUnavailableAttr(this)) - return attr; - - // If D is an extension member, check if the extension is unavailable. - // - // Skip decls imported from Clang, they could be associated to the wrong - // extension and inherit undesired unavailability. The ClangImporter - // associates Objective-C protocol members to the first category where the - // protocol is directly or indirectly adopted, no matter its availability - // and the availability of other categories. rdar://problem/53956555 - if (!getClangNode()) - if (auto ext = dyn_cast(getDeclContext())) - return ext->getUnavailableAttr(); + auto context = AvailabilityContext::forDeploymentTarget(getASTContext()); + if (auto constraint = getAvailabilityConstraintsForDecl(this, context) + .getPrimaryConstraint()) { + if (constraint->isUnavailable()) + return constraint->getAttr(); + } return std::nullopt; } diff --git a/lib/AST/AvailabilityConstraint.cpp b/lib/AST/AvailabilityConstraint.cpp index 678ea455df63e..11a225062cbee 100644 --- a/lib/AST/AvailabilityConstraint.cpp +++ b/lib/AST/AvailabilityConstraint.cpp @@ -24,7 +24,7 @@ PlatformKind AvailabilityConstraint::getPlatform() const { std::optional AvailabilityConstraint::getRequiredNewerAvailabilityRange( - ASTContext &ctx) const { + const ASTContext &ctx) const { switch (getReason()) { case Reason::UnconditionallyUnavailable: case Reason::Obsoleted: @@ -35,7 +35,8 @@ AvailabilityConstraint::getRequiredNewerAvailabilityRange( } } -bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const { +bool AvailabilityConstraint::isActiveForRuntimeQueries( + const ASTContext &ctx) const { if (getAttr().getPlatform() == PlatformKind::none) return true; @@ -44,6 +45,78 @@ bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const { /*forRuntimeQuery=*/true); } +static bool constraintIsStronger(const AvailabilityConstraint &lhs, + const AvailabilityConstraint &rhs) { + DEBUG_ASSERT(lhs.getDomain() == rhs.getDomain()); + + // If the constraints have matching domains but different reasons, the + // constraint with the lowest reason is "strongest". + if (lhs.getReason() != rhs.getReason()) + return lhs.getReason() < rhs.getReason(); + + switch (lhs.getReason()) { + case AvailabilityConstraint::Reason::UnconditionallyUnavailable: + // Just keep the first. + return false; + + case AvailabilityConstraint::Reason::Obsoleted: + // Pick the earliest obsoleted version. + return *lhs.getAttr().getObsoleted() < *rhs.getAttr().getObsoleted(); + + case AvailabilityConstraint::Reason::IntroducedInLaterVersion: + case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion: + // Pick the latest introduced version. + return *lhs.getAttr().getIntroduced() > *rhs.getAttr().getIntroduced(); + } +} + +void addConstraint(llvm::SmallVector &constraints, + const AvailabilityConstraint &constraint, + const ASTContext &ctx) { + + auto iter = llvm::find_if( + constraints, [&constraint](AvailabilityConstraint &existing) { + return constraint.getDomain() == existing.getDomain(); + }); + + // There's no existing constraint for the same domain so just add it. + if (iter == constraints.end()) { + constraints.emplace_back(constraint); + return; + } + + if (constraintIsStronger(constraint, *iter)) { + constraints.erase(iter); + constraints.emplace_back(constraint); + } +} + +std::optional +DeclAvailabilityConstraints::getPrimaryConstraint() const { + std::optional result; + + auto isStrongerConstraint = [](const AvailabilityConstraint &lhs, + const AvailabilityConstraint &rhs) { + // Constraint reasons are defined in descending order of strength. + if (lhs.getReason() != rhs.getReason()) + return lhs.getReason() < rhs.getReason(); + + // Pick the constraint from the broader domain. + if (lhs.getDomain() != rhs.getDomain()) + return rhs.getDomain().contains(lhs.getDomain()); + + return false; + }; + + // Pick the strongest constraint. + for (auto const &constraint : constraints) { + if (!result || isStrongerConstraint(constraint, *result)) + result.emplace(constraint); + } + + return result; +} + static bool isInsideCompatibleUnavailableDeclaration(const Decl *decl, const SemanticAvailableAttr &attr, @@ -65,13 +138,12 @@ isInsideCompatibleUnavailableDeclaration(const Decl *decl, return context.containsUnavailableDomain(domain); } -std::optional -swift::getAvailabilityConstraintForAttr(const Decl *decl, - const SemanticAvailableAttr &attr, - const AvailabilityContext &context) { - if (isInsideCompatibleUnavailableDeclaration(decl, attr, context)) - return std::nullopt; - +/// Returns the `AvailabilityConstraint` that describes how \p attr restricts +/// use of \p decl in \p context or `std::nullopt` if there is no restriction. +static std::optional +getAvailabilityConstraintForAttr(const Decl *decl, + const SemanticAvailableAttr &attr, + const AvailabilityContext &context) { if (attr.isUnconditionallyUnavailable()) return AvailabilityConstraint::unconditionallyUnavailable(attr); @@ -128,10 +200,10 @@ activePlatformDomainForDecl(const Decl *decl) { return activeDomain; } -static void -getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints, - const Decl *decl, - const AvailabilityContext &context) { +static void getAvailabilityConstraintsForDecl( + llvm::SmallVector &constraints, const Decl *decl, + const AvailabilityContext &context) { + auto &ctx = decl->getASTContext(); auto activePlatformDomain = activePlatformDomainForDecl(decl); for (auto attr : @@ -141,25 +213,36 @@ getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints, !activePlatformDomain->contains(domain)) continue; - if (auto constraint = - swift::getAvailabilityConstraintForAttr(decl, attr, context)) - constraints.addConstraint(*constraint); + if (auto constraint = getAvailabilityConstraintForAttr(decl, attr, context)) + addConstraint(constraints, *constraint, ctx); } + + // After resolving constraints, remove any constraints that indicate the + // declaration is unconditionally unavailable in a domain for which + // the context is already unavailable. + llvm::erase_if(constraints, [&](const AvailabilityConstraint &constraint) { + return isInsideCompatibleUnavailableDeclaration(decl, constraint.getAttr(), + context); + }); } DeclAvailabilityConstraints swift::getAvailabilityConstraintsForDecl(const Decl *decl, - const AvailabilityContext &context) { - DeclAvailabilityConstraints constraints; + const AvailabilityContext &context, + AvailabilityConstraintFlags flags) { + llvm::SmallVector constraints; // Generic parameters are always available. if (isa(decl)) - return constraints; + return DeclAvailabilityConstraints(); decl = abstractSyntaxDeclForAvailableAttribute(decl); getAvailabilityConstraintsForDecl(constraints, decl, context); + if (flags.contains(AvailabilityConstraintFlag::SkipEnclosingExtension)) + return constraints; + // If decl is an extension member, query the attributes of the extension, too. // // Skip decls imported from Clang, though, as they could be associated to the diff --git a/lib/AST/AvailabilityContext.cpp b/lib/AST/AvailabilityContext.cpp index 505756a28f075..956ad09674220 100644 --- a/lib/AST/AvailabilityContext.cpp +++ b/lib/AST/AvailabilityContext.cpp @@ -202,7 +202,10 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange( bool isConstrained = false; Info info{storage->info}; - auto constraints = swift::getAvailabilityConstraintsForDecl(decl, *this); + AvailabilityConstraintFlags flags = + AvailabilityConstraintFlag::SkipEnclosingExtension; + auto constraints = + swift::getAvailabilityConstraintsForDecl(decl, *this, flags); isConstrained |= info.constrainWith(constraints, decl->getASTContext()); isConstrained |= CONSTRAIN_BOOL(info.IsDeprecated, decl->isDeprecated()); isConstrained |= constrainRange(info.Range, platformRange); diff --git a/lib/Sema/DerivedConformanceRawRepresentable.cpp b/lib/Sema/DerivedConformanceRawRepresentable.cpp index 26a02f12c5158..84e1f34ef69a9 100644 --- a/lib/Sema/DerivedConformanceRawRepresentable.cpp +++ b/lib/Sema/DerivedConformanceRawRepresentable.cpp @@ -20,6 +20,7 @@ #include "TypeCheckAvailability.h" #include "TypeCheckDecl.h" #include "TypeChecker.h" +#include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilitySpec.h" #include "swift/AST/Decl.h" #include "swift/AST/Expr.h" @@ -240,8 +241,8 @@ checkAvailability(const EnumElementDecl *elt, AvailabilityContext availabilityContext, std::optional &versionCheck) { auto &C = elt->getASTContext(); - auto constraint = - getUnsatisfiedAvailabilityConstraint(elt, availabilityContext); + auto constraint = getAvailabilityConstraintsForDecl(elt, availabilityContext) + .getPrimaryConstraint(); // Is it always available? if (!constraint) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 7ca4677bbcb1d..c9d6ca4850a31 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -2417,27 +2417,6 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *parsedAttr) { } } - SourceLoc attrLoc = parsedAttr->getLocation(); - auto versionAvailability = attr->getVersionAvailability(Ctx); - if (versionAvailability == AvailableVersionComparison::Obsoleted || - versionAvailability == AvailableVersionComparison::Unavailable) { - if (auto cannotBeUnavailable = - TypeChecker::diagnosticIfDeclCannotBeUnavailable(D)) { - diagnose(attrLoc, cannotBeUnavailable.value()); - return; - } - - if (auto *PD = dyn_cast(DC)) { - if (auto *VD = dyn_cast(D)) { - if (VD->isProtocolRequirement() && !PD->isObjC()) { - diagnoseAndRemoveAttr(parsedAttr, - diag::unavailable_method_non_objc_protocol); - return; - } - } - } - } - // The remaining diagnostics are only for attributes with introduced versions // for specific platforms. if (!attr->isPlatformSpecific() || !attr->getIntroduced().has_value()) @@ -2486,18 +2465,6 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *parsedAttr) { } } } - - std::optional MaybeNotAllowed = - TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D); - if (MaybeNotAllowed.has_value()) { - AvailabilityRange DeploymentRange = - AvailabilityRange::forDeploymentTarget(Ctx); - if (EnclosingAnnotatedRange.has_value()) - DeploymentRange.intersectWith(*EnclosingAnnotatedRange); - - if (!DeploymentRange.isContainedIn(AttrRange)) - diagnose(attrLoc, MaybeNotAllowed.value()); - } } static bool canDeclareSymbolName(StringRef symbol, ModuleDecl *fromModule) { @@ -5003,6 +4970,54 @@ void AttributeChecker::checkAvailableAttrs(ArrayRef Attrs) { diagnose(D->getLoc(), diag::spi_preferred_over_spi_available); } } + + if (Ctx.LangOpts.DisableAvailabilityChecking) + return; + + // Compute availability constraints for the decl, relative to its parent + // declaration or to the deployment target. + auto availabilityContext = AvailabilityContext::forDeploymentTarget(Ctx); + if (auto parent = + AvailabilityInference::parentDeclForInferredAvailability(D)) { + auto parentAvailability = TypeChecker::availabilityForDeclSignature(parent); + availabilityContext.constrainWithContext(parentAvailability, Ctx); + } + + auto availabilityConstraint = + getAvailabilityConstraintsForDecl(D, availabilityContext) + .getPrimaryConstraint(); + if (!availabilityConstraint) + return; + + // If the decl is unavailable relative to its parent and it's not a + // declaration that is allowed to be unavailable, diagnose. + if (availabilityConstraint->isUnavailable()) { + auto attr = availabilityConstraint->getAttr(); + if (auto diag = TypeChecker::diagnosticIfDeclCannotBeUnavailable(D)) { + diagnose(attr.getParsedAttr()->getLocation(), diag.value()); + return; + } + + if (auto *PD = dyn_cast(D->getDeclContext())) { + if (auto *VD = dyn_cast(D)) { + if (VD->isProtocolRequirement() && !PD->isObjC()) { + diagnoseAndRemoveAttr( + const_cast(attr.getParsedAttr()), + diag::unavailable_method_non_objc_protocol); + return; + } + } + } + } + + // If the decl is potentially unavailable relative to its parent and it's + // not a declaration that is allowed to be potentially unavailable, diagnose. + if (availabilityConstraint->isPotentiallyAvailable()) { + auto attr = availabilityConstraint->getAttr(); + if (auto diag = + TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D)) + diagnose(attr.getParsedAttr()->getLocation(), diag.value()); + } } void AttributeChecker::checkBackDeployedAttrs( diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 0c51c7245e2f5..f17d58d1fd4fa 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -22,6 +22,7 @@ #include "TypeCheckUnsafe.h" #include "TypeChecker.h" #include "swift/AST/ASTWalker.h" +#include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilityDomain.h" #include "swift/AST/AvailabilityInference.h" #include "swift/AST/AvailabilityScope.h" @@ -345,40 +346,6 @@ static bool computeContainedByDeploymentTarget(AvailabilityScope *scope, AvailabilityRange::forDeploymentTarget(ctx)); } -/// 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, AvailabilityContext availabilityContext, - const SemanticAvailableAttr &attr) { - if (!availabilityContext.isUnavailable()) - return false; - - if (!attr.isUnconditionallyUnavailable()) - return false; - - // Refuse calling universally unavailable functions from unavailable code, - // but allow the use of types. - auto declDomain = attr.getDomain(); - if (!isa(D) && !isa(D)) { - if (declDomain.isUniversal() || declDomain.isSwiftLanguage()) - return false; - } - - return availabilityContext.containsUnavailableDomain(declDomain); -} - -std::optional -ExportContext::shouldDiagnoseDeclAsUnavailable(const Decl *D) const { - auto attr = D->getUnavailableAttr(); - if (!attr) - return std::nullopt; - - if (isInsideCompatibleUnavailableDeclaration(D, Availability, *attr)) - return std::nullopt; - - return attr; -} - static bool shouldAllowReferenceToUnavailableInSwiftDeclaration( const Decl *D, const ExportContext &where) { auto *DC = where.getDeclContext(); @@ -2949,7 +2916,8 @@ void swift::diagnoseOverrideOfUnavailableDecl(ValueDecl *override, // recomputing it. ExportContext where = ExportContext::forDeclSignature(override, nullptr); auto constraint = - getUnsatisfiedAvailabilityConstraint(base, where.getAvailability()); + getAvailabilityConstraintsForDecl(base, where.getAvailability()) + .getPrimaryConstraint(); if (!constraint) return; @@ -3000,11 +2968,10 @@ bool shouldHideDomainNameForConstraintDiagnostic( case AvailabilityDomain::Kind::Universal: case AvailabilityDomain::Kind::Embedded: case AvailabilityDomain::Kind::Custom: + case AvailabilityDomain::Kind::PackageDescription: return true; case AvailabilityDomain::Kind::Platform: return false; - - case AvailabilityDomain::Kind::PackageDescription: case AvailabilityDomain::Kind::SwiftLanguage: switch (constraint.getReason()) { case AvailabilityConstraint::Reason::UnconditionallyUnavailable: @@ -3078,55 +3045,14 @@ bool diagnoseExplicitUnavailability(SourceLoc loc, return true; } -std::optional -swift::getUnsatisfiedAvailabilityConstraint( - const Decl *decl, AvailabilityContext availabilityContext) { - auto &ctx = decl->getASTContext(); - - // Generic parameters are always available. - if (isa(decl)) - return std::nullopt; - - if (auto attr = decl->getUnavailableAttr()) { - 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->isSwiftLanguageModeSpecific() || - attr->isPackageDescriptionVersionSpecific()) && - attr->getIntroduced().has_value()) - return AvailabilityConstraint::introducedInLaterVersion(*attr); - - return AvailabilityConstraint::unconditionallyUnavailable(*attr); - - case AvailableVersionComparison::Obsoleted: - return AvailabilityConstraint::obsoleted(*attr); - } - } - - // Check whether the declaration is available in a newer platform version. - if (auto rangeAttr = decl->getAvailableAttrForPlatformIntroduction()) { - auto range = rangeAttr->getIntroducedRange(ctx); - if (!availabilityContext.getPlatformRange().isContainedIn(range)) - return AvailabilityConstraint::introducedInLaterDynamicVersion( - *rangeAttr); - } - - return std::nullopt; -} - std::optional swift::getUnsatisfiedAvailabilityConstraint(const Decl *decl, const DeclContext *referenceDC, SourceLoc referenceLoc) { - return getUnsatisfiedAvailabilityConstraint( - decl, TypeChecker::availabilityAtLocation(referenceLoc, referenceDC)); + return getAvailabilityConstraintsForDecl( + decl, + TypeChecker::availabilityAtLocation(referenceLoc, referenceDC)) + .getPrimaryConstraint(); } /// Check if this is a subscript declaration inside String or @@ -4194,7 +4120,8 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R, auto &ctx = DC->getASTContext(); auto constraint = - getUnsatisfiedAvailabilityConstraint(D, Where.getAvailability()); + getAvailabilityConstraintsForDecl(D, Where.getAvailability()) + .getPrimaryConstraint(); if (constraint) { if (diagnoseExplicitUnavailability(D, R, *constraint, Where, call, Flags)) @@ -4715,7 +4642,8 @@ swift::diagnoseConformanceAvailability(SourceLoc loc, } auto constraint = - getUnsatisfiedAvailabilityConstraint(ext, where.getAvailability()); + getAvailabilityConstraintsForDecl(ext, where.getAvailability()) + .getPrimaryConstraint(); if (constraint) { if (diagnoseExplicitUnavailability(loc, *constraint, rootConf, ext, where, warnIfConformanceUnavailablePreSwift6, diff --git a/lib/Sema/TypeCheckAvailability.h b/lib/Sema/TypeCheckAvailability.h index 2df91b6725ebe..be9970aa0c427 100644 --- a/lib/Sema/TypeCheckAvailability.h +++ b/lib/Sema/TypeCheckAvailability.h @@ -201,13 +201,6 @@ class ExportContext { /// Get the ExportabilityReason for diagnostics. If this is 'None', there /// are no restrictions on referencing unexported declarations. std::optional getExportabilityReason() const; - - /// If \p decl is unconditionally unavailable in this context, and the context - /// is not also unavailable in the same way, then this returns the specific - /// `@available` attribute that makes the decl unavailable. Otherwise, returns - /// nullptr. - std::optional - shouldDiagnoseDeclAsUnavailable(const Decl *decl) const; }; /// Check if a declaration is exported as part of a module's external interface. @@ -250,14 +243,6 @@ void diagnoseOverrideOfUnavailableDecl(ValueDecl *override, const ValueDecl *base, SemanticAvailableAttr attr); -/// 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 unsatisfied constraint. -/// Returns `std::nullopt` if the declaration is available. -std::optional -getUnsatisfiedAvailabilityConstraint(const Decl *decl, - AvailabilityContext availabilityContext); - /// Checks whether a declaration should be considered unavailable when referred /// to at the given source location in the given decl context and, if so, /// returns a result that describes the unsatisfied constraint. diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index efd7c5d6ad164..7b89e8cb4f8ba 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -245,15 +245,21 @@ bool swift::isOverrideBasedOnType(const ValueDecl *decl, Type declTy, static bool isUnavailableInAllVersions(ValueDecl *decl) { ASTContext &ctx = decl->getASTContext(); - auto attr = decl->getUnavailableAttr(); - if (!attr) - return false; - if (attr->isUnconditionallyUnavailable()) - return true; + auto deploymentContext = AvailabilityContext::forDeploymentTarget(ctx); + auto constraints = getAvailabilityConstraintsForDecl(decl, deploymentContext); + for (auto constraint : constraints) { + switch (constraint.getReason()) { + case AvailabilityConstraint::Reason::UnconditionallyUnavailable: + case AvailabilityConstraint::Reason::IntroducedInLaterVersion: + return true; + case AvailabilityConstraint::Reason::Obsoleted: + case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion: + break; + } + } - return attr->getVersionAvailability(ctx) == - AvailableVersionComparison::Unavailable; + return false; } /// Perform basic checking to determine whether a declaration can override a diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index dfa9f721dfc9a..b7b58ee0d2953 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -4964,12 +4964,16 @@ static bool diagnoseTypeWitnessAvailability( bool shouldError = ctx.LangOpts.EffectiveLanguageVersion.isVersionAtLeast(warnBeforeVersion); - if (auto attr = where.shouldDiagnoseDeclAsUnavailable(witness)) { + auto constraint = + getAvailabilityConstraintsForDecl(witness, where.getAvailability()) + .getPrimaryConstraint(); + if (constraint && constraint->isUnavailable()) { + auto attr = constraint->getAttr(); ctx.addDelayedConformanceDiag( conformance, shouldError, [witness, assocType, attr](NormalProtocolConformance *conformance) { SourceLoc loc = getLocForDiagnosingWitness(conformance, witness); - EncodedDiagnosticMessage encodedMessage(attr->getMessage()); + EncodedDiagnosticMessage encodedMessage(attr.getMessage()); auto &ctx = conformance->getDeclContext()->getASTContext(); ctx.Diags .diagnose(loc, diag::witness_unavailable, witness, diff --git a/test/Parse/invalid.swift b/test/Parse/invalid.swift index 377173aa3d898..e886233d6e5c3 100644 --- a/test/Parse/invalid.swift +++ b/test/Parse/invalid.swift @@ -140,5 +140,5 @@ class C_50734<@NSApplicationMain T: AnyObject> {} // expected-error {{@NSApplica func f6_50734<@discardableResult T>(x: T) {} // expected-error {{'@discardableResult' attribute cannot be applied to this declaration}} enum E_50734<@indirect T> {} // expected-error {{'indirect' is a declaration modifier, not an attribute}} expected-error {{'indirect' modifier cannot be applied to this declaration}} protocol P { - @available(swift, introduced: 4) associatedtype Assoc + @available(macOS, introduced: 10.9) associatedtype Assoc } diff --git a/test/attr/attr_availability_swiftpm_v4.swift b/test/attr/attr_availability_swiftpm_v4.swift index b8eb9b4cd08ea..5af875097b9c7 100644 --- a/test/attr/attr_availability_swiftpm_v4.swift +++ b/test/attr/attr_availability_swiftpm_v4.swift @@ -37,6 +37,9 @@ func fourPointOh() {} @available(_PackageDescription 4) class ShortFour {} +@available(_PackageDescription 99) +func ninetyNine() {} // expected-note {{'ninetyNine()' was introduced in PackageDescription 99}} + shortThree() threePointOh() threePointOhOnly() // expected-error {{is unavailable}} @@ -51,6 +54,7 @@ shortFourPointOh() four() fourPointOh() let aa : ShortFour +ninetyNine() // expected-error {{'ninetyNine()' is unavailable}} @available(_PackageDescription, introduced: 4.0) @available(*, deprecated, message: "test deprecated") @@ -63,3 +67,15 @@ func shouldBeAlone() {} @available(_PackageDescription 4.0, swift 2.0, *) // expected-error {{'_PackageDescription' version-availability must be specified alone}} // expected-error {{'swift' version-availability must be specified alone}} func shouldBeAlone2() {} + +@available(*, unavailable, renamed: "shortFour") +@available(_PackageDescription 3) +func unconditionallyRenamed() {} // expected-note {{'unconditionallyRenamed()' has been explicitly marked unavailable here}} + +unconditionallyRenamed() // expected-error {{'unconditionallyRenamed()' has been renamed to 'shortFour'}} + +@available(*, unavailable, renamed: "shortFour") +@available(_PackageDescription 5) +func unconditionallyRenamedAndIntroducedLater() {} // expected-note {{'unconditionallyRenamedAndIntroducedLater()' has been explicitly marked unavailable here}} + +unconditionallyRenamedAndIntroducedLater() // expected-error {{'unconditionallyRenamedAndIntroducedLater()' has been renamed to 'shortFour'}} diff --git a/test/attr/attr_availability_transitive_osx.swift b/test/attr/attr_availability_transitive_osx.swift index 91610e60cf94a..b0a17ee2f3b93 100644 --- a/test/attr/attr_availability_transitive_osx.swift +++ b/test/attr/attr_availability_transitive_osx.swift @@ -533,6 +533,9 @@ func available_func_call_extension_methods(_ e: ExtendMe) { // expected-note {{a // expected-note@-1 {{add 'if #available' version check}} } +@available(OSX, obsoleted: 10.9) +struct OSXObsoleted {} // expected-note 2 {{'OSXObsoleted' was obsoleted in macOS 10.9}} + @available(OSX, unavailable) @available(OSX, introduced: 99) struct OSXUnavailableAndIntroducedInFuture {} @@ -547,32 +550,33 @@ struct OSXIntroducedInFutureAndUnavailable {} @available(OSX, unavailable) func osx_unavailable_func( _ s1: OSXFutureAvailable, - _ s2: OSXUnavailableAndIntroducedInFuture, - _ s3: OSXUnavailableAndIntroducedInFutureSameAttribute, - _ s4: OSXIntroducedInFutureAndUnavailable, + _ s2: OSXObsoleted, + _ s3: OSXUnavailableAndIntroducedInFuture, + _ s4: OSXUnavailableAndIntroducedInFutureSameAttribute, + _ s5: OSXIntroducedInFutureAndUnavailable, ) -> ( OSXFutureAvailable, + OSXObsoleted, OSXUnavailableAndIntroducedInFuture, OSXUnavailableAndIntroducedInFutureSameAttribute, OSXIntroducedInFutureAndUnavailable ) { + // FIXME: [availability] Stop diagnosing potential unavailability or obsoletion in an unavailable context. _ = OSXFutureAvailable() // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}} // expected-note@-1 {{add 'if #available' version check}} - // FIXME: [availability] The following diagnostic is incorrect - _ = OSXUnavailableAndIntroducedInFuture() // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}} - // expected-note@-1 {{add 'if #available' version check}} + _ = OSXObsoleted() // expected-error {{'OSXObsoleted' is unavailable in macOS}} + _ = OSXUnavailableAndIntroducedInFuture() _ = OSXUnavailableAndIntroducedInFutureSameAttribute() _ = OSXIntroducedInFutureAndUnavailable() func takesType(_ t: T.Type) {} takesType(OSXFutureAvailable.self) // expected-error {{'OSXFutureAvailable' is only available in macOS 99 or newer}} // expected-note@-1 {{add 'if #available' version check}} - // FIXME: [availability] The following diagnostic is incorrect - takesType(OSXUnavailableAndIntroducedInFuture.self) // expected-error {{'OSXUnavailableAndIntroducedInFuture' is only available in macOS 99 or newer}} - // expected-note@-1 {{add 'if #available' version check}} + takesType(OSXObsoleted.self) // expected-error {{'OSXObsoleted' is unavailable in macOS}} + takesType(OSXUnavailableAndIntroducedInFuture.self) takesType(OSXUnavailableAndIntroducedInFutureSameAttribute.self) takesType(OSXIntroducedInFutureAndUnavailable.self) - return (s1, s2, s3, s4) + return (s1, s2, s3, s4, s5) } diff --git a/test/decl/ext/objc_implementation_direct_to_storage.swift b/test/decl/ext/objc_implementation_direct_to_storage.swift index 52db9da80003a..b6dd6a43b2c17 100644 --- a/test/decl/ext/objc_implementation_direct_to_storage.swift +++ b/test/decl/ext/objc_implementation_direct_to_storage.swift @@ -4,22 +4,20 @@ import objc_implementation_internal +// FIXME: [availability] An implementation that is less available than the interface it implements should be diagnosed @available(*, unavailable) @objc @implementation extension ObjCPropertyTest { - // FIXME: Shouldn't this be on the `@available` above? - // expected-note@+1 {{'prop1' has been explicitly marked unavailable here}} let prop1: Int32 - // expected-note@+1 2 {{'prop2' has been explicitly marked unavailable here}} var prop2: Int32 { didSet { - _ = prop2 // expected-error {{'prop2' is unavailable}} + _ = prop2 } } override init() { - self.prop1 = 1 // expected-error {{'prop1' is unavailable}} - self.prop2 = 2 // expected-error {{'prop2' is unavailable}} + self.prop1 = 1 + self.prop2 = 2 super.init() } @@ -28,3 +26,8 @@ import objc_implementation_internal _ = self.prop2 } } + +func takesObjCPropertyTest(_ o: ObjCPropertyTest) { + _ = o.prop1 + _ = o.prop2 +} diff --git a/test/decl/protocol/associated_type_availability.swift b/test/decl/protocol/associated_type_availability.swift index c7e05e5aa89e6..1a8bb85a3ea39 100644 --- a/test/decl/protocol/associated_type_availability.swift +++ b/test/decl/protocol/associated_type_availability.swift @@ -89,6 +89,6 @@ protocol P3 { @available(macOS, obsoleted: 12) // expected-error{{associated type cannot be marked unavailable with '@available'}} associatedtype A1 - @available(macOS, obsoleted: 99) // FIXME: this should probably be diagnosed + @available(macOS, obsoleted: 99) associatedtype A2 } diff --git a/test/decl/protocol/req/unavailable.swift b/test/decl/protocol/req/unavailable.swift index c7a585ee68db0..13837f23a8e10 100644 --- a/test/decl/protocol/req/unavailable.swift +++ b/test/decl/protocol/req/unavailable.swift @@ -104,6 +104,15 @@ protocol UnavailableAssoc { @available(*, unavailable) // expected-error {{associated type cannot be marked unavailable with '@available'}} associatedtype A1 - @available(swift, introduced: 99) // expected-error {{associated type cannot be marked unavailable with '@available'}} + @available(swift, introduced: 4) associatedtype A2 + + @available(swift, introduced: 99) // expected-error {{associated type cannot be marked unavailable with '@available'}} + associatedtype A3 + + @available(swift, obsoleted: 4) // expected-error {{associated type cannot be marked unavailable with '@available'}} + associatedtype A4 + + @available(swift, obsoleted: 99) + associatedtype A5 }