feat: 支持 class 静态方法与静态 super
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -21,6 +21,7 @@ impl Compiler {
|
||||
fn emit_class(&self, class: &Class, ctx: &mut CompileCtx) -> Result<u8, String> {
|
||||
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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user