Skip to content

Commit 44a88d4

Browse files
author
Jacob Mizraji
authored
Merge pull request #18449 from jrose-apple/4.2-default-on-your-futures
[4.2] [SIL] Don't drop a default when switching on a non-exhaustive enum
2 parents ea2ee4c + b99669b commit 44a88d4

File tree

14 files changed

+854
-64
lines changed

14 files changed

+854
-64
lines changed

include/swift/AST/Decl.h

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

3205-
/// If this is an enum with two cases, return the other case. Otherwise,
3206-
/// return nullptr.
3207-
EnumElementDecl *getOppositeBinaryDecl(EnumElementDecl *decl) const {
3208-
ElementRange range = getAllElements();
3209-
auto iter = range.begin();
3210-
if (iter == range.end())
3211-
return nullptr;
3212-
bool seenDecl = false;
3213-
EnumElementDecl *result = nullptr;
3214-
if (*iter == decl) {
3215-
seenDecl = true;
3216-
} else {
3217-
result = *iter;
3218-
}
3219-
3220-
++iter;
3221-
if (iter == range.end())
3222-
return nullptr;
3223-
if (seenDecl) {
3224-
assert(!result);
3225-
result = *iter;
3226-
} else {
3227-
if (decl != *iter)
3228-
return nullptr;
3229-
seenDecl = true;
3230-
}
3231-
++iter;
3232-
3233-
// If we reach this point, we saw the decl we were looking for and one other
3234-
// case. If we have any additional cases, then we do not have a binary enum.
3235-
if (iter != range.end())
3236-
return nullptr;
3237-
3238-
// This is always true since we have already returned earlier nullptr if we
3239-
// did not see the decl at all.
3240-
assert(seenDecl);
3241-
return result;
3242-
}
3243-
32443205
/// If this enum has a unique element, return it. A unique element can
32453206
/// either hold a value or not, and the existence of one unique element does
32463207
/// not imply the existence or non-existence of the opposite unique element.
@@ -3327,7 +3288,23 @@ class EnumDecl final : public NominalTypeDecl {
33273288
/// Note that this property is \e not necessarily true for all children of
33283289
/// \p useDC. In particular, an inlinable function does not get to switch
33293290
/// exhaustively over a non-exhaustive enum declared in the same module.
3330-
bool isExhaustive(const DeclContext *useDC) const;
3291+
///
3292+
/// This is the predicate used when deciding if a switch statement needs a
3293+
/// default case. It should not be used for optimization or code generation.
3294+
///
3295+
/// \sa isEffectivelyExhaustive
3296+
bool isFormallyExhaustive(const DeclContext *useDC) const;
3297+
3298+
/// True if the enum can be exhaustively switched within a function defined
3299+
/// within \p M, with \p expansion specifying whether the function is
3300+
/// inlinable.
3301+
///
3302+
/// This is the predicate used when making optimization and code generation
3303+
/// decisions. It should not be used at the AST or semantic level.
3304+
///
3305+
/// \sa isFormallyExhaustive
3306+
bool isEffectivelyExhaustive(ModuleDecl *M,
3307+
ResilienceExpansion expansion) const;
33313308
};
33323309

33333310
/// 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
@@ -3189,7 +3189,7 @@ bool EnumDecl::hasOnlyCasesWithoutAssociatedValues() const {
31893189
return true;
31903190
}
31913191

