Skip to content

Commit 14608cb

Browse files
committed
[Sema] Skip type-checking catch bodies when computing the bound error type
Make sure we only ever type-check the `do` body of a `do-catch` statement when lazily type-checking the bound error type, which we can do for completion. rdar://164481242
1 parent 8b0853d commit 14608cb

File tree

5 files changed

+84
-22
lines changed

5 files changed

+84
-22
lines changed

lib/Sema/TypeCheckDecl.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2727,6 +2727,18 @@ NamingPatternRequest::evaluate(Evaluator &evaluator, VarDecl *VD) const {
27272727

27282728
if (!namingPattern) {
27292729
if (auto parentStmt = VD->getRecursiveParentPatternStmt()) {
2730+
// We have a parent stmt. This should only ever be the case for completion
2731+
// or lazy type-checking, regular type-checking should go through the
2732+
// StmtChecker and assign types before querying this, otherwise we could
2733+
// end up double-type-checking.
2734+
//
2735+
// FIXME: We ought to be able to enable the following assert once we've
2736+
// fixed cases where we currently allow forward references to variables to
2737+
// kick interface type requests
2738+
// (https://github.com/swiftlang/swift/pull/85141).
2739+
// ASSERT(Context.SourceMgr.hasIDEInspectionTargetBuffer() ||
2740+
// Context.TypeCheckerOpts.EnableLazyTypecheck);
2741+
27302742
// Try type checking parent control statement.
27312743
if (auto condStmt = dyn_cast<LabeledConditionalStmt>(parentStmt)) {
27322744
// The VarDecl is defined inside a condition of a `if` or `while` stmt.
@@ -2752,18 +2764,20 @@ NamingPatternRequest::evaluate(Evaluator &evaluator, VarDecl *VD) const {
27522764
assert(foundVarDecl && "VarDecl not declared in its parent?");
27532765
(void) foundVarDecl;
27542766
} else {
2755-
// We have some other parent stmt. Type check it completely.
2767+
// We have some other statement, e.g a switch or some kind of loop. We
2768+
// need to type-check it to get the type of the bound variable. We
2769+
// generally want to skip type-checking any BraceStmts, the only
2770+
// exception being do-catch bodies since we need to compute the thrown
2771+
// error type for catch clauses.
2772+
// FIXME: Rather than going through `typeCheckASTNode` and trying to
2773+
// exclude type-checker work, we ought to do more granular requests.
2774+
auto braceCheck = BraceStmtChecking::OnlyDoCatchBody;
2775+
27562776
if (auto CS = dyn_cast<CaseStmt>(parentStmt))
27572777
parentStmt = CS->getParentStmt();
27582778

2759-
bool LeaveBodyUnchecked = true;
2760-
// type-checking 'catch' patterns depends on the type checked body.
2761-
if (isa<DoCatchStmt>(parentStmt))
2762-
LeaveBodyUnchecked = false;
2763-
27642779
ASTNode node(parentStmt);
2765-
TypeChecker::typeCheckASTNode(node, VD->getDeclContext(),
2766-
LeaveBodyUnchecked);
2780+
TypeChecker::typeCheckASTNode(node, VD->getDeclContext(), braceCheck);
27672781
}
27682782
namingPattern = VD->getCanonicalVarDecl()->NamingPattern;
27692783
}

lib/Sema/TypeCheckStmt.cpp

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,9 +1041,9 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
10411041
/// DC - This is the current DeclContext.
10421042
DeclContext *DC;
10431043

1044-
/// Skip type checking any elements inside 'BraceStmt', also this is
1045-
/// propagated to ConstraintSystem.
1046-
bool LeaveBraceStmtBodyUnchecked = false;
1044+
/// The BraceStmts that should be type-checked. This should always be `All`,
1045+
/// unless we're lazy type-checking for e.g completion.
1046+
BraceStmtChecking BraceChecking = BraceStmtChecking::All;
10471047

10481048
ASTContext &getASTContext() const { return Ctx; };
10491049

@@ -1502,7 +1502,7 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
15021502
// FIXME: This is a hack to avoid cycles through NamingPatternRequest when
15031503
// doing lazy type-checking, we ought to fix the request to be granular in
15041504
// the type-checking work it kicks.
1505-
bool skipWhere = LeaveBraceStmtBodyUnchecked &&
1505+
bool skipWhere = (BraceChecking != BraceStmtChecking::All) &&
15061506
Ctx.SourceMgr.hasIDEInspectionTargetBuffer();
15071507

15081508
TypeChecker::typeCheckForEachPreamble(DC, S, skipWhere);
@@ -1737,11 +1737,19 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
17371737
auto sourceFile = DC->getParentSourceFile();
17381738
checkLabeledStmtShadowing(getASTContext(), sourceFile, S);
17391739

1740-
// Type-check the 'do' body. Type failures in here will generally
1741-
// not cause type failures in the 'catch' clauses.
1742-
Stmt *newBody = S->getBody();
1743-
typeCheckStmt(newBody);
1744-
S->setBody(newBody);
1740+
{
1741+
// If we have do-catch body checking enabled, make sure we visit the
1742+
// BraceStmt.
1743+
std::optional<llvm::SaveAndRestore<BraceStmtChecking>> braceChecking;
1744+
if (BraceChecking == BraceStmtChecking::OnlyDoCatchBody)
1745+
braceChecking.emplace(BraceChecking, BraceStmtChecking::All);
1746+
1747+
// Type-check the 'do' body. Type failures in here will generally
1748+
// not cause type failures in the 'catch' clauses.
1749+
Stmt *newBody = S->getBody();
1750+
typeCheckStmt(newBody);
1751+
S->setBody(newBody);
1752+
}
17451753

17461754
// Do-catch statements always limit exhaustivity checks.
17471755
bool limitExhaustivityChecks = true;
@@ -2191,7 +2199,7 @@ void StmtChecker::typeCheckASTNode(ASTNode &node) {
21912199
}
21922200

21932201
Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
2194-
if (LeaveBraceStmtBodyUnchecked)
2202+
if (BraceChecking != BraceStmtChecking::All)
21952203
return BS;
21962204

21972205
// Diagnose defer statement being last one in block (only if
@@ -2217,12 +2225,12 @@ Stmt *StmtChecker::visitBraceStmt(BraceStmt *BS) {
22172225
}
22182226

22192227
void TypeChecker::typeCheckASTNode(ASTNode &node, DeclContext *DC,
2220-
bool LeaveBodyUnchecked) {
2228+
BraceStmtChecking braceChecking) {
22212229
StmtChecker stmtChecker(DC);
22222230
// FIXME: 'ActiveLabeledStmts', etc. in StmtChecker are not
22232231
// populated. Since they don't affect "type checking", it's doesn't cause
22242232
// any issue for now. But it should be populated nonetheless.
2225-
stmtChecker.LeaveBraceStmtBodyUnchecked = LeaveBodyUnchecked;
2233+
stmtChecker.BraceChecking = braceChecking;
22262234
stmtChecker.typeCheckASTNode(node);
22272235
}
22282236

@@ -2768,7 +2776,7 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
27682776
}
27692777
}
27702778

2771-
TypeChecker::typeCheckASTNode(finder.getRef(), DC, /*LeaveBodyUnchecked=*/false);
2779+
TypeChecker::typeCheckASTNode(finder.getRef(), DC);
27722780
return false;
27732781
}
27742782

