Skip to content

Commit 5da6668

Browse files
committed
Refactor availability-checking code to handle more cases
Moves a lot of it into helper functions and types so it doesn’t disrupt the main flow of the code so much. Also makes it handle always-unavailable and obsolete cases (by skipping them).
1 parent 2bd9eb3 commit 5da6668

File tree

3 files changed

+152
-65
lines changed

3 files changed

+152
-65
lines changed

lib/AST/Attr.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ const AvailableAttr *DeclAttributes::getPotentiallyUnavailable(
147147
continue;
148148

149149
// Definitely not available.
150-
if (AvAttr->isUnconditionallyDeprecated())
150+
if (AvAttr->isUnconditionallyUnavailable())
151151
return AvAttr;
152152

153153
switch (AvAttr->getVersionAvailability(ctx)) {
@@ -164,6 +164,7 @@ const AvailableAttr *DeclAttributes::getPotentiallyUnavailable(
164164
case AvailableVersionComparison::Unavailable:
165165
case AvailableVersionComparison::Obsoleted:
166166
conditional = AvAttr;
167+
break;
167168
}
168169
}
169170

lib/Sema/DerivedConformanceRawRepresentable.cpp

Lines changed: 95 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,83 @@ static VarDecl *deriveRawRepresentable_raw(DerivedConformance &derived) {
188188
return propDecl;
189189
}
190190

191+
/// Contains information needed to synthesize a runtime version check.
192+
struct RuntimeVersionCheck {
193+
PlatformKind Platform;
194+
llvm::VersionTuple Version;
195+
196+
RuntimeVersionCheck(PlatformKind Platform, llvm::VersionTuple Version)
197+
: Platform(Platform), Version(Version)
198+
{ }
199+
200+
VersionRange getVersionRange() const {
201+
return VersionRange::allGTE(Version);
202+
}
203+
204+
/// Synthesizes a statement which returns nil if the runtime version check
205+
/// fails, e.g. "guard #available(iOS 10, *) else { return nil }".
206+
Stmt *createEarlyReturnStmt(ASTContext &C) const {
207+
// platformSpec = "\(attr.platform) \(attr.introduced)"
208+
auto platformSpec = new (C) PlatformVersionConstraintAvailabilitySpec(
209+
Platform, SourceLoc(),
210+
Version, SourceLoc()
211+
);
212+
213+
// otherSpec = "*"
214+
auto otherSpec = new (C) OtherPlatformAvailabilitySpec(SourceLoc());
215+
216+
// availableInfo = "#available(\(platformSpec), \(otherSpec))"
217+
auto availableInfo = PoundAvailableInfo::create(C, SourceLoc(),
218+
{ platformSpec, otherSpec },
219+
SourceLoc());
220+
221+
// This won't be filled in by TypeCheckAvailability because we have
222+
// invalid SourceLocs in this area of the AST.
223+
availableInfo->setAvailableRange(getVersionRange());
224+
225+
// earlyReturnBody = "{ return nil }"
226+
auto earlyReturn = new (C) FailStmt(SourceLoc(), SourceLoc());
227+
auto earlyReturnBody = BraceStmt::create(C, SourceLoc(),
228+
ASTNode(earlyReturn),
229+
SourceLoc(), /*implicit=*/true);
230+
231+
// guardStmt = "guard \(availableInfo) else \(earlyReturnBody)"
232+
StmtConditionElement conds[1] = { availableInfo };
233+
auto guardStmt = new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conds),
234+
earlyReturnBody, /*implicit=*/true);
235+
236+
return guardStmt;
237+
}
238+
};
239+
240+
/// Checks if the case will be available at runtime given the current target
241+
/// platform. If it will never be available, returns false. If it will always
242+
/// be available, returns true. If it will sometimes be available, adds
243+
/// information about the runtime check needed to ensure it is available to
244+
/// \c versionCheck and returns true.
245+
static bool checkAvailability(EnumElementDecl* elt, ASTContext &C,
246+
Optional<RuntimeVersionCheck> &versionCheck) {
247+
auto *attr = elt->getAttrs().getPotentiallyUnavailable(C);
248+
249+
// Is it always available?
250+
if (!attr)
251+
return true;
252+
253+
AvailableVersionComparison availability = attr->getVersionAvailability(C);
254+
255+
assert(availability != AvailableVersionComparison::Available &&
256+
"DeclAttributes::getPotentiallyUnavailable() shouldn't "
257+
"return an available attribute");
258+
259+
// Is it never available?
260+
if (availability != AvailableVersionComparison::PotentiallyUnavailable)
261+
return false;
262+
263+
// It's conditionally available; create a version constraint and return true.
264+
versionCheck.emplace(attr->Platform, *attr->Introduced);
265+
return true;
266+
}
267+
191268
static void
192269
deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
193270
// enum SomeEnum : SomeType {
@@ -238,7 +315,15 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
238315
SmallVector<ASTNode, 4> cases;
239316
unsigned Idx = 0;
240317
for (auto elt : enumDecl->getAllElements()) {
241-
// litPat = "\(elt.rawValueExpr)", e.g. "42" as a pattern
318+
// First, check case availability. If the case will definitely be
319+
// unavailable, skip it. If it might be unavailable at runtime, save
320+
// information about that check in versionCheck and keep processing this
321+
// element.
322+
Optional<RuntimeVersionCheck> versionCheck(None);
323+
if (!checkAvailability(elt, C, versionCheck))
324+
continue;
325+
326+
// litPat = elt.rawValueExpr as a pattern
242327
LiteralExpr *litExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
243328
if (isStringEnum) {
244329
// In case of a string enum we are calling the _findStringSwitchCase
@@ -250,73 +335,31 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
250335
nullptr, nullptr);
251336
litPat->setImplicit();
252337

253-
// Will collect any preliminary guards, plus the final assignment.
338+
/// Statements in the body of this case.
254339
SmallVector<ASTNode, 2> stmts;
255340

256-
// If there's an @available attribute specifying when the case was
257-
// introduced, generate an early return on unavailability, e.g.
258-
// "guard #available(iOS 9, *) else { return nil }"
259-
for (auto *attr : elt->getAttrs().getAttributes<AvailableAttr>()) {
260-
// We only care about attributes which have an "introduced" version for
261-
// the platform we're building for. We don't care about language version
262-
// because we may be compiling in an older language mode, but writing APIs
263-
// for a newer one.
264-
if (attr->Introduced.hasValue() && attr->hasPlatform() &&
265-
attr->isActivePlatform(C) &&
266-
*attr->Introduced > C.LangOpts.getMinPlatformVersion()) {
267-
// platformSpec = "\(attr.platform) \(attr.introduced)"
268-
auto platformSpec =
269-
new (C) PlatformVersionConstraintAvailabilitySpec(
270-
attr->Platform, SourceLoc(),
271-
*attr->Introduced, SourceLoc()
272-
);
273-
274-
// otherSpec = "*"
275-
auto otherSpec = new (C) OtherPlatformAvailabilitySpec(SourceLoc());
276-
277-
// availableInfo = "#available(\(platformSpec), \(otherSpec))"
278-
auto availableInfo = PoundAvailableInfo::create(C, SourceLoc(),
279-
{ platformSpec, otherSpec },
280-
SourceLoc());
281-
282-
// This won't be filled in by TypeCheckAvailability because we have
283-
// invalid SourceLocs in this area of the AST.
284-
auto versionRange = VersionRange::allGTE(*attr->Introduced);
285-
availableInfo->setAvailableRange(versionRange);
286-
287-
// earlyReturnBody = "{ return nil }"
288-
auto earlyReturn = new (C) FailStmt(SourceLoc(), SourceLoc());
289-
auto earlyReturnBody = BraceStmt::create(C, SourceLoc(),
290-
ASTNode(earlyReturn),
291-
SourceLoc(), /*implicit=*/true);
292-
293-
// guardStmt = "guard \(vailableInfo) else \(earlyReturnBody)"
294-
SmallVector<StmtConditionElement, 1>
295-
conds{StmtConditionElement(availableInfo)};
296-
auto guardStmt = new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conds),
297-
earlyReturnBody, /*implicit=*/true);
298-
299-
stmts.push_back(guardStmt);
300-
}
301-
}
341+
// If checkAvailability() discovered we need a runtime version check,
342+
// add it now.
343+
if (versionCheck.hasValue())
344+
stmts.push_back(ASTNode(versionCheck->createEarlyReturnStmt(C)));
345+
346+
// Create a statement which assigns the case to self.
302347

