Bläddra i källkod

LibJS: Separate validation of roundingIncrement option

This is an editorial change in the temporal spec.

See: https://github.com/tc39/proposal-temporal/commit/712c449
forchane 1 år sedan
förälder
incheckning
d2e4da62c8

+ 58 - 31
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -274,48 +274,60 @@ ThrowCompletionOr<String> to_show_offset_option(VM& vm, Object const& normalized
 }
 
 // 13.12 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
-ThrowCompletionOr<u64> to_temporal_rounding_increment(VM& vm, Object const& normalized_options, Optional<double> dividend, bool inclusive)
+ThrowCompletionOr<double> to_temporal_rounding_increment(VM& vm, Object const& normalized_options)
 {
-    double maximum;
-    // 1. If dividend is undefined, then
-    if (!dividend.has_value()) {
-        // a. Let maximum be +∞𝔽.
-        maximum = INFINITY;
+    // 1. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", "number", undefined, 1𝔽)
+    auto increment_value = TRY(get_option(vm, normalized_options, vm.names.roundingIncrement, OptionType::Number, {}, 1.0));
+    VERIFY(increment_value.is_number());
+    auto increment = increment_value.as_double();
+
+    // 2. If increment is not finite, throw a RangeError exception
+    if (!increment_value.is_finite_number())
+        return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
+
+    // 3. If increment < 1𝔽, throw a RangeError exception.
+    if (increment < 1) {
+        return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
     }
-    // 2. Else if inclusive is true, then
-    else if (inclusive) {
+
+    // 4. Return increment
+    return increment;
+}
+
+// 13.13 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
+ThrowCompletionOr<u64> validate_temporal_rounding_increment(VM& vm, double increment, double dividend, bool inclusive)
+{
+    double maximum;
+    // 1. If inclusive is true, then
+    if (inclusive) {
         // a. Let maximum be 𝔽(dividend).
-        maximum = *dividend;
+        maximum = dividend;
     }
-    // 3. Else if dividend is more than 1, then
-    else if (*dividend > 1) {
-        // a. Let maximum be 𝔽(dividend - 1).
-        maximum = *dividend - 1;
+    // 2. Else if dividend is more than 1, then
+    else if (dividend > 1) {
+        // a. Let maximum be 𝔽(dividend - 1)
+        maximum = dividend - 1;
     }
-    // 4. Else,
+    // 3. Else
     else {
         // a. Let maximum be 1𝔽.
         maximum = 1;
     }
-
-    // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", "number", undefined, 1𝔽).
-    auto increment_value = TRY(get_option(vm, normalized_options, vm.names.roundingIncrement, OptionType::Number, {}, 1.0));
-    VERIFY(increment_value.is_number());
-    auto increment = increment_value.as_double();
-
-    // 6. If increment < 1𝔽 or increment > maximum, throw a RangeError exception.
-    if (increment < 1 || increment > maximum)
+    // 4. If increment > maximum, throw a RangeError exception.
+    if (increment > maximum) {
         return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
+    }
 
-    // 7. Set increment to floor(ℝ(increment)).
+    // 5. Set increment to floor(ℝ(increment)).
     auto floored_increment = static_cast<u64>(increment);
 
-    // 8. If dividend is not undefined and dividend modulo increment ≠ 0, then
-    if (dividend.has_value() && static_cast<u64>(*dividend) % floored_increment != 0)
+    // 6. If dividend modulo increment is not zero, then
+    if (modulo(floor(dividend), floored_increment) != 0) {
         // a. Throw a RangeError exception.
         return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
+    }
 
-    // 9. Return increment.
+    // 7. Return increment.
     return floored_increment;
 }
 
@@ -336,8 +348,11 @@ ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(VM& vm, Object c
         maximum = *maximum_temporal_duration_rounding_increment(smallest_unit);
     }
 
-    // 3. Return ? ToTemporalRoundingIncrement(normalizedOptions, maximum, false).
-    return to_temporal_rounding_increment(vm, normalized_options, maximum, false);
+    // 3. Let increment be ? ToTemporalRoundingIncrement(normalizedOptions).
+    auto increment = TRY(to_temporal_rounding_increment(vm, normalized_options));
+
+    // 4. Return ? ValidateTemporalRoundingIncrement(increment, maximum, false).
+    return validate_temporal_rounding_increment(vm, increment, maximum, false);
 }
 
 // 13.14 ToSecondsStringPrecision ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecision
@@ -1946,15 +1961,27 @@ ThrowCompletionOr<DifferenceSettings> get_difference_settings(VM& vm, Difference
     // 11. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit).
     auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit);
 
-    // 12. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, false).
-    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *options, Optional<double> { maximum }, false));
+    // 12. Let roundingIncrement be ? ToTemporalRoundingIncrement(options).
+    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *options));
+
+    u64 floored_rounding_increment;
+    // 13. If maximum is undefined, then
+    if (!maximum.has_value()) {
+        // a. Set roundingIncrement to floor(ℝ(roundingIncrement)).
+        floored_rounding_increment = static_cast<u64>(rounding_increment);
+    }
+    // 14. Else
+    else {
+        // a. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
+        floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false));
+    }
 
     // 13. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, [[Options]]: options }.
     return DifferenceSettings {
         .smallest_unit = smallest_unit.release_value(),
         .largest_unit = largest_unit.release_value(),
         .rounding_mode = move(rounding_mode),
-        .rounding_increment = rounding_increment,
+        .rounding_increment = floored_rounding_increment,
         .options = *options,
     };
 }

