Skip to content

AST: Teach AvailabilityContext to represent version-less availability #79807

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
30 changes: 21 additions & 9 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3308,29 +3308,41 @@ class SemanticAvailableAttr final {
/// The source range of the `introduced:` version component.
SourceRange getIntroducedSourceRange() const { return attr->IntroducedRange; }

/// Returns the effective availability range for the attribute's `introduced:`
/// component (remapping or canonicalizing if necessary).
AvailabilityRange getIntroducedRange(const ASTContext &Ctx) const;
/// Returns the effective introduction range indicated by this attribute.
/// This may correspond to the version specified by the `introduced:`
/// component (remapped or canonicalized if necessary) or it may be "always"
/// for an attribute indicating availability in a version-less domain. Returns
/// `std::nullopt` if the attribute does not indicate introduction.
std::optional<AvailabilityRange>
getIntroducedRange(const ASTContext &Ctx) const;

/// The version tuple for the `deprecated:` component.
std::optional<llvm::VersionTuple> getDeprecated() const;

/// The source range of the `deprecated:` version component.
SourceRange getDeprecatedSourceRange() const { return attr->DeprecatedRange; }

/// Returns the effective availability range for the attribute's `deprecated:`
/// component (remapping or canonicalizing if necessary).
AvailabilityRange getDeprecatedRange(const ASTContext &Ctx) const;
/// Returns the effective deprecation range indicated by this attribute.
/// This may correspond to the version specified by the `deprecated:`
/// component (remapped or canonicalized if necessary) or it may be "always"
/// for an unconditional deprecation attribute. Returns `std::nullopt` if the
/// attribute does not indicate deprecation.
std::optional<AvailabilityRange>
getDeprecatedRange(const ASTContext &Ctx) const;

/// The version tuple for the `obsoleted:` component.
std::optional<llvm::VersionTuple> getObsoleted() const;

/// The source range of the `obsoleted:` version component.
SourceRange getObsoletedSourceRange() const { return attr->ObsoletedRange; }

/// Returns the effective availability range for the attribute's `obsoleted:`
/// component (remapping or canonicalizing if necessary).
AvailabilityRange getObsoletedRange(const ASTContext &Ctx) const;
/// Returns the effective obsoletion range indicated by this attribute.
/// This always corresponds to the version specified by the `obsoleted:`
/// component (remapped or canonicalized if necessary). Returns `std::nullopt`
/// if the attribute does not indicate obsoletion (note that unavailability is
/// separate from obsoletion.
std::optional<AvailabilityRange>
getObsoletedRange(const ASTContext &Ctx) const;

/// Returns the `message:` field of the attribute, or an empty string.
StringRef getMessage() const { return attr->Message; }
Expand Down
13 changes: 13 additions & 0 deletions include/swift/AST/AvailabilityContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,15 @@ class AvailabilityContext {

/// Returns the range of platform versions which may execute code in the
/// availability context, starting at its introduction version.
// FIXME: [availability] Remove; superseded by getAvailableRange().
AvailabilityRange getPlatformRange() const;

/// Returns the range which is available for `domain` in this context. If
/// there are not any constraints established for `domain`, returns
/// `std::nullopt`.
std::optional<AvailabilityRange>
getAvailabilityRange(AvailabilityDomain domain, const ASTContext &ctx) const;

/// Returns true if this context contains any unavailable domains.
bool isUnavailable() const;

Expand All @@ -80,9 +87,15 @@ class AvailabilityContext {
const ASTContext &ctx);

/// Constrain the platform availability range with `platformRange`.
// FIXME: [availability] Remove; superseded by constrainWithAvailableRange().
void constrainWithPlatformRange(const AvailabilityRange &platformRange,
const ASTContext &ctx);

/// Constrain the available range for `domain` by `range`.
void constrainWithAvailabilityRange(const AvailabilityRange &range,
AvailabilityDomain domain,
const ASTContext &ctx);

/// Constrain the context by adding \p domain to the set of unavailable
/// domains.
void constrainWithUnavailableDomain(AvailabilityDomain domain,
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/AvailabilityContextStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class AvailabilityContext::DomainInfo final {
AvailabilityRange getRange() const { return range; }
bool isUnavailable() const { return range.isKnownUnreachable(); }

bool constrainRange(const AvailabilityRange &range);

void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddPointer(domain.getOpaqueValue());
range.getRawVersionRange().Profile(ID);
Expand Down
11 changes: 11 additions & 0 deletions include/swift/AST/AvailabilityDomain.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,24 @@ class AvailabilityDomain final {
/// version ranges.
bool isVersioned() const;

/// Returns true if availability of the domain can be refined using
/// `@available` attributes and `if #available` queries. If not, then the
/// domain's availability is fixed by compilation settings. For example,
/// macOS platform availability supports contextual refinement, whereas Swift
/// language availability does not.
bool supportsContextRefinement() const;

/// Returns true if the domain supports `#available`/`#unavailable` queries.
bool supportsQueries() const;

/// Returns true if this domain is considered active in the current
/// compilation context.
bool isActive(const ASTContext &ctx) const;

/// Returns true if this domain is a platform domain that is contained by the
/// set of active platform-specific domains.
bool isActiveForTargetPlatform(const ASTContext &ctx) const;

/// Returns the minimum available range for the attribute's domain. For
/// example, for the domain of the platform that compilation is targeting,
/// this will be the deployment target. For the Swift language domain, this
Expand Down
28 changes: 12 additions & 16 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6823,20 +6823,15 @@ NOTE(availability_decl_more_than_enclosing_here, none,
"enclosing scope requires availability of %0 %1 or newer",
(AvailabilityDomain, AvailabilityRange))

ERROR(availability_decl_only_version_newer, none,
"%0 is only available in %1 %2 or newer",
(const ValueDecl *, AvailabilityDomain, AvailabilityRange))

ERROR(availability_decl_only_version_newer_for_clients, none,
"%0 is only available in %1 %2 or newer; clients of %3 may have a lower"
" deployment target",
(const ValueDecl *, AvailabilityDomain, AvailabilityRange, ModuleDecl *))
ERROR(availability_decl_only_in, none,
"%0 is only available in %1%select{| %3 or newer}2",
(const ValueDecl *, AvailabilityDomain, bool, AvailabilityRange))

WARNING(availability_decl_only_version_newer_for_clients_warn, none,
"%0 is only available in %1 %2 or newer; clients of %3 may have a lower"
" deployment target",
(const ValueDecl *, AvailabilityDomain, AvailabilityRange,
ModuleDecl *))
ERROR(availability_decl_only_in_for_clients, none,
"%0 is only available in %1%select{| %3 or newer}2"
"%select{|; clients of %4 may have a lower deployment target}2",
(const ValueDecl *, AvailabilityDomain, bool, AvailabilityRange,
ModuleDecl *))

ERROR(availability_opaque_types_only_version_newer, none,
"'some' return types are only available in %0 %1 or newer",
Expand Down Expand Up @@ -6884,9 +6879,10 @@ FIXIT(insert_available_attr,
"@available(%0 %1, *)\n%2",
(StringRef, StringRef, StringRef))

ERROR(availability_inout_accessor_only_version_newer, none,
"cannot pass as inout because %0 is only available in %1 %2 or newer",
(const ValueDecl *, AvailabilityDomain, AvailabilityRange))
ERROR(availability_inout_accessor_only_in, none,
"cannot pass as inout because %0 is only available in %1"
"%select{| %3 or newer}2",
(const ValueDecl *, AvailabilityDomain, bool, AvailabilityRange))

ERROR(availability_query_required_for_platform, none,
"condition required for target platform '%0'", (StringRef))
Expand Down
56 changes: 42 additions & 14 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,13 +453,13 @@ std::optional<SemanticAvailableAttr> Decl::getDeprecatedAttr() const {
return attr;

auto deprecatedRange = attr.getDeprecatedRange(ctx);
if (deprecatedRange.isKnownUnreachable())
if (!deprecatedRange)
continue;

// We treat the declaration as deprecated if it is deprecated on
// all deployment targets.
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
if (deploymentRange && deploymentRange->isContainedIn(deprecatedRange))
if (deploymentRange && deploymentRange->isContainedIn(*deprecatedRange))
result.emplace(attr);
}
return result;
Expand All @@ -475,13 +475,13 @@ std::optional<SemanticAvailableAttr> Decl::getSoftDeprecatedAttr() const {
continue;

auto deprecatedRange = attr.getDeprecatedRange(ctx);
if (deprecatedRange.isKnownUnreachable())
if (!deprecatedRange)
continue;

// We treat the declaration as soft-deprecated if it is deprecated in a
// future version.
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
if (!deploymentRange || !deploymentRange->isContainedIn(deprecatedRange))
if (!deploymentRange || !deploymentRange->isContainedIn(*deprecatedRange))
result.emplace(attr);
}
return result;
Expand Down Expand Up @@ -705,7 +705,8 @@ AvailabilityRange AvailabilityInference::annotatedAvailableRangeForAttr(
}

if (bestAvailAttr)
return bestAvailAttr->getIntroducedRange(ctx);
return bestAvailAttr->getIntroducedRange(ctx).value_or(
AvailabilityRange::alwaysAvailable());

return AvailabilityRange::alwaysAvailable();
}
Expand Down Expand Up @@ -737,8 +738,10 @@ Decl::getAvailableAttrForPlatformIntroduction(bool checkExtension) const {
}

AvailabilityRange AvailabilityInference::availableRange(const Decl *D) {
// ALLANXXX
if (auto attr = D->getAvailableAttrForPlatformIntroduction())
return attr->getIntroducedRange(D->getASTContext());
return attr->getIntroducedRange(D->getASTContext())
.value_or(AvailabilityRange::alwaysAvailable());

return AvailabilityRange::alwaysAvailable();
}
Expand Down Expand Up @@ -854,13 +857,29 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {
return std::nullopt;
}

AvailabilityRange
std::optional<AvailabilityRange>
SemanticAvailableAttr::getIntroducedRange(const ASTContext &Ctx) const {
DEBUG_ASSERT(getDomain().isActive(Ctx));

auto *attr = getParsedAttr();
if (!attr->getRawIntroduced().has_value())
return AvailabilityRange::alwaysAvailable();
if (!attr->getRawIntroduced().has_value()) {
// For versioned domains, an "introduced:" version is always required to
// indicate introduction.
if (getDomain().isVersioned())
return std::nullopt;

// For version-less domains, an attribute that does not indicate some other
// kind of unconditional availability constraint implicitly specifies that
// the decl is available in all versions of the domain.
switch (attr->getKind()) {
case AvailableAttr::Kind::Default:
return AvailabilityRange::alwaysAvailable();
case AvailableAttr::Kind::Deprecated:
case AvailableAttr::Kind::Unavailable:
case AvailableAttr::Kind::NoAsync:
return std::nullopt;
}
}

llvm::VersionTuple introducedVersion = getIntroduced().value();
AvailabilityDomain unusedDomain;
Expand All @@ -878,13 +897,20 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getDeprecated() const {
return std::nullopt;
}

AvailabilityRange
std::optional<AvailabilityRange>
SemanticAvailableAttr::getDeprecatedRange(const ASTContext &Ctx) const {
DEBUG_ASSERT(getDomain().isActive(Ctx));

auto *attr = getParsedAttr();
if (!attr->getRawDeprecated().has_value())
return AvailabilityRange::neverAvailable();
if (!attr->getRawDeprecated().has_value()) {
// Regardless of the whether the domain supports versions or not, an
// unconditional deprecation attribute indicates the decl is always
// deprecated.
if (isUnconditionallyDeprecated())
return AvailabilityRange::alwaysAvailable();

return std::nullopt;
}

llvm::VersionTuple deprecatedVersion = getDeprecated().value();
AvailabilityDomain unusedDomain;
Expand All @@ -902,13 +928,15 @@ std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
return std::nullopt;
}

AvailabilityRange
std::optional<AvailabilityRange>
SemanticAvailableAttr::getObsoletedRange(const ASTContext &Ctx) const {
DEBUG_ASSERT(getDomain().isActive(Ctx));

auto *attr = getParsedAttr();

// Obsoletion always requires a version.
if (!attr->getRawObsoleted().has_value())
return AvailabilityRange::neverAvailable();
return std::nullopt;

llvm::VersionTuple obsoletedVersion = getObsoleted().value();
AvailabilityDomain unusedDomain;
Expand Down
33 changes: 21 additions & 12 deletions lib/AST/AvailabilityConstraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,27 +140,36 @@ static std::optional<AvailabilityConstraint>
getAvailabilityConstraintForAttr(const Decl *decl,
const SemanticAvailableAttr &attr,
const AvailabilityContext &context) {
// Is the decl unconditionally unavailable?
if (attr.isUnconditionallyUnavailable())
return AvailabilityConstraint::unconditionallyUnavailable(attr);

auto &ctx = decl->getASTContext();
auto deploymentRange = attr.getDomain().getDeploymentRange(ctx);
auto domain = attr.getDomain();
auto deploymentRange = domain.getDeploymentRange(ctx);

// Is the decl obsoleted in the deployment context?
if (auto obsoletedRange = attr.getObsoletedRange(ctx)) {
if (deploymentRange && deploymentRange->isContainedIn(*obsoletedRange))
return AvailabilityConstraint::obsoleted(attr);
}

auto obsoletedRange = attr.getObsoletedRange(ctx);
if (deploymentRange && deploymentRange->isContainedIn(obsoletedRange))
return AvailabilityConstraint::obsoleted(attr);
// Is the decl not yet introduced in the local context?
if (auto introducedRange = attr.getIntroducedRange(ctx)) {
if (domain.supportsContextRefinement()) {
auto availableRange = context.getAvailabilityRange(domain, ctx);
if (!availableRange || !availableRange->isContainedIn(*introducedRange))
return AvailabilityConstraint::potentiallyUnavailable(attr);

AvailabilityRange introducedRange = attr.getIntroducedRange(ctx);
return std::nullopt;
}

// FIXME: [availability] Expand this to cover custom versioned domains
if (attr.isPlatformSpecific()) {
if (!context.getPlatformRange().isContainedIn(introducedRange))
return AvailabilityConstraint::potentiallyUnavailable(attr);
} else if (deploymentRange &&
!deploymentRange->isContainedIn(introducedRange)) {
return AvailabilityConstraint::unavailableForDeployment(attr);
// Is the decl not yet introduced in the deployment context?
if (deploymentRange && !deploymentRange->isContainedIn(*introducedRange))
return AvailabilityConstraint::unavailableForDeployment(attr);
}

// FIXME: [availability] Model deprecation as an availability constraint.
return std::nullopt;
}

Expand Down
Loading