From fdfe06bb448feae090aff8ebd82506dc19f60921 Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sat, 10 Feb 2024 14:30:14 +1300 Subject: [PATCH] LibJS: Begin implementing Temporal's CalendarMethodsRecord This is part of a large refactor made as part of the temporal spec. Most AOs using the calendar now pass through this record. There will need to be a long process of going through updating AOs to use this record. --- Userland/Libraries/LibJS/Forward.h | 1 + .../LibJS/Runtime/Temporal/Calendar.cpp | 208 ++++++++++++++++++ .../LibJS/Runtime/Temporal/Calendar.h | 55 +++++ 3 files changed, 264 insertions(+) diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index c902f122c4..591a78f199 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -288,6 +288,7 @@ namespace Temporal { JS_ENUMERATE_TEMPORAL_OBJECTS #undef __JS_ENUMERATE class Temporal; +struct CalendarMethods; struct DurationRecord; struct DateDurationRecord; struct TimeDurationRecord; diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 57974ad6cf..90068142b7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2023-2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -67,6 +68,213 @@ ReadonlySpan available_calendars() return calendars.span(); } +// 12.2.2 CreateCalendarMethodsRecord ( calendar, methods ), https://tc39.es/proposal-temporal/#sec-temporal-createcalendarmethodsrecord +ThrowCompletionOr create_calendar_methods_record(VM& vm, Variant> calendar, ReadonlySpan methods) +{ + // 1. Let record be the Calendar Methods Record { [[Receiver]]: calendar, [[DateAdd]]: undefined, [[DateFromFields]]: undefined, [[DateUntil]]: undefined, [[Day]]: undefined, [[Fields]]: undefined, [[MergeFields]]: undefined, [[MonthDayFromFields]]: undefined, [[YearMonthFromFields]]: undefined }. + CalendarMethods record { + .receiver = move(calendar), + .date_add = nullptr, + .date_from_fields = nullptr, + .date_until = nullptr, + .day = nullptr, + .fields = nullptr, + .merge_fields = nullptr, + .month_day_from_fields = nullptr, + .year_month_from_fields = nullptr, + }; + + // 2. For each element methodName in methods, do + for (auto const& method_name : methods) { + // a. Perform ? CalendarMethodsRecordLookup(record, methodName). + TRY(calendar_methods_record_lookup(vm, record, method_name)); + } + + // 3. Return record. + return record; +} + +ThrowCompletionOr> create_calendar_methods_record_from_relative_to(VM& vm, GCPtr plain_relative_to, GCPtr zoned_relative_to, ReadonlySpan methods) +{ + // FIXME: The casts to NonnullGCPtr should not be here, and can be fixed once PlainDate & ZonedDateTime have the updated type in the [[Calendar]] slot. + + // 1. If zonedRelativeTo is not undefined, return ? CreateCalendarMethodsRecord(zonedRelativeTo.[[Calendar]], methods). + if (zoned_relative_to) + return TRY(create_calendar_methods_record(vm, NonnullGCPtr { zoned_relative_to->calendar() }, methods)); + + // 2. If plainRelativeTo is not undefined, return ? CreateCalendarMethodsRecord(plainRelativeTo.[[Calendar]], methods). + if (plain_relative_to) + return TRY(create_calendar_methods_record(vm, NonnullGCPtr { plain_relative_to->calendar() }, methods)); + + // 3. Return undefined. + return OptionalNone {}; +} + +// 12.2.4 CalendarMethodsRecordLookup ( calendarRec, methodName ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmethodsrecordlookup +ThrowCompletionOr calendar_methods_record_lookup(VM& vm, CalendarMethods& calendar_record, CalendarMethod method_name) +{ + auto& realm = *vm.current_realm(); + + // FIXME: Spec bug? https://github.com/tc39/proposal-temporal/issues/2772 + // Following builtin-lookups should probably be using Temporal.Calendar.prototype + + // 1. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, methodName) is false. + // 2. If methodName is dateAdd, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[DateAdd]] to %Temporal.Calendar.prototype.dateAdd%. + // b. Else, + // i. Set calendarRec.[[DateAdd]] to ? GetMethod(calendarRec.[[Receiver]], "dateAdd"). + // ii. If calendarRec.[[DateAdd]] is undefined, throw a TypeError exception. + // 3. Else if methodName is dateFromFields, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[DateFromFields]] to %Temporal.TimeZone.prototype.dateFromFields%. + // b. Else, + // i. Set calendarRec.[[DateFromFields]] to ? GetMethod(calendarRec.[[Receiver]], "dateFromFields"). + // ii. If calendarRec.[[DateFromFields]] is undefined, throw a TypeError exception. + // 4. Else if methodName is dateUntil, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[DateUntil]] to %Temporal.TimeZone.prototype.dateUntil%. + // b. Else, + // i. Set calendarRec.[[DateUntil]] to ? GetMethod(calendarRec.[[Receiver]], "dateUntil"). + // ii. If calendarRec.[[DateUntil]] is undefined, throw a TypeError exception. + // 5. Else if methodName is day, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[Day]] to %Temporal.TimeZone.prototype.day%. + // b. Else, + // i. Set calendarRec.[[Day]] to ? GetMethod(calendarRec.[[Receiver]], "day"). + // ii. If calendarRec.[[Day]] is undefined, throw a TypeError exception. + // 6. Else if methodName is fields, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[Fields]] to %Temporal.TimeZone.prototype.fields%. + // b. Else, + // i. Set calendarRec.[[Fields]] to ? GetMethod(calendarRec.[[Receiver]], "fields"). + // ii. If calendarRec.[[Fields]] is undefined, throw a TypeError exception. + // 7. Else if methodName is mergeFields, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[MergeFields]] to %Temporal.TimeZone.prototype.mergeFields%. + // b. Else, + // i. Set calendarRec.[[MergeFields]] to ? GetMethod(calendarRec.[[Receiver]], "mergeFields"). + // ii. If calendarRec.[[MergeFields]] is undefined, throw a TypeError exception. + // 8. Else if methodName is monthDayFromFields, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[MonthDayFromFields]] to %Temporal.TimeZone.prototype.monthDayFromFields%. + // b. Else, + // i. Set calendarRec.[[MonthDayFromFields]] to ? GetMethod(calendarRec.[[Receiver]], "monthDayFromFields"). + // ii. If calendarRec.[[MonthDayFromFields]] is undefined, throw a TypeError exception. + // 9. Else if methodName is yearMonthFromFields, then + // a. If calendarRec.[[Receiver]] is a String, then + // i. Set calendarRec.[[YearMonthFromFields]] to %Temporal.TimeZone.prototype.yearMonthFromFields%. + // b. Else, + // i. Set calendarRec.[[YearMonthFromFields]] to ? GetMethod(calendarRec.[[Receiver]], "yearMonthFromFields"). + // ii. If calendarRec.[[YearMonthFromFields]] is undefined, throw a TypeError exception. + switch (method_name) { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + case CalendarMethod::PascalName: { \ + VERIFY(!calendar_record.snake_name); \ + if (calendar_record.receiver.has()) { \ + const auto& calendar_prototype = *realm.intrinsics().temporal_calendar_prototype(); \ + calendar_record.snake_name = calendar_prototype.get_without_side_effects(vm.names.camelName).as_function(); \ + } else { \ + Value calendar { calendar_record.receiver.get>() }; \ + calendar_record.snake_name = TRY(calendar.get_method(vm, vm.names.camelName)); \ + if (!calendar_record.snake_name) \ + return vm.throw_completion(ErrorType::IsUndefined, #camelName##sv); \ + } \ + break; \ + } + JS_ENUMERATE_CALENDAR_METHODS +#undef __JS_ENUMERATE + } + + // 10. Return unused. + return {}; +} + +// 12.2.5 CalendarMethodsRecordHasLookedUp ( calendarRec, methodName ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmethodsrecordhaslookedup +bool calendar_methods_record_has_looked_up(CalendarMethods const& calendar_record, CalendarMethod method_name) +{ + // 1. If methodName is DATE-ADD, then + // a. Let method be calendarRec.[[DateAdd]]. + // 2. Else if methodName is DATE-FROM-FIELDS, then + // a. Let method be calendarRec.[[DateFromFields]]. + // 3. Else if methodName is DATE-UNTIL, then + // a. Let method be calendarRec.[[DateUntil]]. + // 4. Else if methodName is DAY, then + // a. Let method be calendarRec.[[Day]]. + // 5. Else if methodName is FIELDS, then + // a. Let method be calendarRec.[[Fields]]. + // 6. Else if methodName is MERGE-FIELDS, then + // a. Let method be calendarRec.[[MergeFields]]. + // 7. Else if methodName is MONTH-DAY-FROM-FIELDS, then + // a. Let method be calendarRec.[[MonthDayFromFields]]. + // 8. Else if methodName is YEAR-MONTH-FROM-FIELDS, then + // a. Let method be calendarRec.[[YearMonthFromFields]]. + // 9. If method is undefined, return false. + // 10. Return true. + switch (method_name) { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + case CalendarMethod::PascalName: { \ + return calendar_record.snake_name != nullptr; \ + } + JS_ENUMERATE_CALENDAR_METHODS +#undef __JS_ENUMERATE + } + VERIFY_NOT_REACHED(); +} + +// 12.2.6 CalendarMethodsRecordIsBuiltin ( calendarRec ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmethodsrecordisbuiltin +bool calendar_methods_record_is_builtin(CalendarMethods const& calendar_record) +{ + // 1. If calendarRec.[[Receiver]] is a String, return true. + if (calendar_record.receiver.has()) + return true; + + // 2. Return false. + return false; +} + +// 12.2.7 CalendarMethodsRecordCall ( calendarRec, methodName, arguments ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmethodsrecordcall +ThrowCompletionOr calendar_methods_record_call(VM& vm, CalendarMethods const& calendar_record, CalendarMethod method_name, ReadonlySpan arguments) +{ + // 1. Assert: CalendarMethodsRecordHasLookedUp(calendarRec, methodName) is true. + VERIFY(calendar_methods_record_has_looked_up(calendar_record, method_name)); + + // 2. Let receiver be calendarRec.[[Receiver]]. + // 3. If CalendarMethodsRecordIsBuiltin(calendarRec) is true, then + // a. Set receiver to ! CreateTemporalTimeZone(calendarRec.[[Receiver]]). + GCPtr receiver; + if (calendar_methods_record_is_builtin(calendar_record)) + receiver = MUST(create_temporal_time_zone(vm, calendar_record.receiver.get())); + else + receiver = calendar_record.receiver.get>(); + + // 4. If methodName is DATE-ADD, then + // a. Return ? Call(calendarRec.[[DateAdd]], receiver, arguments). + // 5. If methodName is DATE-FROM-FIELDS, then + // a. Return ? Call(calendarRec.[[DateFromFields]], receiver, arguments). + // 6. If methodName is DATE-UNTIL, then + // a. Return ? Call(calendarRec.[[DateUntil]], receiver, arguments). + // 7. If methodName is DAY, then + // a. Return ? Call(calendarRec.[[Day]], receiver, arguments). + // 8. If methodName is FIELDS, then + // a. Return ? Call(calendarRec.[[Fields]], receiver, arguments). + // 9. If methodName is MERGE-FIELDS, then + // a. Return ? Call(calendarRec.[[MergeFields]], receiver, arguments). + // 10. If methodName is MONTH-DAY-FROM-FIELDS, then + // a. Return ? Call(calendarRec.[[MonthDayFromFields]], receiver, arguments). + // 11. If methodName is YEAR-MONTH-FROM-FIELDS, then + // a. Return ? Call(calendarRec.[[YearMonthFromFields]], receiver, arguments). + switch (method_name) { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + case CalendarMethod::PascalName: { \ + return TRY(call(vm, calendar_record.snake_name, receiver, arguments)); \ + } + JS_ENUMERATE_CALENDAR_METHODS +#undef __JS_ENUMERATE + } + VERIFY_NOT_REACHED(); +} + // 12.2.1 CreateTemporalCalendar ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalcalendar ThrowCompletionOr create_temporal_calendar(VM& vm, String const& identifier, FunctionObject const* new_target) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index 1f4fca0dac..0a1c1d3f92 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2023-2024, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ @@ -83,4 +84,58 @@ ThrowCompletionOr default_merge_calendar_fields(VM&, Object const& fiel u16 to_iso_day_of_year(i32 year, u8 month, u8 day); u8 to_iso_day_of_week(i32 year, u8 month, u8 day); +// https://tc39.es/proposal-temporal/#table-temporal-calendar-methods-record-fields +struct CalendarMethods { + // The calendar object, or a string indicating a built-in time zone. + Variant> receiver; // [[Reciever]] + + // The calendar's dateAdd method. For a built-in calendar this is always %Temporal.Calendar.prototype.dateAdd%. + GCPtr date_add; // [[DateAdd]] + + // The calendar's dateFromFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.dateFromFields%. + GCPtr date_from_fields; // [[DateFromFields]] + + // The calendar's dateUntil method. For a built-in calendar this is always %Temporal.Calendar.prototype.dateUntil%. + GCPtr date_until; // [[DateUntil]] + + // The calendar's day method. For a built-in calendar this is always %Temporal.Calendar.prototype.day%. + GCPtr day; // [[Day]] + + // The calendar's fields method. For a built-in calendar this is always %Temporal.Calendar.prototype.fields%. + GCPtr fields; // [[Fields]] + + // The calendar's mergeFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.mergeFields%. + GCPtr merge_fields; // [[MergeFields]] + + // The calendar's monthDayFromFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.monthDayFromFields%. + GCPtr month_day_from_fields; // [[MonthDayFromFields]] + + // The calendar's yearMonthFromFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.yearMonthFromFields%. + GCPtr year_month_from_fields; // [[YearMonthFromFields]] +}; + +#define JS_ENUMERATE_CALENDAR_METHODS \ + __JS_ENUMERATE(DateAdd, dateAdd, date_add) \ + __JS_ENUMERATE(DateFromFields, dateFromFields, date_from_fields) \ + __JS_ENUMERATE(DateUntil, dateUntil, date_until) \ + __JS_ENUMERATE(Day, day, day) \ + __JS_ENUMERATE(Fields, fields, fields) \ + __JS_ENUMERATE(MergeFields, mergeFields, merge_fields) \ + __JS_ENUMERATE(MonthDayFromFields, monthDayFromFields, month_day_from_fields) \ + __JS_ENUMERATE(YearMonthFromFields, yearMonthFromFields, year_month_from_fields) + +enum class CalendarMethod { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + PascalName, + JS_ENUMERATE_CALENDAR_METHODS +#undef __JS_ENUMERATE +}; + +ThrowCompletionOr calendar_methods_record_lookup(VM&, CalendarMethods&, CalendarMethod); +ThrowCompletionOr create_calendar_methods_record(VM&, Variant> calendar, ReadonlySpan); +ThrowCompletionOr> create_calendar_methods_record_from_relative_to(VM&, GCPtr, GCPtr, ReadonlySpan); +bool calendar_methods_record_has_looked_up(CalendarMethods const&, CalendarMethod); +bool calendar_methods_record_is_builtin(CalendarMethods const&); +ThrowCompletionOr calendar_methods_record_call(VM&, CalendarMethods const&, CalendarMethod, ReadonlySpan arguments); + }