Skip to content

Commit 3ce2449

Browse files
authored
Merge pull request #79190 from tshortli/availability-context-unit-tests
AST: Introduce `AvailabilityContext::containsUnavailableDomain()`
2 parents 2f09abb + ddb5f23 commit 3ce2449

File tree

9 files changed

+210
-50
lines changed

9 files changed

+210
-50
lines changed

include/swift/AST/AvailabilityContext.h

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,30 @@ class AvailabilityContext {
7474
/// availability context, starting at its introduction version.
7575
AvailabilityRange getPlatformRange() const;
7676

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

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

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

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

90-
/// Constrain with the availability attributes of `decl`.
91-
void constrainWithDecl(const Decl *decl);
92-
9389
/// Constrain the platform availability range with `platformRange`.
9490
void constrainWithPlatformRange(const AvailabilityRange &platformRange,
9591
ASTContext &ctx);
9692

93+
/// Constrain the context by adding \p domain to the set of unavailable
94+
/// domains.
95+
void constrainWithUnavailableDomain(AvailabilityDomain domain,
96+
ASTContext &ctx);
97+
98+
/// Constrain with the availability attributes of `decl`.
99+
void constrainWithDecl(const Decl *decl);
100+
97101
/// Constrain with the availability attributes of `decl`, intersecting the
98102
/// platform range of `decl` with `platformRange`.
99103
void

lib/AST/AvailabilityContext.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,16 @@ AvailabilityRange AvailabilityContext::getPlatformRange() const {
133133
return storage->info.Range;
134134
}
135135

136-
std::optional<AvailabilityDomain>
137-
AvailabilityContext::getUnavailableDomain() const {
138-
return storage->info.UnavailableDomain;
136+
bool AvailabilityContext::isUnavailable() const {
137+
return storage->info.UnavailableDomain.has_value();
138+
}
139+
140+
bool AvailabilityContext::containsUnavailableDomain(
141+
AvailabilityDomain domain) const {
142+
if (auto unavailableDomain = storage->info.UnavailableDomain)
143+
return unavailableDomain->contains(domain);
144+
145+
return false;
139146
}
140147

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

158-
void AvailabilityContext::constrainWithDecl(const Decl *decl) {
159-
constrainWithDeclAndPlatformRange(decl, AvailabilityRange::alwaysAvailable());
160-
}
161-
162165
void AvailabilityContext::constrainWithPlatformRange(
163166
const AvailabilityRange &platformRange, ASTContext &ctx) {
164167

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

175+
void AvailabilityContext::constrainWithUnavailableDomain(
176+
AvailabilityDomain domain, ASTContext &ctx) {
177+
Info info{storage->info};
178+
if (!info.constrainUnavailability(domain))
179+
return;
180+
181+
storage = Storage::get(info, ctx);
182+
}
183+
184+
void AvailabilityContext::constrainWithDecl(const Decl *decl) {
185+
constrainWithDeclAndPlatformRange(decl, AvailabilityRange::alwaysAvailable());
186+
}
187+
172188
void AvailabilityContext::constrainWithDeclAndPlatformRange(
173189
const Decl *decl, const AvailabilityRange &platformRange) {
174190
bool isConstrained = false;
@@ -203,7 +219,7 @@ stringForAvailability(const AvailabilityRange &availability) {
203219
void AvailabilityContext::print(llvm::raw_ostream &os) const {
204220
os << "version=" << stringForAvailability(getPlatformRange());
205221

206-
if (auto unavailableDomain = getUnavailableDomain())
222+
if (auto unavailableDomain = storage->info.UnavailableDomain)
207223
os << " unavailable=" << unavailableDomain->getNameForAttributePrinting();
208224

209225
if (isDeprecated())

lib/Sema/TypeCheckAttr.cpp

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5124,34 +5124,33 @@ void AttributeChecker::checkBackDeployedAttrs(
51245124
D->getLoc(), D->getInnermostDeclContext());
51255125

51265126
// Unavailable decls cannot be back deployed.
5127-
if (auto unavailableDomain = availability.getUnavailableDomain()) {
5128-
auto backDeployedDomain = AvailabilityDomain::forPlatform(Attr->Platform);
5129-
if (unavailableDomain->contains(backDeployedDomain)) {
5130-
auto platformString = prettyPlatformString(Attr->Platform);
5131-
llvm::VersionTuple ignoredVersion;
5132-
5133-
AvailabilityInference::updateBeforePlatformForFallback(
5134-
Attr, Ctx, platformString, ignoredVersion);
5135-
5136-
diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr, VD,
5137-
platformString);
5138-
5139-
// Find the attribute that makes the declaration unavailable.
5140-
const Decl *attrDecl = D;
5141-
do {
5142-
if (auto unavailableAttr = attrDecl->getUnavailableAttr()) {
5143-
diagnose(unavailableAttr->getParsedAttr()->AtLoc,
5144-
diag::availability_marked_unavailable, VD)
5145-
.highlight(unavailableAttr->getParsedAttr()->getRange());
5146-
break;
5147-
}
5127+
auto backDeployedDomain = AvailabilityDomain::forPlatform(Attr->Platform);
5128+
if (auto unavailableDomain =
5129+
availability.containsUnavailableDomain(backDeployedDomain)) {
5130+
auto platformString = prettyPlatformString(Attr->Platform);
5131+
llvm::VersionTuple ignoredVersion;
5132+
5133+
AvailabilityInference::updateBeforePlatformForFallback(
5134+
Attr, Ctx, platformString, ignoredVersion);
5135+
5136+
diagnose(AtLoc, diag::attr_has_no_effect_on_unavailable_decl, Attr, VD,
5137+
platformString);
5138+
5139+
// Find the attribute that makes the declaration unavailable.
5140+
const Decl *attrDecl = D;
5141+
do {
5142+
if (auto unavailableAttr = attrDecl->getUnavailableAttr()) {
5143+
diagnose(unavailableAttr->getParsedAttr()->AtLoc,
5144+
diag::availability_marked_unavailable, VD)
5145+
.highlight(unavailableAttr->getParsedAttr()->getRange());
5146+
break;
5147+
}
51485148

5149-
attrDecl = AvailabilityInference::parentDeclForInferredAvailability(
5150-
attrDecl);
5151-
} while (attrDecl);
5149+
attrDecl =
5150+
AvailabilityInference::parentDeclForInferredAvailability(attrDecl);
5151+
} while (attrDecl);
51525152

5153-
continue;
5154-
}
5153+
continue;
51555154
}
51565155

51575156
// Verify that the decl is available before the back deployment boundary.

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,7 @@ static bool computeContainedByDeploymentTarget(AvailabilityScope *scope,
349349
static bool isInsideCompatibleUnavailableDeclaration(
350350
const Decl *D, AvailabilityContext availabilityContext,
351351
const SemanticAvailableAttr &attr) {
352-
auto contextDomain = availabilityContext.getUnavailableDomain();
353-
if (!contextDomain)
352+
if (!availabilityContext.isUnavailable())
354353
return false;
355354

356355
if (!attr.isUnconditionallyUnavailable())
@@ -364,7 +363,7 @@ static bool isInsideCompatibleUnavailableDeclaration(
364363
return false;
365364
}
366365

367-
return contextDomain->contains(declDomain);
366+
return availabilityContext.containsUnavailableDomain(declDomain);
368367
}
369368

370369
std::optional<SemanticAvailableAttr>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//===--- AvailabilityContextTests.cpp - Tests for AvailabilityContext -----===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "TestContext.h"
14+
#include "swift/AST/AvailabilityContext.h"
15+
#include "llvm/TargetParser/Triple.h"
16+
#include "gtest/gtest.h"
17+
18+
using namespace swift;
19+
using namespace swift::unittest;
20+
21+
static llvm::VersionTuple getPlatformIntro(const AvailabilityContext &context) {
22+
return context.getPlatformRange().getRawVersionRange().getLowerEndpoint();
23+
}
24+
25+
static AvailabilityRange getAvailabilityRange(unsigned major, unsigned minor) {
26+
return AvailabilityRange(
27+
VersionRange::allGTE(llvm::VersionTuple(major, minor)));
28+
}
29+
30+
class AvailabilityContextTest : public ::testing::Test {
31+
public:
32+
const TestContext defaultTestContext{
33+
DoNotDeclareOptionalTypes, llvm::Triple("x86_64", "apple", "macosx10.9")};
34+
35+
struct {
36+
const AvailabilityDomain universal = AvailabilityDomain::forUniversal();
37+
const AvailabilityDomain macOS =
38+
AvailabilityDomain::forPlatform(PlatformKind::macOS);
39+
const AvailabilityDomain macOSAppExt = AvailabilityDomain::forPlatform(
40+
PlatformKind::macOSApplicationExtension);
41+
} domains;
42+
};
43+
44+
TEST_F(AvailabilityContextTest, PlatformIntroduction) {
45+
auto &ctx = defaultTestContext.Ctx;
46+
47+
auto macOS10_9 = AvailabilityContext::forDeploymentTarget(ctx);
48+
EXPECT_EQ(getPlatformIntro(macOS10_9), llvm::VersionTuple(10, 9));
49+
50+
// Attempt to constrain the macOS version to >= 10.8. Since the context is
51+
// already >= 10.9, this should have no effect.
52+
auto macOS10_8 = macOS10_9;
53+
macOS10_8.constrainWithPlatformRange(getAvailabilityRange(10, 8), ctx);
54+
EXPECT_EQ(macOS10_8, macOS10_9);
55+
56+
// Attempt to constrain the macOS version to >= 10.9. Since the context is
57+
// already >= 10.9, this should have no effect.
58+
auto stillMacOS10_9 = macOS10_9;
59+
stillMacOS10_9.constrainWithPlatformRange(getAvailabilityRange(10, 9), ctx);
60+
EXPECT_EQ(stillMacOS10_9, macOS10_9);
61+
62+
// Constrain the the macOS version to >= 10.10 instead. The resulting context
63+
// should be a new context that is less available than the deployment target
64+
// context (a.k.a. "contained by").
65+
auto macOS10_10 = macOS10_9;
66+
macOS10_10.constrainWithPlatformRange(getAvailabilityRange(10, 10), ctx);
67+
EXPECT_EQ(getPlatformIntro(macOS10_10), llvm::VersionTuple(10, 10));
68+
EXPECT_NE(macOS10_9, macOS10_10);
69+
EXPECT_TRUE(macOS10_10.isContainedIn(macOS10_9));
70+
EXPECT_FALSE(macOS10_9.isContainedIn(macOS10_10));
71+
}
72+
73+
TEST_F(AvailabilityContextTest, UnavailableDomains) {
74+
auto &ctx = defaultTestContext.Ctx;
75+
76+
auto macOS10_9 = AvailabilityContext::forDeploymentTarget(ctx);
77+
EXPECT_FALSE(macOS10_9.isUnavailable());
78+
EXPECT_FALSE(macOS10_9.containsUnavailableDomain(domains.macOS));
79+
EXPECT_FALSE(macOS10_9.containsUnavailableDomain(domains.macOSAppExt));
80+
EXPECT_FALSE(macOS10_9.containsUnavailableDomain(domains.universal));
81+
82+
// Constrain the deployment target context by adding unavailability on macOS.
83+
// The resulting context should be a new context that is less available than
84+
// the deployment target context (a.k.a. "contained by").
85+
auto unavailableOnMacOS = macOS10_9;
86+
unavailableOnMacOS.constrainWithUnavailableDomain(domains.macOS, ctx);
87+
EXPECT_TRUE(unavailableOnMacOS.isUnavailable());
88+
EXPECT_TRUE(unavailableOnMacOS.containsUnavailableDomain(domains.macOS));
89+
EXPECT_TRUE(
90+
unavailableOnMacOS.containsUnavailableDomain(domains.macOSAppExt));
91+
EXPECT_FALSE(unavailableOnMacOS.containsUnavailableDomain(domains.universal));
92+
EXPECT_NE(unavailableOnMacOS, macOS10_9);
93+
EXPECT_TRUE(unavailableOnMacOS.isContainedIn(macOS10_9));
94+
EXPECT_FALSE(macOS10_9.isContainedIn(unavailableOnMacOS));
95+
96+
// Constraining a context that is already unavailable on macOS by adding
97+
// unavailability on macOS should have no effect.
98+
auto stillUnavailableOnMacOS = unavailableOnMacOS;
99+
stillUnavailableOnMacOS.constrainWithUnavailableDomain(domains.macOS, ctx);
100+
EXPECT_EQ(unavailableOnMacOS, stillUnavailableOnMacOS);
101+
102+
// Constraining unavailability on macOS application extensions should also
103+
// have no effect.
104+
auto unavailableInAppExt = unavailableOnMacOS;
105+
unavailableInAppExt.constrainWithUnavailableDomain(domains.macOSAppExt, ctx);
106+
EXPECT_EQ(unavailableOnMacOS, unavailableInAppExt);
107+
108+
// FIXME: [availability] Test adding unavailability for an independent domain.
109+
110+
// Constraining the context to be universally unavailable should create a
111+
// new context that contains the context that was unavailable on macOS only.
112+
auto unavailableUniversally = unavailableOnMacOS;
113+
unavailableUniversally.constrainWithUnavailableDomain(domains.universal, ctx);
114+
EXPECT_TRUE(unavailableUniversally.isUnavailable());
115+
EXPECT_TRUE(unavailableUniversally.containsUnavailableDomain(domains.macOS));
116+
EXPECT_TRUE(
117+
unavailableUniversally.containsUnavailableDomain(domains.macOSAppExt));
118+
EXPECT_TRUE(
119+
unavailableUniversally.containsUnavailableDomain(domains.universal));
120+
EXPECT_NE(unavailableUniversally, unavailableOnMacOS);
121+
EXPECT_TRUE(unavailableUniversally.isContainedIn(unavailableOnMacOS));
122+
EXPECT_TRUE(unavailableUniversally.isContainedIn(macOS10_9));
123+
EXPECT_FALSE(unavailableOnMacOS.isContainedIn(unavailableUniversally));
124+
EXPECT_FALSE(macOS10_9.isContainedIn(unavailableUniversally));
125+
}

unittests/AST/AvailabilityDomainTests.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
//===--- AvailabilityDomainTests.cpp - Tests for AvailabilityDomain -------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
113
#include "swift/AST/AvailabilityDomain.h"
214
#include "gtest/gtest.h"
315

unittests/AST/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_swift_unittest(SwiftASTTests
22
ArithmeticEvaluator.cpp
33
ASTDumperTests.cpp
44
ASTWalkerTests.cpp
5+
AvailabilityContextTests.cpp
56
AvailabilityDomainTests.cpp
67
IndexSubsetTests.cpp
78
DiagnosticBehaviorTests.cpp

unittests/AST/TestContext.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ static Decl *createOptionalType(ASTContext &ctx, SourceFile *fileForLookups,
3333
return decl;
3434
}
3535

36-
TestContext::TestContext(ShouldDeclareOptionalTypes optionals)
37-
: Ctx(*ASTContext::get(LangOpts, TypeCheckerOpts, SILOpts, SearchPathOpts,
36+
TestContext::TestContext(ShouldDeclareOptionalTypes optionals,
37+
llvm::Triple target)
38+
: TestContextBase(target),
39+
Ctx(*ASTContext::get(LangOpts, TypeCheckerOpts, SILOpts, SearchPathOpts,
3840
ClangImporterOpts, SymbolGraphOpts, CASOpts,
3941
SerializationOpts, SourceMgr, Diags)) {
4042
registerParseRequestFunctions(Ctx.evaluator);

unittests/AST/TestContext.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class TestContextBase {
4040
SourceManager SourceMgr;
4141
DiagnosticEngine Diags;
4242

43-
TestContextBase() : Diags(SourceMgr) {
44-
LangOpts.Target = llvm::Triple(llvm::sys::getProcessTriple());
43+
TestContextBase(llvm::Triple target) : Diags(SourceMgr) {
44+
LangOpts.Target = target;
4545
}
4646
};
4747

@@ -57,7 +57,9 @@ class TestContext : public TestContextBase {
5757
public:
5858
ASTContext &Ctx;
5959

60-
TestContext(ShouldDeclareOptionalTypes optionals = DoNotDeclareOptionalTypes);
60+
TestContext(
61+
ShouldDeclareOptionalTypes optionals = DoNotDeclareOptionalTypes,
62+
llvm::Triple target = llvm::Triple(llvm::sys::getProcessTriple()));
6163

6264
template <typename Nominal>
6365
typename std::enable_if<!std::is_same<Nominal, swift::ClassDecl>::value,

0 commit comments

Comments
 (0)