mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Date 增加 toTemporalInstant 与 toPrimitive 方法
This commit is contained in:
Generated
+21
-21
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1228,7 +1228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1237,7 +1237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1258,7 +1258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1272,7 +1272,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1288,7 +1288,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1300,7 +1300,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_fold"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_bytecode",
|
||||
@@ -1312,7 +1312,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1335,7 +1335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1347,7 +1347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1356,11 +1356,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_net"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1375,7 +1375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1391,7 +1391,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1404,7 +1404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1424,7 +1424,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1437,7 +1437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.19.15"
|
||||
version = "0.19.16"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -1181,3 +1181,109 @@ pub fn date_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ms = get_timestamp(obj);
|
||||
NativeResult::Ok(JsValue::float(ms))
|
||||
}
|
||||
|
||||
/// `Date.prototype.toTemporalInstant()`:把时间戳换算为 epoch 纳秒并构造 Instant。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. `date_this` 品牌守卫(非 Date 对象抛 TypeError)。
|
||||
/// 2. 取 `[[DateValue]]`,非有限(Invalid Date)抛 RangeError。
|
||||
/// 3. 毫秒 × 10^6 得纳秒,交 `make_instant` 构造 Instant 对象。
|
||||
///
|
||||
/// # 边界
|
||||
/// 有效 Date 时间戳恒为整数毫秒且在 TimeClip 包络内(|t| ≤ 8.64e15),
|
||||
/// 乘积恒在 i128 内,无溢出面。
|
||||
pub fn date_to_temporal_instant<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let obj = unsafe { &*native_try!(date_this(vm, args)) };
|
||||
let ms = get_timestamp(obj);
|
||||
if !ms.is_finite() {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid date"));
|
||||
}
|
||||
let ns = (ms as i128) * 1_000_000;
|
||||
crate::temporal::make_instant(vm, ns)
|
||||
}
|
||||
|
||||
/// OrdinaryToPrimitive:按 [first, second] 序逐个查 `toString`/`valueOf`,
|
||||
/// 首个产出非对象结果的方法返回其结果;两臂均不产出原始值抛 TypeError。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 按 hint 定方法序(string 族 toString 优先,number 族 valueOf 优先)。
|
||||
/// 2. 逐臂 `Get(O, name)`(receiver = O),null/undefined/非可调用跳过。
|
||||
/// 3. 调用方法(this = O),结果非对象即返回。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 用户 getter 与调用抛错经 `take_uncaught_value` 恢复原始异常值,不降级。
|
||||
fn ordinary_to_primitive<H: VmHost>(vm: &mut H, obj: &JsObject, this_val: JsValue, try_first: &str) -> NativeResult {
|
||||
let (first, second) = if try_first == "string" {
|
||||
("toString", "valueOf")
|
||||
} else {
|
||||
("valueOf", "toString")
|
||||
};
|
||||
for name in [first, second] {
|
||||
let si = vm.perm_intern(name);
|
||||
let method = match vm.ordinary_get(obj, si, this_val) {
|
||||
Ok(m) => m,
|
||||
Err(msg) => {
|
||||
return NativeResult::Err(
|
||||
vm.take_uncaught_value()
|
||||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)),
|
||||
);
|
||||
}
|
||||
};
|
||||
if method.is_undefined() || method.is_null() {
|
||||
continue;
|
||||
}
|
||||
if !crate::iterator::is_callable(method) {
|
||||
continue;
|
||||
}
|
||||
let result = match vm.call_function_sync(method, this_val, &[]) {
|
||||
Ok(r) => r,
|
||||
Err(msg) => {
|
||||
return NativeResult::Err(
|
||||
vm.take_uncaught_value()
|
||||
.unwrap_or_else(|| crate::error::create_type_error(vm, &msg)),
|
||||
);
|
||||
}
|
||||
};
|
||||
if !result.is_object() {
|
||||
return NativeResult::Ok(result);
|
||||
}
|
||||
}
|
||||
NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert object to primitive value"))
|
||||
}
|
||||
|
||||
/// `Date.prototype[Symbol.toPrimitive](hint)`:OrdinaryToPrimitive 原始值转换。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. this 非对象抛 TypeError(不做 Date 品牌检查,普通对象合法)。
|
||||
/// 2. hint 须为原始字符串值,严格比较("string"/"default"/"number"),
|
||||
/// 其余(含 String 对象、带 toString 的对象)抛 TypeError,不做强转。
|
||||
/// 3. 按 hint 定 tryFirst 走 OrdinaryToPrimitive。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 不复用 runtime `to_primitive`:其先查 @@toPrimitive 再走 OrdinaryToPrimitive,
|
||||
/// 语义漂移且对 Date 自身会递归。
|
||||
pub fn date_to_primitive<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
let obj_ptr = this_val.as_js_object_ptr();
|
||||
if obj_ptr.is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
let obj = unsafe { &*obj_ptr };
|
||||
|
||||
let hint_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
if !hint_val.is_string() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "invalid hint"));
|
||||
}
|
||||
// SAFETY: hint_val 已确认是字符串值。
|
||||
let hint = unsafe { (*hint_val.as_string_ptr()).to_owned_string() };
|
||||
let try_first = match hint.as_str() {
|
||||
"string" | "default" => "string",
|
||||
"number" => "number",
|
||||
_ => return NativeResult::Err(crate::error::create_type_error(vm, "invalid hint")),
|
||||
};
|
||||
|
||||
ordinary_to_primitive(vm, obj, this_val, try_first)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ mod time_zone;
|
||||
mod zoned_date_time;
|
||||
|
||||
use common::*;
|
||||
pub(crate) use common::make_instant;
|
||||
use difference::*;
|
||||
pub use duration::*;
|
||||
pub use instant::*;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use crate::bindings::{apply_binding_table, bind_global_value, configure_native_constructor, BindEnv};
|
||||
use crate::bindings::{
|
||||
apply_binding_table, bind_global_value, bind_well_known_method, configure_native_constructor, BindEnv,
|
||||
};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::private_key::{encode_symbol_key, WELL_KNOWN_SYMBOL_TO_PRIMITIVE};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// 把 Date 构造器与原型方法绑定到 global。
|
||||
@@ -134,9 +137,25 @@ pub fn bind_date(env: BindEnv<'_>, global: &mut JsObject) {
|
||||
0,
|
||||
),
|
||||
("valueOf", oxide_builtins::date::date_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
("toTemporalInstant", oxide_builtins::date::date_to_temporal_instant::<crate::vm::Vm> as *const (), 0),
|
||||
("toSource", oxide_builtins::to_source::to_source_date::<crate::vm::Vm> as *const (), 0),
|
||||
],
|
||||
);
|
||||
|
||||
// [Symbol.toPrimitive] 方法:bind 默认描述符可写,规范为不可写,绑定后改 meta。
|
||||
bind_well_known_method(
|
||||
env,
|
||||
proto,
|
||||
WELL_KNOWN_SYMBOL_TO_PRIMITIVE,
|
||||
"[Symbol.toPrimitive]",
|
||||
oxide_builtins::date::date_to_primitive::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
);
|
||||
let to_prim_key = encode_symbol_key(env.realm_id, WELL_KNOWN_SYMBOL_TO_PRIMITIVE);
|
||||
if let Some(pos) = env.shape_forge().lookup_position(proto.shape_id(), to_prim_key) {
|
||||
proto.set_data_meta(pos, PropAttributes::new(false, false, true));
|
||||
proto.bump_generation();
|
||||
}
|
||||
|
||||
bind_global_value(env.core, global, "Date", JsValue::from_js_object(ctor_ptr));
|
||||
}
|
||||
|
||||
@@ -730,3 +730,287 @@ fn date_constructor_object_arg_error_propagates() {
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "yes");
|
||||
}
|
||||
|
||||
// ── toTemporalInstant ──
|
||||
|
||||
#[test]
|
||||
fn date_to_temporal_instant_epoch_values() {
|
||||
let mut vm = Vm::new();
|
||||
// 有效日期换算为 epoch 纳秒(毫秒 × 10^6),逐值与 BigInt 字面量比较。
|
||||
let cases = [
|
||||
("new Date(0)", "0n"),
|
||||
("new Date(123456789)", "123456789000000n"),
|
||||
("new Date(-123456789)", "-123456789000000n"),
|
||||
// TimeClip 包络两端(±8.64e15 ms)。
|
||||
("new Date(8.64e15)", "8640000000000000000000n"),
|
||||
("new Date(-8.64e15)", "-8640000000000000000000n"),
|
||||
];
|
||||
for (date, ns) in cases {
|
||||
let r = eval(&mut vm, &format!("{date}.toTemporalInstant().epochNanoseconds === {ns} ? 1 : 0"))
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1, "date = {date}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_temporal_instant_invalid_date_throws_range_error() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var d = new Date(NaN); \
|
||||
try { d.toTemporalInstant(); 'no' } catch (e) { e instanceof RangeError ? 'yes' : 'no' }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "yes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_temporal_instant_non_date_throws_type_error() {
|
||||
let mut vm = Vm::new();
|
||||
let f = |vm: &mut Vm, this: &str| {
|
||||
eval(
|
||||
vm,
|
||||
&format!(
|
||||
"var m = Date.prototype.toTemporalInstant; \
|
||||
try {{ m.call({this}); 'no' }} catch (e) {{ e instanceof TypeError ? 'yes' : 'no' }}"
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
for this in ["{}", "[]", "function(){}", "Date.prototype", "0", "null", "undefined", "Symbol()"] {
|
||||
let r = f(&mut vm, this);
|
||||
assert_eq!(str_val(&vm, r), "yes", "this = {this}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_temporal_instant_prop_desc() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var d = Object.getOwnPropertyDescriptor(Date.prototype, 'toTemporalInstant'); \
|
||||
d.writable === true && d.enumerable === false && d.configurable === true ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_temporal_instant_length_name() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"Date.prototype.toTemporalInstant.length === 0 && \
|
||||
Date.prototype.toTemporalInstant.name === 'toTemporalInstant' ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
// ── [Symbol.toPrimitive] ──
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_number_hint_first_valid() {
|
||||
let mut vm = Vm::new();
|
||||
// number 提示 valueOf 优先:valueOf 产出原始值即返回,toString 不被引用。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var vo = 0, ts = 0; \
|
||||
var obj = { valueOf: function() { vo++; return 'valueOf test'; }, \
|
||||
get toString() { ts++; } }; \
|
||||
var v = Date.prototype[Symbol.toPrimitive].call(obj, 'number'); \
|
||||
v === 'valueOf test' && vo === 1 && ts === 0 ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_string_hint_first_valid() {
|
||||
let mut vm = Vm::new();
|
||||
// string 提示 toString 优先:toString 产出原始值即返回,valueOf 不被引用。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var vo = 0, ts = 0; \
|
||||
var obj = { get valueOf() { vo++; }, \
|
||||
toString: function() { ts++; return 'toString test'; } }; \
|
||||
var v = Date.prototype[Symbol.toPrimitive].call(obj, 'string'); \
|
||||
v === 'toString test' && vo === 0 && ts === 1 ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_default_hint_uses_string_order() {
|
||||
let mut vm = Vm::new();
|
||||
// default 提示与 string 同序(toString 优先)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var obj = { valueOf: function() { return 1; }, \
|
||||
toString: function() { return 'def'; } }; \
|
||||
Date.prototype[Symbol.toPrimitive].call(obj, 'default') === 'def' ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_number_hint_first_non_callable() {
|
||||
let mut vm = Vm::new();
|
||||
// valueOf 非可调用时回落 toString。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var obj = { valueOf: null, toString: function() { return 'ts'; } }; \
|
||||
Date.prototype[Symbol.toPrimitive].call(obj, 'number') === 'ts' ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_number_hint_first_invalid() {
|
||||
let mut vm = Vm::new();
|
||||
// valueOf 返回对象(非原始值)时回落 toString。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var obj = { valueOf: function() { return {}; }, \
|
||||
toString: function() { return 'ts'; } }; \
|
||||
Date.prototype[Symbol.toPrimitive].call(obj, 'number') === 'ts' ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_no_callables_throws_type_error() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var obj = { valueOf: null, toString: null }; \
|
||||
try { Date.prototype[Symbol.toPrimitive].call(obj, 'number'); 'no' } \
|
||||
catch (e) { e instanceof TypeError ? 'yes' : 'no' }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "yes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_invalid_hint_throws_type_error() {
|
||||
let mut vm = Vm::new();
|
||||
let f = |vm: &mut Vm, hint: &str| {
|
||||
eval(
|
||||
vm,
|
||||
&format!(
|
||||
"var d = new Date(0); \
|
||||
try {{ d[Symbol.toPrimitive]({hint}); 'no' }} \
|
||||
catch (e) {{ e instanceof TypeError ? 'yes' : 'no' }}"
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
// 缺参、undefined、null、空串、非原始串(String 对象 / 带 toString 的对象)均抛 TypeError。
|
||||
for hint in [
|
||||
"",
|
||||
"undefined",
|
||||
"null",
|
||||
"''",
|
||||
"'String'",
|
||||
"new String('number')",
|
||||
"{ toString: function() { 'number'; } }",
|
||||
] {
|
||||
let r = f(&mut vm, hint);
|
||||
assert_eq!(str_val(&vm, r), "yes", "hint = {hint}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_non_object_this_throws_type_error() {
|
||||
let mut vm = Vm::new();
|
||||
let f = |vm: &mut Vm, this: &str| {
|
||||
eval(
|
||||
vm,
|
||||
&format!(
|
||||
"try {{ Date.prototype[Symbol.toPrimitive].call({this}, 'string'); 'no' }} \
|
||||
catch (e) {{ e instanceof TypeError ? 'yes' : 'no' }}"
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
for this in ["undefined", "null", "86", "''", "true"] {
|
||||
let r = f(&mut vm, this);
|
||||
assert_eq!(str_val(&vm, r), "yes", "this = {this}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_works_on_plain_object() {
|
||||
let mut vm = Vm::new();
|
||||
// 非 Date 品牌方法:普通对象合法(仅要求 this 为对象)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var obj = { valueOf: function() { return 42; } }; \
|
||||
Date.prototype[Symbol.toPrimitive].call(obj, 'number') === 42 ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_prop_desc_length_name() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var d = Object.getOwnPropertyDescriptor(Date.prototype, Symbol.toPrimitive); \
|
||||
d.writable === false && d.enumerable === false && d.configurable === true ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"Date.prototype[Symbol.toPrimitive].length === 1 && \
|
||||
Date.prototype[Symbol.toPrimitive].name === '[Symbol.toPrimitive]' ? 1 : 0",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_int(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_user_throw_propagates_original() {
|
||||
let mut vm = Vm::new();
|
||||
// 用户方法抛错经原始异常值传播(不降级为 TypeError)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var obj = { valueOf: function() { throw 'boom'; } }; \
|
||||
try { Date.prototype[Symbol.toPrimitive].call(obj, 'number'); 'no' } \
|
||||
catch (e) { e === 'boom' ? 'yes' : 'no' }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "yes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_to_primitive_called_as_function_no_strict_this() {
|
||||
let mut vm = Vm::new();
|
||||
// 顶层函数形态调用(this 为 undefined)抛 TypeError,且不触发 toString/valueOf 查找。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"var toPrimitive = Date.prototype[Symbol.toPrimitive]; \
|
||||
try { toPrimitive('default'); 'no' } catch (e) { e instanceof TypeError ? 'yes' : 'no' }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "yes");
|
||||
}
|
||||
|
||||
// 既有 Date 强转路径行为回归:绑定 [Symbol.toPrimitive] 后,VM 的 ToPrimitive
|
||||
// 对 Date 先经原型方法再走 OrdinaryToPrimitive,可观察行为与直接 valueOf/toString 等价。
|
||||
#[test]
|
||||
fn date_to_primitive_existing_coercion_regression() {
|
||||
let mut vm = Vm::new();
|
||||
// +new Date(0) 经 valueOf 通道。
|
||||
let r = eval(&mut vm, "+new Date(0)").unwrap();
|
||||
assert_eq!(r.as_double(), 0.0);
|
||||
// "" + new Date(0) 经 toString 通道(得到字符串)。
|
||||
let r = eval(&mut vm, "typeof ('' + new Date(0))").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "string");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user