feat: 函数 length 属性(字节码函数/bind 包装器)+ getter/setter name 前缀

This commit is contained in:
爱特每羊队
2026-08-09 15:38:45 +08:00
parent 8f8c9a39ff
commit c65d7e0373
10 changed files with 263 additions and 19 deletions
+8 -2
View File
@@ -121,6 +121,7 @@ impl Vm {
let needs_home_object = sub.needs_home_object;
let upvalue_captures = sub.upvalue_captures.clone();
let function_name = sub.function_name.clone();
let function_length = sub.function_length;
let result = self.create_function_object(
sub_idx,
is_arrow,
@@ -130,13 +131,18 @@ impl Vm {
);
// 函数名推断:emit 端在变量声明/对象属性赋值点设置 function_name。
let func_obj = unsafe { &mut *result.as_js_object_ptr() };
let length_si = self.kernel_core.perm_interner().intern("length").0;
let name_si = self.kernel_core.perm_interner().intern("name").0;
// length/name 描述符均为不可写、不可枚举、可配置(SetFunctionLength /
// SetFunctionName 语义);length 先于 name 定义保证属性序 [prototype, length, name]。
let attrs = PropAttributes::new(false, false, true);
let length_val = JsValue::int(function_length as i32);
self.define_data_property(func_obj, length_si, length_val, attrs)?;
let name_val = function_name
.as_deref()
.map(|n| self.new_string(n))
.unwrap_or_else(|| self.new_string(""));
// name 属性描述符:不可写、不可枚举、可配置(SetFunctionName 语义)。
self.define_data_property(func_obj, name_si, name_val, PropAttributes::new(false, false, true))?;
self.define_data_property(func_obj, name_si, name_val, attrs)?;
if !upvalue_captures.is_empty() {
// 链式捕获(parent_uv_idx):从父闭包(创建者)的 upvalues 取 cell。
let parent_upvalues: Vec<*mut Cell> = match self.current_callee() {
+10 -4
View File
@@ -237,9 +237,16 @@ impl Vm {
&mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
) -> Result<(), String> {
vm_trace!("call_or_push_setter: frame_push={}", use_frame_push);
// native setter 抛错经 call_function_sync 以 String 返回(未 unwind)——
// 在此经 raise_call_error 恢复为可捕获的 JS 异常(与 getter 路径对称)。
let call_native = |vm: &mut Self| -> Result<(), String> {
if let Err(err) = vm.call_function_sync(setter, receiver, &[val]) {
vm.raise_call_error(&err)?;
}
Ok(())
};
if !use_frame_push {
self.call_function_sync(setter, receiver, &[val])?;
return Ok(());
return call_native(self);
}
if !setter.is_object() {
return self.raise_type_error("setter is not callable");
@@ -249,8 +256,7 @@ impl Vm {
return self.raise_type_error("setter is not callable");
}
if setter_obj.native_fn().is_some() {
self.call_function_sync(setter, receiver, &[val])?;
return Ok(());
return call_native(self);
}
self.push_bytecode_frame(
setter,
@@ -0,0 +1,126 @@
//! 函数 length/name 属性集成测试:普通字节码函数、箭头函数、bind 包装器、
//! getter/setter 前缀。每个用例独立编译执行,断言顶层表达式结果。
use oxide_bytecode::module::CompiledModule;
use oxide_compiler::compiler::Compiler;
use oxide_parser::Allocator;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(source: &str) -> JsValue {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
let module = Compiler::new().compile(&program).expect("compile failed");
run(&module)
}
fn run(module: &CompiledModule) -> JsValue {
let mut vm = Vm::new();
vm.run(module).expect("vm run failed")
}
fn eval_str(source: &str) -> String {
let v = eval(source);
if v.is_string() {
unsafe { oxide_runtime_api::string_data(v) }.to_string()
} else {
format!("{v}")
}
}
#[test]
fn bytecode_function_length_counts_formals_before_first_default() {
assert_eq!(eval("function f(a,b,c){}; f.length"), JsValue::int(3));
assert_eq!(eval("function f(a,b=1,c){}; f.length"), JsValue::int(1));
assert_eq!(eval("function f(...r){}; f.length"), JsValue::int(0));
assert_eq!(eval("var f = function named(a,b,c){}; f.length"), JsValue::int(3));
}
#[test]
fn arrow_function_length_matches_formals() {
assert_eq!(eval("var f=(a,b)=>a+b; f.length"), JsValue::int(2));
assert_eq!(eval("var f=(a,b,c)=>a+b+c; f.length"), JsValue::int(3));
}
#[test]
fn class_method_and_accessor_names() {
assert_eq!(eval("class C { m(a,b){} }; C.prototype.m.length"), JsValue::int(2));
assert_eq!(
eval_str("class C { get x(){} set x(v){} }; Object.getOwnPropertyDescriptor(C.prototype,'x').get.name"),
"get x"
);
assert_eq!(
eval_str("class C { get x(){} set x(v){} }; Object.getOwnPropertyDescriptor(C.prototype,'x').set.name"),
"set x"
);
}
#[test]
fn object_literal_accessor_names() {
assert_eq!(
eval_str("var o={ get x(){}, set x(v){} }; Object.getOwnPropertyDescriptor(o,'x').get.name"),
"get x"
);
assert_eq!(
eval_str("var o={ get x(){}, set x(v){} }; Object.getOwnPropertyDescriptor(o,'x').set.name"),
"set x"
);
}
#[test]
fn bind_wrapper_length_is_target_length_minus_bound_args() {
assert_eq!(eval("function f(a,b){}; f.bind(null,1).length"), JsValue::int(1));
assert_eq!(eval("function f(){}; f.bind(null,1,2,3).length"), JsValue::int(0));
assert_eq!(eval("function f(a,b,c){}; f.bind(null).length"), JsValue::int(3));
}
#[test]
fn bind_wrapper_name_has_bound_prefix() {
assert_eq!(eval_str("function f(a){}; f.bind(null).name"), "bound f");
assert_eq!(eval_str("(function(){}).bind(null).name"), "bound ");
}
#[test]
fn bind_wrapper_length_supports_infinity_and_large_values() {
assert_eq!(
eval("function f(){}; Object.defineProperty(f,'length',{value:Infinity}); f.bind().length === Infinity"),
JsValue::bool(true)
);
assert_eq!(
eval("function f(){}; Object.defineProperty(f,'length',{value:2147483648}); f.bind().length"),
JsValue::float(2147483648.0)
);
}
#[test]
fn bind_wrapper_caller_and_arguments_are_poisoned() {
assert_eq!(
eval_str("var b=(function(){}).bind({}); try { b.caller; 'no' } catch(e) { e instanceof TypeError ? 'throws' : 'wrong' }"),
"throws"
);
assert_eq!(
eval_str("var b=(function(){}).bind({}); try { b.caller = 1; 'no' } catch(e) { e instanceof TypeError ? 'throws' : 'wrong' }"),
"throws"
);
}
#[test]
fn length_and_name_descriptors_are_non_writable_non_enumerable_configurable() {
assert_eq!(
eval_str("function f(){}; var d=Object.getOwnPropertyDescriptor(f,'length'); d.writable===false && d.enumerable===false && d.configurable===true"),
"true"
);
}
#[test]
fn length_comes_before_name_in_property_order() {
assert_eq!(
eval_str("function f(a,b,c){}; var p=Object.getOwnPropertyNames(f); p[p.indexOf('length')] < p[p.indexOf('name')]"),
"true"
);
}
#[test]
fn anonymous_function_name_is_empty_string() {
assert_eq!(eval_str("(function(){}).name"), "");
}