From 55c81482b02ac39e5969226b9ddfae22f606728e Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 18 Nov 2024 12:35:09 -0500 Subject: [PATCH] LibJS: Implement Temporal.Duration.prototype.with --- .../Runtime/Temporal/DurationPrototype.cpp | 77 +++++++++++++++ .../Runtime/Temporal/DurationPrototype.h | 1 + .../Duration/Duration.prototype.with.js | 94 +++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.with.js diff --git a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp index dc9e9b08b5e..e3826931521 100644 --- a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp +++ b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -34,6 +34,9 @@ void DurationPrototype::initialize(Realm& realm) define_native_accessor(realm, vm.names.sign, sign_getter, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.blank, blank_getter, {}, Attribute::Configurable); + + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(realm, vm.names.with, with, 1, attr); } // 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years @@ -85,4 +88,78 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::blank_getter) return false; } +// 7.3.15 Temporal.Duration.prototype.with ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with +JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::with) +{ + // 1. Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + auto duration = TRY(typed_this_object(vm)); + + // 3. Let temporalDurationLike be ? ToTemporalPartialDurationRecord(temporalDurationLike). + auto temporal_duration_like = TRY(to_temporal_partial_duration_record(vm, vm.argument(0))); + + // 4. If temporalDurationLike.[[Years]] is not undefined, then + // a. Let years be temporalDurationLike.[[Years]]. + // 5. Else, + // a. Let years be duration.[[Years]]. + auto years = temporal_duration_like.years.value_or(duration->years()); + + // 6. If temporalDurationLike.[[Months]] is not undefined, then + // a. Let months be temporalDurationLike.[[Months]]. + // 7. Else, + // a. Let months be duration.[[Months]]. + auto months = temporal_duration_like.months.value_or(duration->months()); + + // 8. If temporalDurationLike.[[Weeks]] is not undefined, then + // a. Let weeks be temporalDurationLike.[[Weeks]]. + // 9. Else, + // a. Let weeks be duration.[[Weeks]]. + auto weeks = temporal_duration_like.weeks.value_or(duration->weeks()); + + // 10. If temporalDurationLike.[[Days]] is not undefined, then + // a. Let days be temporalDurationLike.[[Days]]. + // 11. Else, + // a. Let days be duration.[[Days]]. + auto days = temporal_duration_like.days.value_or(duration->days()); + + // 12. If temporalDurationLike.[[Hours]] is not undefined, then + // a. Let hours be temporalDurationLike.[[Hours]]. + // 13. Else, + // a. Let hours be duration.[[Hours]]. + auto hours = temporal_duration_like.hours.value_or(duration->hours()); + + // 14. If temporalDurationLike.[[Minutes]] is not undefined, then + // a. Let minutes be temporalDurationLike.[[Minutes]]. + // 15. Else, + // a. Let minutes be duration.[[Minutes]]. + auto minutes = temporal_duration_like.minutes.value_or(duration->minutes()); + + // 16. If temporalDurationLike.[[Seconds]] is not undefined, then + // a. Let seconds be temporalDurationLike.[[Seconds]]. + // 17. Else, + // a. Let seconds be duration.[[Seconds]]. + auto seconds = temporal_duration_like.seconds.value_or(duration->seconds()); + + // 18. If temporalDurationLike.[[Milliseconds]] is not undefined, then + // a. Let milliseconds be temporalDurationLike.[[Milliseconds]]. + // 19. Else, + // a. Let milliseconds be duration.[[Milliseconds]]. + auto milliseconds = temporal_duration_like.milliseconds.value_or(duration->milliseconds()); + + // 20. If temporalDurationLike.[[Microseconds]] is not undefined, then + // a. Let microseconds be temporalDurationLike.[[Microseconds]]. + // 21. Else, + // a. Let microseconds be duration.[[Microseconds]]. + auto microseconds = temporal_duration_like.microseconds.value_or(duration->microseconds()); + + // 22. If temporalDurationLike.[[Nanoseconds]] is not undefined, then + // a. Let nanoseconds be temporalDurationLike.[[Nanoseconds]]. + // 23. Else, + // a. Let nanoseconds be duration.[[Nanoseconds]]. + auto nanoseconds = temporal_duration_like.nanoseconds.value_or(duration->nanoseconds()); + + // 24. Return ? CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + return TRY(create_temporal_duration(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); +} + } diff --git a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h index e804d3fdf22..1da86cedf24 100644 --- a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h +++ b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h @@ -30,6 +30,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(sign_getter); JS_DECLARE_NATIVE_FUNCTION(blank_getter); + JS_DECLARE_NATIVE_FUNCTION(with); }; } diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.with.js b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.with.js new file mode 100644 index 00000000000..4f4a7c28191 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.with.js @@ -0,0 +1,94 @@ +const DURATION_PROPERTIES = [ + "years", + "months", + "weeks", + "days", + "hours", + "minutes", + "seconds", + "milliseconds", + "microseconds", + "nanoseconds", +]; + +describe("correct behavior", () => { + test("length is 1", () => { + expect(Temporal.Duration.prototype.with).toHaveLength(1); + }); + + test("basic functionality", () => { + const duration = new Temporal.Duration(1, 2, 3).with({ years: 4, foo: 5, weeks: 6 }); + expect(duration.years).toBe(4); + expect(duration.months).toBe(2); + expect(duration.weeks).toBe(6); + }); + + test("each property is looked up from the object", () => { + for (const property of DURATION_PROPERTIES) { + const duration = new Temporal.Duration().with({ [property]: 1 }); + expect(duration[property]).toBe(1); + } + }); + + test("each property is coerced to number", () => { + for (const property of DURATION_PROPERTIES) { + const duration = new Temporal.Duration().with({ [property]: "1" }); + expect(duration[property]).toBe(1); + } + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.Duration object", () => { + expect(() => { + Temporal.Duration.prototype.with.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration"); + }); + + test("argument is not an object", () => { + expect(() => { + new Temporal.Duration().with("foo"); + }).toThrowWithMessage(TypeError, "foo is not an object"); + expect(() => { + new Temporal.Duration().with(42); + }).toThrowWithMessage(TypeError, "42 is not an object"); + }); + + test("argument is an invalid duration-like object", () => { + expect(() => { + new Temporal.Duration().with({}); + }).toThrowWithMessage(TypeError, "Invalid duration-like object"); + expect(() => { + new Temporal.Duration().with({ foo: 1, bar: 2 }); + }).toThrowWithMessage(TypeError, "Invalid duration-like object"); + }); + + test("error when coercing property to number", () => { + for (const property of DURATION_PROPERTIES) { + expect(() => { + new Temporal.Duration().with({ + [property]: { + valueOf() { + throw new Error(); + }, + }, + }); + }).toThrow(Error); + } + }); + + test("invalid duration value", () => { + for (const property of DURATION_PROPERTIES) { + // NOTE: NaN does *not* throw a RangeError anymore - which is questionable, IMO - as of: + // https://github.com/tc39/proposal-temporal/commit/8c854507a52efbc6e9eb2642f0f928df38e5c021 + for (const value of [1.23, Infinity]) { + expect(() => { + new Temporal.Duration().with({ [property]: value }); + }).toThrowWithMessage( + RangeError, + `Invalid value for duration property '${property}': must be an integer, got ${value}` + ); + } + } + }); +});