feat: Symbol.hasInstance 支持 instanceof 双路径分派

This commit is contained in:
爱特每羊队
2026-06-27 22:53:04 +08:00
parent db8aa7025e
commit 9da0ded18d
5 changed files with 86 additions and 2 deletions
+8
View File
@@ -221,6 +221,7 @@ pub struct BuiltinWorld {
pub sym_split: P<JsObject>, pub sym_split: P<JsObject>,
pub sym_iterator: P<JsObject>, pub sym_iterator: P<JsObject>,
pub sym_to_primitive: P<JsObject>, pub sym_to_primitive: P<JsObject>,
pub sym_has_instance: P<JsObject>,
pub stub_objects: Vec<P<JsObject>>, pub stub_objects: Vec<P<JsObject>>,
} }
@@ -544,6 +545,7 @@ impl BuiltinWorld {
BuiltinId::SymSplit => &self.sym_split, BuiltinId::SymSplit => &self.sym_split,
BuiltinId::SymIterator => &self.sym_iterator, BuiltinId::SymIterator => &self.sym_iterator,
BuiltinId::SymToPrimitive => &self.sym_to_primitive, BuiltinId::SymToPrimitive => &self.sym_to_primitive,
BuiltinId::SymHasInstance => &self.sym_has_instance,
} }
} }
@@ -580,6 +582,7 @@ impl BuiltinWorld {
let sym_split = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_split = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_iterator = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_iterator = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_to_primitive = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_to_primitive = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_has_instance = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let stub_objects = Vec::new(); let stub_objects = Vec::new();
let world = Self { let world = Self {
@@ -648,6 +651,7 @@ impl BuiltinWorld {
sym_split, sym_split,
sym_iterator, sym_iterator,
sym_to_primitive, sym_to_primitive,
sym_has_instance,
stub_objects, stub_objects,
}; };
wire_builtin_world_links(&world); wire_builtin_world_links(&world);
@@ -717,6 +721,7 @@ impl BuiltinWorld {
sym_split, sym_split,
sym_iterator, sym_iterator,
sym_to_primitive, sym_to_primitive,
sym_has_instance,
) = if dirty.symbol_family { ) = if dirty.symbol_family {
let (symbol_proto, symbol_constructor) = make_named_pair(string_forge, shape_forge, labels, "Symbol"); let (symbol_proto, symbol_constructor) = make_named_pair(string_forge, shape_forge, labels, "Symbol");
( (
@@ -728,6 +733,7 @@ impl BuiltinWorld {
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
) )
} else { } else {
( (
@@ -739,6 +745,7 @@ impl BuiltinWorld {
current.sym_split.clone(), current.sym_split.clone(),
current.sym_iterator.clone(), current.sym_iterator.clone(),
current.sym_to_primitive.clone(), current.sym_to_primitive.clone(),
current.sym_has_instance.clone(),
) )
}; };
@@ -879,6 +886,7 @@ impl BuiltinWorld {
sym_split, sym_split,
sym_iterator, sym_iterator,
sym_to_primitive, sym_to_primitive,
sym_has_instance,
stub_objects, stub_objects,
}; };
wire_builtin_world_links(&world); wire_builtin_world_links(&world);
+5 -2
View File
@@ -201,7 +201,7 @@ pub struct KernelSession {
/// Maintenance: every new `BuiltinWorld` object field must be added here and to /// Maintenance: every new `BuiltinWorld` object field must be added here and to
/// `KernelSession::dirty_since_snapshot()` so selective reset can rebuild the /// `KernelSession::dirty_since_snapshot()` so selective reset can rebuild the
/// correct builtin family. /// correct builtin family.
pub const NUM_BUILTINS: usize = 65; pub const NUM_BUILTINS: usize = 66;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)] #[repr(u8)]
@@ -271,6 +271,7 @@ pub enum BuiltinId {
SymSplit = 62, SymSplit = 62,
SymIterator = 63, SymIterator = 63,
SymToPrimitive = 64, SymToPrimitive = 64,
SymHasInstance = 65,
} }
impl BuiltinId { impl BuiltinId {
@@ -340,6 +341,7 @@ impl BuiltinId {
BuiltinId::SymSplit, BuiltinId::SymSplit,
BuiltinId::SymIterator, BuiltinId::SymIterator,
BuiltinId::SymToPrimitive, BuiltinId::SymToPrimitive,
BuiltinId::SymHasInstance,
]; ];
} }
@@ -509,7 +511,8 @@ impl KernelSession {
|| gen(BuiltinId::SymSearch) != snap(BuiltinId::SymSearch) || gen(BuiltinId::SymSearch) != snap(BuiltinId::SymSearch)
|| gen(BuiltinId::SymSplit) != snap(BuiltinId::SymSplit) || gen(BuiltinId::SymSplit) != snap(BuiltinId::SymSplit)
|| gen(BuiltinId::SymIterator) != snap(BuiltinId::SymIterator) || gen(BuiltinId::SymIterator) != snap(BuiltinId::SymIterator)
|| gen(BuiltinId::SymToPrimitive) != snap(BuiltinId::SymToPrimitive), || gen(BuiltinId::SymToPrimitive) != snap(BuiltinId::SymToPrimitive)
|| gen(BuiltinId::SymHasInstance) != snap(BuiltinId::SymHasInstance),
math: gen(BuiltinId::MathObject) != snap(BuiltinId::MathObject), math: gen(BuiltinId::MathObject) != snap(BuiltinId::MathObject),
json: gen(BuiltinId::JsonObject) != snap(BuiltinId::JsonObject), json: gen(BuiltinId::JsonObject) != snap(BuiltinId::JsonObject),
date: gen(BuiltinId::DateConstructor) != snap(BuiltinId::DateConstructor) date: gen(BuiltinId::DateConstructor) != snap(BuiltinId::DateConstructor)
@@ -57,6 +57,10 @@ pub fn bind_symbol(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
"toPrimitive", "toPrimitive",
JsValue::from_js_object(session.builtin_world().sym_to_primitive.as_ptr() as *mut JsObject), 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); bind_well_known_symbol(core, ctor, name, val);
} }
+19
View File
@@ -208,6 +208,25 @@ impl Vm {
if !rhs_val.is_object() { if !rhs_val.is_object() {
return self.raise_type_error("INSTANCEOF right-hand side is not callable"); 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() { if !lhs_val.is_object() {
self.regs[rd] = JsValue::bool(false); self.regs[rd] = JsValue::bool(false);
return Ok(()); 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(); let result = eval(&mut vm, "try { Symbol.keyFor(42) } catch (e) { e instanceof TypeError }").unwrap();
assert!(result.as_bool()); 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());
}