303348
// valueExpr = "\(enumType).\(elt)"
304349
auto eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit*/true);
305350
auto metaTyRef = TypeExpr::createImplicit(enumType, C);
306351
auto valueExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef);
307352

308-
// selfRef = "self"
353+
// assignment = "self = \(valueExpr)"
309354
auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(),
310355
/*implicit*/true,
311356
AccessSemantics::DirectToStorage);
312-
313-
// assignment = "\(selfRef) = \(valueExpr)"
314357
auto assignment = new (C) AssignExpr(selfRef, SourceLoc(), valueExpr,
315358
/*implicit*/ true);
316359

317360
stmts.push_back(ASTNode(assignment));
318361

319-
// body = stmts.joined(separator: "\n")
362+
// body = "{ \(stmts) }" (the braces are silent)
320363
auto body = BraceStmt::create(C, SourceLoc(),
321364
stmts, SourceLoc());
322365

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,67 @@
11
// RUN: %target-typecheck-verify-swift
22
// RUN: %target-swift-emit-silgen -emit-sorted-sil %s | %FileCheck %s
3-
// RUN: %target-swift-emit-silgen -emit-sorted-sil -enable-resilience %s | %FileCheck -check-prefix=CHECK-RESILIENT %s
3+
// RUN: %target-swift-emit-silgen -emit-sorted-sil -enable-resilience %s | %FileCheck %s
44

