summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/TemporalFields.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/temporal/TemporalFields.cpp')
-rw-r--r--js/src/builtin/temporal/TemporalFields.cpp939
1 files changed, 939 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/TemporalFields.cpp b/js/src/builtin/temporal/TemporalFields.cpp
new file mode 100644
index 0000000000..9ac2e44639
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalFields.cpp
@@ -0,0 +1,939 @@
+/* -*- 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/TemporalFields.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/RangedPtr.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iterator>
+#include <stdint.h>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Temporal.h"
+#include "ds/Sort.h"
+#include "gc/Barrier.h"
+#include "gc/Tracer.h"
+#include "js/AllocPolicy.h"
+#include "js/ComparisonOperators.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/Printer.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Utility.h"
+#include "js/Value.h"
+#include "util/Text.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+#include "vm/SymbolType.h"
+
+#include "vm/JSAtomUtils-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+void TemporalFields::trace(JSTracer* trc) {
+ TraceNullableRoot(trc, &monthCode, "TemporalFields::monthCode");
+ TraceNullableRoot(trc, &offset, "TemporalFields::offset");
+ TraceNullableRoot(trc, &era, "TemporalFields::era");
+ TraceRoot(trc, &timeZone, "TemporalFields::timeZone");
+}
+
+static PropertyName* ToPropertyName(JSContext* cx, TemporalField field) {
+ switch (field) {
+ case TemporalField::Year:
+ return cx->names().year;
+ case TemporalField::Month:
+ return cx->names().month;
+ case TemporalField::MonthCode:
+ return cx->names().monthCode;
+ case TemporalField::Day:
+ return cx->names().day;
+ case TemporalField::Hour:
+ return cx->names().hour;
+ case TemporalField::Minute:
+ return cx->names().minute;
+ case TemporalField::Second:
+ return cx->names().second;
+ case TemporalField::Millisecond:
+ return cx->names().millisecond;
+ case TemporalField::Microsecond:
+ return cx->names().microsecond;
+ case TemporalField::Nanosecond:
+ return cx->names().nanosecond;
+ case TemporalField::Offset:
+ return cx->names().offset;
+ case TemporalField::Era:
+ return cx->names().era;
+ case TemporalField::EraYear:
+ return cx->names().eraYear;
+ case TemporalField::TimeZone:
+ return cx->names().timeZone;
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static const char* ToCString(TemporalField field) {
+ switch (field) {
+ case TemporalField::Year:
+ return "year";
+ case TemporalField::Month:
+ return "month";
+ case TemporalField::MonthCode:
+ return "monthCode";
+ case TemporalField::Day:
+ return "day";
+ case TemporalField::Hour:
+ return "hour";
+ case TemporalField::Minute:
+ return "minute";
+ case TemporalField::Second:
+ return "second";
+ case TemporalField::Millisecond:
+ return "millisecond";
+ case TemporalField::Microsecond:
+ return "microsecond";
+ case TemporalField::Nanosecond:
+ return "nanosecond";
+ case TemporalField::Offset:
+ return "offset";
+ case TemporalField::Era:
+ return "era";
+ case TemporalField::EraYear:
+ return "eraYear";
+ case TemporalField::TimeZone:
+ return "timeZone";
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static JS::UniqueChars QuoteString(JSContext* cx, const char* str) {
+ Sprinter sprinter(cx);
+ if (!sprinter.init()) {
+ return nullptr;
+ }
+ mozilla::Range range(reinterpret_cast<const Latin1Char*>(str),
+ std::strlen(str));
+ QuoteString<QuoteTarget::String>(&sprinter, range);
+ return sprinter.release();
+}
+
+static JS::UniqueChars QuoteString(JSContext* cx, PropertyKey key) {
+ if (key.isString()) {
+ return QuoteString(cx, key.toString());
+ }
+
+ if (key.isInt()) {
+ Int32ToCStringBuf buf;
+ size_t length;
+ const char* str = Int32ToCString(&buf, key.toInt(), &length);
+ return DuplicateString(cx, str, length);
+ }
+
+ MOZ_ASSERT(key.isSymbol());
+ return QuoteString(cx, key.toSymbol()->description());
+}
+
+static mozilla::Maybe<TemporalField> ToTemporalField(JSContext* cx,
+ PropertyKey property) {
+ static constexpr TemporalField fieldNames[] = {
+ TemporalField::Year, TemporalField::Month,
+ TemporalField::MonthCode, TemporalField::Day,
+ TemporalField::Hour, TemporalField::Minute,
+ TemporalField::Second, TemporalField::Millisecond,
+ TemporalField::Microsecond, TemporalField::Nanosecond,
+ TemporalField::Offset, TemporalField::Era,
+ TemporalField::EraYear, TemporalField::TimeZone,
+ };
+
+ for (const auto& fieldName : fieldNames) {
+ auto* name = ToPropertyName(cx, fieldName);
+ if (property.isAtom(name)) {
+ return mozilla::Some(fieldName);
+ }
+ }
+ return mozilla::Nothing();
+}
+
+static JSString* ToPrimitiveAndRequireString(JSContext* cx,
+ Handle<Value> value) {
+ Rooted<Value> primitive(cx, value);
+ if (!ToPrimitive(cx, JSTYPE_STRING, &primitive)) {
+ return nullptr;
+ }
+ if (!primitive.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, primitive,
+ nullptr, "not a string");
+ return nullptr;
+ }
+ return primitive.toString();
+}
+
+static Value TemporalFieldDefaultValue(TemporalField field) {
+ switch (field) {
+ case TemporalField::Year:
+ case TemporalField::Month:
+ case TemporalField::MonthCode:
+ case TemporalField::Day:
+ case TemporalField::Offset:
+ case TemporalField::Era:
+ case TemporalField::EraYear:
+ case TemporalField::TimeZone:
+ return UndefinedValue();
+ case TemporalField::Hour:
+ case TemporalField::Minute:
+ case TemporalField::Second:
+ case TemporalField::Millisecond:
+ case TemporalField::Microsecond:
+ case TemporalField::Nanosecond:
+ return Int32Value(0);
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static bool TemporalFieldConvertValue(JSContext* cx, TemporalField field,
+ MutableHandle<Value> value) {
+ auto* name = ToCString(field);
+ switch (field) {
+ case TemporalField::Year:
+ case TemporalField::Hour:
+ case TemporalField::Minute:
+ case TemporalField::Second:
+ case TemporalField::Millisecond:
+ case TemporalField::Microsecond:
+ case TemporalField::Nanosecond:
+ case TemporalField::EraYear: {
+ double num;
+ if (!ToIntegerWithTruncation(cx, value, name, &num)) {
+ return false;
+ }
+ value.setNumber(num);
+ return true;
+ }
+
+ case TemporalField::Month:
+ case TemporalField::Day: {
+ double num;
+ if (!ToPositiveIntegerWithTruncation(cx, value, name, &num)) {
+ return false;
+ }
+ value.setNumber(num);
+ return true;
+ }
+
+ case TemporalField::MonthCode:
+ case TemporalField::Offset:
+ case TemporalField::Era: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ value.setString(str);
+ return true;
+ }
+
+ case TemporalField::TimeZone:
+ // NB: timeZone has no conversion function.
+ return true;
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static int32_t ComparePropertyKey(PropertyKey x, PropertyKey y) {
+ MOZ_ASSERT(x.isAtom() || x.isInt());
+ MOZ_ASSERT(y.isAtom() || y.isInt());
+
+ if (MOZ_LIKELY(x.isAtom() && y.isAtom())) {
+ return CompareStrings(x.toAtom(), y.toAtom());
+ }
+
+ if (x.isInt() && y.isInt()) {
+ return x.toInt() - y.toInt();
+ }
+
+ uint32_t index = uint32_t(x.isInt() ? x.toInt() : y.toInt());
+ JSAtom* str = x.isAtom() ? x.toAtom() : y.toAtom();
+
+ char16_t buf[UINT32_CHAR_BUFFER_LENGTH];
+ mozilla::RangedPtr<char16_t> end(std::end(buf), buf, std::end(buf));
+ mozilla::RangedPtr<char16_t> start = BackfillIndexInCharBuffer(index, end);
+
+ int32_t result = CompareChars(start.get(), end - start, str);
+ return x.isInt() ? result : -result;
+}
+
+#ifdef DEBUG
+static bool IsSorted(std::initializer_list<TemporalField> 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;
+ });
+}
+
+static bool IsSorted(const TemporalFieldNames& fieldNames) {
+ return std::is_sorted(
+ fieldNames.begin(), fieldNames.end(),
+ [](auto x, auto y) { return ComparePropertyKey(x, y) < 0; });
+}
+#endif
+
+// clang-format off
+//
+// TODO: |fields| is often a built-in Temporal type, so we likely want to
+// optimise for this case.
+//
+// Consider the case when PlainDate.prototype.toPlainMonthDay is called. The
+// following steps are applied:
+//
+// 1. CalendarFields(calendar, «"day", "monthCode"») is called to retrieve the
+// relevant calendar fields. For (most?) built-in calendars this will just
+// return the input list «"day", "monthCode"».
+// 2. PrepareTemporalFields(plainDate, «"day", "monthCode"») is called. This
+// will access the properties `plainDate.day` and `plainDate.monthCode`.
+// a. `plainDate.day` will call CalendarDay(calendar, plainDate).
+// b. For built-in calendars, this will simply access `plainDate.[[IsoDay]]`.
+// c. `plainDate.monthCode` will call CalendarMonthCode(calendar, plainDate).
+// d. For built-in calendars, ISOMonthCode(plainDate.[[IsoMonth]]) is called.
+// 3. CalendarMonthDayFromFields(calendar, {day, monthCode}) is called.
+// 4. For built-in calendars, this calls PrepareTemporalFields({day, monthCode},
+// «"day", "month", "monthCode", "year"», «"day"»).
+// 5. The previous PrepareTemporalFields call is a no-op and returns {day, monthCode}.
+// 6. Then ISOMonthDayFromFields({day, monthCode}, "constrain") gets called.
+// 7. ResolveISOMonth(monthCode) is called to parse the just created `monthCode`.
+// 8. RegulateISODate(referenceISOYear, month, day, "constrain") is called.
+// 9. Finally CreateTemporalMonthDay is called to create the PlainMonthDay instance.
+//
+// All these steps could be simplified to just:
+// 1. CreateTemporalMonthDay(referenceISOYear, plainDate.[[IsoMonth]], plainDate.[[IsoDay]]).
+//
+// When the following conditions are true:
+// 1. The `plainDate` is a Temporal.PlainDate instance and has no overridden methods.
+// 2. The `calendar` is a Temporal.Calendar instance and has no overridden methods.
+// 3. Temporal.PlainDate.prototype and Temporal.Calendar.prototype are in their initial state.
+// 4. Array iteration is still in its initial state. (Required by CalendarFields)
+//
+// PlainDate_toPlainMonthDay has an example implementation for this optimisation.
+//
+// clang-format on
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields )
+ */
+bool js::temporal::PrepareTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ std::initializer_list<TemporalField> fieldNames,
+ std::initializer_list<TemporalField> requiredFields,
+ MutableHandle<TemporalFields> result) {
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ // Step 4. (|fieldNames| is sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // |requiredFields| is sorted and doesn't contain any duplicate elements.
+ MOZ_ASSERT(IsSorted(requiredFields));
+ MOZ_ASSERT(std::adjacent_find(requiredFields.begin(), requiredFields.end()) ==
+ requiredFields.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (auto fieldName : fieldNames) {
+ auto* property = ToPropertyName(cx, fieldName);
+ auto* cstr = ToCString(fieldName);
+
+ // Step 6.a. (Not applicable in our implementation.)
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return false;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1. (Not applicable in our implementation.)
+
+ // Steps 6.b.ii.2-3.
+ switch (fieldName) {
+ case TemporalField::Year:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.year())) {
+ return false;
+ }
+ break;
+ case TemporalField::Month:
+ if (!ToPositiveIntegerWithTruncation(cx, value, cstr,
+ &result.month())) {
+ return false;
+ }
+ break;
+ case TemporalField::MonthCode: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ result.monthCode().set(str);
+ break;
+ }
+ case TemporalField::Day:
+ if (!ToPositiveIntegerWithTruncation(cx, value, cstr,
+ &result.day())) {
+ return false;
+ }
+ break;
+ case TemporalField::Hour:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.hour())) {
+ return false;
+ }
+ break;
+ case TemporalField::Minute:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.minute())) {
+ return false;
+ }
+ break;
+ case TemporalField::Second:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.second())) {
+ return false;
+ }
+ break;
+ case TemporalField::Millisecond:
+ if (!ToIntegerWithTruncation(cx, value, cstr,
+ &result.millisecond())) {
+ return false;
+ }
+ break;
+ case TemporalField::Microsecond:
+ if (!ToIntegerWithTruncation(cx, value, cstr,
+ &result.microsecond())) {
+ return false;
+ }
+ break;
+ case TemporalField::Nanosecond:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.nanosecond())) {
+ return false;
+ }
+ break;
+ case TemporalField::Offset: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ result.offset().set(str);
+ break;
+ }
+ case TemporalField::Era: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ result.era().set(str);
+ break;
+ }
+ case TemporalField::EraYear:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.eraYear())) {
+ return false;
+ }
+ break;
+ case TemporalField::TimeZone:
+ // NB: TemporalField::TimeZone has no conversion function.
+ result.timeZone().set(value);
+ break;
+ }
+ } else {
+ // Step 6.b.iii.1.
+ if (std::find(requiredFields.begin(), requiredFields.end(), fieldName) !=
+ requiredFields.end()) {
+ if (auto chars = QuoteString(cx, cstr)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY,
+ chars.get());
+ }
+ return false;
+ }
+
+ // `const` can be changed to `constexpr` when we switch to C++20.
+ const TemporalFields FallbackValues{};
+
+ // Steps 6.b.iii.2-3.
+ switch (fieldName) {
+ case TemporalField::Year:
+ result.year() = FallbackValues.year;
+ break;
+ case TemporalField::Month:
+ result.month() = FallbackValues.month;
+ break;
+ case TemporalField::MonthCode:
+ result.monthCode().set(FallbackValues.monthCode);
+ break;
+ case TemporalField::Day:
+ result.day() = FallbackValues.day;
+ break;
+ case TemporalField::Hour:
+ result.hour() = FallbackValues.hour;
+ break;
+ case TemporalField::Minute:
+ result.minute() = FallbackValues.minute;
+ break;
+ case TemporalField::Second:
+ result.second() = FallbackValues.second;
+ break;
+ case TemporalField::Millisecond:
+ result.millisecond() = FallbackValues.millisecond;
+ break;
+ case TemporalField::Microsecond:
+ result.microsecond() = FallbackValues.microsecond;
+ break;
+ case TemporalField::Nanosecond:
+ result.nanosecond() = FallbackValues.nanosecond;
+ break;
+ case TemporalField::Offset:
+ result.offset().set(FallbackValues.offset);
+ break;
+ case TemporalField::Era:
+ result.era().set(FallbackValues.era);
+ break;
+ case TemporalField::EraYear:
+ result.eraYear() = FallbackValues.eraYear;
+ break;
+ case TemporalField::TimeZone:
+ result.timeZone().set(FallbackValues.timeZone);
+ break;
+ }
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ return true;
+}
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* js::temporal::PrepareTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<TemporalFieldNames> fieldNames) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> result(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!result) {
+ return nullptr;
+ }
+
+ // Step 3. (Not applicable in our implementation.)
+
+ // Step 4. (The list is already sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ Handle<PropertyKey> property = fieldNames[i];
+
+ // Step 6.a.
+ MOZ_ASSERT(property != NameToId(cx->names().constructor));
+ MOZ_ASSERT(property != NameToId(cx->names().proto_));
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return nullptr;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (auto fieldName = ToTemporalField(cx, property)) {
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1. (Not applicable in our implementation.)
+
+ // Step 6.b.ii.2.
+ if (!TemporalFieldConvertValue(cx, *fieldName, &value)) {
+ return nullptr;
+ }
+ } else {
+ // Step 6.b.iii.1. (Not applicable in our implementation.)
+
+ // Step 6.b.iii.2.
+ value = TemporalFieldDefaultValue(*fieldName);
+ }
+ }
+
+ // Steps 6.b.ii.3 and 6.b.iii.3.
+ if (!DefineDataProperty(cx, result, property, value)) {
+ return nullptr;
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ return result;
+}
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* js::temporal::PrepareTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<TemporalFieldNames> fieldNames,
+ std::initializer_list<TemporalField> requiredFields) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> result(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!result) {
+ return nullptr;
+ }
+
+ // Step 3. (Not applicable in our implementation.)
+
+ // Step 4. (The list is already sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // |requiredFields| is sorted and doesn't include any duplicate elements.
+ MOZ_ASSERT(IsSorted(requiredFields));
+ MOZ_ASSERT(std::adjacent_find(requiredFields.begin(), requiredFields.end()) ==
+ requiredFields.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ Handle<PropertyKey> property = fieldNames[i];
+
+ // Step 6.a.
+ MOZ_ASSERT(property != NameToId(cx->names().constructor));
+ MOZ_ASSERT(property != NameToId(cx->names().proto_));
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return nullptr;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (auto fieldName = ToTemporalField(cx, property)) {
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1. (Not applicable in our implementation.)
+
+ // Step 6.b.ii.2.
+ if (!TemporalFieldConvertValue(cx, *fieldName, &value)) {
+ return nullptr;
+ }
+ } else {
+ // Step 6.b.iii.1.
+ if (std::find(requiredFields.begin(), requiredFields.end(),
+ *fieldName) != requiredFields.end()) {
+ if (auto chars = QuoteString(cx, property.toString())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY,
+ chars.get());
+ }
+ return nullptr;
+ }
+
+ // Step 6.b.iii.2.
+ value = TemporalFieldDefaultValue(*fieldName);
+ }
+ }
+
+ // Steps 6.b.ii.3 and 6.b.iii.3.
+ if (!DefineDataProperty(cx, result, property, value)) {
+ return nullptr;
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ return result;
+}
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* js::temporal::PreparePartialTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<TemporalFieldNames> fieldNames) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> result(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!result) {
+ return nullptr;
+ }
+
+ // Step 3.
+ bool any = false;
+
+ // Step 4. (The list is already sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ Handle<PropertyKey> property = fieldNames[i];
+
+ // Step 6.a.
+ MOZ_ASSERT(property != NameToId(cx->names().constructor));
+ MOZ_ASSERT(property != NameToId(cx->names().proto_));
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return nullptr;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1.
+ any = true;
+
+ // Step 6.b.ii.2.
+ if (auto fieldName = ToTemporalField(cx, property)) {
+ if (!TemporalFieldConvertValue(cx, *fieldName, &value)) {
+ return nullptr;
+ }
+ }
+
+ // Steps 6.b.ii.3.
+ if (!DefineDataProperty(cx, result, property, value)) {
+ return nullptr;
+ }
+ } else {
+ // Step 6.b.iii. (Not applicable in our implementation.)
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7.
+ if (!any) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_TEMPORAL_FIELDS);
+ return nullptr;
+ }
+
+ // Step 8.
+ return result;
+}
+
+/**
+ * Performs list-concatenation, removes any duplicates, and sorts the result.
+ */
+bool js::temporal::ConcatTemporalFieldNames(
+ const TemporalFieldNames& receiverFieldNames,
+ const TemporalFieldNames& inputFieldNames,
+ TemporalFieldNames& concatenatedFieldNames) {
+ MOZ_ASSERT(IsSorted(receiverFieldNames));
+ MOZ_ASSERT(IsSorted(inputFieldNames));
+ MOZ_ASSERT(concatenatedFieldNames.empty());
+
+ auto appendUnique = [&](auto key) {
+ if (concatenatedFieldNames.empty() ||
+ concatenatedFieldNames.back() != key) {
+ return concatenatedFieldNames.append(key);
+ }
+ return true;
+ };
+
+ size_t i = 0;
+ size_t j = 0;
+
+ // Append the names from |receiverFieldNames| and |inputFieldNames|.
+ while (i < receiverFieldNames.length() && j < inputFieldNames.length()) {
+ auto x = receiverFieldNames[i];
+ auto y = inputFieldNames[j];
+
+ PropertyKey z;
+ if (ComparePropertyKey(x, y) <= 0) {
+ z = x;
+ i++;
+ } else {
+ z = y;
+ j++;
+ }
+ if (!appendUnique(z)) {
+ return false;
+ }
+ }
+
+ // Append the remaining names from |receiverFieldNames|.
+ while (i < receiverFieldNames.length()) {
+ if (!appendUnique(receiverFieldNames[i++])) {
+ return false;
+ }
+ }
+
+ // Append the remaining names from |inputFieldNames|.
+ while (j < inputFieldNames.length()) {
+ if (!appendUnique(inputFieldNames[j++])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool js::temporal::AppendSorted(
+ JSContext* cx, TemporalFieldNames& fieldNames,
+ std::initializer_list<TemporalField> additionalNames) {
+ // |fieldNames| is sorted and doesn't include any duplicates
+ MOZ_ASSERT(IsSorted(fieldNames));
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // |additionalNames| is non-empty, sorted, and doesn't include any duplicates.
+ MOZ_ASSERT(additionalNames.size() > 0);
+ MOZ_ASSERT(IsSorted(additionalNames));
+ MOZ_ASSERT(
+ std::adjacent_find(additionalNames.begin(), additionalNames.end()) ==
+ additionalNames.end());
+
+ // Allocate space for entries from |additionalNames|.
+ if (!fieldNames.growBy(additionalNames.size())) {
+ return false;
+ }
+
+ auto* left = std::prev(fieldNames.end(), additionalNames.size());
+ auto* right = additionalNames.end();
+ auto* out = fieldNames.end();
+
+ // Write backwards into the newly allocated space.
+ while (left != fieldNames.begin() && right != additionalNames.begin()) {
+ MOZ_ASSERT(out != fieldNames.begin());
+ auto x = *std::prev(left);
+ auto y = NameToId(ToPropertyName(cx, *std::prev(right)));
+
+ int32_t r = ComparePropertyKey(x, y);
+
+ // Reject duplicates per PrepareTemporalFields, step 6.c.
+ if (r == 0) {
+ if (auto chars = QuoteString(cx, x)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DUPLICATE_PROPERTY,
+ chars.get());
+ }
+ return false;
+ }
+
+ // Insert the lexicographically greater key.
+ PropertyKey z;
+ if (r > 0) {
+ z = x;
+ left--;
+ } else {
+ z = y;
+ right--;
+ }
+ *--out = z;
+ }
+
+ // Avoid unnecessary copying if possible.
+ if (left == out) {
+ MOZ_ASSERT(right == additionalNames.begin());
+ return true;
+ }
+
+ // Prepend the remaining names from |fieldNames|.
+ while (left != fieldNames.begin()) {
+ MOZ_ASSERT(out != fieldNames.begin());
+ *--out = *--left;
+ }
+
+ // Prepend the remaining names from |additionalNames|.
+ while (right != additionalNames.begin()) {
+ MOZ_ASSERT(out != fieldNames.begin());
+ *--out = NameToId(ToPropertyName(cx, *--right));
+ }
+
+ // All field names were written into the result list.
+ MOZ_ASSERT(out == fieldNames.begin());
+
+ return true;
+}
+
+bool js::temporal::SortTemporalFieldNames(JSContext* cx,
+ TemporalFieldNames& fieldNames) {
+ // Create scratch space for MergeSort().
+ TemporalFieldNames scratch(cx);
+ if (!scratch.resize(fieldNames.length())) {
+ return false;
+ }
+
+ // Sort all field names in alphabetical order.
+ auto comparator = [](const auto& x, const auto& y, bool* lessOrEqual) {
+ *lessOrEqual = ComparePropertyKey(x, y) <= 0;
+ return true;
+ };
+ MOZ_ALWAYS_TRUE(MergeSort(fieldNames.begin(), fieldNames.length(),
+ scratch.begin(), comparator));
+
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ auto property = fieldNames[i];
+
+ // Reject "constructor" and "__proto__" per PrepareTemporalFields, step 6.a.
+ if (property == NameToId(cx->names().constructor) ||
+ property == NameToId(cx->names().proto_)) {
+ if (auto chars = QuoteString(cx, property)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_PROPERTY, chars.get());
+ }
+ return false;
+ }
+
+ // Reject duplicates per PrepareTemporalFields, step 6.c.
+ if (i > 0 && property == fieldNames[i - 1]) {
+ if (auto chars = QuoteString(cx, property)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DUPLICATE_PROPERTY,
+ chars.get());
+ }
+ return false;
+ }
+ }
+
+ return true;
+}