+ 2 - 1
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

@@ -145,7 +145,8 @@ ThrowCompletionOr<String> to_temporal_offset(VM&, Object const* options, StringV
 ThrowCompletionOr<String> to_calendar_name_option(VM&, Object const& normalized_options);
 ThrowCompletionOr<String> to_time_zone_name_option(VM&, Object const& normalized_options);
 ThrowCompletionOr<String> to_show_offset_option(VM&, Object const& normalized_options);
-ThrowCompletionOr<u64> to_temporal_rounding_increment(VM&, Object const& normalized_options, Optional<double> dividend, bool inclusive);
+ThrowCompletionOr<double> to_temporal_rounding_increment(VM& vm, Object const& normalized_options);
+ThrowCompletionOr<u64> validate_temporal_rounding_increment(VM& vm, double increment, double dividend, bool inclusive);
 ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(VM&, Object const& normalized_options, StringView smallest_unit);
 ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(VM&, Object const& normalized_options);
 ThrowCompletionOr<Optional<String>> get_temporal_unit(VM&, Object const& normalized_options, PropertyKey const&, UnitGroup, TemporalUnitDefault const& default_, Vector<StringView> const& extra_values = {});

+ 24 - 12
Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp

@@ -413,36 +413,48 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
     // 18. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit).
     auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit);
 
-    // 19. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, false).
-    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to, Optional<double>(maximum), false));
+    // 19. Let roundingIncrement be ? ToTemporalRoundingIncrement(options).
+    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to));
+
+    u64 floored_rounding_increment;
+    // 20. If maximum is undefined, then
+    if (!maximum.has_value()) {
+        // a. Set roundingIncrement to floor(ℝ(roundingIncrement)).
+        floored_rounding_increment = static_cast<u64>(rounding_increment);
+    }
+    // 21. Else
+    else {
+        // a. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
+        floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false));
+    }
 
-    // 20. Let relativeTo be ? ToRelativeTemporalObject(roundTo).
+    // 22. Let relativeTo be ? ToRelativeTemporalObject(roundTo).
     auto relative_to = TRY(to_relative_temporal_object(vm, *round_to));
     auto relative_to_value = relative_to_converted_to_value(relative_to);
 
-    // 21. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo).
+    // 23. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo).
     auto unbalance_result = TRY(unbalance_duration_relative(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), *largest_unit, relative_to_value));
 
     auto calendar_record = TRY(create_calendar_methods_record_from_relative_to(vm, relative_to.plain_relative_to, relative_to.zoned_relative_to, { { CalendarMethod::DateAdd, CalendarMethod::DateUntil } }));
-    // 22. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]].
-    auto round_result = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr, calendar_record)).duration_record;
+    // 24. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]].
+    auto round_result = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), floored_rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr, calendar_record)).duration_record;
 
-    // 23. Let adjustResult be ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo).
-    auto adjust_result = TRY(adjust_rounded_duration_days(vm, round_result.years, round_result.months, round_result.weeks, round_result.days, round_result.hours, round_result.minutes, round_result.seconds, round_result.milliseconds, round_result.microseconds, round_result.nanoseconds, rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr));
+    // 25. Let adjustResult be ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo).
+    auto adjust_result = TRY(adjust_rounded_duration_days(vm, round_result.years, round_result.months, round_result.weeks, round_result.days, round_result.hours, round_result.minutes, round_result.seconds, round_result.milliseconds, round_result.microseconds, round_result.nanoseconds, floored_rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr));
 