lib/Sema/TypeChecker.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,18 @@ enum class CheckedCastContextKind {
362362
Coercion,
363363
};
364364

365+
/// Determines which BraceStmts should be type-checked.
366+
enum class BraceStmtChecking {
367+
/// Type-check all BraceStmts. This should always be the case for regular
368+
/// type-checking.
369+
All,
370+
371+
/// Only type-check the body of a do-catch statement, not including catch
372+
/// clauses. This is necessary for completion where we still need the caught
373+
/// error type to be computed.
374+
OnlyDoCatchBody,
375+
};
376+
365377
namespace TypeChecker {
366378
// DANGER: callers must verify that elementType satisfies the requirements of
367379
// the Wrapped generic parameter, as this function will not do so!
@@ -525,7 +537,7 @@ bool typeCheckStmtConditionElement(StmtConditionElement &elt, bool &isFalsable,
525537
ConcreteDeclRef getReferencedDeclForHasSymbolCondition(Expr *E);
526538

527539
void typeCheckASTNode(ASTNode &node, DeclContext *DC,
528-
bool LeaveBodyUnchecked = false);
540+
BraceStmtChecking braceChecking = BraceStmtChecking::All);
529541

530542
/// Try to apply the result builder transform of the given builder type
531543
/// to the body of the function.

test/IDE/complete_exception.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,19 @@ func test016() {
189189
i.#^INSIDE_CATCH_TYPEDERR_DOT?check=INT_DOT^#
190190
}
191191
}
192+
193+
// https://github.com/swiftlang/swift/issues/85434
194+
func issue85434() throws {
195+
func foo() throws(Error4) {}
196+
do {
197+
try foo()
198+
} catch let error {
199+
switch error {
200+
case .#^SWITCH_INSIDE_CATCH^#
201+
// SWITCH_INSIDE_CATCH-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: E1[#Error4#]; name=E1
202+
// SWITCH_INSIDE_CATCH-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]; name=E2()
203+
default:
204+
throw error
205+
}
206+
}
207+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %target-swift-ide-test -code-completion -batch-code-completion -skip-filecheck -code-completion-diagnostics -source-filename %s
2+
// https://github.com/swiftlang/swift/issues/85434
3+
protocol MyError: Error {}
4+
func oops() {
5+
do {
6+
} catch let error as any MyError {
7+
switch error.httpStatus {
8+
case .#^COMPLETE^#
9+
default: throw error
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)