Skip to content

Commit 2dcac4b

Browse files
authored
Merge pull request #18408 from jrose-apple/default-on-your-futures
[SIL] Don't drop a default when switching on a non-exhaustive enum rdar://problem/42775178
2 parents c8530f4 + e96886a commit 2dcac4b

File tree

13 files changed

+854
-62
lines changed

13 files changed

+854
-62
lines changed

include/swift/AST/Decl.h

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3266,45 +3266,6 @@ class EnumDecl final : public NominalTypeDecl {
32663266
return std::distance(eltRange.begin(), eltRange.end());
32673267
}
32683268

3269-
/// If this is an enum with two cases, return the other case. Otherwise,
3270-
/// return nullptr.
3271-
EnumElementDecl *getOppositeBinaryDecl(EnumElementDecl *decl) const {
3272-
ElementRange range = getAllElements();
3273-
auto iter = range.begin();
3274-
if (iter == range.end())
3275-
return nullptr;
3276-
bool seenDecl = false;
3277-
EnumElementDecl *result = nullptr;
3278-
if (*iter == decl) {
3279-
seenDecl = true;
3280-
} else {
3281-
result = *iter;
3282-
}
3283-
3284-
++iter;
3285-
if (iter == range.end())
3286-
return nullptr;
3287-
if (seenDecl) {
3288-
assert(!result);
3289-
result = *iter;
3290-
} else {
3291-
if (decl != *iter)
3292-
return nullptr;
3293-
seenDecl = true;
3294-
}
3295-
++iter;
3296-
3297-
// If we reach this point, we saw the decl we were looking for and one other
3298-
// case. If we have any additional cases, then we do not have a binary enum.
3299-
if (iter != range.end())
3300-
return nullptr;
3301-
3302-
// This is always true since we have already returned earlier nullptr if we
3303-
// did not see the decl at all.
3304-
assert(seenDecl);
3305-
return result;
3306-
}
3307-
33083269
/// If this enum has a unique element, return it. A unique element can
33093270
/// either hold a value or not, and the existence of one unique element does
33103271
/// not imply the existence or non-existence of the opposite unique element.
@@ -3388,7 +3349,23 @@ class EnumDecl final : public NominalTypeDecl {
33883349
/// Note that this property is \e not necessarily true for all children of
33893350
/// \p useDC. In particular, an inlinable function does not get to switch
33903351
/// exhaustively over a non-exhaustive enum declared in the same module.
3391-
bool isExhaustive(const DeclContext *useDC) const;
3352+
///
3353+
/// This is the predicate used when deciding if a switch statement needs a
3354+
/// default case. It should not be used for optimization or code generation.
3355+
///
3356+
/// \sa isEffectivelyExhaustive
3357+
bool isFormallyExhaustive(const DeclContext *useDC) const;
3358+
3359+
/// True if the enum can be exhaustively switched within a function defined
3360+
/// within \p M, with \p expansion specifying whether the function is
3361+
/// inlinable.
3362+
///
3363+
/// This is the predicate used when making optimization and code generation
3364+
/// decisions. It should not be used at the AST or semantic level.
3365+
///
3366+
/// \sa isFormallyExhaustive
3367+
bool isEffectivelyExhaustive(ModuleDecl *M,
3368+
ResilienceExpansion expansion) const;
33923369
};
33933370

33943371
/// StructDecl - This is the declaration of a struct, for example:

lib/AST/Decl.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3439,7 +3439,7 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
34393439
return true;
34403440
}
34413441

