Skip to content

Commit 3fb93cd

Browse files
authored
Merge pull request #22198 from brentdax/im-on-the-case
Guard returns of limited-availability cases in init(rawValue:)
2 parents aead660 + 413dd85 commit 3fb93cd

File tree

7 files changed

+325
-38
lines changed

7 files changed

+325
-38
lines changed

include/swift/AST/Attr.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,11 @@ class DeclAttributes {
14461446
bool
14471447
isUnavailableInSwiftVersion(const version::Version &effectiveVersion) const;
14481448

1449+
/// Returns the first @available attribute that indicates
1450+
/// a declaration is unavailable, or the first one that indicates it's
1451+
/// potentially unavailable, or null otherwise.
1452+
const AvailableAttr *getPotentiallyUnavailable(const ASTContext &ctx) const;
1453+
14491454
/// Returns the first @available attribute that indicates
14501455
/// a declaration is unavailable, or null otherwise.
14511456
const AvailableAttr *getUnavailable(const ASTContext &ctx) const;

lib/AST/Attr.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,48 @@ DeclAttributes::isUnavailableInSwiftVersion(
131131
return false;
132132
}
133133

134+
const AvailableAttr *
135+
DeclAttributes::getPotentiallyUnavailable(const ASTContext &ctx) const {
136+
const AvailableAttr *potential = nullptr;
137+
const AvailableAttr *conditional = nullptr;
138+
139+
for (auto Attr : *this)
140+
if (auto AvAttr = dyn_cast<AvailableAttr>(Attr)) {
141+
if (AvAttr->isInvalid())
142+
continue;
143+
144+
if (!AvAttr->isActivePlatform(ctx) &&
145+
!AvAttr->isLanguageVersionSpecific() &&
146+
!AvAttr->isPackageDescriptionVersionSpecific())
147+
continue;
148+
149+
// Definitely not available.
150+
if (AvAttr->isUnconditionallyUnavailable())
151+
return AvAttr;
152+
153+
switch (AvAttr->getVersionAvailability(ctx)) {
154+
case AvailableVersionComparison::Available:
155+
// Doesn't limit the introduced version.
156+
break;
157+
158+
case AvailableVersionComparison::PotentiallyUnavailable:
159+
// We'll return this if we don't see something that proves it's
160+
// not available in this version.
161+
potential = AvAttr;
162+
break;
163+
164+
case AvailableVersionComparison::Unavailable:
165+
case AvailableVersionComparison::Obsoleted:
166+
conditional = AvAttr;
167+
break;
168+
}
169+
}
170+
171+
if (conditional)
172+
return conditional;
173+
return potential;
174+
}
175+
134176
const AvailableAttr *DeclAttributes::getUnavailable(
135177
const ASTContext &ctx) const {
136178
const AvailableAttr *conditional = nullptr;

lib/Sema/DerivedConformanceRawRepresentable.cpp

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,101 @@ 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(
218+
C, SourceLoc(), { platformSpec, otherSpec }, SourceLoc());
219+
220+
// This won't be filled in by TypeCheckAvailability because we have
221+
// invalid SourceLocs in this area of the AST.
222+
availableInfo->setAvailableRange(getVersionRange());
223+
224+
// earlyReturnBody = "{ return nil }"
225+
auto earlyReturn = new (C) FailStmt(SourceLoc(), SourceLoc());
226+
auto earlyReturnBody = BraceStmt::create(C, SourceLoc(),
227+
ASTNode(earlyReturn),
228+
SourceLoc(), /*implicit=*/true);
229+
230+
// guardStmt = "guard \(availableInfo) else \(earlyReturnBody)"
231+
StmtConditionElement conds[1] = { availableInfo };
232+
auto guardStmt = new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conds),
233+
earlyReturnBody, /*implicit=*/true);
234+
235+
return guardStmt;
236+
}
237+
};
238+
239+
/// Checks if the case will be available at runtime given the current target
240+
/// platform. If it will never be available, returns false. If it will always
241+
/// be available, returns true. If it will sometimes be available, adds
242+
/// information about the runtime check needed to ensure it is available to
243+
/// \c versionCheck and returns true.
244+
static bool checkAvailability(const EnumElementDecl* elt, ASTContext &C,
245+
Optional<RuntimeVersionCheck> &versionCheck) {
246+
auto *attr = elt->getAttrs().getPotentiallyUnavailable(C);
247+
248+
// Is it always available?
249+
if (!attr)
250+
return true;
251+
252+
AvailableVersionComparison availability = attr->getVersionAvailability(C);
253+
254+
assert(availability != AvailableVersionComparison::Available &&
255+
"DeclAttributes::getPotentiallyUnavailable() shouldn't "
256+
"return an available attribute");
257+
258+
// Is it never available?
259+
if (availability != AvailableVersionComparison::PotentiallyUnavailable)
260+
return false;
261+
262+
// It's conditionally available; create a version constraint and return true.
263+
assert(attr->getPlatformAgnosticAvailability() ==
264+
PlatformAgnosticAvailabilityKind::None &&
265+
"can only express #available(somePlatform version) checks");
266+
versionCheck.emplace(attr->Platform, *attr->Introduced);
267+
268+
return true;
269+
}
270+
191271
static void
192272
deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
193273
// enum SomeEnum : SomeType {
194274
// case A = 111, B = 222
275+
// @available(iOS 10, *) case C = 333
195276
// @derived
196277
// init?(rawValue: SomeType) {
197278
// switch rawValue {
198279
// case 111:
199280
// self = .A
200281
// case 222:
201282
// self = .B
283+
// case 333:
284+
// guard #available(iOS 10, *) else { return nil }
285+
// self = .C
202286
// default:
203287
// return nil
204288
// }
@@ -234,6 +318,15 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
234318
SmallVector<ASTNode, 4> cases;
235319
unsigned Idx = 0;
236320
for (auto elt : enumDecl->getAllElements()) {
321+
// First, check case availability. If the case will definitely be
322+
// unavailable, skip it. If it might be unavailable at runtime, save
323+
// information about that check in versionCheck and keep processing this
324+
// element.
325+
Optional<RuntimeVersionCheck> versionCheck(None);
326+
if (!checkAvailability(elt, C, versionCheck))
327+
continue;
328+
329+
// litPat = elt.rawValueExpr as a pattern
237330
LiteralExpr *litExpr = cloneRawLiteralExpr(C, elt->getRawValueExpr());
238331
if (isStringEnum) {
239332
// In case of a string enum we are calling the _findStringSwitchCase
@@ -245,23 +338,36 @@ deriveBodyRawRepresentable_init(AbstractFunctionDecl *initDecl, void *) {
245338
nullptr, nullptr);
246339
litPat->setImplicit();
247340

248-
auto labelItem = CaseLabelItem(litPat);
341+
/// Statements in the body of this case.
342+
SmallVector<ASTNode, 2> stmts;
249343

344+
// If checkAvailability() discovered we need a runtime version check,
345+
// add it now.
346+
if (versionCheck.hasValue())
347+
stmts.push_back(ASTNode(versionCheck->createEarlyReturnStmt(C)));
348+
349+
// Create a statement which assigns the case to self.
350+
351+
// valueExpr = "\(enumType).\(elt)"
250352
auto eltRef = new (C) DeclRefExpr(elt, DeclNameLoc(), /*implicit*/true);
251353
auto metaTyRef = TypeExpr::createImplicit(enumType, C);
252354
auto valueExpr = new (C) DotSyntaxCallExpr(eltRef, SourceLoc(), metaTyRef);
253355

356+
// assignment = "self = \(valueExpr)"
254357
auto selfRef = new (C) DeclRefExpr(selfDecl, DeclNameLoc(),
255358
/*implicit*/true,
256359
AccessSemantics::DirectToStorage);
257-
258360
auto assignment = new (C) AssignExpr(selfRef, SourceLoc(), valueExpr,
259361
/*implicit*/ true);
362+
363+
stmts.push_back(ASTNode(assignment));
260364

365+
// body = "{ \(stmts) }" (the braces are silent)
261366
auto body = BraceStmt::create(C, SourceLoc(),
262-
ASTNode(assignment), SourceLoc());
367+
stmts, SourceLoc());
263368

264-
cases.push_back(CaseStmt::create(C, SourceLoc(), labelItem,
369+
// cases.append("case \(litPat): \(body)")
370+
cases.push_back(CaseStmt::create(C, SourceLoc(), CaseLabelItem(litPat),
265371
/*HasBoundDecls=*/false, SourceLoc(),
266372
SourceLoc(), body));
267373
Idx++;

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,6 +2280,19 @@ class AvailabilityWalker : public ASTWalker {
22802280
Optional<TypeChecker::FragileFunctionKind> FragileKind;
22812281
bool TreatUsableFromInlineAsPublic = false;
22822282

2283+
/// Returns true if DC is an \c init(rawValue:) declaration and it is marked
2284+
/// implicit.
2285+
bool inSynthesizedInitRawValue() {
2286+
auto init = dyn_cast_or_null<ConstructorDecl>(
2287+
DC->getInnermostDeclarationDeclContext());
2288+
2289+
return init &&
2290+
init->isImplicit() &&
2291+
init->getParameters()->size() == 1 &&
2292+
init->getParameters()->get(0)->getArgumentName() ==
2293+
TC.Context.Id_rawValue;
2294+
}
2295+
22832296
public:
22842297
AvailabilityWalker(
22852298
TypeChecker &TC, DeclContext *DC) : TC(TC), DC(DC) {
@@ -2299,8 +2312,20 @@ class AvailabilityWalker : public ASTWalker {
22992312
};
23002313

23012314
if (auto DR = dyn_cast<DeclRefExpr>(E)) {
2315+
DeclAvailabilityFlags flags = None;
2316+
if (inSynthesizedInitRawValue())
2317+
// HACK: If a raw-value enum has cases with `@available(introduced:)`
2318+
// attributes, the auto-derived `init(rawValue:)` will contain
2319+
// DeclRefExprs which reference those cases. It will also contain
2320+
// appropriate `guard #available` statements to keep them from running
2321+
// on older versions, but the availability checker can't verify that
2322+
// because the synthesized code uses invalid SourceLocs. Don't diagnose
2323+
// these errors; instead, take it on faith that
2324+
// DerivedConformanceRawRepresentable will do the right thing.
2325+
flags |= DeclAvailabilityFlag::AllowPotentiallyUnavailable;
2326+
23022327
diagAvailability(DR->getDecl(), DR->getSourceRange(),
2303-
getEnclosingApplyExpr());
2328+
getEnclosingApplyExpr(), flags);
23042329
maybeDiagStorageAccess(DR->getDecl(), DR->getSourceRange(), DC);
23052330
}
23062331
if (auto MR = dyn_cast<MemberRefExpr>(E)) {
@@ -2347,9 +2372,7 @@ class AvailabilityWalker : public ASTWalker {
23472372

23482373
bool diagAvailability(const ValueDecl *D, SourceRange R,
23492374
const ApplyExpr *call = nullptr,
2350-
bool AllowPotentiallyUnavailableProtocol = false,
2351-
bool SignalOnPotentialUnavailability = true,
2352-
bool ForInout = false);
2375+
DeclAvailabilityFlags flags = None);
23532376

23542377
private:
23552378
bool diagnoseIncDecRemoval(const ValueDecl *D, SourceRange R,
@@ -2483,46 +2506,45 @@ class AvailabilityWalker : public ASTWalker {
24832506
switch (AccessContext) {
24842507
case MemberAccessContext::Getter:
24852508
diagAccessorAvailability(D->getGetter(), ReferenceRange, ReferenceDC,
2486-
/*ForInout=*/false);
2509+
None);
24872510
break;
24882511

24892512
case MemberAccessContext::Setter:
24902513
diagAccessorAvailability(D->getSetter(), ReferenceRange, ReferenceDC,
2491-
/*ForInout=*/false);
2514+
None);
24922515
break;
24932516

24942517
case MemberAccessContext::InOut:
24952518
diagAccessorAvailability(D->getGetter(), ReferenceRange, ReferenceDC,
2496-
/*ForInout=*/true);
2519+
DeclAvailabilityFlag::ForInout);
24972520

24982521
diagAccessorAvailability(D->getSetter(), ReferenceRange, ReferenceDC,
2499-
/*ForInout=*/true);
2522+
DeclAvailabilityFlag::ForInout);
25002523
break;
25012524
}
25022525
}
25032526

25042527
/// Emit a diagnostic, if necessary for a potentially unavailable accessor.
25052528
void diagAccessorAvailability(AccessorDecl *D, SourceRange ReferenceRange,
25062529
const DeclContext *ReferenceDC,
2507-
bool ForInout) const {
2530+
DeclAvailabilityFlags Flags) const {
2531+
Flags &= DeclAvailabilityFlag::ForInout;
2532+
Flags |= DeclAvailabilityFlag::ContinueOnPotentialUnavailability;
25082533
if (diagnoseDeclAvailability(D, TC,
25092534
const_cast<DeclContext*>(ReferenceDC),
25102535
ReferenceRange,
2511-
/*AllowPotentiallyUnavailableProtocol*/false,
2512-
/*SignalOnPotentialUnavailability*/false,
2513-
ForInout))
2536+
Flags))
25142537
return;
25152538
}
25162539
};
25172540
} // end anonymous namespace
25182541

