Skip to content

AST: Split AvailabilityConstraint classifications into separate Reason and Kind enums #79326

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
Feb 12, 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
72 changes: 50 additions & 22 deletions include/swift/AST/AvailabilityConstraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,23 @@ class Decl;
/// certain context.
class AvailabilityConstraint {
public:
enum class Kind {
enum class Reason {
/// 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
/// constraint.
AlwaysUnavailable,
UnconditionallyUnavailable,

/// 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
/// constraint.
Obsoleted,

/// The declaration is only available in a different version. For example,
/// The declaration is only available in a later 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,
IntroducedInLaterVersion,

/// The declaration is referenced in a context that does not have an
/// adequate minimum version constraint. For example, a reference to a
Expand All @@ -58,37 +58,69 @@ class AvailabilityConstraint {
/// kind of constraint can be satisfied by tightening the minimum
/// version of the context with `if #available(...)` or by adding or
/// adjusting an `@available` attribute.
IntroducedInNewerVersion,
IntroducedInLaterDynamicVersion,
};

/// Classifies constraints into different high level categories.
enum class Kind {
/// There are no contexts in which the declaration would be available.
Unavailable,

/// There are some contexts in which the declaration would be available if
/// additional constraints were added.
PotentiallyAvailable,
};

private:
llvm::PointerIntPair<SemanticAvailableAttr, 2, Kind> attrAndKind;
llvm::PointerIntPair<SemanticAvailableAttr, 2, Reason> attrAndReason;

AvailabilityConstraint(Kind kind, SemanticAvailableAttr attr)
: attrAndKind(attr, kind) {};
AvailabilityConstraint(Reason reason, SemanticAvailableAttr attr)
: attrAndReason(attr, reason) {};

public:
static AvailabilityConstraint
forAlwaysUnavailable(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Kind::AlwaysUnavailable, attr);
unconditionallyUnavailable(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Reason::UnconditionallyUnavailable, attr);
}

static AvailabilityConstraint forObsoleted(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Kind::Obsoleted, attr);
static AvailabilityConstraint obsoleted(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Reason::Obsoleted, attr);
}

static AvailabilityConstraint forRequiresVersion(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Kind::RequiresVersion, attr);
static AvailabilityConstraint
introducedInLaterVersion(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Reason::IntroducedInLaterVersion, attr);
}

static AvailabilityConstraint
forIntroducedInNewerVersion(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Kind::IntroducedInNewerVersion, attr);
introducedInLaterDynamicVersion(SemanticAvailableAttr attr) {
return AvailabilityConstraint(Reason::IntroducedInLaterDynamicVersion,
attr);
}

Kind getKind() const { return attrAndKind.getInt(); }
Reason getReason() const { return attrAndReason.getInt(); }
SemanticAvailableAttr getAttr() const {
return static_cast<SemanticAvailableAttr>(attrAndKind.getPointer());
return static_cast<SemanticAvailableAttr>(attrAndReason.getPointer());
}

Kind getKind() const {
switch (getReason()) {
case Reason::UnconditionallyUnavailable:
case Reason::Obsoleted:
case Reason::IntroducedInLaterVersion:
return Kind::Unavailable;
case Reason::IntroducedInLaterDynamicVersion:
return Kind::PotentiallyAvailable;
}
}

/// Returns true if the constraint cannot be satisfied at runtime.
bool isUnavailable() const { return getKind() == Kind::Unavailable; }

/// Returns true if the constraint is unsatisfied but could be satisfied at
/// runtime in a more constrained context.
bool isPotentiallyAvailable() const {
return getKind() == Kind::PotentiallyAvailable;
}

