aboutsummaryrefslogtreecommitdiff
path: root/src/check_stmt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/check_stmt.cpp')
-rw-r--r--src/check_stmt.cpp230
1 files changed, 186 insertions, 44 deletions
diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp
index 1014b8c46..840df6ad9 100644
--- a/src/check_stmt.cpp
+++ b/src/check_stmt.cpp
@@ -454,7 +454,7 @@ bool check_using_stmt_entity(Checker *c, AstNodeUsingStmt *us, AstNode *expr, bo
case Entity_TypeName: {
Type *t = base_type(e->type);
if (t->kind == Type_Enum) {
- for (isize i = 0; i < t->Enum.field_count; i++) {
+ for_array(i, t->Enum.fields) {
Entity *f = t->Enum.fields[i];
if (!is_entity_exported(f)) continue;
@@ -555,6 +555,52 @@ bool check_using_stmt_entity(Checker *c, AstNodeUsingStmt *us, AstNode *expr, bo
return true;
}
+
+struct TypeAndToken {
+ Type *type;
+ Token token;
+};
+
+void add_constant_switch_case(Checker *c, Map<TypeAndToken> *seen, Operand operand, bool use_expr = true) {
+ if (operand.value.kind == ExactValue_Invalid) {
+ return;
+ }
+ HashKey key = hash_exact_value(operand.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);
+ for (isize i = 0; i < count; i++) {
+ TypeAndToken tap = taps[i];
+ if (are_types_identical(operand.type, tap.type)) {
+ TokenPos pos = tap.token.pos;
+ if (use_expr) {
+ gbString expr_str = expr_to_string(operand.expr);
+ error(operand.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);
+ } else {
+ error(operand.expr,
+ "Duplicate case found with previous case at %.*s(%td:%td)",
+ LIT(pos.file), pos.line, pos.column);
+ }
+ return;
+ }
+ }
+ }
+
+ TypeAndToken tap = {operand.type, ast_node_token(operand.expr)};
+ multi_map_insert(seen, key, tap);
+}
+
void check_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
ast_node(ss, SwitchStmt, node);
@@ -611,12 +657,16 @@ void check_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
}
}
- struct TypeAndToken {
- Type *type;
- Token token;
- };
+ bool complete = ss->complete;
+
+ if (complete) {
+ if (!is_type_enum(x.type)) {
+ error(x.expr, "#complete switch statement can be only used with an enum type");
+ complete = false;
+ }
+ }
- Map<TypeAndToken> seen = {}; // NOTE(bill): Multimap
+ Map<TypeAndToken> seen = {}; // NOTE(bill): Multimap, Key: ExactValue
map_init(&seen, heap_allocator());
defer (map_destroy(&seen));
@@ -680,9 +730,48 @@ void check_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
default: error(ie->op, "Invalid interval operator"); continue;
}
+
+
Operand a1 = lhs;
Operand b1 = rhs;
check_comparison(c, &a1, &b1, op);
+ if (complete) {
+ if (lhs.mode != Addressing_Constant) {
+ error(lhs.expr, "#complete switch statement only allows constant case clauses");
+ }
+ if (rhs.mode != Addressing_Constant) {
+ error(rhs.expr, "#complete switch statement only allows constant case clauses");
+ }
+ }
+
+ if (a1.mode != Addressing_Invalid &&
+ lhs.mode == Addressing_Constant && rhs.mode == Addressing_Constant) {
+ ExactValue start = lhs.value;
+ ExactValue end = rhs.value;
+ ExactValue one = exact_value_i64(1);
+ match_exact_values(&one, &start);
+ for (ExactValue i = start;
+ compare_exact_values(op, i, end);
+ i = exact_value_add(i, one)) {
+ bool use_expr = false;
+ Operand x = lhs;
+ x.value = i;
+ if (compare_exact_values(Token_CmpEq, i, start)) {
+ use_expr = true;
+ } else if (compare_exact_values(Token_CmpEq, i, end)) {
+ x = rhs;
+ use_expr = true;
+ }
+ add_constant_switch_case(c, &seen, x, use_expr);
+ }
+ } else {
+ if (lhs.mode == Addressing_Constant) {
+ add_constant_switch_case(c, &seen, lhs);
+ }
+ if (rhs.mode == Addressing_Constant && op == Token_LtEq) {
+ add_constant_switch_case(c, &seen, rhs);
+ }
+ }
} else {
Operand y = {};
check_expr(c, &y, expr);
@@ -704,47 +793,13 @@ void check_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
continue;
}
if (y.mode != Addressing_Constant) {
+ if (complete) {
+ error(y.expr, "#complete switch statement only allows constant case clauses");
+ }
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);
- }
+ add_constant_switch_case(c, &seen, y);
}
}
@@ -752,6 +807,47 @@ void check_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
check_stmt_list(c, cc->stmts, mod_flags);
check_close_scope(c);
}
+
+ if (complete) {
+ Type *et = base_type(x.type);
+ GB_ASSERT(is_type_enum(et));
+ auto fields = et->Enum.fields;
+
+ gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+ defer (gb_temp_arena_memory_end(tmp));
+
+ Array<Entity *> unhandled = {};
+ array_init(&unhandled, c->tmp_allocator, fields.count);
+
+ for_array(i, fields) {
+ Entity *f = fields[i];
+ if (f->kind != Entity_Constant) {
+ continue;
+ }
+ ExactValue v = f->Constant.value;
+ HashKey key = hash_exact_value(v);
+ auto found = map_get(&seen, key);
+ if (!found) {
+ array_add(&unhandled, f);
+ }
+ }
+
+ if (unhandled.count > 0) {
+ if (unhandled.count == 1) {
+ error_no_newline(node, "Unhandled switch case: ");
+ } else {
+ error_no_newline(node, "Unhandled switch cases: ");
+ }
+ for_array(i, unhandled) {
+ Entity *f = unhandled[i];
+ if (i > 0) {
+ gb_printf_err(", ");
+ }
+ gb_printf_err("%.*s", LIT(f->token.string));
+ }
+ gb_printf_err("\n");
+ }
+ }
}
@@ -812,6 +908,14 @@ void check_type_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
return;
}
+ bool complete = ss->complete;
+ if (complete) {
+ if (switch_kind != Switch_Union) {
+ error(node, "#complete switch statement may only be used with a union");
+ complete = false;
+ }
+ }
+
bool is_ptr = is_type_pointer(x.type);
// NOTE(bill): Check for multiple defaults
@@ -935,6 +1039,44 @@ void check_type_switch_stmt(Checker *c, AstNode *node, u32 mod_flags) {
check_stmt_list(c, cc->stmts, mod_flags);
check_close_scope(c);
}
+
+ if (complete) {
+ Type *ut = base_type(x.type);
+ GB_ASSERT(is_type_union(ut));
+ auto variants = ut->Union.variants;
+
+ gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&c->tmp_arena);
+ defer (gb_temp_arena_memory_end(tmp));
+
+ Array<Type *> unhandled = {};
+ array_init(&unhandled, c->tmp_allocator, variants.count);
+
+ for_array(i, variants) {
+ Type *t = variants[i];
+ auto found = map_get(&seen, hash_type(t));
+ if (!found) {
+ array_add(&unhandled, t);
+ }
+ }
+
+ if (unhandled.count > 0) {
+ if (unhandled.count == 1) {
+ error_no_newline(node, "Unhandled switch case: ");
+ } else {
+ error_no_newline(node, "Unhandled switch cases: ");
+ }
+ for_array(i, unhandled) {
+ Type *t = unhandled[i];
+ if (i > 0) {
+ gb_printf_err(", ");
+ }
+ gbString s = type_to_string(t);
+ gb_printf_err("%s", s);
+ gb_string_free(s);
+ }
+ gb_printf_err("\n");
+ }
+ }
}
void check_stmt_internal(Checker *c, AstNode *node, u32 flags) {