Skip to content

AST: Introduce AvailabilityContext::containsUnavailableDomain() #79190

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 7, 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
20 changes: 12 additions & 8 deletions include/swift/AST/AvailabilityContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,30 @@ class AvailabilityContext {
/// availability context, starting at its introduction version.
AvailabilityRange getPlatformRange() const;

/// Returns the broadest AvailabilityDomain that is unavailable in this
/// context.
std::optional<AvailabilityDomain> getUnavailableDomain() const;
/// Returns true if this context contains any unavailable domains.
bool isUnavailable() const;

/// Returns true if this context is unavailable.
bool isUnavailable() const { return getUnavailableDomain().has_value(); }
/// Returns true if \p domain is unavailable in this context.
bool containsUnavailableDomain(AvailabilityDomain domain) const;

/// Returns true if this context is deprecated on the current platform.
bool isDeprecated() const;

/// Constrain with another `AvailabilityContext`.
void constrainWithContext(const AvailabilityContext &other, ASTContext &ctx);

/// Constrain with the availability attributes of `decl`.
void constrainWithDecl(const Decl *decl);

/// Constrain the platform availability range with `platformRange`.
void constrainWithPlatformRange(const AvailabilityRange &platformRange,
ASTContext &ctx);

/// Constrain the context by adding \p domain to the set of unavailable
/// domains.
void constrainWithUnavailableDomain(AvailabilityDomain domain,
ASTContext &ctx);

/// Constrain with the availability attributes of `decl`.
void constrainWithDecl(const Decl *decl);

/// Constrain with the availability attributes of `decl`, intersecting the
/// platform range of `decl` with `platformRange`.
void
Expand Down
32 changes: 24 additions & 8 deletions lib/AST/AvailabilityContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,16 @@ AvailabilityRange AvailabilityContext::getPlatformRange() const {
return storage->info.Range;
}

std::optional<AvailabilityDomain>
AvailabilityContext::getUnavailableDomain() const {
return storage->info.UnavailableDomain;
bool AvailabilityContext::isUnavailable() const {
return storage->info.UnavailableDomain.has_value();
}

bool AvailabilityContext::containsUnavailableDomain(
AvailabilityDomain domain) const {
if (auto unavailableDomain = storage->info.UnavailableDomain)
return unavailableDomain->contains(domain);

return false;
}

bool AvailabilityContext::isDeprecated() const {
Expand All @@ -155,10 +162,6 @@ void AvailabilityContext::constrainWithContext(const AvailabilityContext &other,
storage = Storage::get(info, ctx);
}

void AvailabilityContext::constrainWithDecl(const Decl *decl) {
constrainWithDeclAndPlatformRange(decl, AvailabilityRange::alwaysAvailable());
}

void AvailabilityContext::constrainWithPlatformRange(
const AvailabilityRange &platformRange, ASTContext &ctx) {

Expand All @@ -169,6 +172,19 @@ void AvailabilityContext::constrainWithPlatformRange(
storage = Storage::get(info, ctx);
}

void AvailabilityContext::constrainWithUnavailableDomain(
AvailabilityDomain domain, ASTContext &ctx) {
Info info{storage->info};
if (!info.constrainUnavailability(domain))
return;

storage = Storage::get(info, ctx);
}

void AvailabilityContext::constrainWithDecl(const Decl *decl) {
constrainWithDeclAndPlatformRange(decl, AvailabilityRange::alwaysAvailable());
}

void AvailabilityContext::constrainWithDeclAndPlatformRange(
const Decl *decl, const AvailabilityRange &platformRange) {
bool isConstrained = false;
Expand Down Expand Up @@ -203,7 +219,7 @@ stringForAvailability(const AvailabilityRange &availability) {
void AvailabilityContext::print(llvm::raw_ostream &os) const {
os << "version=" << stringForAvailability(getPlatformRange());

if (auto unavailableDomain = getUnavailableDomain())
if (auto unavailableDomain = storage->info.UnavailableDomain)
os << " unavailable=" << unavailableDomain->getNameForAttributePrinting();

if (isDeprecated())
Expand Down
51 changes: 25 additions & 26 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5125,34 +5125,33 @@ void AttributeChecker::checkBackDeployedAttrs(
D->getLoc(), D->getInnermostDeclContext());

// Unavailable decls cannot be back deployed.
if (auto unavailableDomain = availability.getUnavailableDomain()) {
auto backDeployedDomain = AvailabilityDomain::forPlatform(Attr->Platform);
if (unavailableDomain->contains(backDeployedDomain)) {
auto platformString = prettyPlatformString(Attr->Platform);
llvm::VersionTuple ignoredVersion;

AvailabilityInference::updateBeforePlatformForFallback(
Attr, Ctx, platformString, ignoredVersion);

diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr, VD,
platformString);

// Find the attribute that makes the declaration unavailable.
const Decl *attrDecl = D;
do {
if (auto unavailableAttr = attrDecl->getUnavailableAttr()) {
diagnose(unavailableAttr->getParsedAttr()->AtLoc,
diag::availability_marked_unavailable, VD)
.highlight(unavailableAttr->getParsedAttr()->getRange());
break;
}
auto backDeployedDomain = AvailabilityDomain::forPlatform(Attr->Platform);
if (auto unavailableDomain =
availability.containsUnavailableDomain(backDeployedDomain)) {
auto platformString = prettyPlatformString(Attr->Platform);
llvm::VersionTuple ignoredVersion;

AvailabilityInference::updateBeforePlatformForFallback(
Attr, Ctx, platformString, ignoredVersion);

diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr, VD,
platformString);

// Find the attribute that makes the declaration unavailable.
const Decl *attrDecl = D;
do {
if (auto unavailableAttr = attrDecl->getUnavailableAttr()) {
diagnose(unavailableAttr->getParsedAttr()->AtLoc,
diag::availability_marked_unavailable, VD)
.highlight(unavailableAttr->getParsedAttr()->getRange());
break;
}

attrDecl = AvailabilityInference::parentDeclForInferredAvailability(
attrDecl);
} while (attrDecl);
attrDecl =
AvailabilityInference::parentDeclForInferredAvailability(attrDecl);
} while (attrDecl);

continue;
}
continue;
}

// Verify that the decl is available before the back deployment boundary.
Expand Down
5 changes: 2 additions & 3 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,7 @@ static bool computeContainedByDeploymentTarget(AvailabilityScope *scope,
static bool isInsideCompatibleUnavailableDeclaration(
const Decl *D, AvailabilityContext availabilityContext,
const SemanticAvailableAttr &attr) {
auto contextDomain = availabilityContext.getUnavailableDomain();
if (!contextDomain)
if (!availabilityContext.isUnavailable())
return false;

if (!attr.isUnconditionallyUnavailable())
Expand All @@ -364,7 +363,7 @@ static bool isInsideCompatibleUnavailableDeclaration(
return false;
}

return contextDomain->contains(declDomain);
return availabilityContext.containsUnavailableDomain(declDomain);
}

std::optional<SemanticAvailableAttr>
Expand Down
125 changes: 125 additions & 0 deletions unittests/AST/AvailabilityContextTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//===--- AvailabilityContextTests.cpp - Tests for AvailabilityContext -----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "TestContext.h"
#include "swift/AST/AvailabilityContext.h"
#include "llvm/TargetParser/Triple.h"
#include "gtest/gtest.h"

using namespace swift;
using namespace swift::unittest;

static llvm::VersionTuple getPlatformIntro(const AvailabilityContext &context) {
return context.getPlatformRange().getRawVersionRange().getLowerEndpoint();
}

static AvailabilityRange getAvailabilityRange(unsigned major, unsigned minor) {
return AvailabilityRange(
VersionRange::allGTE(llvm::VersionTuple(major, minor)));
}

class AvailabilityContextTest : public ::testing::Test {
public:
const TestContext defaultTestContext{
DoNotDeclareOptionalTypes, llvm::Triple("x86_64", "apple", "macosx10.9")};

struct {
const AvailabilityDomain universal = AvailabilityDomain::forUniversal();
const AvailabilityDomain macOS =
AvailabilityDomain::forPlatform(PlatformKind::macOS);
const AvailabilityDomain macOSAppExt = AvailabilityDomain::forPlatform(
PlatformKind::macOSApplicationExtension);
} domains;
};

TEST_F(AvailabilityContextTest, PlatformIntroduction) {
auto &ctx = defaultTestContext.Ctx;

auto macOS10_9 = AvailabilityContext::forDeploymentTarget(ctx);
EXPECT_EQ(getPlatformIntro(macOS10_9), llvm::VersionTuple(10, 9));

// Attempt to constrain the macOS version to >= 10.8. Since the context is
// already >= 10.9, this should have no effect.
auto macOS10_8 = macOS10_9;
macOS10_8.constrainWithPlatformRange(getAvailabilityRange(10, 8), ctx);
EXPECT_EQ(macOS10_8, macOS10_9);

// Attempt to constrain the macOS version to >= 10.9. Since the context is
// already >= 10.9, this should have no effect.
auto stillMacOS10_9 = macOS10_9;
stillMacOS10_9.constrainWithPlatformRange(getAvailabilityRange(10, 9), ctx);
EXPECT_EQ(stillMacOS10_9, macOS10_9);

// Constrain the the macOS version to >= 10.10 instead. The resulting context
// should be a new context that is less available than the deployment target
// context (a.k.a. "contained by").
auto macOS10_10 = macOS10_9;
macOS10_10.constrainWithPlatformRange(getAvailabilityRange(10, 10), ctx);
EXPECT_EQ(getPlatformIntro(macOS10_10), llvm::VersionTuple(10, 10));
EXPECT_NE(macOS10_9, macOS10_10);
EXPECT_TRUE(macOS10_10.isContainedIn(macOS10_9));
EXPECT_FALSE(macOS10_9.isContainedIn(macOS10_10));
}

TEST_F(AvailabilityContextTest, UnavailableDomains) {
auto &ctx = defaultTestContext.Ctx;

auto macOS10_9 = AvailabilityContext::forDeploymentTarget(ctx);
EXPECT_FALSE(macOS10_9.isUnavailable());
EXPECT_FALSE(macOS10_9.containsUnavailableDomain(domains.macOS));
EXPECT_FALSE(macOS10_9.containsUnavailableDomain(domains.macOSAppExt));
EXPECT_FALSE(macOS10_9.containsUnavailableDomain(domains.universal));

// Constrain the deployment target context by adding unavailability on macOS.
// The resulting context should be a new context that is less available than
// the deployment target context (a.k.a. "contained by").
auto unavailableOnMacOS = macOS10_9;
unavailableOnMacOS.constrainWithUnavailableDomain(domains.macOS, ctx);
EXPECT_TRUE(unavailableOnMacOS.isUnavailable());
EXPECT_TRUE(unavailableOnMacOS.containsUnavailableDomain(domains.macOS));
EXPECT_TRUE(
unavailableOnMacOS.containsUnavailableDomain(domains.macOSAppExt));
EXPECT_FALSE(unavailableOnMacOS.containsUnavailableDomain(domains.universal));
EXPECT_NE(unavailableOnMacOS, macOS10_9);
EXPECT_TRUE(unavailableOnMacOS.isContainedIn(macOS10_9));
EXPECT_FALSE(macOS10_9.isContainedIn(unavailableOnMacOS));

// Constraining a context that is already unavailable on macOS by adding
// unavailability on macOS should have no effect.
auto stillUnavailableOnMacOS = unavailableOnMacOS;
stillUnavailableOnMacOS.constrainWithUnavailableDomain(domains.macOS, ctx);
EXPECT_EQ(unavailableOnMacOS, stillUnavailableOnMacOS);

// Constraining unavailability on macOS application extensions should also
// have no effect.
auto unavailableInAppExt = unavailableOnMacOS;
unavailableInAppExt.constrainWithUnavailableDomain(domains.macOSAppExt, ctx);
EXPECT_EQ(unavailableOnMacOS, unavailableInAppExt);

// FIXME: [availability] Test adding unavailability for an independent domain.

// Constraining the context to be universally unavailable should create a
// new context that contains the context that was unavailable on macOS only.
auto unavailableUniversally = unavailableOnMacOS;
unavailableUniversally.constrainWithUnavailableDomain(domains.universal, ctx);
EXPECT_TRUE(unavailableUniversally.isUnavailable());
EXPECT_TRUE(unavailableUniversally.containsUnavailableDomain(domains.macOS));
EXPECT_TRUE(
unavailableUniversally.containsUnavailableDomain(domains.macOSAppExt));
EXPECT_TRUE(
unavailableUniversally.containsUnavailableDomain(domains.universal));
EXPECT_NE(unavailableUniversally, unavailableOnMacOS);
EXPECT_TRUE(unavailableUniversally.isContainedIn(unavailableOnMacOS));
EXPECT_TRUE(unavailableUniversally.isContainedIn(macOS10_9));
EXPECT_FALSE(unavailableOnMacOS.isContainedIn(unavailableUniversally));
EXPECT_FALSE(macOS10_9.isContainedIn(unavailableUniversally));
}
12 changes: 12 additions & 0 deletions unittests/AST/AvailabilityDomainTests.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
//===--- AvailabilityDomainTests.cpp - Tests for AvailabilityDomain -------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/AST/AvailabilityDomain.h"
#include "gtest/gtest.h"

Expand Down
1 change: 1 addition & 0 deletions unittests/AST/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_swift_unittest(SwiftASTTests
ArithmeticEvaluator.cpp
ASTDumperTests.cpp
ASTWalkerTests.cpp
AvailabilityContextTests.cpp
AvailabilityDomainTests.cpp
IndexSubsetTests.cpp
DiagnosticBehaviorTests.cpp
Expand Down
6 changes: 4 additions & 2 deletions unittests/AST/TestContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ static Decl *createOptionalType(ASTContext &ctx, SourceFile *fileForLookups,
return decl;
}

TestContext::TestContext(ShouldDeclareOptionalTypes optionals)
: Ctx(*ASTContext::get(LangOpts, TypeCheckerOpts, SILOpts, SearchPathOpts,
TestContext::TestContext(ShouldDeclareOptionalTypes optionals,
llvm::Triple target)
: TestContextBase(target),
Ctx(*ASTContext::get(LangOpts, TypeCheckerOpts, SILOpts, SearchPathOpts,
ClangImporterOpts, SymbolGraphOpts, CASOpts,
SerializationOpts, SourceMgr, Diags)) {
registerParseRequestFunctions(Ctx.evaluator);
Expand Down
8 changes: 5 additions & 3 deletions unittests/AST/TestContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ class TestContextBase {
SourceManager SourceMgr;
DiagnosticEngine Diags;

TestContextBase() : Diags(SourceMgr) {
LangOpts.Target = llvm::Triple(llvm::sys::getProcessTriple());
TestContextBase(llvm::Triple target) : Diags(SourceMgr) {
LangOpts.Target = target;
}
};

Expand All @@ -57,7 +57,9 @@ class TestContext : public TestContextBase {
public:
ASTContext &Ctx;

TestContext(ShouldDeclareOptionalTypes optionals = DoNotDeclareOptionalTypes);
TestContext(
ShouldDeclareOptionalTypes optionals = DoNotDeclareOptionalTypes,
llvm::Triple target = llvm::Triple(llvm::sys::getProcessTriple()));

template <typename Nominal>
typename std::enable_if<!std::is_same<Nominal, swift::ClassDecl>::value,
Expand Down