From be07e2e91bda0e1bfb7eb2702eac5a5d182c4684 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 14 Aug 2021 23:54:24 +0100 Subject: [PATCH] LibJS: Start implementing Temporal.PlainMonthDay This commit adds the PlainMonthDay object itself, its constructor and prototype (currently empty), and the CreateTemporalMonthDay abstract operations. --- Userland/Libraries/LibJS/CMakeLists.txt | 3 + Userland/Libraries/LibJS/Forward.h | 1 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../Libraries/LibJS/Runtime/GlobalObject.cpp | 2 + .../LibJS/Runtime/Temporal/PlainMonthDay.cpp | 62 ++++++++++ .../LibJS/Runtime/Temporal/PlainMonthDay.h | 38 ++++++ .../Temporal/PlainMonthDayConstructor.cpp | 109 ++++++++++++++++++ .../Temporal/PlainMonthDayConstructor.h | 28 +++++ .../Temporal/PlainMonthDayPrototype.cpp | 23 ++++ .../Runtime/Temporal/PlainMonthDayPrototype.h | 22 ++++ .../LibJS/Runtime/Temporal/Temporal.cpp | 2 + .../Temporal/PlainMonthDay/PlainMonthDay.js | 53 +++++++++ 12 files changed, 344 insertions(+) create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index b55e3a1390..d606f57480 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -141,6 +141,9 @@ set(SOURCES Runtime/Temporal/PlainDateTime.cpp Runtime/Temporal/PlainDateTimeConstructor.cpp Runtime/Temporal/PlainDateTimePrototype.cpp + Runtime/Temporal/PlainMonthDay.cpp + Runtime/Temporal/PlainMonthDayConstructor.cpp + Runtime/Temporal/PlainMonthDayPrototype.cpp Runtime/Temporal/PlainTime.cpp Runtime/Temporal/PlainTimeConstructor.cpp Runtime/Temporal/PlainTimePrototype.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 7247cefd24..5c88c3c236 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -84,6 +84,7 @@ __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \ __JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \ + __JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \ __JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \ __JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor) \ __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 200d33a9ed..fa98a1e02b 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -179,6 +179,7 @@ M(TemporalInvalidOffsetNanosecondsValue, "Invalid offset nanoseconds value, must be in range -86400 * 10^9 to 86400 * 10^9") \ M(TemporalInvalidPlainDate, "Invalid plain date") \ M(TemporalInvalidPlainDateTime, "Invalid plain date time") \ + M(TemporalInvalidPlainMonthDay, "Invalid plain month day") \ M(TemporalInvalidPlainTime, "Invalid plain time") \ M(TemporalInvalidPlainYearMonth, "Invalid plain year month") \ M(TemporalInvalidTime, "Invalid time") \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 09890aab8c..47d4de80af 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -80,6 +80,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp new file mode 100644 index 0000000000..76be370a88 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace JS::Temporal { + +// 10 Temporal.PlainMonthDay Objects, https://tc39.es/proposal-temporal/#sec-temporal-plainmonthday-objects +PlainMonthDay::PlainMonthDay(u8 iso_month, u8 iso_day, i32 iso_year, Object& calendar, Object& prototype) + : Object(prototype) + , m_iso_year(iso_year) + , m_iso_month(iso_month) + , m_iso_day(iso_day) + , m_calendar(calendar) +{ +} + +void PlainMonthDay::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(&m_calendar); +} + +// 10.5.2 CreateTemporalMonthDay ( isoMonth, isoDay, calendar, referenceISOYear [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalmonthday +PlainMonthDay* create_temporal_month_day(GlobalObject& global_object, u8 iso_month, u8 iso_day, Object& calendar, i32 reference_iso_year, FunctionObject* new_target) +{ + auto& vm = global_object.vm(); + + // 1. Assert: isoMonth, isoDay, and referenceISOYear are integers. + // 2. Assert: Type(calendar) is Object. + + // 3. If ! IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. + if (!is_valid_iso_date(reference_iso_year, iso_month, iso_day)) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidPlainMonthDay); + return {}; + } + + // 4. If newTarget is not present, set it to %Temporal.PlainMonthDay%. + if (!new_target) + new_target = global_object.temporal_plain_month_day_constructor(); + + // 5. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainMonthDay.prototype%", ยซ [[InitializedTemporalMonthDay]], [[ISOMonth]], [[ISODay]], [[ISOYear]], [[Calendar]] ยป). + // 6. Set object.[[ISOMonth]] to isoMonth. + // 7. Set object.[[ISODay]] to isoDay. + // 8. Set object.[[Calendar]] to calendar. + // 9. Set object.[[ISOYear]] to referenceISOYear. + auto* object = ordinary_create_from_constructor(global_object, *new_target, &GlobalObject::temporal_plain_month_day_prototype, iso_month, iso_day, reference_iso_year, calendar); + if (vm.exception()) + return {}; + + // 10. Return object. + return object; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h new file mode 100644 index 0000000000..e8209eaef2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDay.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class PlainMonthDay final : public Object { + JS_OBJECT(PlainMonthDay, Object); + +public: + PlainMonthDay(u8 iso_month, u8 iso_day, i32 iso_year, Object& calendar, Object& prototype); + virtual ~PlainMonthDay() override = default; + + [[nodiscard]] i32 iso_year() const { return m_iso_year; } + [[nodiscard]] u8 iso_month() const { return m_iso_month; } + [[nodiscard]] u8 iso_day() const { return m_iso_day; } + [[nodiscard]] Object const& calendar() const { return m_calendar; } + [[nodiscard]] Object& calendar() { return m_calendar; } + +private: + virtual void visit_edges(Visitor&) override; + + // 10.4 Properties of Temporal.PlainMonthDay Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plainmonthday-instances + i32 m_iso_year { 0 }; // [[ISOYear]] + u8 m_iso_month { 0 }; // [[ISOMonth]] + u8 m_iso_day { 0 }; // [[ISODay]] + Object& m_calendar; // [[Calendar]] +}; + +PlainMonthDay* create_temporal_month_day(GlobalObject&, u8 iso_month, u8 iso_day, Object& calendar, i32 reference_iso_year, FunctionObject* new_target = nullptr); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.cpp new file mode 100644 index 0000000000..1fb8732cc7 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS::Temporal { + +// 10.1 The Temporal.PlainMonthDay Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plainmonthday-constructor +PlainMonthDayConstructor::PlainMonthDayConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.PlainMonthDay.as_string(), *global_object.function_prototype()) +{ +} + +void PlainMonthDayConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 10.2.1 Temporal.PlainMonthDay.prototype, https://tc39.es/proposal-temporal/#sec-temporal-plainmonthday-prototype + define_direct_property(vm.names.prototype, global_object.temporal_plain_month_day_prototype(), 0); + + define_direct_property(vm.names.length, Value(2), Attribute::Configurable); +} + +// 10.1.1 Temporal.PlainMonthDay ( isoMonth, isoDay [ , calendarLike [ , referenceISOYear ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday +Value PlainMonthDayConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined, throw a TypeError exception. + vm.throw_exception(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.PlainMonthDay"); + return {}; +} + +// 10.1.1 Temporal.PlainMonthDay ( isoMonth, isoDay [ , calendarLike [ , referenceISOYear ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday +Value PlainMonthDayConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + auto iso_month = vm.argument(0); + auto iso_day = vm.argument(1); + auto calendar_like = vm.argument(2); + auto reference_iso_year = vm.argument(3); + + // 2. If referenceISOYear is undefined, then + if (reference_iso_year.is_undefined()) { + // a. Set referenceISOYear to 1972๐”ฝ. + reference_iso_year = Value(1972); + } + + // 3. Let m be ? ToIntegerOrInfinity(isoMonth). + auto m = iso_month.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 4. If m is +โˆž or -โˆž, throw a RangeError exception. + if (Value(m).is_infinity()) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidPlainMonthDay); + return {}; + } + + // 5. Let d be ? ToIntegerOrInfinity(isoDay). + auto d = iso_day.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 6. If d is +โˆž or -โˆž, throw a RangeError exception. + if (Value(d).is_infinity()) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidPlainMonthDay); + return {}; + } + + // 7. Let calendar be ? ToTemporalCalendarWithISODefault(calendarLike). + auto* calendar = to_temporal_calendar_with_iso_default(global_object, calendar_like); + if (vm.exception()) + return {}; + + // 8. Let ref be ? ToIntegerOrInfinity(referenceISOYear). + auto ref = reference_iso_year.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 9. If ref is +โˆž or -โˆž, throw a RangeError exception. + if (Value(ref).is_infinity()) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidPlainMonthDay); + return {}; + } + + // IMPLEMENTATION DEFINED: This is an optimization that allows us to treat these doubles as normal integers from this point onwards. + // This does not change the exposed behaviour as the call to CreateTemporalMonthDay will immediately check that these values are valid + // ISO values (for years: -273975 - 273975, for months: 1 - 12, for days: 1 - 31) all of which are subsets of this check. + if (!AK::is_within_range(ref) || !AK::is_within_range(m) || !AK::is_within_range(d)) { + vm.throw_exception(global_object, ErrorType::TemporalInvalidPlainYearMonth); + return {}; + } + + // 10. Return ? CreateTemporalMonthDay(m, d, calendar, ref, NewTarget). + return create_temporal_month_day(global_object, m, d, *calendar, ref, &new_target); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.h new file mode 100644 index 0000000000..540df5f210 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class PlainMonthDayConstructor final : public NativeFunction { + JS_OBJECT(PlainMonthDayConstructor, NativeFunction); + +public: + explicit PlainMonthDayConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~PlainMonthDayConstructor() override = default; + + virtual Value call() override; + virtual Value construct(FunctionObject& new_target) override; + +private: + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp new file mode 100644 index 0000000000..11cb1f4068 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Temporal { + +// 10.3 Properties of the Temporal.PlainMonthDay Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-plainmonthday-prototype-object +PlainMonthDayPrototype::PlainMonthDayPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void PlainMonthDayPrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h new file mode 100644 index 0000000000..fe7d73f8ab --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainMonthDayPrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class PlainMonthDayPrototype final : public Object { + JS_OBJECT(PlainMonthDayPrototype, Object); + +public: + explicit PlainMonthDayPrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~PlainMonthDayPrototype() override = default; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index 0d9c405d96..ca919452f4 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ void Temporal::initialize(GlobalObject& global_object) define_direct_property(vm.names.Instant, global_object.temporal_instant_constructor(), attr); define_direct_property(vm.names.PlainDate, global_object.temporal_plain_date_constructor(), attr); define_direct_property(vm.names.PlainDateTime, global_object.temporal_plain_date_time_constructor(), attr); + define_direct_property(vm.names.PlainMonthDay, global_object.temporal_plain_month_day_constructor(), attr); define_direct_property(vm.names.PlainTime, global_object.temporal_plain_time_constructor(), attr); define_direct_property(vm.names.PlainYearMonth, global_object.temporal_plain_year_month_constructor(), attr); define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.js new file mode 100644 index 0000000000..8131656042 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainMonthDay/PlainMonthDay.js @@ -0,0 +1,53 @@ +describe("errors", () => { + test("called without new", () => { + expect(() => { + Temporal.PlainMonthDay(); + }).toThrowWithMessage( + TypeError, + "Temporal.PlainMonthDay constructor must be called with 'new'" + ); + }); + + test("cannot pass Infinity", () => { + expect(() => { + new Temporal.PlainMonthDay(Infinity); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + expect(() => { + new Temporal.PlainMonthDay(1, Infinity); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + expect(() => { + new Temporal.PlainMonthDay(1, 1, {}, Infinity); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + expect(() => { + new Temporal.PlainMonthDay(-Infinity); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + expect(() => { + new Temporal.PlainMonthDay(1, -Infinity); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + expect(() => { + new Temporal.PlainMonthDay(1, 1, {}, -Infinity); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + }); + + test("cannot pass invalid ISO month/day", () => { + expect(() => { + new Temporal.PlainMonthDay(0, 1); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + expect(() => { + new Temporal.PlainMonthDay(1, 0); + }).toThrowWithMessage(RangeError, "Invalid plain month day"); + }); +}); + +describe("normal behavior", () => { + test("length is 2", () => { + expect(Temporal.PlainMonthDay).toHaveLength(2); + }); + + test("basic functionality", () => { + const plainMonthDay = new Temporal.PlainMonthDay(7, 6); + expect(typeof plainMonthDay).toBe("object"); + expect(plainMonthDay).toBeInstanceOf(Temporal.PlainMonthDay); + expect(Object.getPrototypeOf(plainMonthDay)).toBe(Temporal.PlainMonthDay.prototype); + }); +});