feat: 函数 length 属性(字节码函数/bind 包装器)+ getter/setter name 前缀
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
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_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 {
|
||||
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 {
|
||||
let wrapper_val = vm.reg(254);
|
||||
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 bound_target = props.first().copied().unwrap_or(JsValue::undefined());
|
||||
let bound_this = props.get(1).copied().unwrap_or(JsValue::undefined());
|
||||
let bound_target = props.get(4).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)在后,与规范的"绑定实参先于调用实参"一致。
|
||||
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) {
|
||||
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_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 {
|
||||
(*wrapper).set_function(true);
|
||||
(*wrapper).set_native_fn(Some(NativeFnPtr::from_raw(bind_dispatcher::<H> as *const ())));
|
||||
// hash_props 布局:[target, thisArg, ...boundArgs];绑定实参个数记入
|
||||
// native_arg_count,与 Function.length 语义一致。
|
||||
(*wrapper).set_native_arg_count(args.len().saturating_sub(2) as u8);
|
||||
// 绑定实参个数记入 native_arg_count,与 Function.length 语义一致。
|
||||
(*wrapper).set_native_arg_count(bound_arg_count 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();
|
||||
props.push(target_val);
|
||||
props.push(bound_this);
|
||||
for &r in &args[2..] {
|
||||
for &r in args.iter().skip(2) {
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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] }`
|
||||
/// 或 `[bytecode]` 形式;函数名取自 `name` 属性或字节码子模块名。
|
||||
pub fn function_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
@@ -60,6 +60,8 @@ pub struct CompiledModule {
|
||||
pub captured_this_const_idx: u16,
|
||||
/// 由赋值上下文推断的函数名,在变量声明 / 对象属性赋值点设置。
|
||||
pub function_name: Option<String>,
|
||||
/// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。
|
||||
pub function_length: u32,
|
||||
/// 是否为类构造函数(普通 CALL 必须拒绝它,仅 NEW_EXPRESSION 可经它构造)。
|
||||
pub is_class_constructor: bool,
|
||||
/// 类构造函数是否有 `extends` 子句(`this` 在 SUPER_CALL 完成前保持未初始化)。
|
||||
@@ -87,6 +89,7 @@ impl CompiledModule {
|
||||
is_arrow: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
function_length: 0,
|
||||
is_class_constructor: false,
|
||||
is_derived_constructor: false,
|
||||
needs_home_object: false,
|
||||
@@ -116,6 +119,7 @@ impl Clone for CompiledModule {
|
||||
is_arrow: self.is_arrow,
|
||||
captured_this_const_idx: self.captured_this_const_idx,
|
||||
function_name: self.function_name.clone(),
|
||||
function_length: self.function_length,
|
||||
is_class_constructor: self.is_class_constructor,
|
||||
is_derived_constructor: self.is_derived_constructor,
|
||||
needs_home_object: self.needs_home_object,
|
||||
|
||||
@@ -112,7 +112,13 @@ impl Emitter {
|
||||
)?;
|
||||
ctx.in_instance_method = saved_instance;
|
||||
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;
|
||||
ctx.nested.push(method_module);
|
||||
let method_reg = ctx.alloc_reg();
|
||||
|
||||
@@ -175,6 +175,9 @@ pub struct CompileCtx {
|
||||
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
|
||||
/// 消除符号表时序依赖与 cell 索引错位。
|
||||
pub(crate) captured_bindings: BTreeMap<String, u8>,
|
||||
/// 函数 `length` 属性值:首个带默认值形参之前的形参数(rest 不计)。
|
||||
/// emit_params_prologue 前由编译入口从 param_specs 计算。
|
||||
pub(crate) function_length: u32,
|
||||
pub(crate) const_overflow: bool,
|
||||
/// with 语句作用域栈:元素为 (with 对象寄存器, 打开时的作用域深度)。
|
||||
/// 非空时 with 体内的自由标识符需动态解析(先查对象属性,回退外层)。
|
||||
@@ -266,6 +269,7 @@ impl CompileCtx {
|
||||
current_upvalue_captures: Vec::new(),
|
||||
own_bindings: HashSet::new(),
|
||||
captured_bindings: BTreeMap::new(),
|
||||
function_length: 0,
|
||||
const_overflow: false,
|
||||
with_stack: Vec::new(),
|
||||
open_try_handlers: Vec::new(),
|
||||
@@ -601,6 +605,7 @@ impl CompileCtx {
|
||||
needs_home_object: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
function_length: self.function_length,
|
||||
is_top_level: parent_ctx.is_none(),
|
||||
const_overflow: self.const_overflow,
|
||||
nested: std::mem::take(&mut self.nested),
|
||||
@@ -748,6 +753,16 @@ impl Emitter {
|
||||
{
|
||||
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 等)
|
||||
// 解析到父预先分配的槽位。
|
||||
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)?;
|
||||
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 (get_reg, set_reg) = if p.kind == PropertyKind::Get {
|
||||
|
||||
@@ -52,6 +52,8 @@ pub struct IRFunction {
|
||||
pub needs_home_object: bool,
|
||||
pub captured_this_const_idx: u16,
|
||||
pub function_name: Option<String>,
|
||||
/// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。
|
||||
pub function_length: u32,
|
||||
/// 本函数是否为顶层脚本函数:顶层 STORE_VAR 写全局可观察状态,不可被精确 DCE
|
||||
/// 删除;函数内局部 STORE_VAR 才可删。emit 在 assemble_ir 处由
|
||||
/// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。
|
||||
@@ -80,6 +82,7 @@ impl IRFunction {
|
||||
needs_home_object: false,
|
||||
captured_this_const_idx: 0,
|
||||
function_name: None,
|
||||
function_length: 0,
|
||||
is_top_level: false,
|
||||
const_overflow: false,
|
||||
nested: Vec::new(),
|
||||
|
||||
@@ -85,6 +85,7 @@ pub fn lower(f: &IRFunction) -> Result<CompiledModule, String> {
|
||||
is_arrow: f.is_arrow,
|
||||
captured_this_const_idx: f.captured_this_const_idx,
|
||||
function_name: f.function_name.clone(),
|
||||
function_length: f.function_length,
|
||||
is_class_constructor: f.is_class_constructor,
|
||||
is_derived_constructor: f.is_derived_constructor,
|
||||
needs_home_object: f.needs_home_object,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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"), "");
|
||||
}
|
||||
Reference in New Issue
Block a user