AST: Introduce queries on Decl for active @available attrs.

Use these queries to replace some duplicated code. Also, move the
`attr_inlinable_available.swift` test to the `Availability` sub-directory since
the test has more to do with availability checking than it has to do
specifically with the `@inlinable` attr.
This commit is contained in:
Allan Shortlidge
2025-07-16 12:51:30 -07:00
parent b97df2645b
commit a3fbe3d530
6 changed files with 84 additions and 37 deletions

View File

@@ -260,6 +260,12 @@ public:
/// universal domain (`*`) is the bottom element.
bool contains(const AvailabilityDomain &other) const;
/// Returns true if availability in `other` is a subset of availability in
/// this domain or vice-versa.
bool isRelated(const AvailabilityDomain &other) const {
return contains(other) || other.contains(*this);
}
/// Returns true for domains that are not contained by any domain other than
/// the universal domain.
bool isRoot() const;

View File

@@ -1464,6 +1464,27 @@ public:
std::optional<SemanticAvailableAttr>
getAvailableAttrForPlatformIntroduction(bool checkExtension = true) const;
/// Returns true if `decl` has any active `@available` attribute attached to
/// it.
bool hasAnyActiveAvailableAttr() const {
return hasAnyMatchingActiveAvailableAttr(
[](SemanticAvailableAttr attr) -> bool { return true; });
}
/// Returns true if `predicate` returns true for any active availability
/// attribute attached to `decl`. The predicate function should accept a
/// `SemanticAvailableAttr`.
template <typename F>
bool hasAnyMatchingActiveAvailableAttr(F predicate) const {
auto &ctx = getASTContext();
auto decl = getAbstractSyntaxDeclForAttributes();
for (auto attr : decl->getSemanticAvailableAttrs()) {
if (attr.isActive(ctx) && predicate(attr))
return true;
}
return false;
}
/// Returns true if the declaration is deprecated at the current deployment
/// target.
bool isDeprecated() const { return getDeprecatedAttr().has_value(); }

View File

