|
@@ -41,7 +41,6 @@ void check_stmt_list(Checker *c, Array<AstNode *> stmts, u32 flags) {
|
|
|
|
|
|
check_stmt(c, n, new_flags);
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
bool check_is_terminating_list(Array<AstNode *> stmts) {
|
|
@@ -576,6 +575,207 @@ bool check_using_stmt_entity(Checker *c, AstNodeUsingStmt *us, AstNode *expr, bo
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+void check_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
|
|
|
+ ast_node(ss, SwitchStmt, node);
|
|
|
+
|
|
|
+ Operand x = {};
|
|
|
+
|
|
|
+ mod_flags |= Stmt_BreakAllowed | Stmt_FallthroughAllowed;
|
|
|
+ check_open_scope(c, node);
|
|
|
+ defer (check_close_scope(c));
|
|
|
+
|
|
|
+ check_label(c, ss->label); // TODO(bill): What should the label's "scope" be?
|
|
|
+
|
|
|
+ if (ss->init != nullptr) {
|
|
|
+ check_stmt(c, ss->init, 0);
|
|
|
+ }
|
|
|
+ if (ss->tag != nullptr) {
|
|
|
+ check_expr(c, &x, ss->tag);
|
|
|
+ check_assignment(c, &x, nullptr, str_lit("switch expression"));
|
|
|
+ } else {
|
|
|
+ x.mode = Addressing_Constant;
|
|
|
+ x.type = t_bool;
|
|
|
+ x.value = exact_value_bool(true);
|
|
|
+
|
|
|
+ Token token = {};
|
|
|
+ token.pos = ast_node_token(ss->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(x.expr, "Invalid switch expression type: %s", str);
|
|
|
+ gb_string_free(str);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // NOTE(bill): Check for multiple defaults
|
|
|
+ AstNode *first_default = nullptr;
|
|
|
+ ast_node(bs, BlockStmt, ss->body);
|
|
|
+ for_array(i, bs->stmts) {
|
|
|
+ AstNode *stmt = bs->stmts[i];
|
|
|
+ AstNode *default_stmt = nullptr;
|
|
|
+ if (stmt->kind == AstNode_CaseClause) {
|
|
|
+ ast_node(cc, CaseClause, stmt);
|
|
|
+ if (cc->list.count == 0) {
|
|
|
+ default_stmt = stmt;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ error(stmt, "Invalid AST - expected case clause");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (default_stmt != nullptr) {
|
|
|
+ if (first_default != nullptr) {
|
|
|
+ TokenPos pos = ast_node_token(first_default).pos;
|
|
|
+ error(stmt,
|
|
|
+ "multiple default clauses\n"
|
|
|
+ "\tfirst at %.*s(%td:%td)",
|
|
|
+ LIT(pos.file), pos.line, pos.column);
|
|
|
+ } else {
|
|
|
+ first_default = default_stmt;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<TypeAndToken> seen = {}; // NOTE(bill): Multimap
|
|
|
+ map_init(&seen, heap_allocator());
|
|
|
+ defer (map_destroy(&seen));
|
|
|
+
|
|
|
+ for_array(stmt_index, bs->stmts) {
|
|
|
+ AstNode *stmt = bs->stmts[stmt_index];
|
|
|
+ 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[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(expr, "Unordered type '%s', is invalid for an interval expression", str);
|
|
|
+ gb_string_free(str);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ TokenKind op = Token_Invalid;
|
|
|
+
|
|
|
+ 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);
|
|
|
+ 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_get(&seen, key);
|
|
|
+ if (found != nullptr) {
|
|
|
+ gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
|
|
|
+ defer (gb_temp_arena_memory_end(tmp));
|
|
|
+
|
|
|
+ isize count = multi_map_count(&seen, key);
|
|
|
+ TypeAndToken *taps = gb_alloc_array(c->tmp_allocator, TypeAndToken, count);
|
|
|
+
|
|
|
+ multi_map_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(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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (continue_outer) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ TypeAndToken tap = {y.type, ast_node_token(y.expr)};
|
|
|
+ multi_map_insert(&seen, key, tap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ check_open_scope(c, stmt);
|
|
|
+ check_stmt_list(c, cc->stmts, mod_flags);
|
|
|
+ check_close_scope(c);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
|
|
|
u32 mod_flags = flags & (~Stmt_FallthroughAllowed);
|
|
|
switch (node->kind) {
|
|
@@ -940,7 +1140,6 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
|
|
|
check_assignment(c, &operands[i], e->type, str_lit("return statement"));
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
case_end;
|
|
|
|
|
|
case_ast_node(fs, ForStmt, node);
|
|
@@ -1193,202 +1392,7 @@ void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {
|
|
|
case_end;
|
|
|
|
|
|
case_ast_node(ss, SwitchStmt, node);
|
|
|
- Operand x = {};
|
|
|
-
|
|
|
- mod_flags |= Stmt_BreakAllowed | Stmt_FallthroughAllowed;
|
|
|
- check_open_scope(c, node);
|
|
|
- defer (check_close_scope(c));
|
|
|
-
|
|
|
- check_label(c, ss->label); // TODO(bill): What should the label's "scope" be?
|
|
|
-
|
|
|
- if (ss->init != nullptr) {
|
|
|
- check_stmt(c, ss->init, 0);
|
|
|
- }
|
|
|
- if (ss->tag != nullptr) {
|
|
|
- check_expr(c, &x, ss->tag);
|
|
|
- check_assignment(c, &x, nullptr, str_lit("switch expression"));
|
|
|
- } else {
|
|
|
- x.mode = Addressing_Constant;
|
|
|
- x.type = t_bool;
|
|
|
- x.value = exact_value_bool(true);
|
|
|
-
|
|
|
- Token token = {};
|
|
|
- token.pos = ast_node_token(ss->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(x.expr, "Invalid switch expression type: %s", str);
|
|
|
- gb_string_free(str);
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // NOTE(bill): Check for multiple defaults
|
|
|
- AstNode *first_default = nullptr;
|
|
|
- ast_node(bs, BlockStmt, ss->body);
|
|
|
- for_array(i, bs->stmts) {
|
|
|
- AstNode *stmt = bs->stmts[i];
|
|
|
- AstNode *default_stmt = nullptr;
|
|
|
- if (stmt->kind == AstNode_CaseClause) {
|
|
|
- ast_node(cc, CaseClause, stmt);
|
|
|
- if (cc->list.count == 0) {
|
|
|
- default_stmt = stmt;
|
|
|
- }
|
|
|
- } else {
|
|
|
- error(stmt, "Invalid AST - expected case clause");
|
|
|
- }
|
|
|
-
|
|
|
- if (default_stmt != nullptr) {
|
|
|
- if (first_default != nullptr) {
|
|
|
- TokenPos pos = ast_node_token(first_default).pos;
|
|
|
- error(stmt,
|
|
|
- "multiple default clauses\n"
|
|
|
- "\tfirst at %.*s(%td:%td)",
|
|
|
- LIT(pos.file), pos.line, pos.column);
|
|
|
- } else {
|
|
|
- first_default = default_stmt;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Map<TypeAndToken> seen = {}; // NOTE(bill): Multimap
|
|
|
- map_init(&seen, heap_allocator());
|
|
|
- defer (map_destroy(&seen));
|
|
|
-
|
|
|
- for_array(stmt_index, bs->stmts) {
|
|
|
- AstNode *stmt = bs->stmts[stmt_index];
|
|
|
- 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[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(expr, "Unordered type '%s', is invalid for an interval expression", str);
|
|
|
- gb_string_free(str);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- TokenKind op = Token_Invalid;
|
|
|
-
|
|
|
- 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);
|
|
|
- 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_get(&seen, key);
|
|
|
- if (found != nullptr) {
|
|
|
- gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
|
|
|
- defer (gb_temp_arena_memory_end(tmp));
|
|
|
-
|
|
|
- isize count = multi_map_count(&seen, key);
|
|
|
- TypeAndToken *taps = gb_alloc_array(c->tmp_allocator, TypeAndToken, count);
|
|
|
-
|
|
|
- multi_map_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(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;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if (continue_outer) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- }
|
|
|
- TypeAndToken tap = {y.type, ast_node_token(y.expr)};
|
|
|
- multi_map_insert(&seen, key, tap);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- check_open_scope(c, stmt);
|
|
|
- check_stmt_list(c, cc->stmts, mod_flags);
|
|
|
- check_close_scope(c);
|
|
|
- }
|
|
|
+ check_switch_stmt(c, node, mod_flags);
|
|
|
case_end;
|
|
|
|
|
|
case_ast_node(ss, TypeSwitchStmt, node);
|