diff --git a/include/swift/AST/Effects.h b/include/swift/AST/Effects.h index 3595141513143..f5bec45b635d7 100644 --- a/include/swift/AST/Effects.h +++ b/include/swift/AST/Effects.h @@ -30,27 +30,25 @@ namespace swift { class ValueDecl; class ProtocolRethrowsRequirementList { -public: - typedef std::pair Entry; - + using ThrowingRequirements = ArrayRef; + using ThrowingConformances = ArrayRef>; private: - ArrayRef entries; + ThrowingRequirements requirements; + ThrowingConformances conformances; public: - ProtocolRethrowsRequirementList(ArrayRef 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); diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 1f61cdf438175..1cdab3aefeed9 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -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 diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index c94763fd16992..8594312e0a3d2 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -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" @@ -331,9 +332,9 @@ class ProtocolRethrowsRequirementsRequest : bool isCached() const { return true; } }; -class ProtocolConformanceRefClassifyAsThrowsRequest : - public SimpleRequest { public: using SimpleRequest::SimpleRequest; @@ -343,7 +344,7 @@ class ProtocolConformanceRefClassifyAsThrowsRequest : // Evaluation. bool - evaluate(Evaluator &evaluator, ProtocolConformanceRef conformanceRef) const; + evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const; public: // Caching. diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 54c8bace7c24f..324d0f6b590bf 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -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, diff --git a/lib/AST/Effects.cpp b/lib/AST/Effects.cpp index 7f9f6224bb9e0..895d1b9f12ff5 100644 --- a/lib/AST/Effects.cpp +++ b/lib/AST/Effects.cpp @@ -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"; } } @@ -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); } \ No newline at end of file diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 108880cc28293..c0355b5b2bbb0 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -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()); @@ -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(); } diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index b48a2a1d2e74f..f784699a09b0c 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -56,44 +56,40 @@ static bool hasThrowingFunctionClosureParameter(CanType type) { ProtocolRethrowsRequirementList ProtocolRethrowsRequirementsRequest::evaluate(Evaluator &evaluator, ProtocolDecl *decl) const { - SmallVector, 2> found; - llvm::DenseSet checkedProtocols; - ASTContext &ctx = decl->getASTContext(); // only allow rethrowing requirements to be determined from marked protocols - if (!decl->getAttrs().hasAttribute()) { - return ProtocolRethrowsRequirementList(ctx.AllocateCopy(found)); + if (!decl->isRethrowingProtocol()) { + return ProtocolRethrowsRequirementList(); } + SmallVector requirements; + SmallVector, 2> conformances; + // check if immediate members of protocol are 'throws' for (auto member : decl->getMembers()) { auto fnDecl = dyn_cast(member); if (!fnDecl || !fnDecl->hasThrows()) continue; - found.push_back( - std::pair(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(); - 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 @@ -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(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(declRef.getDecl()); + if (!witnessDecl) { + // Enum element constructors never throw. + assert(isa(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(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 visited; + SmallVector 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; } diff --git a/test/attr/attr_rethrows_protocol.swift b/test/attr/attr_rethrows_protocol.swift index 793d5336a1927..80d32fb94c60c 100644 --- a/test/attr/attr_rethrows_protocol.swift +++ b/test/attr/attr_rethrows_protocol.swift @@ -129,7 +129,7 @@ func rethrowsWithRethrowsClosure(_ t: T) rethrows { try t.doIt() {} } -try rethrowsWithRethrowsClosure(RethrowsClosureWitness()) +rethrowsWithRethrowsClosure(RethrowsClosureWitness()) // Empty protocol @rethrows protocol Empty {} @@ -162,4 +162,47 @@ func soundnessHole(_ t: T) { } // This actually can throw... -soundnessHole(ConformsToSimpleThrowsClosure(t: Throws())) \ No newline at end of file +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) 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) rethrows {} + +takesWitnessedByEnumCase(MyEnum.bar) \ No newline at end of file diff --git a/test/decl/circularity.swift b/test/decl/circularity.swift index 61d1272f843db..7ecbf7271982c 100644 --- a/test/decl/circularity.swift +++ b/test/decl/circularity.swift @@ -61,7 +61,6 @@ extension SIMD3 where SIMD3.Scalar == Float { protocol P { associatedtype A // expected-note@-1 {{protocol requires nested type 'A'; do you want to add it?}} - // expected-note@-2 {{through reference here}} func run(a: A) } @@ -70,6 +69,7 @@ class C1 { } class C2: C1, P { + // expected-note@-1 {{through reference here}} override func run(a: A) {} // expected-error@-1 {{circular reference}} // expected-note@-2 {{while resolving type 'A'}} @@ -90,7 +90,7 @@ class C3: G1, P { // Another case that triggers circular override checking. protocol P1 { - associatedtype X = Int // expected-note {{through reference here}} + associatedtype X = Int init(x: X) } @@ -98,7 +98,7 @@ class C4 { required init(x: Int) {} } -class D4 : C4, P1 { // expected-note 2 {{through reference here}} +class D4 : C4, P1 { // expected-note 3 {{through reference here}} required init(x: X) { // expected-error {{circular reference}} // expected-note@-1 {{while resolving type 'X'}} // expected-note@-2 2{{through reference here}}