3192-
bool EnumDecl::isExhaustive(const DeclContext *useDC) const {
3192+
bool EnumDecl::isFormallyExhaustive(const DeclContext *useDC) const {
31933193
// Enums explicitly marked frozen are exhaustive.
31943194
if (getAttrs().hasAttribute<FrozenAttr>())
31953195
return true;
@@ -3236,6 +3236,22 @@ bool EnumDecl::isExhaustive(const DeclContext *useDC) const {
32363236
return false;
32373237
}
32383238

3239+
bool EnumDecl::isEffectivelyExhaustive(ModuleDecl *M,
3240+
ResilienceExpansion expansion) const {
3241+
// Generated Swift code commits to handling garbage values of @objc enums,
3242+
// whether imported or not, to deal with C's loose rules around enums.
3243+
// This covers both frozen and non-frozen @objc enums.
3244+
if (isObjC())
3245+
return false;
3246+
3247+
// Otherwise, the only non-exhaustive cases are those that don't have a fixed
3248+
// layout.
3249+
assert(isFormallyExhaustive(M) == !isResilient(M,ResilienceExpansion::Maximal)
3250+
&& "ignoring the effects of @inlinable, @testable, and @objc, "
3251+
"these should match up");
3252+
return !isResilient(M, expansion);
3253+
}
3254+
32393255
ProtocolDecl::ProtocolDecl(DeclContext *DC, SourceLoc ProtocolLoc,
32403256
SourceLoc NameLoc, Identifier Name,
32413257
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
@@ -1425,10 +1425,11 @@ namespace {
14251425
EnumDecl *decl = enumType.getEnumOrBoundGenericEnum();
14261426
assert(decl && "switch_enum operand is not an enum");
14271427

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

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

lib/SIL/SILVerifier.cpp

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

37003700
// Find the set of enum elements for the type so we can verify
37013701
// exhaustiveness.
3702-
// FIXME: We also need to consider if the enum is resilient, in which case
3703-
// we're never guaranteed to be exhaustive.
37043702
llvm::DenseSet<EnumElementDecl*> unswitchedElts;
37053703
eDecl->getAllElements(unswitchedElts);
37063704

@@ -3723,7 +3721,10 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
37233721
}
37243722

37253723
// If the select is non-exhaustive, we require a default.
3726-
require(unswitchedElts.empty() || I->hasDefault(),
3724+
bool isExhaustive =
3725+
eDecl->isEffectivelyExhaustive(F.getModule().getSwiftModule(),
3726+
F.getResilienceExpansion());
3727+
require((isExhaustive && unswitchedElts.empty()) || I->hasDefault(),
37273728
"nonexhaustive select_enum must have a default destination");
37283729
if (I->hasDefault()) {
37293730
requireSameType(I->getDefaultResult()->getType(),
@@ -3891,7 +3892,10 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
38913892
}
38923893

38933894
// If the switch is non-exhaustive, we require a default.
3894-
require(unswitchedElts.empty() || SOI->hasDefault(),
3895+
bool isExhaustive =
3896+
uDecl->isEffectivelyExhaustive(F.getModule().getSwiftModule(),
3897+
F.getResilienceExpansion());
3898+
require((isExhaustive && unswitchedElts.empty()) || SOI->hasDefault(),
38953899
"nonexhaustive switch_enum must have a default destination");
38963900
if (SOI->hasDefault()) {
38973901
// 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
@@ -752,6 +752,52 @@ class EnumElementPatternInitialization : public RefutablePatternInitialization {
752752
};
753753
} // end anonymous namespace
754754

755+
/// If \p elt belongs to an enum that has exactly two cases and that can be
756+
/// exhaustively switched, return the other case. Otherwise, return nullptr.
757+
static EnumElementDecl *getOppositeBinaryDecl(const SILGenFunction &SGF,
758+
const EnumElementDecl *elt) {
759+
const EnumDecl *enumDecl = elt->getParentEnum();
760+
if (!enumDecl->isEffectivelyExhaustive(SGF.SGM.SwiftModule,
761+
SGF.F.getResilienceExpansion())) {
762+
return nullptr;
763+
}
764+
765+
EnumDecl::ElementRange range = enumDecl->getAllElements();
766+
auto iter = range.begin();
767+
if (iter == range.end())
768+
return nullptr;
769+
bool seenDecl = false;
770+
EnumElementDecl *result = nullptr;
771+
if (*iter == elt) {
772+
seenDecl = true;
773+
} else {
774+
result = *iter;
775+
}
776+
777+
++iter;
778+
if (iter == range.end())
779+
return nullptr;
780+
if (seenDecl) {
781+
assert(!result);
782+
result = *iter;
783+
} else {
784+
if (elt != *iter)
785+
return nullptr;
786+
seenDecl = true;
787+
}
788+
++iter;
789+
790+
// If we reach this point, we saw the decl we were looking for and one other
791+
// case. If we have any additional cases, then we do not have a binary enum.
792+
if (iter != range.end())
793+
return nullptr;
794+
795+
// This is always true since we have already returned earlier nullptr if we
796+
// did not see the decl at all.
797+
assert(seenDecl);
798+
return result;
799+
}
800+
755801
void EnumElementPatternInitialization::emitEnumMatch(
756802
ManagedValue value, EnumElementDecl *eltDecl, Initialization *subInit,
757803
JumpDest failureDest, SILLocation loc, SILGenFunction &SGF) {
@@ -787,8 +833,7 @@ void EnumElementPatternInitialization::emitEnumMatch(
787833
// If we have a binary enum, do not emit a true default case. This ensures
788834
// that we do not emit a destroy_value on a .None.
789835
bool inferredBinaryEnum = false;
790-
auto *enumDecl = value.getType().getEnumOrBoundGenericEnum();
791-
if (auto *otherDecl = enumDecl->getOppositeBinaryDecl(eltDecl)) {
836+
if (auto *otherDecl = getOppositeBinaryDecl(SGF, eltDecl)) {
792837
inferredBinaryEnum = true;
793838
switchBuilder.addCase(otherDecl, defaultBlock, nullptr, handler);
794839
} 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"
@@ -1574,7 +1575,13 @@ bool SimplifyCFG::simplifyCondBrBlock(CondBranchInst *BI) {
15741575
auto Iter = AllElts.begin();
15751576
EnumElementDecl *FirstElt = *Iter;
15761577

1577-
if (SEI->getNumCases() >= 1
1578+
// We can't do this optimization on non-exhaustive enums.
1579+
bool IsExhaustive =
1580+
E->isEffectivelyExhaustive(Fn.getModule().getSwiftModule(),
1581+
Fn.getResilienceExpansion());
1582+
1583+
if (IsExhaustive
1584+
&& SEI->getNumCases() >= 1
15781585
&& SEI->getCase(0).first != FirstElt) {
15791586
++Iter;
15801587

lib/Sema/TypeCheckSwitchStmt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,7 @@ namespace {
11221122
constElemSpaces);
11231123
});
11241124

1125-
if (!E->isExhaustive(DC)) {
1125+
if (!E->isFormallyExhaustive(DC)) {
11261126
arr.push_back(Space::forUnknown(/*allowedButNotRequired*/false));
11271127
} else if (!E->getAttrs().hasAttribute<FrozenAttr>()) {
11281128
arr.push_back(Space::forUnknown(/*allowedButNotRequired*/true));

0 commit comments

Comments
 (0)