ソースを参照

LibJS: Update Temporal's GetPossibleInstantsFor to latest spec

The most noteworthy change is that we now pass through a Time Zone
Methods Record to this function instead of a raw object.
Shannon Booth 1 年間 前
コミット
230ffc022c

+ 72 - 31
Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
+ * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -17,6 +18,7 @@
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
 #include <LibJS/Runtime/Temporal/TimeZone.h>
 #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
+#include <LibJS/Runtime/Temporal/TimeZoneMethods.h>
 #include <LibJS/Runtime/Temporal/ZonedDateTime.h>
 #include <LibTimeZone/TimeZone.h>
 
@@ -389,7 +391,8 @@ ThrowCompletionOr<NonnullGCPtr<Instant>> builtin_time_zone_get_instant_for(VM& v
     // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot.
 
     // 2. Let possibleInstants be ? GetPossibleInstantsFor(timeZone, dateTime).
-    auto possible_instants = TRY(get_possible_instants_for(vm, time_zone, date_time));
+    auto time_zone_record = TRY(create_time_zone_methods_record(vm, NonnullGCPtr<Object> { time_zone.as_object() }, { { TimeZoneMethod::GetPossibleInstantsFor } }));
+    auto possible_instants = TRY(get_possible_instants_for(vm, time_zone_record, date_time));
 
     // 3. Return ? DisambiguatePossibleInstants(possibleInstants, timeZone, dateTime, disambiguation).
     return disambiguate_possible_instants(vm, possible_instants, time_zone, date_time, disambiguation);
@@ -471,6 +474,8 @@ ThrowCompletionOr<NonnullGCPtr<Instant>> disambiguate_possible_instants(VM& vm,
     // 16. Let nanoseconds be offsetAfter - offsetBefore.
     auto nanoseconds = offset_after - offset_before;
 
+    auto time_zone_record = TRY(create_time_zone_methods_record(vm, NonnullGCPtr<Object> { time_zone.as_object() }, { { TimeZoneMethod::GetPossibleInstantsFor } }));
+
     // 17. If disambiguation is "earlier", then
     if (disambiguation == "earlier"sv) {
         // a. Let earlier be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], dateTime.[[Calendar]], 0, 0, 0, 0, 0, 0, 0, 0, 0, -nanoseconds, undefined).
@@ -480,7 +485,7 @@ ThrowCompletionOr<NonnullGCPtr<Instant>> disambiguate_possible_instants(VM& vm,
         auto* earlier_date_time = MUST(create_temporal_date_time(vm, earlier.year, earlier.month, earlier.day, earlier.hour, earlier.minute, earlier.second, earlier.millisecond, earlier.microsecond, earlier.nanosecond, date_time.calendar()));
 
         // c. Set possibleInstants to ? GetPossibleInstantsFor(timeZone, earlierDateTime).
-        auto possible_instants_ = TRY(get_possible_instants_for(vm, time_zone, *earlier_date_time));
+        auto possible_instants_ = TRY(get_possible_instants_for(vm, time_zone_record, *earlier_date_time));
 
         // d. If possibleInstants is empty, throw a RangeError exception.
         if (possible_instants_.is_empty())
@@ -500,7 +505,7 @@ ThrowCompletionOr<NonnullGCPtr<Instant>> disambiguate_possible_instants(VM& vm,
     auto* later_date_time = MUST(create_temporal_date_time(vm, later.year, later.month, later.day, later.hour, later.minute, later.second, later.millisecond, later.microsecond, later.nanosecond, date_time.calendar()));
 
     // 21. Set possibleInstants to ? GetPossibleInstantsFor(timeZone, laterDateTime).
-    auto possible_instants_ = TRY(get_possible_instants_for(vm, time_zone, *later_date_time));
+    auto possible_instants_ = TRY(get_possible_instants_for(vm, time_zone_record, *later_date_time));
 
     // 22. Set n to possibleInstants's length.
     n = possible_instants_.size();
@@ -513,46 +518,82 @@ ThrowCompletionOr<NonnullGCPtr<Instant>> disambiguate_possible_instants(VM& vm,
     return possible_instants_[n - 1];
 }
 
-// 11.6.13 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor
-ThrowCompletionOr<MarkedVector<NonnullGCPtr<Instant>>> get_possible_instants_for(VM& vm, Value time_zone, PlainDateTime& date_time)
+// 11.5.24 GetPossibleInstantsFor ( timeZoneRec, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor
+ThrowCompletionOr<MarkedVector<NonnullGCPtr<Instant>>> get_possible_instants_for(VM& vm, TimeZoneMethods const& time_zone_record, PlainDateTime const& date_time)
 {
-    // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot.
+    // 1. Let possibleInstants be ? TimeZoneMethodsRecordCall(timeZoneRec, GET-POSSIBLE-INSTANTS-FOR, « dateTime »).
+    auto possible_instants = TRY(time_zone_methods_record_call(vm, time_zone_record, TimeZoneMethod::GetPossibleInstantsFor, { { &date_time } }));
+
+    // 2. If TimeZoneMethodsRecordIsBuiltin(timeZoneRec), return ! CreateListFromArrayLike(possibleInstants, « Object »).
+    if (time_zone_methods_record_is_builtin(time_zone_record)) {
+        auto list = MarkedVector<NonnullGCPtr<Instant>> { vm.heap() };
 
-    // 2. Let possibleInstants be ? Invoke(timeZone, "getPossibleInstantsFor", « dateTime »).
-    auto possible_instants = TRY(time_zone.invoke(vm, vm.names.getPossibleInstantsFor, &date_time));
+        (void)MUST(create_list_from_array_like(vm, possible_instants, [&list](auto value) -> ThrowCompletionOr<void> {
+            list.append(verify_cast<Instant>(value.as_object()));
+            return {};
+        }));
 
-    // 3. Let iteratorRecord be ? GetIterator(possibleInstants, sync).
+        return list;
+    }
+
+    // 3. Let iteratorRecord be ? GetIterator(possibleInstants, SYNC).
     auto iterator = TRY(get_iterator(vm, possible_instants, IteratorHint::Sync));
 
     // 4. Let list be a new empty List.
     auto list = MarkedVector<NonnullGCPtr<Instant>> { vm.heap() };
 
-    // 5. Let next be true.
-    GCPtr<Object> next;
-
-    // 6. Repeat, while next is not false,
-    do {
-        // a. Set next to ? IteratorStep(iteratorRecord).
-        next = TRY(iterator_step(vm, iterator));
-
-        // b. If next is not false, then
-        if (next) {
-            // i. Let nextValue be ? IteratorValue(next).
-            auto next_value = TRY(iterator_value(vm, *next));
+    // 5. Repeat,
+    while (true) {
+        // a. Let value be ? IteratorStepValue(iteratorRecord).
+        auto value = TRY(iterator_step_value(vm, iterator));
+
+        // b. If value is DONE, then
+        if (!value.has_value()) {
+            // i. Let numResults be list's length.
+            auto num_results = list.size();
+
+            // ii. If numResults > 1, then
+            if (num_results > 1) {
+                // 1. Let epochNs be a new empty List.
+                // 2. For each value instant in list, do
+                //     a. Append instant.[[EpochNanoseconds]] to the end of the List epochNs.
+                //     FIXME: spec bug? shouldn't [[EpochNanoseconds]] just be called [[Nanoseconds]]?
+                // 3. Let min be the least element of the List epochNs.
+                // 4. Let max be the greatest element of the List epochNs.
+
+                auto const* min = &list.first()->nanoseconds().big_integer();
+                auto const* max = &list.first()->nanoseconds().big_integer();
+
+                for (auto it = list.begin() + 1; it != list.end(); ++it) {
+                    auto const& value = it->ptr()->nanoseconds().big_integer();
+
+                    if (value < *min)
+                        min = &value;
+                    else if (value > *max)
+                        max = &value;
+                }
+
+                // 5. If abs(ℝ(max - min)) > nsPerDay, throw a RangeError exception.
+                if (max->minus(*min).unsigned_value() > ns_per_day_bigint.unsigned_value())
+                    return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDuration);
+            }
 
-            // ii. If Type(nextValue) is not Object or nextValue does not have an [[InitializedTemporalInstant]] internal slot, then
-            if (!next_value.is_object() || !is<Instant>(next_value.as_object())) {
-                // 1. Let completion be ThrowCompletion(a newly created TypeError object).
-                auto completion = vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "Temporal.Instant");
+            // iii. Return list.
+            return list;
+        }
 
-                // 2. Return ? IteratorClose(iteratorRecord, completion).
-                return iterator_close(vm, iterator, move(completion));
-            }
+        // c. If value is not an Object or value does not have an [[InitializedTemporalInstant]] internal slot, then
+        if (!value->is_object() || !is<Instant>(value->as_object())) {
+            // i. Let completion be ThrowCompletion(a newly created TypeError object).
+            auto completion = vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "Temporal.Instant");
 
-            // iii. Append nextValue to the end of the List list.
-            list.append(verify_cast<Instant>(next_value.as_object()));
+            // ii. Return ? IteratorClose(iteratorRecord, completion).
+            return iterator_close(vm, iterator, move(completion));
         }
-    } while (next != nullptr);
+
+        // d. Append value to the end of the List list.
+        list.append(verify_cast<Instant>(value->as_object()));
+    }
 
     // 7. Return list.
     return { move(list) };

+ 1 - 1
Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h

@@ -51,7 +51,7 @@ ThrowCompletionOr<String> builtin_time_zone_get_offset_string_for(VM&, Value tim
 ThrowCompletionOr<PlainDateTime*> builtin_time_zone_get_plain_date_time_for(VM&, Value time_zone, Instant&, Object& calendar);
 ThrowCompletionOr<NonnullGCPtr<Instant>> builtin_time_zone_get_instant_for(VM&, Value time_zone, PlainDateTime&, StringView disambiguation);
 ThrowCompletionOr<NonnullGCPtr<Instant>> disambiguate_possible_instants(VM&, MarkedVector<NonnullGCPtr<Instant>> const& possible_instants, Value time_zone, PlainDateTime&, StringView disambiguation);
-ThrowCompletionOr<MarkedVector<NonnullGCPtr<Instant>>> get_possible_instants_for(VM&, Value time_zone, PlainDateTime&);
+ThrowCompletionOr<MarkedVector<NonnullGCPtr<Instant>>> get_possible_instants_for(VM&, TimeZoneMethods const&, PlainDateTime const&);
 ThrowCompletionOr<bool> time_zone_equals(VM&, Object& one, Object& two);
 
 }

+ 2 - 1
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp

@@ -81,7 +81,8 @@ ThrowCompletionOr<BigInt const*> interpret_iso_date_time_offset(VM& vm, i32 year
     VERIFY(offset_option.is_one_of("prefer"sv, "reject"sv));
 
     // 7. Let possibleInstants be ? GetPossibleInstantsFor(timeZone, dateTime).
-    auto possible_instants = TRY(get_possible_instants_for(vm, time_zone, *date_time));
+    auto time_zone_record = TRY(create_time_zone_methods_record(vm, NonnullGCPtr<Object> { time_zone.as_object() }, { { TimeZoneMethod::GetPossibleInstantsFor } }));
+    auto possible_instants = TRY(get_possible_instants_for(vm, time_zone_record, *date_time));
 
     // 8. For each element candidate of possibleInstants, do
     for (auto candidate : possible_instants) {