Skip to content

Commit b247dd7

Browse files
authored
Merge pull request #1093 from isSerge/1091-ast-walk-visit-switch-default-case-body
fix: update AST walking for `switch` statements to ensure all case bodies are visited
2 parents 671a25e + e0c2f39 commit b247dd7

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

src/ast/stmt.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,11 @@ impl Stmt {
10301030
}
10311031
}
10321032
if let Some(index) = sw.def_case {
1033-
if !sw.expressions[index].lhs.walk(path, on_node) {
1033+
let block = &sw.expressions[index];
1034+
if !block.lhs.walk(path, on_node) {
1035+
return false;
1036+
}
1037+
if !block.rhs.walk(path, on_node) {
10341038
return false;
10351039
}
10361040
}

tests/switch.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(feature = "internals")]
2+
use rhai::{ASTNode, Expr};
13
use rhai::{Engine, ParseErrorType, Scope, INT};
24

35
#[test]
@@ -305,3 +307,79 @@ fn test_switch_ranges() {
305307
'z'
306308
);
307309
}
310+
311+
/// AST walk tests — verify that `walk` visits the body of the `switch` default case.
312+
313+
#[test]
314+
#[cfg(feature = "internals")]
315+
fn test_switch_walk_visits_default_case_body() {
316+
let engine = Engine::new();
317+
// `extra` is inside the default branch body and must be visited.
318+
let ast = engine
319+
.compile(
320+
r#"
321+
switch action {
322+
1 => foo(),
323+
_ => bar(extra)
324+
}
325+
"#,
326+
)
327+
.unwrap();
328+
329+
let mut vars: Vec<String> = Vec::new();
330+
ast.walk(&mut |nodes: &[ASTNode]| {
331+
if let Some(ASTNode::Expr(Expr::Variable(info, _, _))) = nodes.last() {
332+
vars.push(info.1.to_string());
333+
}
334+
true
335+
});
336+
337+
assert!(vars.contains(&"extra".to_string()), "walk should visit `extra` in the default branch body");
338+
}
339+
340+
#[test]
341+
#[cfg(feature = "internals")]
342+
fn test_switch_walk_visits_all_case_bodies_and_default() {
343+
let engine = Engine::new();
344+
let ast = engine
345+
.compile(
346+
r#"
347+
switch x {
348+
1 => arm_one(a),
349+
2 => arm_two(b),
350+
_ => arm_default(c)
351+
}
352+
"#,
353+
)
354+
.unwrap();
355+
356+
let mut vars: Vec<String> = Vec::new();
357+
ast.walk(&mut |nodes: &[ASTNode]| {
358+
if let Some(ASTNode::Expr(Expr::Variable(info, _, _))) = nodes.last() {
359+
vars.push(info.1.to_string());
360+
}
361+
true
362+
});
363+
364+
for name in &["x", "a", "b", "c"] {
365+
assert!(vars.contains(&name.to_string()), "walk should visit `{name}`", name = name);
366+
}
367+
}
368+
369+
#[test]
370+
#[cfg(feature = "internals")]
371+
fn test_switch_walk_default_only() {
372+
let engine = Engine::new();
373+
// A switch with only a default arm — the body must still be visited.
374+
let ast = engine.compile("switch x { _ => fallback(y) }").unwrap();
375+
376+
let mut vars: Vec<String> = Vec::new();
377+
ast.walk(&mut |nodes: &[ASTNode]| {
378+
if let Some(ASTNode::Expr(Expr::Variable(info, _, _))) = nodes.last() {
379+
vars.push(info.1.to_string());
380+
}
381+
true
382+
});
383+
384+
assert!(vars.contains(&"y".to_string()), "walk should visit `y` inside the default-only branch body");
385+
}

0 commit comments

Comments
 (0)