Skip to content

Commit 5380298

Browse files
committed
[Typed throws] Teach witness matching to check typed throws.
1 parent a8ed80d commit 5380298

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "TypeCheckAvailability.h"
2323
#include "TypeCheckConcurrency.h"
2424
#include "TypeCheckDistributed.h"
25+
#include "TypeCheckEffects.h"
2526
#include "TypeCheckObjC.h"
2627
#include "swift/AST/ASTContext.h"
2728
#include "swift/AST/ASTMangler.h"
@@ -589,6 +590,8 @@ RequirementMatch swift::matchWitness(
589590
// Perform basic matching of the requirement and witness.
590591
bool decomposeFunctionType = false;
591592
bool ignoreReturnType = false;
593+
Type reqThrownError;
594+
Type witnessThrownError;
592595
if (isa<FuncDecl>(req) && isa<FuncDecl>(witness)) {
593596
auto funcReq = cast<FuncDecl>(req);
594597
auto funcWitness = cast<FuncDecl>(witness);
@@ -681,12 +684,36 @@ RequirementMatch swift::matchWitness(
681684
MatchKind::MutatingConflict);
682685
}
683686

687+
// Check for async mismatches.
688+
if (!witnessASD->isLessEffectfulThan(reqASD, EffectKind::Async)) {
689+
return RequirementMatch(
690+
getStandinForAccessor(witnessASD, AccessorKind::Get),
691+
MatchKind::AsyncConflict);
692+
}
693+
684694
// Check that the witness has no more effects than the requirement.
685695
if (auto problem = checkEffects(witnessASD, reqASD))
686696
return problem.value();
687697

688698
// Decompose the parameters for subscript declarations.
689699
decomposeFunctionType = isa<SubscriptDecl>(req);
700+
701+
// Dig out the thrown error types from the getter so we can compare them
702+
// later.
703+
auto getThrownErrorType = [](AbstractStorageDecl *asd) -> Type {
704+
if (auto getter = asd->getEffectfulGetAccessor()) {
705+
if (Type thrownErrorType = getter->getThrownInterfaceType()) {
706+
return thrownErrorType;
707+
} else if (getter->hasThrows()) {
708+
return asd->getASTContext().getAnyExistentialType();
709+
}
710+
}
711+
712+
return asd->getASTContext().getNeverType();
713+
};
714+
715+
reqThrownError = getThrownErrorType(reqASD);
716+
witnessThrownError = getThrownErrorType(witnessASD);
690717
} else if (isa<ConstructorDecl>(witness)) {
691718
decomposeFunctionType = true;
692719
ignoreReturnType = true;
@@ -831,12 +858,11 @@ RequirementMatch swift::matchWitness(
831858
return RequirementMatch(witness, MatchKind::AsyncConflict);
832859
}
833860

834-
// If the witness is 'throws', the requirement must be.
835-
// FIXME: We need the same matching we do in the constraint solver,
836-
// with the same fast paths for obvious throws/nonthrows cases.
837-
if (witnessFnType->getExtInfo().isThrowing() &&
838-
!reqFnType->getExtInfo().isThrowing()) {
839-
return RequirementMatch(witness, MatchKind::ThrowsConflict);
861+
if (!reqThrownError) {
862+
// Save the thrown error types of the requirement and witness so we
863+
// can check them later.
864+
reqThrownError = getEffectiveThrownErrorTypeOrNever(reqFnType);
865+
witnessThrownError = getEffectiveThrownErrorTypeOrNever(witnessFnType);
840866
}
841867
}
842868
} else {
@@ -859,6 +885,35 @@ RequirementMatch swift::matchWitness(
859885
}
860886
}
861887

888+
// Check the thrown error types. This includes 'any Error' and 'Never' for
889+
// untyped throws and non-throwing cases as well.
890+
if (reqThrownError && witnessThrownError) {
891+
auto thrownErrorTypes = getTypesToCompare(
892+
req, reqThrownError, false, witnessThrownError, false,
893+
VarianceKind::None);
894+
895+
Type reqThrownError = std::get<0>(thrownErrorTypes);
896+
Type witnessThrownError = std::get<1>(thrownErrorTypes);
897+
switch (compareThrownErrorsForSubtyping(witnessThrownError, reqThrownError,
898+
dc)) {
899+
case ThrownErrorSubtyping::DropsThrows:
900+
case ThrownErrorSubtyping::Mismatch:
901+
return RequirementMatch(witness, MatchKind::ThrowsConflict);
902+
903+
case ThrownErrorSubtyping::ExactMatch:
904+
case ThrownErrorSubtyping::Subtype:
905+
// All is well.
906+
break;
907+
908+
case ThrownErrorSubtyping::Dependent:
909+
// We need to perform type matching
910+
if (auto result = matchTypes(witnessThrownError, reqThrownError)) {
911+
return std::move(result.value());
912+
}
913+
break;
914+
}
915+
}
916+
862917
// Now finalize the match.
863918
auto result = finalize(anyRenaming, optionalAdjustments);
864919
// Check if the requirement's `@differentiable` attributes are satisfied by
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %target-typecheck-verify-swift -parse-as-library -enable-experimental-feature TypedThrows
2+
3+
enum MyError: Error {
4+
case failed
5+
}
6+
7+
enum HomeworkError: Error {
8+
case dogAteIt
9+
}
10+
11+
class SuperError: Error { }
12+
class SubError: SuperError { }
13+
14+
protocol VeryThrowing {
15+
func f() throws
16+
func g() throws(MyError)
17+
func h() throws(HomeworkError) // expected-note{{protocol requires function 'h()' with type '() throws(HomeworkError) -> ()'}}
18+
func i() throws(SuperError)
19+
20+
var prop1: Int { get throws }
21+
var prop2: Int { get throws(MyError) }
22+
var prop3: Int { get throws(HomeworkError) } // expected-note{{protocol requires property 'prop3' with type 'Int'; add a stub for conformance}}
23+
// FIXME: poor diagnostic above
24+
var prop4: Int { get throws(SuperError) }
25+
}
26+
27+
// expected-error@+1{{type 'ConformingToVeryThrowing' does not conform to protocol 'VeryThrowing'}}
28+
struct ConformingToVeryThrowing: VeryThrowing {
29+
func f() throws(MyError) { } // okay to make type more specific
30+
func g() { } // okay to be non-throwing
31+
func h() throws(MyError) { } // expected-note{{candidate throws, but protocol does not allow it}}
32+
// FIXME: Diagnostic above should be better
33+
func i() throws(SubError) { } // okay to have a subtype
34+
35+
var prop1: Int { get throws(MyError) { 0 } }
36+
var prop2: Int { 0 } // okay to be non-throwing
37+
var prop3: Int { get throws(MyError) { 0 } } // expected-note{{candidate throws, but protocol does not allow it}}
38+
// FIXME: Diagnostic above should be better
39+
var prop4: Int { get throws(SubError) { 0 } }
40+
}

0 commit comments

Comments
 (0)