Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions include/swift/AST/Effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,25 @@ namespace swift {
class ValueDecl;

class ProtocolRethrowsRequirementList {
public:
typedef std::pair<Type, ValueDecl *> Entry;

using ThrowingRequirements = ArrayRef<AbstractFunctionDecl *>;
using ThrowingConformances = ArrayRef<std::pair<Type, ProtocolDecl *>>;
private:
ArrayRef<Entry> entries;
ThrowingRequirements requirements;
ThrowingConformances conformances;

public:
ProtocolRethrowsRequirementList(ArrayRef<Entry> entries) : entries(entries) {}
ProtocolRethrowsRequirementList() : entries() {}

typedef const Entry *const_iterator;
typedef const_iterator iterator;

const_iterator begin() const { return entries.begin(); }
const_iterator end() const { return entries.end(); }

size_t size() const { return entries.size(); }

void print(raw_ostream &OS) const;

SWIFT_DEBUG_DUMP;
ProtocolRethrowsRequirementList(ThrowingRequirements requirements,
ThrowingConformances conformances)
: requirements(requirements), conformances(conformances) {}
ProtocolRethrowsRequirementList() {}

ThrowingRequirements getRequirements() const {
return requirements;
}

ThrowingConformances getConformances() const {
return conformances;
}
};

void simple_display(llvm::raw_ostream &out, const ProtocolRethrowsRequirementList reqs);
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@ inline bool ProtocolConformance::hasWitness(ValueDecl *requirement) const {
return getRootConformance()->hasWitness(requirement);
}

SourceLoc extractNearestSourceLoc(const ProtocolConformance *conf);
void simple_display(llvm::raw_ostream &out, const ProtocolConformance *conf);

} // end namespace swift
Expand Down
9 changes: 5 additions & 4 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "swift/AST/Type.h"
#include "swift/AST/Evaluator.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/SimpleRequest.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/TypeResolutionStage.h"
Expand Down Expand Up @@ -331,9 +332,9 @@ class ProtocolRethrowsRequirementsRequest :
bool isCached() const { return true; }
};

