mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
Analyzer: W0104 warn on dead code after terminator
When a block contains a statement after \`return\`, \`break\`, \`continue\`, or \`transition\`, the trailing statement is unreachable and the analyzer now emits W0104 pointing at it. The warning includes a secondary label on the terminator showing where execution stops. Adds \`Statement::span\` helper to the AST so diagnostics can point at arbitrary statements cleanly. https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
e688397594
commit
f8743cf95e
3 changed files with 112 additions and 0 deletions
|
|
@ -573,8 +573,26 @@ impl Analyzer {
|
|||
}
|
||||
|
||||
fn check_block(&mut self, block: &Block, state_names: &[&str]) {
|
||||
let mut terminated_by: Option<Span> = None;
|
||||
let mut warned_dead_code = false;
|
||||
for stmt in &block.statements {
|
||||
if let Some(term_span) = terminated_by {
|
||||
if !warned_dead_code {
|
||||
self.diagnostics.push(
|
||||
Diagnostic::warning(
|
||||
ErrorCode::W0104,
|
||||
"unreachable code after return / break / transition",
|
||||
stmt.span(),
|
||||
)
|
||||
.with_label(term_span, "execution stops here"),
|
||||
);
|
||||
warned_dead_code = true;
|
||||
}
|
||||
}
|
||||
self.check_statement(stmt, state_names);
|
||||
if stmt_is_terminator(stmt) && terminated_by.is_none() {
|
||||
terminated_by = Some(stmt.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1008,6 +1026,18 @@ fn is_small_constant(expr: &Expr) -> bool {
|
|||
matches!(expr, Expr::IntLiteral(_, _))
|
||||
}
|
||||
|
||||
/// True if this statement unconditionally ends block execution —
|
||||
/// subsequent statements in the same block cannot be reached.
|
||||
fn stmt_is_terminator(stmt: &Statement) -> bool {
|
||||
matches!(
|
||||
stmt,
|
||||
Statement::Return(_, _)
|
||||
| Statement::Break(_)
|
||||
| Statement::Continue(_)
|
||||
| Statement::Transition(_, _)
|
||||
)
|
||||
}
|
||||
|
||||
fn block_can_exit_or_yield(block: &Block) -> bool {
|
||||
block.statements.iter().any(stmt_can_exit_or_yield)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,6 +298,64 @@ fn analyze_return_wrong_type() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_dead_code_after_break() {
|
||||
let src = r#"
|
||||
game "Test" { mapper: NROM }
|
||||
var x: u8 = 0
|
||||
on frame {
|
||||
loop {
|
||||
break
|
||||
x += 1
|
||||
}
|
||||
}
|
||||
start Main
|
||||
"#;
|
||||
let errors = analyze_errors(src);
|
||||
assert!(
|
||||
errors.contains(&ErrorCode::W0104),
|
||||
"code after break should trigger W0104, got: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_dead_code_after_transition() {
|
||||
let src = r#"
|
||||
game "Test" { mapper: NROM }
|
||||
state A {
|
||||
on frame {
|
||||
transition B
|
||||
wait_frame
|
||||
}
|
||||
}
|
||||
state B { on frame { wait_frame } }
|
||||
start A
|
||||
"#;
|
||||
let errors = analyze_errors(src);
|
||||
assert!(
|
||||
errors.contains(&ErrorCode::W0104),
|
||||
"code after transition should trigger W0104, got: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_dead_code_after_return_in_fn() {
|
||||
let src = r#"
|
||||
game "Test" { mapper: NROM }
|
||||
fun foo() -> u8 {
|
||||
return 5
|
||||
return 6
|
||||
}
|
||||
on frame { wait_frame }
|
||||
start Main
|
||||
"#;
|
||||
let errors = analyze_errors(src);
|
||||
assert!(
|
||||
errors.contains(&ErrorCode::W0104),
|
||||
"code after return should trigger W0104, got: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_ram_overflow_emits_e0301() {
|
||||
// Two arrays totalling >2 KB cannot fit in NES RAM, triggering
|
||||
|
|
|
|||
|
|
@ -247,6 +247,30 @@ pub enum Statement {
|
|||
DebugAssert(Expr, Span),
|
||||
}
|
||||
|
||||
impl Statement {
|
||||
pub fn span(&self) -> Span {
|
||||
match self {
|
||||
Self::VarDecl(v) => v.span,
|
||||
Self::Draw(d) => d.span,
|
||||
Self::Assign(_, _, _, s)
|
||||
| Self::If(_, _, _, _, s)
|
||||
| Self::While(_, _, s)
|
||||
| Self::Loop(_, s)
|
||||
| Self::Break(s)
|
||||
| Self::Continue(s)
|
||||
| Self::Return(_, s)
|
||||
| Self::Transition(_, s)
|
||||
| Self::WaitFrame(s)
|
||||
| Self::Call(_, _, s)
|
||||
| Self::LoadBackground(_, s)
|
||||
| Self::SetPalette(_, s)
|
||||
| Self::Scroll(_, _, s)
|
||||
| Self::DebugLog(_, s)
|
||||
| Self::DebugAssert(_, s) => *s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DrawStmt {
|
||||
pub sprite_name: String,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue