From dda56765dc5d2ef5d7e5cc2090a518eb7b426631 Mon Sep 17 00:00:00 2001 From: Frank Tang Date: Tue, 5 Apr 2022 09:48:40 -0700 Subject: [PATCH] [Temporal] Add Calendar.prototype.dateFromFields Also add AO: RegulateISODate, ResolveISOMonth, ISODateFromFields Spec Text: https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.datefromfields https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate https://tc39.es/proposal-temporal/#sec-temporal-resolveisomonth https://tc39.es/proposal-temporal/#sec-temporal-isodatefromfields Note: This is only the non-intl version. The intl version in https://tc39.es/proposal-temporal/#sup-temporal.calendar.prototype.datefromfields will be implemented in later cl. Bug: v8:11544 Change-Id: I493dc60694421e9908eb5d785fdb8b07fc968699 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3408462 Reviewed-by: Adam Klein Commit-Queue: Frank Tang Cr-Commit-Position: refs/heads/main@{#79793} --- src/builtins/builtins-temporal.cc | 14 +- src/objects/js-temporal-objects.cc | 229 ++++++++++++++++++++++++++++- src/objects/js-temporal-objects.h | 5 + test/test262/test262.status | 16 -- 4 files changed, 242 insertions(+), 22 deletions(-) diff --git a/src/builtins/builtins-temporal.cc b/src/builtins/builtins-temporal.cc index 4b5ab1a020..1f8449b485 100644 --- a/src/builtins/builtins-temporal.cc +++ b/src/builtins/builtins-temporal.cc @@ -302,8 +302,6 @@ TO_BE_IMPLEMENTED(TemporalTimeZonePrototypeGetPreviousTransition) TO_BE_IMPLEMENTED(TemporalTimeZonePrototypeToJSON) /* Temporal.Calendar */ -/* Temporal #sec-temporal.calendar.prototype.datefromfields */ -TO_BE_IMPLEMENTED(TemporalCalendarPrototypeDateFromFields) /* Temporal #sec-temporal.calendar.prototype.yearmonthfromfields */ TO_BE_IMPLEMENTED(TemporalCalendarPrototypeYearMonthFromFields) /* Temporal #sec-temporal.calendar.prototype.monthdayfromfields */ @@ -404,6 +402,17 @@ TO_BE_IMPLEMENTED(TemporalZonedDateTimePrototypeToLocaleString) JSTemporal##T ::METHOD(isolate, obj, args.atOrUndefined(isolate, 1))); \ } +#define TEMPORAL_PROTOTYPE_METHOD2(T, METHOD, name) \ + BUILTIN(Temporal##T##Prototype##METHOD) { \ + HandleScope scope(isolate); \ + const char* method = "Temporal." #T ".prototype." #name; \ + CHECK_RECEIVER(JSTemporal##T, obj, method); \ + RETURN_RESULT_OR_FAILURE( \ + isolate, \ + JSTemporal##T ::METHOD(isolate, obj, args.atOrUndefined(isolate, 1), \ + args.atOrUndefined(isolate, 2))); \ + } + #define TEMPORAL_PROTOTYPE_METHOD3(T, METHOD, name) \ BUILTIN(Temporal##T##Prototype##METHOD) { \ HandleScope scope(isolate); \ @@ -785,6 +794,7 @@ TEMPORAL_GET_BIGINT_AFTER_DIVID(Instant, EpochMicroseconds, nanoseconds, 1000, // Calendar TEMPORAL_CONSTRUCTOR1(Calendar) +TEMPORAL_PROTOTYPE_METHOD2(Calendar, DateFromFields, dateFromFields) TEMPORAL_ID_BY_TO_STRING(Calendar) TEMPORAL_PROTOTYPE_METHOD1(Calendar, DaysInMonth, daysInMonth) TEMPORAL_PROTOTYPE_METHOD1(Calendar, DaysInWeek, daysInWeek) diff --git a/src/objects/js-temporal-objects.cc b/src/objects/js-temporal-objects.cc index 9c56e1b165..008f2a30c9 100644 --- a/src/objects/js-temporal-objects.cc +++ b/src/objects/js-temporal-objects.cc @@ -1847,10 +1847,10 @@ MaybeHandle ToTemporalDate(Isolate* isolate, // e. Let fieldNames be ? CalendarFields(calendar, « "day", "month", // "monthCode", "year" »). Handle field_names = factory->NewFixedArray(4); - field_names->set(0, *(factory->day_string())); - field_names->set(1, *(factory->month_string())); - field_names->set(2, *(factory->monthCode_string())); - field_names->set(3, *(factory->year_string())); + field_names->set(0, ReadOnlyRoots(isolate).day_string()); + field_names->set(1, ReadOnlyRoots(isolate).month_string()); + field_names->set(2, ReadOnlyRoots(isolate).monthCode_string()); + field_names->set(3, ReadOnlyRoots(isolate).year_string()); ASSIGN_RETURN_ON_EXCEPTION(isolate, field_names, CalendarFields(isolate, calendar, field_names), JSTemporalPlainDate); @@ -4856,6 +4856,186 @@ int32_t ToISODayOfWeek(Isolate* isolate, int32_t year, int32_t month, return weekday == 0 ? 7 : weekday; } +// #sec-temporal-regulateisodate +Maybe RegulateISODate(Isolate* isolate, ShowOverflow overflow, + int32_t year, int32_t* month, int32_t* day) { + TEMPORAL_ENTER_FUNC(); + + // 1. Assert: year, month, and day are integers. + // 2. Assert: overflow is either "constrain" or "reject". + switch (overflow) { + // 3. If overflow is "reject", then + case ShowOverflow::kReject: + // a. If ! IsValidISODate(year, month, day) is false, throw a RangeError + // exception. + if (!IsValidISODate(isolate, year, *month, *day)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NEW_TEMPORAL_INVALD_ARG_RANGE_ERROR(), Nothing()); + } + // b. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day + // }. + return Just(true); + // 4. If overflow is "constrain", then + case ShowOverflow::kConstrain: + // a. Set month to ! ConstrainToRange(month, 1, 12). + *month = std::max(std::min(*month, 12), 1); + // b. Set day to ! ConstrainToRange(day, 1, ! ISODaysInMonth(year, + // month)). + *day = std::max(std::min(*day, ISODaysInMonth(isolate, year, *month)), 1); + // c. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day + // }. + return Just(true); + } +} + +// #sec-temporal-resolveisomonth +Maybe ResolveISOMonth(Isolate* isolate, Handle fields) { + Factory* factory = isolate->factory(); + // 1. Let month be ? Get(fields, "month"). + Handle month_obj; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, month_obj, + Object::GetPropertyOrElement(isolate, fields, factory->month_string()), + Nothing()); + // 2. Let monthCode be ? Get(fields, "monthCode"). + Handle month_code_obj; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, month_code_obj, + Object::GetPropertyOrElement(isolate, fields, + factory->monthCode_string()), + Nothing()); + // 3. If monthCode is undefined, then + if (month_code_obj->IsUndefined(isolate)) { + // a. If month is undefined, throw a TypeError exception. + if (month_obj->IsUndefined(isolate)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NEW_TEMPORAL_INVALD_ARG_TYPE_ERROR(), Nothing()); + } + // b. Return month. + // Note: In Temporal spec, "month" in fields is always converted by + // ToPositiveInteger inside PrepareTemporalFields before calling + // ResolveISOMonth. Therefore the month_obj is always a positive integer. + DCHECK(month_obj->IsSmi() || month_obj->IsHeapNumber()); + return Just(FastD2I(month_obj->Number())); + } + // 4. Assert: Type(monthCode) is String. + DCHECK(month_code_obj->IsString()); + Handle month_code; + ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, month_code, + Object::ToString(isolate, month_code_obj), + Nothing()); + // 5. Let monthLength be the length of monthCode. + // 6. If monthLength is not 3, throw a RangeError exception. + if (month_code->length() != 3) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kPropertyValueOutOfRange, + factory->monthCode_string()), + Nothing()); + } + // 7. Let numberPart be the substring of monthCode from 1. + // 8. Set numberPart to ! ToIntegerOrInfinity(numberPart). + // 9. If numberPart < 1 or numberPart > 12, throw a RangeError exception. + uint16_t m0 = month_code->Get(0); + uint16_t m1 = month_code->Get(1); + uint16_t m2 = month_code->Get(2); + if (!((m0 == 'M') && ((m1 == '0' && '1' <= m2 && m2 <= '9') || + (m1 == '1' && '0' <= m2 && m2 <= '2')))) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kPropertyValueOutOfRange, + factory->monthCode_string()), + Nothing()); + } + int32_t number_part = + 10 * static_cast(m1 - '0') + static_cast(m2 - '0'); + // 10. If month is not undefined, and month ≠ numberPart, then + // 11. If ! SameValueNonNumeric(monthCode, ! BuildISOMonthCode(numberPart)) is + // false, then a. Throw a RangeError exception. + // Note: In Temporal spec, "month" in fields is always converted by + // ToPositiveInteger inside PrepareTemporalFields before calling + // ResolveISOMonth. Therefore the month_obj is always a positive integer. + if (!month_obj->IsUndefined() && + FastD2I(month_obj->Number()) != number_part) { + // a. Throw a RangeError exception. + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kPropertyValueOutOfRange, + factory->month_string()), + Nothing()); + } + + // 12. Return numberPart. + return Just(number_part); +} + +// #sec-temporal-isodatefromfields +Maybe ISODateFromFields(Isolate* isolate, Handle fields, + Handle options, + const char* method_name, int32_t* year, + int32_t* month, int32_t* day) { + Factory* factory = isolate->factory(); + + // 1. Assert: Type(fields) is Object. + // 2. Let overflow be ? ToTemporalOverflow(options). + Maybe maybe_overflow = + ToTemporalOverflow(isolate, options, method_name); + MAYBE_RETURN(maybe_overflow, Nothing()); + // 3. Set fields to ? PrepareTemporalFields(fields, « "day", "month", + // "monthCode", "year" », «»). + Handle field_names = factory->NewFixedArray(4); + field_names->set(0, ReadOnlyRoots(isolate).day_string()); + field_names->set(1, ReadOnlyRoots(isolate).month_string()); + field_names->set(2, ReadOnlyRoots(isolate).monthCode_string()); + field_names->set(3, ReadOnlyRoots(isolate).year_string()); + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, fields, + PrepareTemporalFields(isolate, fields, field_names, + RequiredFields::kNone), + Nothing()); + + // 4. Let year be ? Get(fields, "year"). + Handle year_obj; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, year_obj, + Object::GetPropertyOrElement(isolate, fields, factory->year_string()), + Nothing()); + // 5. If year is undefined, throw a TypeError exception. + if (year_obj->IsUndefined(isolate)) { + THROW_NEW_ERROR_RETURN_VALUE(isolate, NEW_TEMPORAL_INVALD_ARG_TYPE_ERROR(), + Nothing()); + } + // Note: "year" in fields is always converted by + // ToIntegerThrowOnInfinity inside the PrepareTemporalFields above. + // Therefore the year_obj is always an integer. + DCHECK(year_obj->IsSmi() || year_obj->IsHeapNumber()); + *year = FastD2I(year_obj->Number()); + + // 6. Let month be ? ResolveISOMonth(fields). + Maybe maybe_month = ResolveISOMonth(isolate, fields); + MAYBE_RETURN(maybe_month, Nothing()); + *month = maybe_month.FromJust(); + + // 7. Let day be ? Get(fields, "day"). + Handle day_obj; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, day_obj, + Object::GetPropertyOrElement(isolate, fields, factory->day_string()), + Nothing()); + // 8. If day is undefined, throw a TypeError exception. + if (day_obj->IsUndefined(isolate)) { + THROW_NEW_ERROR_RETURN_VALUE(isolate, NEW_TEMPORAL_INVALD_ARG_TYPE_ERROR(), + Nothing()); + } + // Note: "day" in fields is always converted by + // ToIntegerThrowOnInfinity inside the PrepareTemporalFields above. + // Therefore the day_obj is always an integer. + DCHECK(day_obj->IsSmi() || day_obj->IsHeapNumber()); + *day = FastD2I(day_obj->Number()); + // 9. Return ? RegulateISODate(year, month, day, overflow). + return RegulateISODate(isolate, maybe_overflow.FromJust(), *year, month, day); +} + } // namespace // #sec-temporal.calendar.prototype.daysinyear @@ -5109,6 +5289,47 @@ MaybeHandle JSTemporalCalendar::DaysInWeek( return handle(Smi::FromInt(7), isolate); } +// #sec-temporal.calendar.prototype.datefromfields +MaybeHandle JSTemporalCalendar::DateFromFields( + Isolate* isolate, Handle calendar, + Handle fields_obj, Handle options_obj) { + // 1. Let calendar be the this value. + // 2. Perform ? RequireInternalSlot(calendar, + // [[InitializedTemporalCalendar]]). + // 3. Assert: calendar.[[Identifier]] is "iso8601". + // 4. If Type(fields) is not Object, throw a TypeError exception. + const char* method_name = "Temporal.Calendar.prototype.dateFromFields"; + if (!fields_obj->IsJSReceiver()) { + THROW_NEW_ERROR(isolate, + NewTypeError(MessageTemplate::kCalledOnNonObject, + isolate->factory()->NewStringFromAsciiChecked( + method_name)), + JSTemporalPlainDate); + } + Handle fields = Handle::cast(fields_obj); + + // 5. Set options to ? GetOptionsObject(options). + Handle options; + ASSIGN_RETURN_ON_EXCEPTION( + isolate, options, GetOptionsObject(isolate, options_obj, method_name), + JSTemporalPlainDate); + if (calendar->calendar_index() == 0) { + int32_t year; + int32_t month; + int32_t day; + // 6. Let result be ? ISODateFromFields(fields, options). + Maybe maybe_result = ISODateFromFields( + isolate, fields, options, method_name, &year, &month, &day); + MAYBE_RETURN(maybe_result, Handle()); + DCHECK(maybe_result.FromJust()); + // 7. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], + // result.[[Day]], calendar). + return CreateTemporalDate(isolate, year, month, day, calendar); + } + // TODO(ftang) add intl implementation inside #ifdef V8_INTL_SUPPORT + UNREACHABLE(); +} + // #sec-temporal.calendar.prototype.tostring MaybeHandle JSTemporalCalendar::ToString( Isolate* isolate, Handle calendar, diff --git a/src/objects/js-temporal-objects.h b/src/objects/js-temporal-objects.h index 8d6625f8c6..52e14140a5 100644 --- a/src/objects/js-temporal-objects.h +++ b/src/objects/js-temporal-objects.h @@ -92,6 +92,11 @@ class JSTemporalCalendar Isolate* isolate, Handle calendar, Handle temporal_date_like); + // #sec-temporal.calendar.prototype.datefromfields + V8_WARN_UNUSED_RESULT static MaybeHandle DateFromFields( + Isolate* isolate, Handle calendar, + Handle fields, Handle options); + // #sec-temporal.calendar.prototype.tostring static MaybeHandle ToString(Isolate* isolate, Handle calendar, diff --git a/test/test262/test262.status b/test/test262/test262.status index c412f087b5..b6723c1aab 100644 --- a/test/test262/test262.status +++ b/test/test262/test262.status @@ -471,15 +471,8 @@ 'built-ins/Temporal/Calendar/prototype/dateAdd/throw-range-error-from-ToTemporalDate': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateAdd/throw-range-error-from-ToTemporalDuration': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateAdd/throw-type-error-from-GetOptionsObject': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/branding': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/fields-not-object': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/infinity-throws-rangeerror': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/overflow-invalid-string': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateFromFields/overflow-undefined': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateFromFields/overflow-wrong-type': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/throws-range-error': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/throws-type-error': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dateFromFields/throw-type-error-from-GetOptionsObject': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateFromFields/with-year-monthCode-day': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateFromFields/with-year-monthCode-day-need-constrain': [FAIL], 'built-ins/Temporal/Calendar/prototype/dateFromFields/with-year-month-day': [FAIL], @@ -518,23 +511,18 @@ 'built-ins/Temporal/Calendar/prototype/day/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/day/month-day': [FAIL], 'built-ins/Temporal/Calendar/prototype/dayOfWeek/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dayOfWeek/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/dayOfWeek/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/dayOfWeek/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/dayOfYear/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/dayOfYear/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/dayOfYear/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/dayOfYear/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInMonth/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/daysInMonth/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInMonth/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInMonth/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInWeek/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/daysInWeek/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInWeek/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInWeek/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInYear/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/daysInYear/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInYear/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/daysInYear/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/day/string': [FAIL], @@ -548,7 +536,6 @@ 'built-ins/Temporal/Calendar/prototype/fields/repeated-throw': [FAIL], 'built-ins/Temporal/Calendar/prototype/fields/reverse': [FAIL], 'built-ins/Temporal/Calendar/prototype/inLeapYear/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/inLeapYear/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/inLeapYear/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/inLeapYear/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/mergeFields/arguments-empty-object': [FAIL], @@ -589,7 +576,6 @@ 'built-ins/Temporal/Calendar/prototype/month/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/month/month-day-throw-type-error': [FAIL], 'built-ins/Temporal/Calendar/prototype/monthsInYear/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/monthsInYear/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/monthsInYear/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/monthsInYear/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/month/string': [FAIL], @@ -607,7 +593,6 @@ 'built-ins/Temporal/Calendar/prototype/weekOfYear/cross-year': [FAIL], 'built-ins/Temporal/Calendar/prototype/weekOfYear/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/year/basic': [FAIL], - 'built-ins/Temporal/Calendar/prototype/year/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/Calendar/prototype/year/calendar-temporal-object': [FAIL], 'built-ins/Temporal/Calendar/prototype/year/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/Calendar/prototype/yearMonthFromFields/branding': [FAIL], @@ -926,7 +911,6 @@ 'built-ins/Temporal/PlainDate/from/argument-plaindate': [FAIL], 'built-ins/Temporal/PlainDate/from/argument-plaindatetime': [FAIL], 'built-ins/Temporal/PlainDate/from/argument-string': [FAIL], - 'built-ins/Temporal/PlainDate/from/calendar-fields-iterable': [FAIL], 'built-ins/Temporal/PlainDate/from/calendar-temporal-object': [FAIL], 'built-ins/Temporal/PlainDate/from/infinity-throws-rangeerror': [FAIL], 'built-ins/Temporal/PlainDate/from/limits': [FAIL],