Skip to content

AST: Optimize the layout of AvailabilityRange #79784

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 1 commit into from
Mar 5, 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
2 changes: 1 addition & 1 deletion include/swift/AST/ASTBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ enum ENUM_EXTENSIBILITY_ATTR(open) BridgedDiagID : uint32_t {
};

class BridgedDiagnosticArgument {
int64_t storage[4];
int64_t storage[3];

public:
BRIDGED_INLINE BridgedDiagnosticArgument(const swift::DiagnosticArgument &arg);
Expand Down
62 changes: 29 additions & 33 deletions include/swift/AST/AvailabilityRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#ifndef SWIFT_AST_AVAILABILITY_RANGE_H
#define SWIFT_AST_AVAILABILITY_RANGE_H

#include "swift/Basic/Assertions.h"
#include "swift/Basic/LLVM.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/Support/VersionTuple.h"
Expand All @@ -36,36 +37,39 @@ class VersionRange {
// All: all versions
// x.y.x: all versions greater than or equal to x.y.z

enum class ExtremalRange { Empty, All };
/// The sentinel version tuple representing a range containing all versions.
constexpr static llvm::VersionTuple getAllTuple() {
return llvm::VersionTuple(0x7FFFFFFE);
}

/// The sentinel version tuple representing an empty range.
constexpr static llvm::VersionTuple getEmptyTuple() {
return llvm::VersionTuple(0x7FFFFFFF);
}

// A version range is either an extremal value (Empty, All) or
// a single version tuple value representing the lower end point x.y.z of a
// range [x.y.z, +Inf).
union {
llvm::VersionTuple LowerEndpoint;
ExtremalRange ExtremalValue;
};

unsigned HasLowerEndpoint : 1;
llvm::VersionTuple LowerEndpoint;

public:
/// Returns true if the range of versions is empty, or false otherwise.
bool isEmpty() const {
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::Empty;
return !hasLowerEndpoint() && LowerEndpoint == getEmptyTuple();
}

/// Returns true if the range includes all versions, or false otherwise.
bool isAll() const {
return !HasLowerEndpoint && ExtremalValue == ExtremalRange::All;
return !hasLowerEndpoint() && LowerEndpoint == getAllTuple();
}

/// Returns true if the range has a lower end point; that is, if it is of
/// the form [X, +Inf).
bool hasLowerEndpoint() const { return HasLowerEndpoint; }
bool hasLowerEndpoint() const { return isValidVersion(LowerEndpoint); }

/// Returns the range's lower endpoint.
const llvm::VersionTuple &getLowerEndpoint() const {
assert(HasLowerEndpoint);
assert(hasLowerEndpoint());
return LowerEndpoint;
}

Expand Down Expand Up @@ -124,7 +128,7 @@ class VersionRange {
const llvm::VersionTuple maxVersion =
std::max(this->getLowerEndpoint(), Other.getLowerEndpoint());

setLowerEndpoint(maxVersion);
LowerEndpoint = maxVersion;
}

/// Mutates this range to be the union of itself and Other. This is the
Expand All @@ -145,7 +149,7 @@ class VersionRange {
const llvm::VersionTuple minVersion =
std::min(this->getLowerEndpoint(), Other.getLowerEndpoint());

setLowerEndpoint(minVersion);
LowerEndpoint = minVersion;
}

/// Mutates this range to be a best effort over-approximation of the
Expand All @@ -160,37 +164,29 @@ class VersionRange {
}

/// Returns a version range representing all versions.
static VersionRange all() { return VersionRange(ExtremalRange::All); }
static VersionRange all() { return VersionRange(getAllTuple()); }

/// Returns a version range representing no versions.
static VersionRange empty() { return VersionRange(ExtremalRange::Empty); }
static VersionRange empty() { return VersionRange(getEmptyTuple()); }

/// Returns false if the given version tuple cannot be used as a lower
/// endpoint for `VersionRange`.
static bool isValidVersion(const llvm::VersionTuple &EndPoint) {
return EndPoint != getAllTuple() && EndPoint != getEmptyTuple();
}

/// Returns a version range representing all versions greater than or equal
/// to the passed-in version.
static VersionRange allGTE(const llvm::VersionTuple &EndPoint) {
ASSERT(isValidVersion(EndPoint));
return VersionRange(EndPoint);
}

void Profile(llvm::FoldingSetNodeID &ID) const;

private:
VersionRange(const llvm::VersionTuple &LowerEndpoint) {
setLowerEndpoint(LowerEndpoint);
}

VersionRange(ExtremalRange ExtremalValue) {
setExtremalRange(ExtremalValue);
}

void setExtremalRange(ExtremalRange Version) {
HasLowerEndpoint = 0;
ExtremalValue = Version;
}

void setLowerEndpoint(const llvm::VersionTuple &Version) {
HasLowerEndpoint = 1;
LowerEndpoint = Version;
}
VersionRange(const llvm::VersionTuple &LowerEndpoint)
: LowerEndpoint(LowerEndpoint) {}
};

/// Represents a version range in which something is available.
Expand Down Expand Up @@ -327,7 +323,7 @@ class AvailabilityRange {
/// Returns a representation of the raw version range as a string for
/// debugging purposes.
std::string getVersionString() const {
assert(Range.hasLowerEndpoint());
ASSERT(Range.hasLowerEndpoint());
return Range.getLowerEndpoint().getAsString();
}
};
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6754,6 +6754,8 @@ WARNING(availability_suggest_platform_name,
PointsToFirstBadToken, "unrecognized platform name %0;"
" did you mean '%1'?",
(Identifier, StringRef))
WARNING(availability_unsupported_version_number, none,
"'%0' is not a supported version number", (llvm::VersionTuple))

WARNING(attr_availability_expected_deprecated_version, none,
"expected version number with 'deprecated' in '%0' attribute for %1",
Expand Down
28 changes: 23 additions & 5 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,25 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
if (!domain)
return std::nullopt;

auto semanticAttr = SemanticAvailableAttr(attr);
auto checkVersion = [&](std::optional<llvm::VersionTuple> version,
SourceRange sourceRange) {
if (version && !VersionRange::isValidVersion(*version)) {
diags
.diagnose(attrLoc, diag::availability_unsupported_version_number,
*version)
.highlight(sourceRange);
return true;
}

return false;
};

if (checkVersion(attr->getRawIntroduced(), attr->IntroducedRange))
return std::nullopt;
if (checkVersion(attr->getRawDeprecated(), attr->DeprecatedRange))
return std::nullopt;
if (checkVersion(attr->getRawObsoleted(), attr->ObsoletedRange))
return std::nullopt;

bool hasIntroduced = attr->getRawIntroduced().has_value();
bool hasDeprecated = attr->getRawDeprecated().has_value();
Expand All @@ -779,11 +797,11 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
if (!domain->isVersioned() && hasVersionSpec) {
SourceRange versionSourceRange;
if (hasIntroduced)
versionSourceRange = semanticAttr.getIntroducedSourceRange();
versionSourceRange = attr->IntroducedRange;
else if (hasDeprecated)
versionSourceRange = semanticAttr.getDeprecatedSourceRange();
versionSourceRange = attr->DeprecatedRange;
else if (hasObsoleted)
versionSourceRange = semanticAttr.getObsoletedSourceRange();
versionSourceRange = attr->ObsoletedRange;

diags.diagnose(attrLoc, diag::availability_unexpected_version, *domain)
.limitBehaviorIf(domain->isUniversal(), DiagnosticBehavior::Warning)
Expand Down Expand Up @@ -827,7 +845,7 @@ SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
}
}

return semanticAttr;
return SemanticAvailableAttr(attr);
}

