use oxide_bytecode::module::{CompiledModule, Constant}; use oxide_bytecode::opcode::{self, OpCode}; use oxide_compiler::compiler::Compiler; use oxide_parser::Allocator; fn compile_source(source: &str) -> CompiledModule { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); compiler.compile(&program).expect("compile failed") } #[allow(dead_code)] fn compile_source_err(source: &str) -> String { let allocator = Allocator::default(); let program = oxide_parser::parse(&allocator, source).expect("parse failed"); let compiler = Compiler::new(); match compiler.compile(&program) { Ok(_) => panic!("compile should fail"), Err(err) => err, } } #[test] fn compile_simple_expr() { // 变量操作数避免常量折叠:折叠后 1 + 2 只剩单条 LOAD_CONST,无二元运算指令。 let module = compile_source("var a = 1; var b = 2; a + b"); assert!(!module.bytecode.is_empty()); assert!(module.bytecode.len() >= 4); let last = opcode::opcode(*module.bytecode.last().unwrap()); assert_eq!(last, OpCode::HALT); } #[test] fn test_constant_pool_overflow_range_error() { let expr = (0..70_000).map(|i| i.to_string()).collect::>().join(","); let source = format!("var a=[{expr}]; a"); let err = compile_source_err(&source); assert!(err.contains("too many constants"), "unexpected error: {err}"); } #[test] fn compile_logical_assignment_vars() { compile_source("let x = 0; x ||= 1;"); compile_source("let x = 1; x &&= 2;"); compile_source("let x = null; x ??= 3;"); } #[test] fn compile_logical_assignment_members() { compile_source("let obj = { x: 0 }; obj.x ||= 1;"); compile_source("let obj = { x: 1 }; obj.x &&= 2;"); compile_source("let obj = { x: null }; obj.x ??= 3;"); } #[test] fn compile_constants() { let module = compile_source("42"); assert!(!module.constants.is_empty()); assert_eq!(module.constants[0], Constant::Int(42)); } #[test] fn compile_dedups_identical_string_constants() { let module = compile_source("var a = 'hello'; var b = 'hello';"); let count = module .constants .iter() .filter(|c| matches!(c, Constant::String(s) if s == "hello")) .count(); assert_eq!(count, 1, "identical string constants should be interned once per module"); } #[test] fn compile_negation() { // 变量操作数避免常量折叠:折叠后 -5 只剩单条 LOAD_CONST,无 NEG 指令。 let module = compile_source("var a = 5; -a"); let has_neg = module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEG); assert!(has_neg); } #[test] fn compile_multiple_stmts() { let module = compile_source("1; 2; 3;"); // Compiler 默认 DCE 开启:死 `1; 2` 链的 LOAD_CONST 被删除, // 仅剩顶层值 `3` 的 LOAD_CONST(HALT 经 reg0 返回)。精确计数断言放宽为存在性断言。 let has_load = module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LOAD_CONST); assert!(has_load, "expected at least one LOAD_CONST for the final value"); } #[test] fn compile_object_literal_getter_emits_define_accessor() { let module = compile_source("var o = { get x() { return 1; } };"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DEFINE_ACCESSOR), "object literal getter should emit DEFINE_ACCESSOR" ); } #[test] fn compile_object_literal_setter_emits_define_accessor() { let module = compile_source("var o = { set x(v) { this.y = v; } };"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DEFINE_ACCESSOR), "object literal setter should emit DEFINE_ACCESSOR" ); } #[test] fn compile_object_literal_pure_data_keys_emit_batch_construction() { let module = compile_source("var o = { a: 1, b: 2 };"); let new_obj: Vec = module .bytecode .iter() .copied() .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) .collect(); assert_eq!(new_obj.len(), 1, "exactly one NEW_OBJECT"); assert_eq!(opcode::a(new_obj[0]), 2, "NEW_OBJECT a 槽编码属性数"); let batch_writes = module .bytecode .iter() .filter(|&&i| opcode::opcode(i) == OpCode::SET_PROP_BATCH) .count(); assert_eq!(batch_writes, 2, "批量前缀逐属性发 SET_PROP_BATCH"); let slow_writes = module .bytecode .iter() .filter(|&&i| opcode::opcode(i) == OpCode::SET_PROP) .count(); assert_eq!(slow_writes, 0, "纯数据键字面量不应再发慢路径 SET_PROP"); } #[test] fn compile_object_literal_computed_key_terminates_batch_prefix() { let module = compile_source("var o = { a: 1, [k]: 2 };"); let new_obj: Vec = module .bytecode .iter() .copied() .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) .collect(); assert_eq!(opcode::a(new_obj[0]), 1, "computed 键终止批段,前缀仅 a"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_BATCH), "前缀属性仍走批量槽写" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_DYNAMIC), "computed 键后续走动态路径" ); } #[test] fn compile_object_literal_duplicate_key_falls_back_whole_literal() { let module = compile_source("var o = { a: 1, a: 2 };"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP), "重复键整字面量回退慢路径" ); assert!(module.bytecode.iter().all(|&i| opcode::opcode(i) != OpCode::SET_PROP_BATCH), "重复键不批"); } #[test] fn compile_object_literal_proto_key_falls_back() { let module = compile_source("var o = { a: 1, __proto__: null };"); let new_obj: Vec = module .bytecode .iter() .copied() .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) .collect(); assert_eq!(opcode::a(new_obj[0]), 1, "__proto__ 终止批段,前缀仅 a"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP), "__proto__ 键走慢路径拦截" ); } #[test] fn compile_object_literal_accessor_not_batched() { let module = compile_source("var o = { a: 1, get b() { return 2; } };"); let new_obj: Vec = module .bytecode .iter() .copied() .filter(|&i| opcode::opcode(i) == OpCode::NEW_OBJECT) .collect(); assert_eq!(opcode::a(new_obj[0]), 1, "accessor 终止批段"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DEFINE_ACCESSOR), "accessor 后续走访问器定义路径" ); } #[test] fn compile_nested_object_literals_both_batch() { let module = compile_source("var o = { a: { b: 1 } };"); let batch_objects = module .bytecode .iter() .filter(|&&i| opcode::opcode(i) == OpCode::NEW_OBJECT && opcode::a(i) > 0) .count(); assert_eq!(batch_objects, 2, "内外层字面量都走批量构造"); } #[test] fn compile_binary_ops() { // 变量操作数避免常量折叠:折叠后各表达式只剩单条 LOAD_CONST,无运算指令。 let tests = [ ("var a = 3, b = 4; a * b", OpCode::MUL), ("var a = 10, b = 2; a / b", OpCode::DIV), ("var a = 7, b = 3; a % b", OpCode::MOD), ("var a = 5, b = 2; a - b", OpCode::SUB), ("var a = 5, b = 3; a & b", OpCode::BIT_AND), ("var a = 5, b = 2; a | b", OpCode::BIT_OR), ("var a = 5, b = 1; a ^ b", OpCode::BIT_XOR), ("var a = 1, b = 3; a << b", OpCode::SHL), ("var a = -8, b = 1; a >> b", OpCode::SHR), ("var a = -1, b = 0; a >>> b", OpCode::USHR), ]; for (src, expected_op) in tests { let module = compile_source(src); let has_op = module.bytecode.iter().any(|&i| opcode::opcode(i) == expected_op); assert!(has_op, "expected {:?} in '{src}'", expected_op); } } #[test] fn compile_bitwise_not() { // 变量操作数避免常量折叠:折叠后 ~0 只剩单条 LOAD_CONST,无 BIT_NOT 指令。 let module = compile_source("var a = 0; ~a"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::BIT_NOT), "~a should emit BIT_NOT opcode" ); } #[test] fn compile_ternary_emits_jumps() { // 条件走变量避免常量折叠:折叠后 true ? 1 : 2 只剩单条 LOAD_CONST,无分支指令。 let module = compile_source("var a = true; a ? 1 : 2"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::JMP_IF_FALSE), "ternary should contain JMP_IF_FALSE" ); } #[test] fn compile_logical_not() { // 变量操作数避免常量折叠:折叠后 !true 只剩单条 LOAD_CONST,无 NOT 指令。 let module = compile_source("var a = true; !a"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NOT), "!a should emit NOT opcode" ); } #[test] fn compile_logical_and_simple() { // 正则字面量作操作数:side-effect-free 但非字面量常量,避开字面量折叠, // 走 AND 快路径;标识符操作数非 side-effect-free,会落分支路径不发 AND。 let module = compile_source("(/x/) && 2"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::AND), "simple && should emit AND opcode" ); } #[test] fn compile_logical_or_simple() { // 正则字面量作操作数:side-effect-free 但非字面量常量,避开字面量折叠, // 走 OR 快路径;标识符操作数非 side-effect-free,会落分支路径不发 OR。 let module = compile_source("(/x/) || 2"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::OR), "simple || should emit OR opcode" ); } #[test] fn regression_coalesce_consistency() { let module = compile_source("var a = null; var b = 1; a ?? b"); assert!( module .bytecode .iter() .any(|&instr| opcode::opcode(instr) == OpCode::NULLISH || opcode::opcode(instr) == OpCode::JMP_IF_NULLISH), "a ?? b should emit nullish bytecode" ); } #[test] fn compile_strict_eq() { // 变量操作数避免常量折叠:折叠后 1 === 2 只剩单条 LOAD_CONST,无 STRICT_EQ 指令。 let module = compile_source("var a = 1, b = 2; a === b"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::STRICT_EQ), "a === b should emit STRICT_EQ opcode" ); } #[test] fn compile_strict_neq() { // 变量操作数避免常量折叠:折叠后 1 !== 2 只剩单条 LOAD_CONST,无 STRICT_NEQ 指令。 let module = compile_source("var a = 1, b = 2; a !== b"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::STRICT_NEQ), "a !== b should emit STRICT_NEQ opcode" ); } #[test] fn compile_unary_plus() { let module = compile_source("+'hello'"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::UNARY_PLUS), "+'hello' should emit UNARY_PLUS opcode" ); } #[test] fn compile_typeof_strict_eq() { // 变量操作数避免常量折叠:折叠后 typeof 42 === 'number' 只剩单条 LOAD_CONST, // TYPEOF 与 STRICT_EQ 均消失。 let module = compile_source("var a = 42; typeof a === 'number'"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TYPEOF), "should emit TYPEOF opcode" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::STRICT_EQ), "should emit STRICT_EQ opcode" ); } #[test] fn compile_strict_eq_no_error() { let result = std::panic::catch_unwind(|| { compile_source("1 === 2"); }); assert!(result.is_ok(), "1 === 2 should compile without error"); } #[test] fn compile_unary_plus_no_error() { let result = std::panic::catch_unwind(|| { compile_source("+'hello'"); }); assert!(result.is_ok(), "+'hello' should compile without error"); } #[test] fn compile_compound_add() { let module = compile_source("var x=0; x+=1"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_ADD), "x+=1 should emit COMPOUND_ADD opcode" ); } #[test] fn compile_compound_exp() { let module = compile_source("var x=0; x**=2"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_EXP), "x**=2 should emit COMPOUND_EXP opcode" ); } #[test] fn compile_compound_bitwise_ops() { let tests = [ ("var x=5; x&=3", OpCode::COMPOUND_AND), ("var x=5; x|=2", OpCode::COMPOUND_OR), ("var x=5; x^=1", OpCode::COMPOUND_XOR), ("var x=1; x<<=5", OpCode::COMPOUND_SHL), ("var x=-8; x>>=1", OpCode::COMPOUND_SHR), ("var x=-1; x>>>=0", OpCode::COMPOUND_USHR), ]; for (src, expected_op) in tests { let module = compile_source(src); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == expected_op), "expected {:?} in '{src}'", expected_op ); } } #[test] fn compile_compound_assign_no_error() { let result = std::panic::catch_unwind(|| { compile_source("x+=1"); }); assert!(result.is_ok(), "x+=1 should compile without error"); } #[test] fn compile_compound_exp_no_error() { let result = std::panic::catch_unwind(|| { compile_source("x**=2"); }); assert!(result.is_ok(), "x**=2 should compile without error"); } #[test] fn compile_inc_pre() { let module = compile_source("++x"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INC_PRE), "++x should emit INC_PRE opcode" ); } #[test] fn compile_inc_post() { let module = compile_source("x++"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INC_POST), "x++ should emit INC_POST opcode" ); } #[test] fn compile_inc_no_error() { let result = std::panic::catch_unwind(|| { compile_source("x++"); }); assert!(result.is_ok(), "x++ should compile without error"); } #[test] fn compile_dec_no_error() { let result = std::panic::catch_unwind(|| { compile_source("x--"); }); assert!(result.is_ok(), "x-- should compile without error"); } #[test] fn compile_inc_dec_diff_opcodes() { let m1 = compile_source("++x"); let m2 = compile_source("--x"); let has_inc_pre = m1.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INC_PRE); let has_dec_pre = m2.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DEC_PRE); assert!(has_inc_pre, "++x should emit INC_PRE"); assert!(has_dec_pre, "--x should emit DEC_PRE"); } #[test] fn nullish_opcodes_round_trip() { for op in [OpCode::NULLISH, OpCode::JMP_IF_NULLISH] { assert_eq!(OpCode::try_from(op as u8), Ok(op)); assert_eq!(format!("{op}"), format!("{op:?}")); } } #[test] fn compile_nullish_fast_path_opcode() { // 正则字面量作操作数:side-effect-free 但非字面量常量,避开字面量折叠, // 走 NULLISH 快路径;标识符操作数非 side-effect-free,会落分支路径不发 NULLISH。 let module = compile_source("(/x/) ?? 2"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NULLISH), "side-effect-free ?? should emit NULLISH" ); } #[test] fn compile_nullish_short_circuit_opcode() { let module = compile_source("var x = 0; x ?? (x = 5)"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::JMP_IF_NULLISH), "side-effecting ?? should emit JMP_IF_NULLISH" ); } #[test] fn compile_member_inc() { let module = compile_source("var obj={x:1}; obj.x++"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::MEMBER_INC), "obj.x++ should emit MEMBER_INC" ); } #[test] fn compile_dyn_member_inc() { let module = compile_source("var obj={a:3}; var k='a'; obj[k]++"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DYN_MEMBER_INC), "obj[k]++ should emit DYN_MEMBER_INC" ); } #[test] fn compile_compound_member_add() { let module = compile_source("var obj={x:1}; obj.x+=1"); assert!( module .bytecode .iter() .any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MEMBER_ADD), "obj.x+=1 should emit COMPOUND_MEMBER_ADD" ); } #[test] fn compile_compound_member_assign_ok() { let result = std::panic::catch_unwind(|| { compile_source("var obj={x:1}; obj.x+=1"); }); assert!(result.is_ok(), "obj.x+=1 should compile without error"); } #[test] fn compile_prefix_member_inc_ok() { let result = std::panic::catch_unwind(|| { compile_source("var obj={x:1}; ++obj.x"); }); assert!(result.is_ok(), "++obj.x should compile without error"); } #[test] fn compile_compound_member_exp_ok() { let result = std::panic::catch_unwind(|| { compile_source("var obj={x:1}; obj.x**=2"); }); assert!(result.is_ok(), "obj.x**=2 should compile without error"); } #[test] fn compile_this_in_function_ok() { let module = compile_source("function f() { return this; }"); assert!(!module.bytecode.is_empty()); } #[test] fn compile_this_member_assign_ok() { let module = compile_source("function f() { this.x = 1; }"); assert!(!module.bytecode.is_empty()); } #[test] fn compile_this_member_read_ok() { let module = compile_source("function f() { return this.x; }"); assert!(!module.bytecode.is_empty()); } #[test] fn compile_infinity_ok() { let module = compile_source("Infinity"); assert!(!module.bytecode.is_empty()); } #[test] fn compile_global_this_ok() { let module = compile_source("globalThis"); assert!(!module.bytecode.is_empty()); } #[test] fn compile_neg_infinity_ok() { let module = compile_source("1 / -Infinity"); assert!(!module.bytecode.is_empty()); } #[test] fn compile_class_expression_emits_constructor_value() { let module = compile_source("const C = class Foo { method() { return 2; } }; C"); assert_eq!(module.sub_modules.len(), 2, "expected constructor + method submodules"); assert!( module .bytecode .iter() .filter(|&&i| opcode::opcode(i) == OpCode::DEFINE_PROP_ATTRS) .count() >= 3, "class expression should wire method/constructor/prototype properties" ); } #[test] fn compile_class_expression_default_constructor() { let module = compile_source("class A {}; (class B {})"); assert!( module.sub_modules.iter().any(|m| m.is_class_constructor), "expected at least one class constructor submodule" ); } #[test] fn compile_large_pure_expression_reuses_temp_registers() { let expr = std::iter::repeat("1").take(40).collect::>().join(" + "); let module = compile_source(&expr); assert!( module.n_registers < 16, "pure expression should reuse temp registers, got {}", module.n_registers ); } #[test] fn compile_builtin_globals_are_registered_lazily() { // 纯读形经 A 侧全局对象属性(LOAD_GLOBAL)取值,不消费镜像槽:4 个引用虽 // 均为活代码(结果即脚本值)不可 DCE,但读侧不占槽;未被引用的其它内置 // 全局同样不占槽——编译产物镜像槽表为空。 let module = compile_source("[Object, Array, Math, JSON]"); assert_eq!(module.builtin_reg_map.len(), 0, "pure read references should not occupy mirror slots"); // 写形守卫:可写内置名标识符写须双写(镜像槽 + 全局对象属性),写面镜像槽 // 保留在产物中(入口预载与 delete 侧分类依赖在册槽)。 let write_module = compile_source("Object = 1; Array = 2;"); assert_eq!( write_module.builtin_reg_map.len(), 2, "write references should retain writable builtin mirror slots" ); } #[test] fn compile_comparison_complement_ops() { // 结果被使用(赋值)→ 比较指令不被 DCE 删除(精确 DCE 会删结果丢弃的死比较) let module = compile_source("let a = 1, b = 2; var r = a > b; var s = a <= b; var t = a >= b; var u = a != b;"); assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GT), "a > b should emit GT"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LTE), "a <= b should emit LTE" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GTE), "a >= b should emit GTE" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEQ), "a != b should emit NEQ" ); } #[test] fn compile_template_literal_emits_template_str() { let module = compile_source("let name = 'x'; `hello ${name}`;"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TEMPLATE_STR), "template literal should emit TEMPLATE_STR" ); } #[test] fn compile_call_spread_emits_spread_opcodes() { // 普通调用 / new 表达式含 spread 实参 → 发 spread 变体 opcode let module = compile_source("function f(){}; f(...[1,2]); new f(...[3]);"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::CALL_SPREAD), "f(...args) should emit CALL_SPREAD" ); assert!( module .bytecode .iter() .any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION_SPREAD), "new f(...args) should emit NEW_EXPRESSION_SPREAD" ); // 无 spread 的调用不发 spread 变体 let plain = compile_source("function f(a){}; f(1);"); assert!( !plain.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::CALL_SPREAD), "plain call should not emit CALL_SPREAD" ); } #[test] fn compile_dynamic_member_ops() { let module = compile_source("let obj = {}, k = 'x', arr = [1]; obj[k]; obj[k] = 1; arr[0];"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_ARRAY), "array literal should emit NEW_ARRAY" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GET_PROP_DYNAMIC), "obj[k] should emit GET_PROP_DYNAMIC" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_DYNAMIC), "obj[k] = should emit SET_PROP_DYNAMIC" ); } /// 收集字节码指令序列,跳过 IC 扩展字(IC_EXT_WORDS 字)与其它扩展字,逐指令定位。 fn scan_opcodes(module: &CompiledModule) -> Vec { let mut ops = Vec::new(); let mut i = 0; while i < module.bytecode.len() { let op = opcode::opcode(module.bytecode[i]); ops.push(op); i += 1 + if op.has_ic_ext_words() { opcode::IC_EXT_WORDS } else { match op { OpCode::SPILL | OpCode::UNSPILL | OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL | OpCode::DEFINE_ACCESSOR | OpCode::DEFINE_ACCESSOR_DYNAMIC | OpCode::DEFINE_PROP_ATTRS | OpCode::DELETE_PROP_STATIC | OpCode::REST_OBJECT | OpCode::INIT_PRIVATE => 1, OpCode::DEFINE_ACCESSOR_ATTRS | OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC | OpCode::GET_PRIVATE | OpCode::SET_PRIVATE | OpCode::PRIVATE_BRAND_IN => 2, _ => 0, } }; } ops } #[test] fn compile_computed_member_const_string_key_folds_to_ic() { let module = compile_source("let obj = { a: 1 }; obj[\"a\"]; obj[\"a\"] = 2;"); let ops = scan_opcodes(&module); assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"a\"] 应折叠为 IC_GET_PROP"); assert!(ops.contains(&OpCode::IC_SET_PROP), "obj[\"a\"] = 应折叠为 IC_SET_PROP"); assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量字符串键读不应发 GET_PROP_DYNAMIC"); assert!(!ops.contains(&OpCode::SET_PROP_DYNAMIC), "常量字符串键写不应发 SET_PROP_DYNAMIC"); } #[test] fn compile_computed_member_template_key_folds_to_ic() { let module = compile_source("let obj = { a: 1 }; obj[`a`];"); let ops = scan_opcodes(&module); assert!(ops.contains(&OpCode::IC_GET_PROP), "无插值模板键应折叠为 IC_GET_PROP"); assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "无插值模板键不应发 GET_PROP_DYNAMIC"); // 含插值模板保持 DYNAMIC(运行期键非常量) let interp = compile_source("let obj = {}, k = 'a'; obj[`${k}`];"); assert!( scan_opcodes(&interp).contains(&OpCode::GET_PROP_DYNAMIC), "含插值模板键应保持 GET_PROP_DYNAMIC" ); } #[test] fn compile_computed_member_numeric_and_index_keys_stay_dynamic() { // 数字键(obj[0] / arr[\"0\"])不折叠:数组元素区 IC 永不命中。 let module = compile_source("let obj = {}, arr = [1]; obj[0]; arr[\"0\"];"); let ops = scan_opcodes(&module); assert!(ops.contains(&OpCode::GET_PROP_DYNAMIC), "数字/数组下标键应保持 GET_PROP_DYNAMIC"); assert!(!ops.contains(&OpCode::IC_GET_PROP), "数字/数组下标键不应折叠为 IC_GET_PROP"); } #[test] fn compile_computed_member_compound_and_update_fold_to_ic() { // 复合赋值折叠为 ic_get → op → ic_set 三指令序列(保规范求值序),不再用单指令 RMW。 let module = compile_source("let obj = { a: 1 }; obj[\"a\"] += 1; obj[\"b\"]++;"); let ops = scan_opcodes(&module); assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"a\"] += 应先 ic_get 读属性"); assert!(ops.contains(&OpCode::IC_SET_PROP), "obj[\"a\"] += 应 ic_set 写回"); assert!(ops.contains(&OpCode::ADD), "obj[\"a\"] += 应发 ADD 运算"); assert!(!ops.contains(&OpCode::COMPOUND_MEMBER_ADD), "复合赋值不应发 COMPOUND_MEMBER_ADD 单指令"); assert!(ops.contains(&OpCode::MEMBER_INC), "obj[\"b\"]++ 应折叠为 MEMBER_INC"); assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键复合赋值不应发 GET_PROP_DYNAMIC"); } #[test] fn compile_computed_member_call_receiver_folds_to_ic() { let module = compile_source("let obj = { m() { return 1 } }; obj[\"m\"]();"); let ops = scan_opcodes(&module); assert!(ops.contains(&OpCode::IC_GET_PROP), "obj[\"m\"]() 接收者应折叠为 IC_GET_PROP"); assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键方法调用不应发 GET_PROP_DYNAMIC"); } #[test] fn compile_computed_member_chain_folds_to_ic() { let module = compile_source("let obj = { a: 1 }; obj?.[\"a\"];"); let ops = scan_opcodes(&module); assert!(ops.contains(&OpCode::IC_GET_PROP), "可选链 obj?.[\"a\"] 应折叠为 IC_GET_PROP"); assert!(!ops.contains(&OpCode::GET_PROP_DYNAMIC), "常量键可选链不应发 GET_PROP_DYNAMIC"); } #[test] fn compile_delete_new_instanceof_in() { let module = compile_source("let obj = { x: 1 }, B = function(){}; delete obj.x; new B(); obj instanceof B; 'x' in obj;"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DELETE_PROP_STATIC), "delete obj.x should emit DELETE_PROP_STATIC" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION), "new B() should emit NEW_EXPRESSION" ); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INSTANCEOF), "instanceof should emit INSTANCEOF" ); assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::IN), "in should emit IN"); } #[test] fn compile_nonfinal_relational_comparison_kept() { let module = compile_source("let s = 1; s <= 1; s + 1;"); assert!( module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LTE), "非末语句形 s <= 1 应保留 LTE 指令(结果虽未用,ToPrimitive 副作用不可删)" ); }