Skip to content

Commit de16b53

Browse files
committed
[Typed throws] Add compatibility carve-out within rethrows functions
Allow a rethrows function to call a typed-throws function that has a specific form that looks like it only rethrows, i.e., it is generic over its thrown error type and carries the thrown error type from its closure parameters to itself. This is a compatibility carve-out to allow existing rethrows functions to move to typed throws without breaking their clients. Note that it is possible to write a sneaky function that passes this check but throws when it shouldn't, so this compatibility carve-out is phased out with the upcoming feature FullTypedThrows.
1 parent b150656 commit de16b53

File tree

3 files changed

+112
-5
lines changed

3 files changed

+112
-5
lines changed

lib/Sema/TypeCheckEffects.cpp

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,59 @@ static Expr *removeErasureToExistentialError(Expr *expr) {
637637
return expr;
638638
}
639639

640+
/// Determine whether the given function uses typed throws in a manner
641+
/// than is structurally similar to 'rethrows', e.g.,
642+
///
643+
/// \code
644+
/// func map<T, E>(_ body: (Element) throws(E) -> T) throws(E) -> [T]
645+
/// \endcode
646+
static bool isRethrowLikeTypedThrows(AbstractFunctionDecl *func) {
647+
// This notion is only for compatibility in Swift 5 and is disabled
648+
// when FullTypedThrows is enabled.
649+
ASTContext &ctx = func->getASTContext();
650+
if (ctx.LangOpts.hasFeature(Feature::FullTypedThrows))
651+
return false;
652+
653+
// It must have a thrown error type...
654+
auto thrownError = func->getThrownInterfaceType();
655+
if (!thrownError)
656+
return false;
657+
658+
/// ... that is a generic parameter type (call it E)
659+
auto thrownErrorGP = thrownError->getAs<GenericTypeParamType>();
660+
if (!thrownErrorGP)
661+
return false;
662+
663+
/// ... of the generic function.
664+
auto genericParams = func->getGenericParams();
665+
if (!genericParams ||
666+
thrownErrorGP->getDepth() !=
667+
genericParams->getParams().front()->getDepth())
668+
return false;
669+
670+
// E: Error must be the only conformance requirement on the generic parameter.
671+
auto genericSig = func->getGenericSignature();
672+
if (!genericSig)
673+
return false;
674+
675+
auto requiredProtocols = genericSig->getRequiredProtocols(thrownErrorGP);
676+
if (requiredProtocols.size() != 1 ||
677+
requiredProtocols[0]->getKnownProtocolKind() != KnownProtocolKind::Error)
678+
return false;
679+
680+
// Any parameters that are of throwing function type must also throw 'E'.
681+
for (auto param : *func->getParameters()) {
682+
auto paramTy = param->getInterfaceType();
683+
if (auto paramFuncTy = paramTy->getAs<AnyFunctionType>()) {
684+
if (auto paramThrownErrorTy = paramFuncTy->getEffectiveThrownErrorType())
685+
if (!(*paramThrownErrorTy)->isEqual(thrownError))
686+
return false;
687+
}
688+
}
689+
690+
return true;
691+
}
692+
640693
/// A type expressing the result of classifying whether a call or function
641694
/// throws or is async.
642695
class Classification {
@@ -1090,7 +1143,7 @@ class ApplyClassifier {
10901143
}
10911144

10921145
// Handle rethrowing and reasync functions.
1093-
switch (fnRef.getPolymorphicEffectKind(kind)) {
1146+
switch (auto polyKind = fnRef.getPolymorphicEffectKind(kind)) {
10941147
case PolymorphicEffectKind::ByConformance: {
10951148
auto substitutions = fnRef.getSubstitutions();
10961149
for (auto conformanceRef : substitutions.getConformances())
@@ -1101,6 +1154,20 @@ class ApplyClassifier {
11011154
LLVM_FALLTHROUGH;
11021155
}
11031156

1157+
case PolymorphicEffectKind::Always:
1158+
if (polyKind == PolymorphicEffectKind::ByConformance) {
1159+
LLVM_FALLTHROUGH;
1160+
} else if (RethrowsDC &&
1161+
fnRef.getKind() == AbstractFunction::Function &&
1162+
isRethrowLikeTypedThrows(fnRef.getFunction())) {
1163+
// If we are in a rethrowing context and the function we're referring
1164+
// to is a rethrow-like function using typed throws, then look at all
1165+
// of the closure arguments.
1166+
LLVM_FALLTHROUGH;
1167+
} else {
1168+
break;
1169+
}
1170+
11041171
case PolymorphicEffectKind::ByClosure: {
11051172
// We need to walk the original parameter types in parallel
11061173
// because it only counts for rethrows/reasync purposes if it
@@ -1123,8 +1190,9 @@ class ApplyClassifier {
11231190
auto argClassification = classifyArgument(
11241191
args->getExpr(i), params[i].getParameterType(), kind);
11251192

1126-
// Rethrows is untyped, so
1127-
if (kind == EffectKind::Throws) {
1193+
// Rethrows is untyped, so adjust the thrown error type.
1194+
if (kind == EffectKind::Throws &&
1195+
polyKind == PolymorphicEffectKind::ByClosure) {
11281196
argClassification = argClassification.promoteToUntypedThrows();
11291197
}
11301198

@@ -1135,7 +1203,6 @@ class ApplyClassifier {
11351203
}
11361204

11371205
case PolymorphicEffectKind::None:
1138-
case PolymorphicEffectKind::Always:
11391206
case PolymorphicEffectKind::Invalid:
11401207
break;
11411208
}
@@ -1282,8 +1349,15 @@ class ApplyClassifier {
12821349
// within the rethrows context.
12831350
if (!isLocallyDefinedInPolymorphicEffectDeclContext(fn, kind) ||
12841351
!fn->hasBody()) {
1352+
auto conditional = ConditionalEffectKind::Always;
1353+
1354+
// If we are within a rethrows context prior, treat some typed-throws
1355+
// functions as conditionally throwing.
1356+
if (RethrowsDC && isRethrowLikeTypedThrows(fn))
1357+
conditional = ConditionalEffectKind::Conditional;
1358+
12851359
return Classification::forDeclRef(
1286-
ConcreteDeclRef(fn, subs), ConditionalEffectKind::Always, reason)
1360+
ConcreteDeclRef(fn, subs), conditional, reason)
12871361
.onlyEffect(kind);
12881362
}
12891363

test/decl/func/typed_throws.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,28 @@ func rethrowingFunc(body: () throws -> Void) rethrows { }
154154
func typedCallsRethrowingFunc<E>(body: () throws(E) -> Void) throws(E) {
155155
try rethrowingFunc(body: body) // expected-error{{thrown expression type 'any Error' cannot be converted to error type 'E'}}
156156
}
157+
158+
// Compatibility feature: calls from a rethrows function to a rethrows-like
159+
// function using typed throws are permitted.
160+
func rethrowsLike<E>(_ body: () throws(E) -> Void) throws(E) { }
161+
162+
protocol P { }
163+
164+
func notRethrowsLike1<E: P>(_ body: () throws(E) -> Void) throws(E) { }
165+
// expected-note@-1{{required by global function 'notRethrowsLike1' where 'E' = 'any Error'}}
166+
167+
func notRethrowsLike2<E>(_ body: () throws(E) -> Void) throws { }
168+
func notRethrowsLike3<E>(_ body: () throws(E) -> Void, defaulted: () throws -> Void = {}) throws(E) { }
169+
170+
func fromRethrows(body: () throws -> Void) rethrows {
171+
try rethrowsLike(body)
172+
173+
try rethrowsLike(hasThrownMyError) // expected-error{{call can throw, but the error is not handled; a function declared 'rethrows' may only throw if its parameter does}}
174+
// expected-note@-1{{call is to 'rethrows' function, but argument function can throw}}
175+
176+
try notRethrowsLike1(body) // expected-error{{type 'any Error' cannot conform to 'P'}}
177+
// expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}}
178+
179+
try notRethrowsLike2(body) // expected-error{{call can throw, but the error is not handled; a function declared 'rethrows' may only throw if its parameter does}}
180+
try notRethrowsLike3(body) // expected-error{{call can throw, but the error is not handled; a function declared 'rethrows' may only throw if its parameter does}}
181+
}

test/stmt/typed_throws.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,11 @@ func testTryIncompatibleTyped(cond: Bool) throws(HomeworkError) {
138138
throw .forgot
139139
}
140140
}
141+
142+
// "Rethrow-like" functions are only allowed to be called from rethrows
143+
// functions as a compatibility hack, which is removed under FullTypedThrows.
144+
func rethrowsLike<E>(_ body: () throws(E) -> Void) throws(E) { }
145+
146+
func fromRethrows(body: () throws -> Void) rethrows {
147+
try rethrowsLike(body) // expected-error{{call can throw, but the error is not handled; a function declared 'rethrows' may only throw if its parameter does}}
148+
}

0 commit comments

Comments
 (0)