/// Returns the domain that the constraint applies to.
Expand All @@ -103,10 +135,6 @@ class AvailabilityConstraint {
std::optional<AvailabilityRange>
getRequiredNewerAvailabilityRange(ASTContext &ctx) const;

/// Returns true if this unmet requirement can be satisfied by introducing an
/// `if #available(...)` condition in source.
bool isConditionallySatisfiable() const;

/// Some availability constraints are active for type-checking but cannot
/// be translated directly into an `if #available(...)` runtime query.
bool isActiveForRuntimeQueries(ASTContext &ctx) const;
Expand Down
29 changes: 9 additions & 20 deletions lib/AST/AvailabilityConstraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,16 @@ PlatformKind AvailabilityConstraint::getPlatform() const {
std::optional<AvailabilityRange>
AvailabilityConstraint::getRequiredNewerAvailabilityRange(
ASTContext &ctx) const {
switch (getKind()) {
case Kind::AlwaysUnavailable:
case Kind::RequiresVersion:
case Kind::Obsoleted:
switch (getReason()) {
case Reason::UnconditionallyUnavailable:
case Reason::Obsoleted:
case Reason::IntroducedInLaterVersion:
return std::nullopt;
case Kind::IntroducedInNewerVersion:
case Reason::IntroducedInLaterDynamicVersion:
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;
Expand Down Expand Up @@ -84,7 +73,7 @@ swift::getAvailabilityConstraintForAttr(const Decl *decl,
return std::nullopt;

if (attr.isUnconditionallyUnavailable())
return AvailabilityConstraint::forAlwaysUnavailable(attr);
return AvailabilityConstraint::unconditionallyUnavailable(attr);

auto &ctx = decl->getASTContext();
auto deploymentVersion = attr.getActiveVersion(ctx);
Expand All @@ -101,16 +90,16 @@ swift::getAvailabilityConstraintForAttr(const Decl *decl,
}

if (obsoletedVersion && *obsoletedVersion <= deploymentVersion)
return AvailabilityConstraint::forObsoleted(attr);
return AvailabilityConstraint::obsoleted(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);
return AvailabilityConstraint::introducedInLaterDynamicVersion(attr);
} else if (!deploymentRange.isContainedIn(introducedRange)) {
return AvailabilityConstraint::forRequiresVersion(attr);
return AvailabilityConstraint::introducedInLaterVersion(attr);
}

return std::nullopt;
Expand Down
10 changes: 5 additions & 5 deletions lib/AST/AvailabilityContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ bool AvailabilityContext::Info::constrainWith(
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:
switch (constraint.getReason()) {
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
case AvailabilityConstraint::Reason::Obsoleted:
case AvailabilityConstraint::Reason::IntroducedInLaterVersion:
isConstrained |= constrainUnavailability(domain);
break;
case AvailabilityConstraint::Kind::IntroducedInNewerVersion:
case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion:
// FIXME: [availability] Support versioning for other kinds of domains.
DEBUG_ASSERT(domain.isPlatform());
if (domain.isPlatform())
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/CSSyntacticElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2467,7 +2467,7 @@ class ResultBuilderRewriter : public SyntacticElementSolutionApplication {

auto constraint = getUnsatisfiedAvailabilityConstraint(
nominal, context.getAsDeclContext(), loc);
if (constraint && constraint->isConditionallySatisfiable()) {
if (constraint && constraint->isPotentiallyAvailable()) {
auto &ctx = getASTContext();
ctx.Diags.diagnose(loc,
diag::result_builder_missing_limited_availability,
Expand Down
6 changes: 3 additions & 3 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4918,11 +4918,11 @@ bool ConstraintSystem::isReadOnlyKeyPathComponent(
// If the setter is unavailable, then the keypath ought to be read-only
// in this context.
if (auto setter = storage->getOpaqueAccessor(AccessorKind::Set)) {
// FIXME: Fully unavailable setters should cause the key path to be
// readonly too.
// FIXME: [availability] Fully unavailable setters should cause the key path
// to be readonly too.
auto constraint =
getUnsatisfiedAvailabilityConstraint(setter, DC, referenceLoc);
if (constraint && constraint->isConditionallySatisfiable())
if (constraint && constraint->isPotentiallyAvailable())
return true;
}

Expand Down
8 changes: 4 additions & 4 deletions lib/Sema/DerivedConformanceRawRepresentable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,15 +247,15 @@ checkAvailability(const EnumElementDecl *elt,
if (!constraint)
return true;

// Is it never available?
if (constraint->isUnavailable())
return false;

// Some constraints are active for type checking but can't translate to
// runtime restrictions.
if (!constraint->isActiveForRuntimeQueries(C))
return true;

// Is it never available?
if (!constraint->isConditionallySatisfiable())
return false;

// It's conditionally available; create a version constraint and return true.
auto platform = constraint->getPlatform();
auto range = constraint->getRequiredNewerAvailabilityRange(C);
Expand Down
43 changes: 22 additions & 21 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3013,12 +3013,12 @@ bool shouldHideDomainNameForConstraintDiagnostic(

case AvailabilityDomain::Kind::PackageDescription:
case AvailabilityDomain::Kind::SwiftLanguage:
switch (constraint.getKind()) {
case AvailabilityConstraint::Kind::AlwaysUnavailable:
case AvailabilityConstraint::Kind::IntroducedInNewerVersion:
switch (constraint.getReason()) {
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
case AvailabilityConstraint::Reason::IntroducedInLaterVersion:
return false;
case AvailabilityConstraint::Kind::RequiresVersion:
case AvailabilityConstraint::Kind::Obsoleted:
case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion:
case AvailabilityConstraint::Reason::Obsoleted:
return true;
}
}
Expand All @@ -3031,7 +3031,7 @@ bool diagnoseExplicitUnavailability(SourceLoc loc,
const ExportContext &where,
bool warnIfConformanceUnavailablePreSwift6,
bool preconcurrency) {
if (constraint.isConditionallySatisfiable())
if (!constraint.isUnavailable())
return false;

// Invertible protocols are never unavailable.
Expand Down Expand Up @@ -3062,24 +3062,24 @@ bool diagnoseExplicitUnavailability(SourceLoc loc,
.limitBehaviorWithPreconcurrency(behavior, preconcurrency)
.warnUntilSwiftVersionIf(warnIfConformanceUnavailablePreSwift6, 6);

switch (constraint.getKind()) {
case AvailabilityConstraint::Kind::AlwaysUnavailable:
switch (constraint.getReason()) {
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
diags
.diagnose(ext, diag::conformance_availability_marked_unavailable, type,
proto)
.highlight(attr.getParsedAttr()->getRange());
break;
case AvailabilityConstraint::Kind::RequiresVersion:
case AvailabilityConstraint::Reason::IntroducedInLaterVersion:
diags.diagnose(ext, diag::conformance_availability_introduced_in_version,
type, proto, versionedPlatform, *attr.getIntroduced());
break;
case AvailabilityConstraint::Kind::Obsoleted:
case AvailabilityConstraint::Reason::Obsoleted:
diags
.diagnose(ext, diag::conformance_availability_obsoleted, type, proto,
versionedPlatform, *attr.getObsoleted())
.highlight(attr.getParsedAttr()->getRange());
break;
case AvailabilityConstraint::Kind::IntroducedInNewerVersion:
case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion:
llvm_unreachable("unexpected constraint");
}
return true;
Expand Down Expand Up @@ -3108,20 +3108,21 @@ swift::getUnsatisfiedAvailabilityConstraint(
if ((attr->isSwiftLanguageModeSpecific() ||
attr->isPackageDescriptionVersionSpecific()) &&
attr->getIntroduced().has_value())
return AvailabilityConstraint::forRequiresVersion(*attr);
return AvailabilityConstraint::introducedInLaterVersion(*attr);

return AvailabilityConstraint::forAlwaysUnavailable(*attr);
return AvailabilityConstraint::unconditionallyUnavailable(*attr);

case AvailableVersionComparison::Obsoleted:
return AvailabilityConstraint::forObsoleted(*attr);
return AvailabilityConstraint::obsoleted(*attr);
}
}

// Check whether the declaration is available in a newer platform version.
if (auto rangeAttr = decl->getAvailableAttrForPlatformIntroduction()) {
auto range = rangeAttr->getIntroducedRange(ctx);
if (!availabilityContext.getPlatformRange().isContainedIn(range))
return AvailabilityConstraint::forIntroducedInNewerVersion(*rangeAttr);
return AvailabilityConstraint::introducedInLaterDynamicVersion(
*rangeAttr);
}

return std::nullopt;
Expand Down Expand Up @@ -3463,7 +3464,7 @@ bool diagnoseExplicitUnavailability(
const ExportContext &Where, DeclAvailabilityFlags Flags,
llvm::function_ref<void(InFlightDiagnostic &, StringRef)>
attachRenameFixIts) {
if (constraint.isConditionallySatisfiable())
if (!constraint.isUnavailable())
return false;

auto Attr = constraint.getAttr();
Expand Down Expand Up @@ -3527,24 +3528,24 @@ bool diagnoseExplicitUnavailability(
}

auto sourceRange = Attr.getParsedAttr()->getRange();
switch (constraint.getKind()) {
case AvailabilityConstraint::Kind::AlwaysUnavailable:
switch (constraint.getReason()) {
case AvailabilityConstraint::Reason::UnconditionallyUnavailable:
diags.diagnose(D, diag::availability_marked_unavailable, D)
.highlight(sourceRange);
break;
case AvailabilityConstraint::Kind::RequiresVersion:
case AvailabilityConstraint::Reason::IntroducedInLaterVersion:
diags
.diagnose(D, diag::availability_introduced_in_version, D,
versionedPlatform, *Attr.getIntroduced())
.highlight(sourceRange);
break;
case AvailabilityConstraint::Kind::Obsoleted:
case AvailabilityConstraint::Reason::Obsoleted:
diags
.diagnose(D, diag::availability_obsoleted, D, versionedPlatform,
*Attr.getObsoleted())
.highlight(sourceRange);
break;
case AvailabilityConstraint::Kind::IntroducedInNewerVersion:
case AvailabilityConstraint::Reason::IntroducedInLaterDynamicVersion:
llvm_unreachable("unexpected constraint");
break;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ static EnumElementDecl *extractEnumElement(DeclContext *DC, SourceLoc UseLoc,
if (auto constraint =
getUnsatisfiedAvailabilityConstraint(constant, DC, UseLoc)) {
// Only diagnose explicit unavailability.
if (!constraint->isConditionallySatisfiable())
if (constraint->isUnavailable())
diagnoseDeclAvailability(constant, UseLoc, nullptr,
ExportContext::forFunctionBody(DC, UseLoc));
}
Expand Down
Loading