-    // 24. Let balanceResult be ? BalanceDurationRelative(adjustResult.[[Years]], adjustResult.[[Months]], adjustResult.[[Weeks]], adjustResult.[[Days]], largestUnit, relativeTo).
+    // 26. Let balanceResult be ? BalanceDurationRelative(adjustResult.[[Years]], adjustResult.[[Months]], adjustResult.[[Weeks]], adjustResult.[[Days]], largestUnit, relativeTo).
     auto balance_result = TRY(balance_duration_relative(vm, adjust_result.years, adjust_result.months, adjust_result.weeks, adjust_result.days, *largest_unit, relative_to_value));
 
-    // 25. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
+    // 27. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
     if (relative_to.zoned_relative_to) {
         // a. Set relativeTo to ? MoveRelativeZonedDateTime(relativeTo, balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], 0).
         relative_to_value = TRY(move_relative_zoned_date_time(vm, *relative_to.zoned_relative_to, balance_result.years, balance_result.months, balance_result.weeks, 0));
     }
 
-    // 26. Let result be ? BalanceDuration(balanceResult.[[Days]], adjustResult.[[Hours]], adjustResult.[[Minutes]], adjustResult.[[Seconds]], adjustResult.[[Milliseconds]], adjustResult.[[Microseconds]], adjustResult.[[Nanoseconds]], largestUnit, relativeTo).
+    // 28. Let result be ? BalanceDuration(balanceResult.[[Days]], adjustResult.[[Hours]], adjustResult.[[Minutes]], adjustResult.[[Seconds]], adjustResult.[[Milliseconds]], adjustResult.[[Microseconds]], adjustResult.[[Nanoseconds]], largestUnit, relativeTo).
     auto result = TRY(balance_duration(vm, balance_result.days, adjust_result.hours, adjust_result.minutes, adjust_result.seconds, adjust_result.milliseconds, adjust_result.microseconds, Crypto::SignedBigInteger { adjust_result.nanoseconds }, *largest_unit, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr));
 
-    // 27. Return ! CreateTemporalDuration(balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
+    // 29. Return ! CreateTemporalDuration(balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
     return MUST(create_temporal_duration(vm, balance_result.years, balance_result.months, balance_result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds));
 }
 

+ 8 - 5
Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp

@@ -254,13 +254,16 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round)
         maximum = ns_per_day;
     }
 
-    // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, true).
-    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to, maximum, true));
+    // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(options).
+    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to));
 
-    // 15. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode).
-    auto* rounded_ns = round_temporal_instant(vm, instant->nanoseconds(), rounding_increment, smallest_unit, rounding_mode);
+    // 15. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true).
+    auto floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, maximum, true));
 
-    // 16. Return ! CreateTemporalInstant(roundedNs).
+    // 16. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode).
+    auto* rounded_ns = round_temporal_instant(vm, instant->nanoseconds(), floored_rounding_increment, smallest_unit, rounding_mode);
+
+    // 17. Return ! CreateTemporalInstant(roundedNs).
     return MUST(create_temporal_instant(vm, *rounded_ns));
 }
 

+ 11 - 5
Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp

@@ -306,13 +306,19 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::round)
     // 8. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit).
     auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit);
 
-    // 9. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, false).
-    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to, *maximum, false));
+    // 9. Assert maximum is not undefined.
+    VERIFY(maximum.has_value());
 
-    // 10. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode).
-    auto result = round_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), rounding_increment, *smallest_unit, rounding_mode);
+    // 10. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo).
+    auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to));
 
-    // 11. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
+    // 11. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
+    auto floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false));
+
+    // 12. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode).
+    auto result = round_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), floored_rounding_increment, *smallest_unit, rounding_mode);
+
+    // 13. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
     return MUST(create_temporal_time(vm, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond));
 }
 

+ 18 - 0
Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.prototype.round.js

@@ -122,5 +122,23 @@ describe("errors", () => {
         expect(() => {
             plainTime.round({ smallestUnit: "second", roundingIncrement: Infinity });
         }).toThrowWithMessage(RangeError, "inf is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "hours", roundingIncrement: 24 });
+        }).toThrowWithMessage(RangeError, "24 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "minutes", roundingIncrement: 60 });
+        }).toThrowWithMessage(RangeError, "60 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "seconds", roundingIncrement: 60 });
+        }).toThrowWithMessage(RangeError, "60 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "milliseconds", roundingIncrement: 1000 });
+        }).toThrowWithMessage(RangeError, "1000 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "microseconds", roundingIncrement: 1000 });
+        }).toThrowWithMessage(RangeError, "1000 is not a valid value for option roundingIncrement");
+        expect(() => {
+            plainTime.round({ smallestUnit: "nanoseconds", roundingIncrement: 1000 });
+        }).toThrowWithMessage(RangeError, "1000 is not a valid value for option roundingIncrement");
     });
 });