From 270bc8693317158fd3cfad7885493215a543e43e Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 9 Jun 2026 09:59:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20class=20=E9=9D=99?= =?UTF-8?q?=E6=80=81=E6=96=B9=E6=B3=95=E4=B8=8E=E9=9D=99=E6=80=81=20super?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 3 ++ crates/oxide_compiler/src/emitter.rs | 50 ++++++++++++++++++---- crates/oxide_vm/tests/class_tests.rs | 36 ++++++++++++++++ crates/oxide_vm/tests/proto_chain_tests.rs | 9 ++++ 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 0a094958..94534d47 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -72,6 +72,7 @@ pub(crate) struct CompileCtx { pub(crate) enclosing_this_reg: u8, pub(crate) in_derived_constructor: bool, pub(crate) in_instance_method: bool, + pub(crate) in_static_method: bool, } impl CompileCtx { @@ -93,6 +94,7 @@ impl CompileCtx { enclosing_this_reg: 254, // conventional this register at top level in_derived_constructor: false, in_instance_method: false, + in_static_method: false, } } @@ -303,6 +305,7 @@ impl Compiler { ctx.enclosing_this_reg = parent_ctx.enclosing_this_reg; ctx.in_derived_constructor = parent_ctx.in_derived_constructor; ctx.in_instance_method = parent_ctx.in_instance_method; + ctx.in_static_method = parent_ctx.in_static_method; // Inherit parent's global scope entries so previously-declared function names // are visible from within the body. diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 75c4a1b0..d79047fb 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -21,6 +21,7 @@ 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 is_derived = class.super_class.is_some(); for element in &class.body.body { @@ -28,9 +29,6 @@ impl Compiler { return Err("class fields/accessors/static blocks not yet supported".into()); }; let method = method.as_ref(); - if method.r#static { - return Err("static methods not yet supported".into()); - } if method.computed { return Err("computed class methods not yet supported".into()); } @@ -44,7 +42,11 @@ impl Compiler { } MethodDefinitionKind::Method => { let name = self.class_property_name(&method.key)?; - instance_methods.push((method, name)); + if method.r#static { + static_methods.push((method, name)); + } else { + instance_methods.push((method, name)); + } } MethodDefinitionKind::Get | MethodDefinitionKind::Set => { return Err("getters/setters not yet supported".into()); @@ -126,6 +128,28 @@ impl Compiler { ctx.emit(opcode::encode(OpCode::SET_PROP, proto_reg, method_reg, key_reg)); } + 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)?; + 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)); + } + 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); @@ -789,7 +813,7 @@ impl Compiler { } Expression::StaticMemberExpression(member) => { if matches!(&member.object, Expression::Super(_)) { - if !ctx.in_instance_method && !ctx.in_derived_constructor { + if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { return Err("super property only supported in class methods".into()); } let prop_name = member.property.name.as_str(); @@ -799,7 +823,12 @@ impl Compiler { let this_reg = ctx.alloc_reg(); ctx.emit(opcode::encode(OpCode::LOAD_VAR, this_reg, 254, 0)); let result_reg = ctx.alloc_reg(); - ctx.emit(opcode::encode(OpCode::SUPER_GET_PROP, result_reg, this_reg, key_reg)); + let op = if ctx.in_static_method { + OpCode::SUPER_STATIC_GET_PROP + } else { + OpCode::SUPER_GET_PROP + }; + ctx.emit(opcode::encode(op, result_reg, this_reg, key_reg)); return Ok(result_reg); } let obj_reg = self.emit_expression(&member.object, ctx)?; @@ -1220,10 +1249,15 @@ impl Compiler { ctx.emit_load_const(key_reg, idx); let callee_reg = ctx.alloc_reg(); if is_super_member { - if !ctx.in_instance_method && !ctx.in_derived_constructor { + if !ctx.in_instance_method && !ctx.in_static_method && !ctx.in_derived_constructor { return Err("super property only supported in class methods".into()); } - ctx.emit(opcode::encode(OpCode::SUPER_GET_PROP, callee_reg, obj_reg, key_reg)); + let op = if ctx.in_static_method { + OpCode::SUPER_STATIC_GET_PROP + } else { + OpCode::SUPER_GET_PROP + }; + ctx.emit(opcode::encode(op, callee_reg, obj_reg, key_reg)); } else { ctx.emit(opcode::encode(OpCode::LOAD_VAR, callee_reg, obj_reg, 0)); ctx.emit(opcode::encode(OpCode::IC_GET_PROP, 0, callee_reg, key_reg)); diff --git a/crates/oxide_vm/tests/class_tests.rs b/crates/oxide_vm/tests/class_tests.rs index b8ae4b1b..165d1674 100644 --- a/crates/oxide_vm/tests/class_tests.rs +++ b/crates/oxide_vm/tests/class_tests.rs @@ -113,3 +113,39 @@ fn derived_method_super_call_uses_current_receiver() { .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); +} diff --git a/crates/oxide_vm/tests/proto_chain_tests.rs b/crates/oxide_vm/tests/proto_chain_tests.rs index 54de906a..56aedd44 100644 --- a/crates/oxide_vm/tests/proto_chain_tests.rs +++ b/crates/oxide_vm/tests/proto_chain_tests.rs @@ -170,6 +170,15 @@ fn derived_class_instanceof_parent() { ); } +#[test] +fn derived_constructor_inherits_parent_constructor_chain() { + assert_bool!( + eval("class A {} class B extends A {} Object.getPrototypeOf(B) === A"), + true, + "derived constructor __proto__ parent" + ); +} + // -- instanceof for native types should NOT respond to their own false positives -- #[test] fn number_not_instanceof_array() {