1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 19:07:35 +00:00

LibJS: Implement Temporal.PlainTime.prototype.with

Ticks off one box in #8982 and fixes one test262 case.
This commit is contained in:
Luke Wilde 2021-09-08 18:59:09 +01:00 committed by Linus Groh
parent b8d683c5fb
commit d943b8f100
8 changed files with 399 additions and 0 deletions

View file

@ -191,12 +191,14 @@
M(TemporalInvalidPlainDateTime, "Invalid plain date time") \
M(TemporalInvalidPlainMonthDay, "Invalid plain month day") \
M(TemporalInvalidPlainTime, "Invalid plain time") \
M(TemporalInvalidPlainTimeLikeObject, "Invalid plain time-like object") \
M(TemporalInvalidPlainYearMonth, "Invalid plain year month") \
M(TemporalInvalidTime, "Invalid time") \
M(TemporalInvalidTimeZoneName, "Invalid time zone name") \
M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}") \
M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \
M(TemporalMissingRequiredProperty, "Required property {} is missing or undefined") \
M(TemporalPlainTimeWithArgumentMustNotHave, "Argument must not have a defined {} property") \
M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \
M(TemporalPropertyMustBePositiveInteger, "Property must be a positive integer") \
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \

View file

@ -7,6 +7,7 @@
#include <AK/CharacterTypes.h>
#include <AK/DateTimeLexer.h>
#include <AK/TypeCasts.h>
#include <AK/Variant.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/PropertyName.h>
@ -14,8 +15,10 @@
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
@ -597,6 +600,20 @@ Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit)
return 1000;
}
// 13.26 RejectTemporalCalendarType ( object ), https://tc39.es/proposal-temporal/#sec-temporal-rejecttemporalcalendartype
void reject_temporal_calendar_type(GlobalObject& global_object, Object& object)
{
auto& vm = global_object.vm();
// 1. Assert: Type(object) is Object.
// 2. If object has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
if (is<PlainDate>(object) || is<PlainDateTime>(object) || is<PlainMonthDay>(object) || is<PlainTime>(object) || is<PlainYearMonth>(object) || is<ZonedDateTime>(object)) {
// a. Throw a TypeError exception.
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalPlainTimeWithArgumentMustNotHave, "calendar or timeZone");
}
}
// 13.27 FormatSecondsStringPart ( second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatsecondsstringpart
String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<String, u8> const& precision)
{

View file

@ -91,6 +91,7 @@ Optional<String> to_smallest_temporal_unit(GlobalObject&, Object& normalized_opt
void validate_temporal_unit_range(GlobalObject&, String const& largest_unit, String const& smallest_unit);
String larger_of_two_temporal_units(StringView, StringView);
Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit);
void reject_temporal_calendar_type(GlobalObject&, Object&);
String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<String, u8> const& precision);
double constrain_to_range(double x, double minimum, double maximum);
BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, StringView rounding_mode);

View file