class ProtocolConformanceRefClassifyAsThrowsRequest :
public SimpleRequest<ProtocolConformanceRefClassifyAsThrowsRequest,
bool(ProtocolConformanceRef),
class ProtocolConformanceClassifyAsThrowsRequest :
public SimpleRequest<ProtocolConformanceClassifyAsThrowsRequest,
bool(ProtocolConformance *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;
Expand All @@ -343,7 +344,7 @@ class ProtocolConformanceRefClassifyAsThrowsRequest :

// Evaluation.
bool
evaluate(Evaluator &evaluator, ProtocolConformanceRef conformanceRef) const;
evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const;

public:
// Caching.
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest,
SWIFT_REQUEST(TypeChecker, ProtocolRethrowsRequirementsRequest,
ProtocolRethrowsRequirementList(ProtocolDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ProtocolConformanceRefClassifyAsThrowsRequest,
SWIFT_REQUEST(TypeChecker, ProtocolConformanceClassifyAsThrowsRequest,
bool(ProtocolConformanceRef),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ResolveTypeRequest,
Expand Down
15 changes: 11 additions & 4 deletions lib/AST/Effects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@ using namespace swift;

void swift::simple_display(llvm::raw_ostream &out,
const ProtocolRethrowsRequirementList list) {
for (auto entry : list) {
simple_display(out, entry.first);
simple_display(out, entry.second);
for (auto req : list.getRequirements()) {
simple_display(out, req);
out << "\n";
}

for (auto conf : list.getConformances()) {
simple_display(out, conf.first);
out << " : ";
simple_display(out, conf.second);
llvm::errs() << "\n";
}
}

Expand Down Expand Up @@ -75,6 +82,6 @@ void swift::simple_display(llvm::raw_ostream &out,
bool ProtocolConformanceRef::classifyAsThrows() const {
if (!isConcrete()) { return true; }
return evaluateOrDefault(getRequirement()->getASTContext().evaluator,
ProtocolConformanceRefClassifyAsThrowsRequest{ *this },
ProtocolConformanceClassifyAsThrowsRequest{getConcrete()},
true);
}
6 changes: 5 additions & 1 deletion lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,10 @@ void swift::simple_display(llvm::raw_ostream &out,
conf->printName(out);
}

SourceLoc swift::extractNearestSourceLoc(const ProtocolConformance *conformance) {
return extractNearestSourceLoc(conformance->getDeclContext());
}

void swift::simple_display(llvm::raw_ostream &out, ProtocolConformanceRef conformanceRef) {
if (conformanceRef.isAbstract()) {
simple_display(out, conformanceRef.getAbstract());
Expand All @@ -1543,7 +1547,7 @@ SourceLoc swift::extractNearestSourceLoc(const ProtocolConformanceRef conformanc
if (conformanceRef.isAbstract()) {
return extractNearestSourceLoc(conformanceRef.getAbstract());
} else if (conformanceRef.isConcrete()) {
return extractNearestSourceLoc(conformanceRef.getConcrete()->getProtocol());
return extractNearestSourceLoc(conformanceRef.getConcrete());
}
return SourceLoc();
}
140 changes: 79 additions & 61 deletions lib/Sema/TypeCheckEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,44 +56,40 @@ static bool hasThrowingFunctionClosureParameter(CanType type) {
ProtocolRethrowsRequirementList
ProtocolRethrowsRequirementsRequest::evaluate(Evaluator &evaluator,
ProtocolDecl *decl) const {
SmallVector<std::pair<Type, ValueDecl*>, 2> found;
llvm::DenseSet<ProtocolDecl*> checkedProtocols;

ASTContext &ctx = decl->getASTContext();

// only allow rethrowing requirements to be determined from marked protocols
if (!decl->getAttrs().hasAttribute<swift::AtRethrowsAttr>()) {
return ProtocolRethrowsRequirementList(ctx.AllocateCopy(found));
if (!decl->isRethrowingProtocol()) {
return ProtocolRethrowsRequirementList();
}

SmallVector<AbstractFunctionDecl *, 2> requirements;
SmallVector<std::pair<Type, ProtocolDecl *>, 2> conformances;

// check if immediate members of protocol are 'throws'
for (auto member : decl->getMembers()) {
auto fnDecl = dyn_cast<AbstractFunctionDecl>(member);
if (!fnDecl || !fnDecl->hasThrows())
continue;

found.push_back(
std::pair<Type, ValueDecl*>(decl->getSelfInterfaceType(), fnDecl));
requirements.push_back(fnDecl);
}
checkedProtocols.insert(decl);

// check associated conformances of associated types or inheritance
for (auto requirement : decl->getRequirementSignature()) {
if (requirement.getKind() != RequirementKind::Conformance) {
if (requirement.getKind() != RequirementKind::Conformance)
continue;
}

auto protoTy = requirement.getSecondType()->castTo<ProtocolType>();
auto proto = protoTy->getDecl();
if (checkedProtocols.count(proto) != 0) {
auto protoDecl = protoTy->getDecl();
if (!protoDecl->isRethrowingProtocol())
continue;
}
checkedProtocols.insert(proto);
for (auto entry : proto->getRethrowingRequirements()) {
found.emplace_back(requirement.getFirstType(), entry.second);
}

conformances.emplace_back(requirement.getFirstType(), protoDecl);
}

return ProtocolRethrowsRequirementList(ctx.AllocateCopy(found));
return ProtocolRethrowsRequirementList(ctx.AllocateCopy(requirements),
ctx.AllocateCopy(conformances));
}

FunctionRethrowingKind
Expand Down Expand Up @@ -135,68 +131,90 @@ FunctionRethrowingKindRequest::evaluate(Evaluator &evaluator,
return FunctionRethrowingKind::None;
}

static bool classifyRequirement(ModuleDecl *module,
ProtocolConformance *reqConformance,
ValueDecl *requiredFn) {
auto declRef = reqConformance->getWitnessDeclRef(requiredFn);
auto witnessDecl = cast<AbstractFunctionDecl>(declRef.getDecl());
static bool classifyWitness(ModuleDecl *module,
ProtocolConformance *conformance,
AbstractFunctionDecl *req) {
auto declRef = conformance->getWitnessDeclRef(req);
if (!declRef) {
// Invalid conformance.
return true;
}

auto witnessDecl = dyn_cast<AbstractFunctionDecl>(declRef.getDecl());
if (!witnessDecl) {
// Enum element constructors never throw.
assert(isa<EnumElementDecl>(declRef.getDecl()));
return false;
}

switch (witnessDecl->getRethrowingKind()) {
case FunctionRethrowingKind::None:
// Witness doesn't throw at all, so it contributes nothing.
return false;

case FunctionRethrowingKind::ByConformance: {
auto substitutions = reqConformance->getSubstitutions(module);
// Witness throws if the concrete type's @rethrows conformances
// recursively throw.
auto substitutions = conformance->getSubstitutions(module);
for (auto conformanceRef : substitutions.getConformances()) {
if (conformanceRef.classifyAsThrows()) {
return true;
}
}
break;
return false;
}
case FunctionRethrowingKind::None:
break;

case FunctionRethrowingKind::ByClosure:
// Witness only throws if a closure argument throws, so it
// contributes nothng.
return false;

case FunctionRethrowingKind::Throws:
// Witness always throws.
return true;
default:

case FunctionRethrowingKind::Invalid:
// If the code is invalid, just assume it throws.
return true;
}
return false;
}

// classify the type requirements of a given protocol type with a function
// requirement as throws or not. This will detect if the signature of the
// function is throwing or not depending on associated types.
static bool classifyTypeRequirement(ModuleDecl *module, Type protoType,
ValueDecl *requiredFn,
ProtocolConformance *conformance,
ProtocolDecl *requiredProtocol) {
auto reqProtocol = cast<ProtocolDecl>(requiredFn->getDeclContext());
ProtocolConformance *reqConformance;

if(protoType->isEqual(reqProtocol->getSelfInterfaceType()) &&
requiredProtocol == reqProtocol) {
reqConformance = conformance;
} else {
auto reqConformanceRef =
conformance->getAssociatedConformance(protoType, reqProtocol);
if (!reqConformanceRef.isConcrete()) {
return true;
bool
ProtocolConformanceClassifyAsThrowsRequest::evaluate(
Evaluator &evaluator, ProtocolConformance *conformance) const {
auto *module = conformance->getDeclContext()->getParentModule();

llvm::SmallDenseSet<ProtocolConformance *, 2> visited;
SmallVector<ProtocolConformance *, 2> worklist;

worklist.push_back(conformance);

while (!worklist.empty()) {
auto *current = worklist.back();
worklist.pop_back();

if (!visited.insert(current).second)
continue;

auto protoDecl = current->getProtocol();

auto list = protoDecl->getRethrowingRequirements();
for (auto req : list.getRequirements()) {
if (classifyWitness(module, current, req))
return true;
}
reqConformance = reqConformanceRef.getConcrete();
}

return classifyRequirement(module, reqConformance, requiredFn);
}
for (auto pair : list.getConformances()) {
auto assocConf =
current->getAssociatedConformance(
pair.first, pair.second);
if (!assocConf.isConcrete())
return true;

bool
ProtocolConformanceRefClassifyAsThrowsRequest::evaluate(
Evaluator &evaluator, ProtocolConformanceRef conformanceRef) const {
auto conformance = conformanceRef.getConcrete();
auto requiredProtocol = conformanceRef.getRequirement();
auto module = requiredProtocol->getModuleContext();
for (auto req : requiredProtocol->getRethrowingRequirements()) {
if (classifyTypeRequirement(module, req.first, req.second,
conformance, requiredProtocol)) {
return true;
worklist.push_back(assocConf.getConcrete());
}
}

return false;
}

Expand Down
47 changes: 45 additions & 2 deletions test/attr/attr_rethrows_protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func rethrowsWithRethrowsClosure<T : RethrowsClosure>(_ t: T) rethrows {
try t.doIt() {}
}

try rethrowsWithRethrowsClosure(RethrowsClosureWitness())
rethrowsWithRethrowsClosure(RethrowsClosureWitness())

// Empty protocol
@rethrows protocol Empty {}
Expand Down Expand Up @@ -162,4 +162,47 @@ func soundnessHole<T : SimpleThrowsClosure>(_ t: T) {
}

// This actually can throw...
soundnessHole(ConformsToSimpleThrowsClosure(t: Throws()))
soundnessHole(ConformsToSimpleThrowsClosure(t: Throws()))

// Test deeply-nested associated conformances
@rethrows protocol First {
associatedtype A : Second
}

@rethrows protocol Second {
associatedtype B : Third
}

@rethrows protocol Third {
func f() throws
}

struct FirstWitness : First {
typealias A = SecondWitness
}

struct SecondWitness : Second {
typealias B = ThirdWitness
}

struct ThirdWitness : Third {
func f() {}
}

func takesFirst<T : First>(_: T) rethrows {}

takesFirst(FirstWitness())

// Crash with enum case
@rethrows protocol WitnessedByEnumCase {
static func foo(_: Int) throws -> Self
}

enum MyEnum : WitnessedByEnumCase {
case foo(Int)
case bar
}

func takesWitnessedByEnumCase<T : WitnessedByEnumCase>(_: T) rethrows {}

takesWitnessedByEnumCase(MyEnum.bar)
Loading