diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index d176476b..eeed8f0f 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -4494,9 +4494,20 @@ pub fn plain_date_constructor(vm: &mut H, args: &[u8]) -> NativeResul "Class constructor Temporal.PlainDate cannot be invoked without 'new'", )); } - let year = if args.len() > 1 { to_number(vm.reg(args[1])).trunc() as i32 } else { 0 }; - let month = if args.len() > 2 { to_number(vm.reg(args[2])).trunc() as u32 } else { 1 }; - let day = if args.len() > 3 { to_number(vm.reg(args[3])).trunc() as u32 } else { 1 }; + // 分量转换序:year → month → day;缺参归 undefined 经 number-only 路径 + // 统一落 RangeError(日期分量无缺省值,缺参不是合法分量)。 + let year = native_try!(temporal_number_component( + vm, + if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }, + )) as i32; + let month = native_try!(temporal_number_component( + vm, + if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }, + )) as u32; + let day = native_try!(temporal_number_component( + vm, + if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() }, + )) as u32; if !valid_iso_date(year, month, day) { return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date")); } @@ -4604,19 +4615,22 @@ pub fn plain_time_constructor(vm: &mut H, args: &[u8]) -> NativeResul "Class constructor Temporal.PlainTime cannot be invoked without 'new'", )); } - let get = |i: usize, default: f64| -> f64 { - if args.len() > i { - to_number(vm.reg(args[i])).trunc() - } else { - default + // 时间分量:缺参或显式 undefined 取默认 0(undefined 是合法分量,与 PD + // 缺参抛错相反);其余经 number-only 路径(Symbol/BigInt → TypeError, + // NaN/±Inf/不可解析串 → RangeError)。 + let mut get = |i: usize| -> Result { + let raw = if args.len() > i { vm.reg(args[i]) } else { JsValue::undefined() }; + if raw.is_undefined() { + return Ok(0.0); } + temporal_number_component(vm, raw) }; - let hour = get(1, 0.0) as u32; - let minute = get(2, 0.0) as u32; - let second = get(3, 0.0) as u32; - let ms = get(4, 0.0) as u32; - let us = get(5, 0.0) as u32; - let ns = get(6, 0.0) as u32; + let hour = native_try!(get(1)) as u32; + let minute = native_try!(get(2)) as u32; + let second = native_try!(get(3)) as u32; + let ms = native_try!(get(4)) as u32; + let us = native_try!(get(5)) as u32; + let ns = native_try!(get(6)) as u32; if !valid_plain_time(hour, minute, second, ms, us, ns) { return NativeResult::Err(crate::error::create_range_error(vm, "invalid time component")); } diff --git a/crates/oxide_vm/tests/builtins_temporal_tests.rs b/crates/oxide_vm/tests/builtins_temporal_tests.rs index 0eb9bd5a..e3e4afb0 100644 --- a/crates/oxide_vm/tests/builtins_temporal_tests.rs +++ b/crates/oxide_vm/tests/builtins_temporal_tests.rs @@ -3739,3 +3739,80 @@ fn zdt_start_of_day_range_and_direct_read_prop_shape() { "true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true|true" ); } + +// -- PlainDate / PlainTime 构造器数字分量转换面 -- + +#[test] +fn plain_date_ctor_number_component_rejects() { + let mut vm = Vm::new(); + // 红形钉:BigInt/Symbol 分量 → TypeError;NaN/不可解析串/±Inf → RangeError; + // 缺参(全缺与部分缺)→ RangeError;非 new 调用 → TypeError。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + return [ + kind(() => new Temporal.PlainDate(2020n, 1, 1)), + kind(() => new Temporal.PlainDate(Symbol(), 1, 1)), + kind(() => new Temporal.PlainDate(undefined, 1, 1)), + kind(() => new Temporal.PlainDate(2020, 'invalid', 1)), + kind(() => new Temporal.PlainDate(Infinity, 1, 1)), + kind(() => new Temporal.PlainDate(2020, 1, -Infinity)), + kind(() => new Temporal.PlainDate()), + kind(() => new Temporal.PlainDate(2021)), + kind(() => Temporal.PlainDate(2020, 1, 1)), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|TypeError" + ); +} + +#[test] +fn plain_date_ctor_truncation_and_green_forms() { + let mut vm = Vm::new(); + // 绿形钉:小数截断、字符串数字经 ToNumber、缺省日历 iso8601。 + let r = eval( + &mut vm, + "(() => { + return [ + new Temporal.PlainDate(2020.6, 11.7, 24.1).toString(), + new Temporal.PlainDate('2020', 1, 1).toString(), + new Temporal.PlainDate(2020, 2, 29).toString(), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "2020-11-24|2020-01-01|2020-02-29"); +} + +#[test] +fn plain_time_ctor_number_component_rejects_and_undefined_default() { + let mut vm = Vm::new(); + // 红形钉:BigInt/Symbol → TypeError;不可解析串/±Inf → RangeError。 + // 绿形钉:显式 undefined 与缺参时间分量取默认 0(*-undefined.js 现绿面不翻红)。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + return [ + kind(() => new Temporal.PlainTime(1n, 0, 0)), + kind(() => new Temporal.PlainTime(Symbol(), 0, 0)), + kind(() => new Temporal.PlainTime(0, 0, 0, 0, 0, 'invalid')), + kind(() => new Temporal.PlainTime(Infinity, 0, 0)), + kind(() => new Temporal.PlainTime(0, 0, -Infinity)), + new Temporal.PlainTime(undefined).hour, + new Temporal.PlainTime(1, undefined, undefined, 1, undefined, undefined).toString(), + new Temporal.PlainTime(2, undefined).toString(), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "TypeError|TypeError|RangeError|RangeError|RangeError|0|01:00:00.001|02:00:00" + ); +}