Skip to content

Commit

Permalink
Merge pull request swiftlang#77218 from tshortli/unmet-availability-r…
Browse files Browse the repository at this point in the history
…equirement
  • Loading branch information
tshortli authored Oct 25, 2024
2 parents 65f7c9e + c02dccf commit 5c7509b
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 23 deletions.
13 changes: 9 additions & 4 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,15 +350,20 @@ class AvailabilityInference {

static AvailabilityRange inferForType(Type t);

/// Returns the context where a declaration is available
/// We assume a declaration without an annotation is always available.
/// Returns the range of platform versions in which the decl is available.
static AvailabilityRange availableRange(const Decl *D);

/// Returns the range of platform versions in which the decl is available and
/// the attribute which determined this range (which may be `nullptr` if the
/// declaration is always available.
static std::pair<AvailabilityRange, const AvailableAttr *>
availableRangeAndAttr(const Decl *D);

/// Returns true is the declaration is `@_spi_available`.
static bool isAvailableAsSPI(const Decl *D);

/// Returns the availability context for a declaration with the given
/// @available attribute.
/// Returns the range of platform versions in which a declaration with the
/// given `@available` attribute is available.
///
/// NOTE: The attribute must be active on the current platform.
static AvailabilityRange availableRange(const AvailableAttr *attr,
Expand Down
14 changes: 10 additions & 4 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -580,12 +580,18 @@ static const AvailableAttr *attrForAvailableRange(const Decl *D) {
return nullptr;
}

AvailabilityRange AvailabilityInference::availableRange(const Decl *D) {
if (auto attr = attrForAvailableRange(D))
return availableRange(attr, D->getASTContext());
std::pair<AvailabilityRange, const AvailableAttr *>
AvailabilityInference::availableRangeAndAttr(const Decl *D) {
if (auto attr = attrForAvailableRange(D)) {
return {availableRange(attr, D->getASTContext()), attr};
}

// Treat unannotated declarations as always available.
return AvailabilityRange::alwaysAvailable();
return {AvailabilityRange::alwaysAvailable(), nullptr};
}

AvailabilityRange AvailabilityInference::availableRange(const Decl *D) {
return availableRangeAndAttr(D).first;
}

bool AvailabilityInference::isAvailableAsSPI(const Decl *D) {
Expand Down
98 changes: 83 additions & 15 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,19 @@ ExportContext::getExportabilityReason() const {
return std::nullopt;
}

std::optional<AvailabilityRange>
UnmetAvailabilityRequirement::getRequiredNewerAvailabilityRange(
ASTContext &ctx) const {
switch (kind) {
case Kind::AlwaysUnavailable:
case Kind::RequiresVersion:
case Kind::Obsoleted:
return std::nullopt;
case Kind::IntroducedInNewerVersion:
return AvailabilityInference::availableRange(attr, ctx);
}
}

/// Returns the first availability attribute on the declaration that is active
/// on the target platform.
static const AvailableAttr *getActiveAvailableAttribute(const Decl *D,
Expand Down Expand Up @@ -345,8 +358,9 @@ static bool computeContainedByDeploymentTarget(TypeRefinementContext *TRC,
/// 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, const ExportContext &where, const AvailableAttr *attr) {
auto referencedPlatform = where.getUnavailablePlatformKind();
const Decl *D, AvailabilityContext availabilityContext,
const AvailableAttr *attr) {
auto referencedPlatform = availabilityContext.getUnavailablePlatformKind();
if (!referencedPlatform)
return false;

Expand Down Expand Up @@ -374,7 +388,7 @@ ExportContext::shouldDiagnoseDeclAsUnavailable(const Decl *D) const {
if (!attr)
return nullptr;

if (isInsideCompatibleUnavailableDeclaration(D, *this, attr))
if (isInsideCompatibleUnavailableDeclaration(D, Availability, attr))
return nullptr;

return attr;
Expand Down Expand Up @@ -1496,7 +1510,8 @@ TypeChecker::checkDeclarationAvailability(const Decl *D,
// Skip computing potential unavailability if the declaration is explicitly
// unavailable and the context is also unavailable.
if (const AvailableAttr *Attr = AvailableAttr::isUnavailable(D))
if (isInsideCompatibleUnavailableDeclaration(D, Where, Attr))
if (isInsideCompatibleUnavailableDeclaration(D, Where.getAvailability(),
Attr))
return std::nullopt;

if (isDeclarationUnavailable(D, Where.getDeclContext(), [&Where] {
Expand Down Expand Up @@ -3063,6 +3078,51 @@ bool diagnoseExplicitUnavailability(
return true;
}

std::optional<UnmetAvailabilityRequirement>
swift::checkDeclarationAvailability(const Decl *decl,
const DeclContext *declContext,
AvailabilityContext availabilityContext) {
auto &ctx = declContext->getASTContext();
if (ctx.LangOpts.DisableAvailabilityChecking)
return std::nullopt;

// Generic parameters are always available.
if (isa<GenericTypeParamDecl>(decl))
return std::nullopt;

if (auto attr = AvailableAttr::isUnavailable(decl)) {
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->isLanguageVersionSpecific() ||
attr->isPackageDescriptionVersionSpecific()) &&
attr->Introduced.has_value())
return UnmetAvailabilityRequirement::forRequiresVersion(attr);

return UnmetAvailabilityRequirement::forAlwaysUnavailable(attr);

case AvailableVersionComparison::Obsoleted:
return UnmetAvailabilityRequirement::forObsoleted(attr);
}
}

// Check whether the declaration is available in a newer platform version.
auto rangeAndAttr = AvailabilityInference::availableRangeAndAttr(decl);
if (!availabilityContext.getPlatformRange().isContainedIn(rangeAndAttr.first))
return UnmetAvailabilityRequirement::forIntroducedInNewerVersion(
rangeAndAttr.second);

return std::nullopt;
}


/// Check if this is a subscript declaration inside String or
/// Substring that returns String, and if so return true.
bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) {
Expand Down Expand Up @@ -4055,8 +4115,20 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
return false;
}

if (diagnoseExplicitUnavailability(D, R, Where, call, Flags))
return true;
auto *DC = Where.getDeclContext();
auto &ctx = DC->getASTContext();
auto unmetRequirement =
checkDeclarationAvailability(D, DC, Where.getAvailability());
auto requiredRange =
unmetRequirement
? unmetRequirement->getRequiredNewerAvailabilityRange(ctx)
: std::nullopt;

if (unmetRequirement && !requiredRange) {
// FIXME: diagnoseExplicitUnavailability should take an unmet requirement
if (diagnoseExplicitUnavailability(D, R, Where, call, Flags))
return true;
}

if (diagnoseDeclAsyncAvailability(D, R, call, Where))
return true;
Expand All @@ -4077,25 +4149,21 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
return false;

// Diagnose (and possibly signal) for potential unavailability
auto maybeUnavail = TypeChecker::checkDeclarationAvailability(D, Where);
if (!maybeUnavail.has_value())
if (!requiredRange)
return false;

auto requiredAvailability = maybeUnavail.value();
auto *DC = Where.getDeclContext();
auto &ctx = DC->getASTContext();
if (Flags.contains(
DeclAvailabilityFlag::
AllowPotentiallyUnavailableAtOrBelowDeploymentTarget) &&
requiresDeploymentTargetOrEarlier(requiredAvailability, ctx))
requiresDeploymentTargetOrEarlier(*requiredRange, ctx))
return false;

if (accessor) {
bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout);
diagnosePotentialAccessorUnavailability(accessor, R, DC,
requiredAvailability, forInout);
diagnosePotentialAccessorUnavailability(accessor, R, DC, *requiredRange,
forInout);
} else {
if (!diagnosePotentialUnavailability(D, R, DC, requiredAvailability))
if (!diagnosePotentialUnavailability(D, R, DC, *requiredRange))
return false;
}

Expand Down
78 changes: 78 additions & 0 deletions lib/Sema/TypeCheckAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ class ExportContext {

DeclContext *getDeclContext() const { return DC; }

AvailabilityContext getAvailability() const { return Availability; }

AvailabilityRange getAvailabilityRange() const {
return Availability.getPlatformRange();
}
Expand Down Expand Up @@ -198,6 +200,74 @@ class ExportContext {
const AvailableAttr *shouldDiagnoseDeclAsUnavailable(const Decl *decl) const;
};

/// Represents the reason a declaration is considered unavailable in a certain
/// context.
class UnmetAvailabilityRequirement {
public:
enum class Kind {
/// The declaration is referenced in a context in which it is
/// generally unavailable. For example, a reference to a declaration that is
/// unavailable on macOS from a context that may execute on macOS has this
/// unmet requirement.
AlwaysUnavailable,

/// The declaration is referenced in a context in which it is considered
/// obsolete. For example, a reference to a declaration that is obsolete in
/// macOS 13 from a context that may execute on macOS 13 or later has this
/// unmet requirement.
Obsoleted,

/// The declaration is only available in a different version. For example,
/// the declaration might only be introduced in the Swift 6 language mode
/// while the module is being compiled in the Swift 5 language mode.
RequiresVersion,

/// The declaration is referenced in a context that does not have an
/// adequate minimum version constraint. For example, a reference to a
/// declaration that is introduced in macOS 13 from a context that may
/// execute on earlier versions of macOS has this unmet requirement. This
/// kind of unmet requirement can be addressed by tightening the minimum
/// version of the context with `if #available(...)` or by adding or
/// adjusting an `@available` attribute.
IntroducedInNewerVersion,
};

private:
Kind kind;
const AvailableAttr *attr;

UnmetAvailabilityRequirement(Kind kind, const AvailableAttr *attr)
: kind(kind), attr(attr){};

public:
static UnmetAvailabilityRequirement
forAlwaysUnavailable(const AvailableAttr *attr) {
return UnmetAvailabilityRequirement(Kind::AlwaysUnavailable, attr);
}

static UnmetAvailabilityRequirement forObsoleted(const AvailableAttr *attr) {
return UnmetAvailabilityRequirement(Kind::Obsoleted, attr);
}

static UnmetAvailabilityRequirement
forRequiresVersion(const AvailableAttr *attr) {
return UnmetAvailabilityRequirement(Kind::RequiresVersion, attr);
}

static UnmetAvailabilityRequirement
forIntroducedInNewerVersion(const AvailableAttr *attr) {
return UnmetAvailabilityRequirement(Kind::IntroducedInNewerVersion, attr);
}

Kind getKind() const { return kind; }
const AvailableAttr *getAttr() const { return attr; }

/// Returns the required range for `IntroducedInNewerVersion` requirements, or
/// `std::nullopt` otherwise.
std::optional<AvailabilityRange>
getRequiredNewerAvailabilityRange(ASTContext &ctx) const;
};

/// Check if a declaration is exported as part of a module's external interface.
/// This includes public and @usableFromInline decls.
bool isExported(const ValueDecl *VD);
Expand Down Expand Up @@ -248,6 +318,14 @@ bool diagnoseExplicitUnavailability(const ValueDecl *D, SourceRange R,
const Expr *call,
DeclAvailabilityFlags Flags = std::nullopt);

/// 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 unmet availability requirements.
/// Returns `std::nullopt` if the declaration is available.
std::optional<UnmetAvailabilityRequirement>
checkDeclarationAvailability(const Decl *decl, const DeclContext *declContext,
AvailabilityContext availabilityContext);

/// Diagnose uses of the runtime support of the given type, such as
/// type metadata and dynamic casting.
///
Expand Down

0 comments on commit 5c7509b

Please sign in to comment.