From 4dc475d2fa3c35a64f4e8f411fc1975b3debb357 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sat, 12 Sep 2026 05:38:24 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Temporal.PlainYearMonth.from=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=B9=B4-=E6=9C=88=E4=B8=B2=E8=A7=A3=E6=9E=90/?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=20bag/=E5=AE=9E=E4=BE=8B=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E4=B8=8E=20(=E5=B9=B4,=E6=9C=88)=20=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/temporal.rs | 264 +++++++++++++++++- crates/oxide_vm/src/bindings/bind_temporal.rs | 6 + .../oxide_vm/tests/builtins_temporal_tests.rs | 163 +++++++++++ 3 files changed, 429 insertions(+), 4 deletions(-) diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index 6101e643..f3ef36b9 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -7525,9 +7525,12 @@ fn temporal_number_component(vm: &mut H, value: JsValue) -> Result bool { - !((year == -271821 && month < 4) || (year == 275760 && month > 9)) + if !(-271_821..=275_760).contains(&year) { + return false; + } + !((year == -271_821 && month < 4) || (year == 275_760 && month > 9)) } /// 构造 PlainMonthDay 实例对象(槽 0-3 = 月/日/参考年/日历 ID)。 @@ -7544,8 +7547,7 @@ fn make_plain_month_day(vm: &mut H, month: u32, day: u32, ref_year: i } /// 构造 PlainYearMonth 实例对象(槽 0-3 = 年/月/参考日/日历 ID)。 -/// from/toPlainDate 等返回新对象的成员使用;构造器走 receiver 初始化路径。 -#[expect(dead_code)] +/// from 等返回新对象的成员使用;构造器走 receiver 初始化路径。 fn make_plain_year_month(vm: &mut H, year: i32, month: u32, ref_day: u32, calendar: &str) -> NativeResult { let proto = JsValue::from_js_object(vm.session().builtin_world().plain_year_month_proto.as_ptr() as *mut JsObject); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto); @@ -8160,6 +8162,116 @@ fn parse_month_day_string(input: &str) -> Result<(u32, u32), String> { parse_month_day_date_part(date_part) } +/// 年月串边界:月 1..=12;日(若有)1..=31 且过语法级月日静态检查 +/// (2 月 30/31、30 天月份 31 拒;无参考年,2 月 29 不拒)。 +fn finish_year_month_range(year: i32, month: u32, day: Option) -> Result<(i32, u32, Option), String> { + if !(1..=12).contains(&month) { + return Err("invalid ISO month".into()); + } + if let Some(day) = day { + if !(1..=31).contains(&day) || (month == 2 && day > 29) || (matches!(month, 4 | 6 | 9 | 11) && day == 31) { + return Err("invalid ISO date".into()); + } + } + Ok((year, month, day)) +} + +/// PlainYearMonth 串的日期部分:扩展形(无符号 4 位年或带符号 6 位年)+ -MM[-DD], +/// 紧凑形(无符号 6/8 位、带符号 8/10 位纯数字);日部分可缺(年-月形); +/// 负零年(-000000)拒;返回 (year, month, day)。 +fn parse_year_month_date_part(input: &str) -> Result<(i32, u32, Option), String> { + if input.is_empty() { + return Err("invalid ISO date".into()); + } + let bytes = input.as_bytes(); + let signed = matches!(bytes[0], b'-' | b'+'); + let negative = bytes[0] == b'-'; + let mut i = usize::from(signed); + let y_start = i; + while i < bytes.len() && bytes[i].is_ascii_digit() { + i += 1; + } + let digits = &input[y_start..i]; + if negative && digits == "000000" { + return Err("invalid ISO negative zero year".into()); + } + if i < bytes.len() && bytes[i] == b'-' { + // 扩展形:无符号 4 位年或带符号 6 位年,随后 -MM[-DD]。 + let expected = usize::from(signed) * 6 + 4 * (1 - usize::from(signed)); + if digits.len() != expected { + return Err("invalid ISO year".into()); + } + let year = digits.parse::().map_err(|_| "invalid ISO year".to_string())?; + let year = if negative { -year } else { year }; + i += 1; + let month = read_iso2(input, &mut i, bytes)?; + if i >= bytes.len() { + return finish_year_month_range(year, month, None); + } + if bytes[i] != b'-' { + return Err("invalid trailing content".into()); + } + i += 1; + let day = read_iso2(input, &mut i, bytes)?; + if i != bytes.len() { + return Err("invalid trailing content".into()); + } + return finish_year_month_range(year, month, Some(day)); + } + if i != bytes.len() { + return Err("invalid trailing content".into()); + } + // 紧凑纯数字:无符号 6(年+月)/ 8(年+月+日);带符号 8 / 10(6 位年起步)。 + let len = digits.len(); + if !(matches!(len, 6 | 8) && !signed || matches!(len, 8 | 10) && signed) { + return Err("invalid ISO date".into()); + } + let month_start = if signed { 6 } else { 4 }; + let year = digits[..month_start] + .parse::() + .map_err(|_| "invalid ISO year".to_string())?; + let year = if negative { -year } else { year }; + let month = digits[month_start..month_start + 2] + .parse::() + .map_err(|_| "invalid ISO month".to_string())?; + let day = if digits.len() > month_start + 2 { + Some( + digits[month_start + 2..] + .parse::() + .map_err(|_| "invalid ISO day".to_string())?, + ) + } else { + None + }; + finish_year_month_range(year, month, day) +} + +/// 解析 PlainYearMonth ISO 串(年-月 / 年-月-日 / 完整 datetime 形态)。 +/// 返回 (year, month):日部分仅参与语法级校验,参考日恒 1(不入结果)。 +fn parse_year_month_string(input: &str) -> Result<(i32, u32), String> { + let trimmed = input.trim(); + if trimmed.contains('\u{2212}') { + return Err("variant minus sign is not valid for PlainYearMonth".into()); + } + let text = trimmed.to_owned(); + let annotation_start = text.find('[').unwrap_or(text.len()); + validate_month_day_annotation_suffix(&text[annotation_start..])?; + let text = &text[..annotation_start]; + if text.contains('Z') || text.contains('z') { + return Err("UTC designator is not valid for PlainYearMonth".into()); + } + let separator = text.find(['T', 't', ' ']); + let (date_part, time_part) = match separator { + Some(index) => (&text[..index], &text[index + 1..]), + None => (text, ""), + }; + if !time_part.is_empty() { + validate_month_day_time_suffix(time_part)?; + } + let (year, month, _) = parse_year_month_date_part(date_part)?; + Ok((year, month)) +} + /// bag 日历解析:undefined → iso8601;字符串走宽松日历串解析(18-ID 白名单归一小写, /// 合法 ISO 日期-时间串归一 iso8601,含 YYYY-MM / MM-DD 部分日期); /// PD/PDT/ZDT/PMD/PYM 实例 → 直读日历槽;其余对象与原始值 → TypeError。 @@ -8346,6 +8458,150 @@ pub fn plain_month_day_from(vm: &mut H, args: &[u8]) -> NativeResult make_plain_month_day(vm, month, day as u32, 1972, &calendar) } +/// `Temporal.PlainYearMonth.from(item[, options])`:ISO 串 / PYM 实例 / PD 实例 / 字段对象。 +/// 串路径按 (年, 月) 查表示范围(日不参与);实例分支槽直读(PYM 复制保留参考日, +/// PD 参考日恒 1);bag 路径 year 必填、day 从不读取(参考日恒 1)、 +/// monthCode 先 ToPrimitive 再两段校验(语法先于 year 转换、适配在 year 转换后)。 +pub fn plain_year_month_from(vm: &mut H, args: &[u8]) -> NativeResult { + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if value.is_string() { + // 规范顺序:先解析(坏串 RangeError 不触 options),再读 overflow,再查 (年, 月) 范围。 + let (year, month) = native_try!(parse_year_month_string(&to_string(value)) + .map_err(|_| crate::error::create_range_error(vm, "invalid ISO 8601 year-month string"))); + native_try!(temporal_overflow(vm, args)); + if !iso_year_month_within_limits(year, month) { + return NativeResult::Err(crate::error::create_range_error(vm, "ISO year-month is out of range")); + } + return make_plain_year_month(vm, year, month, 1, "iso8601"); + } + if !value.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "from() argument must be a string or an object")); + } + let ptr = value.as_js_object_ptr(); + if ptr.is_null() { + return NativeResult::Err(crate::error::create_type_error(vm, "from() argument must be a string or an object")); + } + let obj = unsafe { &*ptr }; + // 实例快路径:槽直读,不触发属性。 + if obj.is_plain_year_month_obj() { + // 实例复制:overflow 先读(坏 options 先抛)后复制槽(参考日/日历保留)。 + native_try!(temporal_overflow(vm, args)); + let calendar = get_calendar_id(obj, 3); + return make_plain_year_month( + vm, + get_double_prop(obj, 0) as i32, + get_double_prop(obj, 1) as u32, + get_double_prop(obj, 2) as u32, + &calendar, + ); + } + if obj.is_plain_date_obj() { + // PlainDate 实例:年/月槽直读,参考日恒 1(日不读),日历槽直读。 + native_try!(temporal_overflow(vm, args)); + let year = get_double_prop(obj, 0) as i32; + let month = get_double_prop(obj, 1) as u32; + let calendar = get_calendar_id(obj, 3); + return make_plain_year_month(vm, year, month, 1, &calendar); + } + // 字段对象:Get 序 calendar → year → month → monthCode → era → eraYear(day 不读); + // overflow 在全部 Get 之后读取。 + let calendar_raw = native_try!(temporal_option_value(vm, obj, value, "calendar")); + let calendar = native_try!(month_day_bag_calendar_id(vm, calendar_raw)); + let year_raw = native_try!(temporal_option_value(vm, obj, value, "year")); + let month_raw = native_try!(temporal_option_value(vm, obj, value, "month")); + let month_code_raw = native_try!(temporal_option_value(vm, obj, value, "monthCode")); + let era_raw = native_try!(temporal_option_value(vm, obj, value, "era")); + let era_year_raw = native_try!(temporal_option_value(vm, obj, value, "eraYear")); + let constrain = native_try!(temporal_overflow(vm, args)); + // era 族:双现 → RangeError;恰一个 → 忽略(ISO 无纪元体系)。 + if !era_raw.is_undefined() && !era_year_raw.is_undefined() { + return NativeResult::Err(crate::error::create_range_error(vm, "era and eraYear cannot both be present")); + } + // 存在性:year 必填(先于 monthCode 语法);month|monthCode 至少其一。 + if year_raw.is_undefined() { + return NativeResult::Err(crate::error::create_type_error(vm, "year is required")); + } + if month_raw.is_undefined() && month_code_raw.is_undefined() { + return NativeResult::Err(crate::error::create_type_error(vm, "month or monthCode is required")); + } + // monthCode ToPrimitive 语义:字符串直用;对象(含函数)经 valueOf/toString; + // 转换结果非字符串(number/bigint/boolean/null/Symbol 等)→ TypeError。 + let month_code = if month_code_raw.is_undefined() { + None + } else if month_code_raw.is_string() { + Some(to_string(month_code_raw)) + } else if month_code_raw.is_object() { + let primitive = + match oxide_runtime_api::to_primitive(month_code_raw, oxide_runtime_api::ToPrimitiveHint::String, vm) { + Ok(primitive) => primitive, + Err(error) => return NativeResult::Err(native_engine_error(vm, &error)), + }; + if !primitive.is_string() { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid monthCode")); + } + Some(to_string(primitive)) + } else { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid monthCode")); + }; + // monthCode 第一段语法("M"+两位数字,可选再 +"L")先于一切数值转换。 + let (month_code_num, leap_month) = match &month_code { + Some(text) => { + let b = text.as_bytes(); + let well_formed = b[0] == b'M' + && b.len() >= 3 + && b[1].is_ascii_digit() + && b[2].is_ascii_digit() + && (b.len() == 3 || (b.len() == 4 && b[3] == b'L')); + if !well_formed { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid monthCode")); + } + (Some(u32::from(b[1] - b'0') * 10 + u32::from(b[2] - b'0')), b.len() == 4) + } + None => (None, false), + }; + // year 转换(TypeError/RangeError)在 monthCode 语法之后。 + let year = native_try!(temporal_number_component(vm, year_raw)) as i32; + let month_f = match month_raw.is_undefined() { + true => None, + false => Some(native_try!(temporal_number_component(vm, month_raw))), + }; + // monthCode 第二段适配:闰月后缀或月值越界 → RangeError。 + if let Some(code) = month_code_num { + if leap_month || !(1..=12).contains(&code) { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid monthCode")); + } + } + // 月定值:monthCode 与数值 month 并存须一致(冲突 → RangeError); + // 数值 month <1 恒 RangeError,>12 时 constrain 钳 12、reject 抛错。 + let month = if let Some(code) = month_code_num { + if let Some(f) = month_f { + if f as u32 != code { + return NativeResult::Err(crate::error::create_range_error(vm, "month and monthCode conflict")); + } + } + code + } else { + let f = month_f.expect("month or monthCode is required checked above"); + if f < 1.0 { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid month")); + } + if f > 12.0 { + if constrain { + 12 + } else { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid month")); + } + } else { + f as u32 + } + }; + // (年, 月) 表示范围。 + if !iso_year_month_within_limits(year, month) { + return NativeResult::Err(crate::error::create_range_error(vm, "ISO year-month is out of range")); + } + make_plain_year_month(vm, year, month, 1, &calendar) +} + /// `Temporal.PlainMonthDay.prototype.equals(other)`:比较 (月, 日, 参考年) 三元与日历标识; /// other 经 ToTemporalMonthDay(串 / 字段对象 / 实例)转换。 pub fn plain_month_day_equals(vm: &mut H, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_vm/src/bindings/bind_temporal.rs b/crates/oxide_vm/src/bindings/bind_temporal.rs index a41db989..a4344e4e 100644 --- a/crates/oxide_vm/src/bindings/bind_temporal.rs +++ b/crates/oxide_vm/src/bindings/bind_temporal.rs @@ -1062,6 +1062,12 @@ pub fn bind_temporal(core: &Arc, session: &KernelSession, global: &m .map_or(0, |props| props.len() as u32) .saturating_sub(1); plain_year_month_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + apply_binding_table( + world, + plain_year_month_ctor, + core, + &[("from", oxide_builtins::temporal::plain_year_month_from:: as *const (), 1)], + ); let plain_year_month_proto_ptr = world.plain_year_month_proto.as_ptr() as *mut JsObject; let plain_year_month_proto = unsafe { &mut *plain_year_month_proto_ptr }; diff --git a/crates/oxide_vm/tests/builtins_temporal_tests.rs b/crates/oxide_vm/tests/builtins_temporal_tests.rs index 47ef0db7..83d6fc5d 100644 --- a/crates/oxide_vm/tests/builtins_temporal_tests.rs +++ b/crates/oxide_vm/tests/builtins_temporal_tests.rs @@ -3084,3 +3084,166 @@ fn plain_month_day_to_plain_date_matrix() { "2002-01-22|TypeError|TypeError|0012-01-22|RangeError|2020-02-29|2023-02-28|2020-02-29|-271821-04-19|RangeError|275760-09-13|RangeError|iso8601|TypeError|2002-01-22" ); } + +#[test] +fn plain_year_month_from_string_matrix() { + let mut vm = Vm::new(); + // 串形态矩阵:年-月 / 年-月-日 / 紧凑形 / 带符号 6 位年 / 完整 datetime(T/t/空格、 + // leap second 接受、偏移逗号小数)/ 注解规则 / 语法级月日静态检查 / + // (年, 月) 表示范围 / Z 与变体减号拒绝。 + let r = eval( + &mut vm, + "(() => { + const run = (s) => { try { const m = Temporal.PlainYearMonth.from(s); return m.year + '-' + m.month; } catch (e) { return e.constructor.name; } }; + return [ + run('1976-11'), run('1976-11-10'), run('197611'), run('19761110'), + run('+00197611'), run('-009999-11'), run('-009999-11-18T15:23:30.1'), + run('1976-11-01T09:00:00+00:00'), run('1976-11-18T152330.1+0000'), + run('1976-11-18t15:23'), run('1976-11-18 15:23'), + run('2019-12-15T15:23[UTC][u-ca=iso8601]'), + run('1976-11[u-ca=ISO8601]'), + run('2019-12-15T15:23[foo=bar]'), + run('2016-12-31T23:59:60'), + Temporal.PlainYearMonth.from('1976-11-10').toString({calendarName: 'always'}), + run('1976-11junk'), run('1976-11[u-ca=gregory]'), run('1976-11[U-CA=iso8601]'), + run('1976-11[FOO=bar]'), run('1976-11[!foo=bar]'), + run('2020-13'), run('1976-00'), run('1976-11-00'), run('2020-02-31'), run('2020-02-30'), + run('2020-02-29'), + run('+999999-01'), run('-999999-01'), run('-271821-03'), run('+275760-10'), + run('-271821-04'), run('+275760-09'), run('-271821-03-31'), run('+275760-09-30'), + run('-000000-06'), run('1976-11-18T15:23:30.12\\u221202:00'), + run('2019-10-01T09:00:00Z'), run('2022-09Z'), run('2022-09+01:00'), run('2022-09-15Z'), + run('1970-01-01T00:00:00.1234567891'), run('2019-12-15T00+00:00:00,0'), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "1976-11|1976-11|1976-11|1976-11|1976-11|-9999-11|-9999-11|1976-11|1976-11|1976-11|1976-11|2019-12|1976-11|2019-12|2016-12|1976-11-01[u-ca=iso8601]|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|2020-2|RangeError|RangeError|RangeError|RangeError|-271821-4|275760-9|RangeError|275760-9|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|2019-12" + ); +} + +#[test] +fn plain_year_month_from_bag_matrix() { + let mut vm = Vm::new(); + // bag 矩阵:year 必填 / day 从不读(参考日恒 1)/ 未知键忽略 / month 与 monthCode 等值并存、 + // monthCode ToPrimitive 语义(原始值 TypeError,对象经 toString)/ 语法先于 year 转换、 + // 适配在 year 转换后 / era 双现 / constrain 钳 12、<1 恒 RangeError / (年, 月) 范围 / 日历解析。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + const run = (f, o) => { const m = Temporal.PlainYearMonth.from(f, o); return m.year + '-' + m.month; }; + return [ + run({year: 2021, month: 7}), run({year: 2021, monthCode: 'M07'}), + run({year: 2021, month: 7, months: 12, day: 31}), + run({year: 2021, month: 11, monthCode: 'M11'}), + Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M07', day: 50}, {overflow: 'reject'}).month, + Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M02', day: 50}).toString({calendarName: 'always'}), + kind(() => Temporal.PlainYearMonth.from({})), + kind(() => Temporal.PlainYearMonth.from({month: 1})), + kind(() => Temporal.PlainYearMonth.from({year: 2021})), + kind(() => Temporal.PlainYearMonth.from({year: undefined, month: 6})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, months: 6})), + kind(() => Temporal.PlainYearMonth.from({year: 2026, monthCode: 5})), + kind(() => Temporal.PlainYearMonth.from({year: 2026, monthCode: 5n})), + kind(() => Temporal.PlainYearMonth.from({year: 2026, monthCode: false})), + kind(() => Temporal.PlainYearMonth.from({year: 2026, monthCode: Symbol()})), + kind(() => Temporal.PlainYearMonth.from({year: 2026, monthCode: null})), + kind(() => Temporal.PlainYearMonth.from({year: 2026, monthCode: {toString: () => 5}})), + run({year: 2026, monthCode: {toString: () => 'M07'}}), + kind(() => Temporal.PlainYearMonth.from({year: 2021, monthCode: 'm1'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M1'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, monthCode: 'm01'})), + kind(() => Temporal.PlainYearMonth.from({monthCode: 'L99M', year: Symbol()})), + kind(() => Temporal.PlainYearMonth.from({monthCode: 'M99L', year: Symbol()})), + kind(() => Temporal.PlainYearMonth.from({monthCode: 'M99L'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 12, monthCode: 'M11'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M00'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M13'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M05L'})), + run({year: 2021, month: 13}, {overflow: 'constrain'}), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 13}, {overflow: 'reject'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 0})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: -1}, {overflow: 'constrain'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 0.5})), + run({year: 2000, month: 5, era: 'ce'}), + run({year: 2000, month: 5, eraYear: 1}), + kind(() => Temporal.PlainYearMonth.from({year: 2000, month: 5, era: 'ce', eraYear: 1})), + run({year: -271821, month: 4}), + run({year: 275760, month: 9}), + kind(() => Temporal.PlainYearMonth.from({year: -271821, month: 3})), + kind(() => Temporal.PlainYearMonth.from({year: 275760, month: 10})), + kind(() => Temporal.PlainYearMonth.from({year: 999999, month: 5})), + kind(() => Temporal.PlainYearMonth.from({year: -999999, month: 5})), + Temporal.PlainYearMonth.from({year: 2019, monthCode: 'M06', calendar: '2020-01'}).calendarId, + kind(() => Temporal.PlainYearMonth.from({year: 2019, monthCode: 'M06', calendar: 'notacal'})), + kind(() => Temporal.PlainYearMonth.from({year: 2019, monthCode: 'M06', calendar: null})), + kind(() => Temporal.PlainYearMonth.from({year: Symbol(), month: 5})), + kind(() => Temporal.PlainYearMonth.from({year: NaN, month: 5})), + kind(() => Temporal.PlainYearMonth.from({year: Infinity, month: 5})), + kind(() => Temporal.PlainYearMonth.from(1)), + kind(() => Temporal.PlainYearMonth.from()), + Temporal.PlainYearMonth.from.length, + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "2021-7|2021-7|2021-7|2021-11|7|2021-02-01[u-ca=iso8601]|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|2026-7|RangeError|RangeError|RangeError|RangeError|TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|2021-12|RangeError|RangeError|RangeError|RangeError|2000-5|2000-5|RangeError|-271821-4|275760-9|RangeError|RangeError|RangeError|RangeError|iso8601|RangeError|TypeError|TypeError|RangeError|RangeError|TypeError|TypeError|1" + ); +} + +#[test] +fn plain_year_month_from_instances_and_options() { + let mut vm = Vm::new(); + // PYM 实例复制(参考日/日历保留、非同一对象、overflow 先于复制读取)/ PD 实例 + // (参考日恒 1,日不读)/ 坏 options 形态(null/true/1n/Symbol TypeError、 + // 坏 overflow RangeError)/ 函数与 undefined options 归默认 constrain / + // 坏串与原始值在读 options 前抛。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + const orig = new Temporal.PlainYearMonth(2000, 5, 'iso8601', 7); + const copy = Temporal.PlainYearMonth.from(orig); + const pdCopy = Temporal.PlainYearMonth.from(Temporal.PlainDate.from('1976-11-18')); + let optsSeen = []; + const options = { get overflow() { optsSeen.push(1); return 'constrain'; } }; + const a = Temporal.PlainYearMonth.from({year: 2021, monthCode: 'M01'}, options); + let numSeen = []; + const numOptions = { get overflow() { numSeen.push(1); return 'constrain'; } }; + kind(() => Temporal.PlainYearMonth.from(7, numOptions)); + let strSeen = []; + const strOptions = { get overflow() { strSeen.push(1); return 'constrain'; } }; + kind(() => Temporal.PlainYearMonth.from('2020-13', strOptions)); + let instSeen = []; + const instOptions = { get overflow() { instSeen.push(1); return 'constrain'; } }; + kind(() => Temporal.PlainYearMonth.from(orig, instOptions)); + return [ + copy.year + '-' + copy.month + ':' + copy.toString({calendarName: 'always'}), + copy !== orig, + Temporal.PlainYearMonth.from(orig, {overflow: 'reject'}).year, + pdCopy.year + '-' + pdCopy.month + ':' + pdCopy.toString({calendarName: 'always'}), + a.year + '-' + a.month + ':' + optsSeen.length, + numSeen.length + ':' + strSeen.length + ':' + instSeen.length, + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 7}, null)), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 7}, true)), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 7}, 1n)), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 7}, Symbol())), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 7}, {overflow: 'CONSTRAIN'})), + kind(() => Temporal.PlainYearMonth.from({year: 2021, month: 7}, {overflow: 'reject\\0'})), + Temporal.PlainYearMonth.from({year: 2000, month: 13}, () => {}).month, + Temporal.PlainYearMonth.from({year: 2000, month: 13}, undefined).month, + Temporal.PlainYearMonth.from({year: 2000, month: 13}, {overflow: undefined}).month, + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "2000-5:2000-05-07[u-ca=iso8601]|true|2000|1976-11:1976-11-01[u-ca=iso8601]|2021-1:1|0:0:1|TypeError|TypeError|TypeError|TypeError|RangeError|RangeError|12|12|12" + ); +}