blob: c182ecc487f4122cdbfa3e39c9111da9af271d80 [file] [log] [blame]
//===--- TypeCheckSwitchStmt.cpp - Switch Exhaustiveness and Type Checks --===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file contains an algorithm for checking the exhaustiveness of switches.
//
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
#include "swift/AST/ASTPrinter.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/Pattern.h"
#include <numeric>
#include <forward_list>
using namespace swift;
#define DEBUG_TYPE "TypeCheckSwitchStmt"
namespace {
/// The SpaceEngine encapsulates an algorithm for computing the exhaustiveness
/// of a switch statement using an algebra of spaces described by Fengyun Liu
/// and an algorithm for computing warnings for pattern matching by
// Luc Maranget.
///
/// The main algorithm centers around the computation of the difference and
/// the intersection of the "Spaces" given in each case, which reduces the
/// definition of exhaustiveness to checking if the difference of the space
/// 'S' of the user's written patterns and the space 'T' of the pattern
/// condition is empty.
struct SpaceEngine {
enum class SpaceKind : uint8_t {
Empty = 1 << 0,
Type = 1 << 1,
Constructor = 1 << 2,
Disjunct = 1 << 3,
BooleanConstant = 1 << 4,
};
/// A data structure for conveniently pattern-matching on the kinds of
/// two spaces.
struct PairSwitch {
public:
constexpr PairSwitch(SpaceKind pair1, SpaceKind pair2)
: Data(static_cast<uint8_t>(pair1) | (static_cast<uint8_t>(pair2) << 8))
{}
constexpr bool operator==(const PairSwitch other) const {
return Data == other.Data;
}
constexpr operator int() const {
return Data;
}
private:
uint16_t Data;
PairSwitch (const PairSwitch&) = delete;
PairSwitch& operator= (const PairSwitch&) = delete;
};
#define PAIRCASE(XS, YS) case PairSwitch(XS, YS)
class Space final {
private:
SpaceKind Kind;
llvm::PointerIntPair<Type, 1, bool> TypeAndVal;
Identifier Head;
std::forward_list<Space> Spaces;
public:
explicit Space(Type T)
: Kind(SpaceKind::Type), TypeAndVal(T, false), Head(Identifier()),
Spaces({}){}
explicit Space(Type T, Identifier H, SmallVectorImpl<Space> &SP)
: Kind(SpaceKind::Constructor), TypeAndVal(T, false), Head(H),
Spaces(SP.begin(), SP.end()) {}
explicit Space(SmallVectorImpl<Space> &SP)
: Kind(SpaceKind::Disjunct), TypeAndVal(Type(), false),
Head(Identifier()), Spaces(SP.begin(), SP.end()) {}
explicit Space()
: Kind(SpaceKind::Empty), TypeAndVal(Type(), false), Head(Identifier()),
Spaces({}) {}
explicit Space(bool C)
: Kind(SpaceKind::BooleanConstant), TypeAndVal(Type(), C),
Head(Identifier()), Spaces({}) {}
SpaceKind getKind() const { return Kind; }
void dump() const LLVM_ATTRIBUTE_USED;
bool isEmpty() const { return getKind() == SpaceKind::Empty; }
Type getType() const {
assert((getKind() == SpaceKind::Type
|| getKind() == SpaceKind::Constructor)
&& "Wrong kind of space tried to access space type");
return TypeAndVal.getPointer();
}
Identifier getHead() const {
assert(getKind() == SpaceKind::Constructor
&& "Wrong kind of space tried to access head");
return Head;
}
const std::forward_list<Space> &getSpaces() const {
assert((getKind() == SpaceKind::Constructor
|| getKind() == SpaceKind::Disjunct)
&& "Wrong kind of space tried to access subspace list");
return Spaces;
}
bool getBoolValue() const {
assert(getKind() == SpaceKind::BooleanConstant
&& "Wrong kind of space tried to access bool value");
return TypeAndVal.getInt();
}
// Defines a "usefulness" metric that returns whether the given space
// contributes meaningfully to the exhaustiveness of a pattern.
bool isUseful() const {
auto subspacesUseful = [](const Space &space) {
for (auto &subspace : space.getSpaces()) {
if (!subspace.isUseful()) {
return false;
}
}
return true;
};
switch (getKind()) {
case SpaceKind::Empty:
return false;
case SpaceKind::Type:
case SpaceKind::BooleanConstant:
return true;
case SpaceKind::Disjunct:
if (getSpaces().empty()) {
return false;
}
return subspacesUseful(*this);
case SpaceKind::Constructor:
if (getSpaces().empty()) {
return true;
}
return subspacesUseful(*this);
}
}
// An optimization that computes if the difference of this space and
// another space is empty.
bool isSubspace(const Space &other) const {
if (this->isEmpty()) {
return true;
}
if (other.isEmpty()) {
return false;
}
switch (PairSwitch(getKind(), other.getKind())) {
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Empty):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Type):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Constructor):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::BooleanConstant): {
// (S1 | ... | Sn) <= S iff (S1 <= S) && ... && (Sn <= S)
for (auto &space : this->getSpaces()) {
if (!space.isSubspace(other)) {
return false;
}
}
return true;
}
PAIRCASE (SpaceKind::Type, SpaceKind::Type): {
// Optimization: Are the types equal? If so, the space is covered.
if (this->getType()->isEqual(other.getType())) {
return true;
}
// (_ : Ty1) <= (_ : Ty2) iff D(Ty1) == D(Ty2)
if (canDecompose(this->getType())) {
SmallVector<Space, 4> disjuncts;
decompose(this->getType(), disjuncts);
Space or1Space(disjuncts);
if (or1Space.isSubspace(other)) {
return true;
}
}
if (canDecompose(other.getType())) {
SmallVector<Space, 4> disjuncts;
decompose(other.getType(), disjuncts);
Space or2Space(disjuncts);
return this->isSubspace(or2Space);
}
return true;
}
PAIRCASE (SpaceKind::Type, SpaceKind::Disjunct): {
// (_ : Ty1) <= (S1 | ... | Sn) iff (S1 <= S) || ... || (Sn <= S)
for (auto &dis : other.getSpaces()) {
if (this->isSubspace(dis)) {
return true;
}
}
// (_ : Ty1) <= (S1 | ... | Sn) iff D(Ty1) <= (S1 | ... | Sn)
if (!canDecompose(this->getType())) {
return false;
}
SmallVector<Space, 4> disjuncts;
decompose(this->getType(), disjuncts);
Space or1Space(disjuncts);
return or1Space.isSubspace(other);
}
PAIRCASE (SpaceKind::Type, SpaceKind::Constructor): {
// (_ : Ty1) <= H(p1 | ... | pn) iff D(Ty1) <= H(p1 | ... | pn)
if (canDecompose(this->getType())) {
SmallVector<Space, 4> disjuncts;
decompose(this->getType(), disjuncts);
Space or1Space(disjuncts);
return or1Space.isSubspace(other);
}
// An undecomposable type is always larger than its constructor space.
return false;
}
PAIRCASE (SpaceKind::Constructor, SpaceKind::Type):
// Typechecking guaranteed this constructor is a subspace of the type.
return true;
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Type):
return other.getType()->isBool();
PAIRCASE (SpaceKind::Constructor, SpaceKind::Constructor): {
// Optimization: If the constructor heads don't match, subspace is
// impossible.
if (this->Head.compare(other.Head) != 0) {
return false;
}
// Special Case: A constructor pattern may include the head but not
// the payload patterns. In that case the space is covered.
if (other.getSpaces().empty()) {
return true;
}
// H(a1, ..., an) <= H(b1, ..., bn) iff a1 <= b1 && ... && an <= bn
auto i = this->getSpaces().begin();
auto j = other.getSpaces().begin();
for (; i != this->getSpaces().end() && j != other.getSpaces().end();
++i, ++j) {
if (!(*i).isSubspace(*j)) {
return false;
}
}
return true;
}
PAIRCASE (SpaceKind::Constructor, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Disjunct): {
// S <= (S1 | ... | Sn) <= S iff (S <= S1) || ... || (S <= Sn)
for (auto &param : other.getSpaces()) {
if (this->isSubspace(param)) {
return true;
}
}
return false;
}
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::BooleanConstant):
return this->getBoolValue() == other.getBoolValue();
PAIRCASE (SpaceKind::Empty, SpaceKind::BooleanConstant):
PAIRCASE (SpaceKind::Constructor, SpaceKind::BooleanConstant):
PAIRCASE (SpaceKind::Type, SpaceKind::BooleanConstant):
return false;
default:
llvm_unreachable("Uncovered pair found while computing subspaces?");
}
}
// Returns the intersection of this space with another. The intersection
// is the largest shared subspace occupied by both arguments.
Space intersect(const Space &other) const {
// The intersection of an empty space is empty.
if (this->isEmpty() || other.isEmpty()) {
return Space();
}
llvm::function_ref<Space(SmallVectorImpl<Space> &)> examineDecomp
= [&](SmallVectorImpl<Space> &decomposition) -> Space {
if (decomposition.empty()) {
return Space();
} else if (decomposition.size() == 1) {
return decomposition.front();
}
Space ds(decomposition);
return ds;
};
switch (PairSwitch(getKind(), other.getKind())) {
PAIRCASE (SpaceKind::Empty, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Type, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Constructor, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Disjunct): {
// S & (S1 || ... || Sn) iff (S & S1) && ... && (S & Sn)
SmallVector<Space, 4> intersectedCases;
std::transform(other.getSpaces().begin(), other.getSpaces().end(),
std::back_inserter(intersectedCases),
[&](const Space &s) {
return this->intersect(s);
});
// Optimization: Remove all empty spaces.
SmallVector<Space, 4> filteredCases;
std::copy_if(intersectedCases.begin(), intersectedCases.end(),
std::back_inserter(filteredCases),
[&](const Space &s) {
return !s.isEmpty();
});
return examineDecomp(filteredCases);
}
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Empty):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Type):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Constructor):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::BooleanConstant): {
// (S1 || ... || Sn) & S iff (S & S1) && ... && (S & Sn)
SmallVector<Space, 4> intersectedCases;
std::transform(this->getSpaces().begin(), this->getSpaces().end(),
std::back_inserter(intersectedCases),
[&](const Space &s) {
return s.intersect(other);
});
// Optimization: Remove all empty spaces.
SmallVector<Space, 4> filteredCases;
std::copy_if(intersectedCases.begin(), intersectedCases.end(),
std::back_inserter(filteredCases),
[&](const Space &s) {
return !s.isEmpty();
});
return examineDecomp(filteredCases);
}
PAIRCASE (SpaceKind::Type, SpaceKind::Type): {
// Optimization: The intersection of equal types is that type.
if (this->getType()->isEqual(other.getType())) {
return other;
} else if (canDecompose(this->getType())) {
SmallVector<Space, 4> spaces;
decompose(this->getType(), spaces);
auto decomposition = examineDecomp(spaces);
return decomposition.intersect(other);
} else if (canDecompose(other.getType())) {
SmallVector<Space, 4> spaces;
decompose(other.getType(), spaces);
auto disjunctSp = examineDecomp(spaces);
return this->intersect(disjunctSp);
} else {
return other;
}
}
PAIRCASE (SpaceKind::Type, SpaceKind::Constructor): {
if (canDecompose(this->getType())) {
SmallVector<Space, 4> spaces;
decompose(this->getType(), spaces);
auto decomposition = examineDecomp(spaces);
return decomposition.intersect(other);
} else {
return other;
}
}
PAIRCASE (SpaceKind::Constructor, SpaceKind::Type):
return *this;
PAIRCASE (SpaceKind::Constructor, SpaceKind::Constructor): {
// Optimization: If the heads don't match, the intersection of
// the constructor spaces is empty.
if (this->getHead().compare(other.Head) != 0) {
return Space();
}
// Special Case: A constructor pattern may include the head but not
// the payload patterns. In that case, the intersection is the
// whole original space.
if (other.getSpaces().empty()) {
return *this;
}
SmallVector<Space, 4> paramSpace;
auto i = this->getSpaces().begin();
auto j = other.getSpaces().begin();
for (; i != this->getSpaces().end() && j != other.getSpaces().end();
++i, ++j) {
auto intersection = (*i).intersect(*j);
if (intersection.simplify().isEmpty()) {
return Space();
}
paramSpace.push_back(intersection);
}
return examineDecomp(paramSpace);
}
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::BooleanConstant):
return this->getBoolValue() == other.getBoolValue() ? *this : Space();
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Type): {
if (other.getType()->isBool()) {
return this->getKind() == SpaceKind::BooleanConstant ? *this : Space();
}
if (canDecompose(other.getType())) {
SmallVector<Space, 4> spaces;
decompose(other.getType(), spaces);
auto disjunctSp = examineDecomp(spaces);
return this->intersect(disjunctSp);
}
return Space();
}
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Empty):
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Constructor):
return Space();
PAIRCASE (SpaceKind::Type, SpaceKind::BooleanConstant): {
if (canDecompose(this->getType())) {
SmallVector<Space, 4> spaces;
decompose(this->getType(), spaces);
auto disjunctSp = examineDecomp(spaces);
return disjunctSp.intersect(other);
} else {
return Space();
}
}
PAIRCASE (SpaceKind::Empty, SpaceKind::BooleanConstant):
PAIRCASE (SpaceKind::Constructor, SpaceKind::BooleanConstant):
return Space();
default:
llvm_unreachable("Uncovered pair found while computing intersect?");
}
}
// Returns the result of subtracting the other space from this space. The
// result is empty if the other space completely covers this space, or
// non-empty if there were any uncovered cases. The difference of spaces
// is the smallest uncovered set of cases.
Space minus(const Space &other) const {
if (this->isEmpty()) {
return Space();
}
if (other.isEmpty()) {
return *this;
}
llvm::function_ref<Space(SmallVectorImpl<Space> &)> examineDecomp
= [&](SmallVectorImpl<Space> &decomposition) -> Space {
if (decomposition.empty()) {
return Space();
} else if (decomposition.size() == 1) {
return decomposition.front();
}
return Space(decomposition);
};
switch (PairSwitch(this->getKind(), other.getKind())) {
PAIRCASE (SpaceKind::Type, SpaceKind::Type): {
// Optimization: Are the types equal? If so, the space is covered.
if (this->getType()->isEqual(other.getType())) {
return Space();
} else if (canDecompose(this->getType())) {
SmallVector<Space, 4> spaces;
this->decompose(this->getType(), spaces);
return examineDecomp(spaces).intersect(other);
} else if (canDecompose(other.getType())) {
SmallVector<Space, 4> spaces;
this->decompose(other.getType(), spaces);
auto decomp = examineDecomp(spaces);
return this->intersect(decomp);
}
return Space();
}
PAIRCASE (SpaceKind::Type, SpaceKind::Constructor): {
if (canDecompose(this->getType())) {
SmallVector<Space, 4> spaces;
this->decompose(this->getType(), spaces);
auto decomp = examineDecomp(spaces);
return decomp.minus(other);
} else {
return *this;
}
}
PAIRCASE (SpaceKind::Empty, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Type, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Constructor, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Disjunct):
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Disjunct): {
return std::accumulate(other.getSpaces().begin(),
other.getSpaces().end(),
*this,
[](const Space &left, const Space &right){
return left.minus(right);
});
}
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Empty):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Type):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::Constructor):
PAIRCASE (SpaceKind::Disjunct, SpaceKind::BooleanConstant): {
SmallVector<Space, 4> smallSpaces;
std::transform(this->getSpaces().begin(), this->getSpaces().end(),
std::back_inserter(smallSpaces),
[&](const Space &first){
return first.minus(other);
});
return examineDecomp(smallSpaces);
}
PAIRCASE (SpaceKind::Constructor, SpaceKind::Type):
return Space();
PAIRCASE (SpaceKind::Constructor, SpaceKind::Constructor): {
// Optimization: If the heads of the constructors don't match then
// the two are disjoint and their difference is the first space.
if (this->Head.compare(other.Head) != 0) {
return *this;
}
// Special Case: A constructor pattern may include the head but not
// the payload patterns. In that case, because the heads match, it
// covers the whole space.
if (other.getSpaces().empty()) {
return Space();
}
SmallVector<Space, 4> constrSpaces;
bool foundBad = false;
auto i = this->getSpaces().begin();
auto j = other.getSpaces().begin();
for (auto idx = 0;
i != this->getSpaces().end() && j != other.getSpaces().end();
++i, ++j, ++idx) {
auto &s1 = *i;
auto &s2 = *j;
// If the intersection of each subspace is ever empty then the
// two spaces are disjoint and their difference is the first space.
if (s1.intersect(s2).simplify().isEmpty()) {
return *this;
}
// If one constructor parameter doens't cover the other then we've
// got to report the uncovered cases in a user-friendly way.
if (!s1.isSubspace(s2)) {
foundBad = true;
}
// Copy the params and replace the parameter at each index with the
// difference of the two spaces. This unpacks one constructor head
// into each parameter.
SmallVector<Space, 4> copyParams(this->getSpaces().begin(),
this->getSpaces().end());
copyParams[idx] = s1.minus(s2);
Space CS(this->getType(), this->Head, copyParams);
constrSpaces.push_back(CS);
}
if (foundBad) {
return examineDecomp(constrSpaces);
}
return Space();
}
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::BooleanConstant): {
// The difference of boolean constants depends on their values.
if (this->getBoolValue() == other.getBoolValue()) {
return Space();
} else {
return *this;
}
}
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Type): {
if (other.getType()->isBool()) {
return (getKind() == SpaceKind::BooleanConstant) ? Space() : *this;
}
if (canDecompose(other.getType())) {
SmallVector<Space, 4> spaces;
this->decompose(other.getType(), spaces);
auto disjunctSp = examineDecomp(spaces);
return this->minus(disjunctSp);
}
return *this;
}
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Empty):
PAIRCASE (SpaceKind::BooleanConstant, SpaceKind::Constructor):
return *this;
PAIRCASE (SpaceKind::Type, SpaceKind::BooleanConstant): {
if (canDecompose(this->getType())) {
SmallVector<Space, 4> spaces;
this->decompose(this->getType(), spaces);
auto orSpace = examineDecomp(spaces);
return orSpace.minus(other);
} else {
return *this;
}
}
PAIRCASE (SpaceKind::Empty, SpaceKind::BooleanConstant):
PAIRCASE (SpaceKind::Constructor, SpaceKind::BooleanConstant):
return Space();
default:
llvm_unreachable("Uncovered pair found while computing difference?");
}
}
void show(llvm::raw_ostream &buffer, bool normalize = true) const {
switch (getKind()) {
case SpaceKind::Empty:
buffer << "[EMPTY]";
break;
case SpaceKind::Disjunct: {
if (normalize) {
return simplify().show(buffer, false);
} else {
buffer << "DISJOIN(";
for (auto &sp : Spaces) {
buffer << "\n";
sp.show(buffer, normalize);
buffer << " |";
}
buffer << ")";
}
}
break;
case SpaceKind::BooleanConstant:
buffer << (getBoolValue() ? "true" : "false");
break;
case SpaceKind::Constructor: {
if (!Head.empty()) {
buffer << ".";
buffer << Head.str();
}
if (Spaces.empty()) {
return;
}
buffer << "(";
bool first = true;
for (auto &param : Spaces) {
if (!first) {
buffer << ", ";
}
if (normalize) {
param.simplify().show(buffer, normalize);
} else {
param.show(buffer);
}
if (first) {
first = false;
}
}
buffer << ")";
}
break;
case SpaceKind::Type:
if (!normalize) {
getType()->print(buffer);
}
buffer << "_";
break;
}
}
// For optimization, attempt to simplify a space by removing any empty
// cases and unpacking empty or singular disjunctions where possible.
Space simplify() const {
switch (getKind()) {
case SpaceKind::Constructor: {
// If a constructor has no spaces it is an enum without a payload and
// cannot be optimized further.
if (getSpaces().empty()) {
return *this;
}
// Simplify each component subspace. If, after simplification, any
// subspace contains an empty, then the whole space is empty.
SmallVector<Space, 4> simplifiedSpaces;
std::transform(getSpaces().begin(), getSpaces().end(),
std::back_inserter(simplifiedSpaces),
[](const Space &el) {
return el.simplify();
});
for (auto &el : simplifiedSpaces) {
if (el.isEmpty()) {
return Space();
}
}
return Space(getType(), Head, simplifiedSpaces);
}
case SpaceKind::Type: {
// If the decomposition of a space is empty, the space is empty.
if (canDecompose(this->getType())) {
SmallVector<Space, 4> ss;
decompose(this->getType(), ss);
if (ss.empty()) {
return Space();
}
return *this;
} else {
return *this;
}
}
case SpaceKind::Disjunct: {
// Simplify each disjunct.
SmallVector<Space, 4> simplifiedSpaces;
std::transform(Spaces.begin(), Spaces.end(),
std::back_inserter(simplifiedSpaces),
[](const Space &el){
return el.simplify();
});
// If the disjunct is singular, unpack it into its component.
if (simplifiedSpaces.size() == 1) {
return simplifiedSpaces.front();
}
// Otherwise, remove any empties.
SmallVector<Space, 4> compatifiedSpaces;
std::copy_if(simplifiedSpaces.begin(), simplifiedSpaces.end(),
std::back_inserter(compatifiedSpaces),
[&](const Space &el) {
return !el.isEmpty();
});
// If the disjunct was all empty, the space is empty.
if (compatifiedSpaces.empty()) {
return Space();
}
// Else if the disjunct is singular, unpack it into its component.
if (compatifiedSpaces.size() == 1) {
return compatifiedSpaces.front();
}
return Space(compatifiedSpaces);
}
default:
return *this;
}
}
// Decompose a type into its component spaces.
static void decompose(Type tp, SmallVectorImpl<Space> &arr) {
assert(canDecompose(tp) && "Non-decomposable type?");
if (tp->isBool()) {
arr.push_back(Space(true));
arr.push_back(Space(false));
} else if (auto *E = tp->getEnumOrBoundGenericEnum()) {
// Look into each case of the enum and decompose it in turn.
auto children = E->getAllElements();
std::transform(children.begin(), children.end(),
std::back_inserter(arr), [&](EnumElementDecl *eed) {
// FIXME: This shouldn't happen.
if (!eed->hasInterfaceType()) {
return Space();
}
auto eedTy = tp->getCanonicalType()
->getTypeOfMember(E->getModuleContext(), eed,
eed->getArgumentInterfaceType());
SmallVector<Space, 4> constElemSpaces;
if (eedTy) {
if (auto *TTy = eedTy->getAs<TupleType>()) {
// Decompose the payload tuple into its component type spaces.
std::transform(TTy->getElements().begin(),
TTy->getElements().end(),
std::back_inserter(constElemSpaces),
[&](TupleTypeElt ty){
return Space(ty.getType());
});
} else if (auto *TTy = dyn_cast<ParenType>(eedTy.getPointer())) {
constElemSpaces.push_back(Space(TTy->getUnderlyingType()));
}
}
return Space(tp, eed->getName(), constElemSpaces);
});
} else if (auto *TTy = tp->castTo<TupleType>()) {
// Decompose each of the elements into its component type space.
SmallVector<Space, 4> constElemSpaces;
std::transform(TTy->getElements().begin(), TTy->getElements().end(),
std::back_inserter(constElemSpaces),
[&](TupleTypeElt ty){
return Space(ty.getType());
});
// Create an empty constructor head for the tuple space.
arr.push_back(Space(tp, Identifier(), constElemSpaces));
} else {
llvm_unreachable("Can't decompose type?");
}
}
static bool canDecompose(Type tp) {
return tp->is<TupleType>() || tp->isBool()
|| tp->getEnumOrBoundGenericEnum();
}
};
ASTContext &Ctx;
SwitchStmt *Switch;
SpaceEngine(ASTContext &C, SwitchStmt *SS) : Ctx(C), Switch(SS) {}
void checkExhaustiveness(bool limitedChecking) {
if (limitedChecking) {
// Reject switch statements with empty blocks.
if (Switch->getCases().empty())
SpaceEngine::diagnoseMissingCases(Ctx, Switch,
/*justNeedsDefault*/true,
SpaceEngine::Space());
return;
}
SmallVector<Space, 4> spaces;
for (unsigned i = 0, e = Switch->getCases().size(); i < e; ++i) {
auto *caseBlock = Switch->getCases()[i];
for (auto &caseItem : caseBlock->getCaseLabelItems()) {
// 'where'-clauses on cases mean the case does not contribute to
// the exhaustiveness of the pattern.
if (caseItem.getGuardExpr())
continue;
// Space is trivially covered with a default clause.
if (caseItem.isDefault())
return;
auto projection = projectPattern(Ctx, caseItem.getPattern());
if (projection.isUseful() && projection.isSubspace(Space(spaces))) {
Ctx.Diags
.diagnose(caseItem.getStartLoc(),
diag::redundant_particular_case)
.highlight(caseItem.getSourceRange());
}
spaces.push_back(projection);
}
}
Space totalSpace(Switch->getSubjectExpr()->getType());
Space coveredSpace(spaces);
auto uncovered = totalSpace.minus(coveredSpace).simplify();
if (uncovered.isEmpty()) {
return;
}
// If the entire space is left uncovered we have two choices: We can
// decompose the type space and offer them as fixits, or simply offer
// to insert a `default` clause.
if (uncovered.getKind() == SpaceKind::Type) {
if (Space::canDecompose(uncovered.getType())) {
SmallVector<Space, 4> spaces;
Space::decompose(uncovered.getType(), spaces);
diagnoseMissingCases(Ctx, Switch,
/*justNeedsDefault*/ false, Space(spaces));
} else {
diagnoseMissingCases(Ctx, Switch,
/*justNeedsDefault*/ true, Space());
}
return;
}
// If the space isn't a disjunct then make it one.
if (uncovered.getKind() != SpaceKind::Disjunct) {
SmallVector<Space, 1> spaces = { uncovered };
uncovered = Space(spaces);
}
diagnoseMissingCases(Ctx, Switch, /*justNeedsDefault*/ false, uncovered);
}
static void diagnoseMissingCases(ASTContext &Ctx, const SwitchStmt *Switch,
bool JustNeedsDefault,
SpaceEngine::Space uncovered) {
bool Empty = Switch->getCases().empty();
SourceLoc StartLoc = Switch->getStartLoc();
SourceLoc EndLoc = Switch->getEndLoc();
StringRef Placeholder = getCodePlaceholder();
llvm::SmallString<128> Buffer;
llvm::raw_svector_ostream OS(Buffer);
bool InEditor = Ctx.LangOpts.DiagnosticsEditorMode;
if (JustNeedsDefault) {
OS << tok::kw_default << ": " << Placeholder << "\n";
if (Empty) {
Ctx.Diags.diagnose(StartLoc, diag::empty_switch_stmt)
.fixItInsert(EndLoc, Buffer.str());
} else {
Ctx.Diags.diagnose(StartLoc, diag::non_exhaustive_switch,
InEditor, uncovered.isEmpty())
.fixItInsert(EndLoc, Buffer.str());
}
return;
}
// If there's nothing else to diagnose, bail.
if (uncovered.isEmpty()) return;
// If editing is enabled, emit a formatted error of the form:
//
// switch must be exhaustive, do you want to add missing cases?
// case (.none, .some(_)): <#code#>
// case (.some(_), .none): <#code#>
//
// else:
//
// switch must be exhaustive, consider adding missing cases:
//
// missing case '(.none, .some(_))'
// missing case '(.some(_), .none)'
if (InEditor) {
Buffer.clear();
for (auto &uncoveredSpace : uncovered.getSpaces()) {
SmallVector<Space, 4> flats;
flatten(uncoveredSpace, flats);
if (flats.empty()) {
flats.append({ uncoveredSpace });
}
for (auto &flat : flats) {
OS << tok::kw_case << " ";
flat.show(OS);
OS << ": " << Placeholder << "\n";
}
}
Ctx.Diags.diagnose(StartLoc, diag::non_exhaustive_switch, InEditor,
false).fixItInsert(EndLoc, Buffer.str());
} else {
Ctx.Diags.diagnose(StartLoc, diag::non_exhaustive_switch,
InEditor, false);
for (auto &uncoveredSpace : uncovered.getSpaces()) {
SmallVector<Space, 4> flats;
flatten(uncoveredSpace, flats);
if (flats.empty()) {
flats.append({ uncoveredSpace });
}
for (auto &flat : flats) {
Buffer.clear();
flat.show(OS);
Ctx.Diags.diagnose(StartLoc, diag::missing_particular_case,
Buffer.str());
}
}
}
}
private:
// Recursively unpacks a space of disjunctions or constructor parameters
// into its component parts such that the resulting array of flattened
// spaces contains no further disjunctions. If there were no disjunctions
// in the starting space, the original space is already flat and the
// returned array of spaces will be empty.
static void flatten(const Space space, SmallVectorImpl<Space> &flats) {
switch (space.getKind()) {
case SpaceKind::Constructor: {
size_t i = 0;
for (auto &param : space.getSpaces()) {
// We're only interested in recursively unpacking constructors and
// disjunctions (booleans are considered constructors).
if (param.getKind() != SpaceKind::Constructor
|| param.getKind() != SpaceKind::Disjunct
|| param.getKind() != SpaceKind::BooleanConstant)
continue;
SmallVector<Space, 4> flattenedParams;
flatten(param, flattenedParams);
for (auto &space : flattenedParams) {
SmallVector<Space, 4> row(space.getSpaces().begin(),
space.getSpaces().end());
row[i] = space;
Space cs(space.getType(), space.getHead(), row);
flats.push_back(cs);
}
++i;
}
}
break;
case SpaceKind::Disjunct: {
auto begin = space.getSpaces().begin();
auto end = space.getSpaces().end();
for (; begin != end; ++begin) {
flatten(*begin, flats);
}
}
break;
default:
flats.push_back(space);
break;
}
}
// Recursively project a pattern into a Space.
static Space projectPattern(ASTContext &Ctx, const Pattern *item) {
switch (item->getKind()) {
case PatternKind::Any:
case PatternKind::Named:
return Space(item->getType());
case PatternKind::Bool: {
auto *BP = cast<BoolPattern>(item);
return Space(BP->getValue());
}
case PatternKind::Typed:
case PatternKind::Is:
case PatternKind::Expr:
return Space();
case PatternKind::Var: {
auto *VP = cast<VarPattern>(item);
return projectPattern(Ctx, VP->getSubPattern());
}
case PatternKind::Paren: {
auto *PP = cast<ParenPattern>(item);
return projectPattern(Ctx, PP->getSubPattern());
}
case PatternKind::OptionalSome: {
auto *OSP = cast<OptionalSomePattern>(item);
SmallVector<Space, 1> payload = {
projectPattern(Ctx, OSP->getSubPattern())
};
return Space(item->getType(), Ctx.getIdentifier("some"), payload);
}
case PatternKind::EnumElement: {
auto *VP = cast<EnumElementPattern>(item);
SmallVector<Space, 4> conArgSpace;
auto *SP = VP->getSubPattern();
if (!SP) {
// If there's no sub-pattern then there's no further recursive
// structure here. Yield the constructor space.
return Space(item->getType(), VP->getName(), conArgSpace);
}
switch (SP->getKind()) {
case PatternKind::Tuple: {
auto *TP = dyn_cast<TuplePattern>(SP);
std::transform(TP->getElements().begin(), TP->getElements().end(),
std::back_inserter(conArgSpace),
[&](TuplePatternElt pate) {
return projectPattern(Ctx, pate.getPattern());
});
return Space(item->getType(), VP->getName(), conArgSpace);
}
case PatternKind::Paren: {
auto *PP = dyn_cast<ParenPattern>(SP);
auto *SP = PP->getSemanticsProvidingPattern();
// Special Case: A constructor pattern may have all of its payload
// matched by a single var pattern. Project it like the tuple it
// really is.
if (SP->getKind() == PatternKind::Named
|| SP->getKind() == PatternKind::Any
|| SP->getKind() == PatternKind::Tuple) {
if (auto *TTy = SP->getType()->getAs<TupleType>()) {
for (auto ty : TTy->getElements()) {
conArgSpace.push_back(Space(ty.getType()));
}
} else {
conArgSpace.push_back(projectPattern(Ctx, SP));
}
} else {
conArgSpace.push_back(projectPattern(Ctx, SP));
}
return Space(item->getType(), VP->getName(), conArgSpace);
}
default:
return projectPattern(Ctx, SP);
}
}
case PatternKind::Tuple: {
auto *TP = cast<TuplePattern>(item);
SmallVector<Space, 4> conArgSpace;
std::transform(TP->getElements().begin(), TP->getElements().end(),
std::back_inserter(conArgSpace),
[&](TuplePatternElt pate) {
return projectPattern(Ctx, pate.getPattern());
});
return Space(item->getType(), Identifier(), conArgSpace);
}
}
}
};
} // end anonymous namespace
void TypeChecker::checkSwitchExhaustiveness(SwitchStmt *stmt, bool limited) {
SpaceEngine(Context, stmt).checkExhaustiveness(limited);
}
void SpaceEngine::Space::dump() const {
SmallString<128> buf;
llvm::raw_svector_ostream os(buf);
this->show(os, /*normalize*/false);
llvm::errs() << buf.str();
llvm::errs() << "\n";
}