diff --git a/crates/oxide_builtins/src/bigint.rs b/crates/oxide_builtins/src/bigint.rs index 4ff6d6b2..fc9e2f52 100644 --- a/crates/oxide_builtins/src/bigint.rs +++ b/crates/oxide_builtins/src/bigint.rs @@ -2,23 +2,17 @@ use num_bigint::BigInt; use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost}; use oxide_types::value::JsValue; -/// JS `BigInt(value)` 构造逻辑: -/// - `new BigInt()` 抛 TypeError(BigInt 不可 new) -/// - 无参 → `0n` -/// - 其余参数先 ToPrimitive(number),Number 走 NumberToBigInt,其余走 ToBigInt。 +/// JS `BigInt(value)` 构造逻辑(§21.2.1.1): +/// - new 形态无条件抛 TypeError(BigInt 不可 new); +/// - 无参走 ToBigInt(undefined) → TypeError; +/// - Number 参数走 NumberToBigInt(非整数抛 RangeError,幅值无 2^53 上界), +/// 其余参数走 ToBigInt。 pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { - if args.len() > 1 { - let this_val = vm.reg(args[0]); - if this_val.is_object() { - // new 语义:BigInt 不可 new,抛 TypeError。 - return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor")); - } + if vm.constructing_native() { + return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor")); } - // 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。 - if args.len() <= 1 { - return NativeResult::Ok(vm.new_bigint(BigInt::from(0))); - } - let val = vm.reg(args[1]); + // 无参 BigInt() 等价于 ToBigInt(undefined) → TypeError,与显式 undefined 同路。 + let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; // ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString // 抛出的异常原样传播,且装箱转换的异常只触发一次。 let prim = match to_primitive(val, ToPrimitiveHint::Number, vm) { @@ -42,7 +36,7 @@ pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { "The number cannot be converted to a BigInt because it is not an integer", )); } - return NativeResult::Ok(vm.new_bigint(BigInt::from(d.trunc() as i128))); + return NativeResult::Ok(vm.new_bigint(integer_double_to_bigint(d))); } // 步骤 4:其它原始类型走 ToBigInt。 match to_bigint(vm, prim) { @@ -51,6 +45,29 @@ pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { } } +/// NumberToBigInt 步骤 3:整数 double 的精确值 ℤ(x)。 +/// |x| < 2^53 由尾数右移指数位直转;|x| ≥ 2^53 位分解(尾数左移 exp-1075 位), +/// 全程精确无饱和。-0 返回 0(BigInt 无负零)。 +fn integer_double_to_bigint(d: f64) -> BigInt { + let bits = d.to_bits(); + let negative = bits >> 63 != 0; + let exp_field = (bits >> 52) & 0x7FF; + let significand = bits & 0xF_FFFF_FFFF_FFFF; + if exp_field == 0 { + return BigInt::from(0); + } + let mag = if exp_field < 1075 { + BigInt::from((0x10_0000_0000_0000u64 + significand) >> (1075 - exp_field)) + } else { + BigInt::from(0x10_0000_0000_0000u64 + significand) << (exp_field - 1075) + }; + if negative { + -mag + } else { + mag + } +} + /// `BigInt.prototype.toString(radix)`:thisBigIntValue 后按 radix 输出。 pub fn bigint_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); diff --git a/crates/oxide_builtins/tests/bigint_constructor_tests.rs b/crates/oxide_builtins/tests/bigint_constructor_tests.rs new file mode 100644 index 00000000..24e17895 --- /dev/null +++ b/crates/oxide_builtins/tests/bigint_constructor_tests.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + let mut vm = Vm::new(); + vm.run(&Arc::new(module)) +} + +/// 求值一个应得布尔结果的表达式并断言其类型。 +fn eval_bool(source: &str) -> bool { + let result = eval(source).expect("eval should succeed"); + assert!(result.is_bool(), "expected boolean result, got {result:?}"); + result.as_bool() +} + +// --- Number 参数:NumberToBigInt 精确值 --- + +/// 1e300 的 double 精确值(f64 位分解的整数部分),非十进制 10^300。 +const ONE_E300: &str = "1000000000000000052504760255204420248704468581108159154915854115511802457988908195786371375080447864043704443832883878176942523235360430575644792184786706982848387200926575803737830233794788090059368953234970799945081119038967640880074652742780142494579258788820056842838115669472196386865459400540160"; + +#[test] +fn double_1e300_exact_value() { + assert!(eval_bool(&format!("BigInt(1e300) === {ONE_E300}n"))); + assert!(eval_bool(&format!("BigInt(-1e300) === -{ONE_E300}n"))); +} + +#[test] +fn double_two_pow_53_exact_value() { + assert!(eval_bool("BigInt(9007199254740992) === 9007199254740992n")); + assert!(eval_bool("BigInt(9007199254740991.0) === 9007199254740991n")); +} + +#[test] +fn double_between_two_pow_67_exact_value() { + assert!(eval_bool("BigInt(2.5e20) === 250000000000000000000n")); + assert!(eval_bool("BigInt(3500000000000000.0) === 3500000000000000n")); +} + +#[test] +fn double_negative_zero_is_zero_bigint() { + assert!(eval_bool("BigInt(-0.0) === 0n")); +} + +// --- 构造形态:new 无条件 TypeError --- + +#[test] +fn new_bigint_no_arg_throws_type_error() { + assert!(eval_bool("try { new BigInt(); 0 } catch (e) { e instanceof TypeError }")); +} + +#[test] +fn new_bigint_with_value_throws_type_error() { + assert!(eval_bool("try { new BigInt(1); 0 } catch (e) { e instanceof TypeError }")); +} + +// --- 无参:ToBigInt(undefined) → TypeError --- + +#[test] +fn no_arg_throws_type_error() { + assert!(eval_bool("try { BigInt(); 0 } catch (e) { e instanceof TypeError }")); +} + +#[test] +fn explicit_undefined_throws_type_error() { + assert!(eval_bool("try { BigInt(undefined); 0 } catch (e) { e instanceof TypeError }")); +} + +// --- 字符串参数:数字分隔符非法 --- + +#[test] +fn decimal_digit_separator_syntax_error() { + assert!(eval_bool("try { BigInt(\"1_0\"); 0 } catch (e) { e instanceof SyntaxError }")); +} + +#[test] +fn hex_digit_separator_syntax_error() { + assert!(eval_bool("try { BigInt(\"0x1_F\"); 0 } catch (e) { e instanceof SyntaxError }")); +} + +#[test] +fn plain_string_forms_unaffected() { + assert!(eval_bool("BigInt(\"10\") === 10n")); + assert!(eval_bool("BigInt(\"0x1F\") === 31n")); + assert!(eval_bool("BigInt(\" 0o17 \") === 15n")); +} diff --git a/crates/oxide_runtime_api/src/coercion.rs b/crates/oxide_runtime_api/src/coercion.rs index cc13f32e..d9d0044e 100644 --- a/crates/oxide_runtime_api/src/coercion.rs +++ b/crates/oxide_runtime_api/src/coercion.rs @@ -960,7 +960,16 @@ fn string_to_bigint_full(host: &mut H, s: &str) -> Result