Skip to content

Commit a6c2379

Browse files
committed
Add #available guards to init(rawValue:)
When a case has an @available(currentPlatform, introduced: x.y) attribute, the auto-derived init(rawValue:) will now include a “guard #available(currentPlatform x.y, *) else { return nil }” check to prevent the new case from being returned. This requires a targeted hack to disable @available(introduced:) checks in auto-derived init(rawValue:) implementations; the availability checker is SourceLoc-based, so it can’t tell that the synthesized `guard` covers the use of the case. I’m not happy about that, but fixing the deeper problem is a much larger task than I can take on in one day.
1 parent c48be00 commit a6c2379

File tree

4 files changed

+114
-4
lines changed

4 files changed

+114
-4
lines changed

lib/Sema/DerivedConformanceRawRepresentable.cpp

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,17 @@ static void
192192
deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
193193
// enum SomeEnum : SomeType {
194194
// case A = 111, B = 222
195+
// @available(iOS 10, *) case C = 333
195196
// @derived
196197
// init?(rawValue: SomeType) {
197198
// switch rawValue {
198199
// case 111:
199200
// self = .A
200201
// case 222:
201202
// self = .B
203+
// case 333:
204+
// guard #available(iOS 10, *) else { return nil }
205+
// self = .C
202206
// default:
203207
// return nil
204208
// }
@@ -234,6 +238,7 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
234238
SmallVector<ASTNode, 4> cases;
235239
unsigned Idx = 0;
236240
for (auto elt : enumDecl->getAllElements()) {
241+
// litPat = "\(elt.rawValueExpr)", e.g. "42" as a pattern
237242
LiteralExpr *litExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
238243
if (isStringEnum) {
239244
// In case of a string enum we are calling the _findStringSwitchCase
@@ -245,23 +250,78 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
245250
nullptr, nullptr);
246251
litPat->setImplicit();
247252

248-
auto labelItem = CaseLabelItem(litPat);
253+
// Will collect any preliminary guards, plus the final assignment.
254+
SmallVector<ASTNode, 2> stmts;
255+
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+
}
249302

303+
// valueExpr = "\(enumType).\(elt)"
250304
auto eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit*/true);
251305
auto metaTyRef = TypeExpr::createImplicit(enumType, C);
252306
auto valueExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef);
253307

308+
// selfRef = "self"
254309
auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(),
255310
/*implicit*/true,
256311
AccessSemantics::DirectToStorage);
257312

313+
// assignment = "\(selfRef) = \(valueExpr)"
258314
auto assignment = new (C) AssignExpr(selfRef, SourceLoc(), valueExpr,
259315
/*implicit*/ true);
316+
317+
stmts.push_back(ASTNode(assignment));
260318

319+
// body = stmts.joined(separator: "\n")
261320
auto body = BraceStmt::create(C, SourceLoc(),
262-
ASTNode(assignment), SourceLoc());
321+
stmts, SourceLoc());
263322

264-
cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem,
323+
// cases.append("case \(litPat): \(body)")
324+
cases.push_back(CaseStmt::create(C, SourceLoc(), CaseLabelItem(litPat),
265325
/*HasBoundDecls=*/false, SourceLoc(),
266326
SourceLoc(), body));
267327
Idx++;

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,19 @@ class AvailabilityWalker : public ASTWalker {
23012301
Optional<TypeChecker::FragileFunctionKind> FragileKind;
23022302
bool TreatUsableFromInlineAsPublic = false;
23032303

2304+
/// Returns true if DC is an \c init(rawValue:) declaration and it is marked
2305+
/// implicit.
2306+
bool inSynthesizedInitRawValue() {
2307+
auto init = dyn_cast_or_null<ConstructorDecl>(
2308+
DC->getInnermostDeclarationDeclContext());
2309+
2310+
return init &&
2311+
init->isImplicit() &&
2312+
init->getParameters()->size() == 1 &&
2313+
init->getParameters()->get(0)->getArgumentName() ==
2314+
DC->getASTContext().Id_rawValue;
2315+
}
2316+
23042317
public:
23052318
AvailabilityWalker(
23062319
TypeChecker &TC, DeclContext *DC) : TC(TC), DC(DC) {
@@ -2320,8 +2333,20 @@ class AvailabilityWalker : public ASTWalker {
23202333
};
23212334

23222335
if (auto DR = dyn_cast<DeclRefExpr>(E)) {
2336+
DeclAvailabilityFlags flags = None;
2337+
if (inSynthesizedInitRawValue())
2338+
// HACK: If a raw-value enum has cases with `@available(introduced:)`
2339+
// attributes, the auto-derived `init(rawValue:)` will contain
2340+
// DeclRefExprs which reference those cases. It will also contain
2341+
// appropriate `guard #available` statements to keep them from running
2342+
// on older versions, but the availability checker can't verify that
2343+
// because the synthesized code uses invalid SourceLocs. Don't diagnose
2344+
// these errors; instead, take it on faith that
2345+
// DerivedConformanceRawRepresentable will do the right thing.
2346+
flags |= DeclAvailabilityFlag::AllowPotentiallyUnavailable;
2347+
23232348
diagAvailability(DR->getDecl(), DR->getSourceRange(),
2324-
getEnclosingApplyExpr());
2349+
getEnclosingApplyExpr(), flags);
23252350
maybeDiagStorageAccess(DR->getDecl(), DR->getSourceRange(), DC);
23262351
}
23272352
if (auto MR = dyn_cast<MemberRefExpr>(E)) {
@@ -2583,6 +2608,9 @@ AvailabilityWalker::diagAvailability(const ValueDecl *D, SourceRange R,
25832608
&& isa<ProtocolDecl>(D))
25842609
return false;
25852610

2611+
if (Flags.contains(DeclAvailabilityFlag::AllowPotentiallyUnavailable))
2612+
return false;
2613+
25862614
// Diagnose (and possibly signal) for potential unavailability
25872615
auto maybeUnavail = TC.checkDeclarationAvailability(D, R.Start, DC);
25882616
if (maybeUnavail.hasValue()) {

lib/Sema/TypeCheckAvailability.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ enum class DeclAvailabilityFlag : uint8_t {
3838
AllowPotentiallyUnavailableProtocol = 1 << 0,
3939
ContinueOnPotentialUnavailability = 1 << 1,
4040
ForInout = 1 << 2,
41+
AllowPotentiallyUnavailable = 1 << 3,
4142
};
4243
using DeclAvailabilityFlags = OptionSet<DeclAvailabilityFlag>;
4344

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// 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
4+
5+
public enum E: Int {
6+
case a, b
7+
@available(macOS 500, iOS 500, watchOS 500, tvOS 500, *)
8+
case c
9+
}
10+
11+
// CHECK-LABEL: sil [serialized] [ossa] @$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC
12+
// CHECK: integer_literal $Builtin.Word, 500
13+
// CHECK: end sil function '$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC'
14+
15+
// CHECK-LABEL: sil [serialized] [ossa] @$s32enum_raw_representable_available1EO0B5ValueSivg
16+
// CHECK: switch_enum %0 : $E
17+
// CHECK: end sil function '$s32enum_raw_representable_available1EO0B5ValueSivg'
18+
19+
20+
// CHECK-RESILIENT-DAG: sil [ossa] @$s32enum_raw_representable_available1EO0B5ValueACSgSi_tcfC
21+
// CHECK-RESILIENT-DAG: sil [ossa] @$s32enum_raw_representable_available1EO0B5ValueSivg

0 commit comments

Comments
 (0)