feat: Symbol.hasInstance 支持 instanceof 双路径分派
This commit is contained in:
@@ -57,6 +57,10 @@ pub fn bind_symbol(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
"toPrimitive",
|
||||
JsValue::from_js_object(session.builtin_world().sym_to_primitive.as_ptr() as *mut JsObject),
|
||||
),
|
||||
(
|
||||
"hasInstance",
|
||||
JsValue::from_js_object(session.builtin_world().sym_has_instance.as_ptr() as *mut JsObject),
|
||||
),
|
||||
] {
|
||||
bind_well_known_symbol(core, ctor, name, val);
|
||||
}
|
||||
|
||||
@@ -208,6 +208,25 @@ impl Vm {
|
||||
if !rhs_val.is_object() {
|
||||
return self.raise_type_error("INSTANCEOF right-hand side is not callable");
|
||||
}
|
||||
|
||||
let has_instance_ptr = self.session.builtin_world().sym_has_instance.as_ptr() as *mut JsObject;
|
||||
let has_instance_key = JsValue::from_js_object(has_instance_ptr);
|
||||
let has_instance_si = self.property_key_si(has_instance_key);
|
||||
|
||||
let ctor_obj = unsafe { &*rhs_val.as_js_object_ptr() };
|
||||
let has_instance_val = self.ordinary_get(ctor_obj, has_instance_si, rhs_val)?;
|
||||
if !has_instance_val.is_undefined() && !has_instance_val.is_null() {
|
||||
let fn_ptr = has_instance_val.as_js_object_ptr();
|
||||
if !fn_ptr.is_null() {
|
||||
let fn_obj = unsafe { &*fn_ptr };
|
||||
if fn_obj.is_function() {
|
||||
let result = self.call_function_sync(has_instance_val, rhs_val, &[lhs_val])?;
|
||||
self.regs[rd] = JsValue::bool(to_boolean(result));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !lhs_val.is_object() {
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
return Ok(());
|
||||
|
||||
@@ -155,3 +155,53 @@ fn symbol_key_for_non_symbol_throws_type_error() {
|
||||
let result = eval(&mut vm, "try { Symbol.keyFor(42) } catch (e) { e instanceof TypeError }").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
// --- Symbol.hasInstance ---
|
||||
|
||||
#[test]
|
||||
fn symbol_has_instance_exists() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "typeof Symbol.hasInstance").unwrap();
|
||||
assert_eq!(to_str(&vm, result), "object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_instance_overrides_instanceof() {
|
||||
let mut vm = Vm::new();
|
||||
let source = r#"
|
||||
class C { static [Symbol.hasInstance](v) { return typeof v === 'string'; } }
|
||||
'hello' instanceof C
|
||||
"#;
|
||||
let result = eval(&mut vm, source).unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_instance_non_callable_falls_through() {
|
||||
let mut vm = Vm::new();
|
||||
let source = r#"
|
||||
class D {}
|
||||
D[Symbol.hasInstance] = 42;
|
||||
({} instanceof D) === false
|
||||
"#;
|
||||
let result = eval(&mut vm, source).unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_instance_coerces_return_to_boolean() {
|
||||
let mut vm = Vm::new();
|
||||
let source = r#"
|
||||
class C { static [Symbol.hasInstance](v) { return 'truthy'; } }
|
||||
'anything' instanceof C
|
||||
"#;
|
||||
let result = eval(&mut vm, source).unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_instance_preserves_ordinary_has_instance() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "[] instanceof Array").unwrap();
|
||||
assert!(result.as_bool());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user