Skip to content

Commit 6e577e1

Browse files
authored
Merge pull request swiftlang#35891 from slavapestov/more-rethrows-fixes
More rethrows-via-conformance fixes
2 parents a41fadb + af83f88 commit 6e577e1

File tree

9 files changed

+166
-94
lines changed

9 files changed

+166
-94
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/ProtocolConformance.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,7 @@ inline bool ProtocolConformance::hasWitness(ValueDecl *requirement) const {
10221022
return getRootConformance()->hasWitness(requirement);
10231023
}
10241024

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

10271028
} // end namespace swift

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/AST/ProtocolConformance.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,10 @@ void swift::simple_display(llvm::raw_ostream &out,
15311531
conf->printName(out);
15321532
}
15331533

1534+
SourceLoc swift::extractNearestSourceLoc(const ProtocolConformance *conformance) {
1535+
return extractNearestSourceLoc(conformance->getDeclContext());
1536+
}
1537+
15341538
void swift::simple_display(llvm::raw_ostream &out, ProtocolConformanceRef conformanceRef) {
15351539
if (conformanceRef.isAbstract()) {
15361540
simple_display(out, conformanceRef.getAbstract());
@@ -1543,7 +1547,7 @@ SourceLoc swift::extractNearestSourceLoc(const ProtocolConformanceRef conformanc
15431547
if (conformanceRef.isAbstract()) {
15441548
return extractNearestSourceLoc(conformanceRef.getAbstract());
15451549
} else if (conformanceRef.isConcrete()) {
1546-
return extractNearestSourceLoc(conformanceRef.getConcrete()->getProtocol());
1550+
return extractNearestSourceLoc(conformanceRef.getConcrete());
15471551
}
15481552
return SourceLoc();
15491553
}

lib/Sema/TypeCheckEffects.cpp

Lines changed: 79 additions & 61 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,68 +131,90 @@ 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);
142-
auto witnessDecl = cast<AbstractFunctionDecl>(declRef.getDecl());
134+
static bool classifyWitness(ModuleDecl *module,
135+
ProtocolConformance *conformance,
136+
AbstractFunctionDecl *req) {
137+
auto declRef = conformance->getWitnessDeclRef(req);
138+
if (!declRef) {
139+
// Invalid conformance.
140+
return true;
141+
}
142+
143+
auto witnessDecl = dyn_cast<AbstractFunctionDecl>(declRef.getDecl());
144+
if (!witnessDecl) {
145+
// Enum element constructors never throw.
146+
assert(isa<EnumElementDecl>(declRef.getDecl()));
147+
return false;
148+
}
149+
143150
switch (witnessDecl->getRethrowingKind()) {
151+
case FunctionRethrowingKind::None:
152+
// Witness doesn't throw at all, so it contributes nothing.
153+
return false;
154+
144155
case FunctionRethrowingKind::ByConformance: {
145-
auto substitutions = reqConformance->getSubstitutions(module);
156+
// Witness throws if the concrete type's @rethrows conformances
157+
// recursively throw.
158+
auto substitutions = conformance->getSubstitutions(module);
146159
for (auto conformanceRef : substitutions.getConformances()) {
147160
if (conformanceRef.classifyAsThrows()) {
148161
return true;
149162
}
150163
}
151-
break;
164+
return false;
152165
}
153-
case FunctionRethrowingKind::None:
154-
break;
166+
167+
case FunctionRethrowingKind::ByClosure:
168+
// Witness only throws if a closure argument throws, so it
169+
// contributes nothng.
170+
return false;
171+
155172
case FunctionRethrowingKind::Throws:
173+
// Witness always throws.
156174
return true;
157-
default:
175+
176+
case FunctionRethrowingKind::Invalid:
177+
// If the code is invalid, just assume it throws.
158178
return true;
159179
}
160-
return false;
161180
}
162181

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;
182+
bool
183+
ProtocolConformanceClassifyAsThrowsRequest::evaluate(
184+
Evaluator &evaluator, ProtocolConformance *conformance) const {
185+
auto *module = conformance->getDeclContext()->getParentModule();
186+
187+
llvm::SmallDenseSet<ProtocolConformance *, 2> visited;
188+
SmallVector<ProtocolConformance *, 2> worklist;
189+
190+
worklist.push_back(conformance);
191+
192+
while (!worklist.empty()) {
193+
auto *current = worklist.back();
194+
worklist.pop_back();
195+
196+
if (!visited.insert(current).second)
197+
continue;
198+
199+
auto protoDecl = current->getProtocol();
200+
201+
auto list = protoDecl->getRethrowingRequirements();
202+
for (auto req : list.getRequirements()) {
203+
if (classifyWitness(module, current, req))
204+
return true;
181205
}
182-
reqConformance = reqConformanceRef.getConcrete();
183-
}
184206

185-
return classifyRequirement(module, reqConformance, requiredFn);
186-
}
207+
for (auto pair : list.getConformances()) {
208+
auto assocConf =
209+
current->getAssociatedConformance(
210+
pair.first, pair.second);
211+
if (!assocConf.isConcrete())
212+
return true;
187213

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;
214+
worklist.push_back(assocConf.getConcrete());
198215
}
199216
}
217+
200218
return false;
201219
}
202220

test/attr/attr_rethrows_protocol.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func rethrowsWithRethrowsClosure<T : RethrowsClosure>(_ t: T) rethrows {
129129
try t.doIt() {}
130130
}
131131

132-
try rethrowsWithRethrowsClosure(RethrowsClosureWitness())
132+
rethrowsWithRethrowsClosure(RethrowsClosureWitness())
133133

134134
// Empty protocol
135135
@rethrows protocol Empty {}
@@ -162,4 +162,47 @@ 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())
195+
196+
// Crash with enum case
197+
@rethrows protocol WitnessedByEnumCase {
198+
static func foo(_: Int) throws -> Self
199+
}
200+
201+
enum MyEnum : WitnessedByEnumCase {
202+
case foo(Int)
203+
case bar
204+
}
205+
206+
func takesWitnessedByEnumCase<T : WitnessedByEnumCase>(_: T) rethrows {}
207+
208+
takesWitnessedByEnumCase(MyEnum.bar)

0 commit comments

Comments
 (0)