Skip to content

Commit e7f5ca9

Browse files
committed
[Sema] Better handle recovery for structurally invalid ReturnStmts
Make sure we preserve the result expression for an out-of-place `return`, or a non-`nil` result in an initializer. This ensures we can still provide semantic functionality from them and fixes a crash where we would fail to type-check a binding.
1 parent ea79f67 commit e7f5ca9

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

lib/Sema/TypeCheckStmt.cpp

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,12 +1091,14 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
10911091
auto &eval = getASTContext().evaluator;
10921092
auto *S =
10931093
evaluateOrDefault(eval, PreCheckReturnStmtRequest{RS, DC}, nullptr);
1094+
if (!S)
1095+
return nullptr;
10941096

1095-
// We do a cast here as it may have been turned into a FailStmt. We should
1096-
// return that without doing anything else.
1097-
RS = dyn_cast_or_null<ReturnStmt>(S);
1097+
// We do a cast here as we may now have a different stmt (e.g a FailStmt, or
1098+
// a BraceStmt for error cases). If so, recurse into the visitor.
1099+
RS = dyn_cast<ReturnStmt>(S);
10981100
if (!RS)
1099-
return S;
1101+
return visit(S);
11001102

11011103
auto TheFunc = AnyFunctionRef::fromDeclContext(DC);
11021104
assert(TheFunc && "Should have bailed from pre-check if this is None");
@@ -1785,16 +1787,31 @@ Stmt *PreCheckReturnStmtRequest::evaluate(Evaluator &evaluator, ReturnStmt *RS,
17851787
auto &ctx = DC->getASTContext();
17861788
auto fn = AnyFunctionRef::fromDeclContext(DC);
17871789

1790+
auto errorResult = [&]() -> Stmt * {
1791+
// We don't need any recovery if there's no result, just bail.
1792+
if (!RS->hasResult())
1793+
return nullptr;
1794+
1795+
// If we have a result, make sure it's preserved, insert an implicit brace
1796+
// with a wrapping error expression. This ensures we can still do semantic
1797+
// functionality, and avoids downstream crashes where we expect the
1798+
// expression to have been type-checked.
1799+
auto *result = RS->getResult();
1800+
RS->setResult(nullptr);
1801+
auto *err = new (ctx) ErrorExpr(result->getSourceRange(), Type(), result);
1802+
return BraceStmt::createImplicit(ctx, {err});
1803+
};
1804+
17881805
// Not valid outside of a function.
17891806
if (!fn) {
17901807
ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_invalid_outside_func);
1791-
return nullptr;
1808+
return errorResult();
17921809
}
17931810

17941811
// If the return is in a defer, then it isn't valid either.
17951812
if (isDefer(DC)) {
17961813
ctx.Diags.diagnose(RS->getReturnLoc(), diag::jump_out_of_defer, "return");
1797-
return nullptr;
1814+
return errorResult();
17981815
}
17991816

18001817
// The rest of the checks only concern return statements with results.
@@ -1815,8 +1832,7 @@ Stmt *PreCheckReturnStmtRequest::evaluate(Evaluator &evaluator, ReturnStmt *RS,
18151832
if (!nilExpr) {
18161833
ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_init_non_nil)
18171834
.highlight(E->getSourceRange());
1818-
RS->setResult(nullptr);
1819-
return RS;
1835+
return errorResult();
18201836
}
18211837

18221838
// "return nil" is only permitted in a failable initializer.
@@ -1826,8 +1842,7 @@ Stmt *PreCheckReturnStmtRequest::evaluate(Evaluator &evaluator, ReturnStmt *RS,
18261842
ctx.Diags
18271843
.diagnose(ctor->getLoc(), diag::make_init_failable, ctor)
18281844
.fixItInsertAfter(ctor->getLoc(), "?");
1829-
RS->setResult(nullptr);
1830-
return RS;
1845+
return errorResult();
18311846
}
18321847

18331848
// Replace the "return nil" with a new 'fail' statement.

test/IDE/complete_return.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,17 @@ func testClosures(_ g: Gen) {
132132
return g.IG.#^RETURN_TR3_CLOSURE?check=RETURN_TR3^#
133133
}
134134
}
135+
136+
// Make sure we can do a completion in an out-of-place return
137+
do {
138+
return TestStruct.#^COMPLETE_IN_INVALID_RETURN^#
139+
// COMPLETE_IN_INVALID_RETURN: Decl[StaticMethod]/CurrNominal: testTR1_static()[#Int?#]; name=testTR1_static()
140+
// COMPLETE_IN_INVALID_RETURN: Decl[StaticMethod]/CurrNominal: testTR2_static({#(g): Gen#})[#Int?#]; name=testTR2_static(:)
141+
// COMPLETE_IN_INVALID_RETURN: Decl[StaticMethod]/CurrNominal: testTR3_static({#(g): Gen#})[#Int?#]; name=testTR3_static(:)
142+
}
143+
144+
struct TestReturnInInit {
145+
init() {
146+
return TestStruct.#^COMPLETE_IN_INVALID_INIT_RETURN?check=COMPLETE_IN_INVALID_RETURN^#
147+
}
148+
}

test/stmt/statements.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ return 42 // expected-error {{return invalid outside of a func}}
179179

180180
return // expected-error {{return invalid outside of a func}}
181181

182+
return VoidReturn1() // expected-error {{return invalid outside of a func}}
183+
182184
func NonVoidReturn1() -> Int {
183185
_ = 0
184186
return // expected-error {{non-void function should return a value}}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// {"kind":"typecheck","signature":"swift::constraints::ConstraintSystem::getClosureType(swift::ClosureExpr const*) const","signatureAssert":"Assertion failed: (result), function getClosureType"}
2-
// RUN: not --crash %target-swift-frontend -typecheck %s
2+
// RUN: not %target-swift-frontend -typecheck %s
33
return {
44
lazy var a = if .random() { return }
55
}

0 commit comments

Comments
 (0)