55
// Really just requires a platform with meaningful runtime #available() tests.
66
// REQUIRES: objc_interop
77

88
public enum E: Int {
9-
case a, b
10-
@available(macOS 500, iOS 500, watchOS 500, tvOS 500, *)
11-
case c
9+
// For some reason, we generate strange SIL for the first case that's
10+
// difficult to validate. This just gets that out of the way.
11+
case sacrificial = -500
12+
13+
case normal = -1000
14+
@available(macOS 10.8, iOS 8, watchOS 1, tvOS 8, *)
15+
case alwaysAvailable = -2000
16+
@available(macOS 500.600.700, iOS 500.600.700, watchOS 500.600.700, tvOS 500.600.700, *)
17+
case potentiallyUnavailable = -3000
18+
@available(macOS, unavailable) @available(iOS, unavailable)
19+
@available(watchOS, unavailable) @available(tvOS, unavailable)
20+
case neverAvailable = -4000
1221
}
1322

14-
// CHECK-LABEL: sil [serialized] [ossa] @$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC
15-
// CHECK: integer_literal $Builtin.Word, 500
16-
// CHECK: end sil function '$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC'
23+
// CHECK-LABEL: sil {{(\[serialized\] )?}}[ossa] @$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC
1724

18-
// CHECK-LABEL: sil [serialized] [ossa] @$s32enum_raw_representable_available1EO0B5ValueSivg
19-
// CHECK: switch_enum %0 : $E
20-
// CHECK: end sil function '$s32enum_raw_representable_available1EO0B5ValueSivg'
25+
// CHECK-NOT: integer_literal $Builtin.IntLiteral, -4000
26+
27+
// CHECK: integer_literal $Builtin.IntLiteral, -1000
28+
// CHECK: cond_br {{[^,]+}}, [[normal:bb[0-9]+]]
29+
30+
// CHECK-NOT: integer_literal $Builtin.IntLiteral, -4000
31+
32+
// CHECK: integer_literal $Builtin.IntLiteral, -2000
33+
// CHECK: cond_br {{[^,]+}}, [[alwaysAvailable:bb[0-9]+]]
34+
35+
// CHECK-NOT: integer_literal $Builtin.IntLiteral, -4000
36+
37+
// CHECK: integer_literal $Builtin.IntLiteral, -3000
38+
// CHECK: cond_br {{[^,]+}}, [[potentiallyUnavailable:bb[0-9]+]]
2139

40+
// CHECK-NOT: integer_literal $Builtin.IntLiteral, -4000
2241

23-
// CHECK-RESILIENT-DAG: sil [ossa] @$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC
24-
// CHECK-RESILIENT-DAG: sil [ossa] @$s32enum_raw_representable_available1EO0B5ValueSivg
42+
// CHECK: [[potentiallyUnavailable]]:
43+
// CHECK-NEXT: integer_literal $Builtin.Word, 500
44+
// CHECK-NEXT: integer_literal $Builtin.Word, 600
45+
// CHECK-NEXT: integer_literal $Builtin.Word, 700
46+
// CHECK: function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
47+
// CHECK: cond_br {{[^,]+}}, [[potentiallyUnavailable_newEnough:bb[0-9]+]],
48+
49+
// CHECK-NOT: integer_literal $Builtin.IntLiteral, -4000
50+
51+
// CHECK: [[potentiallyUnavailable_newEnough]]:
52+
// CHECK: {{enum \$E|inject_enum_addr %[0-9]+ : \$\*E}}, #E.potentiallyUnavailable!enumelt
53+
54+
// CHECK: [[alwaysAvailable]]:
55+
// CHECK-NOT: function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
56+
// CHECK: {{enum \$E|inject_enum_addr %[0-9]+ : \$\*E}}, #E.alwaysAvailable!enumelt
57+
58+
// CHECK: [[normal]]:
59+
// CHECK-NOT: function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
60+
// CHECK: {{enum \$E|inject_enum_addr %[0-9]+ : \$\*E}}, #E.normal!enumelt
61+
62+
// CHECK: end sil function '$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC'
63+
64+
// CHECK-LABEL: sil {{(\[serialized\] )?}}[ossa] @$s32enum_raw_representable_available1EO0B5ValueSivg
65+
// CHECK: {{switch_enum %0 : \$E|switch_enum_addr %2 : \$\*E}}
66+
// CHECK-NOT: function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
67+
// CHECK: end sil function '$s32enum_raw_representable_available1EO0B5ValueSivg'

0 commit comments

Comments
 (0)