fix: switch 无命中跳 default 且 case 比较改严格相等

This commit is contained in:
2026-09-17 21:43:12 +00:00
parent d0ab7fc2a0
commit 816fdc3dd6
2 changed files with 168 additions and 4 deletions
+8 -4
View File
@@ -43,8 +43,9 @@ impl Emitter {
if let Some(test) = &case.test {
let test_reg = self.emit_expression(test, ctx)?;
let eq_reg = ctx.alloc_reg();
// case 选择式与判别式按严格相等比较(不做类型转换)。
ctx.inst(Inst::new(
OpCode::EQ,
OpCode::STRICT_EQ,
Operand::Reg(eq_reg),
Operand::Reg(disc_reg),
Operand::Reg(test_reg),
@@ -52,9 +53,12 @@ impl Emitter {
ctx.inst(Inst::jmp_if_true(eq_reg, case_label));
}
}
let has_default = cases.iter().any(|c| c.test.is_none());
if !has_default {
ctx.inst(Inst::jmp(end_label));
// 无 case 命中时跳向 default 体,无 default 则跳到 switch 末尾。default 不在
// 源序首位时不能依赖穿落,否则会错误进入首个 case 体。
let default_label = cases.iter().position(|c| c.test.is_none()).map(|idx| case_labels[idx]);
match default_label {
Some(label) => ctx.inst(Inst::jmp(label)),
None => ctx.inst(Inst::jmp(end_label)),
}
let mut last: Option<u32> = None;
for (case_idx, case) in cases.iter().enumerate() {
@@ -0,0 +1,160 @@
//! switch 无 case 命中时的兜底跳转:跳到 default 体(default 不在源序首位时不能
//! 依赖穿落,否则会错误进入首个 case 体);无 default 则跳到 switch 末尾。
//!
//! 覆盖:default 末位/中间/首位、有/无命中、穿落、嵌套、空体、for 包裹多迭代、
//! 仅 default、多 case 无命中,以及 case 选择式的严格相等比较。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile: {}", e))?;
let mut vm = Vm::new();
vm.run(&Arc::new(module))
}
/// 求值为字符串的用例:在 VM 存活期内拷出内容,避免借用随 VM 析构失效。
fn eval_str(source: &str) -> String {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
let module = Compiler::new().compile(&program).expect("compile");
let mut vm = Vm::new();
let value = vm.run(&Arc::new(module)).expect("run");
assert!(value.is_string(), "expected string, got {value:?}");
// SAFETY: is_string 已确认值为字符串指针,内容在 VM 析构前拷贝为自有 String。
unsafe { &*value.as_string_ptr() }.as_str().to_string()
}
#[test]
fn switch_default_last_no_match() {
// default 在末位且无 case 命中时执行 default 体。
assert_eq!(
eval_str(r#"let s=""; switch(7){ case 0: s+="a"; break; default: s+="c"; break; } s"#),
"c"
);
}
#[test]
fn switch_default_middle_no_match() {
// default 在中间且无 case 命中时执行 default 体,不穿入首个 case。
assert_eq!(
eval_str(r#"let s=""; switch(7){ case 0: s+="a"; break; default: s+="c"; break; case 1: s+="b"; break; } s"#),
"c"
);
}
#[test]
fn switch_default_first_no_match() {
// default 在首位时兜底跳转目标紧邻下一条指令,行为不变。
assert_eq!(
eval_str(r#"let s=""; switch(7){ default: s+="c"; break; case 0: s+="a"; break; } s"#),
"c"
);
}
#[test]
fn switch_default_only() {
// 仅 default 时即首个 case 体,穿落正确。
assert_eq!(eval_str(r#"let s=""; switch(7){ default: s+="c"; break; } s"#), "c");
}
#[test]
fn switch_case_match_skips_default() {
// 命中 case 时直接跳 case 标签,不经过兜底跳转。
assert_eq!(
eval_str(r#"let s=""; switch(0){ case 0: s+="a"; break; default: s+="c"; break; } s"#),
"a"
);
}
#[test]
fn switch_case_match_falls_through_default() {
// 命中 case 后无 break,顺序穿落到 default。
assert_eq!(eval_str(r#"let s=""; switch(0){ case 0: s+="a"; default: s+="c"; } s"#), "ac");
}
#[test]
fn switch_no_default_no_match() {
// 无 default 且无命中:跳到 switch 末尾,不执行任何 case 体。
assert_eq!(eval_str(r#"let s=""; switch(7){ case 0: s+="a"; break; } s"#), "");
}
#[test]
fn switch_multi_case_no_match() {
// 多 case 均不命中时跳到 default 体。
assert_eq!(
eval_str(r#"let s=""; switch(9){ case 1: s+="1"; break; case 2: s+="2"; break; default: s+="d"; break; } s"#),
"d"
);
}
#[test]
fn switch_for_wrapped_no_match() {
// for 包裹多迭代且每轮判别式均不命中:每轮都走兜底跳转转 default。
assert_eq!(
eval_str(
r#"let s=""; for(var i=0;i<3;i++){ switch(i+7){ case 0: s+="a"; break; default: s+="c"; break; } } s"#
),
"ccc"
);
}
#[test]
fn switch_for_wrapped_mixed_match() {
// for 包裹下命中与不命中交替:i=0 命中 case,i=1/2 走 default。
assert_eq!(
eval_str(r#"let s=""; for(var i=0;i<3;i++){ switch(i){ case 0: s+="a"; break; default: s+="c"; break; } } s"#),
"acc"
);
}
#[test]
fn switch_nested_default_no_match() {
// 嵌套 switch 内层无命中时跳到内层 default,不影响外层。
assert_eq!(
eval_str(r#"let s=""; switch(1){ case 1: switch(7){ case 0: s+="a"; break; default: s+="c"; break; } } s"#),
"c"
);
}
#[test]
fn switch_empty_body_completion() {
// 空 CaseBlock 无 case 命中可执行,完成值为 undefined。
assert!(eval("switch(7){}").unwrap().is_undefined());
}
#[test]
fn switch_completion_default_break() {
// 无命中跳到 default 体后 break 退出,完成值取自 default 体。
assert_eq!(eval(r#"1; switch ("b") { case "a": 2; break; default: 3; }"#).unwrap().as_int(), 3);
}
#[test]
fn switch_completion_default_empty_after_case() {
// default 体为空且不执行时,未写入的结果寄存器读 undefined。
assert!(eval(r#"7; switch ("b") { case "a": 8; default: }"#).unwrap().is_undefined());
}
#[test]
fn switch_completion_fall_through_default() {
// 无命中跳到 default 后顺序穿落到其后的 case,完成值为末次执行值。
assert_eq!(eval(r#"1; switch ("b") { case "a": 10; default: 11; }"#).unwrap().as_int(), 11);
}
#[test]
fn switch_case_strict_equality() {
// case 选择式按严格相等比较:字符串与数字不匹配,走 default。
assert_eq!(
eval_str(r#"let s; switch(1){ case "1": s="loose"; break; default: s="strict"; break; } s"#),
"strict"
);
assert_eq!(
eval_str(r#"let s; switch(true){ case 1: s="loose"; break; default: s="strict"; break; } s"#),
"strict"
);
}