std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {
Expand Down
30 changes: 22 additions & 8 deletions lib/AST/AvailabilitySpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include "swift/AST/AvailabilitySpec.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/AvailabilityDomain.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/TypeCheckRequests.h"
#include "llvm/Support/raw_ostream.h"

Expand Down Expand Up @@ -116,21 +116,35 @@ std::optional<SemanticAvailabilitySpec>
SemanticAvailabilitySpecRequest::evaluate(
Evaluator &evaluator, const AvailabilitySpec *spec,
const DeclContext *declContext) const {
if (spec->isInvalid())
return std::nullopt;

auto &diags = declContext->getASTContext().Diags;
AvailabilitySpec *mutableSpec = const_cast<AvailabilitySpec *>(spec);
if (mutableSpec->resolveInDeclContext(declContext).has_value())
return SemanticAvailabilitySpec(spec);
return std::nullopt;
if (!mutableSpec->resolveInDeclContext(declContext).has_value())
return std::nullopt;

auto version = spec->getRawVersion();
if (!VersionRange::isValidVersion(version)) {
diags
.diagnose(spec->getStartLoc(),
diag::availability_unsupported_version_number, version)
.highlight(spec->getVersionSrcRange());
return std::nullopt;
}

return SemanticAvailabilitySpec(spec);
}

std::optional<std::optional<SemanticAvailabilitySpec>>
SemanticAvailabilitySpecRequest::getCachedResult() const {
auto *spec = std::get<0>(getStorage());
if (spec->isInvalid())
return std::optional<SemanticAvailabilitySpec>();

auto domainOrIdentifier = spec->getDomainOrIdentifier();
if (!domainOrIdentifier.isResolved())
return {};

if (spec->isInvalid())
return {std::nullopt};
return std::nullopt;

return SemanticAvailabilitySpec(spec);
}
Expand Down
11 changes: 11 additions & 0 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5158,6 +5158,17 @@ static bool diagnoseAvailabilityCondition(PoundAvailableInfo *info,
return true;
}

if (hasVersion) {
auto rawVersion = parsedSpec->getRawVersion();
if (!VersionRange::isValidVersion(rawVersion)) {
diags
.diagnose(loc, diag::availability_unsupported_version_number,
rawVersion)
.highlight(parsedSpec->getVersionSrcRange());
return true;
}
}

if (domain.isVersioned()) {
if (!hasVersion) {
diags.diagnose(loc, diag::avail_query_expected_version_number);
Expand Down
44 changes: 44 additions & 0 deletions test/Sema/availability_versions_unsupported.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %target-typecheck-verify-swift

@available(macOS, introduced: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcIntroducedInMacOS2147483646() { }

@available(macOS 2147483646, *) // expected-warning {{'2147483646' is not a supported version number}}
func funcIntroducedInMacOS2147483646Short() { }

@available(macOS, deprecated: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcDeprecatedInMacOS2147483646() { }

@available(macOS, obsoleted: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcObsoletedInMacOS2147483646() { }

@available(macOS, introduced: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
func funcIntroducedInMacOS2147483647() { }

@available(macOS 2147483647, *) // expected-warning {{'2147483647' is not a supported version number}}
func funcIntroducedInMacOS2147483647Short() { }

@available(macOS, deprecated: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
func funcDeprecatedInMacOS2147483647() { }

@available(macOS, obsoleted: 2147483647) // expected-warning {{'2147483647' is not a supported version number}}
func funcObsoletedInMacOS2147483647() { }

@available(swift, introduced: 2147483646) // expected-warning {{'2147483646' is not a supported version number}}
func funcIntroducedInSwift2147483646() { }

func useExtremeVersions() {
if #available(macOS 2147483646, *) { // expected-warning {{'2147483646' is not a supported version number}}
funcIntroducedInMacOS2147483646()
funcIntroducedInMacOS2147483646Short()
funcDeprecatedInMacOS2147483646()
funcObsoletedInMacOS2147483646()
}
if #available(macOS 2147483647, *) { // expected-warning {{'2147483647' is not a supported version number}}
funcIntroducedInMacOS2147483647()
funcIntroducedInMacOS2147483647Short()
funcDeprecatedInMacOS2147483647()
funcObsoletedInMacOS2147483647()
}
funcIntroducedInSwift2147483646()
}
6 changes: 6 additions & 0 deletions test/attr/attr_availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ let _: Int
@available(OSX, introduced: 0.0.0) // expected-warning{{expected version number in 'available' attribute; this is an error in the Swift 6 language mode}}
let _: Int

@available(OSX, introduced: 2147483646)
let _: Int

@available(OSX, introduced: 2147483647)
let _: Int

@available(*, renamed: "bad name") // expected-error{{'renamed' argument of 'available' attribute must be an operator, identifier, or full function name, optionally prefixed by a type name}}
let _: Int

Expand Down
6 changes: 6 additions & 0 deletions unittests/AST/VersionRangeTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,9 @@ TEST_F(VersionRangeLattice, JoinWithClosedEndedPositiveInfinity) {
EXPECT_TRUE(unionEquals(GreaterThanEqual10_10, GreaterThanEqual10_9,
GreaterThanEqual10_9));
}

TEST_F(VersionRangeLattice, ValidVersionTuples) {
EXPECT_TRUE(VersionRange::isValidVersion(llvm::VersionTuple()));
EXPECT_FALSE(VersionRange::isValidVersion(llvm::VersionTuple(0x7FFFFFFE)));
EXPECT_FALSE(VersionRange::isValidVersion(llvm::VersionTuple(0x7FFFFFFF)));
}