Skip to content

Sema: Lift restriction requiring decls with @_backDeploy to have explicit availability #62876

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
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
4 changes: 4 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2367,6 +2367,10 @@ class DeclAttributes {
/// otherwise.
const AvailableAttr *getNoAsync(const ASTContext &ctx) const;

/// Returns the \c @_backDeploy attribute that is active for the current
/// platform.
const BackDeployAttr *getBackDeploy(const ASTContext &ctx) const;

SWIFT_DEBUG_DUMPER(dump(const Decl *D = nullptr));
void print(ASTPrinter &Printer, const PrintOptions &Options,
const Decl *D = nullptr) const;
Expand Down
6 changes: 6 additions & 0 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

namespace swift {
class ASTContext;
class AvailableAttr;
class Decl;

/// A lattice of version ranges of the form [x.y.z, +Inf).
Expand Down Expand Up @@ -344,6 +345,11 @@ class AvailabilityInference {
/// We assume a declaration without an annotation is always available.
static AvailabilityContext availableRange(const Decl *D, ASTContext &C);

/// Returns the attribute that should be used to determine the availability
/// range of the given declaration, or nullptr if there is none.
static const AvailableAttr *attrForAnnotatedAvailableRange(const Decl *D,
ASTContext &Ctx);

/// Returns the context for which the declaration
/// is annotated as available, or None if the declaration
/// has no availability annotation.
Expand Down
21 changes: 16 additions & 5 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1098,11 +1098,22 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated<Decl> {

bool isAvailableAsSPI() const;

/// Whether the declaration is considered unavailable through either being
/// explicitly marked as such, or has a parent decl that is semantically
/// unavailable. This is a broader notion of unavailability than is checked by
/// \c AvailableAttr::isUnavailable.
bool isSemanticallyUnavailable() const;
/// Retrieve the @available attribute that provides the OS version range that
/// this declaration is available in.
///
/// The attribute may come from another declaration, since availability
/// could be inherited from a parent declaration.
Optional<std::pair<const AvailableAttr *, const Decl *>>
getSemanticAvailableRangeAttr() const;

/// Retrieve the @available attribute that makes this declaration unavailable,
/// if any.
///
/// The attribute may come from another declaration, since unavailability
/// could be inherited from a parent declaration. This is a broader notion of
/// unavailability than is checked by \c AvailableAttr::isUnavailable.
Optional<std::pair<const AvailableAttr *, const Decl *>>
getSemanticUnavailableAttr() const;

// List the SPI groups declared with @_spi or inherited by this decl.
//
Expand Down
10 changes: 8 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3290,8 +3290,14 @@ ERROR(attr_not_on_decl_with_invalid_access_level,none,
(DeclAttribute, AccessLevel))

ERROR(attr_has_no_effect_decl_not_available_before,none,
"'%0' has no effect because %1 is not available before %2 %3",
(DeclAttribute, DeclName, StringRef, llvm::VersionTuple))
"'%0' has no effect because %select{getter for |setter for |}1%2 is not "
"available before %3 %4",
(DeclAttribute, unsigned, DeclName, StringRef, llvm::VersionTuple))

ERROR(attr_has_no_effect_on_unavailable_decl,none,
"'%0' has no effect because %select{getter for |setter for |}1%2 is "
"unavailable on %3",
(DeclAttribute, unsigned, DeclName, StringRef))

ERROR(attr_ambiguous_reference_to_decl,none,
"ambiguous reference to %0 in '@%1' attribute", (DeclNameRef, StringRef))
Expand Down
28 changes: 24 additions & 4 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -3480,17 +3480,37 @@ class RenamedDeclRequest
bool isCached() const { return true; }
};

class IsSemanticallyUnavailableRequest
: public SimpleRequest<IsSemanticallyUnavailableRequest,
bool(const Decl *),
using AvailableAttrDeclPair = std::pair<const AvailableAttr *, const Decl *>;

class SemanticAvailableRangeAttrRequest
: public SimpleRequest<SemanticAvailableRangeAttrRequest,
Optional<AvailableAttrDeclPair>(const Decl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

Optional<AvailableAttrDeclPair> evaluate(Evaluator &evaluator,
const Decl *decl) const;

public:
bool isCached() const { return true; }
};

class SemanticUnavailableAttrRequest
: public SimpleRequest<SemanticUnavailableAttrRequest,
Optional<AvailableAttrDeclPair>(const Decl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

bool evaluate(Evaluator &evaluator, const Decl *decl) const;
Optional<AvailableAttrDeclPair> evaluate(Evaluator &evaluator,
const Decl *decl) const;

public:
bool isCached() const { return true; }
Expand Down
7 changes: 5 additions & 2 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,11 @@ SWIFT_REQUEST(TypeChecker, GetImplicitSendableRequest,
SWIFT_REQUEST(TypeChecker, RenamedDeclRequest,
ValueDecl *(const ValueDecl *, const AvailableAttr *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, IsSemanticallyUnavailableRequest,
bool(const Decl *),
SWIFT_REQUEST(TypeChecker, SemanticAvailableRangeAttrRequest,
Optional<AvailableAttrDeclPair>(const Decl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, SemanticUnavailableAttrRequest,
Optional<AvailableAttrDeclPair>(const Decl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ClosureEffectsRequest,
FunctionType::ExtInfo(ClosureExpr *),
Expand Down
23 changes: 23 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,29 @@ const AvailableAttr *DeclAttributes::getNoAsync(const ASTContext &ctx) const {
return bestAttr;
}

const BackDeployAttr *
DeclAttributes::getBackDeploy(const ASTContext &ctx) const {
const BackDeployAttr *bestAttr = nullptr;

for (auto attr : *this) {
auto *backDeployAttr = dyn_cast<BackDeployAttr>(attr);
if (!backDeployAttr)
continue;

if (backDeployAttr->isInvalid() || !backDeployAttr->isActivePlatform(ctx))
continue;

// We have an attribute that is active for the platform, but
// is it more specific than our current best?
if (!bestAttr || inheritsAvailabilityFromPlatform(backDeployAttr->Platform,
bestAttr->Platform)) {
bestAttr = backDeployAttr;
}
}

return bestAttr;
}

void DeclAttributes::dump(const Decl *D) const {
StreamPrinter P(llvm::errs());
PrintOptions PO = PrintOptions::printDeclarations();
Expand Down
85 changes: 56 additions & 29 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ void AvailabilityInference::applyInferredAvailableAttrs(
}
}

/// Returns the decl that should be considered the parent decl of the given decl
/// when looking for inherited availability annotations.
static Decl *parentDeclForAvailability(const Decl *D) {
if (auto *AD = dyn_cast<AccessorDecl>(D))
return AD->getStorage();

if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
if (auto *NTD = ED->getExtendedNominal())
return NTD;
}

// Clang decls may be inaccurately parented rdar://53956555
if (D->hasClangNode())
return nullptr;

// Availability is inherited from the enclosing context.
return D->getDeclContext()->getInnermostDeclarationDeclContext();
}

/// Returns true if the introduced version in \p newAttr should be used instead
/// of the introduced version in \p prevAttr when both are attached to the same
/// declaration and refer to the active platform.
Expand All @@ -165,8 +184,9 @@ static bool isBetterThan(const AvailableAttr *newAttr,
prevAttr->Platform);
}

Optional<AvailabilityContext>
AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
const AvailableAttr *
AvailabilityInference::attrForAnnotatedAvailableRange(const Decl *D,
ASTContext &Ctx) {
const AvailableAttr *bestAvailAttr = nullptr;

for (auto Attr : D->getAttrs()) {
Expand All @@ -182,6 +202,30 @@ AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
bestAvailAttr = AvailAttr;
}

return bestAvailAttr;
}

Optional<AvailableAttrDeclPair>
SemanticAvailableRangeAttrRequest::evaluate(Evaluator &evaluator,
const Decl *decl) const {
if (auto attr = AvailabilityInference::attrForAnnotatedAvailableRange(
decl, decl->getASTContext()))
return std::make_pair(attr, decl);

if (auto *parent = parentDeclForAvailability(decl))
return parent->getSemanticAvailableRangeAttr();

return None;
}

Optional<AvailableAttrDeclPair> Decl::getSemanticAvailableRangeAttr() const {
auto &eval = getASTContext().evaluator;
return evaluateOrDefault(eval, SemanticAvailableRangeAttrRequest{this}, None);
}

Optional<AvailabilityContext>
AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
auto bestAvailAttr = attrForAnnotatedAvailableRange(D, Ctx);
if (!bestAvailAttr)
return None;

Expand All @@ -195,39 +239,22 @@ bool Decl::isAvailableAsSPI() const {
.isAvailableAsSPI();
}

bool IsSemanticallyUnavailableRequest::evaluate(Evaluator &evaluator,
const Decl *decl) const {
Optional<AvailableAttrDeclPair>
SemanticUnavailableAttrRequest::evaluate(Evaluator &evaluator,
const Decl *decl) const {
// Directly marked unavailable.
if (AvailableAttr::isUnavailable(decl))
return true;
if (auto attr = decl->getAttrs().getUnavailable(decl->getASTContext()))
return std::make_pair(attr, decl);

// If this is an extension, it's semantically unavailable if its nominal is,
// as there is no way to reference or construct the type.
if (auto *ext = dyn_cast<ExtensionDecl>(decl)) {
if (auto *nom = ext->getExtendedNominal()) {
if (nom->isSemanticallyUnavailable())
return true;
}
}
if (auto *parent = parentDeclForAvailability(decl))
return parent->getSemanticUnavailableAttr();

// If the parent decl is semantically unavailable, then this decl is too.
// For local contexts, this means it's a local decl in e.g an unavailable
// function, which cannot be accessed. For non-local contexts, this is a
// nested type or a member with an unavailable parent, which cannot be
// referenced.
// Similar to `AvailableAttr::isUnavailable`, don't apply this logic to
// Clang decls, as they may be inaccurately parented.
if (!decl->hasClangNode()) {
auto *DC = decl->getDeclContext();
if (auto *parentDecl = DC->getInnermostDeclarationDeclContext())
return parentDecl->isSemanticallyUnavailable();
}
return false;
return None;
}

bool Decl::isSemanticallyUnavailable() const {
Optional<AvailableAttrDeclPair> Decl::getSemanticUnavailableAttr() const {
auto &eval = getASTContext().evaluator;
return evaluateOrDefault(eval, IsSemanticallyUnavailableRequest{this}, false);
return evaluateOrDefault(eval, SemanticUnavailableAttrRequest{this}, None);
}

bool UnavailabilityReason::requiresDeploymentTargetOrEarlier(
Expand Down
15 changes: 4 additions & 11 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,19 +390,12 @@ Decl::getIntroducedOSVersion(PlatformKind Kind) const {

Optional<llvm::VersionTuple>
Decl::getBackDeployBeforeOSVersion(ASTContext &Ctx) const {
for (auto *attr : getAttrs()) {
if (auto *backDeployAttr = dyn_cast<BackDeployAttr>(attr)) {
if (backDeployAttr->isActivePlatform(Ctx) && backDeployAttr->Version) {
return backDeployAttr->Version;
}
}
}
if (auto *attr = getAttrs().getBackDeploy(Ctx))
return attr->Version;

// Accessors may inherit `@_backDeploy`.
if (getKind() == DeclKind::Accessor) {
return cast<AccessorDecl>(this)->getStorage()->getBackDeployBeforeOSVersion(
Ctx);
}
if (auto *AD = dyn_cast<AccessorDecl>(this))
return AD->getStorage()->getBackDeployBeforeOSVersion(Ctx);

return None;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/SIL/IR/SILProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ static bool shouldProfile(SILDeclRef Constant) {
// Do not profile AST nodes in unavailable contexts.
auto *DC = Constant.getInnermostDeclContext();
if (auto *D = DC->getInnermostDeclarationDeclContext()) {
if (D->isSemanticallyUnavailable()) {
if (D->getSemanticUnavailableAttr()) {
LLVM_DEBUG(llvm::dbgs() << "Skipping ASTNode: unavailable context\n");
return false;
}
Expand Down
45 changes: 38 additions & 7 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4520,6 +4520,8 @@ void AttributeChecker::checkBackDeployAttrs(ArrayRef<BackDeployAttr *> Attrs) {
auto *VD = cast<ValueDecl>(D);
std::map<PlatformKind, SourceLoc> seenPlatforms;

auto *ActiveAttr = D->getAttrs().getBackDeploy(Ctx);

for (auto *Attr : Attrs) {
// Back deployment only makes sense for public declarations.
if (diagnoseAndRemoveAttrIfDeclIsNonPublic(Attr, /*isError=*/true))
Expand Down Expand Up @@ -4559,18 +4561,47 @@ void AttributeChecker::checkBackDeployAttrs(ArrayRef<BackDeployAttr *> Attrs) {
continue;
}

// Require explicit availability for back deployed decls.
if (diagnoseMissingAvailability(Attr, Platform))
if (Ctx.LangOpts.DisableAvailabilityChecking)
continue;

// Availability conflicts can only be diagnosed for attributes that apply
// to the active platform.
if (Attr != ActiveAttr)
continue;

// Unavailable decls cannot be back deployed.
if (auto unavailableAttrPair = VD->getSemanticUnavailableAttr()) {
auto unavailableAttr = unavailableAttrPair.value().first;
DeclName name;
unsigned accessorKind;
std::tie(accessorKind, name) = getAccessorKindAndNameForDiagnostics(VD);
diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr,
accessorKind, name, prettyPlatformString(Platform));
diagnose(unavailableAttr->AtLoc, diag::availability_marked_unavailable,
accessorKind, name)
.highlight(unavailableAttr->getRange());
continue;
}

// Verify that the decl is available before the back deployment boundary.
// If it's not, the attribute doesn't make sense since the back deployment
// fallback could never be executed at runtime.
auto IntroVer = D->getIntroducedOSVersion(Platform);
if (Attr->Version <= IntroVer.value()) {
diagnose(AtLoc, diag::attr_has_no_effect_decl_not_available_before, Attr,
VD->getName(), prettyPlatformString(Platform), Attr->Version);
continue;
if (auto availableRangeAttrPair = VD->getSemanticAvailableRangeAttr()) {
auto availableAttr = availableRangeAttrPair.value().first;
if (Attr->Version <= availableAttr->Introduced.value()) {
DeclName name;
unsigned accessorKind;
std::tie(accessorKind, name) = getAccessorKindAndNameForDiagnostics(VD);
diagnose(AtLoc, diag::attr_has_no_effect_decl_not_available_before,
Attr, accessorKind, name, prettyPlatformString(Platform),
Attr->Version);
diagnose(availableAttr->AtLoc, diag::availability_introduced_in_version,
accessorKind, name,
prettyPlatformString(availableAttr->Platform),
*availableAttr->Introduced)
.highlight(availableAttr->getRange());
continue;
}
}
}
}
Expand Down
Loading