Skip to content

AST: Begin consolidating availability constraint queries #79249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion include/swift/AST/AvailabilityConstraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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<AvailabilityConstraint, 4>;
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<AvailabilityConstraint>
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
10 changes: 7 additions & 3 deletions include/swift/AST/AvailabilityContextStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

namespace swift {

class DeclAvailabilityConstraints;

/// Summarizes availability the constraints contained by an AvailabilityContext.
class AvailabilityContext::Info {
public:
Expand All @@ -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<AvailabilityDomain> domain);

Expand Down
38 changes: 0 additions & 38 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<AvailabilityRange>
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
Expand Down
189 changes: 189 additions & 0 deletions lib/AST/AvailabilityConstraint.cpp
Original file line number Diff line number Diff line change
@@ -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<AvailabilityRange>
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<TypeDecl>(decl) && !isa<ExtensionDecl>(decl)) {
if (domain.isUniversal() || domain.isSwiftLanguage())
return false;
}

return context.containsUnavailableDomain(domain);
}

std::optional<AvailabilityConstraint>
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<llvm::VersionTuple> 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<AvailabilityDomain>
activePlatformDomainForDecl(const Decl *decl) {
std::optional<AvailabilityDomain> 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<GenericTypeParamDecl>(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<ExtensionDecl>(parent))
getAvailabilityConstraintsForDecl(constraints, extension, context);

return constraints;
}
32 changes: 23 additions & 9 deletions lib/AST/AvailabilityContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions lib/AST/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ add_swift_host_library(swiftAST STATIC
Attr.cpp
AutoDiff.cpp
Availability.cpp
AvailabilityConstraint.cpp
AvailabilityContext.cpp
AvailabilityDomain.cpp
AvailabilityScope.cpp
Expand Down
Loading