|
@@ -1912,337 +1912,354 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
|
|
- u32 mod_flags = flags & (~Stmt_FallthroughAllowed);
|
|
|
- switch (node->kind) {
|
|
|
- case_ast_node(_, EmptyStmt, node); case_end;
|
|
|
- case_ast_node(_, BadStmt, node); case_end;
|
|
|
- case_ast_node(_, BadDecl, node); case_end;
|
|
|
+gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) {
|
|
|
+ ast_node(es, ExprStmt, node);
|
|
|
|
|
|
- case_ast_node(es, ExprStmt, node)
|
|
|
- Operand operand = {Addressing_Invalid};
|
|
|
- ExprKind kind = check_expr_base(ctx, &operand, es->expr, nullptr);
|
|
|
- switch (operand.mode) {
|
|
|
- case Addressing_Type: {
|
|
|
+ Operand operand = {Addressing_Invalid};
|
|
|
+ ExprKind kind = check_expr_base(ctx, &operand, es->expr, nullptr);
|
|
|
+ switch (operand.mode) {
|
|
|
+ case Addressing_Type:
|
|
|
+ {
|
|
|
gbString str = type_to_string(operand.type);
|
|
|
error(node, "'%s' is not an expression", str);
|
|
|
gb_string_free(str);
|
|
|
-
|
|
|
break;
|
|
|
}
|
|
|
- case Addressing_NoValue:
|
|
|
- return;
|
|
|
- default: {
|
|
|
- if (kind == Expr_Stmt) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ case Addressing_NoValue:
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (kind == Expr_Stmt) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- Ast *expr = strip_or_return_expr(operand.expr);
|
|
|
- if (expr->kind == Ast_CallExpr) {
|
|
|
- BuiltinProcId builtin_id = BuiltinProc_Invalid;
|
|
|
- bool do_require = false;
|
|
|
+ Ast *expr = strip_or_return_expr(operand.expr);
|
|
|
+ if (expr->kind == Ast_CallExpr) {
|
|
|
+ BuiltinProcId builtin_id = BuiltinProc_Invalid;
|
|
|
+ bool do_require = false;
|
|
|
|
|
|
- AstCallExpr *ce = &expr->CallExpr;
|
|
|
- Type *t = base_type(type_of_expr(ce->proc));
|
|
|
- if (t->kind == Type_Proc) {
|
|
|
- do_require = t->Proc.require_results;
|
|
|
- } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) {
|
|
|
- auto const &bp = builtin_procs[builtin_id];
|
|
|
- do_require = bp.kind == Expr_Expr && !bp.ignore_results;
|
|
|
- }
|
|
|
- if (do_require) {
|
|
|
- gbString expr_str = expr_to_string(ce->proc);
|
|
|
- error(node, "'%s' requires that its results must be handled", expr_str);
|
|
|
- gb_string_free(expr_str);
|
|
|
- }
|
|
|
- return;
|
|
|
- } else if (expr->kind == Ast_SelectorCallExpr) {
|
|
|
- BuiltinProcId builtin_id = BuiltinProc_Invalid;
|
|
|
- bool do_require = false;
|
|
|
-
|
|
|
- AstSelectorCallExpr *se = &expr->SelectorCallExpr;
|
|
|
- ast_node(ce, CallExpr, se->call);
|
|
|
- Type *t = base_type(type_of_expr(ce->proc));
|
|
|
- if (t == nullptr) {
|
|
|
- gbString expr_str = expr_to_string(ce->proc);
|
|
|
- error(node, "'%s' is not a value field nor procedure", expr_str);
|
|
|
- gb_string_free(expr_str);
|
|
|
- return;
|
|
|
- }
|
|
|
- if (t->kind == Type_Proc) {
|
|
|
- do_require = t->Proc.require_results;
|
|
|
- } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) {
|
|
|
- auto const &bp = builtin_procs[builtin_id];
|
|
|
- do_require = bp.kind == Expr_Expr && !bp.ignore_results;
|
|
|
- }
|
|
|
- if (do_require) {
|
|
|
- gbString expr_str = expr_to_string(ce->proc);
|
|
|
- error(node, "'%s' requires that its results must be handled", expr_str);
|
|
|
- gb_string_free(expr_str);
|
|
|
- }
|
|
|
- return;
|
|
|
- }
|
|
|
- gbString expr_str = expr_to_string(operand.expr);
|
|
|
- error(node, "Expression is not used: '%s'", expr_str);
|
|
|
+ AstCallExpr *ce = &expr->CallExpr;
|
|
|
+ Type *t = base_type(type_of_expr(ce->proc));
|
|
|
+ if (t->kind == Type_Proc) {
|
|
|
+ do_require = t->Proc.require_results;
|
|
|
+ } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) {
|
|
|
+ auto const &bp = builtin_procs[builtin_id];
|
|
|
+ do_require = bp.kind == Expr_Expr && !bp.ignore_results;
|
|
|
+ }
|
|
|
+ if (do_require) {
|
|
|
+ gbString expr_str = expr_to_string(ce->proc);
|
|
|
+ error(node, "'%s' requires that its results must be handled", expr_str);
|
|
|
gb_string_free(expr_str);
|
|
|
- if (operand.expr->kind == Ast_BinaryExpr) {
|
|
|
- ast_node(be, BinaryExpr, operand.expr);
|
|
|
- if (be->op.kind != Token_CmpEq) {
|
|
|
- break;
|
|
|
- }
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ } else if (expr->kind == Ast_SelectorCallExpr) {
|
|
|
+ BuiltinProcId builtin_id = BuiltinProc_Invalid;
|
|
|
+ bool do_require = false;
|
|
|
+
|
|
|
+ AstSelectorCallExpr *se = &expr->SelectorCallExpr;
|
|
|
+ ast_node(ce, CallExpr, se->call);
|
|
|
+ Type *t = base_type(type_of_expr(ce->proc));
|
|
|
+ if (t == nullptr) {
|
|
|
+ gbString expr_str = expr_to_string(ce->proc);
|
|
|
+ error(node, "'%s' is not a value field nor procedure", expr_str);
|
|
|
+ gb_string_free(expr_str);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (t->kind == Type_Proc) {
|
|
|
+ do_require = t->Proc.require_results;
|
|
|
+ } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) {
|
|
|
+ auto const &bp = builtin_procs[builtin_id];
|
|
|
+ do_require = bp.kind == Expr_Expr && !bp.ignore_results;
|
|
|
+ }
|
|
|
+ if (do_require) {
|
|
|
+ gbString expr_str = expr_to_string(ce->proc);
|
|
|
+ error(node, "'%s' requires that its results must be handled", expr_str);
|
|
|
+ gb_string_free(expr_str);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ gbString expr_str = expr_to_string(operand.expr);
|
|
|
+ error(node, "Expression is not used: '%s'", expr_str);
|
|
|
+ gb_string_free(expr_str);
|
|
|
+ if (operand.expr->kind == Ast_BinaryExpr) {
|
|
|
+ ast_node(be, BinaryExpr, operand.expr);
|
|
|
+ if (be->op.kind != Token_CmpEq) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- switch (be->left->tav.mode) {
|
|
|
- case Addressing_Context:
|
|
|
- case Addressing_Variable:
|
|
|
- case Addressing_MapIndex:
|
|
|
- case Addressing_SoaVariable:
|
|
|
- {
|
|
|
- gbString lhs = expr_to_string(be->left);
|
|
|
- gbString rhs = expr_to_string(be->right);
|
|
|
- error_line("\tSuggestion: Did you mean to do an assignment?\n", lhs, rhs);
|
|
|
- error_line("\t '%s = %s;'\n", lhs, rhs);
|
|
|
- gb_string_free(rhs);
|
|
|
- gb_string_free(lhs);
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
+ switch (be->left->tav.mode) {
|
|
|
+ case Addressing_Context:
|
|
|
+ case Addressing_Variable:
|
|
|
+ case Addressing_MapIndex:
|
|
|
+ case Addressing_SoaVariable:
|
|
|
+ {
|
|
|
+ gbString lhs = expr_to_string(be->left);
|
|
|
+ gbString rhs = expr_to_string(be->right);
|
|
|
+ error_line("\tSuggestion: Did you mean to do an assignment?\n", lhs, rhs);
|
|
|
+ error_line("\t '%s = %s;'\n", lhs, rhs);
|
|
|
+ gb_string_free(rhs);
|
|
|
+ gb_string_free(lhs);
|
|
|
}
|
|
|
-
|
|
|
break;
|
|
|
}
|
|
|
- }
|
|
|
- case_end;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- case_ast_node(as, AssignStmt, node);
|
|
|
- switch (as->op.kind) {
|
|
|
- case Token_Eq: {
|
|
|
- // a, b, c = 1, 2, 3; // Multisided
|
|
|
+gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
|
|
|
+ ast_node(as, AssignStmt, node);
|
|
|
|
|
|
- isize lhs_count = as->lhs.count;
|
|
|
- if (lhs_count == 0) {
|
|
|
- error(as->op, "Missing lhs in assignment statement");
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (as->op.kind == Token_Eq) {
|
|
|
+ // a, b, c = 1, 2, 3; // Multisided
|
|
|
|
|
|
- TEMPORARY_ALLOCATOR_GUARD();
|
|
|
+ isize lhs_count = as->lhs.count;
|
|
|
+ if (lhs_count == 0) {
|
|
|
+ error(as->op, "Missing lhs in assignment statement");
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // NOTE(bill): If there is a bad syntax error, rhs > lhs which would mean there would need to be
|
|
|
- // an extra allocation
|
|
|
- auto lhs_operands = array_make<Operand>(temporary_allocator(), lhs_count);
|
|
|
- auto rhs_operands = array_make<Operand>(temporary_allocator(), 0, 2*lhs_count);
|
|
|
+ TEMPORARY_ALLOCATOR_GUARD();
|
|
|
|
|
|
- for_array(i, as->lhs) {
|
|
|
- if (is_blank_ident(as->lhs[i])) {
|
|
|
- Operand *o = &lhs_operands[i];
|
|
|
- o->expr = as->lhs[i];
|
|
|
- o->mode = Addressing_Value;
|
|
|
- } else {
|
|
|
- ctx->assignment_lhs_hint = unparen_expr(as->lhs[i]);
|
|
|
- check_expr(ctx, &lhs_operands[i], as->lhs[i]);
|
|
|
- }
|
|
|
+ // NOTE(bill): If there is a bad syntax error, rhs > lhs which would mean there would need to be
|
|
|
+ // an extra allocation
|
|
|
+ auto lhs_operands = array_make<Operand>(temporary_allocator(), lhs_count);
|
|
|
+ auto rhs_operands = array_make<Operand>(temporary_allocator(), 0, 2*lhs_count);
|
|
|
+
|
|
|
+ for_array(i, as->lhs) {
|
|
|
+ if (is_blank_ident(as->lhs[i])) {
|
|
|
+ Operand *o = &lhs_operands[i];
|
|
|
+ o->expr = as->lhs[i];
|
|
|
+ o->mode = Addressing_Value;
|
|
|
+ } else {
|
|
|
+ ctx->assignment_lhs_hint = unparen_expr(as->lhs[i]);
|
|
|
+ check_expr(ctx, &lhs_operands[i], as->lhs[i]);
|
|
|
}
|
|
|
- ctx->assignment_lhs_hint = nullptr; // Reset the assignment_lhs_hint
|
|
|
+ }
|
|
|
+ ctx->assignment_lhs_hint = nullptr; // Reset the assignment_lhs_hint
|
|
|
|
|
|
- check_assignment_arguments(ctx, lhs_operands, &rhs_operands, as->rhs);
|
|
|
+ check_assignment_arguments(ctx, lhs_operands, &rhs_operands, as->rhs);
|
|
|
|
|
|
- auto lhs_to_ignore = array_make<bool>(temporary_allocator(), lhs_count);
|
|
|
+ auto lhs_to_ignore = array_make<bool>(temporary_allocator(), lhs_count);
|
|
|
|
|
|
- isize rhs_count = rhs_operands.count;
|
|
|
- isize max = gb_min(lhs_count, rhs_count);
|
|
|
- for (isize i = 0; i < max; i++) {
|
|
|
- if (lhs_to_ignore[i]) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i]);
|
|
|
- }
|
|
|
- if (lhs_count != rhs_count) {
|
|
|
- error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count);
|
|
|
+ isize rhs_count = rhs_operands.count;
|
|
|
+ isize max = gb_min(lhs_count, rhs_count);
|
|
|
+ for (isize i = 0; i < max; i++) {
|
|
|
+ if (lhs_to_ignore[i]) {
|
|
|
+ continue;
|
|
|
}
|
|
|
- break;
|
|
|
+ check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i]);
|
|
|
+ }
|
|
|
+ if (lhs_count != rhs_count) {
|
|
|
+ error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count);
|
|
|
}
|
|
|
|
|
|
- default: {
|
|
|
- // a += 1; // Single-sided
|
|
|
- Token op = as->op;
|
|
|
- if (as->lhs.count != 1 || as->rhs.count != 1) {
|
|
|
- error(op, "Assignment operation '%.*s' requires single-valued expressions", LIT(op.string));
|
|
|
- return;
|
|
|
- }
|
|
|
- if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
|
|
|
- error(op, "Unknown Assignment operation '%.*s'", LIT(op.string));
|
|
|
- return;
|
|
|
- }
|
|
|
- Operand lhs = {Addressing_Invalid};
|
|
|
- Operand rhs = {Addressing_Invalid};
|
|
|
- Ast *binary_expr = alloc_ast_node(node->file(), Ast_BinaryExpr);
|
|
|
- ast_node(be, BinaryExpr, binary_expr);
|
|
|
- be->op = op;
|
|
|
- be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
|
|
|
- // NOTE(bill): Only use the first one will be used
|
|
|
- be->left = as->lhs[0];
|
|
|
- be->right = as->rhs[0];
|
|
|
-
|
|
|
- check_expr(ctx, &lhs, as->lhs[0]);
|
|
|
- check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
|
|
|
- if (rhs.mode == Addressing_Invalid) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ } else {
|
|
|
+ // a += 1; // Single-sided
|
|
|
+ Token op = as->op;
|
|
|
+ if (as->lhs.count != 1 || as->rhs.count != 1) {
|
|
|
+ error(op, "Assignment operation '%.*s' requires single-valued expressions", LIT(op.string));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
|
|
|
+ error(op, "Unknown Assignment operation '%.*s'", LIT(op.string));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Operand lhs = {Addressing_Invalid};
|
|
|
+ Operand rhs = {Addressing_Invalid};
|
|
|
+ Ast *binary_expr = alloc_ast_node(node->file(), Ast_BinaryExpr);
|
|
|
+ ast_node(be, BinaryExpr, binary_expr);
|
|
|
+ be->op = op;
|
|
|
+ be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
|
|
|
+ // NOTE(bill): Only use the first one will be used
|
|
|
+ be->left = as->lhs[0];
|
|
|
+ be->right = as->rhs[0];
|
|
|
+
|
|
|
+ check_expr(ctx, &lhs, as->lhs[0]);
|
|
|
+ check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
|
|
|
+ if (rhs.mode != Addressing_Invalid) {
|
|
|
// NOTE(bill): Only use the first one will be used
|
|
|
check_assignment_variable(ctx, &lhs, &rhs);
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
}
|
|
|
- case_end;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- case_ast_node(bs, BlockStmt, node);
|
|
|
- check_open_scope(ctx, node);
|
|
|
- check_label(ctx, bs->label, node);
|
|
|
+gb_internal void check_if_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
|
|
+ ast_node(is, IfStmt, node);
|
|
|
+ check_open_scope(ctx, node);
|
|
|
|
|
|
- check_stmt_list(ctx, bs->stmts, flags);
|
|
|
- check_block_stmt_for_errors(ctx, node);
|
|
|
- check_close_scope(ctx);
|
|
|
- case_end;
|
|
|
+ check_label(ctx, is->label, node);
|
|
|
|
|
|
- case_ast_node(is, IfStmt, node);
|
|
|
- check_open_scope(ctx, node);
|
|
|
+ if (is->init != nullptr) {
|
|
|
+ check_stmt(ctx, is->init, 0);
|
|
|
+ }
|
|
|
|
|
|
- check_label(ctx, is->label, node);
|
|
|
+ Operand operand = {Addressing_Invalid};
|
|
|
+ check_expr(ctx, &operand, is->cond);
|
|
|
+ if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) {
|
|
|
+ error(is->cond, "Non-boolean condition in 'if' statement");
|
|
|
+ }
|
|
|
|
|
|
- if (is->init != nullptr) {
|
|
|
- check_stmt(ctx, is->init, 0);
|
|
|
- }
|
|
|
+ check_stmt(ctx, is->body, mod_flags);
|
|
|
|
|
|
- Operand operand = {Addressing_Invalid};
|
|
|
- check_expr(ctx, &operand, is->cond);
|
|
|
- if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) {
|
|
|
- error(is->cond, "Non-boolean condition in 'if' statement");
|
|
|
+ if (is->else_stmt != nullptr) {
|
|
|
+ switch (is->else_stmt->kind) {
|
|
|
+ case Ast_IfStmt:
|
|
|
+ case Ast_BlockStmt:
|
|
|
+ check_stmt(ctx, is->else_stmt, mod_flags);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ error(is->else_stmt, "Invalid 'else' statement in 'if' statement");
|
|
|
+ break;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- check_stmt(ctx, is->body, mod_flags);
|
|
|
+ check_close_scope(ctx);
|
|
|
+}
|
|
|
|
|
|
- if (is->else_stmt != nullptr) {
|
|
|
- switch (is->else_stmt->kind) {
|
|
|
- case Ast_IfStmt:
|
|
|
- case Ast_BlockStmt:
|
|
|
- check_stmt(ctx, is->else_stmt, mod_flags);
|
|
|
- break;
|
|
|
- default:
|
|
|
- error(is->else_stmt, "Invalid 'else' statement in 'if' statement");
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) {
|
|
|
+ ast_node(rs, ReturnStmt, node);
|
|
|
|
|
|
- check_close_scope(ctx);
|
|
|
- case_end;
|
|
|
+ GB_ASSERT(ctx->curr_proc_sig != nullptr);
|
|
|
|
|
|
- case_ast_node(ws, WhenStmt, node);
|
|
|
- check_when_stmt(ctx, ws, flags);
|
|
|
- case_end;
|
|
|
+ if (ctx->in_defer) {
|
|
|
+ error(rs->token, "'return' cannot be used within a defer statement");
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- case_ast_node(rs, ReturnStmt, node);
|
|
|
- GB_ASSERT(ctx->curr_proc_sig != nullptr);
|
|
|
+ Type *proc_type = ctx->curr_proc_sig;
|
|
|
+ GB_ASSERT(proc_type != nullptr);
|
|
|
+ GB_ASSERT(proc_type->kind == Type_Proc);
|
|
|
|
|
|
- if (ctx->in_defer) {
|
|
|
- error(rs->token, "'return' cannot be used within a defer statement");
|
|
|
- break;
|
|
|
- }
|
|
|
+ TypeProc *pt = &proc_type->Proc;
|
|
|
+ if (pt->diverging) {
|
|
|
+ error(rs->token, "Diverging procedures may not return");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ Entity **result_entities = nullptr;
|
|
|
+ isize result_count = 0;
|
|
|
+ bool has_named_results = pt->has_named_results;
|
|
|
+ if (pt->results) {
|
|
|
+ result_entities = proc_type->Proc.results->Tuple.variables.data;
|
|
|
+ result_count = proc_type->Proc.results->Tuple.variables.count;
|
|
|
+ }
|
|
|
|
|
|
- Type *proc_type = ctx->curr_proc_sig;
|
|
|
- GB_ASSERT(proc_type != nullptr);
|
|
|
- GB_ASSERT(proc_type->kind == Type_Proc);
|
|
|
+ auto operands = array_make<Operand>(heap_allocator(), 0, 2*rs->results.count);
|
|
|
+ defer (array_free(&operands));
|
|
|
|
|
|
- TypeProc *pt = &proc_type->Proc;
|
|
|
- if (pt->diverging) {
|
|
|
- error(rs->token, "Diverging procedures may not return");
|
|
|
- break;
|
|
|
+ check_unpack_arguments(ctx, result_entities, result_count, &operands, rs->results, true, false);
|
|
|
+
|
|
|
+ if (result_count == 0 && rs->results.count > 0) {
|
|
|
+ error(rs->results[0], "No return values expected");
|
|
|
+ } else if (has_named_results && operands.count == 0) {
|
|
|
+ // Okay
|
|
|
+ } else if (operands.count != result_count) {
|
|
|
+ // Ignore error message as it has most likely already been reported
|
|
|
+ if (all_operands_valid(operands)) {
|
|
|
+ error(node, "Expected %td return values, got %td", result_count, operands.count);
|
|
|
}
|
|
|
+ } else {
|
|
|
+ for (isize i = 0; i < result_count; i++) {
|
|
|
+ Entity *e = pt->results->Tuple.variables[i];
|
|
|
+ Operand *o = &operands[i];
|
|
|
+ check_assignment(ctx, o, e->type, str_lit("return statement"));
|
|
|
+ if (is_type_untyped(o->type)) {
|
|
|
+ update_untyped_expr_type(ctx, o->expr, e->type, true);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // NOTE(bill): This is very basic escape analysis
|
|
|
+ // This needs to be improved tremendously, and a lot of it done during the
|
|
|
+ // middle-end (or LLVM side) to improve checks and error messages
|
|
|
+ Ast *expr = unparen_expr(o->expr);
|
|
|
+ if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) {
|
|
|
+ Ast *x = unparen_expr(expr->UnaryExpr.expr);
|
|
|
+ if (x->kind == Ast_CompoundLit) {
|
|
|
+ error(expr, "Cannot return the address to a stack value from a procedure");
|
|
|
+ } else if (x->kind == Ast_IndexExpr) {
|
|
|
+ Ast *array = x->IndexExpr.expr;
|
|
|
+ if (is_type_array_like(type_of_expr(array)) && check_expr_is_stack_variable(array)) {
|
|
|
+ gbString t = type_to_string(type_of_expr(array));
|
|
|
+ error(expr, "Cannot return the address to an element of stack variable from a procedure, of type %s", t);
|
|
|
+ gb_string_free(t);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (check_expr_is_stack_variable(x)) {
|
|
|
+ error(expr, "Cannot return the address to a stack variable from a procedure");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+gb_internal void check_for_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
|
|
+ ast_node(fs, ForStmt, node);
|
|
|
+ mod_flags |= Stmt_BreakAllowed | Stmt_ContinueAllowed;
|
|
|
+
|
|
|
+ check_open_scope(ctx, node);
|
|
|
+ check_label(ctx, fs->label, node); // TODO(bill): What should the label's "scope" be?
|
|
|
|
|
|
- Entity **result_entities = nullptr;
|
|
|
- isize result_count = 0;
|
|
|
- bool has_named_results = pt->has_named_results;
|
|
|
- if (pt->results) {
|
|
|
- result_entities = proc_type->Proc.results->Tuple.variables.data;
|
|
|
- result_count = proc_type->Proc.results->Tuple.variables.count;
|
|
|
+ if (fs->init != nullptr) {
|
|
|
+ check_stmt(ctx, fs->init, 0);
|
|
|
+ }
|
|
|
+ if (fs->cond != nullptr) {
|
|
|
+ Operand o = {Addressing_Invalid};
|
|
|
+ check_expr(ctx, &o, fs->cond);
|
|
|
+ if (o.mode != Addressing_Invalid && !is_type_boolean(o.type)) {
|
|
|
+ error(fs->cond, "Non-boolean condition in 'for' statement");
|
|
|
}
|
|
|
+ }
|
|
|
+ if (fs->post != nullptr) {
|
|
|
+ check_stmt(ctx, fs->post, 0);
|
|
|
|
|
|
- auto operands = array_make<Operand>(heap_allocator(), 0, 2*rs->results.count);
|
|
|
- defer (array_free(&operands));
|
|
|
+ if (fs->post->kind != Ast_AssignStmt) {
|
|
|
+ error(fs->post, "'for' statement post statement must be a simple statement");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ check_stmt(ctx, fs->body, mod_flags);
|
|
|
|
|
|
- check_unpack_arguments(ctx, result_entities, result_count, &operands, rs->results, true, false);
|
|
|
+ check_close_scope(ctx);
|
|
|
+}
|
|
|
|
|
|
- if (result_count == 0 && rs->results.count > 0) {
|
|
|
- error(rs->results[0], "No return values expected");
|
|
|
- } else if (has_named_results && operands.count == 0) {
|
|
|
- // Okay
|
|
|
- } else if (operands.count != result_count) {
|
|
|
- // Ignore error message as it has most likely already been reported
|
|
|
- if (all_operands_valid(operands)) {
|
|
|
- error(node, "Expected %td return values, got %td", result_count, operands.count);
|
|
|
- }
|
|
|
- } else {
|
|
|
- for (isize i = 0; i < result_count; i++) {
|
|
|
- Entity *e = pt->results->Tuple.variables[i];
|
|
|
- Operand *o = &operands[i];
|
|
|
- check_assignment(ctx, o, e->type, str_lit("return statement"));
|
|
|
- if (is_type_untyped(o->type)) {
|
|
|
- update_untyped_expr_type(ctx, o->expr, e->type, true);
|
|
|
- }
|
|
|
|
|
|
+gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
|
|
|
+ u32 mod_flags = flags & (~Stmt_FallthroughAllowed);
|
|
|
+ switch (node->kind) {
|
|
|
+ case_ast_node(_, EmptyStmt, node); case_end;
|
|
|
+ case_ast_node(_, BadStmt, node); case_end;
|
|
|
+ case_ast_node(_, BadDecl, node); case_end;
|
|
|
|
|
|
- // NOTE(bill): This is very basic escape analysis
|
|
|
- // This needs to be improved tremendously, and a lot of it done during the
|
|
|
- // middle-end (or LLVM side) to improve checks and error messages
|
|
|
- Ast *expr = unparen_expr(o->expr);
|
|
|
- if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) {
|
|
|
- Ast *x = unparen_expr(expr->UnaryExpr.expr);
|
|
|
- if (x->kind == Ast_CompoundLit) {
|
|
|
- error(expr, "Cannot return the address to a stack value from a procedure");
|
|
|
- } else if (x->kind == Ast_IndexExpr) {
|
|
|
- Ast *array = x->IndexExpr.expr;
|
|
|
- if (is_type_array_like(type_of_expr(array)) && check_expr_is_stack_variable(array)) {
|
|
|
- gbString t = type_to_string(type_of_expr(array));
|
|
|
- error(expr, "Cannot return the address to an element of stack variable from a procedure, of type %s", t);
|
|
|
- gb_string_free(t);
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (check_expr_is_stack_variable(x)) {
|
|
|
- error(expr, "Cannot return the address to a stack variable from a procedure");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ case_ast_node(es, ExprStmt, node)
|
|
|
+ check_expr_stmt(ctx, node);
|
|
|
case_end;
|
|
|
|
|
|
- case_ast_node(fs, ForStmt, node);
|
|
|
- u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
|
|
|
+ case_ast_node(as, AssignStmt, node);
|
|
|
+ check_assign_stmt(ctx, node);
|
|
|
+ case_end;
|
|
|
|
|
|
+ case_ast_node(bs, BlockStmt, node);
|
|
|
check_open_scope(ctx, node);
|
|
|
- check_label(ctx, fs->label, node); // TODO(bill): What should the label's "scope" be?
|
|
|
+ check_label(ctx, bs->label, node);
|
|
|
|
|
|
- if (fs->init != nullptr) {
|
|
|
- check_stmt(ctx, fs->init, 0);
|
|
|
- }
|
|
|
- if (fs->cond != nullptr) {
|
|
|
- Operand o = {Addressing_Invalid};
|
|
|
- check_expr(ctx, &o, fs->cond);
|
|
|
- if (o.mode != Addressing_Invalid && !is_type_boolean(o.type)) {
|
|
|
- error(fs->cond, "Non-boolean condition in 'for' statement");
|
|
|
- }
|
|
|
- }
|
|
|
- if (fs->post != nullptr) {
|
|
|
- check_stmt(ctx, fs->post, 0);
|
|
|
+ check_stmt_list(ctx, bs->stmts, flags);
|
|
|
+ check_block_stmt_for_errors(ctx, node);
|
|
|
+ check_close_scope(ctx);
|
|
|
+ case_end;
|
|
|
|
|
|
- if (fs->post->kind != Ast_AssignStmt) {
|
|
|
- error(fs->post, "'for' statement post statement must be a simple statement");
|
|
|
- }
|
|
|
- }
|
|
|
- check_stmt(ctx, fs->body, new_flags);
|
|
|
+ case_ast_node(is, IfStmt, node);
|
|
|
+ check_if_stmt(ctx, node, mod_flags);
|
|
|
+ case_end;
|
|
|
|
|
|
- check_close_scope(ctx);
|
|
|
+ case_ast_node(ws, WhenStmt, node);
|
|
|
+ check_when_stmt(ctx, ws, flags);
|
|
|
case_end;
|
|
|
|
|
|
+ case_ast_node(rs, ReturnStmt, node);
|
|
|
+ check_return_stmt(ctx, node);
|
|
|
+ case_end;
|
|
|
+
|
|
|
+ case_ast_node(fs, ForStmt, node);
|
|
|
+ check_for_stmt(ctx, node, mod_flags);
|
|
|
+ case_end;
|
|
|
|
|
|
case_ast_node(rs, RangeStmt, node);
|
|
|
check_range_stmt(ctx, node, mod_flags);
|