mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 03:31:12 +08:00
feat: ToPrimitive 链支持 Symbol.toPrimitive
This commit is contained in:
@@ -448,3 +448,50 @@ pub fn to_object<H: VmHost>(val: JsValue, host: &mut H) -> Result<JsValue, Strin
|
|||||||
obj_ref.set_prop_count(1);
|
obj_ref.set_prop_count(1);
|
||||||
Ok(obj_val)
|
Ok(obj_val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Hint for the abstract ToPrimitive operation (ECMA-262 §7.1.1).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ToPrimitiveHint {
|
||||||
|
Default,
|
||||||
|
String,
|
||||||
|
Number,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToPrimitiveHint {
|
||||||
|
/// OrdinaryToPrimitive method-name ordering hint: `Default`/`Number` -> "number", `String` -> "string".
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ToPrimitiveHint::String => "string",
|
||||||
|
ToPrimitiveHint::Default | ToPrimitiveHint::Number => "number",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ToPrimitive(input, hint) per ECMA-262 §7.1.1.
|
||||||
|
///
|
||||||
|
/// Primitives pass through unchanged. Objects are coerced via the VM's
|
||||||
|
/// `coerce_primitive_bounded`, which consults `obj[Symbol.toPrimitive]` first and
|
||||||
|
/// otherwise runs OrdinaryToPrimitive (valueOf/toString in hint order). Keeping the
|
||||||
|
/// object path in one place avoids duplicating OrdinaryToPrimitive here.
|
||||||
|
pub fn to_primitive<H: VmHost>(val: JsValue, hint: ToPrimitiveHint, host: &mut H) -> Result<JsValue, String> {
|
||||||
|
if !val.is_object() {
|
||||||
|
return Ok(val);
|
||||||
|
}
|
||||||
|
host.coerce_primitive_bounded(val, hint == ToPrimitiveHint::String)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ToNumber(input) with full object coercion: objects go through ToPrimitive
|
||||||
|
/// (number hint); Symbol values throw a TypeError per spec.
|
||||||
|
pub fn to_number_full<H: VmHost>(val: JsValue, host: &mut H) -> Result<f64, String> {
|
||||||
|
let primitive = to_primitive(val, ToPrimitiveHint::Number, host)?;
|
||||||
|
if primitive.is_symbol() {
|
||||||
|
return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a number"));
|
||||||
|
}
|
||||||
|
Ok(to_number(primitive))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ToString(input) with full object coercion: objects go through ToPrimitive (string hint).
|
||||||
|
pub fn to_string_full<H: VmHost>(val: JsValue, host: &mut H) -> Result<String, String> {
|
||||||
|
let primitive = to_primitive(val, ToPrimitiveHint::String, host)?;
|
||||||
|
Ok(to_string(primitive))
|
||||||
|
}
|
||||||
|
|||||||
@@ -195,6 +195,33 @@ impl Vm {
|
|||||||
return Ok(value);
|
return Ok(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ECMA-262 §7.1.1 step 1: an exotic obj[Symbol.toPrimitive] takes precedence
|
||||||
|
// over OrdinaryToPrimitive. ponytail: symbol-object keys all collapse to
|
||||||
|
// to_string()="[object]" inside property_key_si (an engine-wide symbol-key
|
||||||
|
// limitation), so this resolves obj[@@toPrimitive] the same way the setter
|
||||||
|
// stores it; the real fix belongs to a dedicated symbol-key phase.
|
||||||
|
let sym_key = {
|
||||||
|
let sym_ptr = self.session.builtin_world().sym_to_primitive.as_ptr() as *mut JsObject;
|
||||||
|
JsValue::from_js_object(sym_ptr)
|
||||||
|
};
|
||||||
|
let sym_si = self.property_key_si(sym_key);
|
||||||
|
let exotic = {
|
||||||
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
self.ordinary_get(obj, sym_si, value)?
|
||||||
|
};
|
||||||
|
if !exotic.is_undefined() && !exotic.is_null() {
|
||||||
|
let exotic_ptr = exotic.as_js_object_ptr();
|
||||||
|
if !exotic.is_object() || exotic_ptr.is_null() || !unsafe { &*exotic_ptr }.is_function() {
|
||||||
|
return Err(self.error_message_text("TypeError", "Symbol.toPrimitive is not a function"));
|
||||||
|
}
|
||||||
|
let hint_val = self.new_string(if prefer_string { "string" } else { "number" });
|
||||||
|
let result = self.call_function_sync(exotic, value, &[hint_val])?;
|
||||||
|
if result.is_object() {
|
||||||
|
return Err(self.error_message_text("TypeError", "Cannot convert object to primitive value"));
|
||||||
|
}
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] };
|
let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] };
|
||||||
|
|
||||||
for method_name in method_names {
|
for method_name in method_names {
|
||||||
|
|||||||
Reference in New Issue
Block a user