Skip to content

Commit e943c4b

Browse files
committed
AST: Build statement condition availability scopes using AvailabilityContext.
Preparation for building availability scopes for `if #available` statements that query non-platform availability domains.
1 parent 119109d commit e943c4b

File tree

6 files changed

+153
-48
lines changed

6 files changed

+153
-48
lines changed

include/swift/AST/AvailabilityContext.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,15 @@ class AvailabilityContext {
9999
void constrainWithContext(const AvailabilityContext &other,
100100
const ASTContext &ctx);
101101

102-
/// Constrain the platform availability range with `platformRange`.
102+
/// Constrain the platform version range with `range`.
103103
// FIXME: [availability] Remove; superseded by constrainWithAvailableRange().
104-
void constrainWithPlatformRange(const AvailabilityRange &platformRange,
104+
void constrainWithPlatformRange(const AvailabilityRange &range,
105105
const ASTContext &ctx);
106106

107+
/// Expand the platform version range to contain `range`.
108+
void unionWithPlatformRange(const AvailabilityRange &range,
109+
const ASTContext &ctx);
110+
107111
/// Constrain the available range for `domain` by `range`.
108112
void constrainWithAvailabilityRange(const AvailabilityRange &range,
109113
AvailabilityDomain domain,

include/swift/AST/AvailabilityContextStorage.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class AvailabilityContext::DomainInfo final {
4444
bool isUnavailable() const { return range.isKnownUnreachable(); }
4545

4646
bool constrainRange(const AvailabilityRange &range);
47+
bool unionRange(const AvailabilityRange &range);
4748

4849
void Profile(llvm::FoldingSetNodeID &ID) const {
4950
ID.AddPointer(domain.getOpaqueValue());

lib/AST/AvailabilityContext.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ static bool constrainRange(AvailabilityRange &existing,
4646
return true;
4747
}
4848

49+
static bool unionRange(AvailabilityRange &existing,
50+
const AvailabilityRange &other) {
51+
if (!existing.isContainedIn(other))
52+
return false;
53+
54+
existing = other;
55+
return true;
56+
}
57+
4958
/// Returns true if `domainInfos` will be constrained by merging the domain
5059
/// availability represented by `otherDomainInfo`. Additionally, this function
5160
/// has a couple of side-effects:
@@ -136,6 +145,11 @@ bool AvailabilityContext::DomainInfo::constrainRange(
136145
return ::constrainRange(range, otherRange);
137146
}
138147

148+
bool AvailabilityContext::DomainInfo::unionRange(
149+
const AvailabilityRange &otherRange) {
150+
return ::unionRange(range, otherRange);
151+
}
152+
139153
AvailabilityContext
140154
AvailabilityContext::forPlatformRange(const AvailabilityRange &range,
141155
const ASTContext &ctx) {
@@ -284,9 +298,22 @@ void AvailabilityContext::constrainWithPlatformRange(
284298
storage->getDomainInfos(), ctx);
285299
}
286300

301+
void AvailabilityContext::unionWithPlatformRange(const AvailabilityRange &range,
302+
const ASTContext &ctx) {
303+
auto platformRange = storage->platformRange;
304+
if (!unionRange(platformRange, range))
305+
return;
306+
307+
// FIXME: [availability] Should this remove any unavailable platform domains?
308+
309+
storage = Storage::get(platformRange, storage->isDeprecated,
310+
storage->getDomainInfos(), ctx);
311+
}
312+
287313
void AvailabilityContext::constrainWithAvailabilityRange(
288314
const AvailabilityRange &range, AvailabilityDomain domain,
289315
const ASTContext &ctx) {
316+
DEBUG_ASSERT(domain.supportsContextRefinement());
290317

291318
if (domain.isActive(ctx) && domain.isPlatform()) {
292319
constrainWithPlatformRange(range, ctx);

lib/AST/AvailabilityScopeBuilder.cpp

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ class AvailabilityScopeBuilder : private ASTWalker {
103103
return availability;
104104
}
105105

106+
const AvailabilityContext constrainCurrentAvailabilityWithContext(
107+
const AvailabilityContext &otherContext) {
108+
auto availability = getCurrentScope()->getAvailabilityContext();
109+
availability.constrainWithContext(otherContext, Context);
110+
return availability;
111+
}
112+
106113
void pushContext(AvailabilityScope *scope, ParentTy popAfterNode) {
107114
ContextInfo info;
108115
info.Scope = scope;
@@ -701,16 +708,16 @@ class AvailabilityScopeBuilder : private ASTWalker {
701708
/// There is no need for the caller to explicitly traverse the children
702709
/// of this node.
703710
void buildIfStmtRefinementContext(IfStmt *ifStmt) {
704-
std::optional<AvailabilityRange> thenRange;
705-
std::optional<AvailabilityRange> elseRange;
706-
std::tie(thenRange, elseRange) =
711+
std::optional<AvailabilityContext> thenContext;
712+
std::optional<AvailabilityContext> elseContext;
713+
std::tie(thenContext, elseContext) =
707714
buildStmtConditionRefinementContext(ifStmt->getCond());
708715

709-
if (thenRange.has_value()) {
716+
if (thenContext.has_value()) {
710717
// Create a new context for the Then branch and traverse it in that new
711718
// context.
712719
auto availabilityContext =
713-
constrainCurrentAvailabilityWithPlatformRange(thenRange.value());
720+
constrainCurrentAvailabilityWithContext(*thenContext);
714721
auto *thenScope = AvailabilityScope::createForIfStmtThen(
715722
Context, ifStmt, getCurrentDeclContext(), getCurrentScope(),
716723
availabilityContext);
@@ -730,11 +737,11 @@ class AvailabilityScopeBuilder : private ASTWalker {
730737
// the current platform and minimum deployment target.
731738
// If we add a more precise version range lattice (i.e., one that can
732739
// support "<") we should create non-empty contexts for the Else branch.
733-
if (elseRange.has_value()) {
740+
if (elseContext.has_value()) {
734741
// Create a new context for the Then branch and traverse it in that new
735742
// context.
736743
auto availabilityContext =
737-
constrainCurrentAvailabilityWithPlatformRange(elseRange.value());
744+
constrainCurrentAvailabilityWithContext(*elseContext);
738745
auto *elseScope = AvailabilityScope::createForIfStmtElse(
739746
Context, ifStmt, getCurrentDeclContext(), getCurrentScope(),
740747
availabilityContext);
@@ -749,14 +756,14 @@ class AvailabilityScopeBuilder : private ASTWalker {
749756
/// There is no need for the caller to explicitly traverse the children
750757
/// of this node.
751758
void buildWhileStmtRefinementContext(WhileStmt *whileStmt) {
752-
std::optional<AvailabilityRange> bodyRange =
759+
std::optional<AvailabilityContext> bodyContext =
753760
buildStmtConditionRefinementContext(whileStmt->getCond()).first;
754761

755-
if (bodyRange.has_value()) {
762+
if (bodyContext.has_value()) {
756763
// Create a new context for the body and traverse it in the new
757764
// context.
758765
auto availabilityContext =
759-
constrainCurrentAvailabilityWithPlatformRange(bodyRange.value());
766+
constrainCurrentAvailabilityWithContext(*bodyContext);
760767
auto *bodyScope = AvailabilityScope::createForWhileStmtBody(
761768
Context, whileStmt, getCurrentDeclContext(), getCurrentScope(),
762769
availabilityContext);
@@ -783,15 +790,15 @@ class AvailabilityScopeBuilder : private ASTWalker {
783790
// This is slightly tricky because, unlike our other control constructs,
784791
// the refined region is not lexically contained inside the construct
785792
// introducing the availability scope.
786-
std::optional<AvailabilityRange> fallthroughRange;
787-
std::optional<AvailabilityRange> elseRange;
788-
std::tie(fallthroughRange, elseRange) =
793+
std::optional<AvailabilityContext> fallthroughContext;
794+
std::optional<AvailabilityContext> elseContext;
795+
std::tie(fallthroughContext, elseContext) =
789796
buildStmtConditionRefinementContext(guardStmt->getCond());
790797

791798
if (Stmt *elseBody = guardStmt->getBody()) {
792-
if (elseRange.has_value()) {
799+
if (elseContext.has_value()) {
793800
auto availabilityContext =
794-
constrainCurrentAvailabilityWithPlatformRange(elseRange.value());
801+
constrainCurrentAvailabilityWithContext(*elseContext);
795802
auto *trueScope = AvailabilityScope::createForGuardStmtElse(
796803
Context, guardStmt, getCurrentDeclContext(), getCurrentScope(),
797804
availabilityContext);
@@ -804,12 +811,12 @@ class AvailabilityScopeBuilder : private ASTWalker {
804811

805812
auto *parentBrace = dyn_cast<BraceStmt>(Parent.getAsStmt());
806813
assert(parentBrace && "Expected parent of GuardStmt to be BraceStmt");
807-
if (!fallthroughRange.has_value())
814+
if (!fallthroughContext.has_value())
808815
return;
809816

810817
// Create a new context for the fallthrough.
811818
auto fallthroughAvailability =
812-
constrainCurrentAvailabilityWithPlatformRange(fallthroughRange.value());
819+
constrainCurrentAvailabilityWithContext(*fallthroughContext);
813820
auto *fallthroughScope = AvailabilityScope::createForGuardStmtFallthrough(
814821
Context, guardStmt, parentBrace, getCurrentDeclContext(),
815822
getCurrentScope(), fallthroughAvailability);
@@ -818,10 +825,11 @@ class AvailabilityScopeBuilder : private ASTWalker {
818825
}
819826

820827
/// Build the availability scopes for a StmtCondition and return a pair of
821-
/// optional version ranges, the first for the true branch and the second
822-
/// for the false branch. A value of `nullopt` for a given branch indicates
823-
/// that the branch does not introduce a new scope.
824-
std::pair<std::optional<AvailabilityRange>, std::optional<AvailabilityRange>>
828+
/// optional availability contexts, the first for the true branch and the
829+
/// second for the false branch. A value of `nullopt` for a given branch
830+
/// indicates that the branch does not introduce a new scope.
831+
std::pair<std::optional<AvailabilityContext>,
832+
std::optional<AvailabilityContext>>
825833
buildStmtConditionRefinementContext(StmtCondition cond) {
826834
if (Context.LangOpts.DisableAvailabilityChecking)
827835
return {};
@@ -835,8 +843,14 @@ class AvailabilityScopeBuilder : private ASTWalker {
835843
// for the StmtCondition.
836844
unsigned nestedCount = 0;
837845

838-
// Tracks the potential version range when the condition is false.
839-
auto falseFlow = AvailabilityRange::neverAvailable();
846+
// Tracks the availability of the region in which the condition is false.
847+
// By default, we assume the false flow is unreachable and therefore start
848+
// with no platform versions available in the context. If we find that it
849+
// is actually reachable, the context must expand to cover the potentially
850+
// available range.
851+
auto falseFlowContext = getCurrentScope()->getAvailabilityContext();
852+
falseFlowContext.constrainWithPlatformRange(
853+
AvailabilityRange::neverAvailable(), Context);
840854

841855
AvailabilityScope *startingScope = getCurrentScope();
842856

@@ -845,7 +859,7 @@ class AvailabilityScopeBuilder : private ASTWalker {
845859

846860
for (StmtConditionElement element : cond) {
847861
auto *currentScope = getCurrentScope();
848-
auto currentInfo = currentScope->getPlatformAvailabilityRange();
862+
auto currentContext = currentScope->getAvailabilityContext();
849863

850864
// If the element is not a condition, walk it in the current scope.
851865
if (element.getKind() != StmtConditionElement::CK_Availability) {
@@ -854,7 +868,9 @@ class AvailabilityScopeBuilder : private ASTWalker {
854868
// potentially be false, so conservatively combine the version
855869
// range of the current context with the accumulated false flow
856870
// of all other conjuncts.
857-
falseFlow.unionWith(currentInfo);
871+
// FIXME: [availability] Fully union with current context.
872+
falseFlowContext.unionWithPlatformRange(
873+
currentContext.getPlatformRange(), Context);
858874

859875
element.walk(*this);
860876
continue;
@@ -956,7 +972,9 @@ class AvailabilityScopeBuilder : private ASTWalker {
956972
}
957973
}
958974

959-
if (currentInfo.isContainedIn(newConstraint)) {
975+
auto newContext = currentContext;
976+
newContext.constrainWithPlatformRange(newConstraint, Context);
977+
if (currentContext.isContainedIn(newContext)) {
960978
// No need to actually create the availability scope if we know it is
961979
// useless.
962980
continue;
@@ -967,38 +985,38 @@ class AvailabilityScopeBuilder : private ASTWalker {
967985
// context.
968986
// We could be more precise here if we enriched the lattice to include
969987
// ranges of the form [x, y).
970-
falseFlow.unionWith(currentInfo);
988+
// FIXME: [availability] Fully union with current context.
989+
falseFlowContext.unionWithPlatformRange(currentContext.getPlatformRange(),
990+
Context);
971991

972-
auto constrainedAvailability =
973-
constrainCurrentAvailabilityWithPlatformRange(newConstraint);
974992
auto *scope = AvailabilityScope::createForConditionFollowingQuery(
975993
Context, query, lastElement, getCurrentDeclContext(), currentScope,
976-
constrainedAvailability);
994+
newContext);
977995

978996
pushContext(scope, ParentTy());
979997
++nestedCount;
980998
}
981999

982-
std::optional<AvailabilityRange> falseRefinement = std::nullopt;
1000+
std::optional<AvailabilityContext> falseRefinement = std::nullopt;
9831001
// The version range for the false branch should never have any versions
9841002
// that weren't possible when the condition started evaluating.
985-
assert(
986-
falseFlow.isContainedIn(startingScope->getPlatformAvailabilityRange()));
1003+
assert(falseFlowContext.isContainedIn(
1004+
startingScope->getAvailabilityContext()));
9871005

988-
// If the starting version range is not completely contained in the
989-
// false flow version range then it must be the case that false flow range
990-
// is strictly smaller than the starting range (because the false flow
991-
// range *is* contained in the starting range), so we should introduce a
1006+
// If the starting availability context is not completely contained in the
1007+
// false flow context then it must be the case that false flow context
1008+
// is strictly smaller than the starting context (because the false flow
1009+
// context *is* contained in the starting context), so we should introduce a
9921010
// new availability scope for the false flow.
993-
if (!startingScope->getPlatformAvailabilityRange().isContainedIn(
994-
falseFlow)) {
995-
falseRefinement = falseFlow;
1011+
if (!startingScope->getAvailabilityContext().isContainedIn(
1012+
falseFlowContext)) {
1013+
falseRefinement = falseFlowContext;
9961014
}
9971015

9981016
auto makeResult =
999-
[isUnavailability](std::optional<AvailabilityRange> trueRefinement,
1000-
std::optional<AvailabilityRange> falseRefinement) {
1001-
if (isUnavailability.has_value() && isUnavailability.value()) {
1017+
[isUnavailability](std::optional<AvailabilityContext> trueRefinement,
1018+
std::optional<AvailabilityContext> falseRefinement) {
1019+
if (isUnavailability.has_value() && *isUnavailability) {
10021020
// If this is an unavailability check, invert the result.
10031021
return std::make_pair(falseRefinement, trueRefinement);
10041022
}
@@ -1014,8 +1032,7 @@ class AvailabilityScopeBuilder : private ASTWalker {
10141032

10151033
assert(getCurrentScope() == startingScope);
10161034

1017-
return makeResult(nestedScope->getPlatformAvailabilityRange(),
1018-
falseRefinement);
1035+
return makeResult(nestedScope->getAvailabilityContext(), falseRefinement);
10191036
}
10201037

10211038
/// Return the best active spec for the target platform or nullptr if no

test/Sema/availability_scopes.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ extension SomeClass {
9494
// CHECK-NEXT: {{^}} (condition_following_availability version=56
9595
// CHECK-NEXT: {{^}} (guard_fallthrough version=56
9696
// CHECK-NEXT: {{^}} (decl version=57 decl=funcInInnerIfElse()
97+
// CHECK-NEXT: {{^}} (decl version=53 decl=funcInOuterIfElse()
9798
@available(OSX 51, *)
9899
func functionWithStmtCondition() {
99100
if #available(OSX 52, *),
@@ -109,6 +110,9 @@ func functionWithStmtCondition() {
109110
@available(OSX 57, *)
110111
func funcInInnerIfElse() { }
111112
}
113+
} else {
114+
@available(OSX 53, *)
115+
func funcInOuterIfElse() { }
112116
}
113117
}
114118

@@ -247,6 +251,45 @@ extension SomeClass {
247251
}()
248252
}
249253

254+
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=extension.SomeClass
255+
// CHECK-NEXT: {{^}} (decl version=50 unavailable=macOS decl=extension.SomeClass
256+
// CHECK-NEXT: {{^}} (decl version=51 unavailable=macOS decl=functionWithStmtConditionsInUnavailableExt()
257+
// CHECK-NEXT: {{^}} (condition_following_availability version=52 unavailable=macOS
258+
// CHECK-NEXT: {{^}} (condition_following_availability version=53 unavailable=macOS
259+
// CHECK-NEXT: {{^}} (if_then version=53 unavailable=macOS
260+
// CHECK-NEXT: {{^}} (condition_following_availability version=54 unavailable=macOS
261+
// CHECK-NEXT: {{^}} (if_then version=54 unavailable=macOS
262+
// CHECK-NEXT: {{^}} (condition_following_availability version=55 unavailable=macOS
263+
// CHECK-NEXT: {{^}} (decl version=55 unavailable=macOS decl=funcInGuardElse()
264+
// CHECK-NEXT: {{^}} (guard_fallthrough version=55 unavailable=macOS
265+
// CHECK-NEXT: {{^}} (condition_following_availability version=56 unavailable=macOS
266+
// CHECK-NEXT: {{^}} (guard_fallthrough version=56 unavailable=macOS
267+
// CHECK-NEXT: {{^}} (decl version=57 unavailable=macOS decl=funcInInnerIfElse()
268+
// CHECK-NEXT: {{^}} (decl version=53 unavailable=macOS decl=funcInOuterIfElse()
269+
@available(OSX, unavailable)
270+
extension SomeClass {
271+
@available(OSX 51, *)
272+
func functionWithStmtConditionsInUnavailableExt() {
273+
if #available(OSX 52, *),
274+
let x = (nil as Int?),
275+
#available(OSX 53, *) {
276+
if #available(OSX 54, *) {
277+
guard #available(OSX 55, *) else {
278+
@available(OSX 55, *)
279+
func funcInGuardElse() { }
280+
}
281+
guard #available(OSX 56, *) else { }
282+
} else {
283+
@available(OSX 57, *)
284+
func funcInInnerIfElse() { }
285+
}
286+
} else {
287+
@available(OSX 53, *)
288+
func funcInOuterIfElse() { }
289+
}
290+
}
291+
}
292+
250293
// CHECK-NEXT: {{^}} (decl_implicit version=50 decl=wrappedValue
251294

252295
@propertyWrapper

0 commit comments

Comments
 (0)