feat: Duration 构造器逐字段强转与负零归一

This commit is contained in:
2026-10-15 16:46:45 +00:00
parent 0af2db45b2
commit 5d4ed8908c
4 changed files with 74 additions and 25 deletions
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]]
name = "oxide_fold"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"num-bigint",
"oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"bumpalo",
"dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"tracing",
"tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]]
name = "oxide_net"
version = "0.19.19"
version = "0.19.20"
[[package]]
name = "oxide_parser"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"num-bigint",
"num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"bumpalo",
"dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.19.19"
version = "0.19.20"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "0.19.19"
version = "0.19.20"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+26 -3
View File
@@ -1,7 +1,7 @@
//! Temporal.Duration:ISO 字符串与 property bag 解析、构造、getter、total/round、
//! 加减比较与格式化。
use oxide_runtime_api::{to_number, to_string, NativeResult, ProtoKind, VmHost};
use oxide_runtime_api::{to_number, to_number_full, to_string, NativeResult, ProtoKind, VmHost};
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
@@ -264,6 +264,18 @@ pub(crate) fn duration_like_values<H: VmHost>(vm: &mut H, val: JsValue) -> Resul
}
/// `Temporal.Duration` 构造器,保存十个整数时长分量。
///
/// # 步骤
/// 1. 校验构造调用(非 `new` 形态抛 TypeError)。
/// 2. 按年、月、周、日、时、分、秒、毫秒、微秒、纳秒顺序逐字段转换:
/// undefined 视为 0;BigInt 抛 TypeError;其余经完整 ToNumber
/// (对象走 valueOf 路径,Symbol 抛 TypeError);NaN、非整数、
/// 非有限值抛 RangeError。
/// 3. 共享校验器做符号一致性与范围检查。
///
/// # 注意事项
/// 零分量归一为 +0:规范经整数往返(取整再还原为 Number),
/// 传入 −0 时 getter 仍返回 +0。
pub fn duration_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let ctor_proto = vm.builtin_proto(ProtoKind::DurationProto);
if !is_ctor_call(vm, args, ctor_proto) {
@@ -277,11 +289,22 @@ pub fn duration_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
if args.len() <= index + 1 {
continue;
}
let number = to_number(vm.reg(args[index + 1]));
let arg = vm.reg(args[index + 1]);
if arg.is_undefined() {
continue;
}
if arg.is_bigint() {
return NativeResult::Err(crate::error::create_type_error(vm, "cannot convert duration field to number"));
}
let number = match to_number_full(arg, vm) {
Ok(value) => value,
Err(error) => return NativeResult::Err(native_engine_error(vm, &error)),
};
if !number.is_finite() || number.fract() != 0.0 {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid duration"));
}
*value = number;
// 零分量归一为 +0:规范整数往返(取整再还原 Number)使 −0 落为 +0。
*value = if number == 0.0 { 0.0 } else { number };
}
native_try!(validate_duration_values(vm, &values));
initialize_temporal_receiver(vm, args, JsObject::OBJ_TYPE_DURATION, values.map(JsValue::float))
@@ -574,6 +574,32 @@ fn duration_constructor_exposes_components() {
assert_eq!(str_val(&vm, r), "P1Y2M3W4D");
}
#[test]
fn duration_constructor_coerces_fields_per_spec() {
let mut vm = Vm::new();
// undefined 分量视为 0
let r = eval(&mut vm, "new Temporal.Duration(undefined).toString()").unwrap();
assert_eq!(str_val(&vm, r), "PT0S");
let r = eval(&mut vm, "new Temporal.Duration(1, undefined).toString()").unwrap();
assert_eq!(str_val(&vm, r), "P1Y");
// Symbol 与 BigInt 抛 TypeError
let r = eval(&mut vm, "try { new Temporal.Duration(Symbol()) } catch (e) { e.constructor.name }").unwrap();
assert_eq!(str_val(&vm, r), "TypeError");
let r = eval(&mut vm, "try { new Temporal.Duration(0n) } catch (e) { e.constructor.name }").unwrap();
assert_eq!(str_val(&vm, r), "TypeError");
// 对象经 valueOf 转换
assert_eq!(num(&mut vm, "new Temporal.Duration({valueOf(){return 1}}).years"), 1.0);
// ±Infinity 抛 RangeError
let r = eval(&mut vm, "try { new Temporal.Duration(Infinity) } catch (e) { e.constructor.name }").unwrap();
assert_eq!(str_val(&vm, r), "RangeError");
let r = eval(&mut vm, "try { new Temporal.Duration(-Infinity) } catch (e) { e.constructor.name }").unwrap();
assert_eq!(str_val(&vm, r), "RangeError");
// 传入 −0 时 getter 返回 +0(整数往返归一)
assert!(eval(&mut vm, "Object.is(new Temporal.Duration(-0).years, 0)")
.unwrap()
.as_bool());
}
#[test]
fn duration_from_validates_property_bags_and_preserves_precision() {
let mut vm = Vm::new();