Skip to content

Commit 54589e1

Browse files
committed
[Typed throws] Type-check thrown expressions against the thrown error type
Rather than always type-check the expression in a `throw` statement for conformance to `Error`, check that it converts to the thrown error type.
1 parent 51eed19 commit 54589e1

File tree

5 files changed

+70
-7
lines changed

5 files changed

+70
-7
lines changed

include/swift/AST/AnyFunctionRef.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,24 @@ class AnyFunctionRef {
124124
return TheFunction.get<AbstractClosureExpr *>()->getType();
125125
}
126126

127+
Type getThrownErrorType() const {
128+
if (auto *AFD = TheFunction.dyn_cast<AbstractFunctionDecl *>()) {
129+
if (Type thrownError = AFD->getThrownInterfaceType())
130+
return AFD->mapTypeIntoContext(thrownError);
131+
132+
return Type();
133+
}
134+
135+
Type closureType = TheFunction.get<AbstractClosureExpr *>()->getType();
136+
if (!closureType)
137+
return Type();
138+
139+
if (auto closureFnType = closureType->getAs<AnyFunctionType>())
140+
return closureFnType->getThrownError();
141+
142+
return Type();
143+
}
144+
127145
Type getBodyResultType() const {
128146
if (auto *AFD = TheFunction.dyn_cast<AbstractFunctionDecl *>()) {
129147
if (auto *FD = dyn_cast<FuncDecl>(AFD))

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,9 @@ ERROR(cannot_convert_to_return_type_nil,none,
336336
"'nil' is incompatible with return type %0", (Type))
337337

338338
ERROR(cannot_convert_thrown_type,none,
339-
"thrown expression type %0 does not conform to 'Error'", (Type))
339+
"thrown expression type %0 %select{cannot be converted to error type %1|"
340+
"does not conform to 'Error'}2",
341+
(Type, Type, bool))
340342
ERROR(cannot_throw_error_code,none,
341343
"thrown error code type %0 does not conform to 'Error'; construct an %1 "
342344
"instance", (Type, Type))

lib/Sema/CSDiagnostics.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3136,15 +3136,24 @@ bool ContextualFailure::diagnoseThrowsTypeMismatch() const {
31363136

31373137
auto anchor = getAnchor();
31383138

3139+
auto &Ctx = getASTContext();
3140+
Type toType = getToType();
3141+
bool toErrorExistential = false;
3142+
if (toType->isEqual(Ctx.getErrorExistentialType()))
3143+
toErrorExistential = true;
3144+
else if (auto protoType = toType->getAs<ProtocolType>()) {
3145+
toErrorExistential = protoType->getDecl()->isSpecificProtocol(
3146+
KnownProtocolKind::Error);
3147+
}
3148+
31393149
// If we tried to throw the error code of an error type, suggest object
31403150
// construction.
3141-
auto &Ctx = getASTContext();
31423151
if (auto errorCodeProtocol =
31433152
Ctx.getProtocol(KnownProtocolKind::ErrorCodeProtocol)) {
31443153
Type errorCodeType = getFromType();
31453154
auto conformance = TypeChecker::conformsToProtocol(
31463155
errorCodeType, errorCodeProtocol, getParentModule());
3147-
if (conformance) {
3156+
if (conformance && toErrorExistential) {
31483157
Type errorType =
31493158
conformance
31503159
.getTypeWitnessByName(errorCodeType, getASTContext().Id_ErrorType)
@@ -3164,8 +3173,10 @@ bool ContextualFailure::diagnoseThrowsTypeMismatch() const {
31643173
// The conversion destination of throw is always ErrorType (at the moment)
31653174
// if this ever expands, this should be a specific form like () is for
31663175
// return.
3167-
emitDiagnostic(diag::cannot_convert_thrown_type, getFromType())
3168-
.highlight(getSourceRange());
3176+
emitDiagnostic(
3177+
diag::cannot_convert_thrown_type, getFromType(), toType,
3178+
toErrorExistential)
3179+
.highlight(getSourceRange());
31693180
return true;
31703181
}
31713182

lib/Sema/TypeCheckStmt.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,10 +1195,20 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
11951195
// Coerce the operand to the exception type.
11961196
auto E = TS->getSubExpr();
11971197

1198-
if (!getASTContext().getErrorDecl())
1198+
1199+
1200+
Type errorType;
1201+
if (auto TheFunc = AnyFunctionRef::fromDeclContext(DC)) {
1202+
errorType = TheFunc->getThrownErrorType();
1203+
}
1204+
1205+
if (!errorType) {
1206+
errorType = getASTContext().getErrorExistentialType();
1207+
}
1208+
1209+
if (!errorType)
11991210
return TS;
12001211

1201-
Type errorType = getASTContext().getErrorExistentialType();
12021212
TypeChecker::typeCheckExpression(E, DC, {errorType, CTP_ThrowStmt});
12031213
TS->setSubExpr(E);
12041214

test/decl/func/typed_throws.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ enum MyError: Error {
44
case fail
55
}
66

7+
enum MyOtherError: Error {
8+
case fail
9+
}
10+
711
enum MyBadError {
812
case epicFail
913
}
@@ -37,4 +41,22 @@ func throwsUnusedInSignature<T: Error>() throws(T) { }
3741
func testSubstitutedType() {
3842
let _: (_: MyError.Type) -> Void = throwsGeneric
3943
// expected-error@-1{{invalid conversion from throwing function of type '(MyError.Type) throws(MyError) -> ()'}}
44+
45+
let _: (_: (any Error).Type) -> Void = throwsGeneric
46+
// expected-error@-1{{invalid conversion from throwing function of type '((any Error).Type) throws((any Error)) -> ()' to non-throwing function type}}
47+
48+
let _: (_: Never.Type) -> Void = throwsGeneric
49+
// FIXME wrong: expected-error@-1{{invalid conversion from throwing function of type '(Never.Type) throws(Never) -> ()'}}
50+
}
51+
52+
53+
func testThrowingInFunction(cond: Bool, cond2: Bool) throws(MyError) {
54+
if cond {
55+
throw MyError.fail
56+
} else if cond2 {
57+
throw .fail
58+
} else {
59+
throw MyBadError.epicFail
60+
// expected-error@-1{{thrown expression type 'MyBadError' cannot be converted to error type 'MyError'}}
61+
}
4062
}

0 commit comments

Comments
 (0)