diff --git a/include/swift/AST/AvailabilityConstraint.h b/include/swift/AST/AvailabilityConstraint.h index b6a2676cb97e4..f025f849cb97e 100644 --- a/include/swift/AST/AvailabilityConstraint.h +++ b/include/swift/AST/AvailabilityConstraint.h @@ -17,6 +17,7 @@ #ifndef SWIFT_AST_AVAILABILITY_CONSTRAINT_H #define SWIFT_AST_AVAILABILITY_CONSTRAINT_H +#include "swift/AST/Attr.h" #include "swift/AST/AvailabilityDomain.h" #include "swift/AST/AvailabilityRange.h" #include "swift/AST/PlatformKind.h" @@ -25,7 +26,8 @@ namespace swift { class ASTContext; -class AvailableAttr; +class AvailabilityContext; +class Decl; /// Represents the reason a declaration could be considered unavailable in a /// certain context. @@ -110,6 +112,37 @@ class AvailabilityConstraint { bool isActiveForRuntimeQueries(ASTContext &ctx) const; }; +/// Represents a set of availability constraints that restrict use of a +/// declaration in a particular context. +class DeclAvailabilityConstraints { + using Storage = llvm::SmallVector; + Storage constraints; + +public: + DeclAvailabilityConstraints() {} + + void addConstraint(const AvailabilityConstraint &constraint) { + constraints.emplace_back(constraint); + } + + 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); + +/// 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); } // end namespace swift #endif diff --git a/include/swift/AST/AvailabilityContextStorage.h b/include/swift/AST/AvailabilityContextStorage.h index 2c6255e2c6238..d140943c024f3 100644 --- a/include/swift/AST/AvailabilityContextStorage.h +++ b/include/swift/AST/AvailabilityContextStorage.h @@ -23,6 +23,8 @@ namespace swift { +class DeclAvailabilityConstraints; + /// Summarizes availability the constraints contained by an AvailabilityContext. class AvailabilityContext::Info { public: @@ -41,9 +43,11 @@ class AvailabilityContext::Info { /// of adding this constraint. bool constrainWith(const Info &other); - /// Updates each field to reflect the availability of `decl`, if that - /// availability is more restrictive. Returns true if any field was updated. - bool constrainWith(const Decl *decl); + /// Constrains each field using the given constraints if they are more + /// restrictive than the current values. Returns true if any field was + /// updated. + bool constrainWith(const DeclAvailabilityConstraints &constraints, + ASTContext &ctx); bool constrainUnavailability(std::optional domain); diff --git a/lib/AST/Availability.cpp b/lib/AST/Availability.cpp index 62ff2131144fa..ffc7fdc111bc4 100644 --- a/lib/AST/Availability.cpp +++ b/lib/AST/Availability.cpp @@ -16,7 +16,6 @@ #include "swift/AST/ASTContext.h" #include "swift/AST/Attr.h" -#include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilityDomain.h" #include "swift/AST/AvailabilityInference.h" #include "swift/AST/AvailabilityRange.h" @@ -67,43 +66,6 @@ AvailabilityRange AvailabilityRange::forRuntimeTarget(const ASTContext &Ctx) { return AvailabilityRange(VersionRange::allGTE(Ctx.LangOpts.RuntimeVersion)); } -PlatformKind AvailabilityConstraint::getPlatform() const { - return getAttr().getPlatform(); -} - -std::optional -AvailabilityConstraint::getRequiredNewerAvailabilityRange( - ASTContext &ctx) const { - switch (getKind()) { - case Kind::AlwaysUnavailable: - case Kind::RequiresVersion: - case Kind::Obsoleted: - return std::nullopt; - case Kind::IntroducedInNewerVersion: - return getAttr().getIntroducedRange(ctx); - } -} - -bool AvailabilityConstraint::isConditionallySatisfiable() const { - switch (getKind()) { - case Kind::AlwaysUnavailable: - case Kind::RequiresVersion: - case Kind::Obsoleted: - return false; - case Kind::IntroducedInNewerVersion: - return true; - } -} - -bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const { - if (getAttr().getPlatform() == PlatformKind::none) - return true; - - return swift::isPlatformActive(getAttr().getPlatform(), ctx.LangOpts, - /*forTargetVariant=*/false, - /*forRuntimeQuery=*/true); -} - namespace { /// The inferred availability required to access a group of declarations diff --git a/lib/AST/AvailabilityConstraint.cpp b/lib/AST/AvailabilityConstraint.cpp new file mode 100644 index 0000000000000..eff08c61ecaaf --- /dev/null +++ b/lib/AST/AvailabilityConstraint.cpp @@ -0,0 +1,189 @@ +//===--- AvailabilityConstraint.cpp - Swift Availability Constraints ------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/AvailabilityConstraint.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/AvailabilityContext.h" +#include "swift/AST/AvailabilityInference.h" +#include "swift/AST/Decl.h" + +using namespace swift; + +PlatformKind AvailabilityConstraint::getPlatform() const { + return getAttr().getPlatform(); +} + +std::optional +AvailabilityConstraint::getRequiredNewerAvailabilityRange( + ASTContext &ctx) const { + switch (getKind()) { + case Kind::AlwaysUnavailable: + case Kind::RequiresVersion: + case Kind::Obsoleted: + return std::nullopt; + case Kind::IntroducedInNewerVersion: + return getAttr().getIntroducedRange(ctx); + } +} + +bool AvailabilityConstraint::isConditionallySatisfiable() const { + switch (getKind()) { + case Kind::AlwaysUnavailable: + case Kind::RequiresVersion: + case Kind::Obsoleted: + return false; + case Kind::IntroducedInNewerVersion: + return true; + } +} + +bool AvailabilityConstraint::isActiveForRuntimeQueries(ASTContext &ctx) const { + if (getAttr().getPlatform() == PlatformKind::none) + return true; + + return swift::isPlatformActive(getAttr().getPlatform(), ctx.LangOpts, + /*forTargetVariant=*/false, + /*forRuntimeQuery=*/true); +} + +static bool +isInsideCompatibleUnavailableDeclaration(const Decl *decl, + const SemanticAvailableAttr &attr, + const AvailabilityContext &context) { + if (!context.isUnavailable()) + return false; + + if (!attr.isUnconditionallyUnavailable()) + return false; + + // Refuse calling universally unavailable functions from unavailable code, + // but allow the use of types. + auto domain = attr.getDomain(); + if (!isa(decl) && !isa(decl)) { + if (domain.isUniversal() || domain.isSwiftLanguage()) + return false; + } + + 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; + + if (attr.isUnconditionallyUnavailable()) + return AvailabilityConstraint::forAlwaysUnavailable(attr); + + auto &ctx = decl->getASTContext(); + auto deploymentVersion = attr.getActiveVersion(ctx); + auto deploymentRange = + AvailabilityRange(VersionRange::allGTE(deploymentVersion)); + std::optional obsoletedVersion = attr.getObsoleted(); + + { + StringRef obsoletedPlatform; + llvm::VersionTuple remappedObsoletedVersion; + if (AvailabilityInference::updateObsoletedPlatformForFallback( + attr, ctx, obsoletedPlatform, remappedObsoletedVersion)) + obsoletedVersion = remappedObsoletedVersion; + } + + if (obsoletedVersion && *obsoletedVersion <= deploymentVersion) + return AvailabilityConstraint::forObsoleted(attr); + + AvailabilityRange introducedRange = attr.getIntroducedRange(ctx); + + // FIXME: [availability] Expand this to cover custom versioned domains + if (attr.isPlatformSpecific()) { + if (!context.getPlatformRange().isContainedIn(introducedRange)) + return AvailabilityConstraint::forIntroducedInNewerVersion(attr); + } else if (!deploymentRange.isContainedIn(introducedRange)) { + return AvailabilityConstraint::forRequiresVersion(attr); + } + + return std::nullopt; +} + +/// Returns the most specific platform domain from the availability attributes +/// attached to \p decl or `std::nullopt` if there are none. Platform specific +/// `@available` attributes for other platforms should be ignored. For example, +/// if a declaration has attributes for both iOS and macCatalyst, only the +/// macCatalyst attributes take effect when compiling for a macCatalyst target. +static std::optional +activePlatformDomainForDecl(const Decl *decl) { + std::optional activeDomain; + for (auto attr : + decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) { + auto domain = attr.getDomain(); + if (!domain.isPlatform()) + continue; + + if (activeDomain && domain.contains(*activeDomain)) + continue; + + activeDomain.emplace(domain); + } + + return activeDomain; +} + +static void +getAvailabilityConstraintsForDecl(DeclAvailabilityConstraints &constraints, + const Decl *decl, + const AvailabilityContext &context) { + auto activePlatformDomain = activePlatformDomainForDecl(decl); + + for (auto attr : + decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) { + auto domain = attr.getDomain(); + if (domain.isPlatform() && activePlatformDomain && + !activePlatformDomain->contains(domain)) + continue; + + if (auto constraint = + swift::getAvailabilityConstraintForAttr(decl, attr, context)) + constraints.addConstraint(*constraint); + } +} + +DeclAvailabilityConstraints +swift::getAvailabilityConstraintsForDecl(const Decl *decl, + const AvailabilityContext &context) { + DeclAvailabilityConstraints constraints; + + // Generic parameters are always available. + if (isa(decl)) + return constraints; + + decl = abstractSyntaxDeclForAvailableAttribute(decl); + + getAvailabilityConstraintsForDecl(constraints, decl, context); + + // 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 + // wrong extension and inherit unavailability incorrectly. 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 (decl->getClangNode()) + return constraints; + + auto parent = AvailabilityInference::parentDeclForInferredAvailability(decl); + if (auto extension = dyn_cast_or_null(parent)) + getAvailabilityConstraintsForDecl(constraints, extension, context); + + return constraints; +} diff --git a/lib/AST/AvailabilityContext.cpp b/lib/AST/AvailabilityContext.cpp index 606357080dc1f..b01feb296fc41 100644 --- a/lib/AST/AvailabilityContext.cpp +++ b/lib/AST/AvailabilityContext.cpp @@ -12,6 +12,7 @@ #include "swift/AST/AvailabilityContext.h" #include "swift/AST/ASTContext.h" +#include "swift/AST/AvailabilityConstraint.h" #include "swift/AST/AvailabilityContextStorage.h" #include "swift/AST/AvailabilityInference.h" #include "swift/AST/Decl.h" @@ -63,16 +64,27 @@ bool AvailabilityContext::Info::constrainWith(const Info &other) { return isConstrained; } -bool AvailabilityContext::Info::constrainWith(const Decl *decl) { +bool AvailabilityContext::Info::constrainWith( + const DeclAvailabilityConstraints &constraints, ASTContext &ctx) { bool isConstrained = false; - if (auto range = AvailabilityInference::annotatedAvailableRange(decl)) - isConstrained |= constrainRange(Range, *range); - - if (auto attr = decl->getUnavailableAttr()) - isConstrained |= constrainUnavailability(attr->getDomain()); - - isConstrained |= CONSTRAIN_BOOL(IsDeprecated, decl->isDeprecated()); + for (auto constraint : constraints) { + auto attr = constraint.getAttr(); + auto domain = attr.getDomain(); + switch (constraint.getKind()) { + case AvailabilityConstraint::Kind::AlwaysUnavailable: + case AvailabilityConstraint::Kind::Obsoleted: + case AvailabilityConstraint::Kind::RequiresVersion: + isConstrained |= constrainUnavailability(domain); + break; + case AvailabilityConstraint::Kind::IntroducedInNewerVersion: + // FIXME: [availability] Support versioning for other kinds of domains. + DEBUG_ASSERT(domain.isPlatform()); + if (domain.isPlatform()) + isConstrained |= constrainRange(Range, attr.getIntroducedRange(ctx)); + break; + } + } return isConstrained; } @@ -190,7 +202,9 @@ void AvailabilityContext::constrainWithDeclAndPlatformRange( bool isConstrained = false; Info info{storage->info}; - isConstrained |= info.constrainWith(decl); + auto constraints = swift::getAvailabilityConstraintsForDecl(decl, *this); + isConstrained |= info.constrainWith(constraints, decl->getASTContext()); + isConstrained |= CONSTRAIN_BOOL(info.IsDeprecated, decl->isDeprecated()); isConstrained |= constrainRange(info.Range, platformRange); if (!isConstrained) diff --git a/lib/AST/CMakeLists.txt b/lib/AST/CMakeLists.txt index 723bb6ae1b713..e222ad5650b5f 100644 --- a/lib/AST/CMakeLists.txt +++ b/lib/AST/CMakeLists.txt @@ -27,6 +27,7 @@ add_swift_host_library(swiftAST STATIC Attr.cpp AutoDiff.cpp Availability.cpp + AvailabilityConstraint.cpp AvailabilityContext.cpp AvailabilityDomain.cpp AvailabilityScope.cpp diff --git a/test/attr/attr_availability_maccatalyst.swift b/test/attr/attr_availability_maccatalyst.swift index 91f11fa116c91..61bd6004ed620 100644 --- a/test/attr/attr_availability_maccatalyst.swift +++ b/test/attr/attr_availability_maccatalyst.swift @@ -144,26 +144,85 @@ extension X: P {} // Test platform inheritance for iOS unavailability. // rdar://68597591 +func takesAnything(_ t: T) { } + +@available(macCatalyst, unavailable) +struct UnavailableOnMacCatalyst { } // expected-note * {{'UnavailableOnMacCatalyst' has been explicitly marked unavailable here}} + @available(iOS, unavailable) -public struct UnavailableOniOS { } // expected-note 2 {{'UnavailableOniOS' has been explicitly marked unavailable here}} +struct UnavailableOniOS { } // expected-note * {{'UnavailableOniOS' has been explicitly marked unavailable here}} @available(iOS, unavailable) -func unavailableOniOS(_ p: UnavailableOniOS) { } // ok +@available(macCatalyst, introduced: 13.0) +struct AvailableOnMacCatalystButUnavailableOniOS { } + +extension UnavailableOnMacCatalyst { } // expected-error {{'UnavailableOnMacCatalyst' is unavailable in Mac Catalyst}} +extension UnavailableOniOS { } // expected-error {{'UnavailableOniOS' is unavailable in iOS}} +extension AvailableOnMacCatalystButUnavailableOniOS { } // ok + +@available(macCatalyst, unavailable) +extension UnavailableOnMacCatalyst { + func extensionMethod() { + takesAnything(UnavailableOniOS()) // expected-error {{'UnavailableOniOS' is unavailable in iOS}} + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } -func functionUsingAnUnavailableType(_ p: UnavailableOniOS) { } // expected-error {{'UnavailableOniOS' is unavailable in iOS}} + @available(iOS, unavailable) + func extensionMethodUnavailableOniOS() { + takesAnything(UnavailableOniOS()) + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } + + @available(iOS, introduced: 15) + func extensionMethodIntroducedOniOS() { + takesAnything(UnavailableOniOS()) // expected-error {{'UnavailableOniOS' is unavailable in iOS}} + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } -public extension UnavailableOniOS { } // expected-error {{'UnavailableOniOS' is unavailable in iOS}} +} @available(iOS, unavailable) -public extension UnavailableOniOS { // ok - func someMethod(_ p: UnavailableOniOS) { } +extension UnavailableOniOS { + func extensionMethod() { + takesAnything(UnavailableOniOS()) + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } + + @available(macCatalyst, unavailable) + func extensionMethodUnavailableOnMacCatalyst() { + takesAnything(UnavailableOniOS()) + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } + + @available(macCatalyst, introduced: 13.0) + func extensionMethodMacCatalystIntroduced() { + takesAnything(UnavailableOniOS()) + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } } @available(iOS, unavailable) @available(macCatalyst, introduced: 13.0) -public struct AvailableOnMacCatalyst { } +extension AvailableOnMacCatalystButUnavailableOniOS { + func extensionMethod() { + takesAnything(UnavailableOniOS()) // expected-error {{'UnavailableOniOS' is unavailable in iOS}} + takesAnything(UnavailableOnMacCatalyst()) // expected-error {{'UnavailableOnMacCatalyst' is unavailable in Mac Catalyst}} + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } -public extension AvailableOnMacCatalyst { } // ok + @available(macCatalyst, unavailable) + func extensionMethodUnavailableOnMacCatalyst() { + takesAnything(UnavailableOniOS()) // expected-error {{'UnavailableOniOS' is unavailable in iOS}} + takesAnything(UnavailableOnMacCatalyst()) + takesAnything(AvailableOnMacCatalystButUnavailableOniOS()) + } +} @available(iOS, introduced: 14.0) @available(macCatalyst, introduced: 14.5) diff --git a/test/embedded/availability.swift b/test/embedded/availability.swift index 5ae988a73d972..15f1be053bbbb 100644 --- a/test/embedded/availability.swift +++ b/test/embedded/availability.swift @@ -17,7 +17,7 @@ public func unavailable_in_embedded() { } @available(*, unavailable, message: "always unavailable") public func universally_unavailable() { } -// expected-note@-1 3 {{'universally_unavailable()' has been explicitly marked unavailable here}} +// expected-note@-1 4 {{'universally_unavailable()' has been explicitly marked unavailable here}} @_unavailableInEmbedded public func unused() { } // no error @@ -35,12 +35,29 @@ public func has_universally_unavailable_overload(_ s1: S1) { } public func has_universally_unavailable_overload(_ s2: S2) { } +public struct Available {} + +@_unavailableInEmbedded +extension Available { + public func unavailable_in_embedded_method( // expected-note {{'unavailable_in_embedded_method' has been explicitly marked unavailable here}} + _ uie: UnavailableInEmbedded, + _ uu: UniverallyUnavailable, + _ a: Available, + ) { + unavailable_in_embedded() + universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}} + a.unavailable_in_embedded_method(uie, uu, a) + } +} + public func available( _ uie: UnavailableInEmbedded, // expected-error {{'UnavailableInEmbedded' is unavailable: unavailable in embedded Swift}} - _ uu: UniverallyUnavailable // expected-error {{'UniverallyUnavailable' is unavailable: always unavailable}} + _ uu: UniverallyUnavailable, // expected-error {{'UniverallyUnavailable' is unavailable: always unavailable}} + _ a: Available, ) { unavailable_in_embedded() // expected-error {{'unavailable_in_embedded()' is unavailable: unavailable in embedded Swift}} universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}} + a.unavailable_in_embedded_method(uie, uu, a) // expected-error {{'unavailable_in_embedded_method' is unavailable: unavailable in embedded Swift}} has_unavailable_in_embedded_overload(.init()) has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload } @@ -48,10 +65,12 @@ public func available( @_unavailableInEmbedded public func also_unavailable_in_embedded( _ uie: UnavailableInEmbedded, // OK - _ uu: UniverallyUnavailable // OK + _ uu: UniverallyUnavailable, // OK + _ a: Available, ) { unavailable_in_embedded() // OK universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}} + a.unavailable_in_embedded_method(uie, uu, a) has_unavailable_in_embedded_overload(.init()) // expected-error {{ambiguous use of 'init()'}} has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload } @@ -59,10 +78,12 @@ public func also_unavailable_in_embedded( @available(*, unavailable) public func also_universally_unavailable( _ uie: UnavailableInEmbedded, // OK - _ uu: UniverallyUnavailable // OK + _ uu: UniverallyUnavailable, // OK + _ a: Available, ) { unavailable_in_embedded() universally_unavailable() // expected-error {{'universally_unavailable()' is unavailable: always unavailable}} + a.unavailable_in_embedded_method(uie, uu, a) has_unavailable_in_embedded_overload(.init()) // expected-error {{ambiguous use of 'init()'}} has_universally_unavailable_overload(.init()) // not ambiguous, selects available overload }