diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/builtin/temporal/Calendar.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/temporal/Calendar.cpp')
-rw-r--r-- | js/src/builtin/temporal/Calendar.cpp | 4758 |
1 files changed, 4758 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/Calendar.cpp b/js/src/builtin/temporal/Calendar.cpp new file mode 100644 index 0000000000..8a06877093 --- /dev/null +++ b/js/src/builtin/temporal/Calendar.cpp @@ -0,0 +1,4758 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/temporal/Calendar.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/EnumSet.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/TextUtils.h" + +#include <algorithm> +#include <array> +#include <cmath> +#include <cstring> +#include <iterator> +#include <stddef.h> +#include <stdint.h> +#include <utility> + +#include "jsfriendapi.h" +#include "jsnum.h" +#include "jspubtd.h" +#include "jstypes.h" +#include "NamespaceImports.h" + +#include "builtin/Array.h" +#include "builtin/String.h" +#include "builtin/temporal/Duration.h" +#include "builtin/temporal/PlainDate.h" +#include "builtin/temporal/PlainDateTime.h" +#include "builtin/temporal/PlainMonthDay.h" +#include "builtin/temporal/PlainTime.h" +#include "builtin/temporal/PlainYearMonth.h" +#include "builtin/temporal/Temporal.h" +#include "builtin/temporal/TemporalFields.h" +#include "builtin/temporal/TemporalParser.h" +#include "builtin/temporal/TemporalTypes.h" +#include "builtin/temporal/TemporalUnit.h" +#include "builtin/temporal/Wrapped.h" +#include "builtin/temporal/ZonedDateTime.h" +#include "gc/AllocKind.h" +#include "gc/Barrier.h" +#include "gc/GCEnum.h" +#include "gc/Tracer.h" +#include "js/AllocPolicy.h" +#include "js/CallArgs.h" +#include "js/CallNonGenericMethod.h" +#include "js/Class.h" +#include "js/Conversions.h" +#include "js/ErrorReport.h" +#include "js/ForOfIterator.h" +#include "js/friend/ErrorMessages.h" +#include "js/GCAPI.h" +#include "js/GCHashTable.h" +#include "js/GCVector.h" +#include "js/Id.h" +#include "js/Printer.h" +#include "js/PropertyDescriptor.h" +#include "js/PropertySpec.h" +#include "js/RootingAPI.h" +#include "js/TracingAPI.h" +#include "js/Value.h" +#include "util/Text.h" +#include "vm/ArrayObject.h" +#include "vm/BytecodeUtil.h" +#include "vm/Compartment.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/JSAtomState.h" +#include "vm/JSContext.h" +#include "vm/JSObject.h" +#include "vm/PlainObject.h" +#include "vm/PropertyInfo.h" +#include "vm/PropertyKey.h" +#include "vm/Realm.h" +#include "vm/Shape.h" +#include "vm/Stack.h" +#include "vm/StringType.h" + +#include "vm/Compartment-inl.h" +#include "vm/JSAtomUtils-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/ObjectOperations-inl.h" + +using namespace js; +using namespace js::temporal; + +static inline bool IsCalendar(Handle<Value> v) { + return v.isObject() && v.toObject().is<CalendarObject>(); +} + +void js::temporal::CalendarValue::trace(JSTracer* trc) { + TraceRoot(trc, &value_, "CalendarValue::value"); +} + +void js::temporal::CalendarRecord::trace(JSTracer* trc) { + receiver_.trace(trc); + TraceNullableRoot(trc, &dateAdd_, "CalendarRecord::dateAdd"); + TraceNullableRoot(trc, &dateFromFields_, "CalendarRecord::dateFromFields"); + TraceNullableRoot(trc, &dateUntil_, "CalendarRecord::dateUntil"); + TraceNullableRoot(trc, &day_, "CalendarRecord::day"); + TraceNullableRoot(trc, &fields_, "CalendarRecord::fields"); + TraceNullableRoot(trc, &mergeFields_, "CalendarRecord::mergeFields"); + TraceNullableRoot(trc, &monthDayFromFields_, + "CalendarRecord::monthDayFromFields"); + TraceNullableRoot(trc, &yearMonthFromFields_, + "CalendarRecord::yearMonthFromFields"); +} + +bool js::temporal::WrapCalendarValue(JSContext* cx, + MutableHandle<JS::Value> calendar) { + MOZ_ASSERT(calendar.isString() || calendar.isObject()); + return cx->compartment()->wrap(cx, calendar); +} + +/** + * IteratorToListOfType ( iteratorRecord, elementTypes ) + * + * With `elementTypes = ยซ String ยป`. + * + * This implementation accepts an iterable instead of an iterator record. + */ +static bool IterableToListOfStrings(JSContext* cx, Handle<Value> items, + MutableHandle<CalendarFieldNames> list) { + JS::ForOfIterator iterator(cx); + if (!iterator.init(items)) { + return false; + } + + // Step 1. (Not applicable in our implementation.) + + // Steps 2-3. + Rooted<Value> nextValue(cx); + Rooted<PropertyKey> value(cx); + while (true) { + bool done; + if (!iterator.next(&nextValue, &done)) { + return false; + } + if (done) { + break; + } + + if (nextValue.isString()) { + if (!PrimitiveValueToId<CanGC>(cx, nextValue, &value)) { + return false; + } + if (!list.append(value)) { + return false; + } + continue; + } + + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue, + nullptr, "not a string"); + + iterator.closeThrow(); + return false; + } + + // Step 4. + return true; +} + +/** + * IsISOLeapYear ( year ) + */ +static constexpr bool IsISOLeapYear(int32_t year) { + // Steps 1-5. + return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); +} + +/** + * IsISOLeapYear ( year ) + */ +static bool IsISOLeapYear(double year) { + // Step 1. + MOZ_ASSERT(IsInteger(year)); + + // Steps 2-5. + return std::fmod(year, 4) == 0 && + (std::fmod(year, 100) != 0 || std::fmod(year, 400) == 0); +} + +/** + * ISODaysInYear ( year ) + */ +int32_t js::temporal::ISODaysInYear(int32_t year) { + // Steps 1-3. + return IsISOLeapYear(year) ? 366 : 365; +} + +/** + * ISODaysInMonth ( year, month ) + */ +static constexpr int32_t ISODaysInMonth(int32_t year, int32_t month) { + MOZ_ASSERT(1 <= month && month <= 12); + + constexpr uint8_t daysInMonth[2][13] = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; + + // Steps 1-4. + return daysInMonth[IsISOLeapYear(year)][month]; +} + +/** + * ISODaysInMonth ( year, month ) + */ +int32_t js::temporal::ISODaysInMonth(int32_t year, int32_t month) { + return ::ISODaysInMonth(year, month); +} + +/** + * ISODaysInMonth ( year, month ) + */ +int32_t js::temporal::ISODaysInMonth(double year, int32_t month) { + MOZ_ASSERT(1 <= month && month <= 12); + + static constexpr uint8_t daysInMonth[2][13] = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; + + // Steps 1-4. + return daysInMonth[IsISOLeapYear(year)][month]; +} + +/** + * 21.4.1.6 Week Day + * + * Compute the week day from |day| without first expanding |day| into a full + * date through |MakeDate(day, 0)|: + * + * WeekDay(MakeDate(day, 0)) + * = WeekDay(day ร msPerDay + 0) + * = WeekDay(day ร msPerDay) + * = ๐ฝ(โ(Day(day ร msPerDay) + 4๐ฝ) modulo 7) + * = ๐ฝ(โ(๐ฝ(floor(โ((day ร msPerDay) / msPerDay))) + 4๐ฝ) modulo 7) + * = ๐ฝ(โ(๐ฝ(floor(โ(day))) + 4๐ฝ) modulo 7) + * = ๐ฝ(โ(๐ฝ(day) + 4๐ฝ) modulo 7) + */ +static int32_t WeekDay(int32_t day) { + int32_t result = (day + 4) % 7; + if (result < 0) { + result += 7; + } + return result; +} + +/** + * ToISODayOfWeek ( year, month, day ) + */ +static int32_t ToISODayOfWeek(const PlainDate& date) { + MOZ_ASSERT(ISODateTimeWithinLimits(date)); + + // Steps 1-3. (Not applicable in our implementation.) + + // TODO: Check if ES MakeDate + WeekDay is efficient enough. + // + // https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Methods_in_computer_code + + // Step 4. + int32_t day = MakeDay(date); + + // Step 5. + int32_t weekday = WeekDay(day); + return weekday != 0 ? weekday : 7; +} + +static constexpr auto FirstDayOfMonth(int32_t year) { + // The following array contains the day of year for the first day of each + // month, where index 0 is January, and day 0 is January 1. + std::array<int32_t, 13> days = {}; + for (int32_t month = 1; month <= 12; ++month) { + days[month] = days[month - 1] + ::ISODaysInMonth(year, month); + } + return days; +} + +/** + * ToISODayOfYear ( year, month, day ) + */ +static int32_t ToISODayOfYear(int32_t year, int32_t month, int32_t day) { + MOZ_ASSERT(1 <= month && month <= 12); + + // First day of month arrays for non-leap and leap years. + constexpr decltype(FirstDayOfMonth(0)) firstDayOfMonth[2] = { + FirstDayOfMonth(1), FirstDayOfMonth(0)}; + + // Steps 1-3. (Not applicable in our implementation.) + + // Steps 4-5. + // + // Instead of first computing the date and then using DayWithinYear to map the + // date to the day within the year, directly lookup the first day of the month + // and then add the additional days. + return firstDayOfMonth[IsISOLeapYear(year)][month - 1] + day; +} + +/** + * ToISODayOfYear ( year, month, day ) + */ +int32_t js::temporal::ToISODayOfYear(const PlainDate& date) { + MOZ_ASSERT(ISODateTimeWithinLimits(date)); + + // Steps 1-5. + auto& [year, month, day] = date; + return ::ToISODayOfYear(year, month, day); +} + +static int32_t FloorDiv(int32_t dividend, int32_t divisor) { + MOZ_ASSERT(divisor > 0); + + int32_t quotient = dividend / divisor; + int32_t remainder = dividend % divisor; + if (remainder < 0) { + quotient -= 1; + } + return quotient; +} + +/** + * 21.4.1.3 Year Number, DayFromYear + */ +static int32_t DayFromYear(int32_t year) { + return 365 * (year - 1970) + FloorDiv(year - 1969, 4) - + FloorDiv(year - 1901, 100) + FloorDiv(year - 1601, 400); +} + +/** + * 21.4.1.11 MakeTime ( hour, min, sec, ms ) + */ +static int64_t MakeTime(const PlainTime& time) { + MOZ_ASSERT(IsValidTime(time)); + + // Step 1 (Not applicable). + + // Step 2. + int64_t h = time.hour; + + // Step 3. + int64_t m = time.minute; + + // Step 4. + int64_t s = time.second; + + // Step 5. + int64_t milli = time.millisecond; + + // Steps 6-7. + return h * ToMilliseconds(TemporalUnit::Hour) + + m * ToMilliseconds(TemporalUnit::Minute) + + s * ToMilliseconds(TemporalUnit::Second) + milli; +} + +/** + * 21.4.1.12 MakeDay ( year, month, date ) + */ +int32_t js::temporal::MakeDay(const PlainDate& date) { + MOZ_ASSERT(ISODateTimeWithinLimits(date)); + + return DayFromYear(date.year) + ToISODayOfYear(date) - 1; +} + +/** + * 21.4.1.13 MakeDate ( day, time ) + */ +int64_t js::temporal::MakeDate(const PlainDateTime& dateTime) { + MOZ_ASSERT(ISODateTimeWithinLimits(dateTime)); + + // Step 1 (Not applicable). + + // Steps 2-3. + int64_t tv = MakeDay(dateTime.date) * ToMilliseconds(TemporalUnit::Day) + + MakeTime(dateTime.time); + + // Step 4. + return tv; +} + +/** + * 21.4.1.12 MakeDay ( year, month, date ) + */ +static int32_t MakeDay(int32_t year, int32_t month, int32_t day) { + MOZ_ASSERT(1 <= month && month <= 12); + + // FIXME: spec issue - what should happen for invalid years/days? + return DayFromYear(year) + ::ToISODayOfYear(year, month, day) - 1; +} + +/** + * 21.4.1.13 MakeDate ( day, time ) + */ +int64_t js::temporal::MakeDate(int32_t year, int32_t month, int32_t day) { + // NOTE: This version accepts values outside the valid date-time limits. + MOZ_ASSERT(1 <= month && month <= 12); + + // Step 1 (Not applicable). + + // Steps 2-3. + int64_t tv = ::MakeDay(year, month, day) * ToMilliseconds(TemporalUnit::Day); + + // Step 4. + return tv; +} + +struct YearWeek final { + int32_t year = 0; + int32_t week = 0; +}; + +/** + * ToISOWeekOfYear ( year, month, day ) + */ +static YearWeek ToISOWeekOfYear(const PlainDate& date) { + MOZ_ASSERT(ISODateTimeWithinLimits(date)); + + auto& [year, month, day] = date; + + // TODO: https://en.wikipedia.org/wiki/Week#The_ISO_week_date_system + // TODO: https://en.wikipedia.org/wiki/ISO_week_date#Algorithms + + // Steps 1-3. (Not applicable in our implementation.) + + // Steps 4-5. + int32_t doy = ToISODayOfYear(date); + int32_t dow = ToISODayOfWeek(date); + + int32_t woy = (10 + doy - dow) / 7; + MOZ_ASSERT(0 <= woy && woy <= 53); + + // An ISO year has 53 weeks if the year starts on a Thursday or if it's a + // leap year which starts on a Wednesday. + auto isLongYear = [](int32_t year) { + int32_t startOfYear = ToISODayOfWeek({year, 1, 1}); + return startOfYear == 4 || (startOfYear == 3 && IsISOLeapYear(year)); + }; + + // Part of last year's last week, which is either week 52 or week 53. + if (woy == 0) { + return {year - 1, 52 + int32_t(isLongYear(year - 1))}; + } + + // Part of next year's first week if the current year isn't a long year. + if (woy == 53 && !isLongYear(year)) { + return {year + 1, 1}; + } + + return {year, woy}; +} + +/** + * ISOMonthCode ( month ) + */ +static JSString* ISOMonthCode(JSContext* cx, int32_t month) { + MOZ_ASSERT(1 <= month && month <= 12); + + // Steps 1-2. + char monthCode[3] = {'M', char('0' + (month / 10)), char('0' + (month % 10))}; + return NewStringCopyN<CanGC>(cx, monthCode, std::size(monthCode)); +} + +template <typename T, typename... Ts> +static bool ToPlainDate(JSObject* temporalDateLike, PlainDate* result) { + if (auto* obj = temporalDateLike->maybeUnwrapIf<T>()) { + *result = ToPlainDate(obj); + return true; + } + if constexpr (sizeof...(Ts) > 0) { + return ToPlainDate<Ts...>(temporalDateLike, result); + } + return false; +} + +template <typename... Ts> +static bool ToPlainDate(JSContext* cx, Handle<Value> temporalDateLike, + PlainDate* result) { + if (temporalDateLike.isObject()) { + if (ToPlainDate<Ts...>(&temporalDateLike.toObject(), result)) { + return true; + } + } + + return ToTemporalDate(cx, temporalDateLike, result); +} + +#ifdef DEBUG +template <typename CharT> +static bool StringIsAsciiLowerCase(mozilla::Range<CharT> str) { + return std::all_of(str.begin().get(), str.end().get(), [](CharT ch) { + return mozilla::IsAscii(ch) && !mozilla::IsAsciiUppercaseAlpha(ch); + }); +} + +static bool StringIsAsciiLowerCase(JSLinearString* str) { + JS::AutoCheckCannotGC nogc; + return str->hasLatin1Chars() + ? StringIsAsciiLowerCase(str->latin1Range(nogc)) + : StringIsAsciiLowerCase(str->twoByteRange(nogc)); +} +#endif + +static bool IsISO8601Calendar(JSLinearString* id) { + return StringEqualsLiteral(id, "iso8601"); +} + +#ifdef DEBUG +static bool IsISO8601Calendar(CalendarObject* calendar) { + return IsISO8601Calendar(calendar->identifier()); +} +#endif + +static bool IsISO8601Calendar(JSContext* cx, JSString* id, bool* result) { + JSLinearString* linear = id->ensureLinear(cx); + if (!linear) { + return false; + } + *result = IsISO8601Calendar(linear); + return true; +} + +/** + * IsBuiltinCalendar ( id ) + */ +static bool IsBuiltinCalendar(JSLinearString* id) { + // Callers must convert to lower case. + MOZ_ASSERT(StringIsAsciiLowerCase(id)); + + // Steps 1-3. + return StringEqualsLiteral(id, "iso8601"); +} + +static JSLinearString* ThrowIfNotBuiltinCalendar(JSContext* cx, + Handle<JSLinearString*> id) { + if (!StringIsAscii(id)) { + if (auto chars = QuoteString(cx, id)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get()); + } + return nullptr; + } + + JSString* lower = StringToLowerCase(cx, id); + if (!lower) { + return nullptr; + } + + JSLinearString* linear = lower->ensureLinear(cx); + if (!linear) { + return nullptr; + } + + if (!IsBuiltinCalendar(linear)) { + if (auto chars = QuoteString(cx, id)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get()); + } + return nullptr; + } + + return linear; +} + +bool js::temporal::ToBuiltinCalendar(JSContext* cx, Handle<JSString*> id, + MutableHandle<CalendarValue> result) { + Rooted<JSLinearString*> linear(cx, id->ensureLinear(cx)); + if (!linear) { + return false; + } + + auto* identifier = ThrowIfNotBuiltinCalendar(cx, linear); + if (!identifier) { + return false; + } + + result.set(CalendarValue(identifier)); + return true; +} + +/** + * CreateTemporalCalendar ( identifier [ , newTarget ] ) + */ +static CalendarObject* CreateTemporalCalendar( + JSContext* cx, const CallArgs& args, Handle<JSLinearString*> identifier) { + // Step 1. + MOZ_ASSERT(IsBuiltinCalendar(identifier)); + + // Steps 2-3. + Rooted<JSObject*> proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Calendar, &proto)) { + return nullptr; + } + + auto* obj = NewObjectWithClassProto<CalendarObject>(cx, proto); + if (!obj) { + return nullptr; + } + + // Step 4. + obj->setFixedSlot(CalendarObject::IDENTIFIER_SLOT, StringValue(identifier)); + + // Step 5. + return obj; +} + +/** + * CreateTemporalCalendar ( identifier [ , newTarget ] ) + */ +static CalendarObject* CreateTemporalCalendar( + JSContext* cx, Handle<JSLinearString*> identifier) { + // Step 1. + MOZ_ASSERT(IsBuiltinCalendar(identifier)); + + // Steps 2-3. + auto* obj = NewBuiltinClassInstance<CalendarObject>(cx); + if (!obj) { + return nullptr; + } + + // Step 4. + obj->setFixedSlot(CalendarObject::IDENTIFIER_SLOT, StringValue(identifier)); + + // Step 5. + return obj; +} + +/** + * ObjectImplementsTemporalCalendarProtocol ( object ) + */ +static bool ObjectImplementsTemporalCalendarProtocol(JSContext* cx, + Handle<JSObject*> object, + bool* result) { + // Step 1. (Not applicable in our implementation.) + MOZ_ASSERT(!object->canUnwrapAs<CalendarObject>(), + "Calendar objects handled in the caller"); + + // Step 2. + for (auto key : { + &JSAtomState::dateAdd, &JSAtomState::dateFromFields, + &JSAtomState::dateUntil, &JSAtomState::day, + &JSAtomState::dayOfWeek, &JSAtomState::dayOfYear, + &JSAtomState::daysInMonth, &JSAtomState::daysInWeek, + &JSAtomState::daysInYear, &JSAtomState::fields, + &JSAtomState::id, &JSAtomState::inLeapYear, + &JSAtomState::mergeFields, &JSAtomState::month, + &JSAtomState::monthCode, &JSAtomState::monthDayFromFields, + &JSAtomState::monthsInYear, &JSAtomState::weekOfYear, + &JSAtomState::year, &JSAtomState::yearMonthFromFields, + &JSAtomState::yearOfWeek, + }) { + // Step 2.a. + bool has; + if (!HasProperty(cx, object, cx->names().*key, &has)) { + return false; + } + if (!has) { + *result = false; + return true; + } + } + + // Step 3. + *result = true; + return true; +} + +template <typename T, typename... Ts> +static bool ToTemporalCalendar(JSContext* cx, Handle<JSObject*> object, + MutableHandle<CalendarValue> result) { + if (auto* unwrapped = object->maybeUnwrapIf<T>()) { + result.set(unwrapped->calendar()); + return result.wrap(cx); + } + + if constexpr (sizeof...(Ts) > 0) { + return ToTemporalCalendar<Ts...>(cx, object, result); + } + + result.set(CalendarValue()); + return true; +} + +/** + * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] ) + */ +bool js::temporal::ToTemporalCalendar(JSContext* cx, + Handle<Value> temporalCalendarLike, + MutableHandle<CalendarValue> result) { + // Step 1. (Not applicable) + + // Step 2. + Rooted<Value> calendarLike(cx, temporalCalendarLike); + if (calendarLike.isObject()) { + Rooted<JSObject*> obj(cx, &calendarLike.toObject()); + + // Step 2.b. (Partial) + if (obj->canUnwrapAs<CalendarObject>()) { + result.set(CalendarValue(obj)); + return true; + } + + // Step 2.a. + Rooted<CalendarValue> calendar(cx); + if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject, + PlainMonthDayObject, PlainYearMonthObject, + ZonedDateTimeObject>(cx, obj, &calendar)) { + return false; + } + if (calendar) { + result.set(calendar); + return true; + } + + // Step 2.b. + bool implementsCalendarProtocol; + if (!ObjectImplementsTemporalCalendarProtocol( + cx, obj, &implementsCalendarProtocol)) { + return false; + } + if (!implementsCalendarProtocol) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INVALID_OBJECT, + "Temporal.Calendar", obj->getClass()->name); + return false; + } + + // Step 2.c. + result.set(CalendarValue(obj)); + return true; + } + + // Step 3. + if (!calendarLike.isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, + calendarLike, nullptr, "not a string"); + return false; + } + Rooted<JSString*> str(cx, calendarLike.toString()); + + // Step 4. + Rooted<JSLinearString*> identifier(cx, ParseTemporalCalendarString(cx, str)); + if (!identifier) { + return false; + } + + // Step 5. + identifier = ThrowIfNotBuiltinCalendar(cx, identifier); + if (!identifier) { + return false; + } + + // Step 6. + result.set(CalendarValue(identifier)); + return true; +} + +/** + * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] ) + * + * When called with `default = "iso8601"`. + */ +bool js::temporal::ToTemporalCalendarWithISODefault( + JSContext* cx, Handle<Value> temporalCalendarLike, + MutableHandle<CalendarValue> result) { + // Step 1. + if (temporalCalendarLike.isUndefined()) { + result.set(CalendarValue(cx->names().iso8601)); + return true; + } + + // Steps 2-6. + return ToTemporalCalendar(cx, temporalCalendarLike, result); +} + +/** + * GetTemporalCalendarSlotValueWithISODefault ( item ) + */ +bool js::temporal::GetTemporalCalendarWithISODefault( + JSContext* cx, Handle<JSObject*> item, + MutableHandle<CalendarValue> result) { + // Step 1. + Rooted<CalendarValue> calendar(cx); + if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject, + PlainMonthDayObject, PlainYearMonthObject, + ZonedDateTimeObject>(cx, item, &calendar)) { + return false; + } + if (calendar) { + result.set(calendar); + return true; + } + + // Step 2. + Rooted<Value> calendarValue(cx); + if (!GetProperty(cx, item, item, cx->names().calendar, &calendarValue)) { + return false; + } + + // Step 3. + return ToTemporalCalendarWithISODefault(cx, calendarValue, result); +} + +/** + * ToTemporalCalendarIdentifier ( calendarSlotValue ) + */ +JSString* js::temporal::ToTemporalCalendarIdentifier( + JSContext* cx, Handle<CalendarValue> calendar) { + // Step 1. + if (calendar.isString()) { + // Step 1.a. + MOZ_ASSERT(IsBuiltinCalendar(calendar.toString())); + + // Step 1.b. + return calendar.toString(); + } + + // Step 2. + Rooted<JSObject*> calendarObj(cx, calendar.toObject()); + Rooted<Value> identifier(cx); + if (!GetProperty(cx, calendarObj, calendarObj, cx->names().id, &identifier)) { + return nullptr; + } + + // Step 3. + if (!identifier.isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, identifier, + nullptr, "not a string"); + return nullptr; + } + + // Step 4. + return identifier.toString(); +} + +/** + * ToTemporalCalendarObject ( calendarSlotValue ) + */ +JSObject* js::temporal::ToTemporalCalendarObject( + JSContext* cx, Handle<CalendarValue> calendar) { + // Step 1. + if (calendar.isObject()) { + return calendar.toObject(); + } + + // Step 2. + Rooted<JSLinearString*> calendarId(cx, calendar.toString()); + return CreateTemporalCalendar(cx, calendarId); +} + +static bool Calendar_dateAdd(JSContext* cx, unsigned argc, Value* vp); +static bool Calendar_dateFromFields(JSContext* cx, unsigned argc, Value* vp); +static bool Calendar_dateUntil(JSContext* cx, unsigned argc, Value* vp); +static bool Calendar_day(JSContext* cx, unsigned argc, Value* vp); +static bool Calendar_fields(JSContext* cx, unsigned argc, Value* vp); +static bool Calendar_mergeFields(JSContext* cx, unsigned argc, Value* vp); +static bool Calendar_monthDayFromFields(JSContext* cx, unsigned argc, + Value* vp); +static bool Calendar_yearMonthFromFields(JSContext* cx, unsigned argc, + Value* vp); + +/** + * CalendarMethodsRecordLookup ( calendarRec, methodName ) + */ +static bool CalendarMethodsRecordLookup(JSContext* cx, + MutableHandle<CalendarRecord> calendar, + CalendarMethod methodName) { + // Step 1. (Not applicable in our implementation.) + + // Steps 2-10. + Rooted<JSObject*> object(cx, calendar.receiver().toObject()); + + auto lookup = [&](Handle<PropertyName*> name, JSNative native, + MutableHandle<JSObject*> result) { + auto* method = GetMethod(cx, object, name); + if (!method) { + return false; + } + + // As an optimization we only store the method if the receiver is either + // a custom calendar object or if the method isn't the default, built-in + // calender method. + if (!object->is<CalendarObject>() || !IsNativeFunction(method, native)) { + result.set(method); + } + return true; + }; + + switch (methodName) { + // Steps 2 and 10. + case CalendarMethod::DateAdd: + return lookup(cx->names().dateAdd, Calendar_dateAdd, calendar.dateAdd()); + + // Steps 3 and 10. + case CalendarMethod::DateFromFields: + return lookup(cx->names().dateFromFields, Calendar_dateFromFields, + calendar.dateFromFields()); + + // Steps 4 and 10. + case CalendarMethod::DateUntil: + return lookup(cx->names().dateUntil, Calendar_dateUntil, + calendar.dateUntil()); + + // Steps 5 and 10. + case CalendarMethod::Day: + return lookup(cx->names().day, Calendar_day, calendar.day()); + + // Steps 6 and 10. + case CalendarMethod::Fields: + return lookup(cx->names().fields, Calendar_fields, calendar.fields()); + + // Steps 7 and 10. + case CalendarMethod::MergeFields: + return lookup(cx->names().mergeFields, Calendar_mergeFields, + calendar.mergeFields()); + + // Steps 8 and 10. + case CalendarMethod::MonthDayFromFields: + return lookup(cx->names().monthDayFromFields, Calendar_monthDayFromFields, + calendar.monthDayFromFields()); + + // Steps 9 and 10. + case CalendarMethod::YearMonthFromFields: + return lookup(cx->names().yearMonthFromFields, + Calendar_yearMonthFromFields, + calendar.yearMonthFromFields()); + } + + MOZ_CRASH("invalid calendar method"); +} + +/** + * CreateCalendarMethodsRecord ( calendar, methods ) + */ +bool js::temporal::CreateCalendarMethodsRecord( + JSContext* cx, Handle<CalendarValue> calendar, + mozilla::EnumSet<CalendarMethod> methods, + MutableHandle<CalendarRecord> result) { + MOZ_ASSERT(!methods.isEmpty()); + + // Step 1. + result.set(CalendarRecord{calendar}); + +#ifdef DEBUG + // Remember the set of looked-up methods for assertions. + result.get().lookedUp() += methods; +#endif + + // Built-in calendars don't perform observable lookups. + if (calendar.isString()) { + return true; + } + + // Step 2. + for (auto method : methods) { + if (!CalendarMethodsRecordLookup(cx, result, method)) { + return false; + } + } + + // Step 3. + return true; +} + +static bool ToCalendarField(JSContext* cx, JSLinearString* linear, + CalendarField* result) { + if (StringEqualsLiteral(linear, "year")) { + *result = CalendarField::Year; + return true; + } + if (StringEqualsLiteral(linear, "month")) { + *result = CalendarField::Month; + return true; + } + if (StringEqualsLiteral(linear, "monthCode")) { + *result = CalendarField::MonthCode; + return true; + } + if (StringEqualsLiteral(linear, "day")) { + *result = CalendarField::Day; + return true; + } + if (auto chars = QuoteString(cx, linear, '"')) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_FIELD, + chars.get()); + } + return false; +} + +static PropertyName* ToPropertyName(JSContext* cx, CalendarField field) { + switch (field) { + case CalendarField::Year: + return cx->names().year; + case CalendarField::Month: + return cx->names().month; + case CalendarField::MonthCode: + return cx->names().monthCode; + case CalendarField::Day: + return cx->names().day; + } + MOZ_CRASH("invalid calendar field name"); +} + +#ifdef DEBUG +static const char* ToCString(CalendarField field) { + switch (field) { + case CalendarField::Year: + return "year"; + case CalendarField::Month: + return "month"; + case CalendarField::MonthCode: + return "monthCode"; + case CalendarField::Day: + return "day"; + } + MOZ_CRASH("invalid calendar field name"); +} +#endif + +/** + * Temporal.Calendar.prototype.fields ( fields ) + */ +static bool BuiltinCalendarFields( + JSContext* cx, std::initializer_list<CalendarField> fieldNames, + CalendarFieldNames& result) { + MOZ_ASSERT(result.empty()); + + // Steps 1-5. (Not applicable.) + + // Reserve space for the append operation. + if (!result.reserve(fieldNames.size())) { + return false; + } + + // Steps 6-7. + for (auto fieldName : fieldNames) { + auto* name = ToPropertyName(cx, fieldName); + + // Steps 7.a and 7.b.i-iv. (Not applicable) + + // Step 7.b.v. + result.infallibleAppend(NameToId(name)); + } + + // Step 8. (Not applicable) + return true; +} + +#ifdef DEBUG +static bool IsSorted(std::initializer_list<CalendarField> fieldNames) { + return std::is_sorted(fieldNames.begin(), fieldNames.end(), + [](auto x, auto y) { + auto* a = ToCString(x); + auto* b = ToCString(y); + return std::strcmp(a, b) < 0; + }); +} +#endif + +/** + * CalendarFields ( calendarRec, fieldNames ) + */ +bool js::temporal::CalendarFields( + JSContext* cx, Handle<CalendarRecord> calendar, + std::initializer_list<CalendarField> fieldNames, + MutableHandle<CalendarFieldNames> result) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Fields)); + + // FIXME: spec issue - the input is already sorted, let's assert this, too. + MOZ_ASSERT(IsSorted(fieldNames)); + + // FIXME: spec issue - the input shouldn't have duplicate elements. Let's + // assert this, too. + MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) == + fieldNames.end()); + + // Step 1. + auto fields = calendar.fields(); + if (!fields) { + bool arrayIterationSane; + if (calendar.receiver().isString()) { + // "String" calendars don't perform observable array iteration. + arrayIterationSane = true; + } else { + // "Object" calendars need to ensure array iteration is still sane. + if (!IsArrayIterationSane(cx, &arrayIterationSane)) { + return false; + } + } + + if (arrayIterationSane) { + // Steps 1.a-b. (Not applicable in our implementation.) + + // Step 1.c. + return BuiltinCalendarFields(cx, fieldNames, result.get()); + + // Steps 1.d-f. (Not applicable in our implementation.) + } + } + + // Step 2. (Inlined call to CalendarMethodsRecordCall.) + + auto* array = NewDenseFullyAllocatedArray(cx, fieldNames.size()); + if (!array) { + return false; + } + array->setDenseInitializedLength(fieldNames.size()); + + for (size_t i = 0; i < fieldNames.size(); i++) { + auto* name = ToPropertyName(cx, fieldNames.begin()[i]); + array->initDenseElement(i, StringValue(name)); + } + + Rooted<Value> fieldsFn(cx, ObjectValue(*fields)); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> fieldsArray(cx, ObjectValue(*array)); + if (!Call(cx, fieldsFn, thisv, fieldsArray, &fieldsArray)) { + return false; + } + + // Steps 3-4. + if (!IterableToListOfStrings(cx, fieldsArray, result)) { + return false; + } + + // The spec sorts the field names in PrepareTemporalFields. Sorting is only + // needed for user-defined calendars, so our implementation performs this step + // here instead of in PrepareTemporalFields. + return SortTemporalFieldNames(cx, result.get()); +} + +static bool RequireIntegralNumber(JSContext* cx, Handle<Value> value, + Handle<PropertyName*> name, + MutableHandle<Value> result) { + if (MOZ_LIKELY(value.isInt32())) { + result.set(value); + return true; + } + + if (value.isDouble()) { + double d = value.toDouble(); + if (js::IsInteger(d)) { + result.setNumber(d); + return true; + } + + if (auto str = QuoteString(cx, name)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INVALID_INTEGER, str.get()); + } + return false; + } + + if (auto str = QuoteString(cx, name)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_UNEXPECTED_TYPE, str.get(), "not a number"); + } + return false; +} + +static bool RequireIntegralPositiveNumber(JSContext* cx, Handle<Value> value, + Handle<PropertyName*> name, + MutableHandle<Value> result) { + if (!RequireIntegralNumber(cx, value, name, result)) { + return false; + } + + if (result.toNumber() <= 0) { + if (auto str = QuoteString(cx, name)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INVALID_NUMBER, str.get()); + } + return false; + } + return true; +} + +static bool RequireString(JSContext* cx, Handle<Value> value, + Handle<PropertyName*> name, + MutableHandle<Value> result) { + if (MOZ_LIKELY(value.isString())) { + result.set(value); + return true; + } + + if (auto str = QuoteString(cx, name)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_UNEXPECTED_TYPE, str.get(), "not a string"); + } + return false; +} + +static bool RequireBoolean(JSContext* cx, Handle<Value> value, + Handle<PropertyName*> name, + MutableHandle<Value> result) { + if (MOZ_LIKELY(value.isBoolean())) { + result.set(value); + return true; + } + + if (auto str = QuoteString(cx, name)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_UNEXPECTED_TYPE, str.get(), + "not a boolean"); + } + return false; +} + +using BuiltinCalendarMethod = bool (*)(JSContext* cx, const PlainDate&, + MutableHandle<Value>); + +using CalendarConversion = bool (*)(JSContext*, Handle<Value>, + Handle<PropertyName*>, + MutableHandle<Value>); + +template <BuiltinCalendarMethod builtin, CalendarConversion conversion> +static bool CallCalendarMethod(JSContext* cx, Handle<PropertyName*> name, + JSNative native, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Step 1. + if (calendar.isString()) { + return builtin(cx, date, result); + } + + // Step 2. + Rooted<JSObject*> calendarObj(cx, calendar.toObject()); + JSObject* fn = GetMethod(cx, calendarObj, name); + if (!fn) { + return false; + } + + // Fast-path for the default implementation. + if (calendarObj->is<CalendarObject>() && IsNativeFunction(fn, native)) { + return builtin(cx, date, result); + } + + Rooted<JS::Value> fnVal(cx, ObjectValue(*fn)); + Rooted<JS::Value> dateLikeValue(cx, ObjectValue(*dateLike)); + if (!Call(cx, fnVal, calendarObj, dateLikeValue, result)) { + return false; + } + + // Steps 3-5. + return conversion(cx, result, name, result); +} + +/** + * Temporal.Calendar.prototype.year ( temporalDateLike ) + */ +static bool BuiltinCalendarYear(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-6. + result.setInt32(date.year); + return true; +} + +static bool Calendar_year(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarYear ( calendar, dateLike ) + */ +static bool CalendarYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-5. + return CallCalendarMethod<BuiltinCalendarYear, RequireIntegralNumber>( + cx, cx->names().year, Calendar_year, calendar, dateLike, date, result); +} + +/** + * CalendarYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarYear(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.month ( temporalDateLike ) + */ +static bool BuiltinCalendarMonth(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-5. (Not applicable.) + + // Steps 6-7. + result.setInt32(date.month); + return true; +} + +static bool Calendar_month(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarMonth ( calendar, dateLike ) + */ +static bool CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-6. + return CallCalendarMethod<BuiltinCalendarMonth, + RequireIntegralPositiveNumber>( + cx, cx->names().month, Calendar_month, calendar, dateLike, date, result); +} + +/** + * CalendarMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarMonth(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.monthCode ( temporalDateLike ) + */ +static bool BuiltinCalendarMonthCode(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-6. + JSString* str = ISOMonthCode(cx, date.month); + if (!str) { + return false; + } + + result.setString(str); + return true; +} + +static bool Calendar_monthCode(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarMonthCode ( calendar, dateLike ) + */ +static bool CalendarMonthCode(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. + return CallCalendarMethod<BuiltinCalendarMonthCode, RequireString>( + cx, cx->names().monthCode, Calendar_monthCode, calendar, dateLike, date, + result); +} + +/** + * CalendarMonthCode ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthCode(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthCode ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthCode(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthCode ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthCode(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainMonthDayObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthCode ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthCode(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthCode ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthCode(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarMonthCode(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.day ( temporalDateLike ) + */ +static bool BuiltinCalendarDay(const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-6. + result.setInt32(date.day); + return true; +} + +/** + * CalendarDay ( calendarRec, dateLike ) + */ +static bool CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar, + Handle<JSObject*> dateLike, const PlainDate& date, + MutableHandle<Value> result) { + MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Day)); + + // Step 2. (Reordered) + auto day = calendar.day(); + if (!day) { + return BuiltinCalendarDay(date, result); + } + + // Step 1. (Inlined call to CalendarMethodsRecordCall.) + Rooted<Value> fn(cx, ObjectValue(*day)); + auto thisv = calendar.receiver().toValue(); + Rooted<JS::Value> dateLikeValue(cx, ObjectValue(*dateLike)); + if (!Call(cx, fn, thisv, dateLikeValue, result)) { + return false; + } + + // Steps 3-6. + return RequireIntegralPositiveNumber(cx, result, cx->names().day, result); +} + +/** + * CalendarDay ( calendarRec, dateLike ) + */ +bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + Rooted<CalendarRecord> calendarRec(cx); + if (!CreateCalendarMethodsRecord(cx, calendar, + { + CalendarMethod::Day, + }, + &calendarRec)) { + return false; + } + + return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarDay ( calendarRec, dateLike ) + */ +bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + Rooted<CalendarRecord> calendarRec(cx); + if (!CreateCalendarMethodsRecord(cx, calendar, + { + CalendarMethod::Day, + }, + &calendarRec)) { + return false; + } + + return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarDay ( calendarRec, dateLike ) + */ +bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar, + Handle<PlainMonthDayObject*> dateLike, + MutableHandle<Value> result) { + Rooted<CalendarRecord> calendarRec(cx); + if (!CreateCalendarMethodsRecord(cx, calendar, + { + CalendarMethod::Day, + }, + &calendarRec)) { + return false; + } + + return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result); +} + +/** + * CalendarDay ( calendarRec, dateLike ) + */ +bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar, + const PlainDate& date, + MutableHandle<Value> result) { + Rooted<PlainDateObject*> dateLike( + cx, CreateTemporalDate(cx, date, calendar.receiver())); + if (!dateLike) { + return false; + } + + return ::CalendarDay(cx, calendar, dateLike, date, result); +} + +/** + * CalendarDay ( calendarRec, dateLike ) + */ +bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar.receiver())); + if (!dateLike) { + return false; + } + + return ::CalendarDay(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike ) + */ +static bool BuiltinCalendarDayOfWeek(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-9. + result.setInt32(ToISODayOfWeek(date)); + return true; +} + +static bool Calendar_dayOfWeek(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarDayOfWeek ( calendar, dateLike ) + */ +static bool CalendarDayOfWeek(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-6. + return CallCalendarMethod<BuiltinCalendarDayOfWeek, + RequireIntegralPositiveNumber>( + cx, cx->names().dayOfWeek, Calendar_dayOfWeek, calendar, dateLike, date, + result); +} + +/** + * CalendarDayOfWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarDayOfWeek(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDayOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDayOfWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarDayOfWeek(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDayOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDayOfWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarDayOfWeek(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarDayOfWeek(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike ) + */ +static bool BuiltinCalendarDayOfYear(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-7. + result.setInt32(ToISODayOfYear(date)); + return true; +} + +static bool Calendar_dayOfYear(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarDayOfYear ( calendar, dateLike ) + */ +static bool CalendarDayOfYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-6. + return CallCalendarMethod<BuiltinCalendarDayOfYear, + RequireIntegralPositiveNumber>( + cx, cx->names().dayOfYear, Calendar_dayOfYear, calendar, dateLike, date, + result); +} + +/** + * CalendarDayOfYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDayOfYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDayOfYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDayOfYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDayOfYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDayOfYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDayOfYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDayOfYear(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarDayOfYear(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike ) + */ +static bool BuiltinCalendarWeekOfYear(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-6. + result.setInt32(ToISOWeekOfYear(date).week); + return true; +} + +static bool Calendar_weekOfYear(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarWeekOfYear ( calendar, dateLike ) + */ +static bool CalendarWeekOfYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-6. + return CallCalendarMethod<BuiltinCalendarWeekOfYear, + RequireIntegralPositiveNumber>( + cx, cx->names().weekOfYear, Calendar_weekOfYear, calendar, dateLike, date, + result); +} + +/** + * CalendarWeekOfYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarWeekOfYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarWeekOfYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarWeekOfYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarWeekOfYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarWeekOfYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarWeekOfYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarWeekOfYear(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarWeekOfYear(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike ) + */ +static bool BuiltinCalendarYearOfWeek(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-6. + result.setInt32(ToISOWeekOfYear(date).year); + return true; +} + +static bool Calendar_yearOfWeek(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarYearOfWeek ( calendar, dateLike ) + */ +static bool CalendarYearOfWeek(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-5. + return CallCalendarMethod<BuiltinCalendarYearOfWeek, RequireIntegralNumber>( + cx, cx->names().yearOfWeek, Calendar_yearOfWeek, calendar, dateLike, date, + result); +} + +/** + * CalendarYearOfWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarYearOfWeek(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarYearOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarYearOfWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarYearOfWeek(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarYearOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarYearOfWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarYearOfWeek(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarYearOfWeek(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike ) + */ +static bool BuiltinCalendarDaysInWeek(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Step 5. + result.setInt32(7); + return true; +} + +static bool Calendar_daysInWeek(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarDaysInWeek ( calendar, dateLike ) + */ +static bool CalendarDaysInWeek(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-6. + return CallCalendarMethod<BuiltinCalendarDaysInWeek, + RequireIntegralPositiveNumber>( + cx, cx->names().daysInWeek, Calendar_daysInWeek, calendar, dateLike, date, + result); +} + +/** + * CalendarDaysInWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInWeek(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInWeek(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInWeek(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInWeek(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInWeek ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInWeek(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarDaysInWeek(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike ) + */ +static bool BuiltinCalendarDaysInMonth(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Step 5. + result.setInt32(::ISODaysInMonth(date.year, date.month)); + return true; +} + +static bool Calendar_daysInMonth(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarDaysInMonth ( calendar, dateLike ) + */ +static bool CalendarDaysInMonth(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Step 1-6. + return CallCalendarMethod<BuiltinCalendarDaysInMonth, + RequireIntegralPositiveNumber>( + cx, cx->names().daysInMonth, Calendar_daysInMonth, calendar, dateLike, + date, result); +} + +/** + * CalendarDaysInMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInMonth(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInMonth(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInMonth(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInMonth ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInMonth(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarDaysInMonth(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.daysInYear ( temporalDateLike ) + */ +static bool BuiltinCalendarDaysInYear(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Step 5. + result.setInt32(ISODaysInYear(date.year)); + return true; +} + +static bool Calendar_daysInYear(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarDaysInYear ( calendar, dateLike ) + */ +static bool CalendarDaysInYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Step 1-6. + return CallCalendarMethod<BuiltinCalendarDaysInYear, + RequireIntegralPositiveNumber>( + cx, cx->names().daysInYear, Calendar_daysInYear, calendar, dateLike, date, + result); +} + +/** + * CalendarDaysInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarDaysInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarDaysInYear(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarDaysInYear(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike ) + */ +static bool BuiltinCalendarMonthsInYear(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Step 5. + result.setInt32(12); + return true; +} + +static bool Calendar_monthsInYear(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarMonthsInYear ( calendar, dateLike ) + */ +static bool CalendarMonthsInYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Step 1-6. + return CallCalendarMethod<BuiltinCalendarMonthsInYear, + RequireIntegralPositiveNumber>( + cx, cx->names().monthsInYear, Calendar_monthsInYear, calendar, dateLike, + date, result); +} + +/** + * CalendarMonthsInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthsInYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthsInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthsInYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthsInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthsInYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarMonthsInYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarMonthsInYear(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarMonthsInYear(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike ) + */ +static bool BuiltinCalendarInLeapYear(JSContext* cx, const PlainDate& date, + MutableHandle<Value> result) { + // Steps 1-4. (Not applicable.) + + // Steps 5-6. + result.setBoolean(IsISOLeapYear(date.year)); + return true; +} + +static bool Calendar_inLeapYear(JSContext* cx, unsigned argc, Value* vp); + +/** + * CalendarInLeapYear ( calendar, dateLike ) + */ +static bool CalendarInLeapYear(JSContext* cx, Handle<CalendarValue> calendar, + Handle<JSObject*> dateLike, + const PlainDate& date, + MutableHandle<Value> result) { + // Step 1-4. + return CallCalendarMethod<BuiltinCalendarInLeapYear, RequireBoolean>( + cx, cx->names().inLeapYear, Calendar_inLeapYear, calendar, dateLike, date, + result); +} + +/** + * CalendarInLeapYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarInLeapYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateObject*> dateLike, + MutableHandle<Value> result) { + return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarInLeapYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarInLeapYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainDateTimeObject*> dateLike, + MutableHandle<Value> result) { + return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarInLeapYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarInLeapYear(JSContext* cx, + Handle<CalendarValue> calendar, + Handle<PlainYearMonthObject*> dateLike, + MutableHandle<Value> result) { + return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike), + result); +} + +/** + * CalendarInLeapYear ( calendar, dateLike ) + */ +bool js::temporal::CalendarInLeapYear(JSContext* cx, + Handle<CalendarValue> calendar, + const PlainDateTime& dateTime, + MutableHandle<Value> result) { + Rooted<PlainDateTimeObject*> dateLike( + cx, CreateTemporalDateTime(cx, dateTime, calendar)); + if (!dateLike) { + return false; + } + + return ::CalendarInLeapYear(cx, calendar, dateLike, dateTime.date, result); +} + +/** + * ISOResolveMonth ( fields ) + */ +static bool ISOResolveMonth(JSContext* cx, + MutableHandle<TemporalFields> fields) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + double month = fields.month(); + + // Step 3. + MOZ_ASSERT((IsInteger(month) && month > 0) || std::isnan(month)); + + // Step 4. + Handle<JSString*> monthCode = fields.monthCode(); + + // Step 5. + if (!monthCode) { + // Step 5.a. + if (std::isnan(month)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_MISSING_FIELD, + "monthCode"); + return false; + } + + // Step 5.b. + return true; + } + + // Steps 6-7. (Not applicable in our implementation.) + + // Step 8. + if (monthCode->length() != 3) { + if (auto code = QuoteString(cx, monthCode)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE, + code.get()); + } + return false; + } + + JSLinearString* linear = monthCode->ensureLinear(cx); + if (!linear) { + return false; + } + + char16_t chars[3] = { + linear->latin1OrTwoByteChar(0), + linear->latin1OrTwoByteChar(1), + linear->latin1OrTwoByteChar(2), + }; + + // Steps 9-11. (Partial) + if (chars[0] != 'M' || !mozilla::IsAsciiDigit(chars[1]) || + !mozilla::IsAsciiDigit(chars[2])) { + if (auto code = QuoteString(cx, linear)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE, + code.get()); + } + return false; + } + + // Step 12. + int32_t monthCodeInteger = + AsciiDigitToNumber(chars[1]) * 10 + AsciiDigitToNumber(chars[2]); + + // Step 11. (Partial) + if (monthCodeInteger < 1 || monthCodeInteger > 12) { + if (auto code = QuoteString(cx, linear)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE, + code.get()); + } + return false; + } + + // Step 13. (Not applicable in our implementation.) + + // Step 14. + if (!std::isnan(month) && month != monthCodeInteger) { + if (auto code = QuoteString(cx, linear)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE, + code.get()); + } + return false; + } + + // Step 15. + fields.month() = monthCodeInteger; + + // Step 16. + return true; +} + +/** + * ISODateFromFields ( fields, overflow ) + */ +static bool ISODateFromFields(JSContext* cx, Handle<TemporalFields> fields, + TemporalOverflow overflow, PlainDate* result) { + // Steps 1-2. (Not applicable in our implementation.) + + // Step 3. + double year = fields.year(); + + // Step 4. + double month = fields.month(); + + // Step 5. + double day = fields.day(); + + // Step 6. + MOZ_ASSERT(!std::isnan(year) && !std::isnan(month) && !std::isnan(day)); + + // Step 7. + RegulatedISODate regulated; + if (!RegulateISODate(cx, year, month, day, overflow, ®ulated)) { + return false; + } + + // The result is used to create a new PlainDateObject, so it's okay to + // directly throw an error for invalid years. That way we don't have to worry + // about representing doubles in PlainDate structs. + int32_t intYear; + if (!mozilla::NumberEqualsInt32(regulated.year, &intYear)) { + // CreateTemporalDate, steps 1-2. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_DATE_INVALID); + return false; + } + + *result = {intYear, regulated.month, regulated.day}; + return true; +} + +/** + * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] ) + */ +static PlainDateObject* BuiltinCalendarDateFromFields( + JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) { + // Steps 1-5. (Not applicable) + + // Step 6. + Rooted<TemporalFields> dateFields(cx); + if (!PrepareTemporalFields(cx, fields, + {TemporalField::Day, TemporalField::Month, + TemporalField::MonthCode, TemporalField::Year}, + {TemporalField::Day, TemporalField::Year}, + &dateFields)) { + return nullptr; + } + + // Step 7. + auto overflow = TemporalOverflow::Constrain; + if (maybeOptions) { + if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) { + return nullptr; + } + } + + // Step 8. + if (!ISOResolveMonth(cx, &dateFields)) { + return nullptr; + } + + // Step 9. + PlainDate result; + if (!ISODateFromFields(cx, dateFields, overflow, &result)) { + return nullptr; + } + + // Step 10. + Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601)); + return CreateTemporalDate(cx, result, calendar); +} + +/** + * CalendarDateFromFields ( calendarRec, fields [ , options ] ) + */ +static Wrapped<PlainDateObject*> CalendarDateFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields, + Handle<PlainObject*> maybeOptions) { + MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar, + CalendarMethod::DateFromFields)); + + // Step 1. (Not applicable in our implemetation.) + + // Step 3. (Reordered) + auto dateFromFields = calendar.dateFromFields(); + if (!dateFromFields) { + return BuiltinCalendarDateFromFields(cx, fields, maybeOptions); + } + + // Step 2. (Inlined call to CalendarMethodsRecordCall.) + + Rooted<Value> dateFromFieldsFn(cx, ObjectValue(*dateFromFields)); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> rval(cx); + + FixedInvokeArgs<2> args(cx); + args[0].setObject(*fields); + if (maybeOptions) { + args[1].setObject(*maybeOptions); + } else { + args[1].setUndefined(); + } + + if (!Call(cx, dateFromFieldsFn, thisv, args, &rval)) { + return nullptr; + } + + // Step 4. + if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainDateObject>()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval, + nullptr, "not a PlainDate object"); + return nullptr; + } + + // Step 5. + return &rval.toObject(); +} + +/** + * CalendarDateFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<PlainObject*> fields) { + // Steps 1-6. + return ::CalendarDateFromFields(cx, calendar, fields, nullptr); +} + +/** + * CalendarDateFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields, + Handle<PlainObject*> options) { + // Steps 1-6. + return ::CalendarDateFromFields(cx, calendar, fields, options); +} + +struct RegulatedISOYearMonth final { + double year; + int32_t month; +}; + +/** + * RegulateISOYearMonth ( year, month, overflow ) + */ +static bool RegulateISOYearMonth(JSContext* cx, double year, double month, + TemporalOverflow overflow, + RegulatedISOYearMonth* result) { + // Step 1. + MOZ_ASSERT(IsInteger(year)); + MOZ_ASSERT(IsInteger(month)); + + // Step 2. (Not applicable in our implementation.) + + // Step 3. + if (overflow == TemporalOverflow::Constrain) { + // Step 3.a. + month = std::clamp(month, 1.0, 12.0); + + // Step 3.b. + *result = {year, int32_t(month)}; + return true; + } + + // Step 4.a. + MOZ_ASSERT(overflow == TemporalOverflow::Reject); + + // Step 4.b. + if (month < 1 || month > 12) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID); + return false; + } + + // Step 4.c. + *result = {year, int32_t(month)}; + return true; +} + +/** + * ISOYearMonthFromFields ( fields, overflow ) + */ +static bool ISOYearMonthFromFields(JSContext* cx, Handle<TemporalFields> fields, + TemporalOverflow overflow, + PlainDate* result) { + // Steps 1-2. (Not applicable in our implementation.) + + // Step 3. + double year = fields.year(); + + // Step 4. + double month = fields.month(); + + // Step 5. + MOZ_ASSERT(!std::isnan(year) && !std::isnan(month)); + + // Step 6. + RegulatedISOYearMonth regulated; + if (!RegulateISOYearMonth(cx, year, month, overflow, ®ulated)) { + return false; + } + + // Step 7. + + // The result is used to create a new PlainYearMonthObject, so it's okay to + // directly throw an error for invalid years. That way we don't have to worry + // about representing doubles in PlainDate structs. + int32_t intYear; + if (!mozilla::NumberEqualsInt32(regulated.year, &intYear)) { + // CreateTemporalYearMonth, steps 1-2. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID); + return false; + } + + *result = {intYear, regulated.month, 1}; + return true; +} + +/** + * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] ) + */ +static PlainYearMonthObject* BuiltinCalendarYearMonthFromFields( + JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) { + // Steps 1-5. (Not applicable) + + // Step 6. + Rooted<TemporalFields> dateFields(cx); + if (!PrepareTemporalFields( + cx, fields, + {TemporalField::Month, TemporalField::MonthCode, TemporalField::Year}, + {TemporalField::Year}, &dateFields)) { + return nullptr; + } + + // Step 7. + auto overflow = TemporalOverflow::Constrain; + if (maybeOptions) { + if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) { + return nullptr; + } + } + + // Step 8. + if (!ISOResolveMonth(cx, &dateFields)) { + return nullptr; + } + + // Step 9. + PlainDate result; + if (!ISOYearMonthFromFields(cx, dateFields, overflow, &result)) { + return nullptr; + } + + // Step 10. + Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601)); + return CreateTemporalYearMonth(cx, result, calendar); +} + +/** + * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] ) + */ +static Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields, + Handle<PlainObject*> maybeOptions) { + MOZ_ASSERT(CalendarMethodsRecordHasLookedUp( + calendar, CalendarMethod::YearMonthFromFields)); + + // Step 1. (Not applicable in our implementation.) + + // Step 3. (Reordered) + auto yearMonthFromFields = calendar.yearMonthFromFields(); + if (!yearMonthFromFields) { + return BuiltinCalendarYearMonthFromFields(cx, fields, maybeOptions); + } + + // Step 2. (Inlined call to CalendarMethodsRecordCall.) + + Rooted<Value> yearMonthFromFieldsFn(cx, ObjectValue(*yearMonthFromFields)); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> rval(cx); + + FixedInvokeArgs<2> args(cx); + args[0].setObject(*fields); + if (maybeOptions) { + args[1].setObject(*maybeOptions); + } else { + args[1].setUndefined(); + } + + if (!Call(cx, yearMonthFromFieldsFn, thisv, args, &rval)) { + return nullptr; + } + + // Step 4. + if (!rval.isObject() || + !rval.toObject().canUnwrapAs<PlainYearMonthObject>()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval, + nullptr, "not a PlainYearMonth object"); + return nullptr; + } + + // Step 5. + return &rval.toObject(); +} + +/** + * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<PlainObject*> fields) { + // Steps 1-4. + return ::CalendarYearMonthFromFields(cx, calendar, fields, nullptr); +} + +/** + * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<PlainYearMonthObject*> fields) { + // Steps 1-4. + return ::CalendarYearMonthFromFields(cx, calendar, fields, nullptr); +} + +/** + * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields, + Handle<PlainObject*> options) { + // Steps 1-4. + return ::CalendarYearMonthFromFields(cx, calendar, fields, options); +} + +/** + * ISOMonthDayFromFields ( fields, overflow ) + */ +static bool ISOMonthDayFromFields(JSContext* cx, Handle<TemporalFields> fields, + TemporalOverflow overflow, + PlainDate* result) { + // Steps 1-2. (Not applicable in our implementation.) + + // Step 3. + double month = fields.month(); + + // Step 4. + double day = fields.day(); + + // Step 5. + MOZ_ASSERT(!std::isnan(month)); + MOZ_ASSERT(!std::isnan(day)); + + // Step 6. + double year = fields.year(); + + // Step 7. + int32_t referenceISOYear = 1972; + + // Steps 8-9. + double y = std::isnan(year) ? referenceISOYear : year; + RegulatedISODate regulated; + if (!RegulateISODate(cx, y, month, day, overflow, ®ulated)) { + return false; + } + + // Step 10. + *result = {referenceISOYear, regulated.month, regulated.day}; + return true; +} + +/** + * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] ) + */ +static PlainMonthDayObject* BuiltinCalendarMonthDayFromFields( + JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) { + // Steps 1-5. (Not applicable) + + // Step 6. + Rooted<TemporalFields> dateFields(cx); + if (!PrepareTemporalFields(cx, fields, + {TemporalField::Day, TemporalField::Month, + TemporalField::MonthCode, TemporalField::Year}, + {TemporalField::Day}, &dateFields)) { + return nullptr; + } + + // Step 7. + auto overflow = TemporalOverflow::Constrain; + if (maybeOptions) { + if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) { + return nullptr; + } + } + + // Step 8. + if (!ISOResolveMonth(cx, &dateFields)) { + return nullptr; + } + + // Step 9. + PlainDate result; + if (!ISOMonthDayFromFields(cx, dateFields, overflow, &result)) { + return nullptr; + } + + // Step 10. + Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601)); + return CreateTemporalMonthDay(cx, result, calendar); +} + +/** + * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] ) + */ +static Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields, + Handle<PlainObject*> maybeOptions) { + MOZ_ASSERT(CalendarMethodsRecordHasLookedUp( + calendar, CalendarMethod::MonthDayFromFields)); + + // Step 1. (Not applicable in our implementation.) + + // Step 3. (Reordered) + auto monthDayFromFields = calendar.monthDayFromFields(); + if (!monthDayFromFields) { + return BuiltinCalendarMonthDayFromFields(cx, fields, maybeOptions); + } + + // Step 2. (Inlined call to CalendarMethodsRecordCall.) + + Rooted<Value> monthDayFromFieldsFn(cx, ObjectValue(*monthDayFromFields)); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> rval(cx); + + FixedInvokeArgs<2> args(cx); + args[0].setObject(*fields); + if (maybeOptions) { + args[1].setObject(*maybeOptions); + } else { + args[1].setUndefined(); + } + + if (!Call(cx, monthDayFromFieldsFn, thisv, args, &rval)) { + return nullptr; + } + + // Step 4. + if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainMonthDayObject>()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval, + nullptr, "not a PlainMonthDay object"); + return nullptr; + } + + // Step 5. + return &rval.toObject(); +} + +/** + * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<PlainObject*> fields) { + // Steps 1-4. + return ::CalendarMonthDayFromFields(cx, calendar, fields, nullptr); +} + +/** + * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<PlainMonthDayObject*> fields) { + // Steps 1-4. + return ::CalendarMonthDayFromFields(cx, calendar, fields, nullptr); +} + +/** + * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] ) + */ +Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields, + Handle<PlainObject*> options) { + // Steps 1-4. + return ::CalendarMonthDayFromFields(cx, calendar, fields, options); +} + +using PropertyHashSet = JS::GCHashSet<JS::PropertyKey>; +using PropertyVector = JS::StackGCVector<JS::PropertyKey>; + +/** + * ISOFieldKeysToIgnore ( keys ) + */ +static bool ISOFieldKeysToIgnore(JSContext* cx, const PropertyVector& keys, + PropertyHashSet& ignoredKeys) { + MOZ_ASSERT(ignoredKeys.empty(), "expected an empty output hashset"); + + // Step 1. (Not applicable in our implementation.) + + if (!ignoredKeys.reserve(keys.length())) { + return false; + } + + // Step 2. + bool seenMonthOrMonthCode = false; + for (auto& key : keys) { + // Reorder the substeps in order to use |putNew| instead of |put|, because + // the former is slightly faster. + + // Steps 2.a-c. + if (key.isAtom(cx->names().month) || key.isAtom(cx->names().monthCode)) { + // Steps 2.b-c. + if (!seenMonthOrMonthCode) { + seenMonthOrMonthCode = true; + + // Add both keys at once. + if (!ignoredKeys.putNew(NameToId(cx->names().month)) || + !ignoredKeys.putNew(NameToId(cx->names().monthCode))) { + return false; + } + } + } else { + // Step 2.a. + if (!ignoredKeys.putNew(key)) { + return false; + } + } + } + + // Step 3. + return true; +} + +/** + * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields ) + */ +static PlainObject* BuiltinCalendarMergeFields( + JSContext* cx, Handle<JSObject*> fields, + Handle<JSObject*> additionalFields) { + // NOTE: This function is only called from CalendarMergeFields and its + // result is always passed to PrepareTemporalFields. PrepareTemporalFields + // sorts the incoming property keys, so it doesn't matter in which order the + // properties are created in this function. This allows to reorder the steps + // to process |additionalFields| first. + + // TODO: Consider additionally passing the fieldNames from the succeeding + // PrepareTemporalFields call, so we don't have to process unnecessary keys. + + // Steps 1-2. (Not applicable in our implementation.) + + // Step 13. + Rooted<PlainObject*> merged(cx, NewPlainObjectWithProto(cx, nullptr)); + if (!merged) { + return nullptr; + } + + // Contrary to the spec, add the properties from |additionalFields| first. + + // Step 6. (Not applicable in our implementation.) + + // Steps 7-8. + // + // PrepareTemporalFields ignores symbol property keys, so we don't need to + // pass JSITER_SYMBOLS. |additionalFields| contains no non-enumerable + // properties, therefore JSITER_HIDDEN isn't needed, too. + JS::RootedVector<PropertyKey> keys(cx); + if (!GetPropertyKeys(cx, additionalFields, JSITER_OWNONLY, &keys)) { + return nullptr; + } + + // Steps 9-11. (Not applicable in our implementation.) + + // Step 12. + Rooted<PropertyHashSet> ignoredKeys(cx, PropertyHashSet(cx)); + if (!ignoredKeys.reserve(keys.length())) { + return nullptr; + } + + // Steps 7-8, 12, and 15. + Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); + Rooted<Value> propValue(cx); + bool seenMonthOrMonthCode = false; + for (size_t i = 0; i < keys.length(); i++) { + Handle<PropertyKey> key = keys[i]; + + if (!GetOwnPropertyDescriptor(cx, additionalFields, key, &desc)) { + return nullptr; + } + + propValue.set(desc->value()); + + // Skip |undefined| properties per step 8. + if (propValue.isUndefined()) { + continue; + } + + // Step 15. + if (!DefineDataProperty(cx, merged, key, propValue)) { + return nullptr; + } + + // Step 12. (Inlined ISOFieldKeysToIgnore) + if (key.isAtom(cx->names().month) || key.isAtom(cx->names().monthCode)) { + // ISOFieldKeysToIgnore, steps 2.b-c. + if (!seenMonthOrMonthCode) { + seenMonthOrMonthCode = true; + + if (!ignoredKeys.putNew(NameToId(cx->names().month)) || + !ignoredKeys.putNew(NameToId(cx->names().monthCode))) { + return nullptr; + } + } + } else { + // ISOFieldKeysToIgnore, step 2.a. + if (!ignoredKeys.putNew(key)) { + return nullptr; + } + } + } + + // Now add the properties from |field|. + + // Step 3. (Not applicable in our implementation.) + + // Reuse |keys| to avoid extra allocations. + keys.clear(); + + // Steps 4-5. + // + // See above why neither JSITER_SYMBOLS nor JSITER_HIDDEN is needed. + if (!GetPropertyKeys(cx, fields, JSITER_OWNONLY, &keys)) { + return nullptr; + } + + // Steps 4-5 and 14. + for (size_t i = 0; i < keys.length(); i++) { + Handle<PropertyKey> key = keys[i]; + + // Skip ignored keys per step 14. + if (ignoredKeys.has(key)) { + continue; + } + + if (!GetOwnPropertyDescriptor(cx, fields, key, &desc)) { + return nullptr; + } + + propValue.set(desc->value()); + + // Skip |undefined| properties per step 5. + if (propValue.isUndefined()) { + continue; + } + + // Step 14. + if (!DefineDataProperty(cx, merged, key, propValue)) { + return nullptr; + } + } + + // Step 16. + return merged; +} + +#ifdef DEBUG +static bool IsPlainDataObject(PlainObject* obj) { + for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) { + if (iter->flags() != PropertyFlags::defaultDataPropFlags) { + return false; + } + } + return true; +} +#endif + +/** + * CalendarMergeFields ( calendarRec, fields, additionalFields ) + */ +JSObject* js::temporal::CalendarMergeFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields, + Handle<PlainObject*> additionalFields) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::MergeFields)); + + MOZ_ASSERT(IsPlainDataObject(fields)); + MOZ_ASSERT(IsPlainDataObject(additionalFields)); + + // Step 2. (Reordered) + auto mergeFields = calendar.mergeFields(); + if (!mergeFields) { + return BuiltinCalendarMergeFields(cx, fields, additionalFields); + } + + // Step 1. (Inlined call to CalendarMethodsRecordCall.) + + Rooted<Value> mergeFieldsFn(cx, ObjectValue(*mergeFields)); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> result(cx); + + FixedInvokeArgs<2> args(cx); + args[0].setObject(*fields); + args[1].setObject(*additionalFields); + + if (!Call(cx, mergeFieldsFn, thisv, args, &result)) { + return nullptr; + } + + // Steps 3-4. + return RequireObject(cx, result); +} + +/** + * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ) + */ +static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date, + const Duration& duration, + Handle<JSObject*> options, PlainDate* result) { + MOZ_ASSERT(IsValidISODate(date)); + MOZ_ASSERT(IsValidDuration(duration)); + + // Steps 1-6. (Not applicable) + + // Step 7. + auto overflow = TemporalOverflow::Constrain; + if (options) { + if (!ToTemporalOverflow(cx, options, &overflow)) { + return false; + } + } + + // Step 8. + TimeDuration balanceResult; + if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &balanceResult)) { + return false; + } + + // Step 9. + Duration addDuration = {duration.years, duration.months, duration.weeks, + balanceResult.days}; + return AddISODate(cx, date, addDuration, overflow, result); +} + +/** + * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ) + */ +static PlainDateObject* BuiltinCalendarAdd(JSContext* cx, const PlainDate& date, + const Duration& duration, + Handle<JSObject*> options) { + // Steps 1-6. (Not applicable) + + // Steps 7-9. + PlainDate result; + if (!BuiltinCalendarAdd(cx, date, duration, options, &result)) { + return nullptr; + } + + // Step 10. + Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601)); + return CreateTemporalDate(cx, result, calendar); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +static Wrapped<PlainDateObject*> CalendarDateAddSlow( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, + Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd)); + MOZ_ASSERT(calendar.receiver().isObject()); + MOZ_ASSERT(calendar.dateAdd()); + + // Step 1. (Not applicable). + + // Step 2. (Inlined call to CalendarMethodsRecordCall.) + Rooted<JS::Value> dateAdd(cx, ObjectValue(*calendar.dateAdd())); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> rval(cx); + + FixedInvokeArgs<3> args(cx); + args[0].setObject(*date); + args[1].setObject(*duration); + if (options) { + args[2].setObject(*options); + } else { + args[2].setUndefined(); + } + + if (!Call(cx, dateAdd, thisv, args, &rval)) { + return nullptr; + } + + // Step 3. (Not applicable) + MOZ_ASSERT(!CalendarMethodsRecordIsBuiltin(calendar)); + + // Step 4. + if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainDateObject>()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval, + nullptr, "not a PlainDate object"); + return nullptr; + } + + // Step 5. + return &rval.toObject(); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +static Wrapped<PlainDateObject*> CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, const Duration& duration, + Handle<JSObject*> options) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd)); + + // Step 1. (Not applicable). + + // Step 3. (Reordered) + if (!calendar.dateAdd()) { + auto* unwrappedDate = date.unwrap(cx); + if (!unwrappedDate) { + return nullptr; + } + auto date = ToPlainDate(unwrappedDate); + + return BuiltinCalendarAdd(cx, date, duration, options); + } + + // Steps 2 and 4-5. + Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration)); + if (!durationObj) { + return nullptr; + } + return CalendarDateAddSlow(cx, calendar, date, durationObj, options); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +static Wrapped<PlainDateObject*> CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, + Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd)); + + // Step 1. (Not applicable). + + // Step 3. (Reordered) + if (!calendar.dateAdd()) { + auto* unwrappedDate = date.unwrap(cx); + if (!unwrappedDate) { + return nullptr; + } + auto date = ToPlainDate(unwrappedDate); + + auto* unwrappedDuration = duration.unwrap(cx); + if (!unwrappedDuration) { + return nullptr; + } + auto duration = ToDuration(unwrappedDuration); + + return BuiltinCalendarAdd(cx, date, duration, options); + } + + // Steps 2 and 4-5. + return CalendarDateAddSlow(cx, calendar, date, duration, options); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, + const Duration& duration, Handle<JSObject*> options, + PlainDate* result) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd)); + + // Step 1. (Not applicable). + + // Step 3. (Reordered) + if (!calendar.dateAdd()) { + auto* unwrappedDate = date.unwrap(cx); + if (!unwrappedDate) { + return false; + } + auto date = ToPlainDate(unwrappedDate); + + return BuiltinCalendarAdd(cx, date, duration, options, result); + } + + // Steps 2 and 4-5. + + Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration)); + if (!durationObj) { + return false; + } + + auto obj = CalendarDateAddSlow(cx, calendar, date, durationObj, options); + if (!obj) { + return false; + } + + *result = ToPlainDate(&obj.unwrap()); + return true; +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar, + const PlainDate& date, const Duration& duration, + Handle<JSObject*> options, PlainDate* result) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd)); + + // Step 1. (Not applicable). + + // Step 3. (Reordered) + if (!calendar.dateAdd()) { + return BuiltinCalendarAdd(cx, date, duration, options, result); + } + + // Steps 2 and 4-5. + + Rooted<PlainDateObject*> dateObj( + cx, CreateTemporalDate(cx, date, calendar.receiver())); + if (!dateObj) { + return false; + } + + Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration)); + if (!durationObj) { + return false; + } + + auto obj = CalendarDateAddSlow(cx, calendar, dateObj, durationObj, options); + if (!obj) { + return false; + } + + *result = ToPlainDate(&obj.unwrap()); + return true; +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, const Duration& duration, + Handle<JSObject*> options) { + // Step 1. (Not applicable). + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, const Duration& duration) { + // Step 1. + Handle<JSObject*> options = nullptr; + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, + Handle<Wrapped<DurationObject*>> duration) { + // Step 1. + Handle<JSObject*> options = nullptr; + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, + Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) { + // Step 1. (Not applicable). + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +bool js::temporal::CalendarDateAdd(JSContext* cx, + Handle<CalendarRecord> calendar, + const PlainDate& date, + const Duration& duration, + PlainDate* result) { + // Step 1. + Handle<JSObject*> options = nullptr; + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options, result); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +bool js::temporal::CalendarDateAdd( + JSContext* cx, Handle<CalendarRecord> calendar, const PlainDate& date, + const Duration& duration, Handle<JSObject*> options, PlainDate* result) { + // Step 1. (Not applicable) + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options, result); +} + +/** + * CalendarDateAdd ( calendarRec, date, duration [ , options ] ) + */ +bool js::temporal::CalendarDateAdd(JSContext* cx, + Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> date, + const Duration& duration, + PlainDate* result) { + // Step 1. + Handle<JSObject*> options = nullptr; + + // Steps 2-5. + return ::CalendarDateAdd(cx, calendar, date, duration, options, result); +} + +/** + * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ) + */ +static Duration BuiltinCalendarDateUntil(const PlainDate& one, + const PlainDate& two, + TemporalUnit largestUnit) { + // Steps 1-3. (Not applicable) + + // Steps 4-8. (Not applicable) + + // Step 9. + auto difference = DifferenceISODate(one, two, largestUnit); + + // Step 10. + return difference.toDuration(); +} + +/** + * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ) + */ +static bool BuiltinCalendarDateUntil(JSContext* cx, + Handle<Wrapped<PlainDateObject*>> one, + Handle<Wrapped<PlainDateObject*>> two, + TemporalUnit largestUnit, + Duration* result) { + MOZ_ASSERT(largestUnit <= TemporalUnit::Day); + + auto* unwrappedOne = one.unwrap(cx); + if (!unwrappedOne) { + return false; + } + auto dateOne = ToPlainDate(unwrappedOne); + + auto* unwrappedTwo = two.unwrap(cx); + if (!unwrappedTwo) { + return false; + } + auto dateTwo = ToPlainDate(unwrappedTwo); + + // Steps 1-10. + *result = BuiltinCalendarDateUntil(dateOne, dateTwo, largestUnit); + return true; +} + +/** + * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ) + */ +static bool BuiltinCalendarDateUntil(JSContext* cx, + Handle<Wrapped<PlainDateObject*>> one, + Handle<Wrapped<PlainDateObject*>> two, + Handle<JSObject*> options, + Duration* result) { + // Steps 1-6. (Not applicable) + + // Steps 7-8. + auto largestUnit = TemporalUnit::Day; + if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit, + TemporalUnitGroup::Date, &largestUnit)) { + return false; + } + + // Steps 9-10. + return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result); +} + +static bool CalendarDateUntilSlow(JSContext* cx, + Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> one, + Handle<Wrapped<PlainDateObject*>> two, + Handle<JSObject*> options, Duration* result) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil)); + MOZ_ASSERT(calendar.receiver().isObject()); + MOZ_ASSERT(calendar.dateUntil()); + + // Step 1. (Inlined call to CalendarMethodsRecordCall.) + Rooted<JS::Value> dateUntil(cx, ObjectValue(*calendar.dateUntil())); + auto thisv = calendar.receiver().toValue(); + Rooted<Value> rval(cx); + + FixedInvokeArgs<3> args(cx); + args[0].setObject(*one); + args[1].setObject(*two); + args[2].setObject(*options); + + if (!Call(cx, dateUntil, thisv, args, &rval)) { + return false; + } + + // Step 2. (Not applicable) + MOZ_ASSERT(!CalendarMethodsRecordIsBuiltin(calendar)); + + // Step 3. + if (!rval.isObject() || !rval.toObject().canUnwrapAs<DurationObject>()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval, + nullptr, "not a Duration object"); + return false; + } + + // Step 4. + *result = ToDuration(&rval.toObject().unwrapAs<DurationObject>()); + return true; +} + +/** + * CalendarDateUntil ( calendarRec, one, two, options ) + */ +bool js::temporal::CalendarDateUntil(JSContext* cx, + Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> one, + Handle<Wrapped<PlainDateObject*>> two, + Handle<PlainObject*> options, + Duration* result) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil)); + + // Step 2. (Reordered) + if (!calendar.dateUntil()) { + return BuiltinCalendarDateUntil(cx, one, two, options, result); + } + + // Steps 1 and 3-4. + return CalendarDateUntilSlow(cx, calendar, one, two, options, result); +} + +/** + * CalendarDateUntil ( calendarRec, one, two, options ) + */ +bool js::temporal::CalendarDateUntil(JSContext* cx, + Handle<CalendarRecord> calendar, + Handle<Wrapped<PlainDateObject*>> one, + Handle<Wrapped<PlainDateObject*>> two, + TemporalUnit largestUnit, + Duration* result) { + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil)); + MOZ_ASSERT(largestUnit <= TemporalUnit::Day); + + // Step 2. (Reordered) + if (!calendar.dateUntil()) { + return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result); + } + + Rooted<PlainObject*> untilOptions(cx, NewPlainObjectWithProto(cx, nullptr)); + if (!untilOptions) { + return false; + } + + Rooted<Value> value(cx, StringValue(TemporalUnitToString(cx, largestUnit))); + if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit, value)) { + return false; + } + + // Steps 1 and 3-4. + return CalendarDateUntilSlow(cx, calendar, one, two, untilOptions, result); +} + +/** + * CalendarEquals ( one, two ) + */ +bool js::temporal::CalendarEquals(JSContext* cx, Handle<CalendarValue> one, + Handle<CalendarValue> two, bool* equals) { + // Step 1. + if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) { + *equals = true; + return true; + } + + // Step 2. + Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one)); + if (!calendarOne) { + return false; + } + + // Step 3. + JSString* calendarTwo = ToTemporalCalendarIdentifier(cx, two); + if (!calendarTwo) { + return false; + } + + // Steps 4-5. + return EqualStrings(cx, calendarOne, calendarTwo, equals); +} + +/** + * CalendarEquals ( one, two ) + */ +bool js::temporal::CalendarEqualsOrThrow(JSContext* cx, + Handle<CalendarValue> one, + Handle<CalendarValue> two) { + // Step 1. + if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) { + return true; + } + + // Step 2. + Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one)); + if (!calendarOne) { + return false; + } + + // Step 3. + JSString* calendarTwo = ToTemporalCalendarIdentifier(cx, two); + if (!calendarTwo) { + return false; + } + + // Steps 4-5. + bool equals; + if (!EqualStrings(cx, calendarOne, calendarTwo, &equals)) { + return false; + } + if (equals) { + return true; + } + + // Throw an error when the calendar identifiers don't match. Used when unequal + // calendars throw a RangeError. + if (auto charsOne = QuoteString(cx, calendarOne)) { + if (auto charsTwo = QuoteString(cx, calendarTwo)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, + charsOne.get(), charsTwo.get()); + } + } + return false; +} + +/** + * ConsolidateCalendars ( one, two ) + */ +bool js::temporal::ConsolidateCalendars(JSContext* cx, + Handle<CalendarValue> one, + Handle<CalendarValue> two, + MutableHandle<CalendarValue> result) { + // Step 1. + if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) { + result.set(two); + return true; + } + + // Step 2. + Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one)); + if (!calendarOne) { + return false; + } + + // Step 3. + Rooted<JSString*> calendarTwo(cx, ToTemporalCalendarIdentifier(cx, two)); + if (!calendarTwo) { + return false; + } + + // Step 4. + bool equals; + if (!EqualStrings(cx, calendarOne, calendarTwo, &equals)) { + return false; + } + if (equals) { + result.set(two); + return true; + } + + // Step 5. + bool isoCalendarOne; + if (!IsISO8601Calendar(cx, calendarOne, &isoCalendarOne)) { + return false; + } + if (isoCalendarOne) { + result.set(two); + return true; + } + + // Step 6. + bool isoCalendarTwo; + if (!IsISO8601Calendar(cx, calendarTwo, &isoCalendarTwo)) { + return false; + } + if (isoCalendarTwo) { + result.set(one); + return true; + } + + // Step 7. + if (auto charsOne = QuoteString(cx, calendarOne)) { + if (auto charsTwo = QuoteString(cx, calendarTwo)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, + charsOne.get(), charsTwo.get()); + } + } + return false; +} + +/** + * Temporal.Calendar ( id ) + */ +static bool CalendarConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Temporal.Calendar")) { + return false; + } + + // Step 2. + if (!args.requireAtLeast(cx, "Temporal.Calendar", 1)) { + return false; + } + + if (!args[0].isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0], + nullptr, "not a string"); + return false; + } + + Rooted<JSLinearString*> identifier(cx, args[0].toString()->ensureLinear(cx)); + if (!identifier) { + return false; + } + + // Step 3. + identifier = ThrowIfNotBuiltinCalendar(cx, identifier); + if (!identifier) { + return false; + } + + // Step 4. + auto* calendar = CreateTemporalCalendar(cx, args, identifier); + if (!calendar) { + return false; + } + + args.rval().setObject(*calendar); + return true; +} + +/** + * Temporal.Calendar.from ( item ) + */ +static bool Calendar_from(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted<CalendarValue> calendar(cx); + if (!ToTemporalCalendar(cx, args.get(0), &calendar)) { + return false; + } + + // Step 2. + auto* obj = ToTemporalCalendarObject(cx, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * get Temporal.Calendar.prototype.id + */ +static bool Calendar_id(JSContext* cx, const CallArgs& args) { + auto* calendar = &args.thisv().toObject().as<CalendarObject>(); + + // Step 3. + args.rval().setString(calendar->identifier()); + return true; +} + +/** + * get Temporal.Calendar.prototype.id + */ +static bool Calendar_id(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_id>(cx, args); +} + +/** + * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] ) + */ +static bool Calendar_dateFromFields(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + Rooted<JSObject*> fields( + cx, RequireObjectArg(cx, "fields", "dateFromFields", args.get(0))); + if (!fields) { + return false; + } + + // Step 5. + Rooted<JSObject*> options(cx); + if (args.hasDefined(1)) { + options = RequireObjectArg(cx, "options", "dateFromFields", args[1]); + if (!options) { + return false; + } + } + + // Steps 6-10. + auto* obj = BuiltinCalendarDateFromFields(cx, fields, options); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] ) + */ +static bool Calendar_dateFromFields(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_dateFromFields>(cx, args); +} + +/** + * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] ) + */ +static bool Calendar_yearMonthFromFields(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + Rooted<JSObject*> fields( + cx, RequireObjectArg(cx, "fields", "yearMonthFromFields", args.get(0))); + if (!fields) { + return false; + } + + // Step 5. + Rooted<JSObject*> options(cx); + if (args.hasDefined(1)) { + options = RequireObjectArg(cx, "options", "yearMonthFromFields", args[1]); + if (!options) { + return false; + } + } + + // Steps 6-10. + auto* obj = BuiltinCalendarYearMonthFromFields(cx, fields, options); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] ) + */ +static bool Calendar_yearMonthFromFields(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_yearMonthFromFields>(cx, + args); +} + +/** + * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] ) + */ +static bool Calendar_monthDayFromFields(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + Rooted<JSObject*> fields( + cx, RequireObjectArg(cx, "fields", "monthDayFromFields", args.get(0))); + if (!fields) { + return false; + } + + // Step 5. + Rooted<JSObject*> options(cx); + if (args.hasDefined(1)) { + options = RequireObjectArg(cx, "options", "monthDayFromFields", args[1]); + if (!options) { + return false; + } + } + + // Steps 6-10. + auto* obj = BuiltinCalendarMonthDayFromFields(cx, fields, options); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] ) + */ +static bool Calendar_monthDayFromFields(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_monthDayFromFields>(cx, + args); +} + +/** + * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ) + */ +static bool Calendar_dateAdd(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToTemporalDate(cx, args.get(0), &date)) { + return false; + } + + // Step 5. + Duration duration; + if (!ToTemporalDuration(cx, args.get(1), &duration)) { + return false; + } + + // Step 6. + Rooted<JSObject*> options(cx); + if (args.hasDefined(2)) { + options = RequireObjectArg(cx, "options", "dateAdd", args[2]); + if (!options) { + return false; + } + } + + // Steps 7-10. + auto* obj = BuiltinCalendarAdd(cx, date, duration, options); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ) + */ +static bool Calendar_dateAdd(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_dateAdd>(cx, args); +} + +/** + * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ) + */ +static bool Calendar_dateUntil(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate one; + if (!ToTemporalDate(cx, args.get(0), &one)) { + return false; + } + + // Step 5. + PlainDate two; + if (!ToTemporalDate(cx, args.get(1), &two)) { + return false; + } + + // Steps 6-8. + auto largestUnit = TemporalUnit::Day; + if (args.hasDefined(2)) { + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", "dateUntil", args[2])); + if (!options) { + return false; + } + + // Steps 7-8. + if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit, + TemporalUnitGroup::Date, &largestUnit)) { + return false; + } + } + + // Steps 9-10. + auto duration = BuiltinCalendarDateUntil(one, two, largestUnit); + + auto* obj = CreateTemporalDuration(cx, duration); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ) + */ +static bool Calendar_dateUntil(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_dateUntil>(cx, args); +} + +/** + * Temporal.Calendar.prototype.year ( temporalDateLike ) + */ +static bool Calendar_year(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>( + cx, args.get(0), &date)) { + return false; + } + + // Steps 5-6. + return BuiltinCalendarYear(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.year ( temporalDateLike ) + */ +static bool Calendar_year(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_year>(cx, args); +} + +/** + * Temporal.Calendar.prototype.month ( temporalDateLike ) + */ +static bool Calendar_month(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + Handle<Value> temporalDateLike = args.get(0); + + // Step 4. + if (temporalDateLike.isObject() && + temporalDateLike.toObject().canUnwrapAs<PlainMonthDayObject>()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, + temporalDateLike, nullptr, "a PlainMonthDay object"); + return false; + } + + // Step 5. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>( + cx, temporalDateLike, &date)) { + return false; + } + + // Steps 6-7. + return BuiltinCalendarMonth(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.month ( temporalDateLike ) + */ +static bool Calendar_month(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_month>(cx, args); +} + +/** + * Temporal.Calendar.prototype.monthCode ( temporalDateLike ) + */ +static bool Calendar_monthCode(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainMonthDayObject, + PlainYearMonthObject>(cx, args.get(0), &date)) { + return false; + } + + // Steps 5-6. + return BuiltinCalendarMonthCode(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.monthCode ( temporalDateLike ) + */ +static bool Calendar_monthCode(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_monthCode>(cx, args); +} + +/** + * Temporal.Calendar.prototype.day ( temporalDateLike ) + */ +static bool Calendar_day(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainMonthDayObject>( + cx, args.get(0), &date)) { + return false; + } + + // Steps 5-6. + return BuiltinCalendarDay(date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.day ( temporalDateLike ) + */ +static bool Calendar_day(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_day>(cx, args); +} + +/** + * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike ) + */ +static bool Calendar_dayOfWeek(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToTemporalDate(cx, args.get(0), &date)) { + return false; + } + + // Steps 5-9. + return BuiltinCalendarDayOfWeek(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike ) + */ +static bool Calendar_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_dayOfWeek>(cx, args); +} + +/** + * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike ) + */ +static bool Calendar_dayOfYear(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToTemporalDate(cx, args.get(0), &date)) { + return false; + } + + // Steps 5-7. + return BuiltinCalendarDayOfYear(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike ) + */ +static bool Calendar_dayOfYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_dayOfYear>(cx, args); +} + +/** + * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike ) + */ +static bool Calendar_weekOfYear(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToTemporalDate(cx, args.get(0), &date)) { + return false; + } + + // Steps 5-6. + return BuiltinCalendarWeekOfYear(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike ) + */ +static bool Calendar_weekOfYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_weekOfYear>(cx, args); +} + +/** + * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike ) + */ +static bool Calendar_yearOfWeek(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToTemporalDate(cx, args.get(0), &date)) { + return false; + } + + // Steps 5-6. + return BuiltinCalendarYearOfWeek(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike ) + */ +static bool Calendar_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_yearOfWeek>(cx, args); +} + +/** + * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike ) + */ +static bool Calendar_daysInWeek(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToTemporalDate(cx, args.get(0), &date)) { + return false; + } + + // Step 5. + return BuiltinCalendarDaysInWeek(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike ) + */ +static bool Calendar_daysInWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_daysInWeek>(cx, args); +} + +/** + * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike ) + */ +static bool Calendar_daysInMonth(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>( + cx, args.get(0), &date)) { + return false; + } + + // Step 5. + return BuiltinCalendarDaysInMonth(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike ) + */ +static bool Calendar_daysInMonth(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_daysInMonth>(cx, args); +} + +/** + * Temporal.Calendar.prototype.daysInYear ( temporalDateLike ) + */ +static bool Calendar_daysInYear(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>( + cx, args.get(0), &date)) { + return false; + } + + // Step 5. + return BuiltinCalendarDaysInYear(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.daysInYear ( temporalDateLike ) + */ +static bool Calendar_daysInYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_daysInYear>(cx, args); +} + +/** + * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike ) + */ +static bool Calendar_monthsInYear(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>( + cx, args.get(0), &date)) { + return false; + } + + // Step 5. + return BuiltinCalendarMonthsInYear(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike ) + */ +static bool Calendar_monthsInYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_monthsInYear>(cx, args); +} + +/** + * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike ) + */ +static bool Calendar_inLeapYear(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + PlainDate date; + if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>( + cx, args.get(0), &date)) { + return false; + } + + // Steps 5-6. + return BuiltinCalendarInLeapYear(cx, date, args.rval()); +} + +/** + * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike ) + */ +static bool Calendar_inLeapYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_inLeapYear>(cx, args); +} + +/** + * Temporal.Calendar.prototype.fields ( fields ) + */ +static bool Calendar_fields(JSContext* cx, const CallArgs& args) { + // Step 3. + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 4. + JS::ForOfIterator iterator(cx); + if (!iterator.init(args.get(0))) { + return false; + } + + // Step 5. + JS::RootedVector<Value> fieldNames(cx); + mozilla::EnumSet<CalendarField> seen; + + // Steps 6-7. + Rooted<Value> nextValue(cx); + Rooted<JSLinearString*> linear(cx); + while (true) { + // Steps 7.a and 7.b.i. + bool done; + if (!iterator.next(&nextValue, &done)) { + return false; + } + if (done) { + break; + } + + // Step 7.b.ii. + if (!nextValue.isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue, + nullptr, "not a string"); + iterator.closeThrow(); + return false; + } + + linear = nextValue.toString()->ensureLinear(cx); + if (!linear) { + return false; + } + + // Step 7.b.iv. (Reordered) + CalendarField field; + if (!ToCalendarField(cx, linear, &field)) { + iterator.closeThrow(); + return false; + } + + // Step 7.b.iii. + if (seen.contains(field)) { + if (auto chars = QuoteString(cx, linear, '"')) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_CALENDAR_DUPLICATE_FIELD, + chars.get()); + } + iterator.closeThrow(); + return false; + } + + // Step 7.b.v. + if (!fieldNames.append(nextValue)) { + return false; + } + seen += field; + } + + // Step 8. + auto* array = + NewDenseCopiedArray(cx, fieldNames.length(), fieldNames.begin()); + if (!array) { + return false; + } + + args.rval().setObject(*array); + return true; +} + +/** + * Temporal.Calendar.prototype.fields ( fields ) + */ +static bool Calendar_fields(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_fields>(cx, args); +} + +/** + * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields ) + */ +static bool Calendar_mergeFields(JSContext* cx, const CallArgs& args) { + // Step 7. (Reordered) + MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>())); + + // Step 3. + Rooted<JSObject*> fields(cx, JS::ToObject(cx, args.get(0))); + if (!fields) { + return false; + } + + Rooted<PlainObject*> fieldsCopy( + cx, SnapshotOwnPropertiesIgnoreUndefined(cx, fields)); + if (!fieldsCopy) { + return false; + } + + // Step 4. + Rooted<JSObject*> additionalFields(cx, JS::ToObject(cx, args.get(1))); + if (!additionalFields) { + return false; + } + + Rooted<PlainObject*> additionalFieldsCopy( + cx, SnapshotOwnPropertiesIgnoreUndefined(cx, additionalFields)); + if (!additionalFieldsCopy) { + return false; + } + + // Steps 5-6. + // + // JSITER_HIDDEN doesn't need to be passed, because CopyDataProperties creates + // all properties as enumerable. + JS::RootedVector<PropertyKey> additionalKeys(cx); + if (!GetPropertyKeys(cx, additionalFieldsCopy, + JSITER_OWNONLY | JSITER_SYMBOLS, &additionalKeys)) { + return false; + } + + // Step 8. + Rooted<PropertyHashSet> overriddenKeys(cx, PropertyHashSet(cx)); + if (!ISOFieldKeysToIgnore(cx, additionalKeys, overriddenKeys.get())) { + return false; + } + + // Step 9. + Rooted<PlainObject*> merged(cx, NewPlainObjectWithProto(cx, nullptr)); + if (!merged) { + return false; + } + + // Steps 10-11. + // + // JSITER_HIDDEN doesn't need to be passed, because CopyDataProperties creates + // all properties as enumerable. + JS::RootedVector<PropertyKey> fieldsKeys(cx); + if (!GetPropertyKeys(cx, fieldsCopy, JSITER_OWNONLY | JSITER_SYMBOLS, + &fieldsKeys)) { + return false; + } + + // Step 12. + Rooted<Value> propValue(cx); + for (size_t i = 0; i < fieldsKeys.length(); i++) { + Handle<PropertyKey> key = fieldsKeys[i]; + + // Step 12.a. + // FIXME: spec issue - unnecessary initialisation + // https://github.com/tc39/proposal-temporal/issues/2549 + + // Steps 12.b-c. + if (overriddenKeys.has(key)) { + if (!GetProperty(cx, additionalFieldsCopy, additionalFieldsCopy, key, + &propValue)) { + return false; + } + + // Step 12.d. (Reordered) + if (propValue.isUndefined()) { + // The property can be undefined if the key is "month" or "monthCode". + MOZ_ASSERT(key.isAtom(cx->names().month) || + key.isAtom(cx->names().monthCode)); + + continue; + } + } else { + if (!GetProperty(cx, fieldsCopy, fieldsCopy, key, &propValue)) { + return false; + } + + // All properties of |fieldsCopy| have a non-undefined value. + MOZ_ASSERT(!propValue.isUndefined()); + } + + // Step 12.d. + if (!DefineDataProperty(cx, merged, key, propValue)) { + return false; + } + } + + // Step 13. + if (!CopyDataProperties(cx, merged, additionalFieldsCopy)) { + return false; + } + + // Step 14. + args.rval().setObject(*merged); + return true; +} + +/** + * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields ) + */ +static bool Calendar_mergeFields(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_mergeFields>(cx, args); +} + +/** + * Temporal.Calendar.prototype.toString ( ) + */ +static bool Calendar_toString(JSContext* cx, const CallArgs& args) { + auto* calendar = &args.thisv().toObject().as<CalendarObject>(); + + // Step 3. + args.rval().setString(calendar->identifier()); + return true; +} + +/** + * Temporal.Calendar.prototype.toString ( ) + */ +static bool Calendar_toString(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_toString>(cx, args); +} + +/** + * Temporal.Calendar.prototype.toJSON ( ) + */ +static bool Calendar_toJSON(JSContext* cx, const CallArgs& args) { + auto* calendar = &args.thisv().toObject().as<CalendarObject>(); + + // Step 3. + args.rval().setString(calendar->identifier()); + return true; +} + +/** + * Temporal.Calendar.prototype.toJSON ( ) + */ +static bool Calendar_toJSON(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsCalendar, Calendar_toJSON>(cx, args); +} + +const JSClass CalendarObject::class_ = { + "Temporal.Calendar", + JSCLASS_HAS_RESERVED_SLOTS(CalendarObject::SLOT_COUNT) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Calendar), + JS_NULL_CLASS_OPS, + &CalendarObject::classSpec_, +}; + +const JSClass& CalendarObject::protoClass_ = PlainObject::class_; + +static const JSFunctionSpec Calendar_methods[] = { + JS_FN("from", Calendar_from, 1, 0), + JS_FS_END, +}; + +static const JSFunctionSpec Calendar_prototype_methods[] = { + JS_FN("dateFromFields", Calendar_dateFromFields, 1, 0), + JS_FN("yearMonthFromFields", Calendar_yearMonthFromFields, 1, 0), + JS_FN("monthDayFromFields", Calendar_monthDayFromFields, 1, 0), + JS_FN("dateAdd", Calendar_dateAdd, 2, 0), + JS_FN("dateUntil", Calendar_dateUntil, 2, 0), + JS_FN("year", Calendar_year, 1, 0), + JS_FN("month", Calendar_month, 1, 0), + JS_FN("monthCode", Calendar_monthCode, 1, 0), + JS_FN("day", Calendar_day, 1, 0), + JS_FN("dayOfWeek", Calendar_dayOfWeek, 1, 0), + JS_FN("dayOfYear", Calendar_dayOfYear, 1, 0), + JS_FN("weekOfYear", Calendar_weekOfYear, 1, 0), + JS_FN("yearOfWeek", Calendar_yearOfWeek, 1, 0), + JS_FN("daysInWeek", Calendar_daysInWeek, 1, 0), + JS_FN("daysInMonth", Calendar_daysInMonth, 1, 0), + JS_FN("daysInYear", Calendar_daysInYear, 1, 0), + JS_FN("monthsInYear", Calendar_monthsInYear, 1, 0), + JS_FN("inLeapYear", Calendar_inLeapYear, 1, 0), + JS_FN("fields", Calendar_fields, 1, 0), + JS_FN("mergeFields", Calendar_mergeFields, 2, 0), + JS_FN("toString", Calendar_toString, 0, 0), + JS_FN("toJSON", Calendar_toJSON, 0, 0), + JS_FS_END, +}; + +static const JSPropertySpec Calendar_prototype_properties[] = { + JS_PSG("id", Calendar_id, 0), + JS_STRING_SYM_PS(toStringTag, "Temporal.Calendar", JSPROP_READONLY), + JS_PS_END, +}; + +const ClassSpec CalendarObject::classSpec_ = { + GenericCreateConstructor<CalendarConstructor, 1, gc::AllocKind::FUNCTION>, + GenericCreatePrototype<CalendarObject>, + Calendar_methods, + nullptr, + Calendar_prototype_methods, + Calendar_prototype_properties, + nullptr, + ClassSpec::DontDefineConstructor, +}; + +struct MOZ_STACK_CLASS CalendarNameAndNative final { + PropertyName* name; + JSNative native; +}; + +static CalendarNameAndNative GetCalendarNameAndNative(JSContext* cx, + CalendarField fieldName) { + switch (fieldName) { + case CalendarField::Year: + return {cx->names().year, Calendar_year}; + case CalendarField::Month: + return {cx->names().month, Calendar_month}; + case CalendarField::MonthCode: + return {cx->names().monthCode, Calendar_monthCode}; + case CalendarField::Day: + return {cx->names().day, Calendar_day}; + } + MOZ_CRASH("invalid temporal field name"); +} + +bool js::temporal::IsBuiltinAccess( + JSContext* cx, Handle<CalendarObject*> calendar, + std::initializer_list<CalendarField> fieldNames) { + // Don't optimize when the object has any own properties which may shadow the + // built-in methods. + if (!calendar->empty()) { + return false; + } + + JSObject* proto = cx->global()->maybeGetPrototype(JSProto_Calendar); + + // Don't attempt to optimize when the class isn't yet initialized. + if (!proto) { + return false; + } + + // Don't optimize when the prototype isn't the built-in prototype. + if (calendar->staticPrototype() != proto) { + return false; + } + + auto* nproto = &proto->as<NativeObject>(); + for (auto fieldName : fieldNames) { + auto [name, native] = GetCalendarNameAndNative(cx, fieldName); + auto prop = nproto->lookupPure(name); + + // Return if the property isn't a data property. + if (!prop || !prop->isDataProperty()) { + return false; + } + + // Return if the property isn't the initial method. + if (!IsNativeFunction(nproto->getSlot(prop->slot()), native)) { + return false; + } + } + + // TODO: Pass accessor list from caller to avoid excessive checks. + + // Additionally check the various calendar fields operations. + for (const auto& [name, native] : (CalendarNameAndNative[]){ + {cx->names().fields, Calendar_fields}, + {cx->names().mergeFields, Calendar_mergeFields}, + {cx->names().dateFromFields, Calendar_dateFromFields}, + {cx->names().monthDayFromFields, Calendar_monthDayFromFields}, + {cx->names().yearMonthFromFields, Calendar_yearMonthFromFields}, + }) { + auto prop = nproto->lookupPure(name); + + // Return if the property isn't a data property. + if (!prop || !prop->isDataProperty()) { + return false; + } + + // Return if the property isn't the initial method. + if (!IsNativeFunction(nproto->getSlot(prop->slot()), native)) { + return false; + } + } + + // CalendarFields observably uses array iteration. + bool arrayIterationSane; + if (!IsArrayIterationSane(cx, &arrayIterationSane)) { + cx->recoverFromOutOfMemory(); + return false; + } + if (!arrayIterationSane) { + return false; + } + + // Success! The access can be optimized. + return true; +} |