@ -134,6 +134,54 @@ PlainTime* to_temporal_time(GlobalObject& global_object, Value item, Optional<St
return create_temporal_time(global_object, result->hour, result->minute, result->second, result->millisecond, result->microsecond, result->nanosecond);
}
// 4.5.3 ToPartialTime ( temporalTimeLike ), https://tc39.es/proposal-temporal/#sec-temporal-topartialtime
Optional<PartialUnregulatedTemporalTime> to_partial_time(GlobalObject& global_object, Object& temporal_time_like)
{
auto& vm = global_object.vm();
// 1. Assert: Type(temporalTimeLike) is Object.
// 2. Let result be the Record { [[Hour]]: undefined, [[Minute]]: undefined, [[Second]]: undefined, [[Millisecond]]: undefined, [[Microsecond]]: undefined, [[Nanosecond]]: undefined }.
auto result = PartialUnregulatedTemporalTime {};
// 3. Let any be false.
bool any = false;
// 4. For each row of Table 3, except the header row, in table order, do
for (auto& [internal_slot, property] : temporal_time_like_properties<PartialUnregulatedTemporalTime, Optional<double>>(vm)) {
// a. Let property be the Property value of the current row.
// b. Let value be ? Get(temporalTimeLike, property).
auto value = temporal_time_like.get(property);
if (vm.exception())
return {};
// c. If value is not undefined, then
if (!value.is_undefined()) {
// i. Set any to true.
any = true;
// ii. Set value to ? ToIntegerThrowOnInfinity(value).
auto value_number = to_integer_throw_on_infinity(global_object, value, ErrorType::TemporalPropertyMustBeFinite);
if (vm.exception())
return {};
// iii. Set result's internal slot whose name is the Internal Slot value of the current row to value.
result.*internal_slot = value_number;
}
}
// 5. If any is false, then
if (!any) {
// a. Throw a TypeError exception.
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalInvalidPlainTimeLikeObject);
return {};
}
// 6. Return result.
return result;
}
// 4.5.4 RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulatetime
Optional<TemporalTime> regulate_time(GlobalObject& global_object, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, StringView overflow)
{

View file

@ -60,6 +60,15 @@ struct UnregulatedTemporalTime {
double nanosecond;
};
struct PartialUnregulatedTemporalTime {
Optional<double> hour;
Optional<double> minute;
Optional<double> second;
Optional<double> millisecond;
Optional<double> microsecond;
Optional<double> nanosecond;
};
// Table 3: Properties of a TemporalTimeLike, https://tc39.es/proposal-temporal/#table-temporal-temporaltimelike-properties
template<typename StructT, typename ValueT>
@ -82,6 +91,7 @@ auto temporal_time_like_properties = [](VM& vm) {
};
PlainTime* to_temporal_time(GlobalObject&, Value item, Optional<StringView> overflow = {});
Optional<PartialUnregulatedTemporalTime> to_partial_time(GlobalObject&, Object& temporal_time_like);
Optional<TemporalTime> regulate_time(GlobalObject&, double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, StringView overflow);
bool is_valid_time(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond);
DaysAndTime balance_time(i64 hour, i64 minute, i64 second, i64 millisecond, i64 microsecond, i64 nanosecond);

View file

@ -38,6 +38,7 @@ void PlainTimePrototype::initialize(GlobalObject& global_object)
define_native_accessor(vm.names.nanosecond, nanosecond_getter, {}, Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(vm.names.with, with, 1, attr);
define_native_function(vm.names.equals, equals, 1, attr);
define_native_function(vm.names.toPlainDateTime, to_plain_date_time, 1, attr);
define_native_function(vm.names.getISOFields, get_iso_fields, 0, attr);
@ -148,6 +149,115 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::nanosecond_getter)
return Value(temporal_time->iso_nanosecond());
}
// 4.3.12 Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::with)
{
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]).
auto* temporal_time = typed_this(global_object);
if (vm.exception())
return {};
auto temporal_time_like_argument = vm.argument(0);
// 3. If Type(temporalTimeLike) is not Object, then
if (!temporal_time_like_argument.is_object()) {
// a. Throw a TypeError exception.
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, temporal_time_like_argument.to_string_without_side_effects());
return {};
}
auto& temporal_time_like = temporal_time_like_argument.as_object();
// 4. Perform ? RejectTemporalCalendarType(temporalTimeLike).
reject_temporal_calendar_type(global_object, temporal_time_like);
if (vm.exception())
return {};
// 5. Let calendarProperty be ? Get(temporalTimeLike, "calendar").
auto calendar_property = temporal_time_like.get(vm.names.calendar);
if (vm.exception())
return {};
// 6. If calendarProperty is not undefined, then
if (!calendar_property.is_undefined()) {
// a. Throw a TypeError exception.
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalPlainTimeWithArgumentMustNotHave, "calendar");
return {};
}
// 7. Let timeZoneProperty be ? Get(temporalTimeLike, "timeZone").
auto time_zone_property = temporal_time_like.get(vm.names.timeZone);
if (vm.exception())
return {};
// 8. If timeZoneProperty is not undefined, then
if (!time_zone_property.is_undefined()) {
// a. Throw a TypeError exception.
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalPlainTimeWithArgumentMustNotHave, "timeZone");
return {};
}
// 9. Let partialTime be ? ToPartialTime(temporalTimeLike).
auto partial_time = to_partial_time(global_object, temporal_time_like);
if (vm.exception())
return {};
// 10. Set options to ? GetOptionsObject(options).
auto* options = get_options_object(global_object, vm.argument(1));
if (vm.exception())
return {};
// 11. Let overflow be ? ToTemporalOverflow(options).
auto overflow = to_temporal_overflow(global_object, *options);
if (vm.exception())
return {};
// 12. If partialTime.[[Hour]] is not undefined, then
// a. Let hour be partialTime.[[Hour]].
// 13. Else,
// a. Let hour be temporalTime.[[ISOHour]].
auto hour = partial_time->hour.value_or(temporal_time->iso_hour());
// 14. If partialTime.[[Minute]] is not undefined, then
// a. Let minute be partialTime.[[Minute]].
// 15. Else,
// a. Let minute be temporalTime.[[ISOMinute]].
auto minute = partial_time->minute.value_or(temporal_time->iso_minute());
// 16. If partialTime.[[Second]] is not undefined, then
// a. Let second be partialTime.[[Second]].
// 17. Else,
// a. Let second be temporalTime.[[ISOSecond]].
auto second = partial_time->second.value_or(temporal_time->iso_second());
// 18. If partialTime.[[Millisecond]] is not undefined, then
// a. Let millisecond be partialTime.[[Millisecond]].
// 19. Else,
// a. Let millisecond be temporalTime.[[ISOMillisecond]].
auto millisecond = partial_time->millisecond.value_or(temporal_time->iso_millisecond());
// 20. If partialTime.[[Microsecond]] is not undefined, then
// a. Let microsecond be partialTime.[[Microsecond]].
// 21. Else,
// a. Let microsecond be temporalTime.[[ISOMicrosecond]].
auto microsecond = partial_time->microsecond.value_or(temporal_time->iso_microsecond());
// 22. If partialTime.[[Nanosecond]] is not undefined, then
// a. Let nanosecond be partialTime.[[Nanosecond]].
// 23. Else,
// a. Let nanosecond be temporalTime.[[ISONanosecond]].
auto nanosecond = partial_time->nanosecond.value_or(temporal_time->iso_nanosecond());
// 24. Let result be ? RegulateTime(hour, minute, second, millisecond, microsecond, nanosecond, overflow).
auto result = regulate_time(global_object, hour, minute, second, millisecond, microsecond, nanosecond, *overflow);
if (vm.exception())
return {};
// 25. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
return create_temporal_time(global_object, result->hour, result->minute, result->second, result->millisecond, result->microsecond, result->nanosecond);
}
// 4.3.16 Temporal.PlainTime.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals
JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::equals)
{

View file

@ -26,6 +26,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(millisecond_getter);
JS_DECLARE_NATIVE_FUNCTION(microsecond_getter);
JS_DECLARE_NATIVE_FUNCTION(nanosecond_getter);
JS_DECLARE_NATIVE_FUNCTION(with);
JS_DECLARE_NATIVE_FUNCTION(equals);
JS_DECLARE_NATIVE_FUNCTION(to_plain_date_time);
JS_DECLARE_NATIVE_FUNCTION(get_iso_fields);

View file

@ -0,0 +1,210 @@
const PLAIN_TIME_PROPERTIES = [
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
];
const REJECTED_CALENDAR_TYPES_THREE_ARGUMENTS = [
Temporal.PlainDate,
Temporal.PlainDateTime,
Temporal.PlainTime,
];
const REJECTED_CALENDAR_TYPES_TWO_ARGUMENTS = [Temporal.PlainMonthDay, Temporal.PlainYearMonth];
describe("correct behaviour", () => {
test("length is 1", () => {
expect(Temporal.PlainTime.prototype.with).toHaveLength(1);
});
test("basic functionality", () => {
const plainTime = new Temporal.PlainTime(1, 2, 3).with({ hour: 4, foo: 5, second: 6 });
expect(plainTime.hour).toBe(4);
expect(plainTime.minute).toBe(2);
expect(plainTime.second).toBe(6);
});
test("each property is looked up from the object", () => {
for (const property of PLAIN_TIME_PROPERTIES) {
const plainTime = new Temporal.PlainTime().with({ [property]: 1 });
expect(plainTime[property]).toBe(1);
}
});
test("each property is coerced to number", () => {
for (const property of PLAIN_TIME_PROPERTIES) {
const plainTime = new Temporal.PlainTime().with({ [property]: "1" });
expect(plainTime[property]).toBe(1);
}
});
test("argument can have a calendar property as long as it's undefined", () => {
expect(() => {
new Temporal.PlainTime().with({
calendar: undefined,
});
}).not.toThrowWithMessage(TypeError, "Argument must not have a defined calendar property");
});
test("argument can have a timeZone property as long as it's undefined", () => {
expect(() => {
new Temporal.PlainTime().with({
timeZone: undefined,
});
}).not.toThrowWithMessage(TypeError, "Argument must not have a defined timeZone property");
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainTime object", () => {
expect(() => {
Temporal.PlainTime.prototype.with.call("foo");
}).toThrowWithMessage(TypeError, "Not a Temporal.PlainTime object");
});
test("argument is not an object", () => {
expect(() => {
new Temporal.PlainTime().with("foo");
}).toThrowWithMessage(TypeError, "foo is not an object");
expect(() => {
new Temporal.PlainTime().with(42);
}).toThrowWithMessage(TypeError, "42 is not an object");
});
test("options is not an object", () => {
expect(() => {
new Temporal.PlainTime().with({ hour: 1 }, "foo");
}).toThrowWithMessage(TypeError, "Options is not an object");
expect(() => {
new Temporal.PlainTime().with({ hour: 1 }, 42);
}).toThrowWithMessage(TypeError, "Options is not an object");
});
test("invalid overflow option", () => {
expect(() => {
new Temporal.PlainTime().with({ hour: 1 }, { overflow: "a" });
}).toThrowWithMessage(RangeError, "a is not a valid value for option overflow");
});
test("argument is an invalid plain time-like object", () => {
expect(() => {
new Temporal.PlainTime().with({});
}).toThrowWithMessage(TypeError, "Invalid plain time-like object");
expect(() => {
new Temporal.PlainTime().with({ foo: 1, bar: 2 });
}).toThrowWithMessage(TypeError, "Invalid plain time-like object");
});
test("error when coercing property to number", () => {
for (const property of PLAIN_TIME_PROPERTIES) {
expect(() => {
new Temporal.PlainTime().with({
[property]: {
valueOf() {
throw new Error("error occurred");
},
},
});
}).toThrowWithMessage(Error, "error occurred");
}
});
test("property must be finite", () => {
for (const property of PLAIN_TIME_PROPERTIES) {
expect(() => {
new Temporal.PlainTime().with({ [property]: Infinity });
}).toThrowWithMessage(RangeError, "Property must not be Infinity");
expect(() => {
new Temporal.PlainTime().with({ [property]: -Infinity });
}).toThrowWithMessage(RangeError, "Property must not be Infinity");
}
});
test("error when getting property", () => {
for (const property of PLAIN_TIME_PROPERTIES) {
expect(() => {
new Temporal.PlainTime().with({
get [property]() {
throw new Error("error occurred");
},
});
}).toThrowWithMessage(Error, "error occurred");
}
});
test("argument must not have a defined calendar property", () => {
expect(() => {
new Temporal.PlainTime().with({
calendar: null,
});
}).toThrowWithMessage(TypeError, "Argument must not have a defined calendar property");
expect(() => {
new Temporal.PlainTime().with({
calendar: 1,
});
}).toThrowWithMessage(TypeError, "Argument must not have a defined calendar property");
});
test("argument must not have a defined timeZone property", () => {
expect(() => {
new Temporal.PlainTime().with({
timeZone: null,
});
}).toThrowWithMessage(TypeError, "Argument must not have a defined timeZone property");
expect(() => {
new Temporal.PlainTime().with({
timeZone: 1,
});
}).toThrowWithMessage(TypeError, "Argument must not have a defined timeZone property");
});
test("error when getting calendar", () => {
expect(() => {
new Temporal.PlainTime().with({
get calendar() {
throw new Error("error occurred");
},
});
}).toThrowWithMessage(Error, "error occurred");
});
test("error when getting timeZone", () => {
expect(() => {
new Temporal.PlainTime().with({
get timeZone() {
throw new Error("error occurred");
},
});
}).toThrowWithMessage(Error, "error occurred");
});
test("rejects calendar types", () => {
for (const typeWithCalendar of REJECTED_CALENDAR_TYPES_THREE_ARGUMENTS) {
expect(() => {
new Temporal.PlainTime().with(new typeWithCalendar(1, 1, 1));
}).toThrowWithMessage(
TypeError,
"Argument must not have a defined calendar or timeZone property"
);
}
for (const typeWithCalendar of REJECTED_CALENDAR_TYPES_TWO_ARGUMENTS) {
expect(() => {
new Temporal.PlainTime().with(new typeWithCalendar(1, 1));
}).toThrowWithMessage(
TypeError,
"Argument must not have a defined calendar or timeZone property"
);
}
expect(() => {
new Temporal.PlainTime().with(new Temporal.ZonedDateTime(1n, {}));
}).toThrowWithMessage(
TypeError,
"Argument must not have a defined calendar or timeZone property"
);
});
});