Skip to content

Commit 165b4ba

Browse files
committed
[Sema] Introduce PreCheckReturnStmtRequest
Factor out some type-checking logic for ReturnStmt, including the conversion to FailStmt, into a request. We can then invoke this request from both the regular type-checking path, as well as during a pre-check of an expression.
1 parent e6eec6b commit 165b4ba

File tree

4 files changed

+102
-47
lines changed

4 files changed

+102
-47
lines changed

include/swift/AST/TypeCheckRequests.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class PropertyWrapperInitializerInfo;
5454
struct PropertyWrapperLValueness;
5555
struct PropertyWrapperMutability;
5656
class RequirementRepr;
57+
class ReturnStmt;
5758
class SpecializeAttr;
5859
class TrailingWhereClause;
5960
class TypeAliasDecl;
@@ -3759,6 +3760,24 @@ class ContinueTargetRequest
37593760
bool isCached() const { return true; }
37603761
};
37613762

3763+
/// Precheck a ReturnStmt, which involves some initial validation, as well as
3764+
/// applying a conversion to a FailStmt if needed.
3765+
class PreCheckReturnStmtRequest
3766+
: public SimpleRequest<PreCheckReturnStmtRequest,
3767+
Stmt *(ReturnStmt *, DeclContext *DC),
3768+
RequestFlags::Cached> {
3769+
public:
3770+
using SimpleRequest::SimpleRequest;
3771+
3772+
private:
3773+
friend SimpleRequest;
3774+
3775+
Stmt *evaluate(Evaluator &evaluator, ReturnStmt *RS, DeclContext *DC) const;
3776+
3777+
public:
3778+
bool isCached() const { return true; }
3779+
};
3780+
37623781
class GetTypeWrapperInitializer
37633782
: public SimpleRequest<GetTypeWrapperInitializer,
37643783
ConstructorDecl *(NominalTypeDecl *),

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ SWIFT_REQUEST(TypeChecker, BreakTargetRequest,
443443
SWIFT_REQUEST(TypeChecker, ContinueTargetRequest,
444444
LabeledStmt *(const ContinueStmt *),
445445
Cached, NoLocationInfo)
446+
SWIFT_REQUEST(TypeChecker, PreCheckReturnStmtRequest,
447+
Stmt *(ReturnStmt *, DeclContext *),
448+
Cached, NoLocationInfo)
446449
SWIFT_REQUEST(TypeChecker, GetTypeWrapperInitializer,
447450
ConstructorDecl *(NominalTypeDecl *),
448451
Cached, NoLocationInfo)

lib/Sema/PreCheckExpr.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,17 @@ namespace {
13531353
}
13541354

13551355
PreWalkResult<Stmt *> walkToStmtPre(Stmt *stmt) override {
1356+
if (auto *RS = dyn_cast<ReturnStmt>(stmt)) {
1357+
// Pre-check a return statement, which includes potentially turning it
1358+
// into a FailStmt.
1359+
auto &eval = Ctx.evaluator;
1360+
auto *S = evaluateOrDefault(eval, PreCheckReturnStmtRequest{RS, DC},
1361+
nullptr);
1362+
if (!S)
1363+
return Action::Stop();
1364+
1365+
return Action::Continue(S);
1366+
}
13561367
return Action::Continue(stmt);
13571368
}
13581369

lib/Sema/TypeCheckStmt.cpp

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -757,20 +757,20 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
757757
Stmt *visitBraceStmt(BraceStmt *BS);
758758

759759
Stmt *visitReturnStmt(ReturnStmt *RS) {
760-
auto TheFunc = AnyFunctionRef::fromDeclContext(DC);
760+
// First, let's do a pre-check, and bail if the return is completely
761+
// invalid.
762+
auto &eval = getASTContext().evaluator;
763+
auto *S =
764+
evaluateOrDefault(eval, PreCheckReturnStmtRequest{RS, DC}, nullptr);
765+
766+
// We do a cast here as it may have been turned into a FailStmt. We should
767+
// return that without doing anything else.
768+
RS = dyn_cast_or_null<ReturnStmt>(S);
769+
if (!RS)
770+
return S;
761771

762-
if (!TheFunc.has_value()) {
763-
getASTContext().Diags.diagnose(RS->getReturnLoc(),
764-
diag::return_invalid_outside_func);
765-
return nullptr;
766-
}
767-
768-
// If the return is in a defer, then it isn't valid either.
769-
if (isInDefer()) {
770-
getASTContext().Diags.diagnose(RS->getReturnLoc(),
771-
diag::jump_out_of_defer, "return");
772-
return nullptr;
773-
}
772+
auto TheFunc = AnyFunctionRef::fromDeclContext(DC);
773+
assert(TheFunc && "Should have bailed from pre-check if this is None");
774774

775775
Type ResultTy = TheFunc->getBodyResultType();
776776
if (!ResultTy || ResultTy->hasError())
@@ -808,40 +808,6 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
808808
}
809809

810810
Expr *E = RS->getResult();
811-
812-
// In an initializer, the only expression allowed is "nil", which indicates
813-
// failure from a failable initializer.
814-
if (auto ctor = dyn_cast_or_null<ConstructorDecl>(
815-
TheFunc->getAbstractFunctionDecl())) {
816-
// The only valid return expression in an initializer is the literal
817-
// 'nil'.
818-
auto nilExpr = dyn_cast<NilLiteralExpr>(E->getSemanticsProvidingExpr());
819-
if (!nilExpr) {
820-
getASTContext().Diags.diagnose(RS->getReturnLoc(),
821-
diag::return_init_non_nil)
822-
.highlight(E->getSourceRange());
823-
RS->setResult(nullptr);
824-
return RS;
825-
}
826-
827-
// "return nil" is only permitted in a failable initializer.
828-
if (!ctor->isFailable()) {
829-
getASTContext().Diags.diagnose(RS->getReturnLoc(),
830-
diag::return_non_failable_init)
831-
.highlight(E->getSourceRange());
832-
getASTContext().Diags.diagnose(ctor->getLoc(), diag::make_init_failable,
833-
ctor->getName())
834-
.fixItInsertAfter(ctor->getLoc(), "?");
835-
RS->setResult(nullptr);
836-
return RS;
837-
}
838-
839-
// Replace the "return nil" with a new 'fail' statement.
840-
return new (getASTContext()) FailStmt(RS->getReturnLoc(),
841-
nilExpr->getLoc(),
842-
RS->isImplicit());
843-
}
844-
845811
TypeCheckExprOptions options = {};
846812

847813
if (LeaveBraceStmtBodyUnchecked) {
@@ -1294,6 +1260,62 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
12941260
};
12951261
} // end anonymous namespace
12961262