3442-
bool EnumDecl::isExhaustive(const DeclContext *useDC) const {
3442+
bool EnumDecl::isFormallyExhaustive(const DeclContext *useDC) const {
34433443
// Enums explicitly marked frozen are exhaustive.
34443444
if (getAttrs().hasAttribute<FrozenAttr>())
34453445
return true;
@@ -3486,6 +3486,22 @@ bool EnumDecl::isExhaustive(const DeclContext *useDC) const {
34863486
return false;
34873487
}
34883488

3489+
bool EnumDecl::isEffectivelyExhaustive(ModuleDecl *M,
3490+
ResilienceExpansion expansion) const {
3491+
// Generated Swift code commits to handling garbage values of @objc enums,
3492+
// whether imported or not, to deal with C's loose rules around enums.
3493+
// This covers both frozen and non-frozen @objc enums.
3494+
if (isObjC())
3495+
return false;
3496+
3497+
// Otherwise, the only non-exhaustive cases are those that don't have a fixed
3498+
// layout.
3499+
assert(isFormallyExhaustive(M) == !isResilient(M,ResilienceExpansion::Maximal)
3500+
&& "ignoring the effects of @inlinable, @testable, and @objc, "
3501+
"these should match up");
3502+
return !isResilient(M, expansion);
3503+
}
3504+
34893505
ProtocolDecl::ProtocolDecl(DeclContext *DC, SourceLoc ProtocolLoc,
34903506
SourceLoc NameLoc, Identifier Name,
34913507
MutableArrayRef<TypeLoc> Inherited,

lib/PrintAsObjC/PrintAsObjC.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ class ObjCPrinter : private DeclVisitor<ObjCPrinter>,
421421
os << ", " << customName
422422
<< ", \"" << ED->getName() << "\"";
423423
}
424-
os << ", " << (ED->isExhaustive(/*useDC*/nullptr) ? "closed" : "open")
424+
os << ", "
425+
<< (ED->isFormallyExhaustive(/*useDC*/nullptr) ? "closed" : "open")
425426
<< ") {\n";
426427

427428
for (auto Elt : ED->getAllElements()) {

lib/SIL/SILInstructions.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,10 +1424,11 @@ namespace {
14241424
EnumDecl *decl = enumType.getEnumOrBoundGenericEnum();
14251425
assert(decl && "switch_enum operand is not an enum");
14261426

1427-
// FIXME: Get expansion from SILFunction
1428-
if (decl->isResilient(inst->getModule().getSwiftModule(),
1429-
ResilienceExpansion::Maximal))
1427+
const SILFunction *F = inst->getFunction();
1428+
if (!decl->isEffectivelyExhaustive(F->getModule().getSwiftModule(),
1429+
F->getResilienceExpansion())) {
14301430
return nullptr;
1431+
}
14311432

14321433
llvm::SmallPtrSet<EnumElementDecl *, 4> unswitchedElts;
14331434
for (auto elt : decl->getAllElements())

lib/SIL/SILVerifier.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3683,8 +3683,6 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
36833683

36843684
// Find the set of enum elements for the type so we can verify
36853685
// exhaustiveness.
3686-
// FIXME: We also need to consider if the enum is resilient, in which case
3687-
// we're never guaranteed to be exhaustive.
36883686
llvm::DenseSet<EnumElementDecl*> unswitchedElts;
36893687
eDecl->getAllElements(unswitchedElts);
36903688

@@ -3707,7 +3705,10 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
37073705
}
37083706

37093707
// If the select is non-exhaustive, we require a default.
3710-
require(unswitchedElts.empty() || I->hasDefault(),
3708+
bool isExhaustive =
3709+
eDecl->isEffectivelyExhaustive(F.getModule().getSwiftModule(),
3710+
F.getResilienceExpansion());
3711+
require((isExhaustive && unswitchedElts.empty()) || I->hasDefault(),
37113712
"nonexhaustive select_enum must have a default destination");
37123713
if (I->hasDefault()) {
37133714
requireSameType(I->getDefaultResult()->getType(),
@@ -3875,7 +3876,10 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
38753876
}
38763877

38773878
// If the switch is non-exhaustive, we require a default.
3878-
require(unswitchedElts.empty() || SOI->hasDefault(),
3879+
bool isExhaustive =
3880+
uDecl->isEffectivelyExhaustive(F.getModule().getSwiftModule(),
3881+
F.getResilienceExpansion());
3882+
require((isExhaustive && unswitchedElts.empty()) || SOI->hasDefault(),
38793883
"nonexhaustive switch_enum must have a default destination");
38803884
if (SOI->hasDefault()) {
38813885
// When SIL ownership is enabled, we require all default branches to take

lib/SILGen/SILGenDecl.cpp

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,52 @@ class EnumElementPatternInitialization : public RefutablePatternInitialization {
758758
};
759759
} // end anonymous namespace
760760

761+
/// If \p elt belongs to an enum that has exactly two cases and that can be
762+
/// exhaustively switched, return the other case. Otherwise, return nullptr.
763+
static EnumElementDecl *getOppositeBinaryDecl(const SILGenFunction &SGF,
764+
const EnumElementDecl *elt) {
765+
const EnumDecl *enumDecl = elt->getParentEnum();
766+
if (!enumDecl->isEffectivelyExhaustive(SGF.SGM.SwiftModule,
767+
SGF.F.getResilienceExpansion())) {
768+
return nullptr;
769+
}
770+
771+
EnumDecl::ElementRange range = enumDecl->getAllElements();
772+
auto iter = range.begin();
773+
if (iter == range.end())
774+
return nullptr;
775+
bool seenDecl = false;
776+
EnumElementDecl *result = nullptr;
777+
if (*iter == elt) {
778+
seenDecl = true;
779+
} else {
780+
result = *iter;
781+
}
782+
783+
++iter;
784+
if (iter == range.end())
785+
return nullptr;
786+
if (seenDecl) {
787+
assert(!result);
788+
result = *iter;
789+
} else {
790+
if (elt != *iter)
791+
return nullptr;
792+
seenDecl = true;
793+
}
794+
++iter;
795+
796+
// If we reach this point, we saw the decl we were looking for and one other
797+
// case. If we have any additional cases, then we do not have a binary enum.
798+
if (iter != range.end())
799+
return nullptr;
800+
801+
// This is always true since we have already returned earlier nullptr if we
802+
// did not see the decl at all.
803+
assert(seenDecl);
804+
return result;
805+
}
806+
761807
void EnumElementPatternInitialization::emitEnumMatch(
762808
ManagedValue value, EnumElementDecl *eltDecl, Initialization *subInit,
763809
JumpDest failureDest, SILLocation loc, SILGenFunction &SGF) {
@@ -793,8 +839,7 @@ void EnumElementPatternInitialization::emitEnumMatch(
793839
// If we have a binary enum, do not emit a true default case. This ensures
794840
// that we do not emit a destroy_value on a .None.
795841
bool inferredBinaryEnum = false;
796-
auto *enumDecl = value.getType().getEnumOrBoundGenericEnum();
797-
if (auto *otherDecl = enumDecl->getOppositeBinaryDecl(eltDecl)) {
842+
if (auto *otherDecl = getOppositeBinaryDecl(SGF, eltDecl)) {
798843
inferredBinaryEnum = true;
799844
switchBuilder.addCase(otherDecl, defaultBlock, nullptr, handler);
800845
} else {

lib/SILGen/SILGenPattern.cpp

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,15 +1802,9 @@ CaseBlocks::CaseBlocks(
18021802
// Check to see if the enum may have values beyond the cases we can see
18031803
// at compile-time. This includes future cases (for resilient enums) and
18041804
// random values crammed into C enums.
1805-
//
1806-
// Note: This relies on the fact that there are no "non-resilient" enums that
1807-
// are still non-exhaustive, except for @objc enums.
1808-
bool canAssumeExhaustive = !enumDecl->isObjC();
1809-
if (canAssumeExhaustive) {
1810-
canAssumeExhaustive =
1811-
!enumDecl->isResilient(SGF.SGM.SwiftModule,
1812-
SGF.F.getResilienceExpansion());
1813-
}
1805+
bool canAssumeExhaustive =
1806+
enumDecl->isEffectivelyExhaustive(SGF.getModule().getSwiftModule(),
1807+
SGF.F.getResilienceExpansion());
18141808
if (canAssumeExhaustive) {
18151809
// Check that Sema didn't let any cases slip through. (This can happen
18161810
// with @_downgrade_exhaustivity_check.)

lib/SILOptimizer/Transforms/SILCodeMotion.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#define DEBUG_TYPE "sil-codemotion"
1414
#include "swift/SILOptimizer/PassManager/Passes.h"
15+
#include "swift/AST/Module.h"
1516
#include "swift/Basic/BlotMapVector.h"
1617
#include "swift/SIL/SILBuilder.h"
1718
#include "swift/SIL/SILModule.h"
@@ -315,6 +316,13 @@ void BBEnumTagDataflowState::handlePredCondSelectEnum(CondBranchInst *CondBr) {
315316
// If the enum only has 2 values and its tag isn't the true branch, then we
316317
// know the true branch must be the other tag.
317318
if (EnumDecl *E = Operand->getType().getEnumOrBoundGenericEnum()) {
319+
// We can't do this optimization on non-exhaustive enums.
320+
const SILFunction *Fn = CondBr->getFunction();
321+
bool IsExhaustive =
322+
E->isEffectivelyExhaustive(Fn->getModule().getSwiftModule(),
323+
Fn->getResilienceExpansion());
324+
if (!IsExhaustive)
325+
return;
318326
// Look for a single other element on this enum.
319327
EnumElementDecl *OtherElt = nullptr;
320328
for (EnumElementDecl *Elt : E->getAllElements()) {

lib/SILOptimizer/Transforms/SimplifyCFG.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#define DEBUG_TYPE "sil-simplify-cfg"
14+
#include "swift/AST/Module.h"
1415
#include "swift/SIL/DebugUtils.h"
1516
#include "swift/SIL/Dominance.h"
1617
#include "swift/SIL/InstructionUtils.h"
@@ -1581,7 +1582,13 @@ bool SimplifyCFG::simplifyCondBrBlock(CondBranchInst *BI) {
15811582
auto Iter = AllElts.begin();
15821583
EnumElementDecl *FirstElt = *Iter;
15831584

1584-
if (SEI->getNumCases() >= 1
1585+
// We can't do this optimization on non-exhaustive enums.
1586+
bool IsExhaustive =
1587+
E->isEffectivelyExhaustive(Fn.getModule().getSwiftModule(),
1588+
Fn.getResilienceExpansion());
1589+
1590+
if (IsExhaustive
1591+
&& SEI->getNumCases() >= 1
15851592
&& SEI->getCase(0).first != FirstElt) {
15861593
++Iter;
15871594

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ namespace {
818818
constElemSpaces);
819819
});
820820

821-
if (!E->isExhaustive(DC)) {
821+
if (!E->isFormallyExhaustive(DC)) {
822822
arr.push_back(Space::forUnknown(/*allowedButNotRequired*/false));
823823
} else if (!E->getAttrs().hasAttribute<FrozenAttr>()) {
824824
arr.push_back(Space::forUnknown(/*allowedButNotRequired*/true));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Even though these are marked "closed", Swift shouldn't trust it.
2+
3+
enum Alpha {
4+
AlphaA __attribute__((swift_name("a"))),
5+
AlphaB __attribute__((swift_name("b"))),
6+
AlphaC __attribute__((swift_name("c"))),
7+
AlphaD __attribute__((swift_name("d"))),
8+
AlphaE __attribute__((swift_name("e")))
9+
} __attribute__((enum_extensibility(closed)));
10+
11+
enum Coin {
12+
CoinHeads,
13+
CoinTails
14+
} __attribute__((enum_extensibility(closed)));

0 commit comments

Comments
 (0)