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

This commit is contained in:
2026-08-09 15:38:45 +08:00
parent 36fb61fc74
commit c8740bfb49
10 changed files with 263 additions and 19 deletions
+82 -11
View File
@@ -1,8 +1,8 @@
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, NativeFnPtr}; use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
use oxide_runtime_api::{to_string_full, NativeResult, VmHost}; use oxide_runtime_api::{to_integer_or_infinity, to_string_full, NativeResult, VmHost};
fn invoke_target<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult { fn invoke_target<H: VmHost>(vm: &mut H, target_val: JsValue, this_val: JsValue, arg_regs: &[u8]) -> NativeResult {
let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect(); let args: Vec<JsValue> = arg_regs.iter().map(|&r| vm.reg(r)).collect();
@@ -57,13 +57,16 @@ fn to_string_error_value<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
fn bind_dispatcher<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { fn bind_dispatcher<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let wrapper_val = vm.reg(254); let wrapper_val = vm.reg(254);
let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() }; let wrapper = unsafe { &*wrapper_val.as_js_object_ptr() };
// dense 布局:[length, name, caller, arguments, target, thisArg, ...boundArgs]——
// 前 4 槽是 shape 属性(length/name 数据 + caller/arguments 访问器占位),
// 绑定状态从槽位 4 起(shape 槽位与 dense 下标对齐)。
let props = wrapper.hash_props_vec().cloned().unwrap_or_default(); let props = wrapper.hash_props_vec().cloned().unwrap_or_default();
let bound_target = props.first().copied().unwrap_or(JsValue::undefined()); let bound_target = props.get(4).copied().unwrap_or(JsValue::undefined());
let bound_this = props.get(1).copied().unwrap_or(JsValue::undefined()); let bound_this = props.get(5).copied().unwrap_or(JsValue::undefined());
// 拼接调用实参:绑定实参(hash_props[2..])在前,本次调用实参(跳过 args[0] // 拼接调用实参:绑定实参(props[6..])在前,本次调用实参(跳过 args[0]
// 即绑定包装器的 receiver)在后,与规范的"绑定实参先于调用实参"一致。 // 即绑定包装器的 receiver)在后,与规范的"绑定实参先于调用实参"一致。
let mut call_args: Vec<JsValue> = props.iter().skip(2).copied().collect(); let mut call_args: Vec<JsValue> = props.iter().skip(6).copied().collect();
for &r in args.iter().skip(1) { for &r in args.iter().skip(1) {
call_args.push(vm.reg(r)); call_args.push(vm.reg(r));
} }
@@ -148,18 +151,78 @@ pub fn function_bind<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
)); ));
} }
let bound_this = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let bound_this = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let bound_arg_count = args.len().saturating_sub(2);
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); // bound 函数 [[Prototype]] 为 Function.prototype(BoundFunctionCreate 语义)。
let fn_proto_val = JsValue::from_js_object(vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject);
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
unsafe { unsafe {
(*wrapper).set_function(true); (*wrapper).set_function(true);
(*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher::<H> as *const ()))); (*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher::<H> as *const ())));
// hash_props 布局:[target, thisArg, ...boundArgs];绑定实参个数记入 // 绑定实参个数记入 native_arg_count,与 Function.length 语义一致。
// native_arg_count,与 Function.length 语义一致。 (*wrapper).set_native_arg_count(bound_arg_count as u8);
(*wrapper).set_native_arg_count(args.len().saturating_sub(2) as u8); }
// length/name 先定义占 dense 槽位 0/1(shape 属性),绑定状态 [target, thisArg,
// ...boundArgs] 随后从槽位 2 起存放,保证 shape 槽位与 dense 下标对齐。
let length_si = vm.kernel_core().perm_interner().intern("length").0;
let name_si = vm.kernel_core().perm_interner().intern("name").0;
let caller_si = vm.kernel_core().perm_interner().intern("caller").0;
let arguments_si = vm.kernel_core().perm_interner().intern("arguments").0;
let attrs = PropAttributes::new(false, false, true);
// bound 函数的 caller/arguments 是受限访问器:读写一律抛 TypeError(poisoned)。
let thrower = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
unsafe {
(*thrower).set_function(true);
(*thrower).set_native_fn(Some(NativeFnPtr::from_raw(bound_restricted_thrower::<H> as *const ())));
}
let thrower_val = JsValue::from_js_object(thrower);
// 全部 shape 属性(length/name/caller/arguments)须先于状态数据定义完成,
// 保证 shape 槽位与 dense 下标对齐,再 push [target, thisArg, ...boundArgs]。
unsafe {
let wrapper_ref = &mut *wrapper;
let target_obj = &*target_val.as_js_object_ptr();
let target_length = vm
.resolve_property(target_obj, length_si)
.map(to_integer_or_infinity)
.unwrap_or(0.0);
// length = max(0, target.length - boundArgs);target.length 为 +∞ 时保持 +∞。
let length = if target_length == f64::INFINITY {
f64::INFINITY
} else {
(target_length - bound_arg_count as f64).max(0.0)
};
// 规范 length 无上界:i32 装得下用 int,否则(如 2^31、MAX_SAFE_INTEGER)用 float。
let length_val = if length <= i32::MAX as f64 {
JsValue::int(length as i32)
} else {
JsValue::float(length)
};
if let Err(e) = vm.define_data_property(wrapper_ref, length_si, length_val, attrs) {
return NativeResult::Err(crate::error::create_type_error(vm, &e));
}
let target_name = vm
.resolve_property(target_obj, name_si)
.and_then(|v| vm.lookup_str(v))
.unwrap_or_default();
let name_val = vm.new_string(&format!("bound {target_name}"));
if let Err(e) = vm.define_data_property(wrapper_ref, name_si, name_val, attrs) {
return NativeResult::Err(crate::error::create_type_error(vm, &e));
}
if let Err(e) = vm.define_accessor_property(wrapper_ref, caller_si, thrower_val, thrower_val, attrs) {
return NativeResult::Err(crate::error::create_type_error(vm, &e));
}
if let Err(e) = vm.define_accessor_property(wrapper_ref, arguments_si, thrower_val, thrower_val, attrs) {
return NativeResult::Err(crate::error::create_type_error(vm, &e));
}
}
unsafe {
let props = (*wrapper).ensure_hash_props(); let props = (*wrapper).ensure_hash_props();
props.push(target_val); props.push(target_val);
props.push(bound_this); props.push(bound_this);
for &r in &args[2..] { for &r in args.iter().skip(2) {
props.push(vm.reg(r)); props.push(vm.reg(r));
} }
} }
@@ -167,6 +230,14 @@ pub fn function_bind<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::from_js_object(wrapper)) NativeResult::Ok(JsValue::from_js_object(wrapper))
} }
/// bound 函数 caller/arguments 的受限访问器:任何访问(get/set)都抛 TypeError。
fn bound_restricted_thrower<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
NativeResult::Err(crate::error::create_type_error(
vm,
"'caller' and 'arguments' are restricted on bound functions",
))
}
/// `Function.prototype.toString`:返回 `function name() { [native code] }` /// `Function.prototype.toString`:返回 `function name() { [native code] }`
/// 或 `[bytecode]` 形式;函数名取自 `name` 属性或字节码子模块名。 /// 或 `[bytecode]` 形式;函数名取自 `name` 属性或字节码子模块名。
pub fn function_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn function_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
+4
View File
@@ -60,6 +60,8 @@ pub struct CompiledModule {
pub captured_this_const_idx: u16, pub captured_this_const_idx: u16,
/// 由赋值上下文推断的函数名,在变量声明 / 对象属性赋值点设置。 /// 由赋值上下文推断的函数名,在变量声明 / 对象属性赋值点设置。
pub function_name: Option<String>, pub function_name: Option<String>,
/// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。
pub function_length: u32,
/// 是否为类构造函数(普通 CALL 必须拒绝它,仅 NEW_EXPRESSION 可经它构造)。 /// 是否为类构造函数(普通 CALL 必须拒绝它,仅 NEW_EXPRESSION 可经它构造)。
pub is_class_constructor: bool, pub is_class_constructor: bool,
/// 类构造函数是否有 `extends` 子句(`this` 在 SUPER_CALL 完成前保持未初始化)。 /// 类构造函数是否有 `extends` 子句(`this` 在 SUPER_CALL 完成前保持未初始化)。
@@ -87,6 +89,7 @@ impl CompiledModule {
is_arrow: false, is_arrow: false,
captured_this_const_idx: 0, captured_this_const_idx: 0,
function_name: None, function_name: None,
function_length: 0,
is_class_constructor: false, is_class_constructor: false,
is_derived_constructor: false, is_derived_constructor: false,
needs_home_object: false, needs_home_object: false,
@@ -116,6 +119,7 @@ impl Clone for CompiledModule {
is_arrow: self.is_arrow, is_arrow: self.is_arrow,
captured_this_const_idx: self.captured_this_const_idx, captured_this_const_idx: self.captured_this_const_idx,
function_name: self.function_name.clone(), function_name: self.function_name.clone(),
function_length: self.function_length,
is_class_constructor: self.is_class_constructor, is_class_constructor: self.is_class_constructor,
is_derived_constructor: self.is_derived_constructor, is_derived_constructor: self.is_derived_constructor,
needs_home_object: self.needs_home_object, needs_home_object: self.needs_home_object,
+7 -1
View File
@@ -112,7 +112,13 @@ impl Emitter {
)?; )?;
ctx.in_instance_method = saved_instance; ctx.in_instance_method = saved_instance;
ctx.in_static_method = saved_static; ctx.in_static_method = saved_static;
method_module.function_name = Some(method_name.to_string()); // 访问器函数名带 "get "/"set " 前缀(SetFunctionName 语义),普通方法裸属性名。
let display_name = match method.kind {
MethodDefinitionKind::Get => format!("get {method_name}"),
MethodDefinitionKind::Set => format!("set {method_name}"),
_ => method_name.to_string(),
};
method_module.function_name = Some(display_name);
method_module.needs_home_object = true; method_module.needs_home_object = true;
ctx.nested.push(method_module); ctx.nested.push(method_module);
let method_reg = ctx.alloc_reg(); let method_reg = ctx.alloc_reg();
+15
View File
@@ -175,6 +175,9 @@ pub struct CompileCtx {
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射, /// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
/// 消除符号表时序依赖与 cell 索引错位。 /// 消除符号表时序依赖与 cell 索引错位。
pub(crate) captured_bindings: BTreeMap<String, u8>, pub(crate) captured_bindings: BTreeMap<String, u8>,
/// 函数 `length` 属性值:首个带默认值形参之前的形参数(rest 不计)。
/// emit_params_prologue 前由编译入口从 param_specs 计算。
pub(crate) function_length: u32,
pub(crate) const_overflow: bool, pub(crate) const_overflow: bool,
/// with 语句作用域栈:元素为 (with 对象寄存器, 打开时的作用域深度)。 /// with 语句作用域栈:元素为 (with 对象寄存器, 打开时的作用域深度)。
/// 非空时 with 体内的自由标识符需动态解析(先查对象属性,回退外层)。 /// 非空时 with 体内的自由标识符需动态解析(先查对象属性,回退外层)。
@@ -266,6 +269,7 @@ impl CompileCtx {
current_upvalue_captures: Vec::new(), current_upvalue_captures: Vec::new(),
own_bindings: HashSet::new(), own_bindings: HashSet::new(),
captured_bindings: BTreeMap::new(), captured_bindings: BTreeMap::new(),
function_length: 0,
const_overflow: false, const_overflow: false,
with_stack: Vec::new(), with_stack: Vec::new(),
open_try_handlers: Vec::new(), open_try_handlers: Vec::new(),
@@ -601,6 +605,7 @@ impl CompileCtx {
needs_home_object: false, needs_home_object: false,
captured_this_const_idx: 0, captured_this_const_idx: 0,
function_name: None, function_name: None,
function_length: self.function_length,
is_top_level: parent_ctx.is_none(), is_top_level: parent_ctx.is_none(),
const_overflow: self.const_overflow, const_overflow: self.const_overflow,
nested: std::mem::take(&mut self.nested), nested: std::mem::take(&mut self.nested),
@@ -748,6 +753,16 @@ impl Emitter {
{ {
let mut ctx = CompileCtx::new(); let mut ctx = CompileCtx::new();
// length = 第一个带默认值形参之前的形参数(解构默认与标识符默认同规则);
// rest 参数不在 param_specs 中(独立字段),天然不计入。
ctx.function_length = param_specs
.iter()
.take_while(|spec| match spec {
ParamSpec::Identifier { initializer, .. } => initializer.is_none(),
ParamSpec::Pattern { initializer, .. } => initializer.is_none(),
})
.count() as u32;
// 继承父内置寄存器映射:子模块寄存器文件中,内置标识符(Math、Object 等) // 继承父内置寄存器映射:子模块寄存器文件中,内置标识符(Math、Object 等)
// 解析到父预先分配的槽位。 // 解析到父预先分配的槽位。
ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone(); ctx.scopes.builtin_reg_map = parent_ctx.scopes.builtin_reg_map.clone();
@@ -45,7 +45,13 @@ impl Emitter {
} }
let accessor_reg = self.emit_expression(&p.value, ctx)?; let accessor_reg = self.emit_expression(&p.value, ctx)?;
if let Some(sub_mod) = ctx.nested.last_mut() { if let Some(sub_mod) = ctx.nested.last_mut() {
sub_mod.function_name = Some(prop_name.to_string()); // 访问器函数名带 "get "/"set " 前缀(SetFunctionName 语义)。
let fn_name = match p.kind {
PropertyKind::Get => format!("get {prop_name}"),
PropertyKind::Set => format!("set {prop_name}"),
_ => prop_name.clone(),
};
sub_mod.function_name = Some(fn_name);
} }
let undef_reg = self.emit_undefined(ctx); let undef_reg = self.emit_undefined(ctx);
let (get_reg, set_reg) = if p.kind == PropertyKind::Get { let (get_reg, set_reg) = if p.kind == PropertyKind::Get {
+3
View File
@@ -52,6 +52,8 @@ pub struct IRFunction {
pub needs_home_object: bool, pub needs_home_object: bool,
pub captured_this_const_idx: u16, pub captured_this_const_idx: u16,
pub function_name: Option<String>, pub function_name: Option<String>,
/// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。
pub function_length: u32,
/// 本函数是否为顶层脚本函数:顶层 STORE_VAR 写全局可观察状态,不可被精确 DCE /// 本函数是否为顶层脚本函数:顶层 STORE_VAR 写全局可观察状态,不可被精确 DCE
/// 删除;函数内局部 STORE_VAR 才可删。emit 在 assemble_ir 处由 /// 删除;函数内局部 STORE_VAR 才可删。emit 在 assemble_ir 处由
/// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。 /// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。
@@ -80,6 +82,7 @@ impl IRFunction {
needs_home_object: false, needs_home_object: false,
captured_this_const_idx: 0, captured_this_const_idx: 0,
function_name: None, function_name: None,
function_length: 0,
is_top_level: false, is_top_level: false,
const_overflow: false, const_overflow: false,
nested: Vec::new(), nested: Vec::new(),
+1
View File
@@ -85,6 +85,7 @@ pub fn lower(f: &IRFunction) -> Result<CompiledModule, String> {
is_arrow: f.is_arrow, is_arrow: f.is_arrow,
captured_this_const_idx: f.captured_this_const_idx, captured_this_const_idx: f.captured_this_const_idx,
function_name: f.function_name.clone(), function_name: f.function_name.clone(),
function_length: f.function_length,
is_class_constructor: f.is_class_constructor, is_class_constructor: f.is_class_constructor,
is_derived_constructor: f.is_derived_constructor, is_derived_constructor: f.is_derived_constructor,
needs_home_object: f.needs_home_object, needs_home_object: f.needs_home_object,
+8 -2
View File
@@ -121,6 +121,7 @@ impl Vm {
let needs_home_object = sub.needs_home_object; let needs_home_object = sub.needs_home_object;
let upvalue_captures = sub.upvalue_captures.clone(); let upvalue_captures = sub.upvalue_captures.clone();
let function_name = sub.function_name.clone(); let function_name = sub.function_name.clone();
let function_length = sub.function_length;
let result = self.create_function_object( let result = self.create_function_object(
sub_idx, sub_idx,
is_arrow, is_arrow,
@@ -130,13 +131,18 @@ impl Vm {
); );
// 函数名推断:emit 端在变量声明/对象属性赋值点设置 function_name。 // 函数名推断:emit 端在变量声明/对象属性赋值点设置 function_name。
let func_obj = unsafe { &mut *result.as_js_object_ptr() }; 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; 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 let name_val = function_name
.as_deref() .as_deref()
.map(|n| self.new_string(n)) .map(|n| self.new_string(n))
.unwrap_or_else(|| self.new_string("")); .unwrap_or_else(|| self.new_string(""));
// name 属性描述符:不可写、不可枚举、可配置(SetFunctionName 语义)。 self.define_data_property(func_obj, name_si, name_val, attrs)?;
self.define_data_property(func_obj, name_si, name_val, PropAttributes::new(false, false, true))?;
if !upvalue_captures.is_empty() { if !upvalue_captures.is_empty() {
// 链式捕获(parent_uv_idx):从父闭包(创建者)的 upvalues 取 cell。 // 链式捕获(parent_uv_idx):从父闭包(创建者)的 upvalues 取 cell。
let parent_upvalues: Vec<*mut Cell> = match self.current_callee() { 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, &mut self, setter: JsValue, receiver: JsValue, val: JsValue, use_frame_push: bool,
) -> Result<(), String> { ) -> Result<(), String> {
vm_trace!("call_or_push_setter: frame_push={}", use_frame_push); 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 { if !use_frame_push {
self.call_function_sync(setter, receiver, &[val])?; return call_native(self);
return Ok(());
} }
if !setter.is_object() { if !setter.is_object() {
return self.raise_type_error("setter is not callable"); return self.raise_type_error("setter is not callable");
@@ -249,8 +256,7 @@ impl Vm {
return self.raise_type_error("setter is not callable"); return self.raise_type_error("setter is not callable");
} }
if setter_obj.native_fn().is_some() { if setter_obj.native_fn().is_some() {
self.call_function_sync(setter, receiver, &[val])?; return call_native(self);
return Ok(());
} }
self.push_bytecode_frame( self.push_bytecode_frame(
setter, 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"), "");
}