feat: 实现最小 BigInt 子集(字面量/算术/比较/构造器,Temporal 前置)

This commit is contained in:
2026-08-10 23:02:31 +08:00
parent 9e26a13655
commit 078688070d
22 changed files with 706 additions and 32 deletions
@@ -0,0 +1,33 @@
use std::sync::Arc;
use crate::bindings::{apply_binding_table, bind_global_value, configure_native_constructor};
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
/// 把 BigInt 构造器与原型方法绑定到 global。
///
/// 构造器/原型对象由 `BuiltinWorld` 创建(含 name/prototype/constructor 互指),
/// 这里只配置 native 实现并挂方法。`BigInt(value)` 当函数调用时返回 BigInt 值;
/// `new BigInt()` 抛 TypeError(BigInt 不可 new)。
pub fn bind_bigint(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
let world = session.builtin_world();
let ctor_ptr = world.bigint_constructor.as_ptr() as *mut JsObject;
let ctor = unsafe { &mut *ctor_ptr };
let proto_ptr = world.bigint_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &mut *proto_ptr };
configure_native_constructor(ctor, oxide_builtins::bigint::bigint_constructor::<crate::vm::Vm> as *const (), 1);
apply_binding_table(
world,
proto,
core,
&[
("toString", oxide_builtins::bigint::bigint_to_string::<crate::vm::Vm> as *const (), 0),
("valueOf", oxide_builtins::bigint::bigint_value_of::<crate::vm::Vm> as *const (), 0),
],
);
bind_global_value(core, global, "BigInt", JsValue::from_js_object(ctor_ptr));
}
+2 -3
View File
@@ -8,9 +8,8 @@ use oxide_types::value::JsValue;
use crate::bindings::{bind_global_value, configure_native_constructor};
const STUBS: [(&str, *const (), u8); 8] = [
const STUBS: [(&str, *const (), u8); 7] = [
("Proxy", oxide_builtins::stubs::proxy_stub::<crate::vm::Vm> as *const (), 2),
("BigInt", oxide_builtins::stubs::bigint_stub::<crate::vm::Vm> as *const (), 1),
("WeakMap", oxide_builtins::stubs::weakmap_stub::<crate::vm::Vm> as *const (), 0),
("WeakSet", oxide_builtins::stubs::weakset_stub::<crate::vm::Vm> as *const (), 0),
("WeakRef", oxide_builtins::stubs::weakref_stub::<crate::vm::Vm> as *const (), 1),
@@ -27,7 +26,7 @@ const STUBS: [(&str, *const (), u8); 8] = [
("Atomics", oxide_builtins::stubs::atomics_stub::<crate::vm::Vm> as *const (), 0),
];
/// 把未实现内置(Proxy/BigInt/WeakMap/WeakSet/WeakRef/FinalizationRegistry/SharedArrayBuffer/Atomics)
/// 把未实现内置(Proxy/WeakMap/WeakSet/WeakRef/FinalizationRegistry/SharedArrayBuffer/Atomics)
/// 的 stub 构造器绑定到 global,并登记到 `stub_objects` 供快照跟踪。
pub fn bind_stubs(core: &Arc<KernelCore>, session: &mut KernelSession, global: &mut JsObject) {
let builtin_world = Arc::get_mut(&mut session.builtin_world)
+6 -1
View File
@@ -6,6 +6,8 @@
pub mod bind_array;
/// ArrayBuffer 构造器与原型的 native 方法绑定。
pub mod bind_array_buffer;
/// BigInt 构造器与原型的 native 方法绑定。
pub mod bind_bigint;
/// Boolean 构造器与原型的 native 方法绑定。
pub mod bind_boolean;
/// DataView 构造器与原型的 native 方法绑定。
@@ -371,7 +373,6 @@ fn bind_iterator_global(core: &Arc<KernelCore>, session: &KernelSession, global:
fn bind_stub_globals(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
for (index, (name, native_fn, arg_count)) in [
("Proxy", oxide_builtins::stubs::proxy_stub::<crate::vm::Vm> as *const (), 2),
("BigInt", oxide_builtins::stubs::bigint_stub::<crate::vm::Vm> as *const (), 1),
("WeakMap", oxide_builtins::stubs::weakmap_stub::<crate::vm::Vm> as *const (), 0),
("WeakSet", oxide_builtins::stubs::weakset_stub::<crate::vm::Vm> as *const (), 0),
("WeakRef", oxide_builtins::stubs::weakref_stub::<crate::vm::Vm> as *const (), 1),
@@ -603,6 +604,7 @@ pub fn bind_global_builtin_slots(core: &Arc<KernelCore>, session: &KernelSession
bind_reflect_global(core, session, global);
bind_iterator_global(core, session, global);
bind_stub_globals(core, session, global);
bind_bigint::bind_bigint(core, session, global);
bind_global_functions(core, session, global);
let global_this = JsValue::from_js_object(global as *mut JsObject);
bind_existing_global(core, global, "globalThis", global_this);
@@ -674,6 +676,9 @@ pub fn rebind_dirty_builtins(core: &Arc<KernelCore>, session: &mut KernelSession
if dirty.map_or(true, |d| d.stubs) {
bind_stubs::bind_stubs(core, session, global);
}
if dirty.map_or(true, |d| d.stubs) {
bind_bigint::bind_bigint(core, session, global);
}
}
/// 完整初始化一个 session 的内置对象(全量绑定 + `globalThis` + 快照记录)。
+120 -14
View File
@@ -13,8 +13,25 @@ impl Vm {
self.regs[rd] = JsValue::float(lv.as_int() as f64 + rv.as_int() as f64);
return Ok(());
}
if lv.is_bigint() && rv.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) + self.bigint_value(rv));
return Ok(());
}
// 注意:不能在此前置检查 BigInt 混合(`lv.is_bigint() || rv.is_bigint()`)——
// 对象操作数(如 `{valueOf: () => 2n}`)需先 ToPrimitive 再判定,混合检查
// 必须放在 coerce 之后。
let lhs = self.coerce_primitive_bounded(lv, false)?;
let rhs = self.coerce_primitive_bounded(rv, false)?;
if lhs.is_bigint() && rhs.is_bigint() {
// 包装对象 coerce 后暴露双 BigInt(如 Object(2n) + 2n)。
self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
return Ok(());
}
if lhs.is_bigint() != rhs.is_bigint() {
// 包装对象 coerce 后暴露 BigInt:与另一非 BigInt 操作数混合加法必须抛
// TypeError(规范不允许 BigInt 与 Number/String 相加)。
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
if lhs.is_string() || rhs.is_string() {
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 };
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 };
@@ -38,7 +55,12 @@ impl Vm {
#[inline(always)]
pub(crate) fn dispatch_neg(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("NEG rd={} r{}={:?}", rd, a, self.regs[a]);
let v = self.coerce_number_bounded(self.regs[a])?;
let v = self.regs[a];
if v.is_bigint() {
self.regs[rd] = self.new_bigint(-self.bigint_value(v));
return Ok(());
}
let v = self.coerce_number_bounded(v)?;
self.regs[rd] = JsValue::float(-v);
Ok(())
}
@@ -46,7 +68,12 @@ impl Vm {
#[inline(always)]
pub(crate) fn dispatch_unary_plus(&mut self, rd: usize, a: usize) -> Result<(), String> {
vm_trace!("UNARY_PLUS rd={} r{}={:?}", rd, a, self.regs[a]);
let v = self.coerce_number_bounded(self.regs[a])?;
let v = self.regs[a];
if v.is_bigint() {
// 一元 + 对 BigInt 必须抛 TypeError(ToNumber(BigInt) 在隐式路径禁止)。
return self.raise_type_error("Cannot convert a BigInt value to a number");
}
let v = self.coerce_number_bounded(v)?;
self.regs[rd] = JsValue::float(v);
Ok(())
}
@@ -59,8 +86,19 @@ impl Vm {
self.regs[rd] = JsValue::float(lv.as_int() as f64 + rv.as_int() as f64);
return Ok(());
}
if lv.is_bigint() && rv.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) + self.bigint_value(rv));
return Ok(());
}
let lhs = self.coerce_primitive_bounded(lv, false)?;
let rhs = self.coerce_primitive_bounded(rv, false)?;
if lhs.is_bigint() && rhs.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(lhs) + self.bigint_value(rhs));
return Ok(());
}
if lhs.is_bigint() != rhs.is_bigint() {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
if lhs.is_string() || rhs.is_string() {
let lbytes = if lhs.is_string() { unsafe { (*lhs.as_string_ptr()).len() } } else { 0 };
let rbytes = if rhs.is_string() { unsafe { (*rhs.as_string_ptr()).len() } } else { 0 };
@@ -90,9 +128,22 @@ impl Vm {
self.regs[rd] = JsValue::float(lv.as_int() as f64 - rv.as_int() as f64);
return Ok(());
}
let l = self.coerce_number_bounded(lv)?;
let r = self.coerce_number_bounded(rv)?;
self.regs[rd] = JsValue::float(l - r);
if lv.is_bigint() && rv.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) - self.bigint_value(rv));
return Ok(());
}
let l = self.coerce_primitive_bounded(lv, false)?;
let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(l) - self.bigint_value(r));
return Ok(());
}
if l.is_bigint() != r.is_bigint() {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
let ln = coercion::to_number(l);
let rn = coercion::to_number(r);
self.regs[rd] = JsValue::float(ln - rn);
Ok(())
}
@@ -105,9 +156,22 @@ impl Vm {
self.regs[rd] = JsValue::float(lv.as_int() as f64 * rv.as_int() as f64);
return Ok(());
}
let l = self.coerce_number_bounded(lv)?;
let r = self.coerce_number_bounded(rv)?;
self.regs[rd] = JsValue::float(l * r);
if lv.is_bigint() && rv.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(lv) * self.bigint_value(rv));
return Ok(());
}
let l = self.coerce_primitive_bounded(lv, false)?;
let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() {
self.regs[rd] = self.new_bigint(self.bigint_value(l) * self.bigint_value(r));
return Ok(());
}
if l.is_bigint() != r.is_bigint() {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
let ln = coercion::to_number(l);
let rn = coercion::to_number(r);
self.regs[rd] = JsValue::float(ln * rn);
Ok(())
}
@@ -120,9 +184,30 @@ impl Vm {
self.regs[rd] = JsValue::float(lv.as_int() as f64 / rv.as_int() as f64);
return Ok(());
}
let l = self.coerce_number_bounded(lv)?;
let r = self.coerce_number_bounded(rv)?;
self.regs[rd] = JsValue::float(l / r);
if lv.is_bigint() && rv.is_bigint() {
let r = self.bigint_value(rv);
if r == 0 {
return self.raise_error_kind("RangeError", "Division by zero");
}
self.regs[rd] = self.new_bigint(self.bigint_value(lv) / r);
return Ok(());
}
let l = self.coerce_primitive_bounded(lv, false)?;
let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() {
let rv = self.bigint_value(r);
if rv == 0 {
return self.raise_error_kind("RangeError", "Division by zero");
}
self.regs[rd] = self.new_bigint(self.bigint_value(l) / rv);
return Ok(());
}
if l.is_bigint() != r.is_bigint() {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
let ln = coercion::to_number(l);
let rn = coercion::to_number(r);
self.regs[rd] = JsValue::float(ln / rn);
Ok(())
}
@@ -135,9 +220,30 @@ impl Vm {
self.regs[rd] = JsValue::float(lv.as_int() as f64 % rv.as_int() as f64);
return Ok(());
}
let l = self.coerce_number_bounded(lv)?;
let r = self.coerce_number_bounded(rv)?;
self.regs[rd] = JsValue::float(l % r);
if lv.is_bigint() && rv.is_bigint() {
let r = self.bigint_value(rv);
if r == 0 {
return self.raise_error_kind("RangeError", "Division by zero");
}
self.regs[rd] = self.new_bigint(self.bigint_value(lv) % r);
return Ok(());
}
let l = self.coerce_primitive_bounded(lv, false)?;
let r = self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() {
let rv = self.bigint_value(r);
if rv == 0 {
return self.raise_error_kind("RangeError", "Division by zero");
}
self.regs[rd] = self.new_bigint(self.bigint_value(l) % rv);
return Ok(());
}
if l.is_bigint() != r.is_bigint() {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
let ln = coercion::to_number(l);
let rn = coercion::to_number(r);
self.regs[rd] = JsValue::float(ln % rn);
Ok(())
}
@@ -29,6 +29,9 @@ impl Vm {
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 vb = self.coerce_primitive_bounded(self.regs[b], false)?;
if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
match coercion::relational_compare(va, vb) {
Some(r) => self.regs[rd] = JsValue::bool(r),
None => {
@@ -44,6 +47,9 @@ impl Vm {
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 vb = self.coerce_primitive_bounded(self.regs[b], false)?;
if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
match coercion::relational_compare(vb, va) {
Some(r) => self.regs[rd] = JsValue::bool(r),
None => {
@@ -59,6 +65,9 @@ impl Vm {
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 vb = self.coerce_primitive_bounded(self.regs[b], false)?;
if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
match coercion::relational_compare(vb, va) {
Some(r) => self.regs[rd] = JsValue::bool(!r),
None => {
@@ -74,6 +83,9 @@ impl Vm {
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 vb = self.coerce_primitive_bounded(self.regs[b], false)?;
if (va.is_bigint() || vb.is_bigint()) && (va.is_symbol() || vb.is_symbol()) {
return self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions");
}
match coercion::relational_compare(va, vb) {
Some(r) => self.regs[rd] = JsValue::bool(!r),
None => {
+2
View File
@@ -36,6 +36,8 @@ impl Vm {
"string"
} else if val.is_symbol() {
"symbol"
} else if val.is_bigint() {
"bigint"
} else if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_function() {
+5
View File
@@ -65,6 +65,11 @@ impl Vm {
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
if val.is_bigint() {
let proto_ptr = self.session.builtin_world().bigint_proto.as_ptr() as *mut JsObject;
let proto = unsafe { &*proto_ptr };
return self.ordinary_get(proto, prop_name_si, val).map(Some);
}
Ok(None)
}
+39 -3
View File
@@ -84,10 +84,40 @@ macro_rules! binary_arith {
let rv = $self.regs[$b];
if lv.is_int() && rv.is_int() {
$self.regs[$rd] = JsValue::float(lv.as_int() as f64 $op rv.as_int() as f64);
} else if lv.is_bigint() && rv.is_bigint() {
let l = $self.bigint_value(lv);
let r = $self.bigint_value(rv);
if r == 0 {
// BigInt 除/模零:规范要求 RangeError(Number 路径走 f64 inf/NaN)。
$self.raise_error_kind("RangeError", "Division by zero")?;
$self.regs[$rd] = JsValue::undefined();
} else {
$self.regs[$rd] = $self.new_bigint(l $op r);
}
} else {
let l = $self.coerce_number_bounded(lv)?;
let r = $self.coerce_number_bounded(rv)?;
$self.regs[$rd] = JsValue::float(l $op r);
// 注意:混合 BigInt/Number 检查不能在此前置(对象操作数如
// `{valueOf: () => 2n}` 需先 ToPrimitive 再判定),统一放 coerce 之后。
let l = $self.coerce_primitive_bounded(lv, false)?;
let r = $self.coerce_primitive_bounded(rv, false)?;
if l.is_bigint() && r.is_bigint() {
// 包装对象 coerce 后暴露双 BigInt(如 Object(2n) / 2n)。
let lv = $self.bigint_value(l);
let rv = $self.bigint_value(r);
if rv == 0 {
$self.raise_error_kind("RangeError", "Division by zero")?;
$self.regs[$rd] = JsValue::undefined();
} else {
$self.regs[$rd] = $self.new_bigint(lv $op rv);
}
} else if l.is_bigint() != r.is_bigint() {
// 包装对象 coerce 后暴露 BigInt 混合(如 Object(1n) - 1)。
$self.raise_type_error("Cannot mix BigInt and other types, use explicit conversions")?;
$self.regs[$rd] = JsValue::undefined();
} else {
let ln = coercion::to_number(l);
let rn = coercion::to_number(r);
$self.regs[$rd] = JsValue::float(ln $op rn);
}
}
}}
}
@@ -1745,6 +1775,12 @@ impl oxide_runtime_api::VmHost for Vm {
fn new_string(&mut self, s: &str) -> JsValue {
self.new_string(s)
}
fn new_bigint(&mut self, v: i128) -> JsValue {
Vm::new_bigint(self, v)
}
fn bigint_value(&mut self, val: JsValue) -> i128 {
Vm::bigint_value(self, val)
}
fn kernel_core(&self) -> &Arc<KernelCore> {
self.kernel_core()
}
+5 -2
View File
@@ -8,14 +8,13 @@
//!
//! 本文件只定义字段;方法随 `Vm` 存放。
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use rustc_hash::FxBuildHasher;
use oxide_types::object::{JsObject, JsString};
use oxide_types::value::JsValue;
use crate::session_gc::SessionGc;
use crate::vm::ForInIter;
@@ -30,6 +29,10 @@ pub(crate) struct GcState {
pub(crate) epoch_object_ptrs: Vec<*mut JsObject>,
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
pub(crate) session_string_ptrs: Vec<*mut JsString>,
/// BigInt 堆 box(`Box<i128>`)追踪表。`RefCell` 使 `&self` 的
/// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与
/// mark/sweep 回收(值量少),只在 full_reset 统一释放。
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut i128>>,
pub(crate) session_bytes_allocated: usize,
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
}
+107
View File
@@ -79,6 +79,7 @@ impl Vm {
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
@@ -171,6 +172,7 @@ impl Vm {
epoch_object_ptrs: Vec::new(),
session_object_ptrs: Vec::new(),
session_string_ptrs: Vec::new(),
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
},
@@ -275,6 +277,7 @@ impl Vm {
self.gc_state.session_bytes_allocated = 0;
self.gc_state.session_gc = crate::session_gc::SessionGc::new();
self.free_session_string_heap_data();
self.free_session_bigint_heap_data();
self.symbols.reset();
self.root_reg_limit = 0;
self.active_reg_limit = 0;
@@ -477,10 +480,39 @@ impl Vm {
format!("{val}")
}
/// 分配一个 BigInt 值:把 `i128` 堆分配为 box 并返回携带指针的 `JsValue`。
///
/// box 指针登记进 `gc_state.session_bigint_ptrs`,在 `full_reset` 统一释放。
/// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。
pub fn new_bigint(&self, v: i128) -> JsValue {
let ptr = Box::into_raw(Box::new(v));
self.gc_state.session_bigint_ptrs.borrow_mut().push(ptr);
JsValue::bigint(ptr)
}
/// 读取 BigInt 值;调用方须保证 `val.is_bigint()`。
pub fn bigint_value(&self, val: JsValue) -> i128 {
// SAFETY: bigint 指针由 new_bigint 经 Box::into_raw 产生,存活至 full_reset。
unsafe { *val.as_bigint_ptr() }
}
/// 释放全部 session 堆 `i128` box。仅在完全隔离重置(`full_reset`)时调用,
/// 此时没有存活的 session 对象/寄存器会引用它们。
fn free_session_bigint_heap_data(&mut self) {
for ptr in self.gc_state.session_bigint_ptrs.borrow_mut().drain(..) {
// SAFETY: 每个指针来自 new_bigint 的 Box::into_raw(Box::new(i128)),
// 且只在这里恰好释放一次。
unsafe {
drop(Box::from_raw(ptr));
}
}
}
fn convert_constant(&self, constant: &Constant) -> JsValue {
match constant {
Constant::Number(v) => JsValue::float(*v),
Constant::Int(v) => JsValue::int(*v),
Constant::BigInt(v) => self.new_bigint(*v),
Constant::String(s) => self.perm_string(s),
Constant::Boolean(b) => JsValue::bool(*b),
Constant::Null => JsValue::null(),
@@ -752,4 +784,79 @@ mod tests {
));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn bigint_literal_arithmetic_and_comparison() {
let mut vm = Vm::new();
assert_eq!(run_source(&mut vm, "100n + 23n"), run_source(&mut vm, "123n"));
assert_eq!(run_source(&mut vm, "100n - 30n"), run_source(&mut vm, "70n"));
assert_eq!(run_source(&mut vm, "7n * 6n"), run_source(&mut vm, "42n"));
assert_eq!(run_source(&mut vm, "10n / 4n"), run_source(&mut vm, "2n"));
assert_eq!(run_source(&mut vm, "10n % 3n"), run_source(&mut vm, "1n"));
assert_eq!(run_source(&mut vm, "-7n"), run_source(&mut vm, "0n - 7n"));
assert_eq!(run_source(&mut vm, "123n == 123n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "123n === 123n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "5n < 3n"), JsValue::bool(false));
assert_eq!(run_source(&mut vm, "5n > 3n"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "1n === 1"), JsValue::bool(false));
let typeof_result = run_source(&mut vm, "typeof 123n");
assert!(typeof_result.is_string());
assert_eq!(vm.lookup_str(typeof_result).as_deref(), Some("bigint"));
}
#[test]
fn bigint_constructor_and_string() {
let mut vm = Vm::new();
assert_eq!(run_source(&mut vm, "BigInt(42)"), run_source(&mut vm, "42n"));
assert_eq!(run_source(&mut vm, "BigInt('123')"), run_source(&mut vm, "123n"));
assert_eq!(run_source(&mut vm, "BigInt('0x10')"), run_source(&mut vm, "16n"));
let s = run_source(&mut vm, "String(123n)");
assert!(s.is_string());
assert_eq!(vm.lookup_str(s).as_deref(), Some("123"));
let ts = run_source(&mut vm, "(123n).toString()");
assert!(ts.is_string());
assert_eq!(vm.lookup_str(ts).as_deref(), Some("123"));
assert_eq!(run_source(&mut vm, "Number(5n)"), JsValue::int(5));
}
#[test]
fn bigint_mixed_type_throws() {
let mut vm = Vm::new();
let te = run_source(&mut vm, "try { 1n + 1 } catch(e) { e.name }");
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
let re = run_source(&mut vm, "try { 1n / 0n } catch(e) { e.name }");
assert_eq!(vm.lookup_str(re).as_deref(), Some("RangeError"));
let ne = run_source(&mut vm, "try { new BigInt(1) } catch(e) { e.name }");
assert_eq!(vm.lookup_str(ne).as_deref(), Some("TypeError"));
}
#[test]
fn bigint_survives_reset_and_gc() {
let mut vm = Vm::new();
let result = run_source(&mut vm, "100n + 23n");
assert!(result.is_bigint());
assert_eq!(vm.bigint_value(result), 123);
vm.reset();
// reset 保留 session 字符串/bigint box:值仍可读。
assert_eq!(vm.bigint_value(result), 123);
}
#[test]
fn bigint_wrapped_and_number_comparison() {
let mut vm = Vm::new();
// 包装对象 coerce 后双 BigInt 运算。
assert_eq!(run_source(&mut vm, "Object(2n) / 2n"), run_source(&mut vm, "1n"));
assert_eq!(run_source(&mut vm, "Object(2n) * 3n"), run_source(&mut vm, "6n"));
assert_eq!(run_source(&mut vm, "2n + Object(3n)"), run_source(&mut vm, "5n"));
// BigInt 与 Number 精确关系比较(超出 f64 精度仍精确)。
assert_eq!(run_source(&mut vm, "9007199254740993n > 9007199254740992"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "9007199254740993n < 9007199254740994"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "2n < 3"), JsValue::bool(true));
assert_eq!(run_source(&mut vm, "3n >= 3"), JsValue::bool(true));
// NaN 关系比较为 false。
assert_eq!(run_source(&mut vm, "0n < NaN"), JsValue::bool(false));
// 混合算术抛 TypeError。
let te = run_source(&mut vm, "try { Object(1n) - 1 } catch(e) { e.name }");
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
}
}