blob: a69ad7cf599d2351f8ad8591067d4dea123ed6db [file] [log] [blame]
//===--- TypeCheckCircularity.cpp - Type decl circularity checking --------===//
//
// 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 implements an infinitely-sized-type check.
//
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
using namespace swift;
#define DEBUG_TYPE "TypeCheckCircularity"
namespace {
/// The information we track for a type.
class TrackingInfo {
/// The parent type; either null or a key for an entry in TrackingMap.
CanType Parent;
/// The member of the parent type that lead to this type, or null
/// for a tuple element; and whether the type is currently being
/// expanded.
llvm::PointerIntPair<ValueDecl *, 1, bool> ParentMemberAndIsBeingExpanded;
public:
TrackingInfo(CanType parent, ValueDecl *parentMember)
: Parent(parent), ParentMemberAndIsBeingExpanded(parentMember, false) {}
CanType getParentType() const {
return Parent;
}
ValueDecl *getParentMember() const {
return ParentMemberAndIsBeingExpanded.getPointer();
}
bool isBeingExpanded() const {
return ParentMemberAndIsBeingExpanded.getInt();
}
void setBeingExpanded(bool isBeingExpanded) {
ParentMemberAndIsBeingExpanded.setInt(isBeingExpanded);
}
};
struct WorkItem {
enum : unsigned {
/// A special depth we use to say that this work item
/// is to *finish* expanding the target type.
FinishExpandingType = ~0U
};
unsigned Depth;
CanType Type;
WorkItem(unsigned depth, CanType type)
: Depth(depth), Type(type) {}
};
struct PathElement {
ValueDecl *Member; // Or nullptr for a tuple element type.
size_t TupleIndex;
Type Ty;
void dump() const;
void print(llvm::raw_ostream &out) const;
};
class Path {
SmallVector<PathElement, 8> Elements;
public:
void push_back(const PathElement &elt) { Elements.push_back(elt); }
bool empty() const { return Elements.empty(); }
size_t size() const { return Elements.size(); }
const PathElement &operator[](size_t index) const { return Elements[index]; }
const PathElement &back() const { return Elements.back(); }
void dump() const;
void printCycle(llvm::raw_ostream &out, size_t cycleIndex) const;
void printInfinite(llvm::raw_ostream &out) const;
private:
void printSegment(llvm::raw_ostream &out, size_t begin, size_t end,
size_t maxContext, bool printFirstType = true) const;
};
/// A helper class for performing a circularity check.
class CircularityChecker {
TypeChecker &TC;
/// The original type declaration we're starting with.
NominalTypeDecl *OriginalDecl;
/// The maximum circularity depth.
unsigned MaxDepth;
/// Whether we encountered an unchecked declaration.
bool RequireDelayedChecking = false;
llvm::DenseMap<CanType, TrackingInfo> TrackingMap;
SmallVector<WorkItem, 8> Workstack;
public:
CircularityChecker(TypeChecker &tc, NominalTypeDecl *typeDecl)
: TC(tc), OriginalDecl(typeDecl),
MaxDepth(tc.Context.LangOpts.MaxCircularityDepth) {}
void run();
private:
Type getOriginalType() const {
return OriginalDecl->getDeclaredInterfaceType();
}
bool expandType(CanType type, unsigned depth);
bool expandTuple(CanTupleType type, unsigned depth);
bool expandNominal(CanType type, NominalTypeDecl *D, unsigned depth);
bool expandStruct(CanType type, StructDecl *S, unsigned depth);
bool expandEnum(CanType type, EnumDecl *E, unsigned depth);
bool addMember(CanType parentType, ValueDecl *member, Type memberType,
unsigned parentDepth);
void startExpandingType(CanType type) {
auto it = TrackingMap.find(type);
assert(it != TrackingMap.end());
// Set the IsBeginExpanded flag.
assert(!it->second.isBeingExpanded() && "already expanding type");
it->second.setBeingExpanded(true);
// Push a work item to clear that flag.
pushFinishExpandingTypeWorkItem(type);
}
void finishExpandingType(CanType type) {
auto it = TrackingMap.find(type);
assert(it != TrackingMap.end());
// Clear the IsBeginExpanded flag.
assert(it->second.isBeingExpanded() && "not already expanding type");
it->second.setBeingExpanded(false);
}
void pushFinishExpandingTypeWorkItem(CanType type) {
Workstack.emplace_back(WorkItem::FinishExpandingType, type);
}
void pushExpandTypeWorkItem(CanType type, unsigned depth) {
assert(depth != WorkItem::FinishExpandingType);
Workstack.emplace_back(depth, type);
}
bool diagnoseCircularity(CanType parentType, ValueDecl *member,
CanType memberType);
bool diagnoseInfiniteRecursion(CanType parentType, ValueDecl *member,
CanType memberType);
void diagnoseNonWellFoundedEnum(EnumDecl *E);
void addPathElementsTo(Path &path, CanType type);
void addPathElement(Path &path, ValueDecl *member, CanType memberType);
Path buildPath(CanType parentType, ValueDecl *member, CanType memberType);
};
} // end anonymous namespace
void TypeChecker::checkDeclCircularity(NominalTypeDecl *decl) {
CircularityChecker(*this, decl).run();
}
/// The main routine for performing circularity checks.
void CircularityChecker::run() {
auto type = getOriginalType()->getCanonicalType();
// Prime the tracking map.
TrackingMap.insert({type, TrackingInfo(CanType(), nullptr)});
// Recurse into the top-level nominal type.
expandNominal(type, OriginalDecl, 0);
// Execute the workstack.
while (!Workstack.empty()) {
auto item = Workstack.pop_back_val();
if (item.Depth == WorkItem::FinishExpandingType) {
finishExpandingType(item.Type);
} else if (expandType(item.Type, item.Depth)) {
return;
}
}
// If we didn't report an error, but we encountered a property that
// hadn't been type-checked, queue the type for delayed checking.
if (RequireDelayedChecking) {
TC.DelayedCircularityChecks.push_back(OriginalDecl);
}
}
/// Visit a type and try to expand it one level.
///
/// \return true if a problem was found and all further processing
/// should be aborted.
bool CircularityChecker::expandType(CanType type, unsigned depth) {
if (auto D = type.getAnyNominal()) {
return expandNominal(type, D, depth);
} else if (auto tuple = dyn_cast<TupleType>(type)) {
return expandTuple(tuple, depth);
} else {
return false;
}
}
/// Visit a tuple type and try to expand it one level.
bool CircularityChecker::expandTuple(CanTupleType tupleType, unsigned depth) {
startExpandingType(tupleType);
for (auto eltType : tupleType.getElementTypes()) {
if (addMember(tupleType, nullptr, eltType, depth))
return true;
}
return false;
}
/// Visit a nominal type and try to expand it one level.
bool CircularityChecker::expandNominal(CanType type, NominalTypeDecl *D,
unsigned depth) {
if (auto S = dyn_cast<StructDecl>(D)) {
return expandStruct(type, S, depth);
} else if (auto E = dyn_cast<EnumDecl>(D)) {
return expandEnum(type, E, depth);
} else {
// Other nominal types are representational leaves.
return false;
}
}
/// Visit a struct type and try to expand it one level.
bool CircularityChecker::expandStruct(CanType type, StructDecl *S,
unsigned depth) {
startExpandingType(type);
for (auto field: S->getStoredProperties()) {
// Ignore unchecked fields, but flag that we'll need more checking later.
if (!field->hasInterfaceType()) {
RequireDelayedChecking = true;
continue;
}
auto fieldType =
type->getTypeOfMember(S->getModuleContext(), field, nullptr);
if (addMember(type, field, fieldType, depth))
return true;
}
return false;
}
/// Visit an enum type and try to expand it one level.
bool CircularityChecker::expandEnum(CanType type, EnumDecl *E,
unsigned depth) {
// Indirect enums are representational leaves.
if (E->isIndirect()) {
// Diagnose whether the enum is non-well-founded before bailing
diagnoseNonWellFoundedEnum(E);
return false;
}
startExpandingType(type);
for (auto elt: E->getAllElements()) {
// Ignore unchecked elements, but flag that we'll need more checking later.
if (!elt->hasInterfaceType()) {
RequireDelayedChecking = true;
continue;
}
// Indirect elements are representational leaves.
if (elt->isIndirect())
continue;
// Ignore elements with no payload.
auto eltIfaceType = elt->getArgumentInterfaceType();
if (!eltIfaceType)
continue;
auto eltType =
type->getTypeOfMember(E->getModuleContext(), elt, eltIfaceType);
if (addMember(type, elt, eltType, depth))
return true;
}
diagnoseNonWellFoundedEnum(E);
return false;
}
bool CircularityChecker::addMember(CanType parentType, ValueDecl *member,
Type memberNCType, unsigned parentDepth) {
auto memberType = memberNCType->getCanonicalType();
unsigned depth = parentDepth + 1;
if (depth > MaxDepth) {
return diagnoseInfiniteRecursion(parentType, member, memberType);
}
// Fast path: if the type isn't some sort of interesting type,
// just ignore it.
if (isa<TupleType>(memberType)) {
// Ok, visit tuples.
} else if (memberType.getStructOrBoundGenericStruct()) {
// Ok, visit structs.
// TODO: skip non-generic types in different modules?
} else if (auto E = memberType.getEnumOrBoundGenericEnum()) {
// Ok, visit non-indirect enums.
if (E->isIndirect()) return false;
// TODO: skip non-generic types in different modules?
} else {
// Everything else is a representational leaf.
return false;
}
// Try to start tracking the type.
auto insertion = TrackingMap.insert({memberType,
TrackingInfo(parentType, member)});
// If it's not already there, add an item to recurse into it.
if (insertion.second) {
pushExpandTypeWorkItem(memberType, depth);
return false;
}
// Otherwise, we've already enqueued it. If we're not currently
// expanding it, there's no circularity to worry about.
auto &info = insertion.first->second;
if (!info.isBeingExpanded())
return false;
return diagnoseCircularity(parentType, member, memberType);
}
static size_t findCycleIndex(const Path &path) {
for (auto index : IntRange<size_t>(0, path.size() - 1)) {
if (path[index].Ty->isEqual(path.back().Ty))
return index;
}
llvm_unreachable("didn't find cycle in path");
}
static Type getMemberStorageInterfaceType(ValueDecl *member) {
if (auto elt = dyn_cast<EnumElementDecl>(member)) {
return elt->getArgumentInterfaceType();
} else {
return member->getInterfaceType();
}
}
static bool isNonDependentField(const PathElement &elt) {
if (!elt.Member) return false;
return !getMemberStorageInterfaceType(elt.Member)->hasTypeParameter();
}
void LLVM_ATTRIBUTE_USED Path::dump() const {
auto &out = llvm::errs();
printSegment(out, 0, size(), size());
out << '\n';
}
/// Prints:
/// TypeA -> (a: TypeB) -> (b: TypeB) -> (c: CycleType)
/// -> (d: TypeD) -> (e: CycleType)
void Path::printCycle(llvm::raw_ostream &out, size_t cycleIndex) const {
// If the cycle goes to Self or the member type, print the
// path in one segment starting from the field type.
if (cycleIndex <= 1) {
printSegment(out, 1, size(), 3);
// Otherwise, print the path in two segments.
} else {
printSegment(out, 1, cycleIndex + 1, 2);
printSegment(out, cycleIndex, size(), 2, false);
}
}
/// Prints:
/// TypeA -> (x: TypeB) -> (y: TypeB) -> (z: TypeC) -> ...
void Path::printInfinite(llvm::raw_ostream &out) const {
printSegment(out, 1, std::min(size(), size_t(7)), 7);
out << " -> ...";
}
/// Prints:
/// [TypeA] -> (a: TypeB) -> (b: TypeB) -> (c: TypeC)
/// If the path is too long, elides the middle with '-> ...'.
void Path::printSegment(llvm::raw_ostream &out, size_t begin, size_t end,
size_t maxContext, bool printFirstType) const {
if (printFirstType) {
out << Elements[begin].Ty;
}
size_t numElements = end - begin;
if (numElements > maxContext * 2) {
for (size_t i = begin + 1; i != begin + maxContext + 1; ++i)
Elements[i].print(out);
out << " -> ... ";
for (size_t i = end - maxContext; i != end; ++i)
Elements[i].print(out);
} else {
for (size_t i = begin + 1; i != end; ++i) {
Elements[i].print(out);
}
}
}
void LLVM_ATTRIBUTE_USED PathElement::dump() const {
auto &out = llvm::errs();
print(out);
out << '\n';
}
/// Prints:
/// -> (a: TypeA)
void PathElement::print(llvm::raw_ostream &out) const {
out << " -> (";
if (Member) {
auto name = Member->getFullName();
if (name) {
out << name;
} else {
out << "<anonymous>";
}
} else {
out << '.' << TupleIndex;
}
out << ": " << Ty << ')';
}
/// Recreate a non-canonical type path.
Path CircularityChecker::buildPath(CanType parentType, ValueDecl *member,
CanType memberType) {
Path path;
addPathElementsTo(path, parentType);
addPathElement(path, member, memberType);
return path;
}
/// Recreate a non-canonical path that leads to the target type.
void CircularityChecker::addPathElementsTo(Path &path, CanType targetType) {
auto it = TrackingMap.find(targetType);
assert(it != TrackingMap.end() && "no entry in tracking map?");
CanType canParentType = it->second.getParentType();
if (!canParentType) {
path.push_back({ nullptr, 0, getOriginalType()});
return;
}
addPathElementsTo(path, canParentType);
addPathElement(path, it->second.getParentMember(), targetType);
}
/// Add a non-canonical path element to a path.
void CircularityChecker::addPathElement(Path &path, ValueDecl *member,
CanType canMemberType) {
assert(!path.empty());
Type parentType = path.back().Ty;
PathElement elt;
if (member) {
elt.Member = member;
elt.TupleIndex = 0;
Type memberIfaceType = getMemberStorageInterfaceType(member);
elt.Ty = parentType->getTypeOfMember(member->getModuleContext(), member,
memberIfaceType);
} else {
auto tupleType = parentType->castTo<TupleType>();
for (auto index : indices(tupleType->getElementTypes())) {
auto eltType = tupleType->getElementType(index);
if (eltType->getCanonicalType() == canMemberType) {
elt.Member = nullptr;
elt.TupleIndex = index;
elt.Ty = eltType;
break;
}
}
assert(elt.Ty && "didn't find matching element of tuple");
}
// We shouldn't print reference storage types here.
if (auto ref = elt.Ty->getAs<ReferenceStorageType>()) {
elt.Ty = ref->getReferentType();
}
// Strip outer parens from the type. (This is especially common with
// enum elements.)
if (auto parens = dyn_cast<ParenType>(elt.Ty.getPointer())) {
elt.Ty = parens->getSinglyDesugaredType();
}
path.push_back(elt);
}
/// Diagnose a circularity.
///
/// \returns always true
bool CircularityChecker::diagnoseCircularity(CanType parentType,
ValueDecl *member,
CanType memberType) {
auto path = buildPath(parentType, member, memberType);
// Find the index that the cycle leads back to.
auto cycleIndex = findCycleIndex(path);
// If the path to the cycle passes through a field (other than the one
// directly declared on this type) that does not depend on the original
// declaration in any way, then the type that contains that field should
// be responsible for reporting the cycle.
// TODO: we can also suppress this if the cycle would exist independently.
for (size_t i = 2; i < cycleIndex + 1; ++i) {
if (isNonDependentField(path[i]))
return true;
}
auto baseType = path[0].Ty;
if (cycleIndex != 0) {
TC.diagnose(OriginalDecl->getLoc(),
diag::unsupported_infinitely_sized_type,
baseType);
} else if (isa<StructDecl>(OriginalDecl)) {
TC.diagnose(path[1].Member->getLoc(),
diag::unsupported_recursive_struct,
baseType);
} else if (isa<EnumDecl>(OriginalDecl)) {
TC.diagnose(OriginalDecl->getLoc(),
diag::recursive_enum_not_indirect,
baseType)
.fixItInsert(OriginalDecl->getStartLoc(), "indirect ");
} else {
llvm_unreachable("what kind of entity was this?");
}
// Add a note about the path we found unless it's completely trivial.
if (path.size() > 2) {
llvm::SmallString<128> pathString; {
llvm::raw_svector_ostream out(pathString);
path.printCycle(out, cycleIndex);
}
TC.diagnose(path[1].Member->getLoc(),
diag::note_type_cycle_starts_here,
pathString);
} else if (isa<EnumDecl>(OriginalDecl)) {
TC.diagnose(path[1].Member->getLoc(),
diag::note_recursive_enum_case_here);
}
return true;
}
bool CircularityChecker::diagnoseInfiniteRecursion(CanType parentType,
ValueDecl *member,
CanType memberType) {
auto path = buildPath(parentType, member, memberType);
// If the path passes through a field (other than the one directly
// declared on this type) that does not depend on the original
// declaration in any way, then the type that contains that field should
// be responsible for reporting the cycle.
// Applying this heuristic only makes sense if we're assuming that
// it really is an infinite expansion.
for (size_t i = 2, e = path.size(); i < e; ++i) {
if (isNonDependentField(path[i]))
return true;
}
auto baseType = path[0].Ty;
TC.diagnose(OriginalDecl->getLoc(),
diag::unsupported_infinitely_sized_type,
baseType);
// Add a note about the start of the path.
llvm::SmallString<128> pathString; {
llvm::raw_svector_ostream out(pathString);
path.printInfinite(out);
}
TC.diagnose(path[1].Member->getLoc(),
diag::note_type_cycle_starts_here,
pathString);
return true;
}
/// Show a warning if all cases of the given enum are recursive,
/// making it impossible to be instantiated. Such an enum is 'non-well-founded'.
/// The outcome of this method is irrelevant.
void CircularityChecker::diagnoseNonWellFoundedEnum(EnumDecl *E) {
auto containsType = [](TupleType *tuple, Type E) -> bool {
for (auto type: tuple->getElementTypes()) {
if (type->isEqual(E))
return true;
}
return false;
};
auto isNonWellFounded = [containsType, E]() -> bool {
auto elts = E->getAllElements();
if (elts.empty())
return false;
for (auto elt: elts) {
if (!elt->hasInterfaceType() ||
!(elt->isIndirect() || E->isIndirect()))
return false;
auto argTy = elt->getArgumentInterfaceType();
if (!argTy)
return false;
if (auto tuple = argTy->getAs<TupleType>()) {
if (!containsType(tuple, E->getSelfInterfaceType()))
return false;
} else if (auto paren = dyn_cast<ParenType>(argTy.getPointer())) {
if (!E->getSelfInterfaceType()->isEqual(paren->getUnderlyingType()))
return false;
}
}
return true;
};
if (isNonWellFounded())
TC.diagnose(E, diag::enum_non_well_founded);
}