mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
8c4b766e70
derived 构造器 super 状态改由 CallFrame.super_called 显式标志承载, 取代依赖 regs[254] 值判定:多层继承中间 derived 帧由 SUPER_CALL 压入 this=构造对象,值判定三重失效(未调 super 不报错 / this-before-super 失效 / 合法 super() 误判 more-than-once)。父构造器正常返回经 construct_result_reg==Some(254) 识别置位调用方帧(该 ABI 不变量仅 SUPER_CALL 压帧满足),抛错走 unwind 不置位。 do_return 检查放宽为非对象返回值(含 undefined/null/原始值)且未调 super 时抛 ReferenceError(规范 §9.2.2.2),调过 super 回退构造 this。 super 构造器改取当前帧 callee 原型而非 newTarget 原型:newTarget 跨 SUPER_CALL 压帧保持为最外层类,取其原型在中间 derived 层解析回同一 父类导致无限压帧。
570 lines
18 KiB
Rust
570 lines
18 KiB
Rust
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(&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 {actual}");
|
||
}
|
||
|
||
#[test]
|
||
fn class_declaration_is_function_value() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A {} ; typeof A").unwrap();
|
||
let ty = vm.lookup_str(result).expect("typeof result should be string");
|
||
assert_eq!(ty, "function");
|
||
}
|
||
|
||
#[test]
|
||
fn class_method_is_on_prototype() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { m() { return 1; } } new A().m()").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn class_instance_getter_returns_value() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { get x() { return 2; } } new A().x").unwrap();
|
||
assert_eq!(result.as_int(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn class_instance_setter_updates_receiver() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { set x(v) { this.y = v; } } var a = new A(); a.x = 4; a.y").unwrap();
|
||
assert_eq!(result.as_int(), 4);
|
||
}
|
||
|
||
#[test]
|
||
fn class_static_getter_returns_value() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { static get x() { return 5; } } A.x").unwrap();
|
||
assert_eq!(result.as_int(), 5);
|
||
}
|
||
|
||
#[test]
|
||
fn class_constructor_initializes_instance_state() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { constructor(x) { this.x = x; } } new A(3).x").unwrap();
|
||
assert_eq!(result.as_int(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn class_expression_constructs_instances() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "const C = class Foo { method() { return 2; } }; new C().method()").unwrap();
|
||
assert_eq!(result.as_int(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn class_expression_inner_name_is_visible_in_method_body() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "const C = class Foo { self() { return Foo; } }; new C().self() === C").unwrap();
|
||
assert!(
|
||
result.is_bool() && result.as_bool(),
|
||
"expected inner class name to resolve to constructor"
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn class_constructor_cannot_be_called_without_new() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "class A {} ; A()").unwrap_err();
|
||
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn class_constructor_object_return_overrides_instance() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { constructor() { return { x: 1 }; } } new A().x").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn class_constructor_primitive_return_preserves_instance() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { constructor() { this.x = 1; return 5; } } new A().x").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_constructor_super_initializes_parent_state() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { constructor(){ this.x = 1 } } class B extends A { constructor(){ super(); this.y = 2 } } let b = new B(); b.x + b.y",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_default_constructor_delegates_to_parent() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { constructor(){ this.x = 1 } } class B extends A {} new B().x").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_constructor_this_before_super_throws_reference_error() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(
|
||
&mut vm,
|
||
"class A { constructor(){ this.x = 1 } } class B extends A { constructor(){ this.x = 2; super(); } } new B()",
|
||
)
|
||
.unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn three_level_inheritance_super_chain_constructs_normally() {
|
||
// 多层继承(≥3 层):中间 derived 帧由 SUPER_CALL 压入,合法 super() 不得误报
|
||
// more-than-once,构造链须逐层完成。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { constructor(){ this.v = 1; } } class B extends A { constructor(){ super(); this.w = 2; } } class C extends B { constructor(){ super(); this.u = 3; } } var c = new C(); c.v + c.w + c.u",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 6.0);
|
||
}
|
||
|
||
#[test]
|
||
fn intermediate_derived_returning_without_super_throws() {
|
||
// 中间 derived 构造器未调 super 直接 return:须抛 ReferenceError(此前
|
||
// regs[254] 值判定因 this 为外层构造对象而漏检)。
|
||
let mut vm = Vm::new();
|
||
let err = eval(
|
||
&mut vm,
|
||
"class A{} class B extends A{ constructor(){ return; } } class C extends B{} new C()",
|
||
)
|
||
.unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn derived_returning_without_super_throws_reference_error() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "class A{} class B extends A{ constructor(){ return; } } new B()").unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn intermediate_derived_this_before_super_throws() {
|
||
// 中间 derived 帧由 SUPER_CALL 压入后 super() 前读 this:须抛 ReferenceError。
|
||
let mut vm = Vm::new();
|
||
let err = eval(
|
||
&mut vm,
|
||
"class A{} class B extends A{ constructor(){ this.x = 1; super(); } } class C extends B{ constructor(){ super(); } } new C()",
|
||
)
|
||
.unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn intermediate_derived_double_super_throws() {
|
||
// 中间 derived 帧二次调用 super():须抛 more-than-once(字节码父构造器返回后
|
||
// 置位 super_called 生效)。
|
||
let mut vm = Vm::new();
|
||
let err = eval(
|
||
&mut vm,
|
||
"class A{} class B extends A{ constructor(){ super(); super(); } } class C extends B{} new C()",
|
||
)
|
||
.unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn derived_constructor_returning_primitive_without_super_throws() {
|
||
// 规范 §9.2.2.2:derived 返回非对象值(42/null)且未调 super → ReferenceError,
|
||
// 而非静默回退构造 this。
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "class A{} class B extends A{ constructor(){ return 42; } } new B()").unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn derived_constructor_returning_null_without_super_throws() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "class A{} class B extends A{ constructor(){ return null; } } new B()").unwrap_err();
|
||
assert!(err.contains("ReferenceError"), "expected ReferenceError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn derived_constructor_returning_object_overrides_instance() {
|
||
// derived 返回对象值:直接作为构造结果,不校验 super。
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A{} class B extends A{ constructor(){ return { x: 1 }; } } new B().x").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_constructor_primitive_return_after_super_preserves_this() {
|
||
// derived 调过 super 后返回原始值:回退构造 this,实例字段保留。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A{ constructor(){ this.v = 5; } } class B extends A{ constructor(){ super(); return 42; } } new B().v",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 5.0);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_method_super_call_uses_current_receiver() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { m(){ return this.x } } class B extends A { constructor(){ super(); this.x = 2 } m(){ return super.m() + 1 } } new B().m()",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn static_method_is_callable_on_constructor() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { static m(){ return 1 } } A.m()").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn static_and_instance_methods_do_not_collide() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { m(){ return 1 } static m(){ return 2 } } let a = new A(); a.m() + A.m()",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_static_method_super_call_resolves_parent_constructor() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { static m(){ return 1 } } class B extends A { static n(){ return super.m() + 1 } } B.n()",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 2.0);
|
||
}
|
||
|
||
#[test]
|
||
fn inherited_static_method_is_found_on_constructor_chain() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class A { static m(){ return 4 } } class B extends A {} B.m()").unwrap();
|
||
assert_eq!(result.as_int(), 4);
|
||
}
|
||
|
||
#[test]
|
||
fn public_class_field_initializer_sets_own_property() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class C { x = 1; } new C().x").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn public_class_field_without_initializer_is_undefined() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class C { x; } typeof new C().x").unwrap();
|
||
let ty = vm.lookup_str(result).expect("typeof result should be string");
|
||
assert_eq!(ty, "undefined");
|
||
}
|
||
|
||
#[test]
|
||
fn public_class_fields_run_before_base_constructor_body() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class C { x = this.y; constructor(){ this.y = 2; } } typeof new C().x").unwrap();
|
||
let ty = vm.lookup_str(result).expect("typeof result should be string");
|
||
assert_eq!(ty, "undefined");
|
||
}
|
||
|
||
#[test]
|
||
fn public_class_fields_run_after_super_in_derived_constructor() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class B { constructor(){ this.b = 1; } } class D extends B { x = this.b; } new D().x",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn static_class_field_sets_constructor_property() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class C { static x = 1; } C.x").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn static_class_block_binds_this_to_constructor() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { static x = 1; static { this.y = this.x + 1; } static z = this.y + 1; } C.z",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn computed_public_class_method_key_is_supported() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var k = 'm'; class C { [k](){ return 3; } } new C().m()").unwrap();
|
||
assert_eq!(result.as_int(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn computed_public_class_field_key_is_supported() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var k = 'x'; class C { [k] = 5; } new C().x").unwrap();
|
||
assert_eq!(result.as_int(), 5);
|
||
}
|
||
|
||
#[test]
|
||
fn computed_static_class_field_key_is_supported() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var k = 'x'; class C { static [k] = 7; } C.x").unwrap();
|
||
assert_eq!(result.as_int(), 7);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_field_read_returns_value() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class C { #x = 1; get(){ return this.#x; } } new C().get()").unwrap();
|
||
assert_eq!(result.as_int(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_field_write_round_trips() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #x; set(v){ this.#x = v; } get(){ return this.#x; } } var c = new C(); c.set(4); c.get()",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result.as_int(), 4);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_method_call_uses_receiver() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "class C { #m(){ return 4; } get(){ return this.#m(); } } new C().get()").unwrap();
|
||
assert_eq!(result.as_int(), 4);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_brand_in_checks_presence() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #x = 1; has(o){ return #x in o; } } var c = new C(); c.has(c) && !c.has({})",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_missing_brand_throws_type_error() {
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "class C { #x = 1; get(o){ return o.#x; } } new C().get({})").unwrap_err();
|
||
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn private_static_field_and_method_work_on_constructor() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { static #x = 2; static #m(){ return this.#x + 3; } static get(){ return this.#m(); } } C.get()",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 5.0);
|
||
}
|
||
|
||
#[test]
|
||
fn derived_private_field_initializes_after_super() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class B { constructor(){ this.b = 3; } } class D extends B { #x = this.b; get(){ return this.#x; } } new D().get()",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result.as_int(), 3);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_fields_are_hidden_from_reflection_and_string_access() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #x = 1; y = 2; keys(){ return Object.keys(this).length + Object.getOwnPropertyNames(this).length + (this['#x'] === undefined ? 10 : 0); } } new C().keys()",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 12.0);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_fields_are_hidden_from_for_in() {
|
||
let mut vm = Vm::new();
|
||
// 公有字段 y 可枚举(计数 1),私有 #x 不可见:若 #x 泄漏则计数为 2。
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #x = 1; y = 2; } var o = new C(); var n = 0; for (var k in o) { n = n + 1; } n",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 1.0);
|
||
}
|
||
|
||
#[test]
|
||
fn private_class_same_name_different_classes_do_not_share_brand() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class A { #x = 1; has(o){ return #x in o; } } class B { #x = 2; } var a = new A(); a.has(a) && !a.has(new B())",
|
||
)
|
||
.unwrap();
|
||
assert!(result.is_bool() && result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn this_survives_native_call_inside_method() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { v=7; m(){ Object.keys(this); Object.getOwnPropertyNames(this); return this.v; } } new C().m()",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result.as_int(), 7);
|
||
}
|
||
|
||
#[test]
|
||
fn private_field_access_does_not_pierce_prototype() {
|
||
// P4:Object.create(instance) 的对象访问 #x 抛 TypeError(字段不跨原型链)。
|
||
let mut vm = Vm::new();
|
||
let err = eval(&mut vm, "class C { #x = 1; m() { return this.#x; } } Object.create(new C()).m()").unwrap_err();
|
||
assert!(err.contains("TypeError"), "expected TypeError, got: {err}");
|
||
}
|
||
|
||
#[test]
|
||
fn private_brand_in_does_not_pierce_prototype() {
|
||
// P5:#x in Object.create(instance) 为 false(PrivateFieldIn 只查 own)。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #x = 1; check(o) { return #x in o; } } var inst = new C(); var o = Object.create(inst); new C().check(o) === false && new C().check(inst) === true",
|
||
)
|
||
.unwrap();
|
||
assert!(result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn private_method_in_requires_own_brand() {
|
||
// 方法场景:#m in Object.create(instance) 为 false(无 own brand)。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #m(){ return 1; } check(o) { return #m in o; } } var inst = new C(); new C().check(inst) === true && new C().check(Object.create(inst)) === false",
|
||
)
|
||
.unwrap();
|
||
assert!(result.as_bool());
|
||
}
|
||
|
||
#[test]
|
||
fn private_field_read_write_after_two_statements() {
|
||
// 回归:字段 set/get 跨语句边界正常(不依赖 brand cell 值比较)。
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"class C { #x = 0; set(v){ this.#x = v; } get(){ return this.#x; } } var c = new C(); c.set(4); c.get()",
|
||
)
|
||
.unwrap();
|
||
assert_eq!(result.as_int(), 4);
|
||
}
|
||
|
||
#[test]
|
||
fn computed_field_key_evaluated_once_at_class_definition() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var n=0; class C { [++n] = 1; [++n] = 2; } n").unwrap();
|
||
assert_num(result, 2.0);
|
||
}
|
||
|
||
#[test]
|
||
fn computed_field_key_once_but_value_per_instance() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var n=0; class C { [++n] = ++n; } var a=new C(); var b=new C(); n*100 + a[1]*10 + b[1]",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 323.0);
|
||
}
|
||
|
||
#[test]
|
||
fn computed_keys_evaluate_in_source_order_across_static_and_instance() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var i=0; class C { [i++] = i++; static [i++] = i++; [i++] = i++; } var c = new C(); i*1000 + c[0]*100 + c[2]*10 + C[1]",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 6453.0);
|
||
}
|
||
|
||
#[test]
|
||
fn field_definition_does_not_trigger_inherited_setter() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var log=[]; class B { set x(v){ log.push('setter'); } } class C extends B { x = 1; } var c = new C(); log.length*100 + (c.hasOwnProperty('x')?10:0) + c.x",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 11.0);
|
||
}
|
||
|
||
#[test]
|
||
fn field_value_captures_outer_variable() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var n=0; class C { x = ++n; } var c = new C(); n*10 + c.x").unwrap();
|
||
assert_num(result, 11.0);
|
||
}
|
||
|
||
#[test]
|
||
fn class_expression_field_value_captures_outer_variable() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(&mut vm, "var n=0; var C = class { x = ++n; }; new C(); n").unwrap();
|
||
assert_num(result, 1.0);
|
||
}
|
||
|
||
#[test]
|
||
fn field_initializers_run_once_on_second_super_call() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var n=0; class B {} var C = class extends B { field = ++n; constructor(){ super(); super(); } }; var err; try { new C(); } catch(e) { err = e.name; } (err === 'ReferenceError'?1:0)*10 + n",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 11.0);
|
||
}
|
||
|
||
#[test]
|
||
fn postfix_increment_on_captured_cell_returns_old_value() {
|
||
let mut vm = Vm::new();
|
||
let result = eval(
|
||
&mut vm,
|
||
"var n=0; var arr = (function(){ return [n++, n, n++]; })(); arr[0]*1000 + arr[1]*100 + arr[2]*10 + n",
|
||
)
|
||
.unwrap();
|
||
assert_num(result, 112.0);
|
||
}
|