diff --git a/crates/oxide_builtins/src/temporal.rs b/crates/oxide_builtins/src/temporal.rs index 71d1d261..407fc16e 100644 --- a/crates/oxide_builtins/src/temporal.rs +++ b/crates/oxide_builtins/src/temporal.rs @@ -8160,74 +8160,17 @@ fn parse_month_day_string(input: &str) -> Result<(u32, u32), String> { parse_month_day_date_part(date_part) } -/// bag 日历字符串是否合法:接受任何 PlainMonthDay 串形态,外加 "YYYY-MM" 年月形。 -fn is_valid_month_day_calendar_string(input: &str) -> bool { - let trimmed = input.trim(); - if trimmed.contains('\u{2212}') { - return false; - } - let text = trimmed.to_owned(); - let annotation_start = text.find('[').unwrap_or(text.len()); - if validate_month_day_annotation_suffix(&text[annotation_start..]).is_err() { - return false; - } - let text = &text[..annotation_start]; - if text.contains('Z') || text.contains('z') { - return false; - } - 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).is_err() { - return false; - } - if parse_month_day_date_part(date_part).is_ok() { - return true; - } - // "YYYY-MM" 年月形(bag 日历独有宽形态)。 - let bytes = date_part.as_bytes(); - let (start, negative) = match bytes.first() { - Some(b'-') => (1usize, true), - Some(b'+') => (1usize, false), - _ => (0usize, false), - }; - let mut i = start; - while i < bytes.len() && bytes[i].is_ascii_digit() { - i += 1; - } - let digits = &date_part[start..i]; - let expected = if start > 0 { 6 } else { 4 }; - if digits.len() != expected || i >= bytes.len() || bytes[i] != b'-' || date_part.len() != i + 3 { - return false; - } - let m_s = &date_part[i + 1..]; - if !m_s.bytes().all(|b| b.is_ascii_digit()) { - return false; - } - let month: u32 = m_s.parse().unwrap_or(0); - if month == 0 || month > 12 { - return false; - } - !(negative && digits == "000000") -} - -/// bag 日历解析:undefined → iso8601;字符串(iso8601 大小写不敏感 / 合法 ISO 串原样 -/// 保留为 ID);PD/PDT/ZDT/PMD/PYM 实例 → 直读日历槽;其余对象与原始值 → TypeError。 +/// bag 日历解析:undefined → iso8601;字符串走宽松日历串解析(18-ID 白名单归一小写, +/// 合法 ISO 日期-时间串归一 iso8601,含 YYYY-MM / MM-DD 部分日期); +/// PD/PDT/ZDT/PMD/PYM 实例 → 直读日历槽;其余对象与原始值 → TypeError。 fn month_day_bag_calendar_id(vm: &mut H, value: JsValue) -> Result { if value.is_undefined() { return Ok("iso8601".to_string()); } if value.is_string() { let text = to_string(value); - if text.to_lowercase() == "iso8601" { - return Ok("iso8601".to_string()); - } - if is_valid_month_day_calendar_string(&text) { - return Ok(text); - } - return Err(crate::error::create_range_error(vm, "invalid calendar")); + return parse_temporal_calendar_string(&text) + .map_err(|_| crate::error::create_range_error(vm, "invalid calendar")); } if value.is_object() { let ptr = value.as_js_object_ptr(); @@ -8299,29 +8242,17 @@ pub fn plain_month_day_from(vm: &mut H, args: &[u8]) -> NativeResult return make_plain_month_day(vm, month, day, 1972, &calendar); } // 字段对象:Get 序 calendar → day → month → monthCode → year → era → eraYear, - // 各分量在 Get 处即转换(观测序钉死)。 + // 全部读原始值(不转换);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 day_raw = native_try!(temporal_option_value(vm, obj, value, "day")); - let day_f = match day_raw.is_undefined() { - true => None, - false => Some(native_try!(temporal_number_component(vm, day_raw))), - }; let month_raw = native_try!(temporal_option_value(vm, obj, value, "month")); - let month_f = match month_raw.is_undefined() { - true => None, - false => Some(native_try!(temporal_number_component(vm, month_raw))), - }; let month_code_raw = native_try!(temporal_option_value(vm, obj, value, "monthCode")); let month_code = match month_code_raw.is_undefined() { true => None, false => Some(native_try!(temporal_option_string(vm, month_code_raw))), }; let year_raw = native_try!(temporal_option_value(vm, obj, value, "year")); - let year = match year_raw.is_undefined() { - true => None, - false => Some(native_try!(temporal_number_component(vm, year_raw)) as i32), - }; 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)); @@ -8330,57 +8261,72 @@ pub fn plain_month_day_from(vm: &mut H, args: &[u8]) -> NativeResult return NativeResult::Err(crate::error::create_range_error(vm, "era and eraYear cannot both be present")); } // 月来源:双给 → RangeError;双缺 → TypeError(先于日存在性)。 - if month_f.is_some() && month_code.is_some() { + if !month_raw.is_undefined() && month_code.is_some() { return NativeResult::Err(crate::error::create_range_error(vm, "month and monthCode cannot both be present")); } - if month_f.is_none() && month_code.is_none() { + if month_raw.is_undefined() && month_code.is_none() { return NativeResult::Err(crate::error::create_type_error(vm, "month or monthCode is required")); } - if day_f.is_none() { + if day_raw.is_undefined() { return NativeResult::Err(crate::error::create_type_error(vm, "day is required")); } - // monthCode 两段校验:第一段语法("M"+两位)先于第二段 ISO 适配。 - let month_code_num = match &month_code { + // monthCode 两段校验:第一段语法("M"+两位数字,可选再 +"L")先于一切数值转换; + // 第二段适配(闰月后缀、1..12 范围)在 year 转换之后。 + let (month_code_num, leap_month) = match &month_code { Some(text) => { let b = text.as_bytes(); - if b.len() != 3 || b[0] != b'M' || !b[1].is_ascii_digit() || !b[2].is_ascii_digit() { + 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')) + (Some(u32::from(b[1] - b'0') * 10 + u32::from(b[2] - b'0')), b.len() == 4) } - None => None, + None => (None, false), }; - // 月定值:负值恒 RangeError;>12 时 constrain 钳 12、reject 抛错。 - let month = match (month_f, month_code_num) { - (Some(f), Some(code)) => { + // year 转换(TypeError/RangeError)在 monthCode 语法之后。 + let year = match year_raw.is_undefined() { + true => None, + false => Some(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 负值恒 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 } - (Some(f), None) => { - if f < 1.0 { + 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")); } - if f > 12.0 { - if constrain { - 12 - } else { - return NativeResult::Err(crate::error::create_range_error(vm, "invalid month")); - } - } else { - f as u32 - } + } else { + f as u32 } - (None, Some(code)) => { - if !(1..=12).contains(&code) { - return NativeResult::Err(crate::error::create_range_error(vm, "invalid monthCode")); - } - code - } - _ => unreachable!("month or monthCode is required checked above"), }; - let day_f = day_f.unwrap(); + let day_f = native_try!(temporal_number_component(vm, day_raw)); if day_f < 1.0 { return NativeResult::Err(crate::error::create_range_error(vm, "invalid day")); } @@ -8400,6 +8346,177 @@ pub fn plain_month_day_from(vm: &mut H, args: &[u8]) -> NativeResult make_plain_month_day(vm, month, day as u32, 1972, &calendar) } +/// `Temporal.PlainMonthDay.prototype.equals(other)`:比较 (月, 日, 参考年) 三元与日历标识; +/// other 经 ToTemporalMonthDay(串 / 字段对象 / 实例)转换。 +pub fn plain_month_day_equals(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_month_day(vm, obj)); + let other = match plain_month_day_from(vm, args) { + NativeResult::Ok(val) => val, + NativeResult::Err(err) => return NativeResult::Err(err), + NativeResult::TailCall { .. } => unreachable!("工厂路径不产生尾调用"), + }; + let other_ptr = other.as_js_object_ptr(); + if other_ptr.is_null() { + return NativeResult::Err(crate::error::create_type_error(vm, "incompatible receiver")); + } + let o = unsafe { &*other_ptr }; + let equal = get_double_prop(obj, 0) == get_double_prop(o, 0) + && get_double_prop(obj, 1) == get_double_prop(o, 1) + && get_double_prop(obj, 2) == get_double_prop(o, 2) + && get_calendar_id(obj, 3) == get_calendar_id(o, 3); + NativeResult::Ok(JsValue::bool(equal)) +} + +/// `Temporal.PlainMonthDay.prototype.with(monthDayLike[, options])`:覆盖 day/month/monthCode; +/// year 只参与 overflow 月长判断;undefined 不覆盖;calendar/timeZone 键拒绝; +/// 无识别字段 TypeError;overflow 读自 options(缺省 constrain)。 +/// 结果保留 receiver 日历,参考年恒 1972。 +pub fn plain_month_day_with(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_month_day(vm, obj)); + let like = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + native_try!(reject_partial_object_with_calendar_or_time_zone(vm, like)); + let lptr = like.as_js_object_ptr(); + if lptr.is_null() { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid argument")); + } + let like_obj = unsafe { &*lptr }; + // Get 序(字典序):day → month → monthCode → year;无识别字段 → TypeError。 + let day_raw = native_try!(temporal_option_value(vm, like_obj, like, "day")); + let month_raw = native_try!(temporal_option_value(vm, like_obj, like, "month")); + let month_code_raw = native_try!(temporal_option_value(vm, like_obj, like, "monthCode")); + let month_code = match month_code_raw.is_undefined() { + true => None, + false => Some(native_try!(temporal_option_string(vm, month_code_raw))), + }; + let year_raw = native_try!(temporal_option_value(vm, like_obj, like, "year")); + if [day_raw, month_raw, month_code_raw, year_raw] + .iter() + .all(|raw| raw.is_undefined()) + { + return NativeResult::Err(crate::error::create_type_error(vm, "no properties present")); + } + let constrain = native_try!(temporal_overflow(vm, args)); + // monthCode 两段校验:语法("M"+两位数字,可选 +"L")先于 year 转换; + // 适配(闰月后缀、1..12 范围)与冲突检查在 year 转换之后。 + 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), + }; + let year = match year_raw.is_undefined() { + true => None, + false => Some(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))), + }; + 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")); + } + } + // 月定值:receiver 月(槽 0)为缺省;monthCode 与数值 month 并存须一致; + // 数值 month 负值恒 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 { + match month_f { + Some(f) => { + 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 + } + } + None => get_double_prop(obj, 0) as u32, + } + }; + // 日:receiver 日(槽 1)为缺省;<1 恒 RangeError。 + let day_f = if day_raw.is_undefined() { + get_double_prop(obj, 1) + } else { + native_try!(temporal_number_component(vm, day_raw)) + }; + if day_f < 1.0 { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid day")); + } + // overflow 应用:月长按 bag year(缺省 1972)判断;constrain 钳制,reject 抛错。 + let overflow_year = year.unwrap_or(1972); + let days = days_in_month_iso(overflow_year, month) as i32; + let day_i = day_f as i32; + let day = if day_i > days { + if constrain { + days + } else { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid day")); + } + } else { + day_i + }; + let calendar = get_calendar_id(obj, 3); + make_plain_month_day(vm, month, day as u32, 1972, &calendar) +} + +/// `Temporal.PlainMonthDay.prototype.toPlainDate(yearLike)`:yearLike 为 number 或 {year}; +/// 只 Get item 的 year;overflow 恒 constrain 且不读 options; +/// constrain 钳 day、day 级范围检查后产出 PlainDate(receiver 日历)。 +pub fn plain_month_day_to_plain_date(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_month_day(vm, obj)); + let item = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if !item.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid argument")); + } + let iptr = item.as_js_object_ptr(); + if iptr.is_null() { + return NativeResult::Err(crate::error::create_type_error(vm, "invalid argument")); + } + let item_obj = unsafe { &*iptr }; + let year_raw = native_try!(temporal_option_value(vm, item_obj, item, "year")); + if year_raw.is_undefined() { + return NativeResult::Err(crate::error::create_type_error(vm, "year is required")); + } + let year = native_try!(temporal_number_component(vm, year_raw)) as i32; + let month = get_double_prop(obj, 0) as u32; + // constrain:day 钳到 (year, month) 月长。 + let day = (get_double_prop(obj, 1) as u32).min(days_in_month_iso(year, month)); + // day 级表示范围:-100_000_001..=100_000_000。 + let day_count = days_from_civil(i128::from(year), i128::from(month), i128::from(day)); + if !(-100_000_001..=100_000_000).contains(&day_count) { + return NativeResult::Err(crate::error::create_range_error(vm, "date is out of range")); + } + let calendar = get_calendar_id(obj, 3); + make_plain_date(vm, year, month, day, &calendar) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/oxide_vm/src/bindings/bind_temporal.rs b/crates/oxide_vm/src/bindings/bind_temporal.rs index bb9c9f55..a41db989 100644 --- a/crates/oxide_vm/src/bindings/bind_temporal.rs +++ b/crates/oxide_vm/src/bindings/bind_temporal.rs @@ -1021,6 +1021,17 @@ pub fn bind_temporal(core: &Arc, session: &KernelSession, global: &m oxide_builtins::temporal::plain_month_day_value_of:: as *const (), 0, ), + ( + "equals", + oxide_builtins::temporal::plain_month_day_equals:: as *const (), + 1, + ), + ("with", oxide_builtins::temporal::plain_month_day_with:: as *const (), 1), + ( + "toPlainDate", + oxide_builtins::temporal::plain_month_day_to_plain_date:: as *const (), + 1, + ), ], ); bind_well_known_data_property( diff --git a/crates/oxide_vm/tests/builtins_temporal_tests.rs b/crates/oxide_vm/tests/builtins_temporal_tests.rs index 3964e443..47ef0db7 100644 --- a/crates/oxide_vm/tests/builtins_temporal_tests.rs +++ b/crates/oxide_vm/tests/builtins_temporal_tests.rs @@ -2922,7 +2922,7 @@ fn plain_month_day_from_bag_matrix() { .unwrap(); assert_eq!( str_val(&vm, r), - "M10-1|M12-31|1972-07-03[u-ca=iso8601]|M10-1|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|RangeError|TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|RangeError|M05-2|M05-2|RangeError|M01-1|M02-28|M02-29|RangeError|M02-29|M02-29|M12-31|RangeError|RangeError|RangeError|RangeError|TypeError|TypeError|TypeError|TypeError|iso8601|iso8601|2020-01-01|M11|RangeError|RangeError|TypeError|TypeError" + "M10-1|M12-31|1972-07-03[u-ca=iso8601]|M10-1|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|RangeError|TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|RangeError|M05-2|M05-2|RangeError|M01-1|M02-28|M02-29|RangeError|M02-29|M02-29|M12-31|RangeError|RangeError|RangeError|RangeError|TypeError|TypeError|TypeError|TypeError|iso8601|iso8601|iso8601|M11|RangeError|RangeError|TypeError|TypeError" ); } @@ -2975,3 +2975,112 @@ fn plain_month_day_from_instances_and_options() { "M05-2|2000-05-02[u-ca=iso8601]|true|true|2|M10-1:1972-10-01[u-ca=iso8601]|M02-3:1|0:0|TypeError|TypeError|TypeError|TypeError|RangeError|RangeError|M02-2" ); } + +#[test] +fn plain_month_day_equals_matrix() { + let mut vm = Vm::new(); + // equals 比较 (月, 日, 参考年) 三元与日历;other 走 ToTemporalMonthDay + // (实例/串/bag/日历 ISO 串归一),坏参 TypeError/RangeError。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + const pmd = new Temporal.PlainMonthDay(5, 2); + return [ + pmd.equals(new Temporal.PlainMonthDay(5, 2)), + pmd.equals(new Temporal.PlainMonthDay(5, 2, 'iso8601', 2000)), + pmd.equals(Temporal.PlainMonthDay.from('05-02')), + pmd.equals({monthCode: 'M05', day: 2}), + pmd.equals({month: 5, day: 2}), + pmd.equals({monthCode: 'M05', day: 2, calendar: '2020-01-01'}), + pmd.equals('05-02'), + pmd.equals('05-03'), + pmd.equals(new Temporal.PlainMonthDay(5, 3, 'iso8601', 1972)), + kind(() => pmd.equals()), + kind(() => pmd.equals(1)), + kind(() => pmd.equals(1n)), + kind(() => pmd.equals('junk')), + kind(() => pmd.equals({monthCode: 'M99', day: 2})), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "true|false|true|true|true|true|true|false|false|TypeError|TypeError|TypeError|RangeError|RangeError" + ); +} + +#[test] +fn plain_month_day_with_matrix() { + let mut vm = Vm::new(); + // with 覆盖 day/month/monthCode;year 仅供 overflow 月长;undefined 不覆盖; + // 空 bag/无识别字段/非对象/Temporal 实例/calendar/timeZone 键 TypeError; + // constrain 按 bag year(缺省 1972)钳日;结果参考年恒 1972、保留 receiver 日历。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + const md = Temporal.PlainMonthDay.from('01-22'); + const f = (m) => m.monthCode + '-' + m.day; + const leap = new Temporal.PlainMonthDay(2, 29, 'iso8601', 1972); + const r2000 = new Temporal.PlainMonthDay(5, 2, 'iso8601', 2000); + return [ + f(md.with({day: 22})), f(md.with({month: 12})), f(md.with({monthCode: 'M12'})), + f(md.with({month: 12, monthCode: 'M12'})), f(md.with({monthCode: 'M12', days: 1})), + f(md.with({year: 2000})), f(md.with({day: 1, monthCode: undefined})), + kind(() => md.with({month: 12, monthCode: 'M11'})), + kind(() => md.with({})), kind(() => md.with({months: 12})), + kind(() => md.with(7)), kind(() => md.with(Temporal.PlainMonthDay.from('05-02'))), + kind(() => md.with({day: 1, calendar: 'iso8601'})), + kind(() => md.with({day: 1, timeZone: 'UTC'})), + kind(() => md.with({day: Infinity})), kind(() => md.with({month: 13}, {overflow: 'reject'})), + f(leap.with({year: -999999})), f(leap.with({year: -1000000})), + kind(() => leap.with({year: -999999}, {overflow: 'reject'})), + r2000.with({day: 3}).toString({calendarName: 'always'}), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "M01-22|M12-22|M12-22|M12-22|M12-22|M01-22|M01-1|RangeError|TypeError|TypeError|TypeError|TypeError|TypeError|TypeError|RangeError|RangeError|M02-28|M02-29|RangeError|1972-05-03[u-ca=iso8601]" + ); +} + +#[test] +fn plain_month_day_to_plain_date_matrix() { + let mut vm = Vm::new(); + // toPlainDate:yearLike number | {year}(缺 year/非对象 TypeError); + // options.overflow 绝不读取;constrain 钳日;day 级表示范围 ±100_000_00x。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + const md = Temporal.PlainMonthDay.from('01-22'); + const leap = Temporal.PlainMonthDay.from('02-29'); + return [ + md.toPlainDate({year: 2002}).toString(), + kind(() => md.toPlainDate({something: 'nothing'})), + kind(() => md.toPlainDate(12)), + md.toPlainDate({year: 12}).toString(), + kind(() => md.toPlainDate({year: Infinity})), + leap.toPlainDate({year: 2020}).toString(), + leap.toPlainDate({year: 2023}).toString(), + leap.toPlainDate({year: 2020}, {get overflow() { throw new Error('nope'); }}).toString(), + Temporal.PlainMonthDay.from('04-19').toPlainDate({year: -271821}).toString(), + kind(() => Temporal.PlainMonthDay.from('01-01').toPlainDate({year: -271821})), + Temporal.PlainMonthDay.from('09-13').toPlainDate({year: 275760}).toString(), + kind(() => Temporal.PlainMonthDay.from('09-14').toPlainDate({year: 275760})), + md.toPlainDate({year: 2002}).calendarId, + kind(() => md.toPlainDate(2002)), + md.toPlainDate({year: '2002'}).toString(), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "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" + ); +}