feat: BigInt 构造器 ToPrimitive 语义、toString(radix)、asIntN/asUintN

This commit is contained in:
2026-08-12 02:23:27 +08:00
parent a84738cd60
commit aa51d57660
3 changed files with 371 additions and 65 deletions
+317 -64
View File
@@ -1,16 +1,10 @@
use oxide_runtime_api::{NativeResult, VmHost};
use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost};
use oxide_types::value::JsValue;
/// JS `BigInt()` 构造逻辑:把参数转成 BigInt 值。
///
/// 普通调用返回原始 BigInt;`new BigInt()` 抛 TypeError(BigInt 不可构造)。
/// 参数转换规则(最小子集):
/// - 无参数 → `0n`
/// - BigInt → 原样返回
/// - Number → 截断为整数(非整数抛 RangeError)
/// - String → 解析十进制整数
/// - Boolean → `0n`/`1n`
/// - 其它类型 → TypeError
/// JS `BigInt(value)` 构造逻辑(ES2020 §20.2.1.1):
/// - `new BigInt()` 抛 TypeError(BigInt 不可 new)
/// - 无参 → `0n`
/// - 其余参数先 ToPrimitive(number),Number 走 NumberToBigInt,其余走 ToBigInt。
pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if args.len() > 1 {
let this_val = vm.reg(args[0]);
@@ -22,31 +16,170 @@ pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
));
}
}
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
match to_bigint_value(vm, val) {
// 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。
if args.len() <= 1 {
return NativeResult::Ok(vm.new_bigint(0));
}
let val = vm.reg(args[1]);
// ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString
// 抛出的异常要原样传播(只触发一次,见 constructor-coercion 测试)。
let prim = match to_primitive(val, ToPrimitiveHint::Number, vm) {
Ok(p) => p,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(
vm,
"Cannot convert value to a BigInt",
));
}
};
// 步骤 3:Type(prim) 为 Number → NumberToBigInt。
if prim.is_int() {
return NativeResult::Ok(vm.new_bigint(prim.as_int() as i128));
}
if prim.is_double() {
let d = prim.as_double();
if d.is_nan() || d.is_infinite() || d.trunc() != d {
return NativeResult::Err(crate::error::create_range_error(
vm,
"The number cannot be converted to a BigInt because it is not an integer",
));
}
// i128 暂存上限:超出范围的整数后续由任意精度表示补齐。
if d < i128::MIN as f64 || d > i128::MAX as f64 {
return NativeResult::Err(crate::error::create_range_error(
vm,
"The number is too large to be converted to a BigInt",
));
}
return NativeResult::Ok(vm.new_bigint(d.trunc() as i128));
}
// 步骤 4:其它原始类型走 ToBigInt。
match to_bigint(vm, prim) {
Ok(v) => NativeResult::Ok(v),
Err(err) => NativeResult::Err(err),
Err(e) => NativeResult::Err(e),
}
}
/// `BigInt.prototype.toString`:返回 BigInt 的十进制字符串。
/// `BigInt.prototype.toString(radix)`:thisBigIntValue 后按 radix 输出。
pub fn bigint_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if !this_val.is_bigint() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"BigInt.prototype.toString called on incompatible receiver",
));
}
let v = unsafe { oxide_runtime_api::bigint_data(this_val) };
NativeResult::Ok(vm.new_string(&oxide_runtime_api::bigint_to_string(v)))
let v = match this_bigint_value(vm, this_val) {
Ok(v) => v,
Err(e) => return NativeResult::Err(e),
};
let radix = if args.len() > 1 {
let radix_arg = vm.reg(args[1]);
if radix_arg.is_undefined() {
10u32
} else {
// radix 走 ToIntegerOrInfinity:ToNumber 拒绝 Symbol/BigInt,
// 对象先 ToPrimitive(用户异常原样传播),NaN/±0 归 0。
let raw = match coerce_number_or_throw(vm, radix_arg) {
Ok(n) => n,
Err(e) => return NativeResult::Err(e),
};
let r = if raw.is_nan() || raw == 0.0 {
0.0
} else if raw.is_infinite() {
raw
} else {
raw.trunc()
};
if !(2.0..=36.0).contains(&r) {
return NativeResult::Err(crate::error::create_range_error(
vm,
"toString() radix must be between 2 and 36",
));
}
r as u32
}
} else {
10u32
};
let text = i128_to_radix_string(vm.bigint_value(v), radix);
NativeResult::Ok(vm.new_string(&text))
}
/// `BigInt.prototype.toLocaleString`:返回与 toString() 相同的十进制字符串。
pub fn bigint_to_locale_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
bigint_to_string(vm, args)
}
/// `BigInt.prototype.valueOf`:返回包装的 BigInt 原始值。
pub fn bigint_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
match this_bigint_value(vm, this_val) {
Ok(v) => NativeResult::Ok(v),
Err(e) => NativeResult::Err(e),
}
}
/// `BigInt.asIntN(bits, bigint)`:ToIndex(bits) + ToBigInt(bigint),
/// 结果按二进制补码截断为 bits 位有符号整数。
pub fn bigint_as_int_n<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let bits = match to_index(vm, args) {
Ok(b) => b,
Err(e) => return NativeResult::Err(e),
};
let arg = bigint_arg(vm, args);
let val = match to_bigint(vm, arg) {
Ok(v) => v,
Err(e) => return NativeResult::Err(e),
};
let v = vm.bigint_value(val);
if bits == 0 {
return NativeResult::Ok(vm.new_bigint(0));
}
if bits >= 127 {
// i128 表示范围内 `2^bits` 恒大于 |v|:取模后符号位折叠回原值。
return NativeResult::Ok(val);
}
let modulus = 1i128 << bits;
let half = 1i128 << (bits - 1);
let mut m = v % modulus;
if m < 0 {
m += modulus;
}
if m >= half {
m -= modulus;
}
NativeResult::Ok(vm.new_bigint(m))
}
/// `BigInt.asUintN(bits, bigint)`:按 `2^bits` 取模的无符号截断。
pub fn bigint_as_uint_n<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let bits = match to_index(vm, args) {
Ok(b) => b,
Err(e) => return NativeResult::Err(e),
};
let arg = bigint_arg(vm, args);
let val = match to_bigint(vm, arg) {
Ok(v) => v,
Err(e) => return NativeResult::Err(e),
};
let v = vm.bigint_value(val);
if bits == 0 {
return NativeResult::Ok(vm.new_bigint(0));
}
if bits >= 128 {
// i128 上限:非负值取模不变;负值结果超出表示范围,待任意精度补齐。
return NativeResult::Ok(val);
}
let modulus = 1i128 << bits;
let mut m = v % modulus;
if m < 0 {
m += modulus;
}
NativeResult::Ok(vm.new_bigint(m))
}
/// thisBigIntValue:BigInt 原样返回;包装对象解盒;其它类型 TypeError。
fn this_bigint_value<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<JsValue, JsValue> {
if this_val.is_bigint() {
return NativeResult::Ok(this_val);
return Ok(this_val);
}
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
@@ -54,78 +187,198 @@ pub fn bigint_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let obj = unsafe { &*ptr };
if obj.proto().is_object() {
let proto_ptr = obj.proto().as_js_object_ptr();
let bigint_proto = vm.session().builtin_world().bigint_proto.as_ptr() as *mut oxide_types::object::JsObject;
let bigint_proto =
vm.session().builtin_world().bigint_proto.as_ptr() as *mut oxide_types::object::JsObject;
if !proto_ptr.is_null() && std::ptr::eq(proto_ptr, bigint_proto) {
let v = obj.get_prop_at(0);
if v.is_bigint() {
return NativeResult::Ok(v);
return Ok(v);
}
}
}
}
}
NativeResult::Err(crate::error::create_type_error(
Err(crate::error::create_type_error(
vm,
"BigInt.prototype.valueOf called on incompatible receiver",
"BigInt.prototype method called on incompatible receiver",
))
}
/// 把原始值转为 BigInt 值(`BigInt(value)` 的参数转换)。
fn to_bigint_value<H: VmHost>(vm: &mut H, val: JsValue) -> Result<JsValue, JsValue> {
/// ToNumber 且拒绝 Symbol/BigInt(ToIntegerOrInfinity 的 ToNumber 语义)。
fn coerce_number_or_throw<H: VmHost>(vm: &mut H, value: JsValue) -> Result<f64, JsValue> {
let prim = match to_primitive(value, ToPrimitiveHint::Number, vm) {
Ok(p) => p,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::error::create_type_error(vm, "Cannot convert value to a number"));
}
};
if prim.is_symbol() || prim.is_bigint() {
return Err(crate::error::create_type_error(
vm,
"Cannot convert a Symbol or BigInt value to a number",
));
}
Ok(oxide_runtime_api::to_number(prim))
}
/// ToIndex(§7.1.17):ToIntegerOrInfinity 后必须为有限且 `[0, 2^53-1]` 内的整数。
fn to_index<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<usize, JsValue> {
// ToIndex 作用在第一个参数(bits)上;缺省为 undefined。
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let n = coerce_number_or_throw(vm, value)?;
if n.is_nan() {
return Ok(0);
}
// ToIntegerOrInfinity 先截断(-0.9 → -0,不越界);再检查有限且 [0, 2^53-1]。
let int = if n == 0.0 { 0.0 } else if n.is_infinite() { n } else { n.trunc() };
if !(0.0..=9007199254740991.0).contains(&int) {
return Err(crate::error::create_range_error(vm, "Invalid index"));
}
Ok(int as usize)
}
/// 取 asIntN/asUintN 的第二个参数;缺省为 undefined。
fn bigint_arg<H: VmHost>(vm: &mut H, args: &[u8]) -> JsValue {
if args.len() > 2 {
vm.reg(args[2])
} else {
JsValue::undefined()
}
}
/// ToBigInt 抽象操作(§7.1.14):BigInt 原样;String → StringToBigInt;
/// Boolean → 0/1;Number/Symbol/undefined/null → TypeError;对象先 ToPrimitive 再递归。
fn to_bigint<H: VmHost>(vm: &mut H, val: JsValue) -> Result<JsValue, JsValue> {
if val.is_bigint() {
return Ok(val);
}
if val.is_undefined() {
return Ok(vm.new_bigint(0));
}
if val.is_bool() {
return Ok(vm.new_bigint(if val.as_bool() { 1 } else { 0 }));
}
if val.is_int() {
return Ok(vm.new_bigint(val.as_int() as i128));
// ToBigInt(Number) 抛 TypeError(与构造器不同:构造器先 ToPrimitive 再
// NumberToBigInt;ToBigInt 直接拒绝 Number)。
return Err(crate::error::create_type_error(
vm,
"Cannot convert a Number value to a BigInt",
));
}
if val.is_double() {
let d = val.as_double();
if d.is_nan() || d.is_infinite() {
return Err(crate::error::create_range_error(vm, "The number cannot be converted to a BigInt because it is not an integer"));
}
if d.trunc() != d {
return Err(crate::error::create_range_error(vm, "The number cannot be converted to a BigInt because it is not an integer"));
}
return Ok(vm.new_bigint(d.trunc() as i128));
return Err(crate::error::create_type_error(
vm,
"Cannot convert a Number value to a BigInt",
));
}
if val.is_string() {
let s = unsafe { oxide_runtime_api::string_data(val) }.to_string();
let s = s.trim();
let sign = if let Some(rest) = s.strip_prefix('-') {
(true, rest)
} else if let Some(rest) = s.strip_prefix('+') {
(false, rest)
} else {
(false, s)
return match string_to_bigint(vm, &s) {
Ok(v) => Ok(v),
Err(e) => Err(e),
};
let (radix, digits) = if let Some(hex) = sign.1.strip_prefix("0x").or_else(|| sign.1.strip_prefix("0X")) {
(16u32, hex)
} else if let Some(oct) = sign.1.strip_prefix("0o").or_else(|| sign.1.strip_prefix("0O")) {
(8u32, oct)
} else if let Some(bin) = sign.1.strip_prefix("0b").or_else(|| sign.1.strip_prefix("0B")) {
(2u32, bin)
} else {
(10u32, sign.1)
};
let magnitude = match i128::from_str_radix(digits, radix) {
Ok(m) => m,
}
if val.is_object() {
let prim = match to_primitive(val, ToPrimitiveHint::Default, vm) {
Ok(p) => p,
Err(_) => {
return Err(crate::error::create_syntax_error(
if let Some(exc) = vm.take_uncaught_value() {
return Err(exc);
}
return Err(crate::error::create_type_error(
vm,
"Cannot convert string to BigInt: invalid integer literal",
))
"Cannot convert value to a BigInt",
));
}
};
return Ok(vm.new_bigint(if sign.0 { -magnitude } else { magnitude }));
return to_bigint(vm, prim);
}
// undefined / null / symbol。
Err(crate::error::create_type_error(
vm,
"Cannot convert value to a BigInt",
))
}
/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀;
/// 空串 → 0n;非法 → SyntaxError。
fn string_to_bigint<H: VmHost>(vm: &mut H, s: &str) -> Result<JsValue, JsValue> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Ok(vm.new_bigint(0));
}
let (neg, rest) = if let Some(r) = trimmed.strip_prefix('-') {
(true, r)
} else if let Some(r) = trimmed.strip_prefix('+') {
(false, r)
} else {
(false, trimmed)
};
// StringIntegerLiteral:符号只允许出现在纯十进制前;0x/0o/0b 前缀前带
// +/-(如 "-0x1")属于非法语法(test262 constructor-from-string-syntax-errors)。
let (radix, digits) = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) {
if neg {
return Err(crate::error::create_syntax_error(
vm,
"Cannot convert string to BigInt: invalid integer literal",
));
}
(16u32, hex)
} else if let Some(oct) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) {
if neg {
return Err(crate::error::create_syntax_error(
vm,
"Cannot convert string to BigInt: invalid integer literal",
));
}
(8u32, oct)
} else if let Some(bin) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) {
if neg {
return Err(crate::error::create_syntax_error(
vm,
"Cannot convert string to BigInt: invalid integer literal",
));
}
(2u32, bin)
} else {
(10u32, rest)
};
if digits.is_empty() {
return Err(crate::error::create_syntax_error(
vm,
"Cannot convert string to BigInt: invalid integer literal",
));
}
match i128::from_str_radix(digits, radix) {
Ok(m) => Ok(vm.new_bigint(if neg { -m } else { m })),
Err(_) => Err(crate::error::create_syntax_error(
vm,
"Cannot convert string to BigInt: invalid integer literal",
)),
}
}
/// 把 i128 按 radix 转成字符串(含负号前缀)。
fn i128_to_radix_string(value: i128, radix: u32) -> String {
let neg = value < 0;
let mut mag = value.unsigned_abs();
let chars = b"0123456789abcdefghijklmnopqrstuvwxyz";
let mut result = String::new();
if mag == 0 {
result.push('0');
} else {
let mut digits = Vec::new();
while mag > 0 {
digits.push(chars[(mag % radix as u128) as usize] as char);
mag /= radix as u128;
}
for ch in digits.iter().rev() {
result.push(*ch);
}
}
if neg {
result.insert(0, '-');
}
result
}
+29
View File
@@ -516,6 +516,35 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
wire_ctor_proto(&world.plain_date_time_constructor, &world.plain_date_time_proto);
wire_ctor_proto(&world.bigint_constructor, &world.bigint_proto);
// 所有内置构造器的 [[Prototype]] 指向 %FunctionPrototype%(ECMA-262 §17:
// 标准内置函数对象均继承 Function.prototype;此前仅 TypedArray 构造器设置过)。
let fn_proto_val = world.fn_proto_val();
for ctor in [
&world.object_constructor,
&world.array_constructor,
&world.function_constructor,
&world.string_constructor,
&world.number_constructor,
&world.boolean_constructor,
&world.error_constructor,
&world.symbol_constructor,
&world.date_constructor,
&world.set_constructor,
&world.map_constructor,
&world.regexp_constructor,
&world.array_buffer_constructor,
&world.data_view_constructor,
&world.bigint_constructor,
&world.instant_constructor,
&world.plain_date_constructor,
&world.plain_time_constructor,
&world.duration_constructor,
&world.zoned_date_time_constructor,
&world.plain_date_time_constructor,
] {
set_proto_if_changed(ctor, fn_proto_val);
}
let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
let non_object_protos: [&P<JsObject>; 21] = [
&world.array_proto,
+25 -1
View File
@@ -2,7 +2,7 @@ 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::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
/// 把 BigInt 构造器与原型方法绑定到 global。
@@ -18,6 +18,24 @@ pub fn bind_bigint(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
let proto = unsafe { &mut *proto_ptr };
configure_native_constructor(ctor, oxide_builtins::bigint::bigint_constructor::<crate::vm::Vm> as *const (), 1);
// BigInt.length = 1,属性 { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }。
let length_si = core.perm_interner().intern("length").0;
let length_shape = core.shape_forge().make_shape(ctor.shape_id(), length_si);
ctor.set_shape_id(length_shape);
ctor.ensure_hash_props().push(JsValue::int(1));
let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
// 静态方法:asIntN / asUintN(length 均为 2)。
apply_binding_table(
world,
ctor,
core,
&[
("asIntN", oxide_builtins::bigint::bigint_as_int_n::<crate::vm::Vm> as *const (), 2),
("asUintN", oxide_builtins::bigint::bigint_as_uint_n::<crate::vm::Vm> as *const (), 2),
],
);
apply_binding_table(
world,
@@ -25,9 +43,15 @@ pub fn bind_bigint(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
core,
&[
("toString", oxide_builtins::bigint::bigint_to_string::<crate::vm::Vm> as *const (), 0),
("toLocaleString", oxide_builtins::bigint::bigint_to_locale_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));
// 全局 BigInt 属性描述符:{ writable: true, enumerable: false, configurable: true }。
let bigint_si = core.perm_interner().intern("BigInt").0;
if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), bigint_si) {
global.set_data_meta(pos, PropAttributes::new(true, false, true));
}
}