feat: Date 严格 ISO 解析与 6 位年字符串输出

This commit is contained in:
2026-10-15 15:51:40 +00:00
parent f0b0dda887
commit d4d5b578de
14 changed files with 1012 additions and 266 deletions
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_engine" name = "oxide_engine"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_fold" name = "oxide_fold"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_bytecode", "oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]] [[package]]
name = "oxide_net" name = "oxide_net"
version = "0.19.16" version = "0.19.17"
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.19.16" version = "0.19.17"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.19.16" version = "0.19.17"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost}; use oxide_runtime_api::{NativeResult, ProtoKind, ToPrimitiveHint, VmHost};
/// 整值且 i32 域内的有限数转 int 表示;-0 排除 /// 整值且 i32 域内的有限数转 int 表示;-0 排除
/// (int 表示丢符号,规范要求 `Number(-0)` 得 -0)。 /// (int 表示丢符号,规范要求 `Number(-0)` 得 -0)。
@@ -23,7 +23,7 @@ pub fn number_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// ToPrimitive 先解盒:BigInt 原始值走 lossy 转换(BigIntToNumber 不抛错, // ToPrimitive 先解盒:BigInt 原始值走 lossy 转换(BigIntToNumber 不抛错,
// 显式 `Number(bigint)` 是规范唯一合法入口);其余经 bounded ToNumber // 显式 `Number(bigint)` 是规范唯一合法入口);其余经 bounded ToNumber
// 入口(Symbol 抛 TypeError)。 // 入口(Symbol 抛 TypeError)。
let prim = match vm.coerce_primitive_bounded(raw, false) { let prim = match vm.coerce_primitive_bounded(raw, ToPrimitiveHint::Number) {
Ok(p) => p, Ok(p) => p,
Err(_) => { Err(_) => {
// 对象经 ToPrimitive 转换时 toString/valueOf 可抛异常,须原样传播原始异常。 // 对象经 ToPrimitive 转换时 toString/valueOf 可抛异常,须原样传播原始异常。
+14 -5
View File
@@ -543,7 +543,7 @@ pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Resul
} else { } else {
rhs rhs
}; };
let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?; let prim = to_primitive(lhs, ToPrimitiveHint::Number, host)?;
abstract_eq(prim, rhs, host) abstract_eq(prim, rhs, host)
} }
( (
@@ -556,7 +556,7 @@ pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Resul
} else { } else {
lhs lhs
}; };
let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?; let prim = to_primitive(rhs, ToPrimitiveHint::Number, host)?;
abstract_eq(lhs, prim, host) abstract_eq(lhs, prim, host)
} }
// 其余组合不相等(含 null/undefined 与其它类型;Object/Object 已由同类型分支处理)。 // 其余组合不相等(含 null/undefined 与其它类型;Object/Object 已由同类型分支处理)。
@@ -872,13 +872,22 @@ pub enum ToPrimitiveHint {
} }
impl ToPrimitiveHint { impl ToPrimitiveHint {
/// OrdinaryToPrimitive 方法名顺序提示:`Default`/`Number` → "number",`String` → "string"。 /// 传给 `@@toPrimitive` 的 hint 字符串:`String` → "string",`Default` → "default",
/// `Number` → "number"。
pub fn as_str(self) -> &'static str { pub fn as_str(self) -> &'static str {
match self { match self {
ToPrimitiveHint::String => "string", ToPrimitiveHint::String => "string",
ToPrimitiveHint::Default | ToPrimitiveHint::Number => "number", ToPrimitiveHint::Default => "default",
ToPrimitiveHint::Number => "number",
} }
} }
/// OrdinaryToPrimitive 回退方法序:`String` 先试 `toString`,
/// `Default`/`Number` 先试 `valueOf`。
#[inline]
pub fn prefer_string(self) -> bool {
self == ToPrimitiveHint::String
}
} }
/// ToPrimitive(input, hint) per ECMA-262 §7.1.1. /// ToPrimitive(input, hint) per ECMA-262 §7.1.1.
@@ -890,7 +899,7 @@ pub fn to_primitive<H: VmHost>(val: JsValue, hint: ToPrimitiveHint, host: &mut H
if !val.is_object() { if !val.is_object() {
return Ok(val); return Ok(val);
} }
host.coerce_primitive_bounded(val, hint == ToPrimitiveHint::String) host.coerce_primitive_bounded(val, hint)
} }
/// 带完整对象强制转换的 ToNumber(input):对象经 ToPrimitive(number hint) /// 带完整对象强制转换的 ToNumber(input):对象经 ToPrimitive(number hint)
+3 -1
View File
@@ -311,7 +311,9 @@ pub trait VmHost {
// 查找 / 强制转换 // 查找 / 强制转换
fn lookup_str(&self, val: JsValue) -> Option<String>; fn lookup_str(&self, val: JsValue) -> Option<String>;
fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result<JsValue, String>; fn coerce_primitive_bounded(
&mut self, value: JsValue, hint: crate::ToPrimitiveHint,
) -> Result<JsValue, String>;
fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String>; fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String>;
// 调用基础设施 // 调用基础设施
+18 -18
View File
@@ -108,8 +108,8 @@ impl Vm {
// 注意:不能在此前置检查 BigInt 混合(`lv.is_bigint() || rv.is_bigint()`)—— // 注意:不能在此前置检查 BigInt 混合(`lv.is_bigint() || rv.is_bigint()`)——
// 对象操作数(如 `{valueOf: () => 2n}`)需先 ToPrimitive 再判定,混合检查 // 对象操作数(如 `{valueOf: () => 2n}`)需先 ToPrimitive 再判定,混合检查
// 必须放在 coerce 之后。 // 必须放在 coerce 之后。
let lhs = self.coerce_primitive_bounded(lv, false)?; let lhs = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let rhs = self.coerce_primitive_bounded(rv, false)?; let rhs = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
reject_symbol_add!(self, lhs, rhs); reject_symbol_add!(self, lhs, rhs);
if lhs.is_string() || rhs.is_string() { if lhs.is_string() || rhs.is_string() {
self.regs[rd] = self.concat_strings(lhs, rhs); self.regs[rd] = self.concat_strings(lhs, rhs);
@@ -238,8 +238,8 @@ impl Vm {
} }
// 快路径失败才 coerce:acc 已原语零副作用;next 对象 ToPrimitive 在此触发, // 快路径失败才 coerce:acc 已原语零副作用;next 对象 ToPrimitive 在此触发,
// 顺序与左结合 ADD 一致。 // 顺序与左结合 ADD 一致。
let lhs = self.coerce_primitive_bounded(acc, false)?; let lhs = self.coerce_primitive_bounded(acc, coercion::ToPrimitiveHint::Number)?;
let rhs = self.coerce_primitive_bounded(next, false)?; let rhs = self.coerce_primitive_bounded(next, coercion::ToPrimitiveHint::Number)?;
reject_symbol_add!(self, lhs, rhs); reject_symbol_add!(self, lhs, rhs);
if lhs.is_string() || rhs.is_string() { if lhs.is_string() || rhs.is_string() {
// ── 阶段 2:字符串模式单趟预分配 ── // ── 阶段 2:字符串模式单趟预分配 ──
@@ -280,7 +280,7 @@ impl Vm {
parts.push(rhs); parts.push(rhs);
for &reg in rest { for &reg in rest {
let v = self.regs[reg]; let v = self.regs[reg];
let prim = if v.is_object() { self.coerce_primitive_bounded(v, false)? } else { v }; let prim = if v.is_object() { self.coerce_primitive_bounded(v, coercion::ToPrimitiveHint::Number)? } else { v };
// 字符串模式对全部操作数做 ToString,Symbol 按 §7.1.17 抛 TypeError。 // 字符串模式对全部操作数做 ToString,Symbol 按 §7.1.17 抛 TypeError。
if prim.is_symbol() { if prim.is_symbol() {
self.raise_type_error("Cannot convert a Symbol value to a string")?; self.raise_type_error("Cannot convert a Symbol value to a string")?;
@@ -322,7 +322,7 @@ impl Vm {
let v = self.regs[a]; let v = self.regs[a];
// ToPrimitive 先解盒(BigInt 包装对象如 Object(1n) 的 valueOf 返回 BigInt), // ToPrimitive 先解盒(BigInt 包装对象如 Object(1n) 的 valueOf 返回 BigInt),
// 解盒后是 BigInt 才抛 TypeError——用户覆盖 valueOf/toString 的普通对象不受影响。 // 解盒后是 BigInt 才抛 TypeError——用户覆盖 valueOf/toString 的普通对象不受影响。
let prim = self.coerce_primitive_bounded(v, false)?; let prim = self.coerce_primitive_bounded(v, coercion::ToPrimitiveHint::Number)?;
if prim.is_bigint() { if prim.is_bigint() {
return self.raise_type_error("Cannot convert a BigInt value to a number"); return self.raise_type_error("Cannot convert a BigInt value to a number");
} }
@@ -348,8 +348,8 @@ impl Vm {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) + self.bigint_value(rv)); self.regs[rd] = self.new_bigint(self.bigint_value(lv) + self.bigint_value(rv));
return Ok(()); return Ok(());
} }
let lhs = self.coerce_primitive_bounded(lv, false)?; let lhs = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let rhs = self.coerce_primitive_bounded(rv, false)?; let rhs = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
reject_symbol_add!(self, lhs, rhs); reject_symbol_add!(self, lhs, rhs);
if lhs.is_string() || rhs.is_string() { if lhs.is_string() || rhs.is_string() {
self.regs[rd] = self.concat_strings(lhs, rhs); self.regs[rd] = self.concat_strings(lhs, rhs);
@@ -387,8 +387,8 @@ impl Vm {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) - self.bigint_value(rv)); self.regs[rd] = self.new_bigint(self.bigint_value(lv) - self.bigint_value(rv));
return Ok(()); return Ok(());
} }
let l = self.coerce_primitive_bounded(lv, false)?; let l = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let r = self.coerce_primitive_bounded(rv, false)?; let r = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
reject_symbol_number!(self, l, r); reject_symbol_number!(self, l, r);
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(l) - self.bigint_value(r)); self.regs[rd] = self.new_bigint(self.bigint_value(l) - self.bigint_value(r));
@@ -420,8 +420,8 @@ impl Vm {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) * self.bigint_value(rv)); self.regs[rd] = self.new_bigint(self.bigint_value(lv) * self.bigint_value(rv));
return Ok(()); return Ok(());
} }
let l = self.coerce_primitive_bounded(lv, false)?; let l = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let r = self.coerce_primitive_bounded(rv, false)?; let r = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
reject_symbol_number!(self, l, r); reject_symbol_number!(self, l, r);
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(l) * self.bigint_value(r)); self.regs[rd] = self.new_bigint(self.bigint_value(l) * self.bigint_value(r));
@@ -457,8 +457,8 @@ impl Vm {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) / r); self.regs[rd] = self.new_bigint(self.bigint_value(lv) / r);
return Ok(()); return Ok(());
} }
let l = self.coerce_primitive_bounded(lv, false)?; let l = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let r = self.coerce_primitive_bounded(rv, false)?; let r = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
reject_symbol_number!(self, l, r); reject_symbol_number!(self, l, r);
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
let rv = self.bigint_value(r); let rv = self.bigint_value(r);
@@ -506,8 +506,8 @@ impl Vm {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) % r); self.regs[rd] = self.new_bigint(self.bigint_value(lv) % r);
return Ok(()); return Ok(());
} }
let l = self.coerce_primitive_bounded(lv, false)?; let l = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let r = self.coerce_primitive_bounded(rv, false)?; let r = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
reject_symbol_number!(self, l, r); reject_symbol_number!(self, l, r);
if l.is_bigint() && r.is_bigint() { if l.is_bigint() && r.is_bigint() {
let rv = self.bigint_value(r); let rv = self.bigint_value(r);
@@ -551,8 +551,8 @@ impl Vm {
let exp = self.bigint_value(rv).clone(); let exp = self.bigint_value(rv).clone();
return self.bigint_exp(&base, &exp); return self.bigint_exp(&base, &exp);
} }
let l = self.coerce_primitive_bounded(lv, false)?; let l = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let r = self.coerce_primitive_bounded(rv, false)?; let r = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
if l.is_symbol() || r.is_symbol() { if l.is_symbol() || r.is_symbol() {
self.raise_type_error("Cannot convert a Symbol value to a number")?; self.raise_type_error("Cannot convert a Symbol value to a number")?;
return Ok(JsValue::undefined()); return Ok(JsValue::undefined());
+4 -3
View File
@@ -2,6 +2,7 @@ use crate::vm::Vm;
use crate::vm_trace; use crate::vm_trace;
use num_bigint::{BigInt, Sign}; use num_bigint::{BigInt, Sign};
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
use oxide_runtime_api as coercion;
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
impl Vm { impl Vm {
@@ -19,13 +20,13 @@ impl Vm {
#[inline(always)] #[inline(always)]
fn coerce_primitive_pair(&mut self, lv: JsValue, rv: JsValue) -> Result<(JsValue, JsValue), String> { fn coerce_primitive_pair(&mut self, lv: JsValue, rv: JsValue) -> Result<(JsValue, JsValue), String> {
let saved_pc = self.pc; let saved_pc = self.pc;
let l = self.coerce_primitive_bounded(lv, false)?; let l = self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
if self.pc != saved_pc { if self.pc != saved_pc {
// 转换异常已被外围 try/catch 接住:unwind 已改写 pc,立即中止后续转换, // 转换异常已被外围 try/catch 接住:unwind 已改写 pc,立即中止后续转换,
// 由调用方在检测到 pc 变化后直接返回,避免继续用 undefined 计算结果并二次抛错。 // 由调用方在检测到 pc 变化后直接返回,避免继续用 undefined 计算结果并二次抛错。
return Ok((JsValue::undefined(), JsValue::undefined())); return Ok((JsValue::undefined(), JsValue::undefined()));
} }
let r = self.coerce_primitive_bounded(rv, false)?; let r = self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
if self.pc != saved_pc { if self.pc != saved_pc {
return Ok((JsValue::undefined(), JsValue::undefined())); return Ok((JsValue::undefined(), JsValue::undefined()));
} }
@@ -183,7 +184,7 @@ impl Vm {
pub(crate) fn dispatch_bit_not(&mut self, rd: usize, a: usize) -> Result<(), String> { pub(crate) fn dispatch_bit_not(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("BIT_NOT rd={} r{}", rd, a); vm_trace!("BIT_NOT rd={} r{}", rd, a);
let saved_pc = self.pc; let saved_pc = self.pc;
let v = self.coerce_primitive_bounded(self.regs[a], false)?; let v = self.coerce_primitive_bounded(self.regs[a], coercion::ToPrimitiveHint::Number)?;
if self.pc != saved_pc { if self.pc != saved_pc {
return Ok(()); return Ok(());
} }
+8 -8
View File
@@ -27,8 +27,8 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_lt(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { pub(crate) fn dispatch_lt(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("LT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); vm_trace!("LT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
let va = self.coerce_primitive_bounded(self.regs[a], false)?; let va = self.coerce_primitive_bounded(self.regs[a], coercion::ToPrimitiveHint::Number)?;
let vb = self.coerce_primitive_bounded(self.regs[b], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], coercion::ToPrimitiveHint::Number)?;
if va.is_symbol() || vb.is_symbol() { if va.is_symbol() || vb.is_symbol() {
return self.raise_type_error("Cannot convert a Symbol value to a number"); return self.raise_type_error("Cannot convert a Symbol value to a number");
} }
@@ -45,8 +45,8 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_gt(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { pub(crate) fn dispatch_gt(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); vm_trace!("GT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
let va = self.coerce_primitive_bounded(self.regs[a], false)?; let va = self.coerce_primitive_bounded(self.regs[a], coercion::ToPrimitiveHint::Number)?;
let vb = self.coerce_primitive_bounded(self.regs[b], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], coercion::ToPrimitiveHint::Number)?;
if va.is_symbol() || vb.is_symbol() { if va.is_symbol() || vb.is_symbol() {
return self.raise_type_error("Cannot convert a Symbol value to a number"); return self.raise_type_error("Cannot convert a Symbol value to a number");
} }
@@ -63,8 +63,8 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_lte(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { pub(crate) fn dispatch_lte(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("LTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); vm_trace!("LTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
let va = self.coerce_primitive_bounded(self.regs[a], false)?; let va = self.coerce_primitive_bounded(self.regs[a], coercion::ToPrimitiveHint::Number)?;
let vb = self.coerce_primitive_bounded(self.regs[b], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], coercion::ToPrimitiveHint::Number)?;
if va.is_symbol() || vb.is_symbol() { if va.is_symbol() || vb.is_symbol() {
return self.raise_type_error("Cannot convert a Symbol value to a number"); return self.raise_type_error("Cannot convert a Symbol value to a number");
} }
@@ -81,8 +81,8 @@ impl Vm {
#[inline(always)] #[inline(always)]
pub(crate) fn dispatch_gte(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { pub(crate) fn dispatch_gte(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
vm_trace!("GTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]); vm_trace!("GTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
let va = self.coerce_primitive_bounded(self.regs[a], false)?; let va = self.coerce_primitive_bounded(self.regs[a], coercion::ToPrimitiveHint::Number)?;
let vb = self.coerce_primitive_bounded(self.regs[b], false)?; let vb = self.coerce_primitive_bounded(self.regs[b], coercion::ToPrimitiveHint::Number)?;
if va.is_symbol() || vb.is_symbol() { if va.is_symbol() || vb.is_symbol() {
return self.raise_type_error("Cannot convert a Symbol value to a number"); return self.raise_type_error("Cannot convert a Symbol value to a number");
} }
+2 -2
View File
@@ -191,8 +191,8 @@ impl Vm {
let obj = unsafe { &mut *obj_ptr }; let obj = unsafe { &mut *obj_ptr };
self.read_member_prop(obj, prop_name_si, receiver)? self.read_member_prop(obj, prop_name_si, receiver)?
}; };
let prop_val = self.coerce_primitive_bounded(prop_raw, false)?; let prop_val = self.coerce_primitive_bounded(prop_raw, coercion::ToPrimitiveHint::Number)?;
let rhs = self.coerce_primitive_bounded(self.regs[a], false)?; let rhs = self.coerce_primitive_bounded(self.regs[a], coercion::ToPrimitiveHint::Number)?;
if prop_val.is_symbol() || rhs.is_symbol() { if prop_val.is_symbol() || rhs.is_symbol() {
// 复合成员加法分流同 `+`:字符串分支走 ToString、数值分支走 ToNumber, // 复合成员加法分流同 `+`:字符串分支走 ToString、数值分支走 ToNumber,
// 两者对 Symbol 均抛 TypeError(ECMA-262 §7.1.4 / §7.1.17)。 // 两者对 Symbol 均抛 TypeError(ECMA-262 §7.1.4 / §7.1.17)。
+2 -2
View File
@@ -31,8 +31,8 @@ macro_rules! binary_arith {
} else { } else {
// 注意:混合 BigInt/Number 检查不能在此前置(对象操作数如 // 注意:混合 BigInt/Number 检查不能在此前置(对象操作数如
// `{valueOf: () => 2n}` 需先 ToPrimitive 再判定),统一放 coerce 之后。 // `{valueOf: () => 2n}` 需先 ToPrimitive 再判定),统一放 coerce 之后。
let l = $self.coerce_primitive_bounded(lv, false)?; let l = $self.coerce_primitive_bounded(lv, coercion::ToPrimitiveHint::Number)?;
let r = $self.coerce_primitive_bounded(rv, false)?; let r = $self.coerce_primitive_bounded(rv, coercion::ToPrimitiveHint::Number)?;
if l.is_symbol() || r.is_symbol() { if l.is_symbol() || r.is_symbol() {
// ToNumber(Symbol) 抛 TypeError(ECMA-262 §7.1.4),先于 BigInt 混合判定。 // ToNumber(Symbol) 抛 TypeError(ECMA-262 §7.1.4),先于 BigInt 混合判定。
$self.raise_type_error("Cannot convert a Symbol value to a number")?; $self.raise_type_error("Cannot convert a Symbol value to a number")?;
+9 -7
View File
@@ -14,19 +14,19 @@ use crate::vm_debug;
impl Vm { impl Vm {
/// ToPrimitive 有界版:按规范序把对象转为原始值,失败统一抛 `TypeError`。 /// ToPrimitive 有界版:按规范序把对象转为原始值,失败统一抛 `TypeError`。
/// ///
/// 对象上(沿原型链读取)的 `Symbol.toPrimitive` 优先;不存在时按 `prefer_string` 定序尝试 /// 对象上(沿原型链读取)的 `Symbol.toPrimitive` 优先;不存在时按 hint 定序尝试
/// `toString`/`valueOf`,等价于 OrdinaryToPrimitive。 /// `toString`/`valueOf`,等价于 OrdinaryToPrimitive。
/// ///
/// # 步骤 /// # 步骤
/// 1. 非对象或空指针原样返回。 /// 1. 非对象或空指针原样返回。
/// 2. 读 `@@toPrimitive`:存在则必须可调用(否则抛 `TypeError`),以 /// 2. 读 `@@toPrimitive`:存在则必须可调用(否则抛 `TypeError`),以
/// `"string"`/`"number"` hint 调用;返回对象再抛 `TypeError`。 /// hint 字符串(`"string"`/`"default"`/`"number"`)调用;返回对象再抛 `TypeError`。
/// 3. 回退方法族:按 hint 顺序试 `toString`/`valueOf`,不可调用者跳过, /// 3. 回退方法族:按 hint 顺序试 `toString`/`valueOf`,不可调用者跳过,
/// 首个返回非对象的结果即采用。 /// 首个返回非对象的结果即采用。
/// 4. 两条路径都不产出原始值时抛 `TypeError`。 /// 4. 两条路径都不产出原始值时抛 `TypeError`。
/// ///
/// # 边界与前提 /// # 边界与前提
/// - `prefer_string` 为 `true` 时先试 `toString`,否则先试 `valueOf`。 /// - hint 为 `String` 时先试 `toString`,`Default`/`Number` 时先试 `valueOf`。
/// - 方法调用经 `call_function_sync`;调用错误转 `raise_call_error` 恢复 /// - 方法调用经 `call_function_sync`;调用错误转 `raise_call_error` 恢复
/// 原始异常。 /// 原始异常。
/// ///
@@ -36,7 +36,9 @@ impl Vm {
/// # 注意事项 /// # 注意事项
/// - 主 dispatch 下就地展开异常;原生 builtin 内部只返回格式化 `Err`, /// - 主 dispatch 下就地展开异常;原生 builtin 内部只返回格式化 `Err`,
/// 由其调用边界恢复(见 `conversion_error`)。 /// 由其调用边界恢复(见 `conversion_error`)。
pub(crate) fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result<JsValue, String> { pub(crate) fn coerce_primitive_bounded(
&mut self, value: JsValue, hint: coercion::ToPrimitiveHint,
) -> Result<JsValue, String> {
if !value.is_object() { if !value.is_object() {
return Ok(value); return Ok(value);
} }
@@ -60,7 +62,7 @@ impl Vm {
self.conversion_error("Symbol.toPrimitive is not a function")?; self.conversion_error("Symbol.toPrimitive is not a function")?;
return Ok(JsValue::undefined()); return Ok(JsValue::undefined());
} }
let hint_val = self.new_string(if prefer_string { "string" } else { "number" }); let hint_val = self.new_string(hint.as_str());
let result = match self.call_function_sync(exotic, value, &[hint_val]) { let result = match self.call_function_sync(exotic, value, &[hint_val]) {
Ok(r) => r, Ok(r) => r,
Err(err) => return self.raise_call_error(&err), Err(err) => return self.raise_call_error(&err),
@@ -72,7 +74,7 @@ impl Vm {
return Ok(result); return Ok(result);
} }
let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] }; let method_names = if hint.prefer_string() { ["toString", "valueOf"] } else { ["valueOf", "toString"] };
for method_name in method_names { for method_name in method_names {
let method_si = self.kernel_core.perm_interner().intern(method_name).0; let method_si = self.kernel_core.perm_interner().intern(method_name).0;
@@ -184,7 +186,7 @@ impl Vm {
/// 对 BigInt 做 lossy 转换)。BigInt 入口语义:显式 lossy 站点 /// 对 BigInt 做 lossy 转换)。BigInt 入口语义:显式 lossy 站点
/// (`Number` 构造器)先解盒自行转换,不经本入口。 /// (`Number` 构造器)先解盒自行转换,不经本入口。
pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> { pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> {
let primitive = self.coerce_primitive_bounded(value, false)?; let primitive = self.coerce_primitive_bounded(value, coercion::ToPrimitiveHint::Number)?;
if primitive.is_symbol() { if primitive.is_symbol() {
self.conversion_error("Cannot convert a Symbol value to a number")?; self.conversion_error("Cannot convert a Symbol value to a number")?;
return Ok(f64::NAN); return Ok(f64::NAN);
+4 -2
View File
@@ -253,8 +253,10 @@ impl oxide_runtime_api::VmHost for Vm {
fn lookup_str(&self, val: JsValue) -> Option<String> { fn lookup_str(&self, val: JsValue) -> Option<String> {
self.lookup_str(val) self.lookup_str(val)
} }
fn coerce_primitive_bounded(&mut self, value: JsValue, prefer_string: bool) -> Result<JsValue, String> { fn coerce_primitive_bounded(
self.coerce_primitive_bounded(value, prefer_string) &mut self, value: JsValue, hint: oxide_runtime_api::ToPrimitiveHint,
) -> Result<JsValue, String> {
self.coerce_primitive_bounded(value, hint)
} }
fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> { fn coerce_number_bounded(&mut self, value: JsValue) -> Result<f64, String> {
self.coerce_number_bounded(value) self.coerce_number_bounded(value)
@@ -1013,3 +1013,174 @@ fn date_to_primitive_existing_coercion_regression() {
let r = eval(&mut vm, "typeof ('' + new Date(0))").unwrap(); let r = eval(&mut vm, "typeof ('' + new Date(0))").unwrap();
assert_eq!(str_val(&vm, r), "string"); assert_eq!(str_val(&vm, r), "string");
} }
// ── 严格 ISO 解析(Date Time String Format)──
#[test]
fn date_parse_extended_year_time_clip_boundaries() {
let mut vm = Vm::new();
// 扩展年落在 TimeClip 包络两端,按规范解析为包络端点值。
let r = eval(&mut vm, "Date.parse('275760-09-13T00:00:00Z')").unwrap();
assert_eq!(r.as_double(), 8.64e15);
let r = eval(&mut vm, "Date.parse('-271821-04-20T00:00:00Z')").unwrap();
assert_eq!(r.as_double(), -8.64e15);
// 扩展年越出包络:整体可识别,TimeClip 归 NaN。
let r = eval(&mut vm, "Date.parse('275761-01-01T00:00:00Z')").unwrap();
assert!(r.as_double().is_nan());
let r = eval(&mut vm, "Date.parse('-271822-01-01T00:00:00Z')").unwrap();
assert!(r.as_double().is_nan());
}
#[test]
fn date_parse_negative_zero_year_rejected() {
let mut vm = Vm::new();
// 年 0 视为正,须写作 +000000;-000000 不是合法扩展年。
let r = eval(&mut vm, "Date.parse('-000000-01-01T00:00:00Z')").unwrap();
assert!(r.as_double().is_nan());
let r = eval(&mut vm, "Date.parse('+000000-01-01T00:00:00Z')").unwrap();
assert_eq!(r.as_double(), -62167219200000.0);
}
#[test]
fn date_parse_date_only_is_utc() {
let mut vm = Vm::new();
// 日期-only 形式解释为 UTC 零点。
let r = eval(&mut vm, "Date.parse('2020-01-01')").unwrap();
assert_eq!(r.as_double(), 1577836800000.0);
}
#[test]
fn date_parse_offsetless_datetime_is_local() {
let mut vm = Vm::new();
// 无偏移完整日期时间解释为本地时间(与本地构造器互证,宿主无关)。
let r = eval(&mut vm, "Date.parse('2020-01-01T12:00:00')").unwrap();
let r2 = eval(&mut vm, "new Date(2020, 0, 1, 12).getTime()").unwrap();
assert_eq!(r.as_double(), r2.as_double());
}
#[test]
fn date_parse_midnight_24_accepted() {
let mut vm = Vm::new();
// 24:00:00 等价于次日 00:00。
let r = eval(&mut vm, "Date.parse('2020-01-01T24:00:00Z')").unwrap();
assert_eq!(r.as_double(), 1577923200000.0);
// 24 时仅允许 24:00:00 形态,分秒毫秒非零即非法。
let r = eval(&mut vm, "Date.parse('2020-01-01T24:01:00Z')").unwrap();
assert!(r.as_double().is_nan());
let r = eval(&mut vm, "Date.parse('2020-01-01T24:00:01Z')").unwrap();
assert!(r.as_double().is_nan());
}
#[test]
fn date_parse_invalid_calendar_rejected() {
let mut vm = Vm::new();
// 月 13、二月 30 日、时 25 均不是合法日期时间串。
let r = eval(&mut vm, "Date.parse('2020-13-01T00:00:00Z')").unwrap();
assert!(r.as_double().is_nan());
let r = eval(&mut vm, "Date.parse('2020-02-30T00:00:00Z')").unwrap();
assert!(r.as_double().is_nan());
let r = eval(&mut vm, "Date.parse('2020-01-01T25:00:00Z')").unwrap();
assert!(r.as_double().is_nan());
}
// ── toISOString 6 位年与 TimeClip 边界 ──
#[test]
fn date_to_iso_string_six_digit_year() {
let mut vm = Vm::new();
// 4 位年零填充(epoch 为 1970 年)。
let r = eval(&mut vm, "new Date(0).toISOString()").unwrap();
assert_eq!(str_val(&vm, r), "1970-01-01T00:00:00.000Z");
// 年 0 输出 4 位 0000。
let r = eval(&mut vm, "new Date(-62167219200000).toISOString()").unwrap();
assert_eq!(str_val(&vm, r), "0000-01-01T00:00:00.000Z");
// 负年带负号前缀(年 -1 的 1 月 1 日)。
let r = eval(&mut vm, "new Date(-62198755200000).toISOString()").unwrap();
assert_eq!(str_val(&vm, r), "-000001-01-01T00:00:00.000Z");
}
#[test]
fn date_to_iso_string_time_clip_boundaries() {
let mut vm = Vm::new();
// 包络两端合法(正年 > 9999 带 + 前缀 6 位,负年带 - 前缀 6 位)。
let r = eval(&mut vm, "new Date(8.64e15).toISOString()").unwrap();
assert_eq!(str_val(&vm, r), "+275760-09-13T00:00:00.000Z");
let r = eval(&mut vm, "new Date(-8.64e15).toISOString()").unwrap();
assert_eq!(str_val(&vm, r), "-271821-04-20T00:00:00.000Z");
// 越出包络一毫秒:时间值归 NaN,toISOString 抛 RangeError。
let r = eval(
&mut vm,
"try { new Date(8.64e15 + 1).toISOString(); 'no' } catch (e) { e instanceof RangeError ? 'yes' : 'no' }",
)
.unwrap();
assert_eq!(str_val(&vm, r), "yes");
let r = eval(
&mut vm,
"try { new Date(-8.64e15 - 1).toISOString(); 'no' } catch (e) { e instanceof RangeError ? 'yes' : 'no' }",
)
.unwrap();
assert_eq!(str_val(&vm, r), "yes");
}
// ── toJSON 泛型化 ──
#[test]
fn date_to_json_invalid_date_returns_null() {
let mut vm = Vm::new();
let r = eval(&mut vm, "new Date(NaN).toJSON()").unwrap();
assert!(r.is_null());
}
#[test]
fn date_to_json_generic_non_date_this() {
let mut vm = Vm::new();
// 非有限 Number 结果返回 null(有限性检查仅对 Number 生效)。
let r = eval(&mut vm, "Date.prototype.toJSON.call(Infinity)").unwrap();
assert!(r.is_null());
// 字符串结果(非 Number)不拦截,直落 Invoke(toISOString),
// 装箱后的 String 对象无该方法,抛 TypeError。
let r = eval(
&mut vm,
"try { Date.prototype.toJSON.call('2020-01-01T00:00:00Z'); 'no' } \
catch (e) { e instanceof TypeError ? 'yes' : 'no' }",
)
.unwrap();
assert_eq!(str_val(&vm, r), "yes");
// 有限数值接收者装箱后走到 Invoke(toISOString),方法不可调用抛 TypeError。
let r = eval(
&mut vm,
"try { Date.prototype.toJSON.call(42); 'no' } catch (e) { e instanceof TypeError ? 'yes' : 'no' }",
)
.unwrap();
assert_eq!(str_val(&vm, r), "yes");
}
#[test]
fn date_to_json_via_json_stringify() {
let mut vm = Vm::new();
let r = eval(&mut vm, "JSON.stringify({ a: new Date(0) })").unwrap();
assert_eq!(str_val(&vm, r), "{\"a\":\"1970-01-01T00:00:00.000Z\"}");
}
// ── 构造器单参对象:ToPrimitive 传 default 提示 ──
#[test]
fn date_constructor_single_arg_object_default_hint() {
let mut vm = Vm::new();
// 单参对象构造经 ToPrimitive(default 提示),@@toPrimitive 收到 "default"。
let r = eval(
&mut vm,
"var d = new Date({ [Symbol.toPrimitive]: function(h) { return h === 'default' ? 0 : 1; } }); \
d.getTime() === 0 ? 1 : 0",
)
.unwrap();
assert_eq!(r.as_int(), 1);
}
#[test]
fn date_constructor_single_arg_truncates_before_time_clip() {
let mut vm = Vm::new();
// 单参数值先截断再 TimeClip;-0.5 截断为 -0,归一为 +0。
let r = eval(&mut vm, "Object.is(new Date(-0.5).getTime(), 0) ? 1 : 0").unwrap();
assert_eq!(r.as_int(), 1);
}