Skip to content

Commit e2d4eb6

Browse files
committed
Sema: Fix recursion into associated conformances when checking for rethrows
The logic for recursively expanding protocol requirements was broken. Instead, let's do the recursive visitation when evaluating a conformance to a @rethrows protocol.
1 parent 07bd079 commit e2d4eb6

File tree

6 files changed

+113
-83
lines changed

6 files changed

+113
-83
lines changed

include/swift/AST/Effects.h

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,25 @@ namespace swift {
3030
class ValueDecl;
3131

3232
class ProtocolRethrowsRequirementList {
33-
public:
34-
typedef std::pair<Type, ValueDecl *> Entry;
35-
33+
using ThrowingRequirements = ArrayRef<AbstractFunctionDecl *>;
34+
using ThrowingConformances = ArrayRef<std::pair<Type, ProtocolDecl *>>;
3635
private:
37-
ArrayRef<Entry> entries;
36+
ThrowingRequirements requirements;
37+
ThrowingConformances conformances;
3838

3939
public:
40-
ProtocolRethrowsRequirementList(ArrayRef<Entry> entries) : entries(entries) {}
41-
ProtocolRethrowsRequirementList() : entries() {}
42-
43-
typedef const Entry *const_iterator;
44-
typedef const_iterator iterator;
45-
46-
const_iterator begin() const { return entries.begin(); }
47-
const_iterator end() const { return entries.end(); }
48-
49-
size_t size() const { return entries.size(); }
50-
51-
void print(raw_ostream &OS) const;
52-
53-
SWIFT_DEBUG_DUMP;
40+
ProtocolRethrowsRequirementList(ThrowingRequirements requirements,
41+
ThrowingConformances conformances)
42+
: requirements(requirements), conformances(conformances) {}
43+
ProtocolRethrowsRequirementList() {}
44+
45+
ThrowingRequirements getRequirements() const {
46+
return requirements;
47+
}
48+
49+
ThrowingConformances getConformances() const {
50+
return conformances;
51+
}
5452
};
5553

5654
void simple_display(llvm::raw_ostream &out, const ProtocolRethrowsRequirementList reqs);

include/swift/AST/TypeCheckRequests.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/Type.h"
2626
#include "swift/AST/Evaluator.h"
2727
#include "swift/AST/Pattern.h"
28+
#include "swift/AST/ProtocolConformance.h"
2829
#include "swift/AST/SimpleRequest.h"
2930
#include "swift/AST/SourceFile.h"
3031
#include "swift/AST/TypeResolutionStage.h"
@@ -331,9 +332,9 @@ class ProtocolRethrowsRequirementsRequest :
331332
bool isCached() const { return true; }
332333
};
333334

