Show code that caused unreachable code #2207

`$echo` would suppress warning about unreachable code. #2205
This commit is contained in:
Christoffer Lerno
2025-06-15 00:37:28 +02:00
parent f79f6d4001
commit dda2d2ecbe
7 changed files with 69 additions and 48 deletions

View File

@@ -21,6 +21,7 @@
- Add `--sources` build option to add additional files to compile. #2097
- Support untyped second argument for operator overloading.
- The form-feed character '\f' is no longer valid white space.
- Show code that caused unreachable code #2207
### Fixes
- `-2147483648`, MIN literals work correctly.
@@ -35,6 +36,7 @@
- @operator macro using untyped parameter causes compiler segfault #2200.
- Make `unreachable()` only panic in safe mode.
- `cflags` additions for targets was not handed properly. #2209
- `$echo` would suppress warning about unreachable code. #2205
### Stdlib changes
- Deprecate `String.is_zstr` and `String.quick_zstr` #2188.

View File

@@ -1536,14 +1536,19 @@ typedef struct Module_
} Module;
typedef struct EndJump_
{
bool active;
SourceSpan span;
} EndJump;
typedef struct DynamicScope_
{
ScopeId scope_id;
bool allow_dead_code : 1;
bool jump_end : 1;
bool is_dead : 1;
bool is_invalid : 1;
EndJump end_jump;
ScopeFlags flags;
unsigned label_start;
unsigned current_local;

View File

@@ -2627,13 +2627,13 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s
sema_append_contract_asserts(assert_first, body);
if (!sema_analyse_statement(&macro_context, body)) goto EXIT_FAIL;
ASSERT_SPAN(call_expr, macro_context.active_scope.depth == 1);
bool implicit_void_return = !macro_context.active_scope.jump_end;
bool implicit_void_return = !macro_context.active_scope.end_jump.active;
params = macro_context.macro_params;
bool is_no_return = sig->attrs.noreturn;
if (!vec_size(macro_context.returns))
{
if (rtype && rtype != type_void && !macro_context.active_scope.jump_end)
if (rtype && rtype != type_void && !macro_context.active_scope.end_jump.active)
{
SEMA_ERROR(decl,
"Missing return in macro that should evaluate to %s.",
@@ -2730,7 +2730,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s
unsigned returns_found = vec_size(macro_context.returns);
// We may have zero normal macro returns but the active scope still has a "jump end".
// In this case it is triggered by the @body()
if (!returns_found && macro_context.active_scope.jump_end)
if (!returns_found && macro_context.active_scope.end_jump.active)
{
is_no_return = true;
}
@@ -2779,7 +2779,10 @@ EXIT:
}
ASSERT_SPAN(call_expr, context->active_scope.defer_last == context->active_scope.defer_start);
context->active_scope = old_scope;
if (is_no_return) context->active_scope.jump_end = true;
if (is_no_return)
{
SET_JUMP_END(context, call_expr);
}
sema_context_destroy(&macro_context);
call_expr->resolve_status = RESOLVE_DONE;
if (is_always_const && !expr_is_runtime_const(call_expr))
@@ -2893,9 +2896,9 @@ static bool sema_call_analyse_body_expansion(SemaContext *macro_context, Expr *c
return SCOPE_POP_ERROR();
}
ASSERT_SPAN(call, ast->ast_kind == AST_COMPOUND_STMT);
if (context->active_scope.jump_end)
if (context->active_scope.end_jump.active)
{
macro_context->active_scope.jump_end = true;
macro_context->active_scope.end_jump = context->active_scope.end_jump;
}
if (first_defer)
{
@@ -8573,7 +8576,7 @@ static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr,
RETURN_SEMA_ERROR(left, "No optional to use '\?\?' with, please remove the '\?\?'.");
}
bool active_scope_jump = context->active_scope.jump_end;
EndJump active_scope_jump = context->active_scope.end_jump;
// First we analyse the "else" and try to implictly cast.
if (!sema_analyse_inferred_expr(context, infer_type, right)) return false;
@@ -8585,7 +8588,7 @@ static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr,
}
// Ignore the jump here.
context->active_scope.jump_end = active_scope_jump;
context->active_scope.end_jump = active_scope_jump;
// Here we might need to insert casts.
Type *else_type = right->type;

View File

@@ -41,6 +41,7 @@
#define PUSH_BREAKCONT(ast) PUSH_CONTINUE(ast); PUSH_BREAK(ast)
#define POP_BREAKCONT() POP_CONTINUE(); POP_BREAK()
#define CHECK_ON_DEFINED(ref__) do { if (!ref__) break; *ref__ = true; return false; } while(0)
#define SET_JUMP_END(context__, node__) do { (context__)->active_scope.end_jump = (EndJump) { true, (node__)->span }; } while(0)
Decl **global_context_acquire_locals_list(void);
void generic_context_release_locals_list(Decl **);

View File

@@ -161,14 +161,14 @@ static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement
// If this is a test, then assert(false) is permitted.
if (context->call_env.current_function && context->call_env.current_function->func_decl.attr_test)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
return true;
}
// Otherwise, require unreachable.
RETURN_SEMA_ERROR(expr, "Use 'unreachable' instead of 'assert(false)'.");
}
// Otherwise we print an error.
if (!context->active_scope.jump_end && !context->active_scope.is_dead)
if (!context->active_scope.end_jump.active && !context->active_scope.is_dead)
{
if (message_expr && sema_cast_const(message_expr) && vec_size(statement->assert_stmt.args))
{
@@ -212,7 +212,7 @@ static inline bool sema_analyse_break_stmt(SemaContext *context, Ast *statement)
}
// Is jump, and set it as resolved.
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
statement->contbreak_stmt.is_resolved = true;
AstId defer_begin;
@@ -248,13 +248,13 @@ static inline bool sema_analyse_break_stmt(SemaContext *context, Ast *statement)
static inline bool sema_analyse_compound_stmt(SemaContext *context, Ast *statement)
{
bool success;
bool ends_with_jump;
EndJump ends_with_jump;
SCOPE_START
success = sema_analyse_compound_statement_no_scope(context, statement);
ends_with_jump = context->active_scope.jump_end;
ends_with_jump = context->active_scope.end_jump;
SCOPE_END;
// If this ends with a jump, then we know we don't need to certain analysis.
context->active_scope.jump_end = ends_with_jump;
context->active_scope.end_jump = ends_with_jump;
return success;
}
@@ -313,7 +313,7 @@ static inline bool sema_analyse_continue_stmt(SemaContext *context, Ast *stateme
}
// This makes the active scope jump.
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
// Link the parent and add the defers.
statement->contbreak_stmt.ast = astid(parent);
@@ -567,7 +567,7 @@ static inline bool sema_analyse_block_exit_stmt(SemaContext *context, Ast *state
bool is_macro = (context->active_scope.flags & SCOPE_MACRO) != 0;
ASSERT(context->active_scope.flags & SCOPE_MACRO);
statement->ast_kind = AST_BLOCK_EXIT_STMT;
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
Type *block_type = context->expected_block_type;
Expr *ret_expr = statement->return_stmt.expr;
if (ret_expr)
@@ -680,7 +680,7 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement
}
// 1. We mark that the current scope ends with a jump.
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
Type *expected_rtype = context->rtype;
ASSERT(expected_rtype && "We should always have known type from a function return.");
@@ -1229,26 +1229,26 @@ static inline bool sema_analyse_expr_stmt(SemaContext *context, Ast *statement)
case EXPR_RETHROW:
if (expr->rethrow_expr.inner->expr_kind == EXPR_OPTIONAL)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, expr);
}
break;
case EXPR_FORCE_UNWRAP:
if (expr->inner_expr->expr_kind == EXPR_OPTIONAL)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, expr);
}
break;
case EXPR_POST_UNARY:
if (expr->rethrow_expr.inner->expr_kind == EXPR_OPTIONAL)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, expr);
}
break;
case EXPR_CALL:
if (expr->call_expr.no_return) context->active_scope.jump_end = true;
if (expr->call_expr.no_return) SET_JUMP_END(context, expr);
break;
case EXPR_MACRO_BLOCK:
if (expr->macro_block.is_noreturn) context->active_scope.jump_end = true;
if (expr->macro_block.is_noreturn) SET_JUMP_END(context, expr);
break;
case EXPR_CONST:
// Remove all const statements.
@@ -1385,7 +1385,7 @@ static inline bool sema_analyse_for_stmt(SemaContext *context, Ast *statement)
PUSH_BREAKCONT(statement);
success = sema_analyse_statement(context, body);
statement->for_stmt.flow.no_exit = context->active_scope.jump_end;
statement->for_stmt.flow.no_exit = context->active_scope.end_jump.active;
POP_BREAKCONT();
// End for body scope
@@ -1427,7 +1427,7 @@ static inline bool sema_analyse_for_stmt(SemaContext *context, Ast *statement)
if (is_infinite && !statement->for_stmt.flow.has_break)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
}
return success;
}
@@ -1920,18 +1920,18 @@ static inline bool sema_analyse_if_stmt(SemaContext *context, Ast *statement)
success = false;
}
}
if (context->active_scope.jump_end && !context->active_scope.allow_dead_code)
if (context->active_scope.end_jump.active && !context->active_scope.allow_dead_code)
{
if (!SEMA_WARN(then, "This code will never execute."))
{
success = false;
}
context->active_scope.allow_dead_code = true;
bool warn = SEMA_WARN(statement, "This code will never execute.");
sema_note_prev_at(context->active_scope.end_jump.span, "This code is preventing it from exectuting");
if (!warn) return success = false;
}
SCOPE_START_WITH_LABEL(statement->if_stmt.flow.label);
if (result == COND_FALSE) context->active_scope.is_dead = true;
success = success && sema_analyse_statement(context, then);
then_jump = context->active_scope.jump_end;
then_jump = context->active_scope.end_jump.active;
SCOPE_END;
if (!success) goto END;
@@ -1943,7 +1943,7 @@ static inline bool sema_analyse_if_stmt(SemaContext *context, Ast *statement)
sema_remove_unwraps_from_try(context, cond);
sema_unwrappable_from_catch_in_else(context, cond);
success = success && sema_analyse_statement(context, else_body);
else_jump = context->active_scope.jump_end;
else_jump = context->active_scope.end_jump.active;
SCOPE_END;
}
@@ -1964,15 +1964,15 @@ END:
}
if (then_jump && else_jump && !statement->flow.has_break)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
}
else if (then_jump && result == COND_TRUE)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
}
else if (else_jump && result == COND_FALSE)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
}
return true;
}
@@ -2070,7 +2070,7 @@ static bool context_labels_exist_in_scope(SemaContext *context)
static bool sema_analyse_nextcase_stmt(SemaContext *context, Ast *statement)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
if (!context->next_target && !statement->nextcase_stmt.label.name && !statement->nextcase_stmt.expr && !statement->nextcase_stmt.is_default)
{
if (context->next_switch)
@@ -2527,7 +2527,7 @@ static bool sema_analyse_switch_body(SemaContext *context, Ast *statement, Sourc
POP_BREAK();
POP_NEXT();
if (!body && i < case_count - 1) continue;
all_jump_end &= context->active_scope.jump_end;
all_jump_end &= context->active_scope.end_jump.active;
SCOPE_END;
}
if (is_enum_switch && !exhaustive && success)
@@ -2894,7 +2894,7 @@ static inline bool sema_analyse_switch_stmt(SemaContext *context, Ast *statement
if (statement->flow.no_exit && !statement->flow.has_break)
{
context->active_scope.jump_end = true;
SET_JUMP_END(context, statement);
}
return true;
}
@@ -3108,21 +3108,20 @@ bool sema_analyse_statement(SemaContext *context, Ast *statement)
{
if (context->active_scope.is_invalid) return false;
if (statement->ast_kind == AST_POISONED) return false;
bool dead_code = context->active_scope.jump_end;
EndJump end_jump = context->active_scope.end_jump;
unsigned returns = vec_size(context->returns);
if (!sema_analyse_statement_inner(context, statement)) return ast_poison(statement);
if (dead_code)
if (end_jump.active)
{
if (!context->active_scope.allow_dead_code)
{
context->active_scope.allow_dead_code = true;
// If we start with an don't start with an assert AND the scope is a macro, then it's bad.
if (statement->ast_kind != AST_ASSERT_STMT && statement->ast_kind != AST_NOP_STMT && !(context->active_scope.flags & SCOPE_MACRO))
{
if (!SEMA_WARN(statement, "This code will never execute."))
{
return ast_poison(statement);
}
context->active_scope.allow_dead_code = true;
bool warn = SEMA_WARN(statement, "This code will never execute.");
sema_note_prev_at(end_jump.span, "No code will execute after this statement.");
if (!warn) return ast_poison(statement);
}
// Remove it
}
@@ -3296,7 +3295,7 @@ bool sema_analyse_function_body(SemaContext *context, Decl *func)
NEXT:
if (!sema_analyse_compound_statement_no_scope(context, body)) return false;
ASSERT_SPAN(func,context->active_scope.depth == 1);
if (!context->active_scope.jump_end)
if (!context->active_scope.end_jump.active)
{
if (canonical_rtype != type_void)
{

View File

@@ -37,7 +37,6 @@ void context_change_scope_with_flags(SemaContext *context, ScopeFlags flags)
context->active_scope = (DynamicScope) {
.scope_id = ++context->scope_id,
.allow_dead_code = false,
.jump_end = false,
.is_dead = scope_is_dead,
.is_invalid = scope_is_invalid,
.depth = depth,
@@ -99,7 +98,7 @@ AstId context_get_defers(SemaContext *context, AstId defer_top, AstId defer_bott
void context_pop_defers(SemaContext *context, AstId *next)
{
AstId defer_start = context->active_scope.defer_start;
if (next && !context->active_scope.jump_end)
if (next && !context->active_scope.end_jump.active)
{
AstId defer_current = context->active_scope.defer_last;
while (defer_current != defer_start)

View File

@@ -0,0 +1,12 @@
import std;
macro something() {
unreachable("Hi there");
}
fn void main() {
something();
$echo "Hello";
io::printn("Hello, World"); // #warning: This code will never execute
}