Skip to content

Commit 9e68479

Browse files
authored
Merge pull request #69805 from DougGregor/rethrows-to-typed-throws-compatibility
[Typed throws] Add compatibility carve-out within rethrows functions
2 parents b150656 + de16b53 commit 9e68479

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)