aboutsummaryrefslogtreecommitdiff
path: root/src/check_stmt.cpp
diff options
context:
space:
mode:
authorGinger Bill <bill@gingerbill.org>2017-06-08 12:03:40 +0100
committerGinger Bill <bill@gingerbill.org>2017-06-08 12:03:40 +0100
commit9b61adb97dd78e1cf04ad410e72166f684f97925 (patch)
treeccb50b757f31c36dcd2bac161d191e2d23dcb6d1 /src/check_stmt.cpp
parent333924cce15e10e941ee63d6fcdc19d5cb95bb3c (diff)
Build as C++
Diffstat (limited to 'src/check_stmt.cpp')
-rw-r--r--src/check_stmt.cpp1662
1 files changed, 1662 insertions, 0 deletions
diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp
new file mode 100644
index 000000000..6031c9120
--- /dev/null
+++ b/src/check_stmt.cpp
@@ -0,0 +1,1662 @@
+void check_stmt_list(Checker *c, AstNodeArray stmts, u32 flags) {
+ if (stmts.count == 0) {
+ return;
+ }
+
+ if (flags&Stmt_CheckScopeDecls) {
+ check_scope_decls(c, stmts, 1.2*stmts.count);
+ }
+
+ bool ft_ok = (flags & Stmt_FallthroughAllowed) != 0;
+ flags &= ~Stmt_FallthroughAllowed;
+
+ isize max = stmts.count;
+ for (isize i = stmts.count-1; i >= 0; i--) {
+ if (stmts.e[i]->kind != AstNode_EmptyStmt) {
+ break;
+ }
+ max--;
+ }
+ for (isize i = 0; i < max; i++) {
+ AstNode *n = stmts.e[i];
+ if (n->kind == AstNode_EmptyStmt) {
+ continue;
+ }
+ u32 new_flags = flags;
+ if (ft_ok && i+1 == max) {
+ new_flags |= Stmt_FallthroughAllowed;
+ }
+
+ if (i+1 < max) {
+ switch (n->kind) {
+ case AstNode_ReturnStmt:
+ error_node(n, "Statements after this `return` are never executed");
+ break;
+ }
+ }
+
+ check_stmt(c, n, new_flags);
+ }
+
+}
+
+bool check_is_terminating_list(AstNodeArray stmts) {
+ // Iterate backwards
+ for (isize n = stmts.count-1; n >= 0; n--) {
+ AstNode *stmt = stmts.e[n];
+ if (stmt->kind != AstNode_EmptyStmt) {
+ return check_is_terminating(stmt);
+ }
+ }
+
+ return false;
+}
+
+bool check_has_break_list(AstNodeArray stmts, bool implicit) {
+ for_array(i, stmts) {
+ AstNode *stmt = stmts.e[i];
+ if (check_has_break(stmt, implicit)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+bool check_has_break(AstNode *stmt, bool implicit) {
+ switch (stmt->kind) {
+ case AstNode_BranchStmt:
+ if (stmt->BranchStmt.token.kind == Token_break) {
+ return implicit;
+ }
+ break;
+ case AstNode_BlockStmt:
+ return check_has_break_list(stmt->BlockStmt.stmts, implicit);
+
+ case AstNode_IfStmt:
+ if (check_has_break(stmt->IfStmt.body, implicit) ||
+ (stmt->IfStmt.else_stmt != NULL && check_has_break(stmt->IfStmt.else_stmt, implicit))) {
+ return true;
+ }
+ break;
+
+ case AstNode_CaseClause:
+ return check_has_break_list(stmt->CaseClause.stmts, implicit);
+ }
+
+ return false;
+}
+
+
+
+// NOTE(bill): The last expression has to be a `return` statement
+// TODO(bill): This is a mild hack and should be probably handled properly
+// TODO(bill): Warn/err against code after `return` that it won't be executed
+bool check_is_terminating(AstNode *node) {
+ 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);
+ case_end;
+
+ case_ast_node(es, ExprStmt, node);
+ return check_is_terminating(es->expr);
+ case_end;
+
+ case_ast_node(is, IfStmt, node);
+ if (is->else_stmt != NULL) {
+ if (check_is_terminating(is->body) &&
+ check_is_terminating(is->else_stmt)) {
+ return true;
+ }
+ }
+ case_end;
+
+ case_ast_node(ws, WhenStmt, node);
+ if (ws->else_stmt != NULL) {
+ if (check_is_terminating(ws->body) &&
+ check_is_terminating(ws->else_stmt)) {
+ return true;
+ }
+ }
+ case_end;
+
+ case_ast_node(fs, ForStmt, node);
+ if (fs->cond == NULL && !check_has_break(fs->body, true)) {
+ return check_is_terminating(fs->body);
+ }
+ case_end;
+
+ case_ast_node(rs, RangeStmt, node);
+ return false;
+ case_end;
+
+ case_ast_node(ms, MatchStmt, node);
+ bool has_default = false;
+ for_array(i, ms->body->BlockStmt.stmts) {
+ AstNode *clause = ms->body->BlockStmt.stmts.e[i];
+ ast_node(cc, CaseClause, clause);
+ if (cc->list.count == 0) {
+ has_default = true;
+ }
+ if (!check_is_terminating_list(cc->stmts) ||
+ check_has_break_list(cc->stmts, true)) {
+ return false;
+ }
+ }
+ return has_default;
+ case_end;
+
+ case_ast_node(ms, TypeMatchStmt, node);
+ bool has_default = false;
+ for_array(i, ms->body->BlockStmt.stmts) {
+ AstNode *clause = ms->body->BlockStmt.stmts.e[i];
+ ast_node(cc, CaseClause, clause);
+ if (cc->list.count == 0) {
+ has_default = true;
+ }
+ if (!check_is_terminating_list(cc->stmts) ||
+ check_has_break_list(cc->stmts, true)) {
+ return false;
+ }
+ }
+ return has_default;
+ case_end;
+
+ case_ast_node(pa, PushAllocator, node);
+ return check_is_terminating(pa->body);
+ case_end;
+ case_ast_node(pc, PushContext, node);
+ return check_is_terminating(pc->body);
+ case_end;
+ }
+
+ return false;
+}
+
+Type *check_assignment_variable(Checker *c, Operand *rhs, AstNode *lhs_node) {
+ if (rhs->mode == Addressing_Invalid ||
+ (rhs->type == t_invalid && rhs->mode != Addressing_Overload)) {
+ return NULL;
+ }
+
+ AstNode *node = unparen_expr(lhs_node);
+
+ // NOTE(bill): Ignore assignments to `_`
+ if (node->kind == AstNode_Ident &&
+ str_eq(node->Ident.string, str_lit("_"))) {
+ add_entity_definition(&c->info, node, NULL);
+ check_assignment(c, rhs, NULL, str_lit("assignment to `_` identifier"));
+ if (rhs->mode == Addressing_Invalid) {
+ return NULL;
+ }
+ return rhs->type;
+ }
+
+ Entity *e = NULL;
+ bool used = false;
+ Operand lhs = {Addressing_Invalid};
+
+
+ check_expr(c, &lhs, lhs_node);
+ if (lhs.mode == Addressing_Invalid ||
+ lhs.type == t_invalid) {
+ return NULL;
+ }
+
+ if (rhs->mode == Addressing_Overload) {
+ isize overload_count = rhs->overload_count;
+ Entity **procs = rhs->overload_entities;
+ GB_ASSERT(procs != NULL && overload_count > 0);
+
+ // NOTE(bill): These should be done
+ for (isize i = 0; i < overload_count; i++) {
+ Type *t = base_type(procs[i]->type);
+ if (t == t_invalid) {
+ continue;
+ }
+ Operand x = {};
+ x.mode = Addressing_Value;
+ x.type = t;
+ if (check_is_assignable_to(c, &x, lhs.type)) {
+ e = procs[i];
+ add_entity_use(c, rhs->expr, e);
+ break;
+ }
+ }
+
+ if (e != NULL) {
+ // HACK TODO(bill): Should the entities be freed as it's technically a leak
+ rhs->mode = Addressing_Value;
+ rhs->type = e->type;
+ rhs->overload_count = 0;
+ rhs->overload_entities = NULL;
+ }
+ } else {
+ if (node->kind == AstNode_Ident) {
+ ast_node(i, Ident, node);
+ e = scope_lookup_entity(c->context.scope, i->string);
+ if (e != NULL && e->kind == Entity_Variable) {
+ used = (e->flags & EntityFlag_Used) != 0; // TODO(bill): Make backup just in case
+ }
+ }
+
+ }
+
+ if (e != NULL && used) {
+ e->flags |= EntityFlag_Used;
+ }
+
+ Type *assignment_type = lhs.type;
+ switch (lhs.mode) {
+ case Addressing_Invalid:
+ return NULL;
+
+ case Addressing_Variable: {
+ if (is_type_bit_field_value(lhs.type)) {
+ Type *lt = base_type(lhs.type);
+ i64 lhs_bits = lt->BitFieldValue.bits;
+ if (rhs->mode == Addressing_Constant) {
+ ExactValue v = exact_value_to_integer(rhs->value);
+ if (v.kind == ExactValue_Integer) {
+ i128 i = v.value_integer;
+ u128 u = *cast(u128 *)&i;
+ u128 umax = U128_NEG_ONE;
+ if (lhs_bits < 128) {
+ umax = u128_sub(u128_shl(U128_ONE, lhs_bits), U128_ONE);
+ }
+ i128 imax = i128_shl(I128_ONE, lhs_bits-1ll);
+
+ bool ok = false;
+ ok = !(u128_lt(u, U128_ZERO) || u128_gt(u, umax));
+
+ if (ok) {
+ return rhs->type;
+ }
+ }
+ } else if (is_type_integer(rhs->type)) {
+ // TODO(bill): Any other checks?
+ return rhs->type;
+ }
+ gbString lhs_expr = expr_to_string(lhs.expr);
+ gbString rhs_expr = expr_to_string(rhs->expr);
+ error_node(rhs->expr, "Cannot assign `%s` to bit field `%s`", rhs_expr, lhs_expr);
+ gb_string_free(rhs_expr);
+ gb_string_free(lhs_expr);
+ return NULL;
+ }
+ break;
+ }
+
+ case Addressing_MapIndex: {
+ AstNode *ln = unparen_expr(lhs_node);
+ if (ln->kind == AstNode_IndexExpr) {
+ AstNode *x = ln->IndexExpr.expr;
+ TypeAndValue tav = type_and_value_of_expr(&c->info, x);
+ GB_ASSERT(tav.mode != Addressing_Invalid);
+ if (tav.mode != Addressing_Variable) {
+ if (!is_type_pointer(tav.type)) {
+ gbString str = expr_to_string(lhs.expr);
+ error_node(lhs.expr, "Cannot assign to the value of a map `%s`", str);
+ gb_string_free(str);
+ return NULL;
+ }
+ }
+ }
+ } break;
+
+ default: {
+ if (lhs.expr->kind == AstNode_SelectorExpr) {
+ // NOTE(bill): Extra error checks
+ Operand op_c = {Addressing_Invalid};
+ ast_node(se, SelectorExpr, lhs.expr);
+ check_expr(c, &op_c, se->expr);
+ if (op_c.mode == Addressing_MapIndex) {
+ gbString str = expr_to_string(lhs.expr);
+ error_node(lhs.expr, "Cannot assign to record field `%s` in map", str);
+ gb_string_free(str);
+ return NULL;
+ }
+ }
+
+ gbString str = expr_to_string(lhs.expr);
+ if (lhs.mode == Addressing_Immutable) {
+ error_node(lhs.expr, "Cannot assign to an immutable: `%s`", str);
+ } else {
+ error_node(lhs.expr, "Cannot assign to `%s`", str);
+ }
+ gb_string_free(str);
+ } break;
+ }
+
+ check_assignment(c, rhs, assignment_type, str_lit("assignment"));
+ if (rhs->mode == Addressing_Invalid) {
+ return NULL;
+ }
+
+ return rhs->type;
+}
+
+typedef enum MatchTypeKind {
+ MatchType_Invalid,
+ MatchType_Union,
+ MatchType_Any,
+} MatchTypeKind;
+
+MatchTypeKind check_valid_type_match_type(Type *type) {
+ type = type_deref(type);
+ if (is_type_union(type)) {
+ return MatchType_Union;
+ }
+ if (is_type_any(type)) {
+ return MatchType_Any;
+ }
+ return MatchType_Invalid;
+}
+
+void check_stmt_internal(Checker *c, AstNode *node, u32 flags);
+void check_stmt(Checker *c, AstNode *node, u32 flags) {
+ u32 prev_stmt_state_flags = c->context.stmt_state_flags;
+
+ if (node->stmt_state_flags != 0) {
+ u32 in = node->stmt_state_flags;
+ u32 out = c->context.stmt_state_flags;
+
+ if (in & StmtStateFlag_no_bounds_check) {
+ out |= StmtStateFlag_no_bounds_check;
+ out &= ~StmtStateFlag_bounds_check;
+ } else {
+ // if (in & StmtStateFlag_bounds_check) {
+ out |= StmtStateFlag_bounds_check;
+ out &= ~StmtStateFlag_no_bounds_check;
+ }
+
+ c->context.stmt_state_flags = out;
+ }
+
+ check_stmt_internal(c, node, flags);
+
+ c->context.stmt_state_flags = prev_stmt_state_flags;
+}
+
+
+
+typedef struct TypeAndToken {
+ Type *type;
+ Token token;
+} TypeAndToken;
+
+#define MAP_TYPE TypeAndToken
+#define MAP_PROC map_type_and_token_
+#define MAP_NAME MapTypeAndToken
+#include "map.cpp"
+
+void check_when_stmt(Checker *c, AstNodeWhenStmt *ws, u32 flags) {
+ flags &= ~Stmt_CheckScopeDecls;
+ Operand operand = {Addressing_Invalid};
+ check_expr(c, &operand, ws->cond);
+ if (operand.mode != Addressing_Constant || !is_type_boolean(operand.type)) {
+ error_node(ws->cond, "Non-constant boolean `when` condition");
+ return;
+ }
+ if (ws->body == NULL || ws->body->kind != AstNode_BlockStmt) {
+ error_node(ws->cond, "Invalid body for `when` statement");
+ return;
+ }
+ if (operand.value.kind == ExactValue_Bool &&
+ operand.value.value_bool) {
+ check_stmt_list(c, ws->body->BlockStmt.stmts, flags);
+ } else if (ws->else_stmt) {
+ switch (ws->else_stmt->kind) {
+ case AstNode_BlockStmt:
+ check_stmt_list(c, ws->else_stmt->BlockStmt.stmts, flags);
+ break;
+ case AstNode_WhenStmt:
+ check_when_stmt(c, &ws->else_stmt->WhenStmt, flags);
+ break;
+ default:
+ error_node(ws->else_stmt, "Invalid `else` statement in `when` statement");
+ break;
+ }
+ }
+}
+
+void check_label(Checker *c, AstNode *label) {
+ if (label == NULL) {
+ return;
+ }
+ ast_node(l, Label, label);
+ if (l->name->kind != AstNode_Ident) {
+ error_node(l->name, "A label's name must be an identifier");
+ return;
+ }
+ String name = l->name->Ident.string;
+ if (str_eq(name, str_lit("_"))) {
+ error_node(l->name, "A label's name cannot be a blank identifier");
+ return;
+ }
+
+
+ if (c->proc_stack.count == 0) {
+ error_node(l->name, "A label is only allowed within a procedure");
+ return;
+ }
+ GB_ASSERT(c->context.decl != NULL);
+
+ bool ok = true;
+ for_array(i, c->context.decl->labels) {
+ BlockLabel bl = c->context.decl->labels.e[i];
+ if (str_eq(bl.name, name)) {
+ error_node(label, "Duplicate label with the name `%.*s`", LIT(name));
+ ok = false;
+ break;
+ }
+ }
+
+ Entity *e = make_entity_label(c->allocator, c->context.scope, l->name->Ident, t_invalid, label);
+ add_entity(c, c->context.scope, l->name, e);
+ e->parent_proc_decl = c->context.curr_proc_decl;
+
+ if (ok) {
+ BlockLabel bl = {name, label};
+ array_add(&c->context.decl->labels, bl);
+ }
+}
+
+// Returns `true` for `continue`, `false` for `return`
+bool check_using_stmt_entity(Checker *c, AstNodeUsingStmt *us, AstNode *expr, bool is_selector, Entity *e) {
+ if (e == NULL) {
+ error(us->token, "`using` applied to an unknown entity");
+ return true;
+ }
+
+ add_entity_use(c, expr, e);
+
+ switch (e->kind) {
+ case Entity_TypeName: {
+ Type *t = base_type(e->type);
+ if (is_type_union(t)) {
+ TokenPos pos = ast_node_token(expr).pos;
+ for (isize i = 1; i < t->Record.variant_count; i++) {
+ Entity *f = t->Record.variants[i];
+ // gb_printf_err("%s\n", type_to_string(f->type));
+ Entity *found = scope_insert_entity(c->context.scope, f);
+ if (found != NULL) {
+ gbString expr_str = expr_to_string(expr);
+ error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string));
+ gb_string_free(expr_str);
+ return false;
+ }
+ f->using_parent = e;
+ }
+ } else if (is_type_enum(t)) {
+ for (isize i = 0; i < t->Record.field_count; i++) {
+ Entity *f = t->Record.fields[i];
+ Entity *found = scope_insert_entity(c->context.scope, f);
+ if (found != NULL) {
+ gbString expr_str = expr_to_string(expr);
+ error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(found->token.string));
+ gb_string_free(expr_str);
+ return false;
+ }
+ f->using_parent = e;
+ }
+
+ } else {
+ error(us->token, "`using` can be only applied to `union` or `enum` type entities");
+ }
+ } break;
+
+ case Entity_ImportName: {
+ Scope *scope = e->ImportName.scope;
+ for_array(i, scope->elements.entries) {
+ Entity *decl = scope->elements.entries.e[i].value;
+ Entity *found = scope_insert_entity(c->context.scope, decl);
+ if (found != NULL) {
+ gbString expr_str = expr_to_string(expr);
+ error(us->token,
+ "Namespace collision while `using` `%s` of: %.*s\n"
+ "\tat %.*s(%td:%td)\n"
+ "\tat %.*s(%td:%td)",
+ expr_str, LIT(found->token.string),
+ LIT(found->token.pos.file), found->token.pos.line, found->token.pos.column,
+ LIT(decl->token.pos.file), decl->token.pos.line, decl->token.pos.column
+ );
+ gb_string_free(expr_str);
+ return false;
+ }
+ }
+ } break;
+
+ case Entity_Variable: {
+ Type *t = base_type(type_deref(e->type));
+ if (is_type_struct(t) || is_type_raw_union(t) || is_type_union(t)) {
+ // TODO(bill): Make it work for unions too
+ Scope **found_ = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
+ GB_ASSERT(found_ != NULL);
+ Scope *found = *found_;
+ for_array(i, found->elements.entries) {
+ Entity *f = found->elements.entries.e[i].value;
+ if (f->kind == Entity_Variable) {
+ Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+ // if (is_selector) {
+ uvar->using_expr = expr;
+ // }
+ Entity *prev = scope_insert_entity(c->context.scope, uvar);
+ if (prev != NULL) {
+ gbString expr_str = expr_to_string(expr);
+ error(us->token, "Namespace collision while `using` `%s` of: %.*s", expr_str, LIT(prev->token.string));
+ gb_string_free(expr_str);
+ return false;
+ }
+ }
+ }
+ } else {
+ error(us->token, "`using` can only be applied to variables of type struct or raw_union");
+ return false;
+ }
+ } break;
+
+ case Entity_Constant:
+ error(us->token, "`using` cannot be applied to a constant");
+ break;
+
+ case Entity_Procedure:
+ case Entity_Builtin:
+ error(us->token, "`using` cannot be applied to a procedure");
+ break;
+
+ case Entity_Nil:
+ error(us->token, "`using` cannot be applied to `nil`");
+ break;
+
+ case Entity_Label:
+ error(us->token, "`using` cannot be applied to a label");
+ break;
+
+ case Entity_Invalid:
+ error(us->token, "`using` cannot be applied to an invalid entity");
+ break;
+
+ default:
+ GB_PANIC("TODO(bill): `using` other expressions?");
+ }
+
+ return true;
+}
+
+void check_stmt_internal(Checker *c, AstNode *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;
+
+ case_ast_node(es, ExprStmt, node)
+ Operand operand = {Addressing_Invalid};
+ ExprKind kind = check_expr_base(c, &operand, es->expr, NULL);
+ switch (operand.mode) {
+ case Addressing_Type: {
+ gbString str = type_to_string(operand.type);
+ error_node(node, "`%s` is not an expression", str);
+ gb_string_free(str);
+ } break;
+ case Addressing_NoValue:
+ return;
+ default: {
+ if (kind == Expr_Stmt) {
+ return;
+ }
+ if (operand.expr->kind == AstNode_CallExpr) {
+ AstNodeCallExpr *ce = &operand.expr->CallExpr;
+ Type *t = type_of_expr(&c->info, ce->proc);
+ if (is_type_proc(t)) {
+ if (t->Proc.require_results) {
+ gbString expr_str = expr_to_string(ce->proc);
+ error_node(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(node, "Expression is not used: `%s`", expr_str);
+ gb_string_free(expr_str);
+ } break;
+ }
+ case_end;
+
+ case_ast_node(ts, TagStmt, node);
+ // TODO(bill): Tag Statements
+ error_node(node, "Tag statements are not supported yet");
+ check_stmt(c, ts->stmt, flags);
+ case_end;
+
+ case_ast_node(s, IncDecStmt, node);
+ TokenKind op = s->op.kind;
+ switch (op) {
+ case Token_Inc: op = Token_Add; break;
+ case Token_Dec: op = Token_Sub; break;
+ default:
+ error_node(node, "Invalid inc/dec operation");
+ return;
+ }
+
+ Operand x = {};
+ check_expr(c, &x, s->expr);
+ if (x.mode == Addressing_Invalid) {
+ return;
+ }
+ if (!is_type_integer(x.type) && !is_type_float(x.type)) {
+ gbString e = expr_to_string(s->expr);
+ gbString t = type_to_string(x.type);
+ error_node(node, "%s%.*s used on non-numeric type %s", e, LIT(s->op.string), t);
+ gb_string_free(t);
+ gb_string_free(e);
+ return;
+ }
+ AstNode *left = s->expr;
+ AstNode *right = gb_alloc_item(c->allocator, AstNode);
+ right->kind = AstNode_BasicLit;
+ right->BasicLit.pos = s->op.pos;
+ right->BasicLit.kind = Token_Integer;
+ right->BasicLit.string = str_lit("1");
+
+ AstNode *be = gb_alloc_item(c->allocator, AstNode);
+ be->kind = AstNode_BinaryExpr;
+ be->BinaryExpr.op = s->op;
+ be->BinaryExpr.op.kind = op;
+ be->BinaryExpr.left = left;
+ be->BinaryExpr.right = right;
+ check_binary_expr(c, &x, be);
+ if (x.mode == Addressing_Invalid) {
+ return;
+ }
+ check_assignment_variable(c, &x, left);
+ case_end;
+
+ case_ast_node(as, AssignStmt, node);
+ switch (as->op.kind) {
+ case Token_Eq: {
+ // a, b, c = 1, 2, 3; // Multisided
+
+ isize lhs_count = as->lhs.count;
+ if (lhs_count == 0) {
+ error(as->op, "Missing lhs in assignment statement");
+ return;
+ }
+
+ gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+
+ // NOTE(bill): If there is a bad syntax error, rhs > lhs which would mean there would need to be
+ // an extra allocation
+ ArrayOperand operands = {};
+ array_init_reserve(&operands, c->tmp_allocator, 2 * lhs_count);
+ check_unpack_arguments(c, lhs_count, &operands, as->rhs, true);
+
+ isize rhs_count = operands.count;
+ for_array(i, operands) {
+ if (operands.e[i].mode == Addressing_Invalid) {
+ rhs_count--;
+ }
+ }
+
+ isize max = gb_min(lhs_count, rhs_count);
+ for (isize i = 0; i < max; i++) {
+ check_assignment_variable(c, &operands.e[i], as->lhs.e[i]);
+ }
+ if (lhs_count != rhs_count) {
+ error_node(as->lhs.e[0], "Assignment count mismatch `%td` = `%td`", lhs_count, rhs_count);
+ }
+
+ gb_temp_arena_memory_end(tmp);
+ } break;
+
+ 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 operand = {Addressing_Invalid};
+ AstNode binary_expr = {AstNode_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.e[0];
+ be->right = as->rhs.e[0];
+
+ check_binary_expr(c, &operand, &binary_expr);
+ if (operand.mode == Addressing_Invalid) {
+ return;
+ }
+ // NOTE(bill): Only use the first one will be used
+ check_assignment_variable(c, &operand, as->lhs.e[0]);
+ } break;
+ }
+ case_end;
+
+ case_ast_node(bs, BlockStmt, node);
+ check_open_scope(c, node);
+ check_stmt_list(c, bs->stmts, mod_flags);
+ check_close_scope(c);
+ case_end;
+
+ case_ast_node(is, IfStmt, node);
+ check_open_scope(c, node);
+
+ if (is->init != NULL) {
+ check_stmt(c, is->init, 0);
+ }
+
+ Operand operand = {Addressing_Invalid};
+ check_expr(c, &operand, is->cond);
+ if (operand.mode != Addressing_Invalid && !is_type_boolean(operand.type)) {
+ error_node(is->cond, "Non-boolean condition in `if` statement");
+ }
+
+ check_stmt(c, is->body, mod_flags);
+
+ if (is->else_stmt != NULL) {
+ switch (is->else_stmt->kind) {
+ case AstNode_IfStmt:
+ case AstNode_BlockStmt:
+ check_stmt(c, is->else_stmt, mod_flags);
+ break;
+ default:
+ error_node(is->else_stmt, "Invalid `else` statement in `if` statement");
+ break;
+ }
+ }
+
+ check_close_scope(c);
+ case_end;
+
+ case_ast_node(ws, WhenStmt, node);
+ check_when_stmt(c, ws, flags);
+ case_end;
+
+ case_ast_node(rs, ReturnStmt, node);
+ GB_ASSERT(c->proc_stack.count > 0);
+
+ if (c->context.in_defer) {
+ error(rs->token, "You cannot `return` within a defer statement");
+ break;
+ }
+
+
+ Type *proc_type = c->proc_stack.e[c->proc_stack.count-1];
+ isize result_count = 0;
+ if (proc_type->Proc.results) {
+ result_count = proc_type->Proc.results->Tuple.variable_count;
+ }
+
+ if (result_count > 0) {
+ Entity **variables = NULL;
+ if (proc_type->Proc.results != NULL) {
+ TypeTuple *tuple = &proc_type->Proc.results->Tuple;
+ variables = tuple->variables;
+ }
+ if (rs->results.count == 0) {
+ error_node(node, "Expected %td return values, got 0", result_count);
+ } else {
+ // TokenPos pos = rs->token.pos;
+ // if (pos.line == 10) {
+ // gb_printf_err("%s\n", type_to_string(variables[0]->type));
+ // }
+ check_init_variables(c, variables, result_count,
+ rs->results, str_lit("return statement"));
+ // if (pos.line == 10) {
+ // AstNode *x = rs->results.e[0];
+ // gb_printf_err("%s\n", expr_to_string(x));
+ // gb_printf_err("%s\n", type_to_string(type_of_expr(&c->info, x)));
+ // }
+ }
+ } else if (rs->results.count > 0) {
+ error_node(rs->results.e[0], "No return values expected");
+ }
+ case_end;
+
+ case_ast_node(fs, ForStmt, node);
+ u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
+
+ check_open_scope(c, node);
+ check_label(c, fs->label); // TODO(bill): What should the label's "scope" be?
+
+ if (fs->init != NULL) {
+ check_stmt(c, fs->init, 0);
+ }
+ if (fs->cond != NULL) {
+ Operand o = {Addressing_Invalid};
+ check_expr(c, &o, fs->cond);
+ if (o.mode != Addressing_Invalid && !is_type_boolean(o.type)) {
+ error_node(fs->cond, "Non-boolean condition in `for` statement");
+ }
+ }
+ if (fs->post != NULL) {
+ check_stmt(c, fs->post, 0);
+
+ if (fs->post->kind != AstNode_AssignStmt &&
+ fs->post->kind != AstNode_IncDecStmt) {
+ error_node(fs->post, "`for` statement post statement must be a simple statement");
+ }
+ }
+ check_stmt(c, fs->body, new_flags);
+
+ check_close_scope(c);
+ case_end;
+
+ case_ast_node(rs, RangeStmt, node);
+ u32 new_flags = mod_flags | Stmt_BreakAllowed | Stmt_ContinueAllowed;
+
+ check_open_scope(c, node);
+ check_label(c, rs->label);
+
+ Type *val = NULL;
+ Type *idx = NULL;
+ Entity *entities[2] = {};
+ isize entity_count = 0;
+
+ AstNode *expr = unparen_expr(rs->expr);
+
+
+ if (is_ast_node_a_range(expr)) {
+ ast_node(ie, BinaryExpr, expr);
+ Operand x = {Addressing_Invalid};
+ Operand y = {Addressing_Invalid};
+
+ check_expr(c, &x, ie->left);
+ if (x.mode == Addressing_Invalid) {
+ goto skip_expr;
+ }
+ check_expr(c, &y, ie->right);
+ if (y.mode == Addressing_Invalid) {
+ goto skip_expr;
+ }
+
+ convert_to_typed(c, &x, y.type, 0);
+ if (x.mode == Addressing_Invalid) {
+ goto skip_expr;
+ }
+ convert_to_typed(c, &y, x.type, 0);
+ if (y.mode == Addressing_Invalid) {
+ goto skip_expr;
+ }
+
+ convert_to_typed(c, &x, default_type(y.type), 0);
+ if (x.mode == Addressing_Invalid) {
+ goto skip_expr;
+ }
+ convert_to_typed(c, &y, default_type(x.type), 0);
+ if (y.mode == Addressing_Invalid) {
+ goto skip_expr;
+ }
+
+ if (!are_types_identical(x.type, y.type)) {
+ if (x.type != t_invalid &&
+ y.type != t_invalid) {
+ gbString xt = type_to_string(x.type);
+ gbString yt = type_to_string(y.type);
+ gbString expr_str = expr_to_string(x.expr);
+ error(ie->op, "Mismatched types in interval expression `%s` : `%s` vs `%s`", expr_str, xt, yt);
+ gb_string_free(expr_str);
+ gb_string_free(yt);
+ gb_string_free(xt);
+ }
+ goto skip_expr;
+ }
+
+ Type *type = x.type;
+ if (!is_type_integer(type) && !is_type_float(type) && !is_type_pointer(type)) {
+ error(ie->op, "Only numerical and pointer types are allowed within interval expressions");
+ goto skip_expr;
+ }
+
+ if (x.mode == Addressing_Constant &&
+ y.mode == Addressing_Constant) {
+ ExactValue a = x.value;
+ ExactValue b = y.value;
+
+ GB_ASSERT(are_types_identical(x.type, y.type));
+
+ TokenKind op = Token_Lt;
+ switch (ie->op.kind) {
+ case Token_Ellipsis: op = Token_LtEq; break;
+ case Token_HalfClosed: op = Token_Lt; break;
+ default: error(ie->op, "Invalid range operator"); break;
+ }
+ bool ok = compare_exact_values(op, a, b);
+ if (!ok) {
+ // TODO(bill): Better error message
+ error(ie->op, "Invalid interval range");
+ goto skip_expr;
+ }
+ }
+
+ if (x.mode != Addressing_Constant) {
+ x.value = ExactValue{};
+ }
+ if (y.mode != Addressing_Constant) {
+ y.value = ExactValue{};
+ }
+
+
+ add_type_and_value(&c->info, ie->left, x.mode, x.type, x.value);
+ add_type_and_value(&c->info, ie->right, y.mode, y.type, y.value);
+ val = type;
+ idx = t_int;
+ } else {
+ Operand operand = {Addressing_Invalid};
+ check_expr_or_type(c, &operand, rs->expr);
+
+ if (operand.mode == Addressing_Type) {
+ if (!is_type_enum(operand.type)) {
+ gbString t = type_to_string(operand.type);
+ error_node(operand.expr, "Cannot iterate over the type `%s`", t);
+ gb_string_free(t);
+ goto skip_expr;
+ } else {
+ val = operand.type;
+ idx = t_int;
+ add_type_info_type(c, operand.type);
+ goto skip_expr;
+ }
+ } else if (operand.mode != Addressing_Invalid) {
+ Type *t = base_type(type_deref(operand.type));
+ switch (t->kind) {
+ case Type_Basic:
+ if (is_type_string(t)) {
+ val = t_rune;
+ idx = t_int;
+ }
+ break;
+ case Type_Array:
+ val = t->Array.elem;
+ idx = t_int;
+ break;
+
+ case Type_DynamicArray:
+ val = t->DynamicArray.elem;
+ idx = t_int;
+ break;
+
+ case Type_Slice:
+ val = t->Slice.elem;
+ idx = t_int;
+ break;
+
+ case Type_Vector:
+ val = t->Vector.elem;
+ idx = t_int;
+ break;
+
+ case Type_Map:
+ val = t->Map.value;
+ idx = t->Map.key;
+ break;
+ }
+ }
+
+ if (val == NULL) {
+ gbString s = expr_to_string(operand.expr);
+ gbString t = type_to_string(operand.type);
+ error_node(operand.expr, "Cannot iterate over `%s` of type `%s`", s, t);
+ gb_string_free(t);
+ gb_string_free(s);
+ }
+ }
+
+ skip_expr:; // NOTE(zhiayang): again, declaring a variable immediately after a label... weird.
+ AstNode *lhs[2] = {rs->value, rs->index};
+ Type * rhs[2] = {val, idx};
+
+ for (isize i = 0; i < 2; i++) {
+ if (lhs[i] == NULL) {
+ continue;
+ }
+ AstNode *name = lhs[i];
+ Type * type = rhs[i];
+
+ Entity *entity = NULL;
+ if (name->kind == AstNode_Ident) {
+ Token token = name->Ident;
+ String str = token.string;
+ Entity *found = NULL;
+
+ if (str_ne(str, str_lit("_"))) {
+ found = current_scope_lookup_entity(c->context.scope, str);
+ }
+ if (found == NULL) {
+ entity = make_entity_variable(c->allocator, c->context.scope, token, type, true);
+ add_entity_definition(&c->info, name, entity);
+ } else {
+ TokenPos pos = found->token.pos;
+ error(token,
+ "Redeclaration of `%.*s` in this scope\n"
+ "\tat %.*s(%td:%td)",
+ LIT(str), LIT(pos.file), pos.line, pos.column);
+ entity = found;
+ }
+ } else {
+ error_node(name, "A variable declaration must be an identifier");
+ }
+
+ if (entity == NULL) {
+ entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
+ }
+
+ entities[entity_count++] = entity;
+
+ if (type == NULL) {
+ entity->type = t_invalid;
+ entity->flags |= EntityFlag_Used;
+ }
+ }
+
+ for (isize i = 0; i < entity_count; i++) {
+ add_entity(c, c->context.scope, entities[i]->identifier, entities[i]);
+ }
+
+ check_stmt(c, rs->body, new_flags);
+
+ check_close_scope(c);
+ case_end;
+
+ case_ast_node(ms, MatchStmt, node);
+ Operand x = {};
+
+ mod_flags |= Stmt_BreakAllowed;
+ check_open_scope(c, node);
+ check_label(c, ms->label); // TODO(bill): What should the label's "scope" be?
+
+ if (ms->init != NULL) {
+ check_stmt(c, ms->init, 0);
+ }
+ if (ms->tag != NULL) {
+ check_expr(c, &x, ms->tag);
+ check_assignment(c, &x, NULL, str_lit("match expression"));
+ } else {
+ x.mode = Addressing_Constant;
+ x.type = t_bool;
+ x.value = exact_value_bool(true);
+
+ Token token = {};
+ token.pos = ast_node_token(ms->body).pos;
+ token.string = str_lit("true");
+ x.expr = ast_ident(c->curr_ast_file, token);
+ }
+ if (is_type_vector(x.type)) {
+ gbString str = type_to_string(x.type);
+ error_node(x.expr, "Invalid match expression type: %s", str);
+ gb_string_free(str);
+ break;
+ }
+
+
+ // NOTE(bill): Check for multiple defaults
+ AstNode *first_default = NULL;
+ ast_node(bs, BlockStmt, ms->body);
+ for_array(i, bs->stmts) {
+ AstNode *stmt = bs->stmts.e[i];
+ AstNode *default_stmt = NULL;
+ if (stmt->kind == AstNode_CaseClause) {
+ ast_node(cc, CaseClause, stmt);
+ if (cc->list.count == 0) {
+ default_stmt = stmt;
+ }
+ } else {
+ error_node(stmt, "Invalid AST - expected case clause");
+ }
+
+ if (default_stmt != NULL) {
+ if (first_default != NULL) {
+ TokenPos pos = ast_node_token(first_default).pos;
+ error_node(stmt,
+ "multiple `default` clauses\n"
+ "\tfirst at %.*s(%td:%td)",
+ LIT(pos.file), pos.line, pos.column);
+ } else {
+ first_default = default_stmt;
+ }
+ }
+ }
+
+ MapTypeAndToken seen = {}; // NOTE(bill): Multimap
+ map_type_and_token_init(&seen, heap_allocator());
+
+ for_array(i, bs->stmts) {
+ AstNode *stmt = bs->stmts.e[i];
+ if (stmt->kind != AstNode_CaseClause) {
+ // NOTE(bill): error handled by above multiple default checker
+ continue;
+ }
+ ast_node(cc, CaseClause, stmt);
+
+ for_array(j, cc->list) {
+ AstNode *expr = unparen_expr(cc->list.e[j]);
+
+ if (is_ast_node_a_range(expr)) {
+ ast_node(ie, BinaryExpr, expr);
+ Operand lhs = {};
+ Operand rhs = {};
+ check_expr(c, &lhs, ie->left);
+ if (x.mode == Addressing_Invalid) {
+ continue;
+ }
+ if (lhs.mode == Addressing_Invalid) {
+ continue;
+ }
+ check_expr(c, &rhs, ie->right);
+ if (rhs.mode == Addressing_Invalid) {
+ continue;
+ }
+
+ if (!is_type_ordered(x.type)) {
+ gbString str = type_to_string(x.type);
+ error_node(x.expr, "Unordered type `%s`, is invalid for an interval expression", str);
+ gb_string_free(str);
+ continue;
+ }
+
+
+ TokenKind op = {};
+
+ Operand a = lhs;
+ Operand b = rhs;
+ check_comparison(c, &a, &x, Token_LtEq);
+ if (a.mode == Addressing_Invalid) {
+ continue;
+ }
+ switch (ie->op.kind) {
+ case Token_Ellipsis: op = Token_GtEq; break;
+ case Token_HalfClosed: op = Token_Gt; break;
+ default: error(ie->op, "Invalid interval operator"); continue;
+ }
+
+ check_comparison(c, &b, &x, op);
+ if (b.mode == Addressing_Invalid) {
+ continue;
+ }
+
+ switch (ie->op.kind) {
+ case Token_Ellipsis: op = Token_LtEq; break;
+ case Token_HalfClosed: op = Token_Lt; break;
+ default: error(ie->op, "Invalid interval operator"); continue;
+ }
+
+ Operand a1 = lhs;
+ Operand b1 = rhs;
+ check_comparison(c, &a1, &b1, op);
+ } else {
+ Operand y = {};
+ check_expr(c, &y, expr);
+ if (x.mode == Addressing_Invalid ||
+ y.mode == Addressing_Invalid) {
+ continue;
+ }
+
+ convert_to_typed(c, &y, x.type, 0);
+ if (y.mode == Addressing_Invalid) {
+ continue;
+ }
+
+ // NOTE(bill): the ordering here matters
+ Operand z = y;
+ check_comparison(c, &z, &x, Token_CmpEq);
+ if (z.mode == Addressing_Invalid) {
+ continue;
+ }
+ if (y.mode != Addressing_Constant) {
+ continue;
+ }
+
+
+ if (y.value.kind != ExactValue_Invalid) {
+ HashKey key = hash_exact_value(y.value);
+ TypeAndToken *found = map_type_and_token_get(&seen, key);
+ if (found != NULL) {
+ gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+ isize count = map_type_and_token_multi_count(&seen, key);
+ TypeAndToken *taps = gb_alloc_array(c->tmp_allocator, TypeAndToken, count);
+
+ map_type_and_token_multi_get_all(&seen, key, taps);
+ bool continue_outer = false;
+
+ for (isize i = 0; i < count; i++) {
+ TypeAndToken tap = taps[i];
+ if (are_types_identical(y.type, tap.type)) {
+ TokenPos pos = tap.token.pos;
+ gbString expr_str = expr_to_string(y.expr);
+ error_node(y.expr,
+ "Duplicate case `%s`\n"
+ "\tprevious case at %.*s(%td:%td)",
+ expr_str,
+ LIT(pos.file), pos.line, pos.column);
+ gb_string_free(expr_str);
+ continue_outer = true;
+ break;
+ }
+ }
+
+ gb_temp_arena_memory_end(tmp);
+
+ if (continue_outer) {
+ continue;
+ }
+ }
+ TypeAndToken tap = {y.type, ast_node_token(y.expr)};
+ map_type_and_token_multi_insert(&seen, key, tap);
+ }
+ }
+ }
+
+ check_open_scope(c, stmt);
+ u32 ft_flags = mod_flags;
+ if (i+1 < bs->stmts.count) {
+ ft_flags |= Stmt_FallthroughAllowed;
+ }
+ check_stmt_list(c, cc->stmts, ft_flags);
+ check_close_scope(c);
+ }
+
+ map_type_and_token_destroy(&seen);
+
+ check_close_scope(c);
+ case_end;
+
+ case_ast_node(ms, TypeMatchStmt, node);
+ Operand x = {};
+
+ mod_flags |= Stmt_BreakAllowed;
+ check_open_scope(c, node);
+ check_label(c, ms->label); // TODO(bill): What should the label's "scope" be?
+
+ MatchTypeKind match_type_kind = MatchType_Invalid;
+
+ if (ms->tag->kind != AstNode_AssignStmt) {
+ error_node(ms->tag, "Expected an `in` assignment for this type match statement");
+ break;
+ }
+
+ ast_node(as, AssignStmt, ms->tag);
+ Token as_token = ast_node_token(ms->tag);
+ if (as->lhs.count != 1) {
+ syntax_error(as_token, "Expected 1 name before `in`");
+ break;
+ }
+ if (as->rhs.count != 1) {
+ syntax_error(as_token, "Expected 1 expression after `in`");
+ break;
+ }
+ AstNode *lhs = as->lhs.e[0];
+ AstNode *rhs = as->rhs.e[0];
+
+ check_expr(c, &x, rhs);
+ check_assignment(c, &x, NULL, str_lit("type match expression"));
+ match_type_kind = check_valid_type_match_type(x.type);
+ if (check_valid_type_match_type(x.type) == MatchType_Invalid) {
+ gbString str = type_to_string(x.type);
+ error_node(x.expr, "Invalid type for this type match expression, got `%s`", str);
+ gb_string_free(str);
+ break;
+ }
+
+ bool is_ptr = is_type_pointer(x.type);
+
+ // NOTE(bill): Check for multiple defaults
+ AstNode *first_default = NULL;
+ ast_node(bs, BlockStmt, ms->body);
+ for_array(i, bs->stmts) {
+ AstNode *stmt = bs->stmts.e[i];
+ AstNode *default_stmt = NULL;
+ if (stmt->kind == AstNode_CaseClause) {
+ ast_node(cc, CaseClause, stmt);
+ if (cc->list.count == 0) {
+ default_stmt = stmt;
+ }
+ } else {
+ error_node(stmt, "Invalid AST - expected case clause");
+ }
+
+ if (default_stmt != NULL) {
+ if (first_default != NULL) {
+ TokenPos pos = ast_node_token(first_default).pos;
+ error_node(stmt,
+ "Multiple `default` clauses\n"
+ "\tfirst at %.*s(%td:%td)", LIT(pos.file), pos.line, pos.column);
+ } else {
+ first_default = default_stmt;
+ }
+ }
+ }
+
+
+ if (lhs->kind != AstNode_Ident) {
+ error_node(rhs, "Expected an identifier, got `%.*s`", LIT(ast_node_strings[rhs->kind]));
+ break;
+ }
+
+
+ MapBool seen = {}; // Multimap
+ map_bool_init(&seen, heap_allocator());
+
+ for_array(i, bs->stmts) {
+ AstNode *stmt = bs->stmts.e[i];
+ if (stmt->kind != AstNode_CaseClause) {
+ // NOTE(bill): error handled by above multiple default checker
+ continue;
+ }
+ ast_node(cc, CaseClause, stmt);
+
+ // TODO(bill): Make robust
+ Type *bt = base_type(type_deref(x.type));
+
+ Type *case_type = NULL;
+ for_array(type_index, cc->list) {
+ AstNode *type_expr = cc->list.e[type_index];
+ if (type_expr != NULL) { // Otherwise it's a default expression
+ Operand y = {};
+ check_expr_or_type(c, &y, type_expr);
+
+ if (match_type_kind == MatchType_Union) {
+ GB_ASSERT(is_type_union(bt));
+ bool tag_type_found = false;
+ for (isize i = 0; i < bt->Record.variant_count; i++) {
+ Entity *f = bt->Record.variants[i];
+ if (are_types_identical(f->type, y.type)) {
+ tag_type_found = true;
+ break;
+ }
+ }
+ if (!tag_type_found) {
+ gbString type_str = type_to_string(y.type);
+ error_node(y.expr, "Unknown tag type, got `%s`", type_str);
+ gb_string_free(type_str);
+ continue;
+ }
+ case_type = y.type;
+ } else if (match_type_kind == MatchType_Any) {
+ case_type = y.type;
+ } else {
+ GB_PANIC("Unknown type to type match statement");
+ }
+
+ HashKey key = hash_pointer(y.type);
+ bool *found = map_bool_get(&seen, key);
+ if (found) {
+ TokenPos pos = cc->token.pos;
+ gbString expr_str = expr_to_string(y.expr);
+ error_node(y.expr,
+ "Duplicate type case `%s`\n"
+ "\tprevious type case at %.*s(%td:%td)",
+ expr_str,
+ LIT(pos.file), pos.line, pos.column);
+ gb_string_free(expr_str);
+ break;
+ }
+ map_bool_set(&seen, key, cast(bool)true);
+ }
+ }
+
+ if (is_ptr &&
+ !is_type_any(type_deref(x.type)) &&
+ cc->list.count == 1 &&
+ case_type != NULL) {
+ case_type = make_type_pointer(c->allocator, case_type);
+ }
+
+ if (cc->list.count > 1) {
+ case_type = NULL;
+ }
+ if (case_type == NULL) {
+ case_type = x.type;
+ }
+ add_type_info_type(c, case_type);
+
+ check_open_scope(c, stmt);
+ {
+ Entity *tag_var = make_entity_variable(c->allocator, c->context.scope, lhs->Ident, case_type, false);
+ tag_var->flags |= EntityFlag_Used;
+ tag_var->flags |= EntityFlag_Value;
+ add_entity(c, c->context.scope, lhs, tag_var);
+ add_entity_use(c, lhs, tag_var);
+ add_implicit_entity(c, stmt, tag_var);
+ }
+ check_stmt_list(c, cc->stmts, mod_flags);
+ check_close_scope(c);
+ }
+ map_bool_destroy(&seen);
+
+ check_close_scope(c);
+ case_end;
+
+
+ case_ast_node(ds, DeferStmt, node);
+ if (is_ast_node_decl(ds->stmt)) {
+ error(ds->token, "You cannot defer a declaration");
+ } else {
+ bool out_in_defer = c->context.in_defer;
+ c->context.in_defer = true;
+ check_stmt(c, ds->stmt, 0);
+ c->context.in_defer = out_in_defer;
+ }
+ case_end;
+
+ case_ast_node(bs, BranchStmt, node);
+ Token token = bs->token;
+ switch (token.kind) {
+ case Token_break:
+ if ((flags & Stmt_BreakAllowed) == 0) {
+ error(token, "`break` only allowed in loops or `match` statements");
+ }
+ break;
+ case Token_continue:
+ if ((flags & Stmt_ContinueAllowed) == 0) {
+ error(token, "`continue` only allowed in loops");
+ }
+ break;
+ case Token_fallthrough:
+ if ((flags & Stmt_FallthroughAllowed) == 0) {
+ error(token, "`fallthrough` statement in illegal position");
+ }
+ break;
+ default:
+ error(token, "Invalid AST: Branch Statement `%.*s`", LIT(token.string));
+ break;
+ }
+
+ if (bs->label != NULL) {
+ if (bs->label->kind != AstNode_Ident) {
+ error_node(bs->label, "A branch statement's label name must be an identifier");
+ return;
+ }
+ AstNode *ident = bs->label;
+ String name = ident->Ident.string;
+ Operand o = {};
+ Entity *e = check_ident(c, &o, ident, NULL, NULL, false);
+ if (e == NULL) {
+ error_node(ident, "Undeclared label name: %.*s", LIT(name));
+ return;
+ }
+ add_entity_use(c, ident, e);
+ if (e->kind != Entity_Label) {
+ error_node(ident, "`%.*s` is not a label", LIT(name));
+ return;
+ }
+ }
+
+ case_end;
+
+ case_ast_node(us, UsingStmt, node);
+ if (us->list.count == 0) {
+ error(us->token, "Empty `using` list");
+ return;
+ }
+ for_array(i, us->list) {
+ AstNode *expr = unparen_expr(us->list.e[0]);
+ Entity *e = NULL;
+
+ bool is_selector = false;
+ if (expr->kind == AstNode_Ident) {
+ Operand o = {};
+ e = check_ident(c, &o, expr, NULL, NULL, true);
+ } else if (expr->kind == AstNode_SelectorExpr) {
+ Operand o = {};
+ e = check_selector(c, &o, expr, NULL);
+ is_selector = true;
+ } else if (expr->kind == AstNode_Implicit) {
+ error(us->token, "`using` applied to an implicit value");
+ continue;
+ }
+
+ if (!check_using_stmt_entity(c, us, expr, is_selector, e)) {
+ return;
+ }
+ }
+ case_end;
+
+
+ case_ast_node(pa, PushAllocator, node);
+ Operand op = {};
+ check_expr(c, &op, pa->expr);
+ check_assignment(c, &op, t_allocator, str_lit("argument to push_allocator"));
+ check_stmt(c, pa->body, mod_flags);
+ case_end;
+
+
+ case_ast_node(pa, PushContext, node);
+ Operand op = {};
+ check_expr(c, &op, pa->expr);
+ check_assignment(c, &op, t_context, str_lit("argument to push_context"));
+ check_stmt(c, pa->body, mod_flags);
+ case_end;
+
+
+ case_ast_node(vd, ValueDecl, node);
+ GB_ASSERT(!c->context.scope->is_file);
+ if (!vd->is_var) {
+ // NOTE(bill): Handled elsewhere
+ } else {
+ Entity **entities = gb_alloc_array(c->allocator, Entity *, vd->names.count);
+ isize entity_count = 0;
+
+ if (vd->flags & VarDeclFlag_thread_local) {
+ vd->flags &= ~VarDeclFlag_thread_local;
+ error_node(node, "`thread_local` may only be applied to a variable declaration");
+ }
+
+ for_array(i, vd->names) {
+ AstNode *name = vd->names.e[i];
+ Entity *entity = NULL;
+ if (name->kind != AstNode_Ident) {
+ error_node(name, "A variable declaration must be an identifier");
+ } else {
+ Token token = name->Ident;
+ String str = token.string;
+ Entity *found = NULL;
+ // NOTE(bill): Ignore assignments to `_`
+ if (str_ne(str, str_lit("_"))) {
+ found = current_scope_lookup_entity(c->context.scope, str);
+ }
+ if (found == NULL) {
+ entity = make_entity_variable(c->allocator, c->context.scope, token, NULL, (vd->flags&VarDeclFlag_immutable) != 0);
+ entity->identifier = name;
+ } else {
+ TokenPos pos = found->token.pos;
+ error(token,
+ "Redeclaration of `%.*s` in this scope\n"
+ "\tat %.*s(%td:%td)",
+ LIT(str), LIT(pos.file), pos.line, pos.column);
+ entity = found;
+ }
+ }
+ if (entity == NULL) {
+ entity = make_entity_dummy_variable(c->allocator, c->global_scope, ast_node_token(name));
+ }
+ entity->parent_proc_decl = c->context.curr_proc_decl;
+ entities[entity_count++] = entity;
+ }
+
+ Type *init_type = NULL;
+ if (vd->type) {
+ init_type = check_type_extra(c, vd->type, NULL);
+ if (init_type == NULL) {
+ init_type = t_invalid;
+ }
+ }
+
+ for (isize i = 0; i < entity_count; i++) {
+ Entity *e = entities[i];
+ GB_ASSERT(e != NULL);
+ if (e->flags & EntityFlag_Visited) {
+ e->type = t_invalid;
+ continue;
+ }
+ e->flags |= EntityFlag_Visited;
+
+ if (e->type == NULL) {
+ e->type = init_type;
+ }
+ }
+
+ check_arity_match(c, vd);
+ check_init_variables(c, entities, entity_count, vd->values, str_lit("variable declaration"));
+
+ for (isize i = 0; i < entity_count; i++) {
+ add_entity(c, c->context.scope, entities[i]->identifier, entities[i]);
+ }
+
+ if ((vd->flags & VarDeclFlag_using) != 0) {
+ Token token = ast_node_token(node);
+ if (vd->type != NULL && entity_count > 1) {
+ error(token, "`using` can only be applied to one variable of the same type");
+ // TODO(bill): Should a `continue` happen here?
+ }
+
+ for (isize entity_index = 0; entity_index < entity_count; entity_index++) {
+ Entity *e = entities[entity_index];
+ if (e == NULL) {
+ continue;
+ }
+ if (e->kind != Entity_Variable) {
+ continue;
+ }
+ bool is_immutable = e->Variable.is_immutable;
+ String name = e->token.string;
+ Type *t = base_type(type_deref(e->type));
+
+ if (is_type_struct(t) || is_type_raw_union(t)) {
+ Scope **found = map_scope_get(&c->info.scopes, hash_pointer(t->Record.node));
+ GB_ASSERT(found != NULL);
+ for_array(i, (*found)->elements.entries) {
+ Entity *f = (*found)->elements.entries.e[i].value;
+ if (f->kind == Entity_Variable) {
+ Entity *uvar = make_entity_using_variable(c->allocator, e, f->token, f->type);
+ uvar->Variable.is_immutable = is_immutable;
+ Entity *prev = scope_insert_entity(c->context.scope, uvar);
+ if (prev != NULL) {
+ error(token, "Namespace collision while `using` `%.*s` of: %.*s", LIT(name), LIT(prev->token.string));
+ return;
+ }
+ }
+ }
+ } else {
+ // NOTE(bill): skip the rest to remove extra errors
+ error(token, "`using` can only be applied to variables of type struct or raw_union");
+ return;
+ }
+ }
+ }
+ }
+ case_end;
+ }
+}