334-
class ProtocolConformanceRefClassifyAsThrowsRequest :
335-
public SimpleRequest<ProtocolConformanceRefClassifyAsThrowsRequest,
336-
bool(ProtocolConformanceRef),
335+
class ProtocolConformanceClassifyAsThrowsRequest :
336+
public SimpleRequest<ProtocolConformanceClassifyAsThrowsRequest,
337+
bool(ProtocolConformance *),
337338
RequestFlags::Cached> {
338339
public:
339340
using SimpleRequest::SimpleRequest;
@@ -343,7 +344,7 @@ class ProtocolConformanceRefClassifyAsThrowsRequest :
343344

344345
// Evaluation.
345346
bool
346-
evaluate(Evaluator &evaluator, ProtocolConformanceRef conformanceRef) const;
347+
evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const;
347348

348349
public:
349350
// Caching.

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest,
270270
SWIFT_REQUEST(TypeChecker, ProtocolRethrowsRequirementsRequest,
271271
ProtocolRethrowsRequirementList(ProtocolDecl *),
272272
Cached, NoLocationInfo)
273-
SWIFT_REQUEST(TypeChecker, ProtocolConformanceRefClassifyAsThrowsRequest,
273+
SWIFT_REQUEST(TypeChecker, ProtocolConformanceClassifyAsThrowsRequest,
274274
bool(ProtocolConformanceRef),
275275
Cached, NoLocationInfo)
276276
SWIFT_REQUEST(TypeChecker, ResolveTypeRequest,

lib/AST/Effects.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,16 @@ using namespace swift;
2828

2929
void swift::simple_display(llvm::raw_ostream &out,
3030
const ProtocolRethrowsRequirementList list) {
31-
for (auto entry : list) {
32-
simple_display(out, entry.first);
33-
simple_display(out, entry.second);
31+
for (auto req : list.getRequirements()) {
32+
simple_display(out, req);
33+
out << "\n";
34+
}
35+
36+
for (auto conf : list.getConformances()) {
37+
simple_display(out, conf.first);
38+
out << " : ";
39+
simple_display(out, conf.second);
40+
llvm::errs() << "\n";
3441
}
3542
}
3643

@@ -75,6 +82,6 @@ void swift::simple_display(llvm::raw_ostream &out,
7582
bool ProtocolConformanceRef::classifyAsThrows() const {
7683
if (!isConcrete()) { return true; }
7784
return evaluateOrDefault(getRequirement()->getASTContext().evaluator,
78-
ProtocolConformanceRefClassifyAsThrowsRequest{ *this },
85+
ProtocolConformanceClassifyAsThrowsRequest{getConcrete()},
7986
true);
8087
}

lib/Sema/TypeCheckEffects.cpp

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -56,44 +56,40 @@ static bool hasThrowingFunctionClosureParameter(CanType type) {
5656
ProtocolRethrowsRequirementList
5757
ProtocolRethrowsRequirementsRequest::evaluate(Evaluator &evaluator,
5858
ProtocolDecl *decl) const {
59-
SmallVector<std::pair<Type, ValueDecl*>, 2> found;
60-
llvm::DenseSet<ProtocolDecl*> checkedProtocols;
61-
6259
ASTContext &ctx = decl->getASTContext();
6360

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

66+
SmallVector<AbstractFunctionDecl *, 2> requirements;
67+
SmallVector<std::pair<Type, ProtocolDecl *>, 2> conformances;
68+
6969
// check if immediate members of protocol are 'throws'
7070
for (auto member : decl->getMembers()) {
7171
auto fnDecl = dyn_cast<AbstractFunctionDecl>(member);
7272
if (!fnDecl || !fnDecl->hasThrows())
7373
continue;
7474

75-
found.push_back(
76-
std::pair<Type, ValueDecl*>(decl->getSelfInterfaceType(), fnDecl));
75+
requirements.push_back(fnDecl);
7776
}
78-
checkedProtocols.insert(decl);
7977

8078
// check associated conformances of associated types or inheritance
8179
for (auto requirement : decl->getRequirementSignature()) {
82-
if (requirement.getKind() != RequirementKind::Conformance) {
80+
if (requirement.getKind() != RequirementKind::Conformance)
8381
continue;
84-
}
82+
8583
auto protoTy = requirement.getSecondType()->castTo<ProtocolType>();
86-
auto proto = protoTy->getDecl();
87-
if (checkedProtocols.count(proto) != 0) {
84+
auto protoDecl = protoTy->getDecl();
85+
if (!protoDecl->isRethrowingProtocol())
8886
continue;
89-
}
90-
checkedProtocols.insert(proto);
91-
for (auto entry : proto->getRethrowingRequirements()) {
92-
found.emplace_back(requirement.getFirstType(), entry.second);
93-
}
87+
88+
conformances.emplace_back(requirement.getFirstType(), protoDecl);
9489
}
9590

96-
return ProtocolRethrowsRequirementList(ctx.AllocateCopy(found));
91+
return ProtocolRethrowsRequirementList(ctx.AllocateCopy(requirements),
92+
ctx.AllocateCopy(conformances));
9793
}
9894

9995
FunctionRethrowingKind
@@ -135,14 +131,14 @@ FunctionRethrowingKindRequest::evaluate(Evaluator &evaluator,
135131
return FunctionRethrowingKind::None;
136132
}
137133

138-
static bool classifyRequirement(ModuleDecl *module,
139-
ProtocolConformance *reqConformance,
140-
ValueDecl *requiredFn) {
141-
auto declRef = reqConformance->getWitnessDeclRef(requiredFn);
134+
static bool classifyWitness(ModuleDecl *module,
135+
ProtocolConformance *conformance,
136+
AbstractFunctionDecl *req) {
137+
auto declRef = conformance->getWitnessDeclRef(req);
142138
auto witnessDecl = cast<AbstractFunctionDecl>(declRef.getDecl());
143139
switch (witnessDecl->getRethrowingKind()) {
144140
case FunctionRethrowingKind::ByConformance: {
145-
auto substitutions = reqConformance->getSubstitutions(module);
141+
auto substitutions = conformance->getSubstitutions(module);
146142
for (auto conformanceRef : substitutions.getConformances()) {
147143
if (conformanceRef.classifyAsThrows()) {
148144
return true;
@@ -160,43 +156,42 @@ static bool classifyRequirement(ModuleDecl *module,
160156
return false;
161157
}
162158

163-
// classify the type requirements of a given protocol type with a function
164-
// requirement as throws or not. This will detect if the signature of the
165-
// function is throwing or not depending on associated types.
166-
static bool classifyTypeRequirement(ModuleDecl *module, Type protoType,
167-
ValueDecl *requiredFn,
168-
ProtocolConformance *conformance,
169-
ProtocolDecl *requiredProtocol) {
170-
auto reqProtocol = cast<ProtocolDecl>(requiredFn->getDeclContext());
171-
ProtocolConformance *reqConformance;
172-
173-
if(protoType->isEqual(reqProtocol->getSelfInterfaceType()) &&
174-
requiredProtocol == reqProtocol) {
175-
reqConformance = conformance;
176-
} else {
177-
auto reqConformanceRef =
178-
conformance->getAssociatedConformance(protoType, reqProtocol);
179-
if (!reqConformanceRef.isConcrete()) {
180-
return true;
159+
bool
160+
ProtocolConformanceClassifyAsThrowsRequest::evaluate(
161+
Evaluator &evaluator, ProtocolConformance *conformance) const {
162+
auto *module = conformance->getDeclContext()->getParentModule();
163+
164+
llvm::SmallDenseSet<ProtocolConformance *, 2> visited;
165+
SmallVector<ProtocolConformance *, 2> worklist;
166+
167+
worklist.push_back(conformance);
168+
169+
while (!worklist.empty()) {
170+
auto *current = worklist.back();
171+
worklist.pop_back();
172+
173+
if (!visited.insert(current).second)
174+
continue;
175+
176+
auto protoDecl = current->getProtocol();
177+
178+
auto list = protoDecl->getRethrowingRequirements();
179+
for (auto req : list.getRequirements()) {
180+
if (classifyWitness(module, current, req))
181+
return true;
181182
}
182-
reqConformance = reqConformanceRef.getConcrete();
183-
}
184183

185-
return classifyRequirement(module, reqConformance, requiredFn);
186-
}
184+
for (auto pair : list.getConformances()) {
185+
auto assocConf =
186+
current->getAssociatedConformance(
187+
pair.first, pair.second);
188+
if (!assocConf.isConcrete())
189+
return true;
187190

188-
bool
189-
ProtocolConformanceRefClassifyAsThrowsRequest::evaluate(
190-
Evaluator &evaluator, ProtocolConformanceRef conformanceRef) const {
191-
auto conformance = conformanceRef.getConcrete();
192-
auto requiredProtocol = conformanceRef.getRequirement();
193-
auto module = requiredProtocol->getModuleContext();
194-
for (auto req : requiredProtocol->getRethrowingRequirements()) {
195-
if (classifyTypeRequirement(module, req.first, req.second,
196-
conformance, requiredProtocol)) {
197-
return true;
191+
worklist.push_back(assocConf.getConcrete());
198192
}
199193
}
194+
200195
return false;
201196
}
202197

test/attr/attr_rethrows_protocol.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,33 @@ func soundnessHole<T : SimpleThrowsClosure>(_ t: T) {
162162
}
163163

164164
// This actually can throw...
165-
soundnessHole(ConformsToSimpleThrowsClosure(t: Throws()))
165+
soundnessHole(ConformsToSimpleThrowsClosure(t: Throws()))
166+
167+
// Test deeply-nested associated conformances
168+
@rethrows protocol First {
169+
associatedtype A : Second
170+
}
171+
172+
@rethrows protocol Second {
173+
associatedtype B : Third
174+
}
175+
176+
@rethrows protocol Third {
177+
func f() throws
178+
}
179+
180+
struct FirstWitness : First {
181+
typealias A = SecondWitness
182+
}
183+
184+
struct SecondWitness : Second {
185+
typealias B = ThirdWitness
186+
}
187+
188+
struct ThirdWitness : Third {
189+
func f() {}
190+
}
191+
192+
func takesFirst<T : First>(_: T) rethrows {}
193+
194+
takesFirst(FirstWitness())

0 commit comments

Comments
 (0)