25192542
/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
25202543
/// was emitted.
2521-
bool AvailabilityWalker::diagAvailability(const ValueDecl *D, SourceRange R,
2522-
const ApplyExpr *call,
2523-
bool AllowPotentiallyUnavailableProtocol,
2524-
bool SignalOnPotentialUnavailability,
2525-
bool ForInout) {
2544+
bool
2545+
AvailabilityWalker::diagAvailability(const ValueDecl *D, SourceRange R,
2546+
const ApplyExpr *call,
2547+
DeclAvailabilityFlags Flags) {
25262548
if (!D)
25272549
return false;
25282550

@@ -2561,20 +2583,25 @@ bool AvailabilityWalker::diagAvailability(const ValueDecl *D, SourceRange R,
25612583
if (!isAccessorWithDeprecatedStorage)
25622584
TC.diagnoseIfDeprecated(R, DC, D, call);
25632585

2564-
if (AllowPotentiallyUnavailableProtocol && isa<ProtocolDecl>(D))
2586+
if (Flags.contains(DeclAvailabilityFlag::AllowPotentiallyUnavailable))
2587+
return false;
2588+
2589+
if (Flags.contains(DeclAvailabilityFlag::AllowPotentiallyUnavailableProtocol)
2590+
&& isa<ProtocolDecl>(D))
25652591
return false;
25662592

25672593
// Diagnose (and possibly signal) for potential unavailability
25682594
auto maybeUnavail = TC.checkDeclarationAvailability(D, R.Start, DC);
25692595
if (maybeUnavail.hasValue()) {
25702596
if (accessor) {
2597+
bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout);
25712598
TC.diagnosePotentialAccessorUnavailability(accessor, R, DC,
25722599
maybeUnavail.getValue(),
2573-
ForInout);
2600+
forInout);
25742601
} else {
25752602
TC.diagnosePotentialUnavailability(D, R, DC, maybeUnavail.getValue());
25762603
}
2577-
if (SignalOnPotentialUnavailability)
2604+
if (!Flags.contains(DeclAvailabilityFlag::ContinueOnPotentialUnavailability))
25782605
return true;
25792606
}
25802607
return false;
@@ -2748,13 +2775,8 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *Decl,
27482775
TypeChecker &TC,
27492776
DeclContext *DC,
27502777
SourceRange R,
2751-
bool AllowPotentiallyUnavailableProtocol,
2752-
bool SignalOnPotentialUnavailability,
2753-
bool ForInout)
2778+
DeclAvailabilityFlags Flags)
27542779
{
27552780
AvailabilityWalker AW(TC, DC);
2756-
return AW.diagAvailability(Decl, R, nullptr,
2757-
AllowPotentiallyUnavailableProtocol,
2758-
SignalOnPotentialUnavailability,
2759-
ForInout);
2781+
return AW.diagAvailability(Decl, R, nullptr, Flags);
27602782
}

0 commit comments

Comments
 (0)