diff --git a/Cargo.lock b/Cargo.lock index ae7d19fccb..2a4d53881f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3201,7 +3201,7 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "temporal_rs" version = "0.0.2" -source = "git+https://github.com/boa-dev/temporal.git?rev=ec2f2d00294ee641285ec1570df6683ecd0d1a8e#ec2f2d00294ee641285ec1570df6683ecd0d1a8e" +source = "git+https://github.com/boa-dev/temporal.git?rev=c658ac7db4701822cc179d04b56bb9c8fb7e954c#c658ac7db4701822cc179d04b56bb9c8fb7e954c" dependencies = [ "bitflags 2.6.0", "icu_calendar", diff --git a/Cargo.toml b/Cargo.toml index 2a8478a18d..2e287af24d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ exclude = [ [workspace.package] edition = "2021" version = "0.18.0" -rust-version = "1.74.0" +rust-version = "1.79.0" authors = ["boa-dev"] repository = "https://github.com/boa-dev/boa" license = "Unlicense OR MIT" @@ -115,7 +115,7 @@ intrusive-collections = "0.9.6" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.1" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "ec2f2d00294ee641285ec1570df6683ecd0d1a8e" } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "c658ac7db4701822cc179d04b56bb9c8fb7e954c" } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.9.0" diff --git a/core/engine/src/builtins/mod.rs b/core/engine/src/builtins/mod.rs index 17e723ce5d..2a40c0819a 100644 --- a/core/engine/src/builtins/mod.rs +++ b/core/engine/src/builtins/mod.rs @@ -284,7 +284,6 @@ impl Realm { #[cfg(feature = "temporal")] { - temporal::TimeZone::init(self); temporal::Temporal::init(self); temporal::Now::init(self); temporal::Instant::init(self); @@ -295,7 +294,6 @@ impl Realm { temporal::PlainMonthDay::init(self); temporal::PlainYearMonth::init(self); temporal::ZonedDateTime::init(self); - temporal::Calendar::init(self); } } } diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index 489e8f0163..11b5a4a049 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -2,1024 +2,18 @@ use std::str::FromStr; -use super::{ - create_temporal_date, create_temporal_duration, create_temporal_month_day, - create_temporal_year_month, fields, options::TemporalUnitGroup, PlainDate, PlainDateTime, - PlainMonthDay, PlainYearMonth, ZonedDateTime, -}; -use crate::{ - builtins::{ - iterable::IteratorHint, - options::{get_option, get_options_object}, - temporal, Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, - }, - context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - js_string, - object::internal_methods::get_prototype_from_constructor, - property::Attribute, - realm::Realm, - string::StaticJsStrings, - Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, -}; -use boa_gc::{custom_trace, Finalize, Trace}; -use boa_macros::js_str; -use boa_profiler::Profiler; -use temporal_rs::{ - components::calendar::{ - CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot, - CALENDAR_PROTOCOL_METHODS, - }, - options::{ArithmeticOverflow, TemporalUnit}, -}; - -mod object; - -#[cfg(test)] -mod tests; -/// The `Temporal.Calendar` object. -#[derive(Debug, Finalize, JsData)] -pub struct Calendar { - slot: CalendarSlot, -} - -unsafe impl Trace for Calendar { - custom_trace!(this, mark, { - match &this.slot { - CalendarSlot::Protocol(custom) => mark(custom), - // SAFETY: CalendarSlot::Builtin does not contain any JsValues for the gc to trace. - CalendarSlot::Builtin(_) => {} - } - }); -} - -impl Calendar { - pub(crate) fn new(slot: CalendarSlot) -> Self { - Self { slot } - } -} - -impl BuiltInObject for Calendar { - const NAME: JsString = StaticJsStrings::CALENDAR; -} - -impl IntrinsicObject for Calendar { - fn init(realm: &Realm) { - let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); - - let get_id = BuiltInBuilder::callable(realm, Self::get_id) - .name(js_string!("get Id")) - .build(); - - BuiltInBuilder::from_standard_constructor::(realm) - .property( - JsSymbol::to_string_tag(), - Self::NAME, - Attribute::CONFIGURABLE, - ) - .accessor( - js_string!("id"), - Some(get_id), - None, - Attribute::CONFIGURABLE, - ) - .static_method(Self::from, js_string!("from"), 1) - .method(Self::date_from_fields, js_string!("dateFromFields"), 2) - .method( - Self::year_month_from_fields, - js_string!("yearMonthFromFields"), - 2, - ) - .method( - Self::month_day_from_fields, - js_string!("monthDayFromFields"), - 2, - ) - .method(Self::date_add, js_string!("dateAdd"), 3) - .method(Self::date_until, js_string!("dateUntil"), 3) - .method(Self::era, js_string!("era"), 1) - .method(Self::era_year, js_string!("eraYear"), 1) - .method(Self::year, js_string!("year"), 1) - .method(Self::month, js_string!("month"), 1) - .method(Self::month_code, js_string!("monthCode"), 1) - .method(Self::day, js_string!("day"), 1) - .method(Self::day_of_week, js_string!("dayOfWeek"), 1) - .method(Self::day_of_year, js_string!("dayOfYear"), 1) - .method(Self::week_of_year, js_string!("weekOfYear"), 1) - .method(Self::year_of_week, js_string!("yearOfWeek"), 1) - .method(Self::days_in_week, js_string!("daysInWeek"), 1) - .method(Self::days_in_month, js_string!("daysInMonth"), 1) - .method(Self::days_in_year, js_string!("daysInYear"), 1) - .method(Self::months_in_year, js_string!("monthsInYear"), 1) - .method(Self::in_leap_year, js_string!("inLeapYear"), 1) - .method(Self::fields, js_string!("fields"), 1) - .method(Self::merge_fields, js_string!("mergeFields"), 2) - .method(Self::get_id, js_string!("toString"), 0) - .method(Self::get_id, js_string!("toJSON"), 0) - .build(); - } - - fn get(intrinsics: &Intrinsics) -> JsObject { - Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() - } -} - -impl BuiltInConstructor for Calendar { - const LENGTH: usize = 1; - - const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = - StandardConstructors::calendar; - - fn constructor( - new_target: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. If NewTarget is undefined, then - if new_target.is_undefined() { - // a. Throw a TypeError exception. - return Err(JsNativeError::typ() - .with_message( - "newTarget cannot be undefined when constructing a Temporal.Calendar object.", - ) - .into()); - } - - let identifier = args.get_or_undefined(0); - - // 2. If id is not a String, throw a TypeError exception. - let JsValue::String(id) = identifier else { - return Err(JsNativeError::typ() - .with_message("Calendar id must be a string.") - .into()); - }; - - // 3. If IsBuiltinCalendar(id) is false, then - // a. Throw a RangeError exception. - - // 4. Return ? CreateTemporalCalendar(id, NewTarget). - create_temporal_calendar( - CalendarSlot::::from_str(&id.to_std_string_escaped())?, - Some(new_target.clone()), - context, - ) - } -} - -impl Calendar { - fn from(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar_like = args.get_or_undefined(0); - let slot = to_temporal_calendar_slot_value(calendar_like, context)?; - create_temporal_calendar(slot, None, context) - } - - fn get_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "the this value of Calendar.prototype.id must be a Calendar object.", - ) - })?; - - Ok(JsString::from(calendar.slot.identifier(context)?.as_str()).into()) - } - - /// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4 - fn date_from_fields( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "this value of Calendar dateFromFields must be a Calendar object.", - ) - })?; - - // 3. If Type(fields) is not Object, throw a TypeError exception. - let fields = args.get_or_undefined(0); - let fields_obj = fields.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("fields parameter must be an object.") - })?; - - // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; - - // 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ». - let mut relevant_field_names = Vec::from([ - js_string!("day"), - js_string!("month"), - js_string!("monthCode"), - js_string!("year"), - ]); - - // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if calendar.slot.is_iso() { - // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). - let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); - fields::prepare_temporal_fields( - fields_obj, - &mut relevant_field_names, - &mut required_fields, - None, - false, - None, - context, - )? - // 7. Else, - } else { - // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date). - let calendar_relevant_fields = - calendar.slot.field_descriptors(CalendarFieldsType::Date)?; - // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - fields::prepare_temporal_fields( - fields_obj, - &mut relevant_field_names, - &mut Vec::new(), - Some(calendar_relevant_fields), - false, - None, - context, - )? - }; - - // 8. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option(&options, js_str!("overflow"), context)? - .unwrap_or(ArithmeticOverflow::Constrain); - - // NOTE: implement the below on the calenar itself - // 9. If calendar.[[Identifier]] is "iso8601", then - // a. Perform ? ISOResolveMonth(fields). - // b. Let result be ? ISODateFromFields(fields, overflow). - // 10. Else, - // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). - // b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). - - let result = calendar - .slot - .date_from_fields(&mut fields, overflow, context)?; - - create_temporal_date(result, None, context).map(Into::into) - } - - /// 15.8.2.2 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )` - Supercedes 12.5.5 - fn year_month_from_fields( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "this value of Calendar yearMonthFromFields must be a Calendar object.", - ) - })?; - - let fields = args.get_or_undefined(0); - let fields_obj = fields.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("fields parameter must be an object.") - })?; - - // 5. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; - - let mut relevant_field_names = Vec::from([ - js_string!("year"), - js_string!("month"), - js_string!("monthCode"), - ]); - - // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). - let mut fields = if calendar.slot.identifier(context)?.as_str() == "iso8601" { - // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). - let mut required_fields = Vec::from([js_string!("year")]); - fields::prepare_temporal_fields( - fields_obj, - &mut relevant_field_names, - &mut required_fields, - None, - false, - None, - context, - )? - } else { - // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month). - // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - - let calendar_relevant_fields = calendar - .slot - .field_descriptors(CalendarFieldsType::YearMonth)?; - fields::prepare_temporal_fields( - fields_obj, - &mut relevant_field_names, - &mut Vec::new(), - Some(calendar_relevant_fields), - false, - None, - context, - )? - - // TODO: figure out the below. Maybe a method on fields? - // c. Let firstDayIndex be the 1-based index of the first day of the month described by fields (i.e., 1 unless the month's first day is skipped by this calendar.) - // d. Perform ! CreateDataPropertyOrThrow(fields, "day", 𝔽(firstDayIndex)). - }; - - // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option::(&options, js_str!("overflow"), context)? - .unwrap_or(ArithmeticOverflow::Constrain); - - let result = calendar - .slot - .year_month_from_fields(&mut fields, overflow, context)?; - - create_temporal_year_month(result, None, context) - } - - /// 15.8.2.3 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )` - Supercedes 12.5.6 - fn month_day_from_fields( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "this value of Calendar monthDayFromFields must be a Calendar object.", - ) - })?; - - // 3. If Type(fields) is not Object, throw a TypeError exception. - let fields = args.get_or_undefined(0); - let fields_obj = fields.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("fields parameter must be an object.") - })?; - - // 4. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(1))?; - - // 5. Let relevantFieldNames be « "day", "month", "monthCode", "year" ». - let mut relevant_field_names = Vec::from([ - js_string!("day"), - js_string!("month"), - js_string!("monthCode"), - js_string!("year"), - ]); - - // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if calendar.slot.identifier(context)?.as_str() == "iso8601" { - // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). - let mut required_fields = Vec::from([js_string!("day")]); - fields::prepare_temporal_fields( - fields_obj, - &mut relevant_field_names, - &mut required_fields, - None, - false, - None, - context, - )? - // 7. Else, - } else { - // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day). - let calendar_relevant_fields = calendar - .slot - .field_descriptors(CalendarFieldsType::MonthDay)?; - // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - fields::prepare_temporal_fields( - fields_obj, - &mut relevant_field_names, - &mut Vec::new(), - Some(calendar_relevant_fields), - false, - None, - context, - )? - }; - - // 8. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option(&options, js_str!("overflow"), context)? - .unwrap_or(ArithmeticOverflow::Constrain); - - let result = calendar - .slot - .month_day_from_fields(&mut fields, overflow, context)?; - - create_temporal_month_day(result, None, context) - } - - /// 15.8.2.4 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )` - supercedes 12.5.7 - fn date_add(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - // 3. Assert: calendar.[[Identifier]] is "iso8601". - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar dateAdd must be a Calendar object.") - })?; - - // 4. Set date to ? ToTemporalDate(date). - let date_like = args.get_or_undefined(0); - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - - // 5. Set duration to ? ToTemporalDuration(duration). - let duration_like = args.get_or_undefined(1); - let duration = temporal::duration::to_temporal_duration(duration_like, context)?; - - // 6. Set options to ? GetOptionsObject(options). - let options = args.get_or_undefined(2); - let options_obj = get_options_object(options)?; - - // 7. Let overflow be ? ToTemporalOverflow(options). - let overflow = get_option(&options_obj, js_str!("overflow"), context)? - .unwrap_or(ArithmeticOverflow::Constrain); - - // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], - // duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). - let result = calendar - .slot - .date_add(&date.inner, &duration, overflow, context)?; - - create_temporal_date(result, None, context).map(Into::into) - } - - ///15.8.2.5 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )` - Supercedes 12.5.8 - fn date_until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - // 3. Assert: calendar.[[Identifier]] is "iso8601". - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar dateUntil must be a Calendar object.") - })?; - - // 4. Set one to ? ToTemporalDate(one). - let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - // 5. Set two to ? ToTemporalDate(two). - let two = temporal::plain_date::to_temporal_date(args.get_or_undefined(1), None, context)?; - - // 6. Set options to ? GetOptionsObject(options). - let options = get_options_object(args.get_or_undefined(2))?; - - // 7. Let largestUnit be ? GetTemporalUnit(options, "largestUnit", date, "auto"). - // 8. If largestUnit is "auto", set largestUnit to "day". - let largest_unit = super::options::get_temporal_unit( - &options, - js_str!("largestUnit"), - TemporalUnitGroup::Date, - None, - context, - )? - .unwrap_or(TemporalUnit::Day); - - let result = calendar - .slot - .date_until(&one.inner, &two.inner, largest_unit, context)?; - - create_temporal_duration(result, None, context).map(Into::into) - } - - /// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )` - fn era(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar era must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar - .slot - .era(&date_like, context)? - .map_or(JsValue::undefined(), |r| JsString::from(r.as_str()).into()); - - Ok(result) - } - - /// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )` - fn era_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar eraYear must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar - .slot - .era_year(&date_like, context)? - .map_or(JsValue::undefined(), JsValue::from); - - Ok(result) - } - - /// 15.8.2.8 `Temporal.Calendar.prototype.year ( temporalDateLike )` - fn year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar year must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar.slot.year(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.9 `Temporal.Calendar.prototype.month ( temporalDateLike )` - fn month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar month must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - // 3. If Type(temporalDateLike) is Object and temporalDateLike has an [[InitializedTemporalMonthDay]] internal slot, then - // 3.a. Throw a TypeError exception. - // 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then - // 4.a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike). - - let result = calendar.slot.month(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.10 `Temporal.Calendar.prototype.monthCode ( temporalDateLike )` - fn month_code(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar monthCode must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar.slot.month_code(&date_like, context)?; - - Ok(JsString::from(result.as_str()).into()) - } - - /// 15.8.2.11 `Temporal.Calendar.prototype.day ( temporalDateLike )` - fn day(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar day must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar.slot.day(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.12 `Temporal.Calendar.prototype.dayOfWeek ( dateOrDateTime )` - fn day_of_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar dayOfWeek must be a Calendar object.") - })?; - - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). - let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - - let result = calendar - .slot - .day_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; - - Ok(result.into()) - } - - /// 15.8.2.13 `Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )` - fn day_of_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar dayOfYear must be a Calendar object.") - })?; - - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). - let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - - let result = calendar - .slot - .day_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; - - Ok(result.into()) - } - - /// 15.8.2.14 `Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )` - fn week_of_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar weekOfYear must be a Calendar object.") - })?; - - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). - let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - - let result = calendar - .slot - .week_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; - - Ok(result.into()) - } - - /// 15.8.2.15 `Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )` - fn year_of_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar yearOfWeek must be a Calendar object.") - })?; - - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). - let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - - let result = calendar - .slot - .year_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; - - Ok(result.into()) - } - - /// 15.8.2.16 `Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )` - fn days_in_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar daysInWeek must be a Calendar object.") - })?; - - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). - let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - - let result = calendar - .slot - .days_in_week(&CalendarDateLike::Date(date.inner.clone()), context)?; - - Ok(result.into()) - } - - /// 15.8.2.17 `Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )` - fn days_in_month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar daysInMonth must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar.slot.days_in_month(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.18 `Temporal.Calendar.prototype.daysInYear ( temporalDateLike )` - fn days_in_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar daysInYear must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = calendar.slot.days_in_year(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.19 `Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )` - fn months_in_year( - this: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar monthsInYear must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar.slot.months_in_year(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.20 `Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )` - fn in_leap_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar inLeapYear must be a Calendar object.") - })?; - - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - - let result = calendar.slot.in_leap_year(&date_like, context)?; - - Ok(result.into()) - } - - /// 15.8.2.21 `Temporal.Calendar.prototype.fields ( fields )` - fn fields(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar Fields must be a Calendar object.") - })?; - - // Custom Calendars override the `fields` method. - if let CalendarSlot::Protocol(proto) = &calendar.slot { - // TODO: Is there a more efficient way to convert from iterable <-> Vec; - let mut iterator_record = - args.get_or_undefined(0) - .get_iterator(context, Some(IteratorHint::Sync), None)?; - let mut fields_list = Vec::default(); - - while iterator_record.step(context)? { - let next_val = iterator_record.value(context)?; - - if let JsValue::String(item) = next_val { - fields_list.push(item.to_std_string_escaped()); - } else { - // 1. Let completion be ThrowCompletion(a newly created TypeError object). - let completion = Err(JsNativeError::typ() - .with_message("field must be of type string") - .into()); - // 2. Return ? IteratorClose(iteratorRecord, completion). - return iterator_record.close(completion, context); - } - } - - let result = proto.fields(fields_list, context)?; - return Ok(Array::create_array_from_list( - result.iter().map(|s| JsString::from(s.clone()).into()), - context, - ) - .into()); - } - - // 3. Let iteratorRecord be ? GetIterator(fields, sync). - let mut iterator_record = - args.get_or_undefined(0) - .get_iterator(context, Some(IteratorHint::Sync), None)?; - - // 4. Let fieldNames be a new empty List. - let mut fields_names = Vec::new(); - - // 5. Let next be true. - // 6. Repeat, while next is not false, - while iterator_record.step(context)? { - // a. Set next to ? IteratorStep(iteratorRecord). - // b. If next is not false, then - // i. Let nextValue be ? IteratorValue(next). - let next_value = iterator_record.value(context)?; - - // ii. If Type(nextValue) is not String, then - if let JsValue::String(value) = next_value { - // iii. If fieldNames contains nextValue, then - // 1. Let completion be ThrowCompletion(a newly created RangeError object). - // 2. Return ? IteratorClose(iteratorRecord, completion). - // iv. If nextValue is not one of "year", "month", "monthCode", or "day", then - // 1. Let completion be ThrowCompletion(a newly created RangeError object). - // 2. Return ? IteratorClose(iteratorRecord, completion). - // v. Append nextValue to the end of the List fieldNames. - let this_field = value.to_std_string_escaped(); - match this_field.as_str() { - "year" | "month" | "monthCode" | "day" - if !fields_names.contains(&this_field) => - { - fields_names.push(this_field); - } - _ => { - let completion = Err(JsNativeError::range() - .with_message("Invalid field name string.") - .into()); - return iterator_record.close(completion, context); - } - } - } else { - // 1. Let completion be ThrowCompletion(a newly created TypeError object). - let completion = Err(JsNativeError::typ() - .with_message("field must be of type string") - .into()); - // 2. Return ? IteratorClose(iteratorRecord, completion). - return iterator_record.close(completion, context); - } - } - - // 7. Let result be fieldNames. - // 8. If calendar.[[Identifier]] is not "iso8601", then - if !calendar.slot.is_iso() { - // a. NOTE: Every built-in calendar preserves all input field names in output. - // b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames). - let extended_fields = calendar - .slot - .field_descriptors(CalendarFieldsType::from(&fields_names[..]))?; - // c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do - for descriptor in extended_fields { - // i. Append desc.[[Property]] to result. - fields_names.push(descriptor.0); - } - } - - // 9. Return CreateArrayFromList(result). - Ok(Array::create_array_from_list( - fields_names - .iter() - .map(|s| JsString::from(s.clone()).into()), - context, - ) - .into()) - } - - /// 15.8.2.22 `Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )` - fn merge_fields(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let calendar be the this value. - // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let calendar = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("this value of Calendar mergeFields must be a Calendar object.") - })?; - - let fields = args.get_or_undefined(0).to_object(context)?; - let additional_fields = args.get_or_undefined(1).to_object(context)?; - - // 3. Let fieldsCopy be ? SnapshotOwnProperties(? ToObject(fields), null, « », « undefined »). - let fields_copy = fields::object_to_temporal_fields(&fields, context)?; - - // 4. Let additionalFieldsCopy be ? SnapshotOwnProperties(? ToObject(additionalFields), null, « », « undefined »). - let additional_copy = fields::object_to_temporal_fields(&additional_fields, context)?; - - // Custom Calendars override the `fields` method. - if let CalendarSlot::Protocol(proto) = &calendar.slot { - let result = proto.merge_fields(&fields_copy, &additional_copy, context)?; // TBD - return JsObject::from_temporal_fields(&result, context).map(Into::into); - } - - // 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, - // but some property keys may be Symbols. - // 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]](). - // 7. If calendar.[[Identifier]] is "iso8601", then - // a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys). - // 8. Else, - // a. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys). - // 9. Let merged be OrdinaryObjectCreate(null). - // 10. NOTE: The following steps ensure that property iteration order of merged - // matches that of fields as modified by omitting overridden properties and - // appending non-overlapping properties from additionalFields in iteration order. - // 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]](). - // 12. For each element key of fieldsKeys, do - // a. Let propValue be undefined. - // b. If overriddenKeys contains key, then - // i. Set propValue to ! Get(additionalFieldsCopy, key). - // c. Else, - // i. Set propValue to ! Get(fieldsCopy, key). - // d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue). - let merged = fields_copy.merge_fields(&additional_copy, &calendar.slot)?; - - // 13. Perform ! CopyDataProperties(merged, additionalFieldsCopy, « »). - // 14. Return merged. - JsObject::from_temporal_fields(&merged, context).map(Into::into) - } -} +use super::extract_from_temporal_type; +use crate::{js_str, Context, JsNativeError, JsObject, JsResult, JsValue}; +use temporal_rs::components::calendar::Calendar; // -- `Calendar` Abstract Operations -- -/// 12.2.1 `CreateTemporalCalendar ( identifier [ , newTarget ] )` -pub(crate) fn create_temporal_calendar( - identifier: CalendarSlot, - new_target: Option, - context: &mut Context, -) -> JsResult { - // 1. Assert: IsBuiltinCalendar(identifier) is true. - // 2. If newTarget is not provided, set newTarget to %Temporal.Calendar%. - let new_target = new_target.unwrap_or_else(|| { - context - .realm() - .intrinsics() - .constructors() - .calendar() - .constructor() - .into() - }); - - // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Calendar.prototype%", « [[InitializedTemporalCalendar]], [[Identifier]] »). - let proto = - get_prototype_from_constructor(&new_target, StandardConstructors::calendar, context)?; - - let obj = JsObject::from_proto_and_data(proto, Calendar::new(identifier)); - - // 4. Set object.[[Identifier]] to the ASCII-lowercase of identifier. - // 5. Return object. - Ok(obj.into()) -} - -fn extract_from_temporal_type( - object: &JsObject, - date_f: DF, - datetime_f: DTF, - year_month_f: YMF, - month_day_f: MDF, - zoned_datetime_f: ZDTF, -) -> JsResult> -where - DF: FnOnce(JsObject) -> JsResult>, - DTF: FnOnce(JsObject) -> JsResult>, - YMF: FnOnce(JsObject) -> JsResult>, - MDF: FnOnce(JsObject) -> JsResult>, - ZDTF: FnOnce(JsObject) -> JsResult>, -{ - if let Ok(date) = object.clone().downcast::() { - return date_f(date); - } else if let Ok(dt) = object.clone().downcast::() { - return datetime_f(dt); - } else if let Ok(ym) = object.clone().downcast::() { - return year_month_f(ym); - } else if let Ok(md) = object.clone().downcast::() { - return month_day_f(md); - } else if let Ok(dt) = object.clone().downcast::() { - return zoned_datetime_f(dt); - } - - Ok(None) -} - /// 12.2.21 `GetTemporalCalendarSlotValueWithISODefault ( item )` #[allow(unused)] pub(crate) fn get_temporal_calendar_slot_value_with_default( item: &JsObject, context: &mut Context, -) -> JsResult> { +) -> JsResult { // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // a. Return item.[[Calendar]]. if let Some(calendar) = extract_from_temporal_type( @@ -1041,19 +35,16 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( let calendar_like = item.get(js_str!("calendar"), context)?; // 3. Return ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). - to_temporal_calendar_slot_value(&calendar_like, context) + to_temporal_calendar_slot_value(&calendar_like) } /// `12.2.20 ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )` -pub(crate) fn to_temporal_calendar_slot_value( - calendar_like: &JsValue, - context: &mut Context, -) -> JsResult> { +pub(crate) fn to_temporal_calendar_slot_value(calendar_like: &JsValue) -> JsResult { // 1. If temporalCalendarLike is undefined and default is present, then // a. Assert: IsBuiltinCalendar(default) is true. // b. Return default. if calendar_like.is_undefined() { - return Ok(CalendarSlot::default()); + return Ok(Calendar::default()); // 2. If Type(temporalCalendarLike) is Object, then } else if let Some(calendar_like) = calendar_like.as_object() { // a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then @@ -1068,17 +59,6 @@ pub(crate) fn to_temporal_calendar_slot_value( )? { return Ok(calendar); } - - // TODO: implement ObjectImplementsTemporalCalendarProtocol - // b. If ? ObjectImplementsTemporalCalendarProtocol(temporalCalendarLike) is false, throw a TypeError exception. - if !object_implements_calendar_protocol(calendar_like, context) { - return Err(JsNativeError::typ() - .with_message("CalendarLike does not implement the CalendarProtocol.") - .into()); - } - - // c. Return temporalCalendarLike. - return Ok(CalendarSlot::Protocol(calendar_like.clone())); } // 3. If temporalCalendarLike is not a String, throw a TypeError exception. @@ -1091,43 +71,5 @@ pub(crate) fn to_temporal_calendar_slot_value( // 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike). // 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception. // 6. Return the ASCII-lowercase of identifier. - Ok(CalendarSlot::::from_str( - &calendar_id.to_std_string_escaped(), - )?) -} - -fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut Context) -> bool { - CALENDAR_PROTOCOL_METHODS.into_iter().all(|method| { - calendar_like - .__has_property__(&JsString::from(method).into(), &mut context.into()) - .unwrap_or(false) - }) -} - -/// Utility function for taking a `JsValue` and converting it to a temporal library `CalendarDateLike` enum. -fn to_calendar_date_like( - date_like: &JsValue, - context: &mut Context, -) -> JsResult> { - let Some(obj) = date_like.as_object() else { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - - return Ok(CalendarDateLike::Date(date.inner.clone())); - }; - - let Some(date_like) = extract_from_temporal_type( - obj, - |d| Ok(Some(CalendarDateLike::CustomDate(d))), - |dt| Ok(Some(CalendarDateLike::CustomDateTime(dt))), - |ym| Ok(Some(CalendarDateLike::CustomYearMonth(ym))), - |_| Ok(None), - |_| Ok(None), - )? - else { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - - return Ok(CalendarDateLike::Date(date.inner.clone())); - }; - - Ok(date_like) + Ok(Calendar::from_str(&calendar_id.to_std_string_escaped())?) } diff --git a/core/engine/src/builtins/temporal/calendar/object.rs b/core/engine/src/builtins/temporal/calendar/object.rs deleted file mode 100644 index c50e63b230..0000000000 --- a/core/engine/src/builtins/temporal/calendar/object.rs +++ /dev/null @@ -1,860 +0,0 @@ -//! Boa's implementation of a user-defined Anonymous Calendar. - -use crate::{ - builtins::{ - iterable::IteratorHint, - temporal::{ - fields::object_to_temporal_fields, plain_date, plain_date_time, plain_month_day, - plain_year_month, - }, - Array, - }, - property::PropertyKey, - Context, JsObject, JsString, JsValue, -}; - -use boa_macros::js_str; -use num_traits::ToPrimitive; -use plain_date::PlainDate; -use plain_date_time::PlainDateTime; -use plain_month_day::PlainMonthDay; -use plain_year_month::PlainYearMonth; -use temporal_rs::{ - components::{ - calendar::{CalendarDateLike, CalendarProtocol}, - Date, Duration, MonthDay, YearMonth, - }, - options::ArithmeticOverflow, - TemporalError, TemporalFields, TemporalResult, TinyAsciiStr, -}; - -impl CalendarProtocol for JsObject { - type Date = JsObject; - type DateTime = JsObject; - type YearMonth = JsObject; - type MonthDay = JsObject; - type Context = Context; - fn date_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut Context, - ) -> TemporalResult> { - let method = self - .get(js_str!("dateFromFields"), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let fields = JsObject::from_temporal_fields(fields, context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let overflow_obj = JsObject::with_null_proto(); - - overflow_obj - .create_data_property_or_throw( - js_str!("overflow"), - JsString::from(overflow.to_string()), - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let value = method - .as_callable() - .ok_or_else(|| { - TemporalError::general("dateFromFields must be implemented as a callable method.") - })? - .call( - &self.clone().into(), - &[fields.into(), overflow_obj.into()], - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { - TemporalError::r#type() - .with_message("datefromFields must return a valid PlainDate object.") - })?; - - let pd = obj.downcast_ref::().ok_or_else(|| { - TemporalError::r#type().with_message("Object returned was not a PlainDate") - })?; - - Ok(pd.inner.clone()) - } - - fn year_month_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut Context, - ) -> TemporalResult> { - let method = self - .get(js_str!("yearMonthFromFields"), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let fields = JsObject::from_temporal_fields(fields, context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let overflow_obj = JsObject::with_null_proto(); - - overflow_obj - .create_data_property_or_throw( - js_str!("overflow"), - JsString::from(overflow.to_string()), - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let value = method - .as_callable() - .ok_or_else(|| { - TemporalError::general( - "yearMonthFromFields must be implemented as a callable method.", - ) - })? - .call( - &self.clone().into(), - &[fields.into(), overflow_obj.into()], - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { - TemporalError::r#type() - .with_message("yearMonthFromFields must return a valid PlainYearMonth object.") - })?; - - let ym = obj.downcast_ref::().ok_or_else(|| { - TemporalError::r#type().with_message("Object returned was not a PlainDate") - })?; - - Ok(ym.inner.clone()) - } - - fn month_day_from_fields( - &self, - fields: &mut TemporalFields, - overflow: ArithmeticOverflow, - context: &mut Context, - ) -> TemporalResult> { - let method = self - .get(js_str!("yearMonthFromFields"), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let fields = JsObject::from_temporal_fields(fields, context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let overflow_obj = JsObject::with_null_proto(); - - overflow_obj - .create_data_property_or_throw( - js_str!("overflow"), - JsString::from(overflow.to_string()), - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let value = method - .as_callable() - .ok_or_else(|| { - TemporalError::general( - "yearMonthFromFields must be implemented as a callable method.", - ) - })? - .call( - &self.clone().into(), - &[fields.into(), overflow_obj.into()], - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let obj = value.as_object().map(JsObject::borrow).ok_or_else(|| { - TemporalError::r#type() - .with_message("yearMonthFromFields must return a valid PlainYearMonth object.") - })?; - - let md = obj.downcast_ref::().ok_or_else(|| { - TemporalError::r#type().with_message("Object returned was not a PlainDate") - })?; - - Ok(md.inner.clone()) - } - - fn date_add( - &self, - _date: &Date, - _duration: &Duration, - _overflow: ArithmeticOverflow, - _context: &mut Context, - ) -> TemporalResult> { - // TODO - Err(TemporalError::general("Not yet implemented.")) - } - - fn date_until( - &self, - _one: &Date, - _two: &Date, - _largest_unit: temporal_rs::options::TemporalUnit, - _context: &mut Context, - ) -> TemporalResult { - // TODO - Err(TemporalError::general("Not yet implemented.")) - } - - fn era( - &self, - _: &CalendarDateLike, - _: &mut Context, - ) -> TemporalResult>> { - // Return undefined as custom calendars do not implement -> Currently. - Ok(None) - } - - fn era_year( - &self, - _: &CalendarDateLike, - _: &mut Context, - ) -> TemporalResult> { - // Return undefined as custom calendars do not implement -> Currently. - Ok(None) - } - - fn year( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("year")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("year must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("year return must be integral.")); - } - - if number < 1f64 { - return Err(TemporalError::r#type().with_message("year return must be larger than 1.")); - } - - let result = number - .to_i32() - .ok_or_else(|| TemporalError::range().with_message("year exceeded a valid range."))?; - - Ok(result) - } - - fn month( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("month")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("month must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("month return must be integral.")); - } - - if number < 1f64 { - return Err(TemporalError::r#type().with_message("month return must be larger than 1.")); - } - - let result = number - .to_u8() - .ok_or_else(|| TemporalError::range().with_message("month exceeded a valid range."))?; - - Ok(result) - } - - fn month_code( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult> { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("monthCode")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - let JsValue::String(result) = val else { - return Err(TemporalError::r#type().with_message("monthCode return must be a String.")); - }; - - let result = TinyAsciiStr::<4>::from_str(&result.to_std_string_escaped()) - .map_err(|_| TemporalError::general("Unexpected monthCode value."))?; - - Ok(result) - } - - fn day( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("day")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("day must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("day return must be integral.")); - } - - if number < 1f64 { - return Err(TemporalError::r#type().with_message("day return must be larger than 1.")); - } - - let result = number - .to_u8() - .ok_or_else(|| TemporalError::range().with_message("day exceeded a valid range."))?; - - Ok(result) - } - - fn day_of_week( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("dayOfWeek")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("DayOfWeek must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("DayOfWeek return must be integral.")); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("DayOfWeek return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("DayOfWeek exceeded valid range.") - })?; - - Ok(result) - } - - fn day_of_year( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("dayOfYear")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("dayOfYear must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("dayOfYear return must be integral.")); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("dayOfYear return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("dayOfYear exceeded valid range.") - })?; - - Ok(result) - } - - fn week_of_year( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("weekOfYear")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("weekOfYear must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("weekOfYear return must be integral.")); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("weekOfYear return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("weekOfYear exceeded valid range.") - })?; - - Ok(result) - } - - fn year_of_week( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("yearOfWeek")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("yearOfWeek must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("yearOfWeek return must be integral.")); - } - - let result = number.to_i32().ok_or_else(|| { - TemporalError::range().with_message("yearOfWeek exceeded valid range.") - })?; - - Ok(result) - } - - fn days_in_week( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("daysInWeek")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("daysInWeek must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("daysInWeek return must be integral.")); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("daysInWeek return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("daysInWeek exceeded valid range.") - })?; - - Ok(result) - } - - fn days_in_month( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("daysInMonth")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("daysInMonth must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err( - TemporalError::r#type().with_message("daysInMonth return must be integral.") - ); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("daysInMonth return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("daysInMonth exceeded valid range.") - })?; - - Ok(result) - } - - fn days_in_year( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("daysInYear")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("daysInYear must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err(TemporalError::r#type().with_message("daysInYear return must be integral.")); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("daysInYear return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("monthsInYear exceeded valid range.") - })?; - - Ok(result) - } - - fn months_in_year( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("monthsInYear")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - // Validate the return value. - // 3. If Type(result) is not Number, throw a TypeError exception. - // 4. If IsIntegralNumber(result) is false, throw a RangeError exception. - // 5. If result < 1𝔽, throw a RangeError exception. - // 6. Return ℝ(result). - - let Some(number) = val.as_number() else { - return Err(TemporalError::r#type().with_message("monthsInYear must return a number.")); - }; - - if !number.is_finite() || number.fract() != 0.0 { - return Err( - TemporalError::r#type().with_message("monthsInYear return must be integral.") - ); - } - - if number < 1f64 { - return Err( - TemporalError::r#type().with_message("monthsInYear return must be larger than 1.") - ); - } - - let result = number.to_u16().ok_or_else(|| { - TemporalError::range().with_message("monthsInYear exceeded valid range.") - })?; - - Ok(result) - } - - fn in_leap_year( - &self, - date_like: &CalendarDateLike, - context: &mut Context, - ) -> TemporalResult { - let date_like = date_like_to_object(date_like, context)?; - - let method = self - .get(PropertyKey::from(js_str!("inLeapYear")), context) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let val = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[date_like], context) - .map_err(|err| TemporalError::general(err.to_string()))?; - - let JsValue::Boolean(result) = val else { - return Err( - TemporalError::r#type().with_message("inLeapYear must return a valid boolean.") - ); - }; - - Ok(result) - } - - fn fields(&self, fields: Vec, context: &mut Context) -> TemporalResult> { - let fields_js = Array::create_array_from_list( - fields.iter().map(|s| JsString::from(s.clone()).into()), - context, - ); - - let method = self - .get(PropertyKey::from(js_str!("fields")), context) - .expect("method must exist on an object that implements the CalendarProtocol."); - - let result = method - .as_callable() - .expect("is method") - .call(&self.clone().into(), &[fields_js.into()], context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - // validate result and map to a `Vec` - let mut iterator = result - .get_iterator(context, Some(IteratorHint::Sync), None) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let mut result = Vec::default(); - while iterator - .step(context) - .map_err(|e| TemporalError::general(e.to_string()))? - { - let next_value = iterator - .value(context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let JsValue::String(s) = next_value else { - return Err(TemporalError::r#type() - .with_message("Invalid return type in fields method implementation.")); - }; - - result.push(s.to_std_string_escaped()); - } - - Ok(result) - } - - fn merge_fields( - &self, - fields: &TemporalFields, - additional_fields: &TemporalFields, - context: &mut Context, - ) -> TemporalResult { - let fields = JsObject::from_temporal_fields(fields, context) - .map_err(|e| TemporalError::general(e.to_string()))?; - let add_fields = JsObject::from_temporal_fields(additional_fields, context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let method = self - .get(PropertyKey::from(js_str!("mergeFields")), context) - .expect("method must exist on an object that implements the CalendarProtocol."); - - let value = method - .as_callable() - .expect("is method") - .call( - &self.clone().into(), - &[fields.into(), add_fields.into()], - context, - ) - .map_err(|e| TemporalError::general(e.to_string()))?; - - let JsValue::Object(o) = value else { - return Err( - TemporalError::r#type().with_message("mergeFields did not return an object.") - ); - }; - - object_to_temporal_fields(&o, context).map_err(|e| TemporalError::general(e.to_string())) - } - - fn identifier(&self, context: &mut Context) -> TemporalResult { - let identifier = self - .__get__( - &PropertyKey::from(js_str!("id")), - self.clone().into(), - &mut context.into(), - ) - .expect("method must exist on a object that implements the CalendarProtocol."); - - let JsValue::String(s) = identifier else { - return Err(TemporalError::range().with_message("Identifier was not a string")); - }; - - Ok(s.to_std_string_escaped()) - } -} - -/// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`. -pub(crate) fn date_like_to_object( - date_like: &CalendarDateLike, - context: &mut Context, -) -> TemporalResult { - match date_like { - CalendarDateLike::Date(d) => plain_date::create_temporal_date(d.clone(), None, context) - .map_err(|e| TemporalError::general(e.to_string())) - .map(Into::into), - CalendarDateLike::DateTime(dt) => { - plain_date_time::create_temporal_datetime(dt.clone(), None, context) - .map_err(|e| TemporalError::general(e.to_string())) - .map(Into::into) - } - CalendarDateLike::CustomMonthDay(md) => Ok(md.clone().upcast().into()), - CalendarDateLike::CustomYearMonth(ym) => Ok(ym.clone().upcast().into()), - CalendarDateLike::CustomDate(pd) => Ok(pd.clone().upcast().into()), - CalendarDateLike::CustomDateTime(pdt) => Ok(pdt.clone().upcast().into()), - } -} diff --git a/core/engine/src/builtins/temporal/calendar/tests.rs b/core/engine/src/builtins/temporal/calendar/tests.rs deleted file mode 100644 index 7618d924f2..0000000000 --- a/core/engine/src/builtins/temporal/calendar/tests.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::{js_string, run_test_actions, TestAction}; - -#[test] -fn calendar_constructor() { - // TODO: Add other BuiltinCalendars - run_test_actions([TestAction::assert_eq( - "new Temporal.Calendar('iso8601').id", - js_string!("iso8601"), - )]); -} - -#[test] -fn calendar_methods() { - run_test_actions([ - TestAction::run("let iso = new Temporal.Calendar('iso8601');"), - TestAction::assert_eq("iso.inLeapYear('2020-11-20')", true), - TestAction::assert_eq("iso.daysInYear('2020-11-20')", 366), - TestAction::assert_eq("iso.daysInYear('2021-11-20')", 365), - TestAction::assert_eq("iso.monthsInYear('2021-11-20')", 12), - TestAction::assert_eq("iso.daysInWeek('2021-11-20')", 7), - ]); -} - -#[test] -fn run_custom_calendar() { - run_test_actions([ - TestAction::run( - r#"const custom = { - dateAdd() {}, - dateFromFields() {}, - dateUntil() {}, - day() {}, - dayOfWeek() {}, - dayOfYear() {}, - daysInMonth() { return 14 }, - daysInWeek() {return 6}, - daysInYear() {return 360}, - fields() {}, - id: "custom-calendar", - inLeapYear() {}, - mergeFields() {}, - month() {}, - monthCode() {}, - monthDayFromFields() {}, - monthsInYear() {}, - weekOfYear() {}, - year() {}, - yearMonthFromFields() {}, - yearOfWeek() {}, - }; - - let cal = Temporal.Calendar.from(custom); - let date = "1972-05-01"; - "#, - ), - TestAction::assert_eq("cal.id", js_string!("custom-calendar")), - TestAction::assert_eq("cal.daysInMonth(date)", 14), - TestAction::assert_eq("cal.daysInWeek(date)", 6), - TestAction::assert_eq("cal.daysInYear(date)", 360), - ]); -} diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 7c1c91adcd..100884b2b3 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -691,7 +691,6 @@ impl Duration { date: plain_relative_to.as_ref(), zdt: zoned_relative_to.as_ref(), }, - context, )?; create_temporal_duration(rounded_duration, None, context).map(Into::into) } @@ -781,26 +780,6 @@ impl Duration { // -- Duration Abstract Operations -- -/// 7.5.8 `ToTemporalDuration ( item )` -pub(crate) fn to_temporal_duration( - item: &JsValue, - context: &mut Context, -) -> JsResult { - // 1a. If Type(item) is Object - // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then - if let Some(duration) = item - .as_object() - .and_then(JsObject::downcast_ref::) - { - return Ok(duration.inner); - } - - // 2. Let result be ? ToTemporalDurationRecord(item). - let result = to_temporal_duration_record(item, context)?; - // 3. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - Ok(result) -} - /// 7.5.9 `ToTemporalDurationRecord ( temporalDurationLike )` pub(crate) fn to_temporal_duration_record( temporal_duration_like: &JsValue, diff --git a/core/engine/src/builtins/temporal/fields.rs b/core/engine/src/builtins/temporal/fields.rs deleted file mode 100644 index 08bc820161..0000000000 --- a/core/engine/src/builtins/temporal/fields.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! A Rust native implementation of the `fields` object used in `Temporal`. - -use std::str::FromStr; - -use crate::{ - js_string, object::internal_methods::InternalMethodContext, property::PropertyKey, - value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue, -}; - -use rustc_hash::FxHashSet; - -use temporal_rs::fields::{FieldConversion, FieldValue, TemporalFields}; - -use super::{to_integer_with_truncation, to_positive_integer_with_trunc}; - -// TODO: Move extended and required fields into the temporal library? -/// `PrepareTemporalFeilds` -pub(crate) fn prepare_temporal_fields( - fields: &JsObject, - field_names: &mut Vec, - required_fields: &mut Vec, - extended_fields: Option>, - partial: bool, - dup_behaviour: Option, - context: &mut Context, -) -> JsResult { - // 1. If duplicateBehaviour is not present, set duplicateBehaviour to throw. - let dup_option = dup_behaviour.unwrap_or_else(|| js_string!("throw")); - - // 2. Let result be OrdinaryObjectCreate(null). - let mut result = TemporalFields::default(); - - // 3. Let any be false. - let mut any = false; - // 4. If extraFieldDescriptors is present, then - if let Some(extra_fields) = extended_fields { - for (field_name, required) in extra_fields { - // a. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do - // i. Assert: fieldNames does not contain desc.[[Property]]. - // ii. Append desc.[[Property]] to fieldNames. - field_names.push(JsString::from(field_name.clone())); - - // iii. If desc.[[Required]] is true and requiredFields is a List, then - if required && !partial { - // 1. Append desc.[[Property]] to requiredFields. - required_fields.push(JsString::from(field_name)); - } - } - } - - // 5. Let sortedFieldNames be SortStringListByCodeUnit(fieldNames). - // 6. Let previousProperty be undefined. - let mut dups_map = FxHashSet::default(); - - // 7. For each property name property of sortedFieldNames, do - for field in &*field_names { - // a. If property is one of "constructor" or "__proto__", then - if field.to_std_string_escaped().as_str() == "constructor" - || field.to_std_string_escaped().as_str() == "__proto__" - { - // i. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("constructor or proto is out of field range.") - .into()); - } - - let new_value = dups_map.insert(field); - - // b. If property is not equal to previousProperty, then - if new_value { - // i. Let value be ? Get(fields, property). - let value = fields.get(PropertyKey::from(field.clone()), context)?; - // ii. If value is not undefined, then - if !value.is_undefined() { - // 1. Set any to true. - any = true; - - // 2. If property is in the Property column of Table 17 and there is a Conversion value in the same row, then - // a. Let Conversion be the Conversion value of the same row. - - // TODO: Conversion from TemporalError -> JsError - let conversion = FieldConversion::from_str(field.to_std_string_escaped().as_str()) - .map_err(|_| JsNativeError::range().with_message("wrong field value"))?; - // b. If Conversion is ToIntegerWithTruncation, then - let converted_value = match conversion { - FieldConversion::ToIntegerWithTruncation => { - // i. Set value to ? ToIntegerWithTruncation(value). - let v = to_integer_with_truncation(&value, context)?; - // ii. Set value to 𝔽(value). - FieldValue::Integer(v) - } - // c. Else if Conversion is ToPositiveIntegerWithTruncation, then - FieldConversion::ToPositiveIntegerWithTruncation => { - // i. Set value to ? ToPositiveIntegerWithTruncation(value). - let v = to_positive_integer_with_trunc(&value, context)?; - // ii. Set value to 𝔽(value). - FieldValue::Integer(v) - } - // d. Else, - // i. Assert: Conversion is ToPrimitiveAndRequireString. - FieldConversion::ToPrimativeAndRequireString => { - // ii. NOTE: Non-primitive values are supported here for consistency with other fields, but such values must coerce to Strings. - // iii. Set value to ? ToPrimitive(value, string). - let primitive = value.to_primitive(context, PreferredType::String)?; - // iv. If value is not a String, throw a TypeError exception. - FieldValue::String(primitive.to_string(context)?.to_std_string_escaped()) - } - FieldConversion::None => { - unreachable!("todo need to implement conversion handling for tz.") - } - }; - - // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - result - .set_field_value(&field.to_std_string_escaped(), &converted_value) - .expect("FieldConversion enforces the appropriate type"); - // iii. Else if requiredFields is a List, then - } else if !partial { - // 1. If requiredFields contains property, then - if required_fields.contains(field) { - // a. Throw a TypeError exception. - return Err(JsNativeError::typ() - .with_message("A required TemporalField was not provided.") - .into()); - } - - // NOTE: flag that the value is active and the default should be used. - // 2. If property is in the Property column of Table 17, then - // a. Set value to the corresponding Default value of the same row. - // 3. Perform ! CreateDataPropertyOrThrow(result, property, value). - result.require_field(&field.to_std_string_escaped()); - } - // c. Else if duplicateBehaviour is throw, then - } else if dup_option.to_std_string_escaped() == "throw" { - // i. Throw a RangeError exception. - return Err(JsNativeError::range() - .with_message("Cannot have a duplicate field") - .into()); - } - // d. Set previousProperty to property. - } - - // 8. If requiredFields is partial and any is false, then - if partial && !any { - // a. Throw a TypeError exception. - return Err(JsNativeError::range() - .with_message("requiredFields cannot be partial when any is false") - .into()); - } - - // 9. Return result. - Ok(result) -} - -// NOTE(nekevss): The below serves as a replacement for `Snapshot` on `Calendar.prototype.mergeFields`. -// -// Some potential issues here: `Calendar.prototype.mergeFields` appears to allow extra fields that -// are not part of a `TemporalFields` record; however, the specification only calls `mergeFields` on an -// object returned by `PrepareTemporalFields`, so the translation should be fine sound. -// -// The restriction/trade-off would occur if someone wanted to include non-normative calendar fields (i.e. something -// not accounted for in the specification) in a Custom Calendar or use `Calendar.prototype.mergeFields` in -// general as a way to merge two objects. -pub(crate) fn object_to_temporal_fields( - source: &JsObject, - context: &mut Context, -) -> JsResult { - // Adapted from `CopyDataProperties` with ExcludedKeys -> << >> && ExcludedValues -> << Undefined >> - const VALID_FIELDS: [&str; 14] = [ - "year", - "month", - "monthCode", - "day", - "hour", - "minute", - "second", - "millisecond", - "microsecond", - "nanosecond", - "offset", - "timeZone", - "era", - "eraYear", - ]; - let mut copy = TemporalFields::default(); - - let keys = source.__own_property_keys__(context)?; - - for key in &keys { - let desc = source.__get_own_property__(key, &mut InternalMethodContext::new(context))?; - match desc { - // Enforce that the `PropertyKey` is a valid field here. - Some(desc) - if desc.expect_enumerable() && VALID_FIELDS.contains(&key.to_string().as_str()) => - { - let value = source.get(key.clone(), context)?; - // Below is repurposed from `PrepareTemporalFields`. - if !value.is_undefined() { - let conversion = FieldConversion::from_str(&key.to_string())?; - let converted_value = match conversion { - FieldConversion::ToIntegerWithTruncation => { - let v = to_integer_with_truncation(&value, context)?; - FieldValue::Integer(v) - } - FieldConversion::ToPositiveIntegerWithTruncation => { - let v = to_positive_integer_with_trunc(&value, context)?; - FieldValue::Integer(v) - } - FieldConversion::ToPrimativeAndRequireString => { - let primitive = value.to_primitive(context, PreferredType::String)?; - FieldValue::String( - primitive.to_string(context)?.to_std_string_escaped(), - ) - } - FieldConversion::None => { - unreachable!("todo need to implement conversion handling for tz.") - } - }; - // TODO: Test the below further and potentially expand handling. - copy.set_field_value(&key.to_string(), &converted_value) - .expect("FieldConversion enforces the appropriate type"); - } - } - _ => {} - }; - } - - Ok(copy) -} - -impl JsObject { - pub(crate) fn from_temporal_fields( - fields: &TemporalFields, - context: &mut Context, - ) -> JsResult { - let obj = JsObject::with_null_proto(); - - for (key, value) in fields.active_kvs() { - let js_value = match value { - FieldValue::Undefined => JsValue::undefined(), - FieldValue::Integer(x) => JsValue::Integer(x), - FieldValue::String(s) => JsValue::String(s.into()), - }; - - obj.create_data_property_or_throw(JsString::from(key), js_value, context)?; - } - - Ok(obj) - } -} diff --git a/core/engine/src/builtins/temporal/mod.rs b/core/engine/src/builtins/temporal/mod.rs index d8aa749aaa..1988982929 100644 --- a/core/engine/src/builtins/temporal/mod.rs +++ b/core/engine/src/builtins/temporal/mod.rs @@ -7,7 +7,6 @@ mod calendar; mod duration; mod error; -mod fields; mod instant; mod now; mod options; @@ -23,8 +22,8 @@ mod zoned_date_time; mod tests; pub use self::{ - calendar::*, duration::*, instant::*, now::*, plain_date::*, plain_date_time::*, - plain_month_day::*, plain_time::*, plain_year_month::*, time_zone::*, zoned_date_time::*, + duration::*, instant::*, now::*, plain_date::*, plain_date_time::*, plain_month_day::*, + plain_time::*, plain_year_month::*, zoned_date_time::*, }; use crate::{ @@ -242,10 +241,7 @@ pub(crate) fn _iterator_to_list_of_types( // 13.17 `ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )` // Moved to temporal_rs -type RelativeTemporalObjectResult = JsResult<( - Option>, - Option>, -)>; +type RelativeTemporalObjectResult = JsResult<(Option, Option)>; /// 13.21 `ToRelativeTemporalObject ( options )` pub(crate) fn to_relative_temporal_object( @@ -285,6 +281,7 @@ pub(crate) fn to_relative_temporal_object( /// 13.43 `ToPositiveIntegerWithTruncation ( argument )` #[inline] +#[allow(unused)] pub(crate) fn to_positive_integer_with_trunc( value: &JsValue, context: &mut Context, @@ -345,3 +342,33 @@ pub(crate) fn to_integer_if_integral(arg: &JsValue, context: &mut Context) -> Js // Note: Deviates from Proposal spec -> proto appears to be always null across the specification. // 14.7 `SnapshotOwnProperties ( source, proto [ , excludedKeys [ , excludedValues ] ] )` // Migrated or repurposed to `temporal_rs`/`fields.rs` + +fn extract_from_temporal_type( + object: &JsObject, + date_f: DF, + datetime_f: DTF, + year_month_f: YMF, + month_day_f: MDF, + zoned_datetime_f: ZDTF, +) -> JsResult> +where + DF: FnOnce(JsObject) -> JsResult>, + DTF: FnOnce(JsObject) -> JsResult>, + YMF: FnOnce(JsObject) -> JsResult>, + MDF: FnOnce(JsObject) -> JsResult>, + ZDTF: FnOnce(JsObject) -> JsResult>, +{ + if let Ok(date) = object.clone().downcast::() { + return date_f(date); + } else if let Ok(dt) = object.clone().downcast::() { + return datetime_f(dt); + } else if let Ok(ym) = object.clone().downcast::() { + return year_month_f(ym); + } else if let Ok(md) = object.clone().downcast::() { + return month_day_f(md); + } else if let Ok(dt) = object.clone().downcast::() { + return zoned_datetime_f(dt); + } + + Ok(None) +} diff --git a/core/engine/src/builtins/temporal/now.rs b/core/engine/src/builtins/temporal/now.rs index 0578244144..8dc1e16919 100644 --- a/core/engine/src/builtins/temporal/now.rs +++ b/core/engine/src/builtins/temporal/now.rs @@ -1,10 +1,7 @@ //! Boa's implementation of `Temporal.Now` ECMAScript Builtin object. use crate::{ - builtins::{ - temporal::{create_temporal_time_zone, default_time_zone}, - BuiltInBuilder, BuiltInObject, IntrinsicObject, - }, + builtins::{BuiltInBuilder, BuiltInObject, IntrinsicObject}, context::intrinsics::Intrinsics, js_string, property::Attribute, @@ -14,8 +11,9 @@ use crate::{ Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use boa_profiler::Profiler; +use temporal_rs::components::tz::TimeZone; -use super::{ns_max_instant, ns_min_instant}; +use super::{ns_max_instant, ns_min_instant, time_zone::default_time_zone}; /// JavaScript `Temporal.Now` object. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -61,13 +59,16 @@ impl Now { /// `Temporal.Now.timeZoneId ( )` /// /// More information: - /// - [ECMAScript specififcation][spec] + /// - [ECMAScript specification][spec] /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal.now.timezone #[allow(clippy::unnecessary_wraps)] fn time_zone_id(_: &JsValue, _args: &[JsValue], context: &mut Context) -> JsResult { // 1. Return ! SystemTimeZone(). - system_time_zone(context) + system_time_zone(context)? + .id() + .map(|s| JsValue::from(js_string!(s.as_str()))) + .map_err(Into::into) } /// `Temporal.Now.instant()` @@ -176,9 +177,12 @@ fn system_zoned_date_time() { /// /// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-systemtimezone #[allow(unused)] -fn system_time_zone(context: &mut Context) -> JsResult { +fn system_time_zone(context: &mut Context) -> JsResult { // 1. Let identifier be ! DefaultTimeZone(). let identifier = default_time_zone(context); // 2. Return ! CreateTemporalTimeZone(identifier). - create_temporal_time_zone(identifier, None, context) + + Err(JsNativeError::error() + .with_message("not yet implemented.") + .into()) } diff --git a/core/engine/src/builtins/temporal/options.rs b/core/engine/src/builtins/temporal/options.rs index 146c875ccd..4de73d7fd9 100644 --- a/core/engine/src/builtins/temporal/options.rs +++ b/core/engine/src/builtins/temporal/options.rs @@ -47,8 +47,9 @@ pub(crate) fn get_temporal_unit( } #[derive(Debug, Clone, Copy)] +#[allow(unused)] pub(crate) enum TemporalUnitGroup { - Date, + Date, // Need to assert if this is neede anymore with the removal of `Temporal.Calendar` Time, DateTime, } diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index 558d8121a6..a07fa7d87d 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -21,24 +21,24 @@ use boa_macros::js_str; use boa_profiler::Profiler; use temporal_rs::{ components::{ - calendar::{CalendarSlot, GetCalendarSlot}, + calendar::{Calendar, GetTemporalCalendar}, Date as InnerDate, DateTime, }, iso::IsoDateSlots, options::ArithmeticOverflow, }; -use super::{calendar, create_temporal_calendar, PlainDateTime, ZonedDateTime}; +use super::{calendar, PlainDateTime, ZonedDateTime}; /// The `Temporal.PlainDate` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types. pub struct PlainDate { - pub(crate) inner: InnerDate, + pub(crate) inner: InnerDate, } impl PlainDate { - pub(crate) fn new(inner: InnerDate) -> Self { + pub(crate) fn new(inner: InnerDate) -> Self { Self { inner } } } @@ -49,8 +49,8 @@ impl IsoDateSlots for JsObject { } } -impl GetCalendarSlot for JsObject { - fn get_calendar(&self) -> CalendarSlot { +impl GetTemporalCalendar for JsObject { + fn get_calendar(&self) -> Calendar { self.borrow().data().inner.get_calendar() } } @@ -212,7 +212,6 @@ impl IntrinsicObject for PlainDate { .method(Self::to_plain_year_month, js_string!("toPlainYearMonth"), 0) .method(Self::to_plain_month_day, js_string!("toPlainMonthDay"), 0) .method(Self::get_iso_fields, js_string!("getISOFields"), 0) - .method(Self::get_calendar, js_string!("getCalendar"), 0) .method(Self::add, js_string!("add"), 2) .method(Self::subtract, js_string!("subtract"), 2) .method(Self::with, js_string!("with"), 2) @@ -248,8 +247,7 @@ impl BuiltInConstructor for PlainDate { let iso_year = super::to_integer_with_truncation(args.get_or_undefined(0), context)?; let iso_month = super::to_integer_with_truncation(args.get_or_undefined(1), context)?; let iso_day = super::to_integer_with_truncation(args.get_or_undefined(2), context)?; - let calendar_slot = - calendar::to_temporal_calendar_slot_value(args.get_or_undefined(3), context)?; + let calendar_slot = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(3))?; let date = InnerDate::new( iso_year, @@ -275,7 +273,7 @@ impl PlainDate { JsNativeError::typ().with_message("the this object must be a PlainDate object.") })?; - Ok(JsString::from(date.inner.calendar().identifier(context)?).into()) + Ok(JsString::from(date.inner.calendar().identifier()?).into()) } /// 3.3.4 get `Temporal.PlainDate.prototype.year` @@ -284,13 +282,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_year(&date, context)?.into()) + Ok(date.inner.year()?.into()) } /// 3.3.5 get `Temporal.PlainDate.prototype.month` @@ -299,13 +297,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_month(&date, context)?.into()) + Ok(date.inner.month()?.into()) } /// 3.3.6 get Temporal.PlainDate.prototype.monthCode @@ -314,16 +312,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok( - JsString::from(InnerDate::::contextual_month_code(&date, context)?.as_str()) - .into(), - ) + Ok(JsString::from(date.inner.month_code()?.as_str()).into()) } /// 3.3.7 get `Temporal.PlainDate.prototype.day` @@ -332,13 +327,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_day(&date, context)?.into()) + Ok(date.inner.day()?.into()) } /// 3.3.8 get `Temporal.PlainDate.prototype.dayOfWeek` @@ -347,13 +342,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_day_of_week(&date, context)?.into()) + Ok(date.inner.day_of_week()?.into()) } /// 3.3.9 get `Temporal.PlainDate.prototype.dayOfYear` @@ -362,13 +357,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_day_of_year(&date, context)?.into()) + Ok(date.inner.day_of_year()?.into()) } /// 3.3.10 get `Temporal.PlainDate.prototype.weekOfYear` @@ -377,13 +372,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_week_of_year(&date, context)?.into()) + Ok(date.inner.week_of_year()?.into()) } /// 3.3.11 get `Temporal.PlainDate.prototype.yearOfWeek` @@ -392,13 +387,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_year_of_week(&date, context)?.into()) + Ok(date.inner.year_of_week()?.into()) } /// 3.3.12 get `Temporal.PlainDate.prototype.daysInWeek` @@ -407,13 +402,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_days_in_week(&date, context)?.into()) + Ok(date.inner.days_in_week()?.into()) } /// 3.3.13 get `Temporal.PlainDate.prototype.daysInMonth` @@ -426,13 +421,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_days_in_month(&date, context)?.into()) + Ok(date.inner.days_in_month()?.into()) } /// 3.3.14 get `Temporal.PlainDate.prototype.daysInYear` @@ -441,13 +436,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_days_in_year(&date, context)?.into()) + Ok(date.inner.days_in_year()?.into()) } /// 3.3.15 get `Temporal.PlainDate.prototype.monthsInYear` @@ -460,13 +455,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_months_in_year(&date, context)?.into()) + Ok(date.inner.months_in_year()?.into()) } /// 3.3.16 get `Temporal.PlainDate.prototype.inLeapYear` @@ -475,13 +470,13 @@ impl PlainDate { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDate object.") .into()); }; - Ok(InnerDate::::contextual_in_leap_year(&date, context)?.into()) + Ok(date.inner.in_leap_year()?.into()) } } @@ -506,18 +501,6 @@ impl PlainDate { .into()) } - /// 3.3.20 `Temporal.PlainDate.prototype.getCalendar ( )` - fn get_calendar(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let date = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a PlainDate object.") - })?; - - create_temporal_calendar(date.inner.calendar().clone(), None, context) - } - fn add(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { Err(JsNativeError::error() .with_message("not yet implemented.") @@ -575,7 +558,7 @@ impl PlainDate { /// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )` pub(crate) fn create_temporal_date( - inner: InnerDate, + inner: InnerDate, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { @@ -583,7 +566,7 @@ pub(crate) fn create_temporal_date( // 1. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception. // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if !DateTime::::validate(&inner) { + if !DateTime::validate(&inner) { return Err(JsNativeError::range() .with_message("Date is not within ISO date time limits.") .into()); @@ -684,7 +667,7 @@ pub(crate) fn to_temporal_date( // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). let result = date_like_string .to_std_string_escaped() - .parse::>() + .parse::() .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; Ok(PlainDate::new(result)) diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 55e4d81562..28b312ab53 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -23,7 +23,7 @@ mod tests; use temporal_rs::{ components::{ - calendar::{CalendarSlot, GetCalendarSlot}, + calendar::{Calendar, GetTemporalCalendar}, DateTime as InnerDateTime, }, iso::{IsoDate, IsoDateSlots}, @@ -33,15 +33,15 @@ use temporal_rs::{ #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types. pub struct PlainDateTime { - pub(crate) inner: InnerDateTime, + pub(crate) inner: InnerDateTime, } impl PlainDateTime { - fn new(inner: InnerDateTime) -> Self { + fn new(inner: InnerDateTime) -> Self { Self { inner } } - pub(crate) fn inner(&self) -> &InnerDateTime { + pub(crate) fn inner(&self) -> &InnerDateTime { &self.inner } } @@ -52,8 +52,8 @@ impl IsoDateSlots for JsObject { } } -impl GetCalendarSlot for JsObject { - fn get_calendar(&self) -> CalendarSlot { +impl GetTemporalCalendar for JsObject { + fn get_calendar(&self) -> Calendar { self.borrow().data().inner.get_calendar() } } @@ -325,8 +325,7 @@ impl BuiltInConstructor for PlainDateTime { .get(8) .map_or(Ok(0), |v| to_integer_with_truncation(v, context))?; // 11. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). - let calendar_slot = - calendar::to_temporal_calendar_slot_value(args.get_or_undefined(9), context)?; + let calendar_slot = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(9))?; let dt = InnerDateTime::new( iso_year, @@ -358,7 +357,7 @@ impl PlainDateTime { JsNativeError::typ().with_message("the this object must be a PlainDateTime object.") })?; - Ok(JsString::from(date.inner.calendar().identifier(context)?).into()) + Ok(JsString::from(date.inner.calendar().identifier()?).into()) } /// 5.3.4 get `Temporal.PlainDateTime.prototype.year` @@ -367,13 +366,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_year(&date, context)?.into()) + Ok(date.inner.year()?.into()) } /// 5.3.5 get `Temporal.PlainDateTime.prototype.month` @@ -382,13 +381,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_month(&date, context)?.into()) + Ok(date.inner.month()?.into()) } /// 5.3.6 get Temporal.PlainDateTime.prototype.monthCode @@ -397,16 +396,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(JsString::from( - InnerDateTime::::contextual_month_code(&date, context)?.as_str(), - ) - .into()) + Ok(JsString::from(date.inner.month_code()?.as_str()).into()) } /// 5.3.7 get `Temporal.PlainDateTime.prototype.day` @@ -415,13 +411,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_day(&date, context)?.into()) + Ok(date.inner.day()?.into()) } /// 5.3.8 get `Temporal.PlainDateTime.prototype.hour` @@ -520,13 +516,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_day_of_week(&date, context)?.into()) + Ok(date.inner.day_of_week()?.into()) } /// 5.3.15 get `Temporal.PlainDateTime.prototype.dayOfYear` @@ -535,13 +531,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_day_of_year(&date, context)?.into()) + Ok(date.inner.day_of_year()?.into()) } /// 5.3.16 get `Temporal.PlainDateTime.prototype.weekOfYear` @@ -550,13 +546,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_week_of_year(&date, context)?.into()) + Ok(date.inner.week_of_year()?.into()) } /// 5.3.17 get `Temporal.PlainDateTime.prototype.yearOfWeek` @@ -565,13 +561,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_year_of_week(&date, context)?.into()) + Ok(date.inner.year_of_week()?.into()) } /// 5.3.18 get `Temporal.PlainDateTime.prototype.daysInWeek` @@ -580,13 +576,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_days_in_week(&date, context)?.into()) + Ok(date.inner.days_in_week()?.into()) } /// 5.3.19 get `Temporal.PlainDateTime.prototype.daysInMonth` @@ -599,13 +595,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_days_in_month(&date, context)?.into()) + Ok(date.inner.days_in_month()?.into()) } /// 5.3.20 get `Temporal.PlainDateTime.prototype.daysInYear` @@ -614,13 +610,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_days_in_year(&date, context)?.into()) + Ok(date.inner.days_in_year()?.into()) } /// 5.3.21 get `Temporal.PlainDateTime.prototype.monthsInYear` @@ -633,13 +629,13 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_months_in_year(&date, context)?.into()) + Ok(date.inner.months_in_year()?.into()) } /// 5.3.22 get `Temporal.PlainDateTime.prototype.inLeapYear` @@ -648,20 +644,20 @@ impl PlainDateTime { .as_object() .ok_or_else(|| JsNativeError::typ().with_message("this must be an object."))?; - let Ok(date) = obj.clone().downcast::() else { + let Some(date) = obj.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("the this object must be a PlainDateTime object.") .into()); }; - Ok(InnerDateTime::::contextual_in_leap_year(&date, context)?.into()) + Ok(date.inner.in_leap_year()?.into()) } } // ==== `PlainDateTime` Abstract Operations` ==== pub(crate) fn create_temporal_datetime( - inner: InnerDateTime, + inner: InnerDateTime, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { diff --git a/core/engine/src/builtins/temporal/plain_date_time/tests.rs b/core/engine/src/builtins/temporal/plain_date_time/tests.rs index 4cf106bcfc..0c5883291d 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/tests.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/tests.rs @@ -3,8 +3,7 @@ use crate::{run_test_actions, TestAction}; #[test] fn pdt_year_of_week_basic() { run_test_actions([ - TestAction::run("let calendar = Temporal.Calendar.from('iso8601')"), - TestAction::run("let pdt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, calendar)"), + TestAction::run("let pdt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, 'iso8601')"), TestAction::assert_eq("pdt.yearOfWeek", 1976), ]); } diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 62e483ab2b..1c57545d0c 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -14,7 +14,7 @@ use boa_profiler::Profiler; use temporal_rs::{ components::{ - calendar::{CalendarSlot, GetCalendarSlot}, + calendar::{Calendar, GetTemporalCalendar}, DateTime, MonthDay as InnerMonthDay, }, iso::IsoDateSlots, @@ -24,11 +24,11 @@ use temporal_rs::{ #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types. pub struct PlainMonthDay { - pub(crate) inner: InnerMonthDay, + pub(crate) inner: InnerMonthDay, } impl PlainMonthDay { - fn new(inner: InnerMonthDay) -> Self { + fn new(inner: InnerMonthDay) -> Self { Self { inner } } } @@ -39,8 +39,8 @@ impl IsoDateSlots for JsObject { } } -impl GetCalendarSlot for JsObject { - fn get_calendar(&self) -> CalendarSlot { +impl GetTemporalCalendar for JsObject { + fn get_calendar(&self) -> Calendar { self.borrow().data().inner.get_calendar() } } @@ -87,13 +87,13 @@ impl BuiltInConstructor for PlainMonthDay { // ==== `PlainMonthDay` Abstract Operations ==== pub(crate) fn create_temporal_month_day( - inner: InnerMonthDay, + inner: InnerMonthDay, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. // 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if DateTime::::validate(&inner) { + if DateTime::validate(&inner) { return Err(JsNativeError::range() .with_message("PlainMonthDay is not a valid ISO date time.") .into()); diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 4b06c7ae46..fb4b89fe95 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -13,28 +13,28 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use super::calendar::to_temporal_calendar_slot_value; - use temporal_rs::{ iso::IsoDateSlots, { components::{ - calendar::{CalendarSlot, GetCalendarSlot}, + calendar::{Calendar as InnerCalendar, GetTemporalCalendar}, YearMonth as InnerYearMonth, }, options::ArithmeticOverflow, }, }; +use super::calendar; + /// The `Temporal.PlainYearMonth` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types. pub struct PlainYearMonth { - pub(crate) inner: InnerYearMonth, + pub(crate) inner: InnerYearMonth, } impl PlainYearMonth { - pub(crate) fn new(inner: InnerYearMonth) -> Self { + pub(crate) fn new(inner: InnerYearMonth) -> Self { Self { inner } } } @@ -45,8 +45,8 @@ impl IsoDateSlots for JsObject { } } -impl GetCalendarSlot for JsObject { - fn get_calendar(&self) -> CalendarSlot { +impl GetTemporalCalendar for JsObject { + fn get_calendar(&self) -> InnerCalendar { self.borrow().data().inner.get_calendar() } } @@ -193,7 +193,7 @@ impl BuiltInConstructor for PlainYearMonth { // 4. Let m be ? ToIntegerWithTruncation(isoMonth). let m = super::to_integer_with_truncation(args.get_or_undefined(1), context)?; // 5. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). - let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2), context)?; + let calendar = calendar::to_temporal_calendar_slot_value(args.get_or_undefined(2))?; // 7. Return ? CreateTemporalYearMonth(y, m, calendar, ref, NewTarget). let inner = InnerYearMonth::new(y, m, ref_day, calendar, ArithmeticOverflow::Reject)?; @@ -301,7 +301,7 @@ impl PlainYearMonth { // 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )` pub(crate) fn create_temporal_year_month( - ym: InnerYearMonth, + ym: InnerYearMonth, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { diff --git a/core/engine/src/builtins/temporal/time_zone/custom.rs b/core/engine/src/builtins/temporal/time_zone/custom.rs deleted file mode 100644 index 57aca91279..0000000000 --- a/core/engine/src/builtins/temporal/time_zone/custom.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! A custom `TimeZone` object. -use crate::{property::PropertyKey, Context, JsObject, JsValue}; - -use boa_gc::{Finalize, Trace}; -use boa_macros::js_str; -use num_bigint::BigInt; -use temporal_rs::{ - components::{tz::TzProtocol, Instant}, - TemporalError, TemporalResult, -}; - -#[derive(Debug, Clone, Trace, Finalize)] -pub(crate) struct JsCustomTimeZone { - tz: JsObject, -} - -impl TzProtocol for JsCustomTimeZone { - type Context = Context; - fn get_offset_nanos_for(&self, context: &mut Context) -> TemporalResult { - let method = self - .tz - .get(js_str!("getOffsetNanosFor"), context) - .expect("Method must exist for the custom calendar to be valid."); - - let result = method - .as_callable() - .expect("is method") - .call(&method, &[], context) - .map_err(|e| TemporalError::general(e.to_string()))?; - - // TODO (nekevss): Validate that the below conversion is fine vs. matching to JsValue::BigInt() - let Some(bigint) = result.as_bigint() else { - return Err(TemporalError::r#type() - .with_message("Expected BigInt return from getOffsetNanosFor")); - }; - - Ok(bigint.as_inner().clone()) - } - - fn get_possible_instant_for(&self, _context: &mut Context) -> TemporalResult> { - // TODO: Implement once Instant has been migrated to `boa_temporal`'s Instant. - Err(TemporalError::range().with_message("Not yet implemented.")) - } - - fn id(&self, context: &mut Context) -> TemporalResult { - let ident = self - .tz - .__get__( - &PropertyKey::from(js_str!("id")), - JsValue::undefined(), - &mut context.into(), - ) - .expect("Method must exist for the custom calendar to be valid."); - - let JsValue::String(id) = ident else { - return Err( - TemporalError::r#type().with_message("Invalid custom Time Zone identifier type.") - ); - }; - - Ok(id.to_std_string_escaped()) - } -} diff --git a/core/engine/src/builtins/temporal/time_zone/mod.rs b/core/engine/src/builtins/temporal/time_zone/mod.rs index 652d76d524..c5cd5c6b6c 100644 --- a/core/engine/src/builtins/temporal/time_zone/mod.rs +++ b/core/engine/src/builtins/temporal/time_zone/mod.rs @@ -1,279 +1,7 @@ //! Boa's implemetation of the `Temporal.TimeZone` builtin object. #![allow(dead_code)] -use crate::{ - builtins::{ - temporal::to_zero_padded_decimal_string, BuiltInBuilder, BuiltInConstructor, BuiltInObject, - IntrinsicObject, - }, - context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - js_string, - object::{internal_methods::get_prototype_from_constructor, CONSTRUCTOR}, - property::Attribute, - realm::Realm, - string::StaticJsStrings, - Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, -}; -use boa_gc::{custom_trace, Finalize, Trace}; -use boa_profiler::Profiler; -use temporal_rs::components::tz::TimeZoneSlot; - -mod custom; - -#[doc(inline)] -pub(crate) use custom::JsCustomTimeZone; - -/// The `Temporal.TimeZone` object. -#[derive(Debug, Clone, Finalize, JsData)] -pub struct TimeZone { - slot: TimeZoneSlot, -} - -unsafe impl Trace for TimeZone { - custom_trace!(this, mark, { - match &this.slot { - TimeZoneSlot::Protocol(custom) => mark(custom), - // SAFETY: No values that are exposed to gc are in TZ - TimeZoneSlot::Tz(_) => {} - } - }); -} - -impl BuiltInObject for TimeZone { - const NAME: JsString = StaticJsStrings::TIMEZONE; -} - -impl IntrinsicObject for TimeZone { - fn init(realm: &Realm) { - let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); - - let get_id = BuiltInBuilder::callable(realm, Self::get_id) - .name(js_string!("get Id")) - .build(); - - BuiltInBuilder::from_standard_constructor::(realm) - .method( - Self::get_offset_nanoseconds_for, - js_string!("getOffsetNanosecondsFor"), - 1, - ) - .method( - Self::get_offset_string_for, - js_string!("getOffsetStringFor"), - 1, - ) - .method( - Self::get_plain_date_time_for, - js_string!("getPlainDateTimeFor"), - 2, - ) - .method(Self::get_instant_for, js_string!("getInstantFor"), 2) - .method( - Self::get_possible_instants_for, - js_string!("getPossibleInstantFor"), - 1, - ) - .method( - Self::get_next_transition, - js_string!("getNextTransition"), - 1, - ) - .method( - Self::get_previous_transition, - js_string!("getPreviousTransition"), - 1, - ) - .method(Self::to_string, js_string!("toString"), 0) - .method(Self::to_string, js_string!("toJSON"), 0) - .property( - JsSymbol::to_string_tag(), - Self::NAME, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ) - .static_property( - CONSTRUCTOR, - realm.intrinsics().constructors().time_zone().prototype(), - Attribute::default(), - ) - .accessor(js_string!("id"), Some(get_id), None, Attribute::default()) - .build(); - } - - fn get(intrinsics: &Intrinsics) -> JsObject { - Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() - } -} - -impl BuiltInConstructor for TimeZone { - const LENGTH: usize = 1; - - const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = - StandardConstructors::time_zone; - - fn constructor( - new_target: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. If NewTarget is undefined, then - // 1a. Throw a TypeError exception. - if new_target.is_undefined() { - return Err(JsNativeError::typ() - .with_message("newTarget cannot be undefined for Temporal.TimeZone constructor") - .into()); - }; - - // 2. Set identifier to ? ToString(identifier). - let identifier = args.get_or_undefined(0); - if identifier.is_undefined() { - return Err(JsNativeError::range() - .with_message("Temporal.TimeZone must be called with a valid initializer") - .into()); - } - - // 3. If IsTimeZoneOffsetString(identifier) is false, then - // a. If IsAvailableTimeZoneName(identifier) is false, then - // i. Throw a RangeError exception. - // b. Set identifier to ! CanonicalizeTimeZoneName(identifier). - // 4. Return ? CreateTemporalTimeZone(identifier, NewTarget). - create_temporal_time_zone( - identifier.to_string(context)?.to_std_string_escaped(), - Some(new_target.clone()), - context, - ) - } -} - -impl TimeZone { - // NOTE: id, toJSON, toString currently share the exact same implementation -> Consolidate into one function and define multiple accesors? - pub(crate) fn get_id( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - let tz = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; - Ok(JsString::from(tz.slot.id(context)?).into()) - } - - pub(crate) fn get_offset_nanoseconds_for( - this: &JsValue, - args: &[JsValue], - _: &mut Context, - ) -> JsResult { - // 1. Let timeZone be the this value. - // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). - let _tz = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; - // 3. Set instant to ? ToTemporalInstant(instant). - let _i = args.get_or_undefined(0); - - // 4. If timeZone.[[OffsetNanoseconds]] is not undefined, return 𝔽(timeZone.[[OffsetNanoseconds]]). - // 5. Return 𝔽(GetNamedTimeZoneOffsetNanoseconds(timeZone.[[Identifier]], instant.[[Nanoseconds]])). - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - pub(crate) fn get_offset_string_for( - this: &JsValue, - args: &[JsValue], - _: &mut Context, - ) -> JsResult { - // 1. Let timeZone be the this value. - // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). - let _tz = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; - // 3. Set instant to ? ToTemporalInstant(instant). - let _i = args.get_or_undefined(0); - // TODO: to_temporal_instant is abstract operation for Temporal.Instant objects. - // let instant = to_temporal_instant(i)?; - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - - // 4. Return ? GetOffsetStringFor(timeZone, instant). - } - - pub(crate) fn get_plain_date_time_for( - _: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - pub(crate) fn get_instant_for( - _: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - pub(crate) fn get_possible_instants_for( - _: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - pub(crate) fn get_next_transition( - _: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - pub(crate) fn get_previous_transition( - _: &JsValue, - _: &[JsValue], - _: &mut Context, - ) -> JsResult { - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) - } - - pub(crate) fn to_string( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. Let timeZone be the this value. - // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). - let tz = this - .as_object() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; - // 3. Return timeZone.[[Identifier]]. - Ok(JsString::from(tz.slot.id(context)?).into()) - } -} +use crate::{builtins::temporal::to_zero_padded_decimal_string, Context}; // -- TimeZone Abstract Operations -- @@ -303,39 +31,6 @@ pub(super) fn default_time_zone(context: &mut Context) -> String { // TO-DO: full, system-aware implementation (and intl feature) } -/// Abstract operation `CreateTemporalTimeZone ( identifier [ , newTarget ] )` -/// -/// More information: -/// - [ECMAScript specififcation][spec] -/// -/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone -#[allow(clippy::needless_pass_by_value, unused)] -pub(super) fn create_temporal_time_zone( - identifier: String, - new_target: Option, - context: &mut Context, -) -> JsResult { - // 1. If newTarget is not present, set newTarget to %Temporal.TimeZone%. - let new_target = new_target.unwrap_or_else(|| { - context - .realm() - .intrinsics() - .constructors() - .time_zone() - .prototype() - .into() - }); - - // 2. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.TimeZone.prototype%", « [[InitializedTemporalTimeZone]], [[Identifier]], [[OffsetNanoseconds]] »). - let prototype = - get_prototype_from_constructor(&new_target, StandardConstructors::time_zone, context)?; - - // TODO: Migrate ISO8601 parsing to `boa_temporal` - Err(JsNativeError::error() - .with_message("not yet implemented.") - .into()) -} - /// Abstract operation `FormatTimeZoneOffsetString ( offsetNanoseconds )` fn format_time_zone_offset_string(offset_nanoseconds: i64) -> String { // 1. Assert: offsetNanoseconds is an integer. diff --git a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs index 3c0e8691d5..75c0516183 100644 --- a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -7,32 +7,15 @@ use crate::{ string::StaticJsStrings, Context, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use boa_gc::{custom_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use temporal_rs::components::{ - calendar::CalendarSlot, tz::TimeZoneSlot, Duration as TemporalDuration, - ZonedDateTime as InnerZdt, -}; - -use super::JsCustomTimeZone; +use temporal_rs::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt}; /// The `Temporal.ZonedDateTime` object. -#[derive(Debug, Clone, Finalize, JsData)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +#[boa_gc(unsafe_empty_trace)] pub struct ZonedDateTime { - pub(crate) inner: InnerZdt, -} - -unsafe impl Trace for ZonedDateTime { - custom_trace!(this, mark, { - match this.inner.calendar() { - CalendarSlot::Protocol(custom) => mark(custom), - CalendarSlot::Builtin(_) => {} - } - match this.inner.tz() { - TimeZoneSlot::Protocol(custom) => mark(custom), - TimeZoneSlot::Tz(_) => {} - } - }); + pub(crate) inner: InnerZdt, } impl BuiltInObject for ZonedDateTime { diff --git a/core/gc/src/cell.rs b/core/gc/src/cell.rs index fefef87d44..3666d88d97 100644 --- a/core/gc/src/cell.rs +++ b/core/gc/src/cell.rs @@ -10,6 +10,7 @@ use std::{ fmt::{self, Debug, Display}, hash::Hash, ops::{Deref, DerefMut}, + ptr, }; /// `BorrowFlag` represent the internal state of a `GcCell` and @@ -405,7 +406,7 @@ impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> { { #[allow(trivial_casts)] // SAFETY: This is safe as `GcCellRefMut` is already borrowed, so the value is rooted. - let value = unsafe { &mut *(orig.value as *mut U) }; + let value = unsafe { &mut *ptr::from_mut::(orig.value) }; let ret = GcRefMut { gc_cell: orig.gc_cell, @@ -434,7 +435,7 @@ impl<'a, T: ?Sized, U: ?Sized> GcRefMut<'a, T, U> { { #[allow(trivial_casts)] // SAFETY: This is safe as `GcCellRefMut` is already borrowed, so the value is rooted. - let value = unsafe { &mut *(orig.value as *mut U) }; + let value = unsafe { &mut *ptr::from_mut::(orig.value) }; let ret = GcRefMut { gc_cell: orig.gc_cell, diff --git a/test262_config.toml b/test262_config.toml index d5a1842e24..b6678b3e18 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -1,4 +1,4 @@ -commit = "c47b716e8d6bea0c4510d449fd22b7ed5f8b0151" +commit = "3a7a72aef5009eb22117231d40f9a5a66a9a595a" [ignored] # Not implemented yet: diff --git a/tests/tester/src/edition.rs b/tests/tester/src/edition.rs index 8cbfaabcc7..a1a289d2a8 100644 --- a/tests/tester/src/edition.rs +++ b/tests/tester/src/edition.rs @@ -97,6 +97,16 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://github.com/tc39/proposal-float16array "Float16Array" => SpecEdition::ESNext, + // Math.sumPrecise + // https://github.com/tc39/proposal-math-sum + "Math.sumPrecise" => SpecEdition::ESNext, + + // Source Phase Imports + // https://github.com/tc39/proposal-source-phase-imports + "source-phase-imports" => SpecEdition::ESNext, + // test262 special specifier + "source-phase-imports-module-source" => SpecEdition::ESNext, + // Part of the next ES15 edition "Atomics.waitAsync" => SpecEdition::ESNext, "regexp-v-flag" => SpecEdition::ESNext,