Skip to content

Commit 4536407

Browse files
feggeclaude
andcommitted
Limit diagnostic location to loop header for code blocks
When a decompilation failure occurs in a loop, only highlight the loop header (e.g., "while.true" or "repeat.5") rather than the entire block. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent def17b5 commit 4536407

File tree

1 file changed

+25
-4
lines changed

1 file changed

+25
-4
lines changed

src/decompiler/collector.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,27 @@ use super::pseudocode::{
2525
};
2626
use super::state::DecompilerState;
2727

28+
// ═══════════════════════════════════════════════════════════════════════════
29+
// Helper Functions
30+
// ═══════════════════════════════════════════════════════════════════════════
31+
32+
/// Extract just the header span from a loop's full span.
33+
///
34+
/// For `while.true`, this returns a span covering "while.true" (10 chars).
35+
/// For `repeat.N`, this returns a span covering "repeat.N" (7 + digit count chars).
36+
fn loop_header_span(op: &Op) -> SourceSpan {
37+
let span = op.span();
38+
let header_len = match op {
39+
Op::While { .. } => 10, // "while.true"
40+
Op::Repeat { count, .. } => {
41+
// "repeat." (7) + digits
42+
7 + if *count == 0 { 1 } else { (*count as f64).log10().floor() as u32 + 1 }
43+
}
44+
_ => return span, // Not a loop, return full span
45+
};
46+
SourceSpan::new(span.source_id(), span.start()..span.start() + header_len)
47+
}
48+
2849
// ═══════════════════════════════════════════════════════════════════════════
2950
// Hint Collection Types
3051
// ═══════════════════════════════════════════════════════════════════════════
@@ -250,7 +271,7 @@ impl<'a> DecompilationCollector<'a> {
250271
// Fail decompilation - we have dynamic stack content from a previous loop
251272
if let Some(ref mut state) = self.state {
252273
state.fail_tracking(
253-
op.span(),
274+
loop_header_span(op),
254275
"loop with variable iteration count preceded by another loop that modified stack size"
255276
);
256277
}
@@ -278,7 +299,7 @@ impl<'a> DecompilationCollector<'a> {
278299

279300
// Mark stack as dynamic for subsequent loops
280301
if let Some(ref mut state) = self.state {
281-
state.dynamic_stack_source = Some(op.span());
302+
state.dynamic_stack_source = Some(loop_header_span(op));
282303
}
283304
} else {
284305
// Zero net effect: stack shape is preserved, use renaming for consistency
@@ -365,7 +386,7 @@ impl<'a> DecompilationCollector<'a> {
365386
// Fail decompilation - we have dynamic stack content from a previous loop
366387
if let Some(ref mut state) = self.state {
367388
state.fail_tracking(
368-
op.span(),
389+
loop_header_span(op),
369390
"loop with non-zero stack effect preceded by another loop that modified stack size"
370391
);
371392
}
@@ -384,7 +405,7 @@ impl<'a> DecompilationCollector<'a> {
384405

385406
// Mark stack as dynamic for subsequent loops
386407
if let Some(ref mut state) = self.state {
387-
state.dynamic_stack_source = Some(op.span());
408+
state.dynamic_stack_source = Some(loop_header_span(op));
388409
}
389410
} else {
390411
// Zero net effect: stack shape is preserved, use renaming for consistency

0 commit comments

Comments
 (0)