Skip to content

Commit 8ad137f

Browse files
committed
[Typed throws] Infer thrown error type for do..catch blocks within closures.
Start classifying all potential throw sites within a constraint system and associate them with the nearest enclosing catch node. Then, determine the thrown error type for a given catch node by taking the union of the thrown errors at each potential throw site. Use this to compute the error type thrown from the body of a `do..catch` block within a closure. This behavior is limited to the upcoming feature `FullTypedThrows`.
1 parent e1be9c3 commit 8ad137f

File tree

9 files changed

+276
-20
lines changed

9 files changed

+276
-20
lines changed

include/swift/Sema/ConstraintSystem.h

+48
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,31 @@ struct MatchCallArgumentResult {
14401440
}
14411441
};
14421442

1443+
/// Describes a potential throw site in the constraint system.
1444+
///
1445+
/// For example, given `try f() + a[b] + x.y`, each of `f()`, `a[b]`, `x`, and
1446+
/// `x.y` is a potential throw site.
1447+
struct PotentialThrowSite {
1448+
enum Kind {
1449+
/// The application of a function or subscript.
1450+
Application,
1451+
1452+
/// An explicit 'throw'.
1453+
ExplicitThrow,
1454+
1455+
/// A non-exhaustive do...catch, which rethrows whatever is thrown from
1456+
/// inside it's `do` block.
1457+
NonExhaustiveDoCatch,
1458+
} kind;
1459+
1460+
/// The type that describes the potential throw site, such as the type of the
1461+
/// function being called or type being thrown.
1462+
Type type;
1463+
1464+
/// The locator that specifies where the throwing operation occurs.
1465+
ConstraintLocator *locator;
1466+
};
1467+
14431468
/// A complete solution to a constraint system.
14441469
///
14451470
/// A solution to a constraint system consists of type variable bindings to
@@ -1547,6 +1572,13 @@ class Solution {
15471572
llvm::MapVector<const CaseLabelItem *, CaseLabelItemInfo>
15481573
caseLabelItems;
15491574

1575+
/// Maps catch nodes to the set of potential throw sites that will be caught
1576+
/// at that location.
1577+
1578+
/// The set of opened types for a given locator.
1579+
std::vector<std::pair<CatchNode, PotentialThrowSite>>
1580+
potentialThrowSites;
1581+
15501582
/// A map of expressions to the ExprPatterns that they are being solved as
15511583
/// a part of.
15521584
llvm::MapVector<Expr *, ExprPattern *> exprPatterns;
@@ -2210,6 +2242,11 @@ class ConstraintSystem {
22102242
llvm::SmallMapVector<const CaseLabelItem *, CaseLabelItemInfo, 4>
22112243
caseLabelItems;
22122244

2245+
/// Keep track of all of the potential throw sites.
2246+
/// FIXME: This data structure should be replaced with something that
2247+
/// is, in effect, a multimap-vector.
2248+
std::vector<std::pair<CatchNode, PotentialThrowSite>> potentialThrowSites;
2249+
22132250
/// A map of expressions to the ExprPatterns that they are being solved as
22142251
/// a part of.
22152252
llvm::SmallMapVector<Expr *, ExprPattern *, 2> exprPatterns;
@@ -2821,6 +2858,9 @@ class ConstraintSystem {
28212858
/// The length of \c caseLabelItems.
28222859
unsigned numCaseLabelItems;
28232860

2861+
/// The length of \c potentialThrowSites.
2862+
unsigned numPotentialThrowSites;
2863+
28242864
/// The length of \c exprPatterns.
28252865
unsigned numExprPatterns;
28262866

@@ -3293,6 +3333,14 @@ class ConstraintSystem {
32933333
return known->second;
32943334
}
32953335

3336+
/// Note that there is a potential throw site at the given location.
3337+
void recordPotentialThrowSite(
3338+
PotentialThrowSite::Kind kind, Type type,
3339+
ConstraintLocatorBuilder locator);
3340+
3341+
/// Determine the caught error type for the given catch node.
3342+
Type getCaughtErrorType(CatchNode node);
3343+
32963344
/// Retrieve the constraint locator for the given anchor and
32973345
/// path, uniqued.
32983346
ConstraintLocator *

lib/Sema/CSGen.cpp

+8-1
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,13 @@ namespace {
24642464
return explicitType;
24652465
}
24662466

2467+
// Explicitly-specified 'throws' without a type is untyped throws.
2468+
// Use a NULL return here so that the inferred type is written with
2469+
// `throws` instead of `throws(any Error)`, although the two are
2470+
// semantically equivalent.
2471+
if (closure->getThrowsLoc().isValid())
2472+
return Type();
2473+
24672474
// Thrown type inferred from context.
24682475
if (auto contextualType = CS.getContextualType(
24692476
closure, /*forConstraint=*/false)) {
@@ -2474,7 +2481,7 @@ namespace {
24742481
}
24752482

24762483
// We do not try to infer a thrown error type if one isn't immediately
2477-
// available. We could attempt this in the future.
2484+
// available.
24782485
return Type();
24792486
}();
24802487

lib/Sema/CSSimplify.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -12855,6 +12855,10 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyApplicableFnConstraint(
1285512855
// is not valid for operators though, where an inout parameter does not
1285612856
// have an explicit inout argument.
1285712857
if (type1.getPointer() == desugar2) {
12858+
// Note that this could throw.
12859+
recordPotentialThrowSite(
12860+
PotentialThrowSite::Application, Type(desugar2), outerLocator);
12861+
1285812862
if (!isOperator || !hasInOut()) {
1285912863
recordMatchCallArgumentResult(
1286012864
getConstraintLocator(
@@ -12905,6 +12909,10 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyApplicableFnConstraint(
1290512909

1290612910
// For a function, bind the output and convert the argument to the input.
1290712911
if (auto func2 = dyn_cast<FunctionType>(desugar2)) {
12912+
// Note that this could throw.
12913+
recordPotentialThrowSite(
12914+
PotentialThrowSite::Application, Type(desugar2), outerLocator);
12915+
1290812916
ConstraintKind subKind = (isOperator
1290912917
? ConstraintKind::OperatorArgumentConversion
1291012918
: ConstraintKind::ArgumentConversion);

lib/Sema/CSSolver.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ Solution ConstraintSystem::finalize() {
206206
for (const auto &item : caseLabelItems)
207207
solution.caseLabelItems.insert(item);
208208

209+
for (const auto &throwSite : potentialThrowSites)
210+
solution.potentialThrowSites.push_back(throwSite);
211+
209212
for (const auto &pattern : exprPatterns)
210213
solution.exprPatterns.insert(pattern);
211214

@@ -350,6 +353,10 @@ void ConstraintSystem::applySolution(const Solution &solution) {
350353
setCaseLabelItemInfo(info.first, info.second);
351354
}
352355

356+
potentialThrowSites.insert(potentialThrowSites.end(),
357+
solution.potentialThrowSites.begin(),
358+
solution.potentialThrowSites.end());
359+
353360
for (auto param : solution.isolatedParams) {
354361
isolatedParams.insert(param);
355362
}
@@ -653,6 +660,7 @@ ConstraintSystem::SolverScope::SolverScope(ConstraintSystem &cs)
653660
numContextualTypes = cs.contextualTypes.size();
654661
numTargets = cs.targets.size();
655662
numCaseLabelItems = cs.caseLabelItems.size();
663+
numPotentialThrowSites = cs.potentialThrowSites.size();
656664
numExprPatterns = cs.exprPatterns.size();
657665
numIsolatedParams = cs.isolatedParams.size();
658666
numPreconcurrencyClosures = cs.preconcurrencyClosures.size();
@@ -776,6 +784,9 @@ ConstraintSystem::SolverScope::~SolverScope() {
776784
// Remove any case label item infos.
777785
truncate(cs.caseLabelItems, numCaseLabelItems);
778786

787+
// Remove any potential throw sites.
788+
truncate(cs.potentialThrowSites, numPotentialThrowSites);
789+
779790
// Remove any ExprPattern mappings.
780791
truncate(cs.exprPatterns, numExprPatterns);
781792

lib/Sema/CSSyntacticElement.cpp

+20-18
Original file line numberDiff line numberDiff line change
@@ -921,21 +921,11 @@ class SyntacticElementConstraintGenerator
921921
void visitThrowStmt(ThrowStmt *throwStmt) {
922922
// Look up the catch node for this "throw" to determine the error type.
923923
auto dc = context.getAsDeclContext();
924-
CatchNode catchNode = ASTScope::lookupCatchNode(
925-
dc->getParentModule(), throwStmt->getThrowLoc());
924+
auto module = dc->getParentModule();
925+
auto throwLoc = throwStmt->getThrowLoc();
926926
Type errorType;
927-
if (catchNode) {
928-
// FIXME: Introduce something like getThrownErrorTypeInContext() for the
929-
// constraint solver.
930-
if (auto closure = catchNode.dyn_cast<ClosureExpr *>()) {
931-
errorType = cs.getClosureType(closure)->getThrownError();
932-
}
933-
934-
if (!errorType) {
935-
ASTContext &ctx = cs.getASTContext();
936-
errorType = catchNode.getThrownErrorTypeInContext(ctx).value_or(Type());
937-
}
938-
}
927+
if (auto catchNode = ASTScope::lookupCatchNode(module, throwLoc))
928+
errorType = catchNode.getExplicitCaughtType(cs.getASTContext());
939929

940930
if (!errorType) {
941931
if (!cs.getASTContext().getErrorDecl()) {
@@ -1070,10 +1060,15 @@ class SyntacticElementConstraintGenerator
10701060
contextualTy = cs.getType(switchStmt->getSubjectExpr());
10711061
} else if (auto doCatch =
10721062
dyn_cast_or_null<DoCatchStmt>(parent.dyn_cast<Stmt *>())) {
1073-
auto dc = context.getAsDeclContext();
1074-
contextualTy = doCatch->getExplicitCaughtType();
1075-
if (!contextualTy)
1076-
contextualTy = cs.getASTContext().getErrorExistentialType();
1063+
contextualTy = cs.getCaughtErrorType(doCatch);
1064+
1065+
// A non-exhaustive do..catch statement is a potential throw site.
1066+
if (caseStmt == doCatch->getCatches().back() &&
1067+
!doCatch->isSyntacticallyExhaustive()) {
1068+
cs.recordPotentialThrowSite(
1069+
PotentialThrowSite::NonExhaustiveDoCatch, contextualTy,
1070+
cs.getConstraintLocator(doCatch));
1071+
}
10771072
} else {
10781073
hadError = true;
10791074
return;
@@ -1639,6 +1634,13 @@ ConstraintSystem::simplifySyntacticElementConstraint(
16391634
if (generateConstraints(target))
16401635
return SolutionKind::Error;
16411636

1637+
// If this expression is the operand of a `throw` statement, record it as
1638+
// a potential throw site.
1639+
if (contextInfo.purpose == CTP_ThrowStmt) {
1640+
recordPotentialThrowSite(PotentialThrowSite::ExplicitThrow,
1641+
getType(expr), getConstraintLocator(expr));
1642+
}
1643+
16421644
setTargetFor(expr, target);
16431645
return SolutionKind::Solved;
16441646
} else if (auto *stmt = element.dyn_cast<Stmt *>()) {

lib/Sema/ConstraintSystem.cpp

+108
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,114 @@ bool ConstraintSystem::containsIDEInspectionTarget(
368368
Context.SourceMgr);
369369
}
370370

371+
void ConstraintSystem::recordPotentialThrowSite(
372+
PotentialThrowSite::Kind kind, Type type,
373+
ConstraintLocatorBuilder locator) {
374+
ASTContext &ctx = getASTContext();
375+
376+
// Only record potential throw sites when typed throws is enabled.
377+
if (!ctx.LangOpts.hasFeature(Feature::TypedThrows))
378+
return;
379+
380+
// Catch node location is determined by the source location.
381+
auto sourceLoc = locator.getAnchor().getStartLoc();
382+
if (!sourceLoc)
383+
return;
384+
385+
auto catchNode = ASTScope::lookupCatchNode(DC->getParentModule(), sourceLoc);
386+
if (!catchNode)
387+
return;
388+
389+
// If there is an explicit caught type for this node, we don't need to
390+
// record a potential throw site.
391+
if (Type explicitCaughtType = catchNode.getExplicitCaughtType(ctx))
392+
return;
393+
394+
// do..catch statements without an explicit `throws` clause do infer
395+
// thrown types.
396+
if (auto doCatch = catchNode.dyn_cast<DoCatchStmt *>()) {
397+
potentialThrowSites.push_back(
398+
{catchNode,
399+
PotentialThrowSite{kind, type, getConstraintLocator(locator)}});
400+
return;
401+
}
402+
403+
// Closures without an explicit `throws` clause, and which syntactically
404+
// appear that they can throw, do infer thrown types.
405+
auto closure = catchNode.get<ClosureExpr *>();
406+
407+
// Check whether the closure syntactically throws. If not, there is no
408+
// need to record a throw site.
409+
if (!closureEffects(closure).isThrowing())
410+
return;
411+
412+
potentialThrowSites.push_back(
413+
{catchNode,
414+
PotentialThrowSite{kind, type, getConstraintLocator(locator)}});
415+
}
416+
417+
Type ConstraintSystem::getCaughtErrorType(CatchNode catchNode) {
418+
ASTContext &ctx = getASTContext();
419+
420+
// If there is an explicit caught type for this node, use it.
421+
if (Type explicitCaughtType = catchNode.getExplicitCaughtType(ctx)) {
422+
if (explicitCaughtType->hasTypeParameter())
423+
explicitCaughtType = DC->mapTypeIntoContext(explicitCaughtType);
424+
425+
return explicitCaughtType;
426+
}
427+
428+
// Retrieve the thrown error type of a closure.
429+
// FIXME: This will need to change when we do inference of thrown error
430+
// types in closures.
431+
if (auto closure = catchNode.dyn_cast<ClosureExpr *>()) {
432+
return getClosureType(closure)->getEffectiveThrownErrorTypeOrNever();
433+
}
434+
435+
// Handle inference of caught error types.
436+
437+
// Collect all of the potential throw sites for this catch node.
438+
SmallVector<PotentialThrowSite, 2> throwSites;
439+
for (const auto &potentialThrowSite : potentialThrowSites) {
440+
if (potentialThrowSite.first == catchNode) {
441+
throwSites.push_back(potentialThrowSite.second);
442+
}
443+
}
444+
445+
Type caughtErrorType = ctx.getNeverType();
446+
for (const auto &throwSite : throwSites) {
447+
Type type = simplifyType(throwSite.type);
448+
449+
Type thrownErrorType;
450+
switch (throwSite.kind) {
451+
case PotentialThrowSite::Application: {
452+
auto fnType = type->castTo<AnyFunctionType>();
453+
thrownErrorType = fnType->getEffectiveThrownErrorTypeOrNever();
454+
break;
455+
}
456+
457+
case PotentialThrowSite::ExplicitThrow:
458+
case PotentialThrowSite::NonExhaustiveDoCatch:
459+
thrownErrorType = type;
460+
break;
461+
}
462+
463+
// Perform the errorUnion() of the caught error type so far with the
464+
// thrown error type of this potential throw site.
465+
caughtErrorType = TypeChecker::errorUnion(
466+
caughtErrorType, thrownErrorType,
467+
[&](Type type) {
468+
return simplifyType(type);
469+
});
470+
471+
// If we ended up at 'any Error', we're done.
472+
if (caughtErrorType->isErrorExistentialType())
473+
break;
474+
}
475+
476+
return caughtErrorType;
477+
}
478+
371479
ConstraintLocator *ConstraintSystem::getConstraintLocator(
372480
ASTNode anchor, ArrayRef<ConstraintLocator::PathElement> path) {
373481
auto summaryFlags = ConstraintLocator::getSummaryFlagsForPath(path);

lib/Sema/TypeCheckConstraints.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,30 @@ void ConstraintSystem::print(raw_ostream &out) const {
16551655
out << "\n";
16561656
}
16571657
}
1658+
1659+
if (!potentialThrowSites.empty()) {
1660+
out.indent(indent) << "Potential throw sites:\n";
1661+
interleave(potentialThrowSites, [&](const auto &throwSite) {
1662+
out.indent(indent + 2);
1663+
switch (throwSite.second.kind) {
1664+
case PotentialThrowSite::Application:
1665+
out << "- application @ ";
1666+
break;
1667+
case PotentialThrowSite::ExplicitThrow:
1668+
out << " - explicit throw @ ";
1669+
break;
1670+
case PotentialThrowSite::NonExhaustiveDoCatch:
1671+
out << " - non-exhaustive do..catch @ ";
1672+
break;
1673+
}
1674+
1675+
throwSite.second.locator->dump(&getASTContext().SourceMgr, out);
1676+
}, [&] {
1677+
out << "\n";
1678+
});
1679+
out << "\n";
1680+
1681+
}
16581682
}
16591683

16601684
/// Determine the semantics of a checked cast operation.

test/expr/closure/typed_throws.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ func testClosures() {
1717
// expected-error@-1{{invalid conversion from throwing function of type '() throws(MyError) -> ()'}}
1818

1919
let _: () throws(MyError) -> Void = {
20-
throw .fail
20+
// NOTE: under full typed throws, this should succeed
21+
throw MyError.fail // expected-error{{thrown expression type 'any Error' cannot be converted to error type 'MyError'}}
22+
}
23+
24+
let _: () throws(MyError) -> Void = {
25+
throw .fail // expected-error{{type 'any Error' has no member 'fail'}}
2126
}
2227

2328
// FIXME: Terrible diagnostic.

0 commit comments

Comments
 (0)