1263+
Stmt *PreCheckReturnStmtRequest::evaluate(Evaluator &evaluator, ReturnStmt *RS,
1264+
DeclContext *DC) const {
1265+
auto &ctx = DC->getASTContext();
1266+
auto fn = AnyFunctionRef::fromDeclContext(DC);
1267+
1268+
// Not valid outside of a function.
1269+
if (!fn) {
1270+
ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_invalid_outside_func);
1271+
return nullptr;
1272+
}
1273+
1274+
// If the return is in a defer, then it isn't valid either.
1275+
if (isDefer(DC)) {
1276+
ctx.Diags.diagnose(RS->getReturnLoc(), diag::jump_out_of_defer, "return");
1277+
return nullptr;
1278+
}
1279+
1280+
// The rest of the checks only concern return statements with results.
1281+
if (!RS->hasResult())
1282+
return RS;
1283+
1284+
auto *E = RS->getResult();
1285+
1286+
// In an initializer, the only expression allowed is "nil", which indicates
1287+
// failure from a failable initializer.
1288+
if (auto *ctor =
1289+
dyn_cast_or_null<ConstructorDecl>(fn->getAbstractFunctionDecl())) {
1290+
1291+
// The only valid return expression in an initializer is the literal
1292+
// 'nil'.
1293+
auto *nilExpr = dyn_cast<NilLiteralExpr>(E->getSemanticsProvidingExpr());
1294+
if (!nilExpr) {
1295+
ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_init_non_nil)
1296+
.highlight(E->getSourceRange());
1297+
RS->setResult(nullptr);
1298+
return RS;
1299+
}
1300+
1301+
// "return nil" is only permitted in a failable initializer.
1302+
if (!ctor->isFailable()) {
1303+
ctx.Diags.diagnose(RS->getReturnLoc(), diag::return_non_failable_init)
1304+
.highlight(E->getSourceRange());
1305+
ctx.Diags
1306+
.diagnose(ctor->getLoc(), diag::make_init_failable, ctor->getName())
1307+
.fixItInsertAfter(ctor->getLoc(), "?");
1308+
RS->setResult(nullptr);
1309+
return RS;
1310+
}
1311+
1312+
// Replace the "return nil" with a new 'fail' statement.
1313+
return new (ctx)
1314+
FailStmt(RS->getReturnLoc(), nilExpr->getLoc(), RS->isImplicit());
1315+
}
1316+
return RS;
1317+
}
1318+
12971319
static bool isDiscardableType(Type type) {
12981320
return (type->hasError() ||
12991321
type->isUninhabited() ||

0 commit comments

Comments
 (0)