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
@@ -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"), "");
}