aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2020-06-06 15:16:24 +0100
committergingerBill <bill@gingerbill.org>2020-06-06 15:16:24 +0100
commit59a0bbb38512054eb5c6fc83383c3638ed03d1bf (patch)
treebd7f9e00f72a9b0d1c20e6eddd9251e0012e8070 /src
parenta3fa647bfd579e38337cff173a672159d42f7fd6 (diff)
Improve termination rules checking for missing `return`; Make diverging procedure `-> !` be terminators
Diffstat (limited to 'src')
-rw-r--r--src/check_decl.cpp3
-rw-r--r--src/check_expr.cpp4
-rw-r--r--src/check_stmt.cpp103
-rw-r--r--src/ir.cpp4
-rw-r--r--src/llvm_backend.cpp4
5 files changed, 89 insertions, 29 deletions
diff --git a/src/check_decl.cpp b/src/check_decl.cpp
index 5b343c5cb..88e7be316 100644
--- a/src/check_decl.cpp
+++ b/src/check_decl.cpp
@@ -1,4 +1,3 @@
-bool check_is_terminating(Ast *node);
void check_stmt (CheckerContext *ctx, Ast *node, u32 flags);
// NOTE(bill): 'content_name' is for debugging and error messages
@@ -1257,7 +1256,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls);
if (type->Proc.result_count > 0) {
- if (!check_is_terminating(body)) {
+ if (!check_is_terminating(body, str_lit(""))) {
if (token.kind == Token_Ident) {
error(bs->close, "Missing return statement at the end of the procedure '%.*s'", LIT(token.string));
} else {
diff --git a/src/check_expr.cpp b/src/check_expr.cpp
index acfa40b78..21487c231 100644
--- a/src/check_expr.cpp
+++ b/src/check_expr.cpp
@@ -70,8 +70,8 @@ void check_entity_decl (CheckerContext *c, Entity *e, DeclInfo
void check_const_decl (CheckerContext *c, Entity *e, Ast *type_expr, Ast *init_expr, Type *named_type);
void check_proc_body (CheckerContext *c, Token token, DeclInfo *decl, Type *type, Ast *body);
void update_expr_type (CheckerContext *c, Ast *e, Type *type, bool final);
-bool check_is_terminating (Ast *node);
-bool check_has_break (Ast *stmt, bool implicit);
+bool check_is_terminating (Ast *node, String const &label);
+bool check_has_break (Ast *stmt, String const &label, bool implicit);
void check_stmt (CheckerContext *c, Ast *node, u32 flags);
void check_stmt_list (CheckerContext *c, Array<Ast *> const &stmts, u32 flags);
void check_init_constant (CheckerContext *c, Entity *e, Operand *operand);
diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp
index e357d366d..bfca0ae8e 100644
--- a/src/check_stmt.cpp
+++ b/src/check_stmt.cpp
@@ -1,3 +1,16 @@
+bool is_divigering_stmt(Ast *stmt) {
+ if (stmt->kind != Ast_ExprStmt) {
+ return false;
+ }
+ Ast *expr = unparen_expr(stmt->ExprStmt.expr);
+ if (expr->kind != Ast_CallExpr) {
+ return false;
+ }
+ Type *t = type_of_expr(expr->CallExpr.proc);
+ t = base_type(t);
+ return t->kind == Type_Proc && t->Proc.diverging;
+}
+
void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags) {
if (stmts.count == 0) {
return;
@@ -39,6 +52,8 @@ void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags)
new_flags |= Stmt_FallthroughAllowed;
}
+ check_stmt(ctx, n, new_flags);
+
if (i+1 < max_non_constant_declaration) {
switch (n->kind) {
case Ast_ReturnStmt:
@@ -48,14 +63,18 @@ void check_stmt_list(CheckerContext *ctx, Array<Ast *> const &stmts, u32 flags)
case Ast_BranchStmt:
error(n, "Statements after this '%.*s' are never executed", LIT(n->BranchStmt.token.string));
break;
+
+ case Ast_ExprStmt:
+ if (is_divigering_stmt(n)) {
+ error(n, "Statements after a non-diverging procedure call are never executed");
+ }
+ break;
}
}
-
- check_stmt(ctx, n, new_flags);
}
}
-bool check_is_terminating_list(Array<Ast *> const &stmts) {
+bool check_is_terminating_list(Array<Ast *> const &stmts, String const &label) {
// Iterate backwards
for (isize n = stmts.count-1; n >= 0; n--) {
Ast *stmt = stmts[n];
@@ -63,18 +82,20 @@ bool check_is_terminating_list(Array<Ast *> const &stmts) {
// Okay
} else if (stmt->kind == Ast_ValueDecl && !stmt->ValueDecl.is_mutable) {
// Okay
+ } else if (is_divigering_stmt(stmt)) {
+ return true;
} else {
- return check_is_terminating(stmt);
+ return check_is_terminating(stmt, label);
}
}
return false;
}
-bool check_has_break_list(Array<Ast *> const &stmts, bool implicit) {
+bool check_has_break_list(Array<Ast *> const &stmts, String const &label, bool implicit) {
for_array(i, stmts) {
Ast *stmt = stmts[i];
- if (check_has_break(stmt, implicit)) {
+ if (check_has_break(stmt, label, implicit)) {
return true;
}
}
@@ -82,25 +103,56 @@ bool check_has_break_list(Array<Ast *> const &stmts, bool implicit) {
}
-bool check_has_break(Ast *stmt, bool implicit) {
+bool check_has_break(Ast *stmt, String const &label, bool implicit) {
switch (stmt->kind) {
case Ast_BranchStmt:
if (stmt->BranchStmt.token.kind == Token_break) {
- return implicit;
+ if (stmt->BranchStmt.label == nullptr) {
+ return implicit;
+ }
+ if (stmt->BranchStmt.label->kind == Ast_Ident &&
+ stmt->BranchStmt.label->Ident.token.string == label) {
+ return true;
+ }
}
break;
+
case Ast_BlockStmt:
- return check_has_break_list(stmt->BlockStmt.stmts, implicit);
+ return check_has_break_list(stmt->BlockStmt.stmts, label, implicit);
case Ast_IfStmt:
- if (check_has_break(stmt->IfStmt.body, implicit) ||
- (stmt->IfStmt.else_stmt != nullptr && check_has_break(stmt->IfStmt.else_stmt, implicit))) {
+ if (check_has_break(stmt->IfStmt.body, label, implicit) ||
+ (stmt->IfStmt.else_stmt != nullptr && check_has_break(stmt->IfStmt.else_stmt, label, implicit))) {
return true;
}
break;
case Ast_CaseClause:
- return check_has_break_list(stmt->CaseClause.stmts, implicit);
+ return check_has_break_list(stmt->CaseClause.stmts, label, implicit);
+
+ case Ast_SwitchStmt:
+ if (label != "" && check_has_break(stmt->SwitchStmt.body, label, false)) {
+ return true;
+ }
+ break;
+
+ case Ast_TypeSwitchStmt:
+ if (label != "" && check_has_break(stmt->TypeSwitchStmt.body, label, false)) {
+ return true;
+ }
+ break;
+
+ case Ast_ForStmt:
+ if (label != "" && check_has_break(stmt->ForStmt.body, label, false)) {
+ return true;
+ }
+ break;
+
+ case Ast_RangeStmt:
+ if (label != "" && check_has_break(stmt->RangeStmt.body, label, false)) {
+ return true;
+ }
+ break;
}
return false;
@@ -110,41 +162,42 @@ bool check_has_break(Ast *stmt, bool implicit) {
// NOTE(bill): The last expression has to be a 'return' statement
// TODO(bill): This is a mild hack and should be probably handled properly
-bool check_is_terminating(Ast *node) {
+bool check_is_terminating(Ast *node, String const &label) {
switch (node->kind) {
case_ast_node(rs, ReturnStmt, node);
return true;
case_end;
case_ast_node(bs, BlockStmt, node);
- return check_is_terminating_list(bs->stmts);
+ return check_is_terminating_list(bs->stmts, label);
case_end;
case_ast_node(es, ExprStmt, node);
- return check_is_terminating(es->expr);
+ return check_is_terminating(es->expr, label);
case_end;
case_ast_node(is, IfStmt, node);
if (is->else_stmt != nullptr) {
- if (check_is_terminating(is->body) &&
- check_is_terminating(is->else_stmt)) {
+ if (check_is_terminating(is->body, label) &&
+ check_is_terminating(is->else_stmt, label)) {
return true;
}
}
case_end;
case_ast_node(ws, WhenStmt, node);
+ // TODO(bill): Is this logic correct for when statements?
if (ws->else_stmt != nullptr) {
- if (check_is_terminating(ws->body) &&
- check_is_terminating(ws->else_stmt)) {
+ if (check_is_terminating(ws->body, label) &&
+ check_is_terminating(ws->else_stmt, label)) {
return true;
}
}
case_end;
case_ast_node(fs, ForStmt, node);
- if (fs->cond == nullptr && !check_has_break(fs->body, true)) {
- return check_is_terminating(fs->body);
+ if (fs->cond == nullptr && !check_has_break(fs->body, label, true)) {
+ return true;
}
case_end;
@@ -164,8 +217,8 @@ bool check_is_terminating(Ast *node) {
if (cc->list.count == 0) {
has_default = true;
}
- if (!check_is_terminating_list(cc->stmts) ||
- check_has_break_list(cc->stmts, true)) {
+ if (!check_is_terminating_list(cc->stmts, label) ||
+ check_has_break_list(cc->stmts, label, true)) {
return false;
}
}
@@ -180,8 +233,8 @@ bool check_is_terminating(Ast *node) {
if (cc->list.count == 0) {
has_default = true;
}
- if (!check_is_terminating_list(cc->stmts) ||
- check_has_break_list(cc->stmts, true)) {
+ if (!check_is_terminating_list(cc->stmts, label) ||
+ check_has_break_list(cc->stmts, label, true)) {
return false;
}
}
diff --git a/src/ir.cpp b/src/ir.cpp
index 381c24459..04623f65f 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -3173,6 +3173,10 @@ irValue *ir_emit_call(irProcedure *p, irValue *value, Array<irValue *> const &ar
}
}
+ defer (if (pt->Proc.diverging) {
+ ir_emit_unreachable(p);
+ });
+
irValue *context_ptr = nullptr;
if (pt->Proc.calling_convention == ProcCC_Odin) {
context_ptr = ir_find_or_generate_context_ptr(p);
diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp
index 147afcf46..814f79b1b 100644
--- a/src/llvm_backend.cpp
+++ b/src/llvm_backend.cpp
@@ -6902,6 +6902,10 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args,
context_ptr = lb_find_or_generate_context_ptr(p);
}
+ defer (if (pt->Proc.diverging) {
+ LLVMBuildUnreachable(p->builder);
+ });
+
set_procedure_abi_types(heap_allocator(), pt);
bool is_c_vararg = pt->Proc.c_vararg;