From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/builtin/temporal/PlainYearMonth.cpp | 1642 ++++++++++++++++++++++++++++ 1 file changed, 1642 insertions(+) create mode 100644 js/src/builtin/temporal/PlainYearMonth.cpp (limited to 'js/src/builtin/temporal/PlainYearMonth.cpp') diff --git a/js/src/builtin/temporal/PlainYearMonth.cpp b/js/src/builtin/temporal/PlainYearMonth.cpp new file mode 100644 index 0000000000..a4e2f8f9e4 --- /dev/null +++ b/js/src/builtin/temporal/PlainYearMonth.cpp @@ -0,0 +1,1642 @@ +/* -*- 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/PlainYearMonth.h" + +#include "mozilla/Assertions.h" + +#include +#include + +#include "jsnum.h" +#include "jspubtd.h" +#include "NamespaceImports.h" + +#include "builtin/temporal/Calendar.h" +#include "builtin/temporal/Duration.h" +#include "builtin/temporal/PlainDate.h" +#include "builtin/temporal/Temporal.h" +#include "builtin/temporal/TemporalFields.h" +#include "builtin/temporal/TemporalParser.h" +#include "builtin/temporal/TemporalRoundingMode.h" +#include "builtin/temporal/TemporalTypes.h" +#include "builtin/temporal/TemporalUnit.h" +#include "builtin/temporal/ToString.h" +#include "builtin/temporal/Wrapped.h" +#include "ds/IdValuePair.h" +#include "gc/AllocKind.h" +#include "gc/Barrier.h" +#include "js/AllocPolicy.h" +#include "js/CallArgs.h" +#include "js/CallNonGenericMethod.h" +#include "js/Class.h" +#include "js/ErrorReport.h" +#include "js/friend/ErrorMessages.h" +#include "js/GCVector.h" +#include "js/Id.h" +#include "js/PropertyDescriptor.h" +#include "js/PropertySpec.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/Value.h" +#include "vm/BytecodeUtil.h" +#include "vm/GlobalObject.h" +#include "vm/JSAtomState.h" +#include "vm/JSContext.h" +#include "vm/JSObject.h" +#include "vm/ObjectOperations.h" +#include "vm/PlainObject.h" +#include "vm/StringType.h" + +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::temporal; + +static inline bool IsPlainYearMonth(Handle v) { + return v.isObject() && v.toObject().is(); +} + +/** + * ISOYearMonthWithinLimits ( year, month ) + */ +template +static bool ISOYearMonthWithinLimits(T year, int32_t month) { + static_assert(std::is_same_v || std::is_same_v); + + // Step 1. + MOZ_ASSERT(IsInteger(year)); + MOZ_ASSERT(1 <= month && month <= 12); + + // Step 2. + if (year < -271821 || year > 275760) { + return false; + } + + // Step 3. + if (year == -271821 && month < 4) { + return false; + } + + // Step 4. + if (year == 275760 && month > 9) { + return false; + } + + // Step 5. + return true; +} + +/** + * CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , + * newTarget ] ) + */ +static PlainYearMonthObject* CreateTemporalYearMonth( + JSContext* cx, const CallArgs& args, double isoYear, double isoMonth, + double isoDay, Handle calendar) { + MOZ_ASSERT(IsInteger(isoYear)); + MOZ_ASSERT(IsInteger(isoMonth)); + MOZ_ASSERT(IsInteger(isoDay)); + + // Step 1. + if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) { + return nullptr; + } + + // FIXME: spec issue - Consider calling ISODateTimeWithinLimits to include + // testing |referenceISODay|? + + // Step 2. + if (!ISOYearMonthWithinLimits(isoYear, isoMonth)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID); + return nullptr; + } + + // Steps 3-4. + Rooted proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainYearMonth, + &proto)) { + return nullptr; + } + + auto* obj = NewObjectWithClassProto(cx, proto); + if (!obj) { + return nullptr; + } + + // Step 5. + obj->setFixedSlot(PlainYearMonthObject::ISO_YEAR_SLOT, Int32Value(isoYear)); + + // Step 6. + obj->setFixedSlot(PlainYearMonthObject::ISO_MONTH_SLOT, Int32Value(isoMonth)); + + // Step 7. + obj->setFixedSlot(PlainYearMonthObject::CALENDAR_SLOT, calendar.toValue()); + + // Step 8. + obj->setFixedSlot(PlainYearMonthObject::ISO_DAY_SLOT, Int32Value(isoDay)); + + // Step 9. + return obj; +} + +/** + * CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , + * newTarget ] ) + */ +PlainYearMonthObject* js::temporal::CreateTemporalYearMonth( + JSContext* cx, const PlainDate& date, Handle calendar) { + auto& [isoYear, isoMonth, isoDay] = date; + + // Step 1. + if (!ThrowIfInvalidISODate(cx, date)) { + return nullptr; + } + + // FIXME: spec issue - Consider calling ISODateTimeWithinLimits to include + // testing |referenceISODay|? + + // Step 2. + if (!ISOYearMonthWithinLimits(isoYear, isoMonth)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID); + return nullptr; + } + + // Steps 3-4. + auto* obj = NewBuiltinClassInstance(cx); + if (!obj) { + return nullptr; + } + + // Step 5. + obj->setFixedSlot(PlainYearMonthObject::ISO_YEAR_SLOT, Int32Value(isoYear)); + + // Step 6. + obj->setFixedSlot(PlainYearMonthObject::ISO_MONTH_SLOT, Int32Value(isoMonth)); + + // Step 7. + obj->setFixedSlot(PlainYearMonthObject::CALENDAR_SLOT, calendar.toValue()); + + // Step 8. + obj->setFixedSlot(PlainYearMonthObject::ISO_DAY_SLOT, Int32Value(isoDay)); + + // Step 9. + return obj; +} + +/** + * ToTemporalYearMonth ( item [ , options ] ) + */ +static Wrapped ToTemporalYearMonth( + JSContext* cx, Handle item, + Handle maybeOptions = nullptr) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Rooted maybeResolvedOptions(cx); + if (maybeOptions) { + maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions); + if (!maybeResolvedOptions) { + return nullptr; + } + } + + // Step 3. + if (item.isObject()) { + Rooted itemObj(cx, &item.toObject()); + + // Step 3.a. + if (itemObj->canUnwrapAs()) { + return itemObj; + } + + // Step 3.b. + Rooted calendarValue(cx); + if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendarValue)) { + return nullptr; + } + + // Step 3.c. + Rooted calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::Fields, + CalendarMethod::YearMonthFromFields, + }, + &calendar)) { + return nullptr; + } + + // Step 3.d. + JS::RootedVector fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::Month, CalendarField::MonthCode, + CalendarField::Year}, + &fieldNames)) { + return nullptr; + } + + // Step 3.e. + Rooted fields(cx, + PrepareTemporalFields(cx, itemObj, fieldNames)); + if (!fields) { + return nullptr; + } + + // Step 3.f. + if (maybeResolvedOptions) { + return CalendarYearMonthFromFields(cx, calendar, fields, + maybeResolvedOptions); + } + return CalendarYearMonthFromFields(cx, calendar, fields); + } + + // Step 4. + if (!item.isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item, + nullptr, "not a string"); + return nullptr; + } + Rooted string(cx, item.toString()); + + // Step 5. + PlainDate result; + Rooted calendarString(cx); + if (!ParseTemporalYearMonthString(cx, string, &result, &calendarString)) { + return nullptr; + } + + // Steps 6-9. + Rooted calendarValue(cx, CalendarValue(cx->names().iso8601)); + if (calendarString) { + if (!ToBuiltinCalendar(cx, calendarString, &calendarValue)) { + return nullptr; + } + } + + // Step 10. + if (maybeResolvedOptions) { + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) { + return nullptr; + } + } + + // Step 11. + Rooted obj( + cx, CreateTemporalYearMonth(cx, result, calendarValue)); + if (!obj) { + return nullptr; + } + + // Step 12. + Rooted calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::YearMonthFromFields, + }, + &calendar)) { + return nullptr; + } + + // FIXME: spec issue - reorder note to appear directly before + // CalendarYearMonthFromFields + + // Steps 13-14. + return CalendarYearMonthFromFields(cx, calendar, obj); +} + +/** + * ToTemporalYearMonth ( item [ , options ] ) + */ +static bool ToTemporalYearMonth(JSContext* cx, Handle item, + PlainDate* result) { + auto obj = ToTemporalYearMonth(cx, item); + if (!obj) { + return false; + } + + *result = ToPlainDate(&obj.unwrap()); + return true; +} + +/** + * ToTemporalYearMonth ( item [ , options ] ) + */ +static bool ToTemporalYearMonth(JSContext* cx, Handle item, + PlainDate* result, + MutableHandle calendar) { + auto* obj = ToTemporalYearMonth(cx, item).unwrapOrNull(); + if (!obj) { + return false; + } + + *result = ToPlainDate(obj); + calendar.set(obj->calendar()); + return calendar.wrap(cx); +} + +/** + * DifferenceTemporalPlainYearMonth ( operation, yearMonth, other, options ) + */ +static bool DifferenceTemporalPlainYearMonth(JSContext* cx, + TemporalDifference operation, + const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + // Step 1. (Not applicable in our implementation.) + + // Step 2. + auto otherYearMonth = ToTemporalYearMonth(cx, args.get(0)); + if (!otherYearMonth) { + return false; + } + auto* unwrappedOtherYearMonth = &otherYearMonth.unwrap(); + auto otherYearMonthDate = ToPlainDate(unwrappedOtherYearMonth); + + Rooted> other(cx, otherYearMonth); + Rooted otherCalendar(cx, unwrappedOtherYearMonth->calendar()); + if (!otherCalendar.wrap(cx)) { + return false; + } + + // Step 3. + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + if (!CalendarEqualsOrThrow(cx, calendar, otherCalendar)) { + return false; + } + + // Steps 5-6. + DifferenceSettings settings; + Rooted resolvedOptions(cx); + if (args.hasDefined(1)) { + Rooted options( + cx, RequireObjectArg(cx, "options", ToName(operation), args[1])); + if (!options) { + return false; + } + + // Step 5. + resolvedOptions = SnapshotOwnProperties(cx, options); + if (!resolvedOptions) { + return false; + } + + // Step 6. + if (!GetDifferenceSettings(cx, operation, resolvedOptions, + TemporalUnitGroup::Date, TemporalUnit::Month, + TemporalUnit::Month, TemporalUnit::Year, + &settings)) { + return false; + } + } else { + // Steps 5-6. + settings = { + TemporalUnit::Month, + TemporalUnit::Year, + TemporalRoundingMode::Trunc, + Increment{1}, + }; + } + + // Step 7. + if (ToPlainDate(yearMonth) == otherYearMonthDate) { + auto* obj = CreateTemporalDuration(cx, {}); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + + // Step 8. + // FIXME: spec issue - duplicate CreateDataPropertyOrThrow for "largestUnit". + + // Step 9. + Rooted calendarRec(cx); + if (!CreateCalendarMethodsRecord(cx, calendar, + { + CalendarMethod::DateAdd, + CalendarMethod::DateFromFields, + CalendarMethod::DateUntil, + CalendarMethod::Fields, + }, + &calendarRec)) { + return false; + } + + // Step 10. + JS::RootedVector fieldNames(cx); + if (!CalendarFields(cx, calendarRec, + {CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 11. + Rooted thisFields( + cx, PrepareTemporalFields(cx, yearMonth, fieldNames)); + if (!thisFields) { + return false; + } + + // Step 12. + Value one = Int32Value(1); + auto handleOne = Handle::fromMarkedLocation(&one); + if (!DefineDataProperty(cx, thisFields, cx->names().day, handleOne)) { + return false; + } + + // Step 13. + Rooted> thisDate( + cx, CalendarDateFromFields(cx, calendarRec, thisFields)); + if (!thisDate) { + return false; + } + + // Step 14. + Rooted otherFields( + cx, PrepareTemporalFields(cx, other, fieldNames)); + if (!otherFields) { + return false; + } + + // Step 15. + if (!DefineDataProperty(cx, otherFields, cx->names().day, handleOne)) { + return false; + } + + // Step 16. + Rooted> otherDate( + cx, CalendarDateFromFields(cx, calendarRec, otherFields)); + if (!otherDate) { + return false; + } + + // Steps 17-18. + Duration result; + if (resolvedOptions) { + // Step 17. + Rooted largestUnitValue( + cx, StringValue(TemporalUnitToString(cx, settings.largestUnit))); + if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit, + largestUnitValue)) { + return false; + } + + // Step 18. + if (!CalendarDateUntil(cx, calendarRec, thisDate, otherDate, + resolvedOptions, &result)) { + return false; + } + } else { + // Steps 17-18. + if (!CalendarDateUntil(cx, calendarRec, thisDate, otherDate, + settings.largestUnit, &result)) { + return false; + } + } + + // We only care about years and months here, all other fields are set to zero. + Duration duration = {result.years, result.months}; + + // Step 19. + if (settings.smallestUnit != TemporalUnit::Month || + settings.roundingIncrement != Increment{1}) { + // Steps 19.a-b. + Duration roundResult; + if (!RoundDuration(cx, duration, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode, thisDate, + calendarRec, &roundResult)) { + return false; + } + + // Step 19.c. + auto toBalance = Duration{roundResult.years, roundResult.months}; + DateDuration balanceResult; + if (!temporal::BalanceDateDurationRelative( + cx, toBalance, settings.largestUnit, settings.smallestUnit, + thisDate, calendarRec, &balanceResult)) { + return false; + } + duration = balanceResult.toDuration(); + } + + // Step 20. + if (operation == TemporalDifference::Since) { + duration = duration.negate(); + } + + auto* obj = CreateTemporalDuration(cx, {duration.years, duration.months}); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +enum class PlainYearMonthDuration { Add, Subtract }; + +/** + * AddDurationToOrSubtractDurationFromPlainYearMonth ( operation, yearMonth, + * temporalDurationLike, options ) + */ +static bool AddDurationToOrSubtractDurationFromPlainYearMonth( + JSContext* cx, PlainYearMonthDuration operation, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + // Step 1. + Duration duration; + if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) { + return false; + } + + // Step 2. + if (operation == PlainYearMonthDuration::Subtract) { + duration = duration.negate(); + } + + // Step 3. + TimeDuration balanceResult; + if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &balanceResult)) { + return false; + } + + // Step 4. + int32_t sign = DurationSign( + {duration.years, duration.months, duration.weeks, balanceResult.days}); + + // Step 5. + Rooted calendarValue(cx, yearMonth->calendar()); + Rooted calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::DateAdd, + CalendarMethod::DateFromFields, + CalendarMethod::Day, + CalendarMethod::Fields, + CalendarMethod::YearMonthFromFields, + }, + &calendar)) { + return false; + }; + + // Step 6. + JS::RootedVector fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 7. + Rooted fields(cx, + PrepareTemporalFields(cx, yearMonth, fieldNames)); + if (!fields) { + return false; + } + + // Step 8. + Rooted fieldsCopy(cx, SnapshotOwnProperties(cx, fields)); + if (!fieldsCopy) { + return false; + } + + // Step 9. + Value one = Int32Value(1); + auto handleOne = Handle::fromMarkedLocation(&one); + if (!DefineDataProperty(cx, fields, cx->names().day, handleOne)) { + return false; + } + + // Step 10. + Rooted> intermediateDate( + cx, CalendarDateFromFields(cx, calendar, fields)); + if (!intermediateDate) { + return false; + } + + // Steps 11-12. + Rooted> date(cx); + if (sign < 0) { + // |intermediateDate| is initialized to the first day of |yearMonth|'s + // month. Compute the last day of |yearMonth|'s month by first adding one + // month and then subtracting one day. + // + // This is roughly equivalent to these calls: + // + // js> var ym = new Temporal.PlainYearMonth(2023, 1); + // js> ym.toPlainDate({day: 1}).add({months: 1}).subtract({days: 1}).day + // 31 + // + // For many calendars this is equivalent to `ym.daysInMonth`, except when + // some days are skipped, for example consider the Julian-to-Gregorian + // calendar transition. + + // Step 11.a. + Duration oneMonthDuration = {0, 1}; + + // Step 11.b. + Rooted> nextMonth( + cx, CalendarDateAdd(cx, calendar, intermediateDate, oneMonthDuration)); + if (!nextMonth) { + return false; + } + + auto* unwrappedNextMonth = nextMonth.unwrap(cx); + if (!unwrappedNextMonth) { + return false; + } + auto nextMonthDate = ToPlainDate(unwrappedNextMonth); + + // Step 11.c. + PlainDate endOfMonthISO; + if (!AddISODate(cx, nextMonthDate, {0, 0, 0, -1}, + TemporalOverflow::Constrain, &endOfMonthISO)) { + return false; + } + + // Step 11.d. + Rooted endOfMonth(cx); + if (!CreateTemporalDate(cx, endOfMonthISO, calendar.receiver(), + &endOfMonth)) { + return false; + } + + // Step 11.e. + Rooted day(cx); + if (!CalendarDay(cx, calendar, endOfMonth.date(), &day)) { + return false; + } + + // Step 11.f. + if (!DefineDataProperty(cx, fieldsCopy, cx->names().day, day)) { + return false; + } + + // Step 11.g. + date = CalendarDateFromFields(cx, calendar, fieldsCopy); + if (!date) { + return false; + } + } else { + // Step 12.a. + date = intermediateDate; + } + + // Step 13. + Duration durationToAdd = {duration.years, duration.months, duration.weeks, + balanceResult.days}; + + // FIXME: spec issue - GetOptionsObject should be called after + // ToTemporalDurationRecord to validate the input type before performing any + // other user-visible operations. + // https://github.com/tc39/proposal-temporal/issues/2721 + + // Step 14. + Rooted options(cx); + if (args.hasDefined(1)) { + const char* name = + operation == PlainYearMonthDuration::Add ? "add" : "subtract"; + options = RequireObjectArg(cx, "options", name, args[1]); + } else { + // TODO: Avoid creating an options object if not necessary. + options = NewPlainObjectWithProto(cx, nullptr); + } + if (!options) { + return false; + } + + // Step 15. + Rooted optionsCopy(cx, SnapshotOwnProperties(cx, options)); + if (!optionsCopy) { + return false; + } + + // Step 16. + Rooted> addedDate( + cx, AddDate(cx, calendar, date, durationToAdd, options)); + if (!addedDate) { + return false; + } + + // Step 17. + Rooted addedDateFields( + cx, PrepareTemporalFields(cx, addedDate, fieldNames)); + if (!addedDateFields) { + return false; + } + + // Step 18. + auto obj = + CalendarYearMonthFromFields(cx, calendar, addedDateFields, optionsCopy); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainYearMonth ( isoYear, isoMonth [ , calendarLike [ , + * referenceISODay ] ] ) + */ +static bool PlainYearMonthConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainYearMonth")) { + return false; + } + + // Step 3. + double isoYear; + if (!ToIntegerWithTruncation(cx, args.get(0), "year", &isoYear)) { + return false; + } + + // Step 4. + double isoMonth; + if (!ToIntegerWithTruncation(cx, args.get(1), "month", &isoMonth)) { + return false; + } + + // Step 5. + Rooted calendar(cx); + if (!ToTemporalCalendarWithISODefault(cx, args.get(2), &calendar)) { + return false; + } + + // Steps 2 and 6. + double isoDay = 1; + if (args.hasDefined(3)) { + if (!ToIntegerWithTruncation(cx, args[3], "day", &isoDay)) { + return false; + } + } + + // Step 7. + auto* yearMonth = + CreateTemporalYearMonth(cx, args, isoYear, isoMonth, isoDay, calendar); + if (!yearMonth) { + return false; + } + + args.rval().setObject(*yearMonth); + return true; +} + +/** + * Temporal.PlainYearMonth.from ( item [ , options ] ) + */ +static bool PlainYearMonth_from(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted options(cx); + if (args.hasDefined(1)) { + options = RequireObjectArg(cx, "options", "from", args[1]); + if (!options) { + return false; + } + } + + // Step 2. + if (args.get(0).isObject()) { + JSObject* item = &args[0].toObject(); + + if (auto* yearMonth = item->maybeUnwrapIf()) { + auto date = ToPlainDate(yearMonth); + + Rooted calendar(cx, yearMonth->calendar()); + if (!calendar.wrap(cx)) { + return false; + } + + if (options) { + // Step 2.a. + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, options, &ignored)) { + return false; + } + } + + // Step 2.b. + auto* obj = CreateTemporalYearMonth(cx, date, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + } + + // Step 3. + auto obj = ToTemporalYearMonth(cx, args.get(0), options); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainYearMonth.compare ( one, two ) + */ +static bool PlainYearMonth_compare(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + PlainDate one; + if (!ToTemporalYearMonth(cx, args.get(0), &one)) { + return false; + } + + // Step 2. + PlainDate two; + if (!ToTemporalYearMonth(cx, args.get(1), &two)) { + return false; + } + + // Step 3. + args.rval().setInt32(CompareISODate(one, two)); + return true; +} + +/** + * get Temporal.PlainYearMonth.prototype.calendarId + */ +static bool PlainYearMonth_calendarId(JSContext* cx, const CallArgs& args) { + auto* yearMonth = &args.thisv().toObject().as(); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 3. + auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar); + if (!calendarId) { + return false; + } + + args.rval().setString(calendarId); + return true; +} + +/** + * get Temporal.PlainYearMonth.prototype.calendarId + */ +static bool PlainYearMonth_calendarId(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * get Temporal.PlainYearMonth.prototype.year + */ +static bool PlainYearMonth_year(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarYear(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.year + */ +static bool PlainYearMonth_year(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * get Temporal.PlainYearMonth.prototype.month + */ +static bool PlainYearMonth_month(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarMonth(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.month + */ +static bool PlainYearMonth_month(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * get Temporal.PlainYearMonth.prototype.monthCode + */ +static bool PlainYearMonth_monthCode(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarMonthCode(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.monthCode + */ +static bool PlainYearMonth_monthCode(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +/** + * get Temporal.PlainYearMonth.prototype.daysInYear + */ +static bool PlainYearMonth_daysInYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarDaysInYear(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.daysInYear + */ +static bool PlainYearMonth_daysInYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * get Temporal.PlainYearMonth.prototype.daysInMonth + */ +static bool PlainYearMonth_daysInMonth(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarDaysInMonth(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.daysInMonth + */ +static bool PlainYearMonth_daysInMonth(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * get Temporal.PlainYearMonth.prototype.monthsInYear + */ +static bool PlainYearMonth_monthsInYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarMonthsInYear(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.monthsInYear + */ +static bool PlainYearMonth_monthsInYear(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * get Temporal.PlainYearMonth.prototype.inLeapYear + */ +static bool PlainYearMonth_inLeapYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 4. + return CalendarInLeapYear(cx, calendar, yearMonth, args.rval()); +} + +/** + * get Temporal.PlainYearMonth.prototype.inLeapYear + */ +static bool PlainYearMonth_inLeapYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.with ( temporalYearMonthLike [ , options ] + * ) + */ +static bool PlainYearMonth_with(JSContext* cx, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + Rooted calendarValue(cx, yearMonth->calendar()); + + // Step 3. + Rooted temporalYearMonthLike( + cx, RequireObjectArg(cx, "temporalYearMonthLike", "with", args.get(0))); + if (!temporalYearMonthLike) { + return false; + } + + // Step 4. + if (!RejectTemporalLikeObject(cx, temporalYearMonthLike)) { + return false; + } + + // Step 5. + Rooted resolvedOptions(cx); + if (args.hasDefined(1)) { + Rooted options(cx, + RequireObjectArg(cx, "options", "with", args[1])); + if (!options) { + return false; + } + resolvedOptions = SnapshotOwnProperties(cx, options); + } else { + resolvedOptions = NewPlainObjectWithProto(cx, nullptr); + } + if (!resolvedOptions) { + return false; + } + + // Step 6. + Rooted calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::Fields, + CalendarMethod::MergeFields, + CalendarMethod::YearMonthFromFields, + }, + &calendar)) { + return false; + } + + // Step 7. + JS::RootedVector fieldNames(cx); + if (!CalendarFields( + cx, calendar, + {CalendarField::Month, CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 8. + Rooted fields(cx, + PrepareTemporalFields(cx, yearMonth, fieldNames)); + if (!fields) { + return false; + } + + // Step 9. + Rooted partialYearMonth( + cx, PreparePartialTemporalFields(cx, temporalYearMonthLike, fieldNames)); + if (!partialYearMonth) { + return false; + } + + // Step 10. + Rooted mergedFields( + cx, CalendarMergeFields(cx, calendar, fields, partialYearMonth)); + if (!mergedFields) { + return false; + } + + // Step 11. + fields = PrepareTemporalFields(cx, mergedFields, fieldNames); + if (!fields) { + return false; + } + + // Step 12. + auto obj = CalendarYearMonthFromFields(cx, calendar, fields, resolvedOptions); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.with ( temporalYearMonthLike [ , options ] + * ) + */ +static bool PlainYearMonth_with(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] ) + */ +static bool PlainYearMonth_add(JSContext* cx, const CallArgs& args) { + // Step 3. + return AddDurationToOrSubtractDurationFromPlainYearMonth( + cx, PlainYearMonthDuration::Add, args); +} + +/** + * Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] ) + */ +static bool PlainYearMonth_add(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options + * ] ) + */ +static bool PlainYearMonth_subtract(JSContext* cx, const CallArgs& args) { + // Step 3. + return AddDurationToOrSubtractDurationFromPlainYearMonth( + cx, PlainYearMonthDuration::Subtract, args); +} + +/** + * Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options + * ] ) + */ +static bool PlainYearMonth_subtract(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +/** + * Temporal.PlainYearMonth.prototype.until ( other [ , options ] ) + */ +static bool PlainYearMonth_until(JSContext* cx, const CallArgs& args) { + // Step 3. + return DifferenceTemporalPlainYearMonth(cx, TemporalDifference::Until, args); +} + +/** + * Temporal.PlainYearMonth.prototype.until ( other [ , options ] ) + */ +static bool PlainYearMonth_until(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.since ( other [ , options ] ) + */ +static bool PlainYearMonth_since(JSContext* cx, const CallArgs& args) { + // Step 3. + return DifferenceTemporalPlainYearMonth(cx, TemporalDifference::Since, args); +} + +/** + * Temporal.PlainYearMonth.prototype.since ( other [ , options ] ) + */ +static bool PlainYearMonth_since(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.equals ( other ) + */ +static bool PlainYearMonth_equals(JSContext* cx, const CallArgs& args) { + auto* yearMonth = &args.thisv().toObject().as(); + auto date = ToPlainDate(yearMonth); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 3. + PlainDate other; + Rooted otherCalendar(cx); + if (!ToTemporalYearMonth(cx, args.get(0), &other, &otherCalendar)) { + return false; + } + + // Steps 4-7. + bool equals = date == other; + if (equals && !CalendarEquals(cx, calendar, otherCalendar, &equals)) { + return false; + } + + args.rval().setBoolean(equals); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.equals ( other ) + */ +static bool PlainYearMonth_equals(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +/** + * Temporal.PlainYearMonth.prototype.toString ( [ options ] ) + */ +static bool PlainYearMonth_toString(JSContext* cx, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + auto showCalendar = CalendarOption::Auto; + if (args.hasDefined(0)) { + // Step 3. + Rooted options( + cx, RequireObjectArg(cx, "options", "toString", args[0])); + if (!options) { + return false; + } + + // Step 4. + if (!ToCalendarNameOption(cx, options, &showCalendar)) { + return false; + } + } + + // Step 5. + JSString* str = TemporalYearMonthToString(cx, yearMonth, showCalendar); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.toString ( [ options ] ) + */ +static bool PlainYearMonth_toString(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +/** + * Temporal.PlainYearMonth.prototype.toLocaleString ( [ locales [ , options ] ] + * ) + */ +static bool PlainYearMonth_toLocaleString(JSContext* cx, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + // Step 3. + JSString* str = + TemporalYearMonthToString(cx, yearMonth, CalendarOption::Auto); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.toLocaleString ( [ locales [ , options ] ] + * ) + */ +static bool PlainYearMonth_toLocaleString(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.toJSON ( ) + */ +static bool PlainYearMonth_toJSON(JSContext* cx, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + // Step 3. + JSString* str = + TemporalYearMonthToString(cx, yearMonth, CalendarOption::Auto); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.toJSON ( ) + */ +static bool PlainYearMonth_toJSON(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +/** + * Temporal.PlainYearMonth.prototype.valueOf ( ) + */ +static bool PlainYearMonth_valueOf(JSContext* cx, unsigned argc, Value* vp) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, + "PlainYearMonth", "primitive type"); + return false; +} + +/** + * Temporal.PlainYearMonth.prototype.toPlainDate ( item ) + */ +static bool PlainYearMonth_toPlainDate(JSContext* cx, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + // Step 3. + Rooted item( + cx, RequireObjectArg(cx, "item", "toPlainDate", args.get(0))); + if (!item) { + return false; + } + + // Step 4. + Rooted calendarValue(cx, yearMonth->calendar()); + Rooted calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::DateFromFields, + CalendarMethod::Fields, + CalendarMethod::MergeFields, + }, + &calendar)) { + return false; + } + + // Step 5. + JS::RootedVector receiverFieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::MonthCode, CalendarField::Year}, + &receiverFieldNames)) { + return false; + } + + // Step 6. + Rooted fields( + cx, PrepareTemporalFields(cx, yearMonth, receiverFieldNames)); + if (!fields) { + return false; + } + + // Step 7. + JS::RootedVector inputFieldNames(cx); + if (!CalendarFields(cx, calendar, {CalendarField::Day}, &inputFieldNames)) { + return false; + } + + // Step 8. + Rooted inputFields( + cx, PrepareTemporalFields(cx, item, inputFieldNames)); + if (!inputFields) { + return false; + } + + // Step 9. + Rooted mergedFields( + cx, CalendarMergeFields(cx, calendar, fields, inputFields)); + if (!mergedFields) { + return false; + } + + // Step 10. + JS::RootedVector concatenatedFieldNames(cx); + if (!ConcatTemporalFieldNames(receiverFieldNames, inputFieldNames, + concatenatedFieldNames.get())) { + return false; + } + + // Step 11. + Rooted mergedFromConcatenatedFields( + cx, PrepareTemporalFields(cx, mergedFields, concatenatedFieldNames)); + if (!mergedFromConcatenatedFields) { + return false; + } + + // Step 12. + Rooted options(cx, NewPlainObjectWithProto(cx, nullptr)); + if (!options) { + return false; + } + + // Step 13. + Rooted overflow(cx, StringValue(cx->names().constrain)); + if (!DefineDataProperty(cx, options, cx->names().overflow, overflow)) { + return false; + } + + // Step 14. + auto obj = CalendarDateFromFields(cx, calendar, mergedFromConcatenatedFields, + options); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.toPlainDate ( item ) + */ +static bool PlainYearMonth_toPlainDate(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.getISOFields ( ) + */ +static bool PlainYearMonth_getISOFields(JSContext* cx, const CallArgs& args) { + Rooted yearMonth( + cx, &args.thisv().toObject().as()); + + // Step 3. + Rooted fields(cx, IdValueVector(cx)); + + // Step 4. + if (!fields.emplaceBack(NameToId(cx->names().calendar), + yearMonth->calendar().toValue())) { + return false; + } + + // Step 5. + if (!fields.emplaceBack(NameToId(cx->names().isoDay), + Int32Value(yearMonth->isoDay()))) { + return false; + } + + // Step 6. + if (!fields.emplaceBack(NameToId(cx->names().isoMonth), + Int32Value(yearMonth->isoMonth()))) { + return false; + } + + // Step 7. + if (!fields.emplaceBack(NameToId(cx->names().isoYear), + Int32Value(yearMonth->isoYear()))) { + return false; + } + + // Step 8. + auto* obj = + NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length()); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.getISOFields ( ) + */ +static bool PlainYearMonth_getISOFields(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +/** + * Temporal.PlainYearMonth.prototype.getCalendar ( ) + */ +static bool PlainYearMonth_getCalendar(JSContext* cx, const CallArgs& args) { + auto* yearMonth = &args.thisv().toObject().as(); + Rooted calendar(cx, yearMonth->calendar()); + + // Step 3. + auto* obj = ToTemporalCalendarObject(cx, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainYearMonth.prototype.getCalendar ( ) + */ +static bool PlainYearMonth_getCalendar(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +const JSClass PlainYearMonthObject::class_ = { + "Temporal.PlainYearMonth", + JSCLASS_HAS_RESERVED_SLOTS(PlainYearMonthObject::SLOT_COUNT) | + JSCLASS_HAS_CACHED_PROTO(JSProto_PlainYearMonth), + JS_NULL_CLASS_OPS, + &PlainYearMonthObject::classSpec_, +}; + +const JSClass& PlainYearMonthObject::protoClass_ = PlainObject::class_; + +static const JSFunctionSpec PlainYearMonth_methods[] = { + JS_FN("from", PlainYearMonth_from, 1, 0), + JS_FN("compare", PlainYearMonth_compare, 2, 0), + JS_FS_END, +}; + +static const JSFunctionSpec PlainYearMonth_prototype_methods[] = { + JS_FN("with", PlainYearMonth_with, 1, 0), + JS_FN("add", PlainYearMonth_add, 1, 0), + JS_FN("subtract", PlainYearMonth_subtract, 1, 0), + JS_FN("until", PlainYearMonth_until, 1, 0), + JS_FN("since", PlainYearMonth_since, 1, 0), + JS_FN("equals", PlainYearMonth_equals, 1, 0), + JS_FN("toString", PlainYearMonth_toString, 0, 0), + JS_FN("toLocaleString", PlainYearMonth_toLocaleString, 0, 0), + JS_FN("toJSON", PlainYearMonth_toJSON, 0, 0), + JS_FN("valueOf", PlainYearMonth_valueOf, 0, 0), + JS_FN("toPlainDate", PlainYearMonth_toPlainDate, 1, 0), + JS_FN("getISOFields", PlainYearMonth_getISOFields, 0, 0), + JS_FN("getCalendar", PlainYearMonth_getCalendar, 0, 0), + JS_FS_END, +}; + +static const JSPropertySpec PlainYearMonth_prototype_properties[] = { + JS_PSG("calendarId", PlainYearMonth_calendarId, 0), + JS_PSG("year", PlainYearMonth_year, 0), + JS_PSG("month", PlainYearMonth_month, 0), + JS_PSG("monthCode", PlainYearMonth_monthCode, 0), + JS_PSG("daysInYear", PlainYearMonth_daysInYear, 0), + JS_PSG("daysInMonth", PlainYearMonth_daysInMonth, 0), + JS_PSG("monthsInYear", PlainYearMonth_monthsInYear, 0), + JS_PSG("inLeapYear", PlainYearMonth_inLeapYear, 0), + JS_STRING_SYM_PS(toStringTag, "Temporal.PlainYearMonth", JSPROP_READONLY), + JS_PS_END, +}; + +const ClassSpec PlainYearMonthObject::classSpec_ = { + GenericCreateConstructor, + GenericCreatePrototype, + PlainYearMonth_methods, + nullptr, + PlainYearMonth_prototype_methods, + PlainYearMonth_prototype_properties, + nullptr, + ClassSpec::DontDefineConstructor, +}; -- cgit v1.2.3