Skip to content

Sema: Introduce UnmetAvailabilityRequirement #77218

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 3 commits into from
Oct 25, 2024
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
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