diff --git a/include/swift/AST/AvailabilityContext.h b/include/swift/AST/AvailabilityContext.h index 35d0771052db5..3160ace8d9662 100644 --- a/include/swift/AST/AvailabilityContext.h +++ b/include/swift/AST/AvailabilityContext.h @@ -74,12 +74,11 @@ class AvailabilityContext { /// availability context, starting at its introduction version. AvailabilityRange getPlatformRange() const; - /// Returns the broadest AvailabilityDomain that is unavailable in this - /// context. - std::optional 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; @@ -87,13 +86,18 @@ class AvailabilityContext { /// 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 diff --git a/lib/AST/AvailabilityContext.cpp b/lib/AST/AvailabilityContext.cpp index 134845f865e51..606357080dc1f 100644 --- a/lib/AST/AvailabilityContext.cpp +++ b/lib/AST/AvailabilityContext.cpp @@ -133,9 +133,16 @@ AvailabilityRange AvailabilityContext::getPlatformRange() const { return storage->info.Range; } -std::optional -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 { @@ -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) { @@ -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; @@ -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()) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index bdd915619a40b..883e8e4e89252 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -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. diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 1b1a9a0769dfb..ab9d4b3388b7f 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -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()) @@ -364,7 +363,7 @@ static bool isInsideCompatibleUnavailableDeclaration( return false; } - return contextDomain->contains(declDomain); + return availabilityContext.containsUnavailableDomain(declDomain); } std::optional diff --git a/unittests/AST/AvailabilityContextTests.cpp b/unittests/AST/AvailabilityContextTests.cpp new file mode 100644 index 0000000000000..cd2e061a2b094 --- /dev/null +++ b/unittests/AST/AvailabilityContextTests.cpp @@ -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)); +} diff --git a/unittests/AST/AvailabilityDomainTests.cpp b/unittests/AST/AvailabilityDomainTests.cpp index d1596ebe4b423..d5011488ff01d 100644 --- a/unittests/AST/AvailabilityDomainTests.cpp +++ b/unittests/AST/AvailabilityDomainTests.cpp @@ -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" diff --git a/unittests/AST/CMakeLists.txt b/unittests/AST/CMakeLists.txt index 8486e6f635a76..7b91a9a7aff1a 100644 --- a/unittests/AST/CMakeLists.txt +++ b/unittests/AST/CMakeLists.txt @@ -2,6 +2,7 @@ add_swift_unittest(SwiftASTTests ArithmeticEvaluator.cpp ASTDumperTests.cpp ASTWalkerTests.cpp + AvailabilityContextTests.cpp AvailabilityDomainTests.cpp IndexSubsetTests.cpp DiagnosticBehaviorTests.cpp diff --git a/unittests/AST/TestContext.cpp b/unittests/AST/TestContext.cpp index c18cebbb47e8f..5f0e33e9a07b5 100644 --- a/unittests/AST/TestContext.cpp +++ b/unittests/AST/TestContext.cpp @@ -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); diff --git a/unittests/AST/TestContext.h b/unittests/AST/TestContext.h index 60588278b2952..8bc0f44aa9d31 100644 --- a/unittests/AST/TestContext.h +++ b/unittests/AST/TestContext.h @@ -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; } }; @@ -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 std::enable_if::value,