Require exhaustive enum switching.

This commit is contained in:
Christoffer Lerno
2023-07-09 16:24:56 +02:00
parent 943d010dfc
commit cd73b9bc42
3 changed files with 57 additions and 5 deletions

View File

@@ -3322,6 +3322,11 @@ INLINE bool expr_is_const_string(Expr *expr)
return expr->expr_kind == EXPR_CONST && expr->const_expr.const_kind == CONST_STRING;
}
INLINE bool expr_is_const_enum(Expr *expr)
{
return expr->expr_kind == EXPR_CONST && expr->const_expr.const_kind == CONST_ENUM;
}
INLINE bool expr_is_const_pointer(Expr *expr)
{
return expr->expr_kind == EXPR_CONST && expr->const_expr.const_kind == CONST_POINTER;

View File

@@ -2101,6 +2101,48 @@ static inline bool sema_check_value_case(SemaContext *context, Type *switch_type
return true;
}
INLINE const char *create_missing_enums_in_switch_error(Ast **cases, unsigned case_count, Decl **enums)
{
unsigned missing = vec_size(enums) - case_count;
scratch_buffer_clear();
if (missing == 1)
{
scratch_buffer_append("Enum value ");
}
else
{
scratch_buffer_printf("%u enum values were not handled in the switch: ", missing);
}
unsigned printed = 0;
FOREACH_BEGIN(Decl *decl, enums)
for (unsigned i = 0; i < case_count; i++)
{
Expr *e = cases[i]->case_stmt.expr;
assert(expr_is_const_enum(e));
if (e->const_expr.enum_err_val == decl) goto CONTINUE;
}
if (++printed != 1)
{
scratch_buffer_append(printed == missing ? " and " : ", ");
}
scratch_buffer_append(decl->name);
if (printed > 2 && missing > 3)
{
scratch_buffer_append(", ...");
goto DONE;
}
if (printed == missing) goto DONE;
CONTINUE:;
FOREACH_END();
DONE:;
if (missing == 1)
{
scratch_buffer_append(" was not handled in the switch - either add it or add 'default'.");
return scratch_buffer_to_string();
}
scratch_buffer_append(" - either add them or use 'default'.");
return scratch_buffer_to_string();
}
static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, SourceSpan expr_span, Type *switch_type, Ast **cases, ExprAnySwitch *any_switch, Decl *var_holder)
{
bool use_type_id = false;
@@ -2165,7 +2207,7 @@ static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, Sourc
POP_NEXT();
}
if (!exhaustive && is_enum_switch && case_count == vec_size(flat->decl->enums.values)) exhaustive = true;
if (!exhaustive && is_enum_switch) exhaustive = case_count >= vec_size(flat->decl->enums.values);
bool all_jump_end = exhaustive;
for (unsigned i = 0; i < case_count; i++)
{
@@ -2217,6 +2259,11 @@ static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, Sourc
all_jump_end &= context->active_scope.jump_end;
SCOPE_END;
}
if (is_enum_switch && !exhaustive && success)
{
SEMA_ERROR(statement, create_missing_enums_in_switch_error(cases, case_count, flat->decl->enums.values));
success = false;
}
statement->flow.no_exit = all_jump_end;
statement->switch_stmt.flow.if_chain = if_chain || max_ranged;
return success;

View File

@@ -91,14 +91,14 @@ enum Baz
fn void test_missing_all_cases(Baz x)
{
switch (x) // -error: 4 enumeration values not handled in switch: A, B, C, ...
switch (x) // #error: not handled in the switch: A, B, C, ...
{
}
}
fn void test_missing_some_cases(Baz x)
{
switch (x) // -error: 4 enumeration B, C and D not handled in switch
switch (x) // #error: not handled in the switch: B, C and D
{
case A:
break;
@@ -107,7 +107,7 @@ fn void test_missing_some_cases(Baz x)
fn void test_missing_some_cases2(Baz x)
{
switch (x) // -error: 4 enumeration B and D not handled in switch
switch (x) // #error: B and D
{
case C:
case A:
@@ -117,7 +117,7 @@ fn void test_missing_some_cases2(Baz x)
fn void test_missing_some_cases3(Baz x)
{
switch (x) // -error: 4 enumeration B and D not handled in switch
switch (x) // #error: Enum value D was not handled in the switch
{
case B:
case C: