Files
OxideJS-New/crates/oxide_vm/tests/destructuring_tests.rs
T

411 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&Arc::new(module))
}
fn assert_num(value: JsValue, expected: f64) {
let actual = if value.is_int() { value.as_int() as f64 } else { value.as_double() };
assert!((actual - expected).abs() < 0.0001, "expected {expected}, got {value:?}");
}
#[test]
fn for_of_array_values() {
let mut vm = Vm::new();
let result = eval(&mut vm, "let sum=0; for (const x of [1,2,3]) { sum += x; } sum").unwrap();
assert_num(result, 6.0);
}
#[test]
fn array_binding_and_defaults() {
let mut vm = Vm::new();
let result = eval(&mut vm, "const [a,b=4]=[1]; a+b").unwrap();
assert_num(result, 5.0);
}
#[test]
fn array_rest_keeps_numeric_index_access() {
let mut vm = Vm::new();
let result = eval(&mut vm, "const [a,...rest]=[1,2,3]; rest[0] + rest[1] + rest.length").unwrap();
assert_num(result, 7.0);
}
#[test]
fn object_binding_rest_and_computed_key() {
let mut vm = Vm::new();
let result = eval(&mut vm, "const key='x'; const {[key]: a, ...rest}={x:1,y:2}; a + rest.y").unwrap();
assert_num(result, 3.0);
}
#[test]
fn nested_binding_patterns() {
let mut vm = Vm::new();
let result = eval(&mut vm, "const [[a], {b}] = [[1], {b:2}]; a+b").unwrap();
assert_num(result, 3.0);
}
#[test]
fn destructuring_assignment_swap_and_object_target() {
let mut vm = Vm::new();
let result = eval(&mut vm, "let a=1,b=2; [a,b]=[b,a]; ({x:a,y:b}={x:3,y:4}); a*10+b").unwrap();
assert_num(result, 34.0);
}
#[test]
fn for_of_destructuring_left_side() {
let mut vm = Vm::new();
let result = eval(&mut vm, "let sum=0; for (const [k,v] of [[1,2],[3,4]]) { sum += k + v; } sum").unwrap();
assert_num(result, 10.0);
}
#[test]
fn for_of_object_destructuring_assignment_target_runs_once() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var x = null; var count = 0; for ({ x } of [{ x: 3 }]) { count += x; } count").unwrap();
assert_num(result, 3.0);
}
#[test]
fn for_of_object_destructuring_lexical_binding_runs_once() {
let mut vm = Vm::new();
let result = eval(&mut vm, "let count = 0; for (let { x: [y], } of [{ x: [45] }]) { count += y; } count").unwrap();
assert_num(result, 45.0);
}
#[test]
fn function_and_method_parameter_destructuring() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f([a=4], {x}) { return a+x; } class C { m({y}) { return y; } } f([], {x:1}) + new C().m({y:5})",
)
.unwrap();
assert_num(result, 10.0);
}
#[test]
fn destructured_callback_parameters_shadow_outer_bindings() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"let expected=100, result=0; [[1,2]].forEach(([value, expected]) => { result=value+expected; }); result",
)
.unwrap();
assert_num(result, 3.0);
}
// 回归:赋值目标含嵌套 rest 模式(rest 元素本身是模式,如 `[...[x]]`)的 for-of
// 曾无限循环 / 报错,因为计数器相对 emitter 少计了 rest 目标的指令,破坏了循环跳转
// 偏移。(对象 rest 嵌套目标按规范是语法错误,故不测。)
#[test]
fn for_of_assignment_nested_rest_target_runs_once() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var x; var c=0; for([...[x]] of [[1,2,3]]){ c=c+1; } c").unwrap();
assert_num(result, 1.0);
}
#[test]
fn for_of_assignment_nested_rest_empty_body_binds_inner() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var x; for([...[x]] of [[1,2,3]]){} x").unwrap();
assert_num(result, 1.0);
}
#[test]
fn for_of_assignment_nested_rest_with_leading_element() {
let mut vm = Vm::new();
// 外层迭代一次;a=1,rest=[2,3,4],再从 rest 解构 [x,y] -> x=2, y=3
let result = eval(&mut vm, "var a,x,y; for([a,...[x,y]] of [[1,2,3,4]]){} a*100+x*10+y").unwrap();
assert_num(result, 123.0);
}
#[test]
fn standalone_nested_rest_assignment() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var x; [...[x]]=[1,2,3]; x").unwrap();
assert_num(result, 1.0);
}
#[test]
fn object_rest_excludes_numeric_bound_key_from_array_source() {
// 数组元素区整数键:pattern 绑定 "0" 后 rest 不得再包含 "0"。
let mut vm = Vm::new();
let result = eval(&mut vm, "const {0:x, ...r} = [1,2,3]; Object.keys(r).length").unwrap();
assert_eq!(result.as_int(), 2);
let result = eval(&mut vm, "const {0:x, ...r} = [1,2,3]; x === 1").unwrap();
assert!(result.as_bool());
// rest 从 1 开始:r[1] 是源元素 2,r[0] 已被排除为 undefined。
let result = eval(&mut vm, "const {0:x, ...r} = [1,2,3]; r[1] + r[2]").unwrap();
assert_eq!(result.as_int(), 5);
}
#[test]
fn object_rest_excludes_multiple_numeric_keys() {
let mut vm = Vm::new();
let result = eval(&mut vm, "const {0:x, 1:y, ...r} = [1,2,3,4]; Object.keys(r).join(',')").unwrap();
let s = unsafe { &*result.as_string_ptr() }.as_str().to_string();
assert_eq!(s, "2,3");
}
#[test]
fn object_rest_keeps_numeric_key_from_shape_source() {
// 对象源数字键(shape 链整数键)同样排除。
let mut vm = Vm::new();
let result = eval(&mut vm, "const {0:x, ...r} = {0:'a',1:'b',2:'c'}; Object.keys(r).join(',')").unwrap();
let s = unsafe { &*result.as_string_ptr() }.as_str().to_string();
assert_eq!(s, "1,2");
}
// computed 解构键通用 fallback:CallExpression / Template / BigInt 等键表达式
// 走既有 emit 全域,键值运行时求值后经 ToPropertyKey 读属性。
#[test]
fn computed_key_call_expression_binding() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f() { return 'x'; } const { [f()]: a } = {x: 42}; a").unwrap();
assert_num(result, 42.0);
}
#[test]
fn computed_key_call_expression_throw_propagates() {
// 键求值抛错在属性读与绑定前抛出,错误透传不吞。
let mut vm = Vm::new();
let err = eval(
&mut vm,
"function thrower() { throw new TypeError('boom'); } const { [thrower()]: a } = {};",
)
.unwrap_err();
assert!(err.contains("boom"), "expected thrown error, got {err:?}");
}
#[test]
fn computed_key_template_literal_binding() {
// 无插值模板键折叠不可用,仍须正确求值(DYNAMIC 路径语义等价)。
let mut vm = Vm::new();
let result = eval(&mut vm, "const { [`x`]: a } = {x: 5}; a").unwrap();
assert_num(result, 5.0);
let result = eval(&mut vm, "const { [`x${'y'}`]: a } = {xy: 3}; a").unwrap();
assert_num(result, 3.0);
}
#[test]
fn computed_key_bigint_binding() {
// 1n 经 ToPropertyKey 转 "1",命中对象数字键。
let mut vm = Vm::new();
let result = eval(&mut vm, "const { [1n]: a } = {1: 9}; a").unwrap();
assert_num(result, 9.0);
}
#[test]
fn computed_key_call_expression_rest_excludes() {
// rest 排除数组收集运行时键值,computed 键与 rest 语义正确。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function k() { return 'x'; } const { [k()]: a, ...rest } = {x:1,y:2}; a + rest.y",
)
.unwrap();
assert_num(result, 3.0);
}
#[test]
fn computed_key_assignment_target() {
let mut vm = Vm::new();
let result = eval(&mut vm, "let a; function k() { return 'x'; } ({ [k()]: a } = {x: 7}); a").unwrap();
assert_num(result, 7.0);
}
#[test]
fn computed_key_nested_pattern() {
// 嵌套解构的 computed 键逐层求值。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function k1() { return 'x'; } function k2() { return 'y'; } const { [k1()]: { [k2()]: b } } = {x: {y: 3}}; b",
)
.unwrap();
assert_num(result, 3.0);
}
#[test]
fn computed_key_evaluated_before_default_and_getter() {
// 求值序:键先于默认值(键未命中才走默认值);getter 副作用发生在键求值之后。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"const log = []; function k() { log.push('k'); return 'x'; } const { [k()]: a = (log.push('d'), 99) } = {}; log.join(',')",
)
.unwrap();
let s = unsafe { &*result.as_string_ptr() }.as_str().to_string();
assert_eq!(s, "k,d");
let result = eval(
&mut vm,
"let order=[]; function k() { order.push('key'); return 'x'; } const o = { get x() { order.push('get'); return 5; } }; const { [k()]: a } = o; a + ':' + order.join(',')",
)
.unwrap();
let s = unsafe { &*result.as_string_ptr() }.as_str().to_string();
assert_eq!(s, "5:key,get");
}
// 跨作用域:绑定侧计算键引用外层绑定须走闭包捕获(修复前键引用落 LOAD_GLOBAL
// 兜底——读全局错值或 ReferenceError)。
#[test]
fn computed_key_binding_captures_outer_identifier_key() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ let k='x'; return function(){ const {[k]: a}={x:1}; return a; }; } f()()",
)
.unwrap();
assert_num(result, 1.0);
}
#[test]
fn computed_key_binding_captures_outer_const_key() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ const k='x'; return function(){ const {[k]: a}={x:2}; return a; }; } f()()",
)
.unwrap();
assert_num(result, 2.0);
}
#[test]
fn computed_key_binding_captures_outer_call_key() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ function k(){return 'x'} return function(){ const {[k()]: a}={x:1}; return a; }; } f()()",
)
.unwrap();
assert_num(result, 1.0);
}
#[test]
fn computed_key_binding_captures_outer_template_key() {
// 模板插值键引用外层绑定同样须捕获。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ let k='x'; return function(){ const {[`${k}`]: a}={x:3}; return a; }; } f()()",
)
.unwrap();
assert_num(result, 3.0);
}
#[test]
fn computed_key_binding_captures_outer_nested_pattern_key() {
// 嵌套解构的内层计算键引用外层绑定。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ function k(){return 'x'} return function(){ const {a: {[k()]: b}}={a:{x:4}}; return b; }; } f()()",
)
.unwrap();
assert_num(result, 4.0);
}
#[test]
fn computed_key_binding_captures_outer_in_catch_pattern() {
// catch 参数解构模式的计算键引用外层绑定。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ function k(){return 'x'} return function(){ try { throw {x:5}; } catch ({[k()]: a}) { return a; } }; } f()()",
)
.unwrap();
assert_num(result, 5.0);
}
#[test]
fn computed_key_binding_captures_outer_in_fn_default_pattern() {
// 函数形参解构默认值的计算键引用外层绑定。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ function k(){return 'x'} return function g({[k()]: a} = {x:6}) { return a; }; } f()()",
)
.unwrap();
assert_num(result, 6.0);
}
#[test]
fn computed_key_binding_same_scope_identifier_key() {
// 同作用域标识符键(无外层绑定)不回归,仍走局部符号表。
let mut vm = Vm::new();
let result = eval(&mut vm, "const key='x'; const {[key]: a}={x:9}; a").unwrap();
assert_num(result, 9.0);
}
#[test]
fn computed_key_binding_undeclared_key_throws_reference_error() {
// 未声明标识符计算键:读取抛 ReferenceError,而非静默读全局槽错值。
let mut vm = Vm::new();
let err = eval(&mut vm, "const {[undeclaredKey]: a} = {x:1};").unwrap_err();
assert!(err.contains("ReferenceError"), "expected ReferenceError, got {err:?}");
}
// 跨作用域:声明语句模式内嵌默认值引用外层绑定须走闭包捕获(修复前默认值 right
// 不扫 → x 落 LOAD_GLOBAL 兜底——无全局 x 抛 ReferenceError / 有全局 x 读错值)。
#[test]
fn binding_default_captures_outer_in_declaration() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ let x=42; return function(){ const {a = x} = {}; return a; }; } f()()",
)
.unwrap();
assert_num(result, 42.0);
}
#[test]
fn binding_default_captures_outer_with_iife() {
// 内嵌默认值含 IIFE:默认值表达式整体在模式求值语境,IIFE 内引用同样须捕获。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ let x=42; return function(){ const {a = (()=>x)()} = {}; return a; }; } f()()",
)
.unwrap();
assert_num(result, 42.0);
}
#[test]
fn binding_default_captures_outer_in_for_of_left() {
// for-of left 解构模式的内嵌默认值引用外层绑定。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ let x=42; return function(){ let r; for (const {a = x} of [{}]) { r = a; } return r; }; } f()()",
)
.unwrap();
assert_num(result, 42.0);
}
#[test]
fn binding_default_captures_outer_in_array_element() {
// 数组解构元素的内嵌默认值引用外层绑定(ArrayPattern 元素同走 AssignmentPattern 分支)。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){ let x=42; return function(){ const [a = x] = []; return a; }; } f()()",
)
.unwrap();
assert_num(result, 42.0);
}
#[test]
fn binding_default_same_scope_not_regress() {
// 同作用域内嵌默认值走局部寄存器读,不依赖捕获,验证不误捕获/不破坏。
let mut vm = Vm::new();
let result = eval(&mut vm, "const x=7; const {a = x} = {}; a").unwrap();
assert_num(result, 7.0);
}