@@ -30,20 +30,6 @@
using namespace swift;
/// Returns true if there is any availability attribute on the declaration
/// that is active.
// FIXME: [availability] De-duplicate this with TypeCheckAvailability.cpp.
static bool hasActiveAvailableAttribute(const Decl *decl, ASTContext &ctx) {
decl = decl->getAbstractSyntaxDeclForAttributes();
for (auto attr : decl->getSemanticAvailableAttrs()) {
if (attr.isActive(ctx))
return true;
}
return false;
}
static bool computeContainedByDeploymentTarget(AvailabilityScope *scope,
ASTContext &ctx) {
return scope->getPlatformAvailabilityRange().isContainedIn(
@@ -397,7 +383,7 @@ private:
return nullptr;
// Declarations with explicit availability attributes always get a scope.
if (hasActiveAvailableAttribute(decl, Context)) {
if (decl->hasAnyActiveAvailableAttr()) {
return AvailabilityScope::createForDecl(
Context, decl, getCurrentScope(),
getEffectiveAvailabilityForDeclSignature(decl),
@@ -423,16 +409,20 @@ private:
getEffectiveAvailabilityForDeclSignature(const Decl *decl) {
auto effectiveIntroduction = AvailabilityRange::alwaysAvailable();
// Availability attributes are found abstract syntax decls.
// Availability attributes are found on abstract syntax decls.
decl = decl->getAbstractSyntaxDeclForAttributes();
// As a special case, extension decls are treated as effectively as
// available as the nominal type they extend, up to the deployment target.
// This rule is a convenience for library authors who have written
// extensions without specifying availabilty on the extension itself.
// extensions without specifying platform availabilty on the extension
// itself.
if (auto *extension = dyn_cast<ExtensionDecl>(decl)) {
auto extendedType = extension->getExtendedType();
if (extendedType && !hasActiveAvailableAttribute(decl, Context)) {
if (extendedType && !decl->hasAnyMatchingActiveAvailableAttr(
[](SemanticAvailableAttr attr) -> bool {
return attr.getDomain().isPlatform();
})) {
effectiveIntroduction.intersectWith(
swift::AvailabilityInference::inferForType(extendedType));

View File

@@ -241,20 +241,6 @@ ExportContext::getExportabilityReason() const {
return std::nullopt;
}
/// Returns true if there is any availability attribute on the declaration
/// that is active.
// FIXME: [availability] De-duplicate this with AvailabilityScopeBuilder.cpp.
static bool hasActiveAvailableAttribute(const Decl *D, ASTContext &ctx) {
D = D->getAbstractSyntaxDeclForAttributes();
for (auto Attr : D->getSemanticAvailableAttrs()) {
if (Attr.isActive(ctx))
return true;
}
return false;
}
static bool shouldAllowReferenceToUnavailableInSwiftDeclaration(
const Decl *D, const ExportContext &where) {
auto *DC = where.getDeclContext();
@@ -639,15 +625,18 @@ static void fixAvailabilityForDecl(
const AvailabilityRange &RequiredAvailability, ASTContext &Context) {
assert(D);
// Don't suggest adding an @available() to a declaration where we would
// Don't suggest adding an @available to a declaration where we would
// emit a diagnostic saying it is not allowed.
if (TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D).has_value())
return;
if (hasActiveAvailableAttribute(D, Context)) {
// For QoI, in future should emit a fixit to update the existing attribute.
// Don't suggest adding an @available attribute to a declaration that already
// has one that is active for the given domain.
// FIXME: Emit a fix-it to adjust the existing attribute instead.
if (D->hasAnyMatchingActiveAvailableAttr([&](SemanticAvailableAttr attr) {
return attr.getDomain().isRelated(Domain);
}))
return;
}
// For some declarations (variables, enum elements), the location in concrete
// syntax to suggest the Fix-It may differ from the declaration to which

View File

@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend -typecheck -dump-availability-scopes %s -target %target-cpu-apple-macos50 > %t.dump 2>&1
// RUN: %target-swift-frontend -typecheck -dump-availability-scopes %s -target %target-cpu-apple-macos50 -swift-version 5 > %t.dump 2>&1
// RUN: %FileCheck --strict-whitespace %s < %t.dump
// REQUIRES: OS=macosx
@@ -454,6 +454,23 @@ func deprecatedOnMacOS() {
let x = 1
}
// Since availableOniOS() doesn't have any active @available attributes it
// shouldn't create a scope.
// CHECK-NOT: availableOniOS
@available(iOS, introduced: 53)
func availableOniOS() { }
// CHECK-NEXT: {{^}} (decl version=50 decl=availableInSwift5
@available(swift 5)
func availableInSwift5() { }
// CHECK-NEXT: {{^}} (decl version=50 unavailable=swift decl=availableInSwift6
@available(swift 6)
func availableInSwift6() { }
// CHECK-NEXT: {{^}} (decl version=51 decl=FinalDecl
@available(OSX 51, *)

View File

@@ -1292,6 +1292,30 @@ extension BetweenTargets { // expected-note 2 {{add '@available' attribute to en
) {}
}
@available(iOS 8.0, *)
extension BetweenTargets { // expected-note 2 {{add '@available' attribute to enclosing extension}}
public func publicFuncInExtensionWithExplicitiOSAvailability( // expected-note 2 {{add '@available' attribute to enclosing instance method}}
_: NoAvailable,
_: BeforeInliningTarget,
_: AtInliningTarget,
_: BetweenTargets,
_: AtDeploymentTarget, // expected-error {{'AtDeploymentTarget' is only available in macOS 10.15 or newer; clients of 'Test' may have a lower deployment target}}
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in macOS 11 or newer}}
) {}
}
@available(swift 5)
extension BetweenTargets { // expected-note 2 {{add '@available' attribute to enclosing extension}}
public func publicFuncInExtensionWithExplicitSwiftAvailability( // expected-note 2 {{add '@available' attribute to enclosing instance method}}
_: NoAvailable,
_: BeforeInliningTarget,
_: AtInliningTarget,
_: BetweenTargets,
_: AtDeploymentTarget, // expected-error {{'AtDeploymentTarget' is only available in macOS 10.15 or newer; clients of 'Test' may have a lower deployment target}}
_: AfterDeploymentTarget // expected-error {{'AfterDeploymentTarget' is only available in macOS 11 or newer}}
) {}
}
@available(macOS 10.15, *)
extension BetweenTargets {
public func publicFuncInExtensionWithExplicitAvailability( // expected-note {{add '@available' attribute to enclosing instance method}}