From e71c236dcd7deb9579915705ff269b09ce63b9a9 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 17 Jun 2026 08:20:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=85=AC=E5=BC=80?= =?UTF-8?q?=E7=B1=BB=E5=AD=97=E6=AE=B5=E5=92=8C=E9=9D=99=E6=80=81=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 51 +++++ crates/oxide_compiler/src/counter.rs | 65 ++++-- crates/oxide_compiler/src/emitter.rs | 309 ++++++++++++++++---------- crates/oxide_compiler/src/hash.rs | 26 ++- crates/oxide_vm/tests/class_tests.rs | 73 ++++++ 5 files changed, 388 insertions(+), 136 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 4c8d051e..457967f1 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -143,6 +143,9 @@ pub(crate) struct CompileCtx { pub(crate) in_derived_constructor: bool, pub(crate) in_instance_method: bool, pub(crate) in_static_method: bool, + pub(crate) static_block_this_reg: Option, + pub(crate) after_super_insert: Option>, + pub(crate) after_super_inserted: bool, /// Set when alloc_reg() overflows into the reserved this/new.target range (≥254). /// Checked after each emit phase to produce a compile error rather than silent corruption. pub(crate) reg_overflow: bool, @@ -204,6 +207,9 @@ impl CompileCtx { in_derived_constructor: false, in_instance_method: false, in_static_method: false, + static_block_this_reg: None, + after_super_insert: None, + after_super_inserted: false, reg_overflow: false, } } @@ -463,6 +469,29 @@ impl Compiler { &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext, ) -> Result { + self.compile_function_body_with_field_hooks( + param_specs, + body_stmts, + parent_ctx, + is_expression_body, + extra_bindings, + body_context, + None::, + None:: Result<(), String>>, + false, + ) + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn compile_function_body_with_field_hooks<'a, C, E>( + &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, + is_expression_body: bool, extra_bindings: &[(&str, u8)], body_context: FunctionBodyContext, + mut count_fields: Option, mut emit_fields: Option, fields_after_super: bool, + ) -> Result + where + C: FnMut(&Compiler, &mut CompileCtx), + E: FnMut(&Compiler, &mut CompileCtx) -> Result<(), String>, + { let mut ctx = CompileCtx::new(); // Inherit parent's builtin_reg_map so builtin identifiers (Math, Object, etc.) @@ -527,9 +556,19 @@ impl Compiler { } // Count pass + if !fields_after_super { + if let Some(count) = count_fields.as_mut() { + count(self, &mut ctx); + } + } for stmt in body_stmts { self.count_statement(stmt, &mut ctx); } + if fields_after_super { + if let Some(count) = count_fields.as_mut() { + count(self, &mut ctx); + } + } ctx.max_regs = ctx.max_regs.max(1); ctx.reg_overflow = false; ctx.reset_regs(); @@ -548,6 +587,18 @@ impl Compiler { } } + if let Some(emit) = emit_fields.as_mut() { + if fields_after_super { + let start = ctx.bytecode.len(); + emit(self, &mut ctx)?; + let field_code = ctx.bytecode.split_off(start); + ctx.after_super_insert = Some(field_code); + ctx.after_super_inserted = false; + } else { + emit(self, &mut ctx)?; + } + } + // Emit body statements. // Capture the last statement's expression result for expression-body arrows. let mut last_result_reg = None; diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index 1458a9ca..929941d1 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -117,26 +117,6 @@ impl Compiler { ctx.projected_pc += 5; // LOAD_CONST + GET_PROP + LOAD_CONST + two SET_PROP } - for element in &class.body.body { - let ClassElement::MethodDefinition(method) = element else { - continue; - }; - let method = method.as_ref(); - match method.kind { - MethodDefinitionKind::Constructor => {} - MethodDefinitionKind::Method => { - ctx.alloc_reg(); // method_reg - ctx.alloc_reg(); // key_reg - ctx.projected_pc += 4; // LOAD_CONST method + SET_HOME_OBJECT + LOAD_CONST key + SET_PROP - } - MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - ctx.alloc_reg(); // method_reg - ctx.alloc_reg(); // undefined getter/setter placeholder - ctx.projected_pc += 5; // LOAD_CONST method + SET_HOME_OBJECT + LOAD_CONST undefined + DEFINE_ACCESSOR + ext - } - } - } - ctx.alloc_reg(); // constructor key ctx.projected_pc += 1; // LOAD_CONST "constructor" ctx.projected_pc += 1; // SET_PROP proto.constructor = ctor @@ -144,6 +124,51 @@ impl Compiler { ctx.alloc_reg(); // prototype key ctx.projected_pc += 1; // LOAD_CONST "prototype" ctx.projected_pc += 1; // SET_PROP ctor.prototype = proto + + for element in &class.body.body { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if matches!(method.kind, MethodDefinitionKind::Constructor) { + continue; + } + ctx.alloc_reg(); // key_reg + ctx.projected_pc += 1; + ctx.alloc_reg(); // method_reg + ctx.projected_pc += 2; // LOAD_CONST method + SET_HOME_OBJECT + match method.kind { + MethodDefinitionKind::Method => { + ctx.projected_pc += 1; // SET_PROP + } + MethodDefinitionKind::Get | MethodDefinitionKind::Set => { + ctx.alloc_reg(); // undefined placeholder + ctx.projected_pc += 3; // LOAD_CONST undefined + DEFINE_ACCESSOR + ext + } + MethodDefinitionKind::Constructor => {} + } + } + ClassElement::PropertyDefinition(prop) => { + let prop = prop.as_ref(); + if prop.r#static { + ctx.alloc_reg(); // key_reg + ctx.projected_pc += 1; + if let Some(value) = &prop.value { + self.count_expression(value, ctx); + } else { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + ctx.projected_pc += 1; // SET_PROP + } + } + ClassElement::StaticBlock(block) => { + for stmt in &block.body { + self.count_statement(stmt, ctx); + } + } + ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} + } + } } pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) { diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 22c3df20..34c1af77 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -13,6 +13,60 @@ use crate::compiler::{ use crate::module::Constant; impl Compiler { + fn emit_class_key_reg(&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx) -> Result { + if !computed { + let name = self.class_property_name(key)?; + let idx = ctx.add_constant(Constant::String(name)); + let reg = ctx.alloc_reg(); + ctx.emit_load_const(reg, idx); + return Ok(reg); + } + + if matches!(key, PropertyKey::PrivateIdentifier(_)) { + return Err("private class elements not yet supported".into()); + } + self.emit_expression(key.to_expression(), ctx) + } + + fn count_class_key(&self, key: &PropertyKey, computed: bool, ctx: &mut CompileCtx) { + if computed { + self.count_expression(key.to_expression(), ctx); + } else { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + } + + fn emit_public_field_init( + &self, target_reg: u8, key: &PropertyKey, computed: bool, value: Option<&Expression>, ctx: &mut CompileCtx, + ) -> Result<(), String> { + let key_reg = self.emit_class_key_reg(key, computed, ctx)?; + let value_reg = if let Some(expr) = value { + self.emit_expression(expr, ctx)? + } else { + self.emit_undefined(ctx) + }; + if computed { + ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, target_reg, key_reg, value_reg)); + } else { + ctx.emit(opcode::encode(OpCode::SET_PROP, target_reg, value_reg, key_reg)); + } + Ok(()) + } + + fn count_public_field_init( + &self, key: &PropertyKey, computed: bool, value: Option<&Expression>, ctx: &mut CompileCtx, + ) { + self.count_class_key(key, computed, ctx); + if let Some(expr) = value { + self.count_expression(expr, ctx); + } else { + ctx.alloc_reg(); + ctx.projected_pc += 1; + } + ctx.projected_pc += 1; + } + fn static_property_name(&self, key: &PropertyKey) -> Result { match key { PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()), @@ -303,7 +357,9 @@ impl Compiler { fn class_property_name(&self, key: &PropertyKey) -> Result { match key { PropertyKey::StaticIdentifier(ident) => Ok(ident.name.as_str().to_string()), + PropertyKey::Identifier(ident) => Ok(ident.name.as_str().to_string()), PropertyKey::StringLiteral(s) => Ok(s.value.to_string()), + PropertyKey::NumericLiteral(n) => Ok(n.value.to_string()), PropertyKey::PrivateIdentifier(_) => Err("private class elements not yet supported".into()), _ => Err("unsupported class property key type".into()), } @@ -311,44 +367,34 @@ impl Compiler { fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result { let mut constructor_method = None; - let mut instance_methods = Vec::new(); - let mut static_methods = Vec::new(); - let mut instance_accessors = Vec::new(); - let mut static_accessors = Vec::new(); + let mut instance_fields = Vec::new(); let is_derived = class.super_class.is_some(); for element in &class.body.body { - let ClassElement::MethodDefinition(method) = element else { - return Err("class fields/accessors/static blocks not yet supported".into()); - }; - let method = method.as_ref(); - if method.computed { - return Err("computed class methods not yet supported".into()); - } - - match method.kind { - MethodDefinitionKind::Constructor => { - if constructor_method.is_some() { - return Err("duplicate class constructor".into()); + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if matches!(method.key, PropertyKey::PrivateIdentifier(_)) { + return Err("private class elements not yet supported".into()); } - constructor_method = Some(method); - } - MethodDefinitionKind::Method => { - let name = self.class_property_name(&method.key)?; - if method.r#static { - static_methods.push((method, name)); - } else { - instance_methods.push((method, name)); + if method.kind == MethodDefinitionKind::Constructor { + if constructor_method.is_some() { + return Err("duplicate class constructor".into()); + } + constructor_method = Some(method); } } - MethodDefinitionKind::Get | MethodDefinitionKind::Set => { - let name = self.class_property_name(&method.key)?; - if method.r#static { - static_accessors.push((method, name)); - } else { - instance_accessors.push((method, name)); + ClassElement::PropertyDefinition(prop) => { + let prop = prop.as_ref(); + if matches!(prop.key, PropertyKey::PrivateIdentifier(_)) { + return Err("private class elements not yet supported".into()); + } + if !prop.r#static { + instance_fields.push(prop); } } + ClassElement::AccessorProperty(_) => return Err("class accessor properties not yet supported".into()), + ClassElement::StaticBlock(_) | ClassElement::TSIndexSignature(_) => {} } } @@ -367,22 +413,58 @@ impl Compiler { let mut ctor_module = if let Some(method) = constructor_method { let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; - self.compile_function_body_with_bindings( + self.compile_function_body_with_field_hooks( ¶m_names, body_stmts, ctx, false, &self_binding, FunctionBodyContext::ClassElement, + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| { + for field in &instance_fields { + compiler.count_public_field_init(&field.key, field.computed, field.value.as_ref(), field_ctx); + } + }), + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> { + for field in &instance_fields { + compiler.emit_public_field_init( + 254, + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + )?; + } + Ok(()) + }), + is_derived, )? } else { - let mut module = self.compile_function_body_with_bindings( + let mut module = self.compile_function_body_with_field_hooks( &[], &[], ctx, false, &self_binding, FunctionBodyContext::ClassElement, + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| { + for field in &instance_fields { + compiler.count_public_field_init(&field.key, field.computed, field.value.as_ref(), field_ctx); + } + }), + Some(|compiler: &Compiler, field_ctx: &mut CompileCtx| -> Result<(), String> { + for field in &instance_fields { + compiler.emit_public_field_init( + 254, + &field.key, + field.computed, + field.value.as_ref(), + field_ctx, + )?; + } + Ok(()) + }), + is_derived, )?; if is_derived { module.bytecode.clear(); @@ -390,6 +472,13 @@ impl Compiler { module.n_registers = 1; module.bytecode.push(opcode::encode(OpCode::SUPER_CALL, 0, 0, 0)); module.bytecode.push(0); + let mut field_ctx = CompileCtx::new(); + for field in &instance_fields { + self.emit_public_field_init(254, &field.key, field.computed, field.value.as_ref(), &mut field_ctx)?; + } + module.bytecode.extend(field_ctx.bytecode); + module.constants = field_ctx.constants; + module.n_registers = field_ctx.max_regs.max(1); module.bytecode.push(opcode::encode(OpCode::RETURN, 0, 0, 0)); } module @@ -418,88 +507,6 @@ impl Compiler { ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, super_reg, proto_link_key_reg)); } - for (method, method_name) in instance_methods { - let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; - let saved_method = ctx.in_instance_method; - ctx.in_instance_method = true; - let mut method_module = self.compile_function_body_with_bindings( - ¶m_names, - body_stmts, - ctx, - false, - &self_binding, - FunctionBodyContext::ClassElement, - )?; - ctx.in_instance_method = saved_method; - method_module.function_name = Some(method_name.clone()); - method_module.needs_home_object = true; - ctx.sub_modules.push(method_module); - - let method_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32)); - let method_reg = ctx.alloc_reg(); - ctx.emit_load_const(method_reg, method_idx); - ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, proto_reg, 0)); - - let key_idx = ctx.add_constant(Constant::String(method_name)); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, key_idx); - ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, method_reg, key_reg)); - } - - for (method, method_name) in instance_accessors { - let accessor_reg = self.emit_class_method_function(method, &method_name, proto_reg, ctx, &self_binding)?; - let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = match method.kind { - MethodDefinitionKind::Get => (accessor_reg, undef_reg), - MethodDefinitionKind::Set => (undef_reg, accessor_reg), - _ => unreachable!(), - }; - let key_idx = ctx.add_constant(Constant::String(method_name)); - ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, proto_reg, get_reg, set_reg)); - ctx.emit(key_idx as u32); - } - - for (method, method_name) in static_methods { - let (param_names, body_stmts) = self.extract_function_parts(method.value.as_ref())?; - let saved_static = ctx.in_static_method; - ctx.in_static_method = true; - let mut method_module = self.compile_function_body_with_bindings( - ¶m_names, - body_stmts, - ctx, - false, - &self_binding, - FunctionBodyContext::ClassElement, - )?; - ctx.in_static_method = saved_static; - method_module.function_name = Some(method_name.clone()); - method_module.needs_home_object = true; - ctx.sub_modules.push(method_module); - - let method_idx = ctx.add_constant(Constant::BytecodeFunc(ctx.sub_modules.len() as u32)); - let method_reg = ctx.alloc_reg(); - ctx.emit_load_const(method_reg, method_idx); - ctx.emit(opcode::encode(OpCode::SET_HOME_OBJECT, method_reg, ctor_reg, 0)); - - let key_idx = ctx.add_constant(Constant::String(method_name)); - let key_reg = ctx.alloc_reg(); - ctx.emit_load_const(key_reg, key_idx); - ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, method_reg, key_reg)); - } - - for (method, method_name) in static_accessors { - let accessor_reg = self.emit_class_method_function(method, &method_name, ctor_reg, ctx, &self_binding)?; - let undef_reg = self.emit_undefined(ctx); - let (get_reg, set_reg) = match method.kind { - MethodDefinitionKind::Get => (accessor_reg, undef_reg), - MethodDefinitionKind::Set => (undef_reg, accessor_reg), - _ => unreachable!(), - }; - let key_idx = ctx.add_constant(Constant::String(method_name)); - ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, ctor_reg, get_reg, set_reg)); - ctx.emit(key_idx as u32); - } - let ctor_key_idx = ctx.add_constant(Constant::String("constructor".to_string())); let ctor_key_reg = ctx.alloc_reg(); ctx.emit_load_const(ctor_key_reg, ctor_key_idx); @@ -510,6 +517,71 @@ impl Compiler { ctx.emit_load_const(proto_key_reg, proto_key_idx); ctx.emit(opcode::encode(OpCode::SET_PROP, ctor_reg, proto_reg, proto_key_reg)); + for element in &class.body.body { + match element { + ClassElement::MethodDefinition(method) => { + let method = method.as_ref(); + if method.kind == MethodDefinitionKind::Constructor { + continue; + } + let home_reg = if method.r#static { ctor_reg } else { proto_reg }; + let key_reg = self.emit_class_key_reg(&method.key, method.computed, ctx)?; + let method_name = if method.computed { + "".to_string() + } else { + self.class_property_name(&method.key)? + }; + let accessor_reg = + self.emit_class_method_function(method, &method_name, home_reg, ctx, &self_binding)?; + match method.kind { + MethodDefinitionKind::Method => { + if method.computed { + ctx.emit(opcode::encode(OpCode::SET_PROP_DYNAMIC, home_reg, key_reg, accessor_reg)); + } else { + ctx.emit(opcode::encode(OpCode::SET_PROP, home_reg, accessor_reg, key_reg)); + } + } + MethodDefinitionKind::Get | MethodDefinitionKind::Set => { + if method.computed { + return Err("computed class accessors not yet supported".into()); + } + let undef_reg = self.emit_undefined(ctx); + let (get_reg, set_reg) = match method.kind { + MethodDefinitionKind::Get => (accessor_reg, undef_reg), + MethodDefinitionKind::Set => (undef_reg, accessor_reg), + _ => unreachable!(), + }; + let key_name = self.class_property_name(&method.key)?; + let key_idx = ctx.add_constant(Constant::String(key_name)); + ctx.emit(opcode::encode(OpCode::DEFINE_ACCESSOR, home_reg, get_reg, set_reg)); + ctx.emit(key_idx as u32); + } + MethodDefinitionKind::Constructor => unreachable!(), + } + } + ClassElement::PropertyDefinition(prop) => { + let prop = prop.as_ref(); + if prop.r#static { + let saved_static_this = ctx.static_block_this_reg; + ctx.static_block_this_reg = Some(ctor_reg); + self.emit_public_field_init(ctor_reg, &prop.key, prop.computed, prop.value.as_ref(), ctx)?; + ctx.static_block_this_reg = saved_static_this; + } + } + ClassElement::StaticBlock(block) => { + let saved_static_this = ctx.static_block_this_reg; + ctx.static_block_this_reg = Some(ctor_reg); + ctx.push_scope(); + for stmt in &block.body { + self.emit_statement(stmt, ctx)?; + } + ctx.pop_scope(); + ctx.static_block_this_reg = saved_static_this; + } + ClassElement::AccessorProperty(_) | ClassElement::TSIndexSignature(_) => {} + } + } + Ok(ctor_reg) } @@ -1739,6 +1811,12 @@ impl Compiler { let result_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::SUPER_CALL, result_reg, first_arg_reg, 0)); ctx.emit(arg_regs.len() as u32); + if !ctx.after_super_inserted { + if let Some(field_code) = ctx.after_super_insert.clone() { + ctx.bytecode.extend(field_code); + ctx.after_super_inserted = true; + } + } return Ok(result_reg); } let (callee_reg, this_reg) = match &call.callee { @@ -1804,7 +1882,8 @@ impl Compiler { } Expression::ThisExpression(_) => { let r = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, 254, 0)); + let src = ctx.static_block_this_reg.unwrap_or(254); + ctx.emit(opcode::encode(OpCode::LOAD_VAR, r, src, 0)); Ok(r) } Expression::RegExpLiteral(lit) => { diff --git a/crates/oxide_compiler/src/hash.rs b/crates/oxide_compiler/src/hash.rs index 120361fc..037d02bc 100644 --- a/crates/oxide_compiler/src/hash.rs +++ b/crates/oxide_compiler/src/hash.rs @@ -168,13 +168,21 @@ fn hash_property_key(key: &PropertyKey, h: &mut rustc_hash::FxHasher) { PropertyKey::StaticIdentifier(ident) => { ident.name.as_str().hash(h); } + PropertyKey::Identifier(ident) => { + ident.name.as_str().hash(h); + } PropertyKey::StringLiteral(s) => { s.value.hash(h); } + PropertyKey::NumericLiteral(n) => { + n.value.to_bits().hash(h); + } PropertyKey::PrivateIdentifier(pi) => { pi.name.as_str().hash(h); } - _ => {} + _ => { + hash_expression(key.to_expression(), h); + } }); } @@ -185,6 +193,9 @@ fn hash_class_element(element: &ClassElement, h: &mut rustc_hash::FxHasher) { method.computed.hash(h); std::mem::discriminant(&method.kind).hash(h); hash_property_key(&method.key, h); + if method.computed { + hash_property_key(&method.key, h); + } (method.value.params.items.len() as u32).hash(h); if let Some(body) = &method.value.body { for stmt in &body.statements { @@ -192,6 +203,19 @@ fn hash_class_element(element: &ClassElement, h: &mut rustc_hash::FxHasher) { } } } + ClassElement::PropertyDefinition(prop) => { + prop.r#static.hash(h); + prop.computed.hash(h); + hash_property_key(&prop.key, h); + if let Some(value) = &prop.value { + hash_expression(value, h); + } + } + ClassElement::StaticBlock(block) => { + for stmt in &block.body { + hash_statement(stmt, h); + } + } _ => {} }); } diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index e43f0c89..f796f7fd 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -170,3 +170,76 @@ fn inherited_static_method_is_found_on_constructor_chain() { 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); +}