From 5477540992f97b2ce990625da387a568ed4603e1 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 30 Sep 2025 16:13:51 +0200 Subject: [PATCH 01/42] added temporal intrinsic --- Cargo.toml | 1 + nova_vm/Cargo.toml | 6 + nova_vm/src/builtin_strings | 1 + nova_vm/src/ecmascript/builtins.rs | 2 + nova_vm/src/ecmascript/builtins/temporal.rs | 105 ++++++++++++++++++ .../ecmascript/execution/realm/intrinsics.rs | 12 ++ nova_vm/src/heap/heap_constants.rs | 3 +- 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal.rs diff --git a/Cargo.toml b/Cargo.toml index 99cd30009..1882df8be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ sonic-rs = "0.3.17" unicode-normalization = "0.1.24" usdt = { git = "https://github.com/aapoalas/usdt.git", branch = "nova-aarch64-branch" } wtf8 = "0.1" +temporal_rs = "0.0.16" [workspace.metadata.dylint] libraries = [{ path = "nova_lint" }] diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index b35c18c95..8b1c89b30 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -39,6 +39,7 @@ sonic-rs = { workspace = true, optional = true } unicode-normalization = { workspace = true } usdt = { workspace = true } wtf8 = { workspace = true } +temporal_rs = { workspace = true, optional = true } [features] default = [ @@ -52,6 +53,7 @@ default = [ "regexp", "set", "annex-b", + "temporal", ] array-buffer = ["ecmascript_atomics"] atomics = ["array-buffer", "shared-array-buffer", "ecmascript_atomics", "ecmascript_futex"] @@ -63,6 +65,7 @@ shared-array-buffer = ["array-buffer", "ecmascript_atomics"] weak-refs = [] set = [] typescript = [] +temporal = ["temporal_rs"] # Enables features defined by [Annex B](https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers) annex-b = ["annex-b-string", "annex-b-global", "annex-b-date", "annex-b-regexp"] @@ -84,6 +87,7 @@ proposals = [ "proposal-math-clamp", "proposal-is-error", "proposal-atomics-microwait", + "proposal-temporal", ] # Enables the [Float16Array proposal](https://tc39.es/proposal-float16array/) proposal-float16array = ["array-buffer"] @@ -95,6 +99,8 @@ proposal-math-clamp = ["math"] proposal-is-error = [] # Enables the [Atomics.pause proposal](https://tc39.es/proposal-atomics-microwait/) proposal-atomics-microwait = ["atomics"] +# Enable the [Temporal proposal](https://tc39.es/proposal-temporal/) +proposal-temporal = ["temporal"] [build-dependencies] small_string = { path = "../small_string", version = "0.2.0" } diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index ae221de3d..1253d0d58 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -426,6 +426,7 @@ Symbol.toPrimitive Symbol.toStringTag Symbol.unscopables SyntaxError +#[cfg(feature = "temporal")]Temporal #[cfg(feature = "math")]tan #[cfg(feature = "math")]tanh #[cfg(feature = "regexp")]test diff --git a/nova_vm/src/ecmascript/builtins.rs b/nova_vm/src/ecmascript/builtins.rs index 402495bc3..91bb8cd9e 100644 --- a/nova_vm/src/ecmascript/builtins.rs +++ b/nova_vm/src/ecmascript/builtins.rs @@ -66,6 +66,8 @@ pub(crate) mod set; #[cfg(feature = "shared-array-buffer")] pub(crate) mod shared_array_buffer; pub(crate) mod structured_data; +#[cfg(feature = "temporal")] +pub mod temporal; pub(crate) mod text_processing; #[cfg(feature = "array-buffer")] pub(crate) mod typed_array; diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs new file mode 100644 index 000000000..d1655d19b --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -0,0 +1,105 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + + +use temporal_rs::{Instant, Duration, /* etc */}; + +use core::f64::consts; +use std::thread::Builder; + +use crate::{ + ecmascript::{ + abstract_operations::type_conversion::{to_big_int, to_number, to_number_primitive, to_uint32}, + builders::{self, ordinary_object_builder::OrdinaryObjectBuilder}, + builtins::{ArgumentsList, Behaviour, Builtin}, + execution::{agent, Agent, JsResult, Realm}, + types::{IntoValue, Number, Primitive, String, Value, BUILTIN_STRING_MEMORY}, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, + }, + heap::WellKnownSymbolIndexes, +}; + + +pub(crate) struct TemporalObject; + + +impl TemporalObject { + pub fn create_intrinsic( + agent: &mut Agent, + realm: Realm<'static>, + gc: NoGcScope, + ) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let object_prototype = intrinsics.object_prototype(); + let this = intrinsics.temporal(); + + let builders = OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(1) + .with_prototype(object_prototype) + .with_property(|builder| { + builder + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal.into()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) + .build(); + } +} + + + +/* +struct TemporalPlainDateTime; +impl Builtin for TemporalPlainDateTime { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainDateTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainDateTime); +} + +struct TemporalPlainDate; +impl Builtin for TemporalPlainDate { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainDate; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainDate); +} + +struct TemporalPlainTime; +impl Builtin for TemporalPlainTime { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainTime); +} + +struct TemporalPlainYearMonth; +impl Builtin for TemporalPlainYearMonth { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainYearMonth; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainYearMonth); +} + +struct TemporalPlainMonthDay; +impl Builtin for TemporalPlainMonthDay { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainMonthDay; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainMonthDay); +} + +struct TemporalDuration; +impl Builtin for TemporalDuration { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.Duration; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::Duration); +} + +struct TemporalZonedDateTime; +impl Builtin for TemporalZonedDateTime { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.ZonedDateTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::ZonedDateTime); +}*/ \ No newline at end of file diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index bee33fd46..649219913 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -39,6 +39,8 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ shared_array_buffer_constructor::SharedArrayBufferConstructor, shared_array_buffer_prototype::SharedArrayBufferPrototype, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::TemporalObject; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ regexp_constructor::RegExpConstructor, regexp_prototype::RegExpPrototype, @@ -307,6 +309,11 @@ impl Intrinsics { BigIntConstructor::create_intrinsic(agent, realm); #[cfg(feature = "math")] MathObject::create_intrinsic(agent, realm, gc); + + #[cfg(feature = "temporal")] + TemporalObject::create_intrinsic(agent, realm, gc); + + #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); #[cfg(feature = "date")] @@ -1011,6 +1018,11 @@ impl Intrinsics { IntrinsicObjectIndexes::MathObject.get_backing_object(self.object_index_base) } + /// %Temporal% + pub(crate) const fn temporal(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) + } + /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { IntrinsicPrimitiveObjectIndexes::NumberPrototype diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index 0afac0dfa..c76823a05 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -34,7 +34,8 @@ pub(crate) enum IntrinsicObjectIndexes { MathObject, #[cfg(feature = "date")] DatePrototype, - + #[cfg(feature = "temporal")] + TemporalObject, // Text processing #[cfg(feature = "regexp")] RegExpPrototype, From a9a074e5041ed91a4ee81d4f0e119c4521fa7684 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Wed, 8 Oct 2025 17:12:27 +0200 Subject: [PATCH 02/42] Added Placeholders for InstantConstructor%Temporal.Instant% and InstantPrototype%Temporal.Instant.Prototype% as well as heap data and handles --- nova_vm/src/builtin_strings | 9 ++ nova_vm/src/ecmascript/builtins/temporal.rs | 63 +------- .../ecmascript/builtins/temporal/instant.rs | 143 ++++++++++++++++++ .../ecmascript/execution/realm/intrinsics.rs | 22 ++- nova_vm/src/heap/heap_constants.rs | 6 + 5 files changed, 181 insertions(+), 62 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/instant.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 1253d0d58..60e5e61d4 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -250,6 +250,7 @@ isSealed #[cfg(feature = "array-buffer")]isView isWellFormed #[cfg(feature = "annex-b-string")]italics +#[cfg(feature = "temporal")]Instant Iterator iterator join @@ -321,6 +322,14 @@ prototype Proxy push race +#[cfg(feature = "temporal")]PlainDateTime +#[cfg(feature = "temporal")]PlainDate +#[cfg(feature = "temporal")]PlainTime +#[cfg(feature = "temporal")]PlainYearMonth +#[cfg(feature = "temporal")]PlainMonthDay +#[cfg(feature = "temporal")]Duration +#[cfg(feature = "temporal")]ZonedDateTime +#[cfg(feature = "temporal")]Now #[cfg(feature = "math")]random RangeError raw diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index d1655d19b..03d2b9aa4 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -2,11 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. - -use temporal_rs::{Instant, Duration, /* etc */}; - -use core::f64::consts; -use std::thread::Builder; +pub mod instant; use crate::{ ecmascript::{ @@ -38,7 +34,7 @@ impl TemporalObject { let this = intrinsics.temporal(); let builders = OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(1) + .with_property_capacity(2) .with_prototype(object_prototype) .with_property(|builder| { builder @@ -48,58 +44,7 @@ impl TemporalObject { .with_configurable(true) .build() }) + .with_builtin_function_property::() .build(); } -} - - - -/* -struct TemporalPlainDateTime; -impl Builtin for TemporalPlainDateTime { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainDateTime; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainDateTime); -} - -struct TemporalPlainDate; -impl Builtin for TemporalPlainDate { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainDate; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainDate); -} - -struct TemporalPlainTime; -impl Builtin for TemporalPlainTime { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainTime; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainTime); -} - -struct TemporalPlainYearMonth; -impl Builtin for TemporalPlainYearMonth { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainYearMonth; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainYearMonth); -} - -struct TemporalPlainMonthDay; -impl Builtin for TemporalPlainMonthDay { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainMonthDay; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainMonthDay); -} - -struct TemporalDuration; -impl Builtin for TemporalDuration { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.Duration; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::Duration); -} - -struct TemporalZonedDateTime; -impl Builtin for TemporalZonedDateTime { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.ZonedDateTime; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::ZonedDateTime); -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs new file mode 100644 index 000000000..d729b446f --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -0,0 +1,143 @@ +use crate::{ + ecmascript::{ + builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, + builtins::{ + ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor + }, + execution::{agent::{Agent}, JsResult, Realm}, + types::{ + InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY + }, + }, + engine::context::{bindable_handle, GcScope, NoGcScope}, + heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, IntrinsicConstructorIndexes, WorkQueues}, +}; +/// Constructor function object for %Temporal.Instant%. +pub(crate) struct InstantConstructor; +impl Builtin for InstantConstructor { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Constructor(InstantConstructor::construct); +} +impl BuiltinIntrinsicConstructor for InstantConstructor { + const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; +} + +impl InstantConstructor { + fn construct<'gc>(agent: &mut Agent, this_value: Value, args: ArgumentsList, new_target: Option, gc: GcScope<'gc, '_>) -> JsResult<'gc, Value<'gc>> { + todo!(); + } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let instant_prototype = intrinsics.temporal_instant_prototype(); + + BuiltinFunctionBuilder::new_intrinsic_constructor::(agent, realm) + .with_property_capacity(1) + .with_prototype_property(instant_prototype.into_object()) + .build(); + + } +} +/// %Temporal.Instant.Prototype% +pub(crate) struct InstantPrototype; + +impl InstantPrototype { + pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let this = intrinsics.temporal_instant_prototype(); + let object_prototype = intrinsics.object_prototype(); + let instant_constructor = intrinsics.temporal_instant(); + + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(1) // TODO add correct property capacity + .with_prototype(object_prototype) + .with_constructor_property(instant_constructor) + // TODO add all prototype methods + .build(); + } +} +/// HEAP DATA +#[derive(Debug, Clone, Copy)] +pub(crate) struct InstantValue(/*TODO:BigInt*/); + +impl InstantValue { + // TODO +} +#[derive(Debug, Clone, Copy)] +pub struct InstantHeapData<'a> { + pub(crate) object_index: Option>, + pub(crate) date: InstantValue, +} + +impl InstantHeapData<'_> { + // TODO +} + +bindable_handle!(InstantHeapData); + +impl HeapMarkAndSweep for InstantHeapData<'static> { + fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { + todo!() + } + fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { + todo!() + } +} + +// HANDLES +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); +impl Instant<'_> { + //TODO + pub(crate) const fn _def() -> Self { + todo!() + } +} +bindable_handle!(Instant); + +impl<'a> From> for Value<'a> { + fn from(v: Instant<'a>) -> Self { todo!() } +} +impl<'a> From> for Object<'a> { + fn from(v: Instant<'a>) -> Self { todo!() } +} +impl<'a> TryFrom> for Instant<'a> { + type Error = (); + fn try_from(v: Value<'a>) -> Result { + todo!() + } +} +impl<'a> TryFrom> for Instant<'a> { + type Error = (); + fn try_from(o: Object<'a>) -> Result { + todo!() + } +} + +// TODO impl trait bounds properly +impl<'a> InternalSlots<'a> for Instant<'a> { + // TODO: Add TemporalInstant to ProtoIntrinsics + //const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; + fn get_backing_object(self, agent: &Agent) -> Option> { + todo!() + } + fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { + todo!() + } + +} + +impl HeapMarkAndSweep for Instant<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + todo!() + } + fn sweep_values(&mut self, compactions: &CompactionLists) { + todo!() + } +} + +impl<'a> CreateHeapData, Instant<'a>> for Heap { + fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { + todo!() + } +} \ No newline at end of file diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 649219913..eadc372db 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -40,7 +40,9 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ shared_array_buffer_prototype::SharedArrayBufferPrototype, }; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::TemporalObject; +use crate::ecmascript::builtins::temporal::{ + TemporalObject, instant::InstantConstructor, instant::InstantPrototype, +}; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ regexp_constructor::RegExpConstructor, regexp_prototype::RegExpPrototype, @@ -311,8 +313,12 @@ impl Intrinsics { MathObject::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] - TemporalObject::create_intrinsic(agent, realm, gc); - + { + TemporalObject::create_intrinsic(agent, realm, gc); + // Instant + InstantConstructor::create_intrinsic(agent, realm, gc); + InstantPrototype::create_intrinsic(agent, realm, gc); + } #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); @@ -1023,6 +1029,16 @@ impl Intrinsics { IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) } + /// %Temporal.Instant% + pub(crate) const fn temporal_instant(&self) -> BuiltinFunction<'static> { + IntrinsicConstructorIndexes::TemporalInstant + .get_builtin_function(self.builtin_function_index_base) + } + /// %Temporal.Instant.Prototype% + pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) + } + /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { IntrinsicPrimitiveObjectIndexes::NumberPrototype diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index c76823a05..7a44b1afb 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -36,6 +36,10 @@ pub(crate) enum IntrinsicObjectIndexes { DatePrototype, #[cfg(feature = "temporal")] TemporalObject, + #[cfg(feature = "temporal")] + TemporalInstant, + #[cfg(feature = "temporal")] + TemporalInstantPrototype, // Text processing #[cfg(feature = "regexp")] RegExpPrototype, @@ -172,6 +176,8 @@ pub(crate) enum IntrinsicConstructorIndexes { BigInt, #[cfg(feature = "date")] Date, + #[cfg(feature = "temporal")] + TemporalInstant, // Text processing String, From 79059598a09740d7865fdb17511b9722e8260046 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 16 Oct 2025 16:43:26 +0200 Subject: [PATCH 03/42] added engine and heap backing to %temporal.instant% --- Cargo.toml | 2 +- .../ecmascript/builtins/temporal/instant.rs | 75 +++++++++++++++---- nova_vm/src/ecmascript/execution/weak_key.rs | 14 ++++ .../src/ecmascript/types/language/object.rs | 9 +++ .../src/ecmascript/types/language/value.rs | 13 ++++ nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 10 +++ nova_vm/src/heap.rs | 6 ++ nova_vm/src/heap/heap_bits.rs | 13 ++++ nova_vm/src/heap/heap_gc.rs | 28 +++++++ 10 files changed, 157 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1882df8be..ef00e8745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ sonic-rs = "0.3.17" unicode-normalization = "0.1.24" usdt = { git = "https://github.com/aapoalas/usdt.git", branch = "nova-aarch64-branch" } wtf8 = "0.1" -temporal_rs = "0.0.16" +temporal_rs = "0.1.0" [workspace.metadata.dylint] libraries = [{ path = "nova_lint" }] diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index d729b446f..0419de163 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -1,16 +1,18 @@ +use core::ops::{Index, IndexMut}; + use crate::{ ecmascript::{ builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, builtins::{ ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor }, - execution::{agent::{Agent}, JsResult, Realm}, + execution::{agent::Agent, JsResult, Realm}, types::{ - InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY + InternalMethods, InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY }, }, - engine::context::{bindable_handle, GcScope, NoGcScope}, - heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, IntrinsicConstructorIndexes, WorkQueues}, + engine::{context::{bindable_handle, GcScope, NoGcScope}, rootable::{HeapRootRef, Rootable}}, + heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues}, }; /// Constructor function object for %Temporal.Instant%. pub(crate) struct InstantConstructor; @@ -56,7 +58,7 @@ impl InstantPrototype { .build(); } } -/// HEAP DATA +/// HEAP DATA -- Move to internal instant/data.rs #[derive(Debug, Clone, Copy)] pub(crate) struct InstantValue(/*TODO:BigInt*/); @@ -84,33 +86,48 @@ impl HeapMarkAndSweep for InstantHeapData<'static> { } } -// HANDLES +// HANDLES -- Keep public facing within instant.rs #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl Instant<'_> { //TODO pub(crate) const fn _def() -> Self { - todo!() + Instant(BaseIndex::from_u32_index(0)) + } + + pub(crate) const fn get_index(self) -> usize { + self.0.into_index() } } bindable_handle!(Instant); impl<'a> From> for Value<'a> { - fn from(v: Instant<'a>) -> Self { todo!() } + fn from(value: Instant<'a>) -> Self { + Value::Instant(value) // todo: add to value.rs + } } impl<'a> From> for Object<'a> { - fn from(v: Instant<'a>) -> Self { todo!() } + fn from(value: Instant<'a>) -> Self { + Object::Instant(value) // todo: add to object.rs + } } impl<'a> TryFrom> for Instant<'a> { type Error = (); - fn try_from(v: Value<'a>) -> Result { - todo!() + + fn try_from(value: Value<'a>) -> Result { + match value { + Value::Instant(idx) => Ok(idx), // todo: add to value.rs + _ => Err(()), + } } } impl<'a> TryFrom> for Instant<'a> { type Error = (); - fn try_from(o: Object<'a>) -> Result { - todo!() + fn try_from(object: Object<'a>) -> Result { + match object { + Object::Instant(idx) => Ok(idx), // todo: add to object.rs + _ => Err(()), + } } } @@ -127,6 +144,8 @@ impl<'a> InternalSlots<'a> for Instant<'a> { } +impl<'a> InternalMethods<'a> for Instant<'a> {} + impl HeapMarkAndSweep for Instant<'static> { fn mark_values(&self, queues: &mut WorkQueues) { todo!() @@ -136,8 +155,36 @@ impl HeapMarkAndSweep for Instant<'static> { } } +impl HeapSweepWeakReference for Instant<'static> { + fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { + compactions.dates.shift_weak_index(self.0).map(Self) + } +} + impl<'a> CreateHeapData, Instant<'a>> for Heap { fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { todo!() } -} \ No newline at end of file +} + +/* todo - impl keep public facing in temporal/instant.rs +impl Rootable for Instant<'_> { + type RootRepr = HeapRootRef; + + fn to_root_repr(value: Self) -> Result { + todo!() + } + + fn from_root_repr(value: &Self::RootRepr) -> Result { + todo!() + } + + fn from_heap_ref(heap_ref: crate::engine::rootable::HeapRootRef) -> Self::RootRepr { + todo!() + } + + fn from_heap_data(heap_data: crate::engine::rootable::HeapRootData) -> Option { + todo!() + } +} +*/ \ No newline at end of file diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index 051ab68ef..98df938f2 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -141,6 +141,8 @@ pub(crate) enum WeakKey<'a> { Array(Array<'a>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, + #[cfg(feature = "temporal")] + Instant(Instant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -254,6 +256,8 @@ impl<'a> From> for Value<'a> { WeakKey::Array(d) => Self::Array(d), #[cfg(feature = "date")] WeakKey::Date(d) => Self::Date(d), + #[cfg(feature = "temporal")] + WeakKey::Instant(d) => Self::Instant(d), WeakKey::Error(d) => Self::Error(d), WeakKey::FinalizationRegistry(d) => Self::FinalizationRegistry(d), WeakKey::Map(d) => Self::Map(d), @@ -361,6 +365,8 @@ impl<'a> From> for WeakKey<'a> { Object::Array(d) => Self::Array(d), #[cfg(feature = "date")] Object::Date(d) => Self::Date(d), + #[cfg(feature = "temporal")] + Object::Instant(d) => Self::Instant(d), Object::Error(d) => Self::Error(d), Object::FinalizationRegistry(d) => Self::FinalizationRegistry(d), Object::Map(d) => Self::Map(d), @@ -472,6 +478,8 @@ impl<'a> TryFrom> for Object<'a> { WeakKey::Array(d) => Ok(Self::Array(d)), #[cfg(feature = "date")] WeakKey::Date(d) => Ok(Self::Date(d)), + #[cfg(feature = "temporal")] + WeakKey::Instant(d) => Ok(Self::Instant(d)), WeakKey::Error(d) => Ok(Self::Error(d)), WeakKey::FinalizationRegistry(d) => Ok(Self::FinalizationRegistry(d)), WeakKey::Map(d) => Ok(Self::Map(d)), @@ -614,6 +622,8 @@ impl HeapMarkAndSweep for WeakKey<'static> { Self::Array(d) => d.mark_values(queues), #[cfg(feature = "date")] Self::Date(d) => d.mark_values(queues), + #[cfg(feature = "temporal")] + Self::Instant(d) => d.mark_values(queues), Self::Error(d) => d.mark_values(queues), Self::FinalizationRegistry(d) => d.mark_values(queues), Self::Map(d) => d.mark_values(queues), @@ -719,6 +729,8 @@ impl HeapMarkAndSweep for WeakKey<'static> { Self::Array(d) => d.sweep_values(compactions), #[cfg(feature = "date")] Self::Date(d) => d.sweep_values(compactions), + #[cfg(feature = "temporal")] + Self::Instant(d) => d.sweep_values(compactions), Self::Error(d) => d.sweep_values(compactions), Self::FinalizationRegistry(d) => d.sweep_values(compactions), Self::Map(d) => d.sweep_values(compactions), @@ -840,6 +852,8 @@ impl HeapSweepWeakReference for WeakKey<'static> { Self::Array(data) => data.sweep_weak_reference(compactions).map(Self::Array), #[cfg(feature = "date")] Self::Date(data) => data.sweep_weak_reference(compactions).map(Self::Date), + #[cfg(feature = "temporal")] + Self::Instant(data) => data.sweep_weak_reference(compactions).map(Self::Instant), Self::Error(data) => data.sweep_weak_reference(compactions).map(Self::Error), Self::FinalizationRegistry(data) => data .sweep_weak_reference(compactions) diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 002444d9b..efd1078eb 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -179,6 +179,7 @@ pub enum Object<'a> { Array(Array<'a>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, + Instant(Instant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -777,6 +778,8 @@ impl<'a> From> for Value<'a> { Object::Array(data) => Self::Array(data), #[cfg(feature = "date")] Object::Date(data) => Self::Date(data), + #[cfg(feature = "temporal")] + Object::Instant(data) => Value::Instant(data), Object::Error(data) => Self::Error(data), Object::FinalizationRegistry(data) => Self::FinalizationRegistry(data), Object::Map(data) => Self::Map(data), @@ -885,6 +888,8 @@ impl<'a> TryFrom> for Object<'a> { Value::Array(x) => Ok(Self::from(x)), #[cfg(feature = "date")] Value::Date(x) => Ok(Self::Date(x)), + #[cfg(feature = "temporal")] + Value::Instant(x) => Ok(Self::Instant(x)), Value::Error(x) => Ok(Self::from(x)), Value::BoundFunction(x) => Ok(Self::from(x)), Value::BuiltinFunction(x) => Ok(Self::from(x)), @@ -1001,6 +1006,8 @@ macro_rules! object_delegate { Self::Array(data) => data.$method($($arg),+), #[cfg(feature = "date")] Self::Date(data) => data.$method($($arg),+), + #[cfg(feature = "temporal")] + Object::Instant(data) => data.$method($($arg),+), Self::Error(data) => data.$method($($arg),+), Self::BoundFunction(data) => data.$method($($arg),+), Self::BuiltinFunction(data) => data.$method($($arg),+), @@ -1667,6 +1674,8 @@ impl TryFrom for Object<'_> { HeapRootData::Array(array) => Ok(Self::Array(array)), #[cfg(feature = "date")] HeapRootData::Date(date) => Ok(Self::Date(date)), + #[cfg(feature = "temporal")] + HeapRootData::Instant(instant) => Ok(Self::Instant(instant)), HeapRootData::Error(error) => Ok(Self::Error(error)), HeapRootData::FinalizationRegistry(finalization_registry) => { Ok(Self::FinalizationRegistry(finalization_registry)) diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 9e7480c1b..09193cd92 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -2,6 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//#[cfg(feature = "temporal")] +//use temporal_rs::Instant as Instant; // shadow regular instant, should probably change name to TemporalInstant + use super::{ BigInt, BigIntHeapData, IntoValue, Number, Numeric, OrdinaryObject, Primitive, String, StringRecord, Symbol, bigint::HeapBigInt, number::HeapNumber, string::HeapString, @@ -178,6 +181,8 @@ pub enum Value<'a> { Array(Array<'a>), #[cfg(feature = "date")] Date(Date<'a>), + #[cfg(feature = "temporal")] + Instant(Instant<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), Map(Map<'a>), @@ -314,6 +319,8 @@ pub(crate) const OBJECT_DISCRIMINANT: u8 = pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array::_def())); #[cfg(feature = "date")] pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_def())); +#[cfg(feature = "temporal")] +pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(Instant::_def())); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_def())); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_def())); @@ -988,6 +995,8 @@ impl Rootable for Value<'_> { Self::Array(array) => Err(HeapRootData::Array(array.unbind())), #[cfg(feature = "date")] Self::Date(date) => Err(HeapRootData::Date(date.unbind())), + #[cfg(feature = "temporal")] + Self::Instant(instant) => Err(HeapRootData::Instant(instant.unbind())), Self::Error(error) => Err(HeapRootData::Error(error.unbind())), Self::FinalizationRegistry(finalization_registry) => Err( HeapRootData::FinalizationRegistry(finalization_registry.unbind()), @@ -1149,6 +1158,8 @@ impl Rootable for Value<'_> { HeapRootData::Array(array) => Some(Self::Array(array)), #[cfg(feature = "date")] HeapRootData::Date(date) => Some(Self::Date(date)), + #[cfg(feature = "temporal")] + HeapRootData::Instant(instant) => Some(Self::Instant(instant)), HeapRootData::Error(error) => Some(Self::Error(error)), HeapRootData::FinalizationRegistry(finalization_registry) => { Some(Self::FinalizationRegistry(finalization_registry)) @@ -1565,6 +1576,8 @@ fn map_object_to_static_string_repr(value: Value) -> String<'static> { Object::SharedFloat16Array(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "date")] Object::Date(_) => BUILTIN_STRING_MEMORY._object_Object_, + #[cfg(feature = "temporal")] + Object::Instant(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "set")] Object::Set(_) | Object::SetIterator(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "weak-refs")] diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index f82f026e3..50c157081 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1326,6 +1326,8 @@ fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> String<'static> Value::SharedFloat16Array(_) => BUILTIN_STRING_MEMORY.object, #[cfg(feature = "date")] Value::Date(_) => BUILTIN_STRING_MEMORY.object, + #[cfg(feature = "temporal")] + Value::Instant(_) => BUILTIN_STRING_MEMORY.object, // 13. If val has a [[Call]] internal slot, return "function". Value::BoundFunction(_) | Value::BuiltinFunction(_) | Value::ECMAScriptFunction(_) | Value::BuiltinConstructorFunction(_) | diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index 3bf1bfa3b..0481be6f0 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -230,6 +230,8 @@ pub mod private { impl RootableSealed for BuiltinPromiseFinallyFunction<'_> {} #[cfg(feature = "date")] impl RootableSealed for Date<'_> {} + #[cfg(feature = "temporal")] + impl RootableSealed for Instant<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} impl RootableSealed for Error<'_> {} @@ -544,6 +546,8 @@ pub enum HeapRootData { Array(Array<'static>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'static>) = DATE_DISCRIMINANT, + #[cfg(feature = "temporal")] + Instant(Instant<'static>) = INSTANT_DISCRIMINANT, Error(Error<'static>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'static>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'static>) = MAP_DISCRIMINANT, @@ -678,6 +682,8 @@ impl From> for HeapRootData { Object::Array(array) => Self::Array(array), #[cfg(feature = "date")] Object::Date(date) => Self::Date(date), + #[cfg(feature = "temporal")] + Object::Instant(instant) => Self::Instant(instant), Object::Error(error) => Self::Error(error), Object::FinalizationRegistry(finalization_registry) => { Self::FinalizationRegistry(finalization_registry) @@ -837,6 +843,8 @@ impl HeapMarkAndSweep for HeapRootData { Self::Array(array) => array.mark_values(queues), #[cfg(feature = "date")] Self::Date(date) => date.mark_values(queues), + #[cfg(feature = "temporal")] + Self::Instant(instant) => instant.mark_values(queues), Self::Error(error) => error.mark_values(queues), Self::FinalizationRegistry(finalization_registry) => { finalization_registry.mark_values(queues) @@ -986,6 +994,8 @@ impl HeapMarkAndSweep for HeapRootData { Self::Array(array) => array.sweep_values(compactions), #[cfg(feature = "date")] Self::Date(date) => date.sweep_values(compactions), + #[cfg(feature = "temporal")] + Self::Instant(date) => date.sweep_values(compactions), Self::Error(error) => error.sweep_values(compactions), Self::FinalizationRegistry(finalization_registry) => { finalization_registry.sweep_values(compactions) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index b93a43a8e..7c0b6c806 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -29,6 +29,8 @@ pub(crate) use self::heap_constants::{ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::data::DateHeapData; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, @@ -145,6 +147,8 @@ pub(crate) struct Heap { pub(crate) caches: Caches<'static>, #[cfg(feature = "date")] pub(crate) dates: Vec>, + #[cfg(feature = "temporal")] + pub(crate) instants: Vec>, pub(crate) ecmascript_functions: Vec>, /// ElementsArrays is where all keys and values arrays live; /// Element arrays are static arrays of Values plus @@ -292,6 +296,8 @@ impl Heap { caches: Caches::with_capacity(1024), #[cfg(feature = "date")] dates: Vec::with_capacity(1024), + #[cfg(feature = "temporal")] + instants: Vec::with_capacity(1024), // todo: assign appropriate value for instants ecmascript_functions: Vec::with_capacity(1024), elements: ElementArrays { e2pow1: ElementArray2Pow1::with_capacity(1024), diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 378b64b84..cc4ff53e2 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -25,6 +25,8 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::VoidArray}; #[cfg(feature = "shared-array-buffer")] @@ -529,6 +531,8 @@ pub(crate) struct WorkQueues<'a> { pub(crate) data_views: Vec>, #[cfg(feature = "date")] pub(crate) dates: Vec>, + #[cfg(feature = "date")] + pub(crate) instants: Vec>, pub(crate) declarative_environments: Vec>, pub(crate) e_2_1: Vec>, pub(crate) e_2_2: Vec>, @@ -1032,6 +1036,8 @@ impl<'a> WorkQueues<'a> { data_views: Vec::with_capacity(heap.data_views.len() / 4), #[cfg(feature = "date")] dates: Vec::with_capacity(heap.dates.len() / 4), + #[cfg(feature = "temporal")] + instants: Vec::with_capacity(heap.instants.len() / 4), declarative_environments: Vec::with_capacity(heap.environments.declarative.len() / 4), e_2_1: Vec::with_capacity(heap.elements.e2pow1.values.len() / 4), e_2_2: Vec::with_capacity(heap.elements.e2pow2.values.len() / 4), @@ -1138,6 +1144,8 @@ impl<'a> WorkQueues<'a> { data_views, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, declarative_environments, e_2_1, e_2_2, @@ -1257,6 +1265,7 @@ impl<'a> WorkQueues<'a> { && caches.is_empty() && data_views.is_empty() && dates.is_empty() + && instants.is_empty() && declarative_environments.is_empty() && e_2_1.is_empty() && e_2_2.is_empty() @@ -1616,6 +1625,8 @@ pub(crate) struct CompactionLists { pub(crate) data_views: CompactionList, #[cfg(feature = "date")] pub(crate) dates: CompactionList, + #[cfg(feature = "temporal")] + pub(crate) instants: CompactionList, pub(crate) declarative_environments: CompactionList, pub(crate) e_2_1: CompactionList, pub(crate) e_2_2: CompactionList, @@ -1771,6 +1782,8 @@ impl CompactionLists { source_codes: CompactionList::from_mark_bits(&bits.source_codes, &bits.bits), #[cfg(feature = "date")] dates: CompactionList::from_mark_bits(&bits.dates, &bits.bits), + #[cfg(feature = "temporal")] + instants: CompactionList::from_mark_bits(&bits.instants, &bits.bits), errors: CompactionList::from_mark_bits(&bits.errors, &bits.bits), executables: CompactionList::from_mark_bits(&bits.executables, &bits.bits), maps: CompactionList::from_mark_bits(&bits.maps, &bits.bits), diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 6acdd03e8..643af6dd3 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -18,6 +18,8 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::VoidArray}; #[cfg(feature = "shared-array-buffer")] @@ -128,6 +130,8 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc caches, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, ecmascript_functions, elements, embedder_objects, @@ -548,6 +552,22 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc } }); } + #[cfg(feature = "temporal")] + { + let mut instant_marks: Box<[Instant]> = queues.instants.drain(..).collect(); + instant_marks.sort(); + instant_marks.iter().for_each(|&idx| { + let index = idx.get_index(); + if let Some(marked) = bits.instants.get_mut(index) { + if *marked { + // Already marked, ignore + return; + } + *marked = true; + instants.get(index).mark_values(&mut queues); + } + }); + } if !queues.embedder_objects.is_empty() { let mut embedder_object_marks: Box<[EmbedderObject]> = @@ -1251,6 +1271,8 @@ fn sweep( caches, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, ecmascript_functions, elements, embedder_objects, @@ -1704,6 +1726,12 @@ fn sweep( sweep_heap_vector_values(dates, &compactions, &bits.dates, &bits.bits); }); } + #[cfg(feature = "temporal")] + if !instants.is_empty() { + s.spawn(|| { + sweep_heap_vector_values(instants, &compactions, &bits.instants, &bits.bits); + }); + } if !declarative.is_empty() { s.spawn(|| { sweep_heap_vector_values( From 5003bb56e6efc08ea22c53f2a17762d0b4303153 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Sun, 19 Oct 2025 10:02:45 +0200 Subject: [PATCH 04/42] added data.rs --- nova_vm/src/ecmascript/builtins/ordinary.rs | 13 +- .../ecmascript/builtins/temporal/instant.rs | 124 ++++++++++-------- .../builtins/temporal/instant/data.rs | 28 ++++ .../ecmascript/execution/realm/intrinsics.rs | 4 + nova_vm/src/heap.rs | 2 +- 5 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/instant/data.rs diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index ab6da9c06..e2b5f66c2 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -6,9 +6,7 @@ pub(crate) mod caches; pub mod shape; use std::{ - collections::{TryReserveError, hash_map::Entry}, - ops::ControlFlow, - vec, + collections::{hash_map::Entry, TryReserveError}, ops::ControlFlow, time::Instant, vec }; use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; @@ -31,9 +29,7 @@ use crate::{ }, }, engine::{ - Scoped, - context::{Bindable, GcScope, NoGcScope}, - rootable::Scopable, + context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, Scoped }, heap::element_array::{ElementStorageRef, PropertyStorageRef}, }; @@ -1685,6 +1681,11 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .heap .create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None)) .into_object(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => agent + .heap + .create(InstantHeapData::default()) + .into_object(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 0419de163..19943d368 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -1,17 +1,19 @@ use core::ops::{Index, IndexMut}; +pub(crate) mod data; + use crate::{ ecmascript::{ builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, builtins::{ ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor }, - execution::{agent::Agent, JsResult, Realm}, + execution::{agent::Agent, JsResult, ProtoIntrinsics, Realm}, types::{ InternalMethods, InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY }, }, - engine::{context::{bindable_handle, GcScope, NoGcScope}, rootable::{HeapRootRef, Rootable}}, + engine::{context::{bindable_handle, Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}}, heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues}, }; /// Constructor function object for %Temporal.Instant%. @@ -58,36 +60,11 @@ impl InstantPrototype { .build(); } } -/// HEAP DATA -- Move to internal instant/data.rs -#[derive(Debug, Clone, Copy)] -pub(crate) struct InstantValue(/*TODO:BigInt*/); - -impl InstantValue { - // TODO -} -#[derive(Debug, Clone, Copy)] -pub struct InstantHeapData<'a> { - pub(crate) object_index: Option>, - pub(crate) date: InstantValue, -} - -impl InstantHeapData<'_> { - // TODO -} -bindable_handle!(InstantHeapData); -impl HeapMarkAndSweep for InstantHeapData<'static> { - fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { - todo!() - } - fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { - todo!() - } -} - -// HANDLES -- Keep public facing within instant.rs +use self::data::InstantHeapData; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl Instant<'_> { //TODO @@ -103,12 +80,12 @@ bindable_handle!(Instant); impl<'a> From> for Value<'a> { fn from(value: Instant<'a>) -> Self { - Value::Instant(value) // todo: add to value.rs + Value::Instant(value) } } impl<'a> From> for Object<'a> { fn from(value: Instant<'a>) -> Self { - Object::Instant(value) // todo: add to object.rs + Object::Instant(value) } } impl<'a> TryFrom> for Instant<'a> { @@ -116,7 +93,7 @@ impl<'a> TryFrom> for Instant<'a> { fn try_from(value: Value<'a>) -> Result { match value { - Value::Instant(idx) => Ok(idx), // todo: add to value.rs + Value::Instant(idx) => Ok(idx), _ => Err(()), } } @@ -125,66 +102,103 @@ impl<'a> TryFrom> for Instant<'a> { type Error = (); fn try_from(object: Object<'a>) -> Result { match object { - Object::Instant(idx) => Ok(idx), // todo: add to object.rs + Object::Instant(idx) => Ok(idx), _ => Err(()), } } } -// TODO impl trait bounds properly impl<'a> InternalSlots<'a> for Instant<'a> { - // TODO: Add TemporalInstant to ProtoIntrinsics - //const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; + const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; fn get_backing_object(self, agent: &Agent) -> Option> { - todo!() + agent[self].object_index // not implemented for `agent::Agent` } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { - todo!() + assert!(agent[self].object_index.replace(backing_object).is_none()); // not implemented for `agent::Agent` } } impl<'a> InternalMethods<'a> for Instant<'a> {} -impl HeapMarkAndSweep for Instant<'static> { - fn mark_values(&self, queues: &mut WorkQueues) { - todo!() +impl Index> for Agent { + type Output = InstantHeapData<'static>; + + fn index(&self, index: Instant<'_>) -> &Self::Output { + &self.heap.instants[index] } - fn sweep_values(&mut self, compactions: &CompactionLists) { - todo!() +} + +impl IndexMut> for Agent { + fn index_mut(&mut self, index: Instant) -> &mut Self::Output { + &mut self.heap.instants[index] } } -impl HeapSweepWeakReference for Instant<'static> { - fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { - compactions.dates.shift_weak_index(self.0).map(Self) +impl Index> for Vec>> { + type Output = InstantHeapData<'static>; + + fn index(&self, index: Instant<'_>) -> &Self::Output { + self.get(index.get_index()) + .expect("heap access out of bounds") + .as_ref() + .expect("") } } -impl<'a> CreateHeapData, Instant<'a>> for Heap { - fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { - todo!() +impl IndexMut> for Vec>> { + fn index_mut(&mut self, index: Instant<'_>) -> &mut Self::Output { + self.get_mut(index.get_index()) + .expect("dasdas") + .as_mut() + .expect("") } } -/* todo - impl keep public facing in temporal/instant.rs + impl Rootable for Instant<'_> { type RootRepr = HeapRootRef; fn to_root_repr(value: Self) -> Result { - todo!() + Err(HeapRootData::Instant(value.unbind())) } fn from_root_repr(value: &Self::RootRepr) -> Result { - todo!() + Err(*value) } fn from_heap_ref(heap_ref: crate::engine::rootable::HeapRootRef) -> Self::RootRepr { - todo!() + heap_ref } fn from_heap_data(heap_data: crate::engine::rootable::HeapRootData) -> Option { - todo!() + match heap_data { + HeapRootData::Instant(object) => Some(object), + _ => None, + } + } +} + + +impl HeapMarkAndSweep for Instant<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.instants.push(*self); + } + fn sweep_values(&mut self, compactions: &CompactionLists) { + compactions.instants.shift_index(&mut self.0); + } +} + +impl HeapSweepWeakReference for Instant<'static> { + fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { + compactions.dates.shift_weak_index(self.0).map(Self) + } +} + +impl<'a> CreateHeapData, Instant<'a>> for Heap { + fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { + self.instants.push(Some(data.unbind())); + self.alloc_counter += core::mem::size_of::>>(); + Instant(BaseIndex::last(&self.instants)) } } -*/ \ No newline at end of file diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs new file mode 100644 index 000000000..e4d397ba9 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -0,0 +1,28 @@ + +use crate::{ecmascript::types::{OrdinaryObject,bigint::BigInt}, engine::context::bindable_handle, heap::HeapMarkAndSweep}; + +#[derive(Debug, Clone, Copy)] +pub struct InstantHeapData<'a> { + pub(crate) object_index: Option>, + pub(crate) instant: BigInt<'a>, +} + +impl InstantHeapData<'_> { + pub fn default() -> Self { + Self { + object_index: None, + instant: BigInt::zero(), + } + } +} + +bindable_handle!(InstantHeapData); + +impl HeapMarkAndSweep for InstantHeapData<'static> { + fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { + todo!() + } + fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { + todo!() + } +} diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index eadc372db..5450d50a6 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -231,6 +231,8 @@ pub enum ProtoIntrinsics { RegExpStringIterator, Symbol, SyntaxError, + #[cfg(feature = "temporal")] + TemporalInstant, TypeError, #[cfg(feature = "array-buffer")] Uint16Array, @@ -429,6 +431,7 @@ impl Intrinsics { ProtoIntrinsics::String => self.string().into(), ProtoIntrinsics::Symbol => self.symbol().into(), ProtoIntrinsics::SyntaxError => self.syntax_error().into(), + ProtoIntrinsics::TemporalInstant => self.temporal_instant().into(), ProtoIntrinsics::TypeError => self.type_error().into(), ProtoIntrinsics::URIError => self.uri_error().into(), ProtoIntrinsics::AggregateError => self.aggregate_error().into(), @@ -518,6 +521,7 @@ impl Intrinsics { ProtoIntrinsics::String => self.string_prototype().into(), ProtoIntrinsics::Symbol => self.symbol_prototype().into(), ProtoIntrinsics::SyntaxError => self.syntax_error_prototype().into(), + ProtoIntrinsics::TemporalInstant => self.temporal().into(), ProtoIntrinsics::TypeError => self.type_error_prototype().into(), ProtoIntrinsics::URIError => self.uri_error_prototype().into(), ProtoIntrinsics::AggregateError => self.aggregate_error_prototype().into(), diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 7c0b6c806..d009117a8 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -30,7 +30,7 @@ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::InstantHeapData; +use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, From 1efcf9d10a6f9ab2977676d63790c17875a9e132 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:24:03 +0300 Subject: [PATCH 05/42] fix errors after rebase --- nova_vm/src/ecmascript/builtins/ordinary.rs | 19 ++++-- .../ecmascript/builtins/temporal/instant.rs | 63 +++++++++++-------- nova_vm/src/ecmascript/execution/weak_key.rs | 2 + .../src/ecmascript/types/language/object.rs | 6 +- .../src/ecmascript/types/language/value.rs | 9 ++- nova_vm/src/engine/rootable.rs | 4 ++ 6 files changed, 67 insertions(+), 36 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index e2b5f66c2..c446cb2a9 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -6,7 +6,9 @@ pub(crate) mod caches; pub mod shape; use std::{ - collections::{hash_map::Entry, TryReserveError}, ops::ControlFlow, time::Instant, vec + collections::{TryReserveError, hash_map::Entry}, + ops::ControlFlow, + vec, }; use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; @@ -15,6 +17,8 @@ use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::types::try_get_result_into_value; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::{ @@ -29,7 +33,9 @@ use crate::{ }, }, engine::{ - context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, Scoped + Scoped, + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, }, heap::element_array::{ElementStorageRef, PropertyStorageRef}, }; @@ -1682,10 +1688,9 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None)) .into_object(), #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalInstant => agent - .heap - .create(InstantHeapData::default()) - .into_object(), + ProtoIntrinsics::TemporalInstant => { + agent.heap.create(InstantHeapData::default()).into_object() + } ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) @@ -2074,6 +2079,8 @@ fn get_intrinsic_constructor<'a>( ProtoIntrinsics::WeakRef => Some(intrinsics.weak_ref().into_function()), #[cfg(feature = "weak-refs")] ProtoIntrinsics::WeakSet => Some(intrinsics.weak_set().into_function()), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into_function()), } } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 19943d368..88f3794e2 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -4,17 +4,25 @@ pub(crate) mod data; use crate::{ ecmascript::{ - builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, - builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor + builders::{ + builtin_function_builder::BuiltinFunctionBuilder, + ordinary_object_builder::OrdinaryObjectBuilder, }, - execution::{agent::Agent, JsResult, ProtoIntrinsics, Realm}, + builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor}, + execution::{JsResult, ProtoIntrinsics, Realm, agent::Agent}, types::{ - InternalMethods, InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY + BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, IntoObject, Object, + OrdinaryObject, String, Value, }, }, - engine::{context::{bindable_handle, Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}}, - heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues}, + engine::{ + context::{Bindable, GcScope, NoGcScope, bindable_handle}, + rootable::{HeapRootData, HeapRootRef, Rootable}, + }, + heap::{ + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, + }, }; /// Constructor function object for %Temporal.Instant%. pub(crate) struct InstantConstructor; @@ -28,18 +36,23 @@ impl BuiltinIntrinsicConstructor for InstantConstructor { } impl InstantConstructor { - fn construct<'gc>(agent: &mut Agent, this_value: Value, args: ArgumentsList, new_target: Option, gc: GcScope<'gc, '_>) -> JsResult<'gc, Value<'gc>> { + fn construct<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + new_target: Option, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { todo!(); } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { - let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); - + BuiltinFunctionBuilder::new_intrinsic_constructor::(agent, realm) - .with_property_capacity(1) - .with_prototype_property(instant_prototype.into_object()) - .build(); - + .with_property_capacity(1) + .with_prototype_property(instant_prototype.into_object()) + .build(); } } /// %Temporal.Instant.Prototype% @@ -53,7 +66,7 @@ impl InstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(1) // TODO add correct property capacity + .with_property_capacity(1) // TODO add correct property capacity .with_prototype(object_prototype) .with_constructor_property(instant_constructor) // TODO add all prototype methods @@ -61,9 +74,8 @@ impl InstantPrototype { } } - use self::data::InstantHeapData; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl Instant<'_> { @@ -79,9 +91,9 @@ impl Instant<'_> { bindable_handle!(Instant); impl<'a> From> for Value<'a> { - fn from(value: Instant<'a>) -> Self { + fn from(value: Instant<'a>) -> Self { Value::Instant(value) - } + } } impl<'a> From> for Object<'a> { fn from(value: Instant<'a>) -> Self { @@ -116,7 +128,6 @@ impl<'a> InternalSlots<'a> for Instant<'a> { fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { assert!(agent[self].object_index.replace(backing_object).is_none()); // not implemented for `agent::Agent` } - } impl<'a> InternalMethods<'a> for Instant<'a> {} @@ -140,9 +151,9 @@ impl Index> for Vec>> { fn index(&self, index: Instant<'_>) -> &Self::Output { self.get(index.get_index()) - .expect("heap access out of bounds") - .as_ref() - .expect("") + .expect("heap access out of bounds") + .as_ref() + .expect("") } } @@ -155,7 +166,6 @@ impl IndexMut> for Vec>> { } } - impl Rootable for Instant<'_> { type RootRepr = HeapRootRef; @@ -163,7 +173,9 @@ impl Rootable for Instant<'_> { Err(HeapRootData::Instant(value.unbind())) } - fn from_root_repr(value: &Self::RootRepr) -> Result { + fn from_root_repr( + value: &Self::RootRepr, + ) -> Result { Err(*value) } @@ -179,7 +191,6 @@ impl Rootable for Instant<'_> { } } - impl HeapMarkAndSweep for Instant<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.instants.push(*self); diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index 98df938f2..a0f06cd7b 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -14,6 +14,8 @@ use crate::ecmascript::types::DATE_DISCRIMINANT; use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index efd1078eb..bc1a7f693 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -124,10 +124,11 @@ use crate::{ promise::Promise, promise_objects::promise_abstract_operations::promise_finally_functions::BuiltinPromiseFinallyFunction, proxy::Proxy, + temporal::instant::Instant, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{Agent, JsResult, ProtoIntrinsics, agent::TryResult}, - types::PropertyDescriptor, + types::{INSTANT_DISCRIMINANT, PropertyDescriptor}, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -179,6 +180,7 @@ pub enum Object<'a> { Array(Array<'a>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, + #[cfg(feature = "temporal")] Instant(Instant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, @@ -1438,6 +1440,8 @@ impl HeapSweepWeakReference for Object<'static> { Self::Array(data) => data.sweep_weak_reference(compactions).map(Self::Array), #[cfg(feature = "date")] Self::Date(data) => data.sweep_weak_reference(compactions).map(Self::Date), + #[cfg(feature = "temporal")] + Self::Instant(data) => data.sweep_weak_reference(compactions).map(Self::Instant), Self::Error(data) => data.sweep_weak_reference(compactions).map(Self::Error), Self::BoundFunction(data) => data .sweep_weak_reference(compactions) diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 09193cd92..0475b6a56 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -11,6 +11,8 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::builtins::typed_array::Float16Array; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -1308,6 +1310,8 @@ impl HeapMarkAndSweep for Value<'static> { Self::Array(data) => data.mark_values(queues), #[cfg(feature = "date")] Self::Date(dv) => dv.mark_values(queues), + #[cfg(feature = "temporal")] + Self::Instant(dv) => dv.mark_values(queues), Self::Error(data) => data.mark_values(queues), Self::BoundFunction(data) => data.mark_values(queues), Self::BuiltinFunction(data) => data.mark_values(queues), @@ -1328,7 +1332,6 @@ impl HeapMarkAndSweep for Value<'static> { Self::WeakRef(data) => data.mark_values(queues), #[cfg(feature = "weak-refs")] Self::WeakSet(data) => data.mark_values(queues), - #[cfg(feature = "array-buffer")] Self::ArrayBuffer(ab) => ab.mark_values(queues), #[cfg(feature = "array-buffer")] @@ -1357,7 +1360,6 @@ impl HeapMarkAndSweep for Value<'static> { Self::Float32Array(ta) => ta.mark_values(queues), #[cfg(feature = "array-buffer")] Self::Float64Array(ta) => ta.mark_values(queues), - #[cfg(feature = "shared-array-buffer")] Self::SharedArrayBuffer(data) => data.mark_values(queues), #[cfg(feature = "shared-array-buffer")] @@ -1386,7 +1388,6 @@ impl HeapMarkAndSweep for Value<'static> { Self::SharedFloat32Array(sta) => sta.mark_values(queues), #[cfg(feature = "shared-array-buffer")] Self::SharedFloat64Array(sta) => sta.mark_values(queues), - Self::BuiltinConstructorFunction(data) => data.mark_values(queues), Self::BuiltinPromiseResolvingFunction(data) => data.mark_values(queues), Self::BuiltinPromiseFinallyFunction(data) => data.mark_values(queues), @@ -1425,6 +1426,8 @@ impl HeapMarkAndSweep for Value<'static> { Self::Array(data) => data.sweep_values(compactions), #[cfg(feature = "date")] Self::Date(data) => data.sweep_values(compactions), + #[cfg(feature = "temporal")] + Self::Instant(dv) => dv.sweep_values(compactions), Self::Error(data) => data.sweep_values(compactions), Self::BoundFunction(data) => data.sweep_values(compactions), Self::BuiltinFunction(data) => data.sweep_values(compactions), diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index 0481be6f0..cdd61278a 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -17,6 +17,8 @@ use crate::ecmascript::types::DATE_DISCRIMINANT; use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -135,6 +137,8 @@ pub mod private { #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; + #[cfg(feature = "temporal")] + use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::{ data_view::SharedDataView, From c485800ce419a5e4cb7087344c1a1e337c3458ae Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:34:51 +0300 Subject: [PATCH 06/42] cleanup --- nova_vm/src/ecmascript/builtins/ordinary.rs | 4 +- .../ecmascript/builtins/temporal/instant.rs | 45 +++++++++---------- .../builtins/temporal/instant/data.rs | 37 ++++++++++----- nova_vm/src/heap.rs | 4 +- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index c446cb2a9..e7409511c 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -18,7 +18,7 @@ use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::types::try_get_result_into_value; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; +use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::{ @@ -1689,7 +1689,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .into_object(), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => { - agent.heap.create(InstantHeapData::default()).into_object() + agent.heap.create(InstantRecord::default()).into_object() } ProtoIntrinsics::TypeError => agent .heap diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 88f3794e2..f1045a2d6 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -74,10 +74,10 @@ impl InstantPrototype { } } -use self::data::InstantHeapData; +use self::data::InstantRecord; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); +pub struct Instant<'a>(BaseIndex<'a, InstantRecord<'static>>); impl Instant<'_> { //TODO pub(crate) const fn _def() -> Self { @@ -123,17 +123,18 @@ impl<'a> TryFrom> for Instant<'a> { impl<'a> InternalSlots<'a> for Instant<'a> { const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; fn get_backing_object(self, agent: &Agent) -> Option> { - agent[self].object_index // not implemented for `agent::Agent` + agent[self].object_index } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { - assert!(agent[self].object_index.replace(backing_object).is_none()); // not implemented for `agent::Agent` + assert!(agent[self].object_index.replace(backing_object).is_none()); } } impl<'a> InternalMethods<'a> for Instant<'a> {} +// TODO: get rid of Index impls, replace with get/get_mut/get_direct/get_direct_mut functions impl Index> for Agent { - type Output = InstantHeapData<'static>; + type Output = InstantRecord<'static>; fn index(&self, index: Instant<'_>) -> &Self::Output { &self.heap.instants[index] @@ -146,44 +147,38 @@ impl IndexMut> for Agent { } } -impl Index> for Vec>> { - type Output = InstantHeapData<'static>; +impl Index> for Vec> { + type Output = InstantRecord<'static>; fn index(&self, index: Instant<'_>) -> &Self::Output { self.get(index.get_index()) .expect("heap access out of bounds") - .as_ref() - .expect("") } } -impl IndexMut> for Vec>> { +impl IndexMut> for Vec> { fn index_mut(&mut self, index: Instant<'_>) -> &mut Self::Output { self.get_mut(index.get_index()) - .expect("dasdas") - .as_mut() - .expect("") + .expect("heap access out of bounds") } } impl Rootable for Instant<'_> { type RootRepr = HeapRootRef; - fn to_root_repr(value: Self) -> Result { + fn to_root_repr(value: Self) -> Result { Err(HeapRootData::Instant(value.unbind())) } - fn from_root_repr( - value: &Self::RootRepr, - ) -> Result { + fn from_root_repr(value: &Self::RootRepr) -> Result { Err(*value) } - fn from_heap_ref(heap_ref: crate::engine::rootable::HeapRootRef) -> Self::RootRepr { + fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr { heap_ref } - fn from_heap_data(heap_data: crate::engine::rootable::HeapRootData) -> Option { + fn from_heap_data(heap_data: HeapRootData) -> Option { match heap_data { HeapRootData::Instant(object) => Some(object), _ => None, @@ -202,14 +197,14 @@ impl HeapMarkAndSweep for Instant<'static> { impl HeapSweepWeakReference for Instant<'static> { fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { - compactions.dates.shift_weak_index(self.0).map(Self) + compactions.instants.shift_weak_index(self.0).map(Self) } } -impl<'a> CreateHeapData, Instant<'a>> for Heap { - fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { - self.instants.push(Some(data.unbind())); - self.alloc_counter += core::mem::size_of::>>(); - Instant(BaseIndex::last(&self.instants)) +impl<'a> CreateHeapData, Instant<'a>> for Heap { + fn create(&mut self, data: InstantRecord<'a>) -> Instant<'a> { + self.instants.push(data.unbind()); + self.alloc_counter += core::mem::size_of::>(); + Instant(BaseIndex::last_t(&self.instants)) } } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index e4d397ba9..4664fa0c6 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -1,28 +1,41 @@ - -use crate::{ecmascript::types::{OrdinaryObject,bigint::BigInt}, engine::context::bindable_handle, heap::HeapMarkAndSweep}; +use crate::{ + ecmascript::types::OrdinaryObject, + engine::context::bindable_handle, + heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, +}; #[derive(Debug, Clone, Copy)] -pub struct InstantHeapData<'a> { +pub struct InstantRecord<'a> { pub(crate) object_index: Option>, - pub(crate) instant: BigInt<'a>, + pub(crate) instant: temporal_rs::Instant, } -impl InstantHeapData<'_> { +impl InstantRecord<'_> { pub fn default() -> Self { Self { object_index: None, - instant: BigInt::zero(), + instant: temporal_rs::Instant::try_new(0).unwrap(), } } } -bindable_handle!(InstantHeapData); +bindable_handle!(InstantRecord); + +impl HeapMarkAndSweep for InstantRecord<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + object_index, + instant: _, + } = self; -impl HeapMarkAndSweep for InstantHeapData<'static> { - fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { - todo!() + object_index.mark_values(queues); } - fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { - todo!() + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + object_index, + instant: _, + } = self; + + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index d009117a8..8ee6fe8ba 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -30,7 +30,7 @@ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; +use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, @@ -148,7 +148,7 @@ pub(crate) struct Heap { #[cfg(feature = "date")] pub(crate) dates: Vec>, #[cfg(feature = "temporal")] - pub(crate) instants: Vec>, + pub(crate) instants: Vec>, pub(crate) ecmascript_functions: Vec>, /// ElementsArrays is where all keys and values arrays live; /// Element arrays are static arrays of Values plus From da9f21ed70c50b5623f621a464962dc8d81233c1 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:39:07 +0300 Subject: [PATCH 07/42] create Temporal global property --- nova_vm/src/ecmascript/builtins/temporal/instant.rs | 5 +++-- nova_vm/src/ecmascript/execution/realm.rs | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index f1045a2d6..b475feba1 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -11,7 +11,7 @@ use crate::{ builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor}, execution::{JsResult, ProtoIntrinsics, Realm, agent::Agent}, types::{ - BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, IntoObject, Object, + BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, IntoObject, IntoValue, Object, OrdinaryObject, String, Value, }, }, @@ -43,8 +43,9 @@ impl InstantConstructor { new_target: Option, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - todo!(); + Ok(agent.heap.create(InstantRecord::default()).into_value()) } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); diff --git a/nova_vm/src/ecmascript/execution/realm.rs b/nova_vm/src/ecmascript/execution/realm.rs index 9b101de22..eda9bbdab 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -676,6 +676,9 @@ pub(crate) fn set_default_global_bindings<'a>( // 19.4.4 Reflect define_property!(intrinsic Reflect, reflect); + + #[cfg(feature = "temporal")] + define_property!(intrinsic Temporal, temporal); } // 3. Return global. From 02e806368297b4a9b56115c0f5b7a6dc27eb964c Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 12:15:46 +0300 Subject: [PATCH 08/42] Temporal.Instant constructor --- nova_vm/src/ecmascript/builtins/temporal.rs | 27 ++-- .../ecmascript/builtins/temporal/instant.rs | 122 ++++++++++++++++-- .../builtins/temporal/instant/data.rs | 4 + .../src/ecmascript/types/language/bigint.rs | 24 ++++ 4 files changed, 148 insertions(+), 29 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 03d2b9aa4..6b30cd6fc 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -6,34 +6,23 @@ pub mod instant; use crate::{ ecmascript::{ - abstract_operations::type_conversion::{to_big_int, to_number, to_number_primitive, to_uint32}, - builders::{self, ordinary_object_builder::OrdinaryObjectBuilder}, - builtins::{ArgumentsList, Behaviour, Builtin}, - execution::{agent, Agent, JsResult, Realm}, - types::{IntoValue, Number, Primitive, String, Value, BUILTIN_STRING_MEMORY}, - }, - engine::{ - context::{Bindable, GcScope, NoGcScope}, - rootable::Scopable, + builders::ordinary_object_builder::OrdinaryObjectBuilder, + execution::{Agent, Realm}, + types::BUILTIN_STRING_MEMORY, }, + engine::context::NoGcScope, heap::WellKnownSymbolIndexes, }; - pub(crate) struct TemporalObject; - impl TemporalObject { - pub fn create_intrinsic( - agent: &mut Agent, - realm: Realm<'static>, - gc: NoGcScope, - ) { + pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let object_prototype = intrinsics.object_prototype(); let this = intrinsics.temporal(); - let builders = OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) .with_property_capacity(2) .with_prototype(object_prototype) .with_property(|builder| { @@ -46,5 +35,5 @@ impl TemporalObject { }) .with_builtin_function_property::() .build(); - } -} \ No newline at end of file + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index b475feba1..5da6054c9 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -1,23 +1,34 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + use core::ops::{Index, IndexMut}; pub(crate) mod data; use crate::{ ecmascript::{ + abstract_operations::type_conversion::to_big_int, builders::{ builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder, }, - builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor}, - execution::{JsResult, ProtoIntrinsics, Realm, agent::Agent}, + builtins::{ + ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, + ordinary::ordinary_create_from_constructor, + }, + execution::{ + JsResult, ProtoIntrinsics, Realm, + agent::{Agent, ExceptionType}, + }, types::{ - BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, IntoObject, IntoValue, Object, - OrdinaryObject, String, Value, + BUILTIN_STRING_MEMORY, BigInt, Function, InternalMethods, InternalSlots, IntoFunction, + IntoObject, IntoValue, Object, OrdinaryObject, String, Value, }, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, - rootable::{HeapRootData, HeapRootRef, Rootable}, + rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, }, heap::{ CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, @@ -36,17 +47,61 @@ impl BuiltinIntrinsicConstructor for InstantConstructor { } impl InstantConstructor { + /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) fn construct<'gc>( agent: &mut Agent, - this_value: Value, + _: Value, args: ArgumentsList, new_target: Option, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Ok(agent.heap.create(InstantRecord::default()).into_value()) + let epoch_nanoseconds = args.get(0).bind(gc.nogc()); + let new_target = new_target.bind(gc.nogc()); + // 1. If NewTarget is undefined, throw a TypeError exception. + let Some(new_target) = new_target else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "calling a builtin Temporal.Instant constructor without new is forbidden", + gc.into_nogc(), + )); + }; + let Ok(mut new_target) = Function::try_from(new_target) else { + unreachable!() + }; + // 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds). + let epoch_nanoseconds = if let Ok(epoch_nanoseconds) = BigInt::try_from(epoch_nanoseconds) { + epoch_nanoseconds + } else { + let scoped_new_target = new_target.scope(agent, gc.nogc()); + let epoch_nanoseconds = to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // SAFETY: not shared. + new_target = unsafe { scoped_new_target.take(agent) }.bind(gc.nogc()); + epoch_nanoseconds + }; + + // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + let Some(epoch_nanoseconds) = epoch_nanoseconds + .try_into_i128(agent) + .and_then(|nanoseconds| temporal_rs::Instant::try_new(nanoseconds).ok()) + else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "value out of range", + gc.into_nogc(), + )); + }; + // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget). + create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc).map( + |instant| { + eprintln!("Temporal.Instant {:?}", &agent[instant].instant); + instant.into_value() + }, + ) } - pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); @@ -56,11 +111,47 @@ impl InstantConstructor { .build(); } } + +/// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) +/// +/// The abstract operation CreateTemporalInstant takes argument +/// epochNanoseconds (a BigInt) and optional argument newTarget (a constructor) +/// and returns either a normal completion containing a Temporal.Instant or a +/// throw completion. It creates a Temporal.Instant instance and fills the +/// internal slots with valid values. +fn create_temporal_instant<'gc>( + agent: &mut Agent, + epoch_nanoseconds: temporal_rs::Instant, + new_target: Option, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Instant<'gc>> { + // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true. + // 2. If newTarget is not present, set newTarget to %Temporal.Instant%. + let new_target = new_target.unwrap_or_else(|| { + agent + .current_realm_record() + .intrinsics() + .temporal_instant() + .into_function() + }); + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »). + let Object::Instant(object) = + ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? + else { + unreachable!() + }; + // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. + // SAFETY: initialising Instant. + unsafe { object.set_epoch_nanoseconds(agent, epoch_nanoseconds) }; + // 5. Return object. + Ok(object) +} + /// %Temporal.Instant.Prototype% pub(crate) struct InstantPrototype; impl InstantPrototype { - pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { + pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let this = intrinsics.temporal_instant_prototype(); let object_prototype = intrinsics.object_prototype(); @@ -88,6 +179,17 @@ impl Instant<'_> { pub(crate) const fn get_index(self) -> usize { self.0.into_index() } + + /// # Safety + /// + /// Should be only called once; reinitialising the value is not allowed. + unsafe fn set_epoch_nanoseconds( + self, + agent: &mut Agent, + epoch_nanoseconds: temporal_rs::Instant, + ) { + agent[self].instant = epoch_nanoseconds; + } } bindable_handle!(Instant); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index 4664fa0c6..91d365a58 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -1,3 +1,7 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + use crate::{ ecmascript::types::OrdinaryObject, engine::context::bindable_handle, diff --git a/nova_vm/src/ecmascript/types/language/bigint.rs b/nova_vm/src/ecmascript/types/language/bigint.rs index fdf76613a..aef659428 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -247,6 +247,30 @@ impl<'a> BigInt<'a> { } } + pub fn try_into_i128(self, agent: &Agent) -> Option { + match self { + BigInt::BigInt(b) => { + let data = &agent[b].data; + let sign = data.sign(); + let mut digits = data.iter_u64_digits(); + if digits.len() > 2 { + return None; + } else if digits.len() == 1 { + let abs = digits.next().unwrap() as i128; + if sign == Sign::Minus { + return Some(abs.neg()); + } else { + return Some(abs); + } + } + // Hard part: check that u64 << 64 + u64 doesn't have an + // absolute value overflowing i128. + todo!(); + } + BigInt::SmallBigInt(b) => Some(b.into_i64() as i128), + } + } + /// ### [6.1.6.2.1 BigInt::unaryMinus ( x )](https://tc39.es/ecma262/#sec-numeric-types-bigint-unaryMinus) /// /// The abstract operation BigInt::unaryMinus takes argument x (a BigInt) From 74a3139d3c4ca809cb44d86b533cf043746ae30c Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sun, 19 Oct 2025 13:07:16 +0200 Subject: [PATCH 09/42] add prototype methods --- nova_vm/src/builtin_strings | 3 + .../ecmascript/builtins/temporal/instant.rs | 197 +++++++++++++++--- .../ecmascript/execution/realm/intrinsics.rs | 4 +- nova_vm/src/ecmascript/execution/weak_key.rs | 6 +- .../src/ecmascript/types/language/object.rs | 4 +- .../src/ecmascript/types/language/value.rs | 7 +- nova_vm/src/engine/rootable.rs | 10 +- nova_vm/src/heap/heap_bits.rs | 12 +- nova_vm/src/heap/heap_gc.rs | 4 +- 9 files changed, 196 insertions(+), 51 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 60e5e61d4..d355c7817 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -87,6 +87,7 @@ codePointAt concat configurable construct +compare constructor copyWithin #[cfg(feature = "math")]cos @@ -147,6 +148,8 @@ for forEach freeze from +fromEpochNanoseconds +fromEpochMilliseconds fromCharCode fromCodePoint fromEntries diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 5da6054c9..655a99f39 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -8,7 +8,7 @@ pub(crate) mod data; use crate::{ ecmascript::{ - abstract_operations::type_conversion::to_big_int, + abstract_operations::type_conversion::{PreferredType, to_big_int, to_primitive_object}, builders::{ builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder, @@ -23,7 +23,7 @@ use crate::{ }, types::{ BUILTIN_STRING_MEMORY, BigInt, Function, InternalMethods, InternalSlots, IntoFunction, - IntoObject, IntoValue, Object, OrdinaryObject, String, Value, + IntoObject, IntoValue, Object, OrdinaryObject, Primitive, String, Value, }, }, engine::{ @@ -101,6 +101,50 @@ impl InstantConstructor { ) } + /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) + fn from<'gc>( + agent: &mut Agent, + _this_value: Value, + arguments: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let item = arguments.get(0).bind(gc.nogc()); + // 1. Return ? ToTemporalInstant(item). + let instant = to_temporal_instant(agent, item.unbind(), gc)?; + let instant = agent.heap.create(InstantRecord { + object_index: None, + instant, + }); + Ok(instant.into_value()) + } + + fn from_epoch_milliseconds<'gc>( + _agent: &mut Agent, + _this_value: Value, + _arguments: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + todo!() + } + + fn from_epoch_nanoseconds<'gc>( + _agent: &mut Agent, + _this_value: Value, + _arguments: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + todo!() + } + + fn compare<'gc>( + _agent: &mut Agent, + _this_value: Value, + _arguments: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + todo!() + } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); @@ -124,7 +168,7 @@ fn create_temporal_instant<'gc>( epoch_nanoseconds: temporal_rs::Instant, new_target: Option, gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Instant<'gc>> { +) -> JsResult<'gc, TemporalInstant<'gc>> { // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true. // 2. If newTarget is not present, set newTarget to %Temporal.Instant%. let new_target = new_target.unwrap_or_else(|| { @@ -147,10 +191,94 @@ fn create_temporal_instant<'gc>( Ok(object) } +/// ### [8.5.3 ToTemporalInstant ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant) +/// +/// The abstract operation ToTemporalInstant takes argument item (an ECMAScript language value) and +/// returns either a normal completion containing a Temporal.Instant or a throw completion. +/// Converts item to a new Temporal.Instant instance if possible, and throws otherwise. It performs +/// the following steps when called: +fn to_temporal_instant<'gc>( + agent: &mut Agent, + item: Value, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, temporal_rs::Instant> { + let item = item.bind(gc.nogc()); + // 1. If item is an Object, then + let item = if let Ok(item) = Object::try_from(item) { + // a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] + // internal slot, then TODO: TemporalZonedDateTime::try_from(item) + if let Ok(item) = TemporalInstant::try_from(item) { + // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]). + return Ok(agent[item].instant); + } + // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted. + // c. Set item to ? ToPrimitive(item, string). + to_primitive_object(agent, item.unbind(), Some(PreferredType::String), gc)? + } else { + Primitive::try_from(item).unwrap() + }; + // 2. If item is not a String, throw a TypeError exception. + let Ok(item) = String::try_from(item) else { + todo!() // TypeErrror + }; + // 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »). + // 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or + // parsed.[[TimeZone]].[[Z]] is true, but not both. + // 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let + // offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]). + // 6. If parsed.[[Time]] is start-of-day, let time be MidnightTimeRecord(); else let time be + // parsed.[[Time]]. + // 7. Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], + // time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], + // time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds). + // 8. Perform ? CheckISODaysRange(balanced.[[ISODate]]). + // 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced). + // 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + // 11. Return ! CreateTemporalInstant(epochNanoseconds). + let parsed = temporal_rs::Instant::from_utf8(item.as_bytes(agent)).unwrap(); + Ok(parsed) +} + /// %Temporal.Instant.Prototype% -pub(crate) struct InstantPrototype; +pub(crate) struct TemporalInstantPrototype; + +struct TemporalInstantFrom; +impl Builtin for TemporalInstantFrom { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from); +} + +struct TemporalInstantFromEpochMilliseconds; +impl Builtin for TemporalInstantFromEpochMilliseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from_epoch_milliseconds); +} + +struct TemporalInstantFromEpochNanoseconds; +impl Builtin for TemporalInstantFromEpochNanoseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from_epoch_nanoseconds); +} + +struct TemporalInstantCompare; +impl Builtin for TemporalInstantCompare { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; + + const LENGTH: u8 = 2; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::compare); +} -impl InstantPrototype { +impl TemporalInstantPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let this = intrinsics.temporal_instant_prototype(); @@ -158,10 +286,13 @@ impl InstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(1) // TODO add correct property capacity + .with_property_capacity(5) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) - // TODO add all prototype methods + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() .build(); } } @@ -169,11 +300,11 @@ impl InstantPrototype { use self::data::InstantRecord; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct Instant<'a>(BaseIndex<'a, InstantRecord<'static>>); -impl Instant<'_> { +pub struct TemporalInstant<'a>(BaseIndex<'a, InstantRecord<'static>>); +impl TemporalInstant<'_> { //TODO pub(crate) const fn _def() -> Self { - Instant(BaseIndex::from_u32_index(0)) + TemporalInstant(BaseIndex::from_u32_index(0)) } pub(crate) const fn get_index(self) -> usize { @@ -191,19 +322,19 @@ impl Instant<'_> { agent[self].instant = epoch_nanoseconds; } } -bindable_handle!(Instant); +bindable_handle!(TemporalInstant); -impl<'a> From> for Value<'a> { - fn from(value: Instant<'a>) -> Self { +impl<'a> From> for Value<'a> { + fn from(value: TemporalInstant<'a>) -> Self { Value::Instant(value) } } -impl<'a> From> for Object<'a> { - fn from(value: Instant<'a>) -> Self { +impl<'a> From> for Object<'a> { + fn from(value: TemporalInstant<'a>) -> Self { Object::Instant(value) } } -impl<'a> TryFrom> for Instant<'a> { +impl<'a> TryFrom> for TemporalInstant<'a> { type Error = (); fn try_from(value: Value<'a>) -> Result { @@ -213,7 +344,7 @@ impl<'a> TryFrom> for Instant<'a> { } } } -impl<'a> TryFrom> for Instant<'a> { +impl<'a> TryFrom> for TemporalInstant<'a> { type Error = (); fn try_from(object: Object<'a>) -> Result { match object { @@ -223,7 +354,7 @@ impl<'a> TryFrom> for Instant<'a> { } } -impl<'a> InternalSlots<'a> for Instant<'a> { +impl<'a> InternalSlots<'a> for TemporalInstant<'a> { const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; fn get_backing_object(self, agent: &Agent) -> Option> { agent[self].object_index @@ -233,40 +364,40 @@ impl<'a> InternalSlots<'a> for Instant<'a> { } } -impl<'a> InternalMethods<'a> for Instant<'a> {} +impl<'a> InternalMethods<'a> for TemporalInstant<'a> {} // TODO: get rid of Index impls, replace with get/get_mut/get_direct/get_direct_mut functions -impl Index> for Agent { +impl Index> for Agent { type Output = InstantRecord<'static>; - fn index(&self, index: Instant<'_>) -> &Self::Output { + fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { &self.heap.instants[index] } } -impl IndexMut> for Agent { - fn index_mut(&mut self, index: Instant) -> &mut Self::Output { +impl IndexMut> for Agent { + fn index_mut(&mut self, index: TemporalInstant) -> &mut Self::Output { &mut self.heap.instants[index] } } -impl Index> for Vec> { +impl Index> for Vec> { type Output = InstantRecord<'static>; - fn index(&self, index: Instant<'_>) -> &Self::Output { + fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { self.get(index.get_index()) .expect("heap access out of bounds") } } -impl IndexMut> for Vec> { - fn index_mut(&mut self, index: Instant<'_>) -> &mut Self::Output { +impl IndexMut> for Vec> { + fn index_mut(&mut self, index: TemporalInstant<'_>) -> &mut Self::Output { self.get_mut(index.get_index()) .expect("heap access out of bounds") } } -impl Rootable for Instant<'_> { +impl Rootable for TemporalInstant<'_> { type RootRepr = HeapRootRef; fn to_root_repr(value: Self) -> Result { @@ -289,7 +420,7 @@ impl Rootable for Instant<'_> { } } -impl HeapMarkAndSweep for Instant<'static> { +impl HeapMarkAndSweep for TemporalInstant<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.instants.push(*self); } @@ -298,16 +429,16 @@ impl HeapMarkAndSweep for Instant<'static> { } } -impl HeapSweepWeakReference for Instant<'static> { +impl HeapSweepWeakReference for TemporalInstant<'static> { fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { compactions.instants.shift_weak_index(self.0).map(Self) } } -impl<'a> CreateHeapData, Instant<'a>> for Heap { - fn create(&mut self, data: InstantRecord<'a>) -> Instant<'a> { +impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { + fn create(&mut self, data: InstantRecord<'a>) -> TemporalInstant<'a> { self.instants.push(data.unbind()); self.alloc_counter += core::mem::size_of::>(); - Instant(BaseIndex::last_t(&self.instants)) + TemporalInstant(BaseIndex::last_t(&self.instants)) } } diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 5450d50a6..82b81d9b0 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,7 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - TemporalObject, instant::InstantConstructor, instant::InstantPrototype, + TemporalObject, instant::InstantConstructor, instant::TemporalInstantPrototype, }; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ @@ -319,7 +319,7 @@ impl Intrinsics { TemporalObject::create_intrinsic(agent, realm, gc); // Instant InstantConstructor::create_intrinsic(agent, realm, gc); - InstantPrototype::create_intrinsic(agent, realm, gc); + TemporalInstantPrototype::create_intrinsic(agent, realm, gc); } #[cfg(feature = "date")] diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index a0f06cd7b..42b931ee3 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -15,7 +15,9 @@ use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; +use crate::ecmascript::{ + builtins::temporal::instant::TemporalInstant, types::INSTANT_DISCRIMINANT, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -144,7 +146,7 @@ pub(crate) enum WeakKey<'a> { #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] - Instant(Instant<'a>) = INSTANT_DISCRIMINANT, + Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index bc1a7f693..66711b8b2 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -124,7 +124,7 @@ use crate::{ promise::Promise, promise_objects::promise_abstract_operations::promise_finally_functions::BuiltinPromiseFinallyFunction, proxy::Proxy, - temporal::instant::Instant, + temporal::instant::TemporalInstant, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{Agent, JsResult, ProtoIntrinsics, agent::TryResult}, @@ -181,7 +181,7 @@ pub enum Object<'a> { #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] - Instant(Instant<'a>) = INSTANT_DISCRIMINANT, + Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 0475b6a56..e63843624 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -12,7 +12,7 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::Instant; +use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::builtins::typed_array::Float16Array; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -184,7 +184,7 @@ pub enum Value<'a> { #[cfg(feature = "date")] Date(Date<'a>), #[cfg(feature = "temporal")] - Instant(Instant<'a>), + Instant(TemporalInstant<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), Map(Map<'a>), @@ -322,7 +322,8 @@ pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array: #[cfg(feature = "date")] pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_def())); #[cfg(feature = "temporal")] -pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(Instant::_def())); +pub(crate) const INSTANT_DISCRIMINANT: u8 = + value_discriminant(Value::Instant(TemporalInstant::_def())); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_def())); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_def())); diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index cdd61278a..ff7f4c408 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -18,7 +18,9 @@ use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; +use crate::ecmascript::{ + builtins::temporal::instant::TemporalInstant, types::INSTANT_DISCRIMINANT, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -138,7 +140,7 @@ pub mod private { #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] - use crate::ecmascript::builtins::temporal::instant::Instant; + use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::{ data_view::SharedDataView, @@ -235,7 +237,7 @@ pub mod private { #[cfg(feature = "date")] impl RootableSealed for Date<'_> {} #[cfg(feature = "temporal")] - impl RootableSealed for Instant<'_> {} + impl RootableSealed for TemporalInstant<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} impl RootableSealed for Error<'_> {} @@ -551,7 +553,7 @@ pub enum HeapRootData { #[cfg(feature = "date")] Date(Date<'static>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] - Instant(Instant<'static>) = INSTANT_DISCRIMINANT, + Instant(TemporalInstant<'static>) = INSTANT_DISCRIMINANT, Error(Error<'static>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'static>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'static>) = MAP_DISCRIMINANT, diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index cc4ff53e2..f67cda379 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -26,7 +26,7 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::Instant; +use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::VoidArray}; #[cfg(feature = "shared-array-buffer")] @@ -455,6 +455,8 @@ pub(crate) struct HeapBits { pub(super) data_views: BitRange, #[cfg(feature = "date")] pub(super) dates: BitRange, + #[cfg(feature = "temporal")] + pub(super) instants: BitRange, pub(super) declarative_environments: BitRange, pub(super) ecmascript_functions: BitRange, pub(super) embedder_objects: BitRange, @@ -531,8 +533,8 @@ pub(crate) struct WorkQueues<'a> { pub(crate) data_views: Vec>, #[cfg(feature = "date")] pub(crate) dates: Vec>, - #[cfg(feature = "date")] - pub(crate) instants: Vec>, + #[cfg(feature = "temporal")] + pub(crate) instants: Vec>, pub(crate) declarative_environments: Vec>, pub(crate) e_2_1: Vec>, pub(crate) e_2_2: Vec>, @@ -682,6 +684,7 @@ impl HeapBits { let data_views = BitRange::from_bit_count_and_len(&mut bit_count, heap.data_views.len()); #[cfg(feature = "date")] let dates = BitRange::from_bit_count_and_len(&mut bit_count, heap.dates.len()); + let instants = BitRange::from_bit_count_and_len(&mut bit_count, heap.instants.len()); let declarative_environments = BitRange::from_bit_count_and_len(&mut bit_count, heap.environments.declarative.len()); let ecmascript_functions = @@ -789,6 +792,8 @@ impl HeapBits { data_views, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, declarative_environments, e_2_1, e_2_2, @@ -899,6 +904,7 @@ impl HeapBits { WeakKey::Array(d) => self.arrays.get_bit(d.get_index(), &self.bits), #[cfg(feature = "date")] WeakKey::Date(d) => self.dates.get_bit(d.get_index(), &self.bits), + WeakKey::Instant(d) => self.instants.get_bit(d.get_index(), &self.bits), WeakKey::Error(d) => self.errors.get_bit(d.get_index(), &self.bits), WeakKey::FinalizationRegistry(d) => self .finalization_registrys diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 643af6dd3..e3fb7ed48 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -19,7 +19,7 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::Instant; +use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::VoidArray}; #[cfg(feature = "shared-array-buffer")] @@ -554,7 +554,7 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc } #[cfg(feature = "temporal")] { - let mut instant_marks: Box<[Instant]> = queues.instants.drain(..).collect(); + let mut instant_marks: Box<[TemporalInstant]> = queues.instants.drain(..).collect(); instant_marks.sort(); instant_marks.iter().for_each(|&idx| { let index = idx.get_index(); From 0f4472d9376081ebccb3bf29e0ea0ce5f3e48611 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 14:26:20 +0300 Subject: [PATCH 10/42] get it working --- nova_vm/src/ecmascript/builtins/temporal.rs | 14 +++++- .../ecmascript/builtins/temporal/instant.rs | 47 ++++++++++--------- .../ecmascript/execution/realm/intrinsics.rs | 4 +- nova_vm/src/heap/heap_constants.rs | 2 - 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 6b30cd6fc..4df4d759c 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -7,8 +7,9 @@ pub mod instant; use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, + builtins::Builtin, execution::{Agent, Realm}, - types::BUILTIN_STRING_MEMORY, + types::{BUILTIN_STRING_MEMORY, IntoValue}, }, engine::context::NoGcScope, heap::WellKnownSymbolIndexes, @@ -22,6 +23,8 @@ impl TemporalObject { let object_prototype = intrinsics.object_prototype(); let this = intrinsics.temporal(); + let temporal_instant_constructor = intrinsics.temporal_instant(); + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) .with_property_capacity(2) .with_prototype(object_prototype) @@ -33,7 +36,14 @@ impl TemporalObject { .with_configurable(true) .build() }) - .with_builtin_function_property::() + .with_property(|builder| { + builder + .with_key(BUILTIN_STRING_MEMORY.Instant.into()) + .with_value(temporal_instant_constructor.into_value()) + .with_enumerable(instant::TemporalInstantConstructor::ENUMERABLE) + .with_configurable(instant::TemporalInstantConstructor::CONFIGURABLE) + .build() + }) .build(); } } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 655a99f39..d92b8ce32 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -36,17 +36,17 @@ use crate::{ }, }; /// Constructor function object for %Temporal.Instant%. -pub(crate) struct InstantConstructor; -impl Builtin for InstantConstructor { +pub(crate) struct TemporalInstantConstructor; +impl Builtin for TemporalInstantConstructor { const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Constructor(InstantConstructor::construct); + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::construct); } -impl BuiltinIntrinsicConstructor for InstantConstructor { +impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; } -impl InstantConstructor { +impl TemporalInstantConstructor { /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) fn construct<'gc>( agent: &mut Agent, @@ -93,12 +93,8 @@ impl InstantConstructor { )); }; // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget). - create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc).map( - |instant| { - eprintln!("Temporal.Instant {:?}", &agent[instant].instant); - instant.into_value() - }, - ) + create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc) + .map(|instant| instant.into_value()) } /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) @@ -145,13 +141,20 @@ impl InstantConstructor { todo!() } - pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); - BuiltinFunctionBuilder::new_intrinsic_constructor::(agent, realm) - .with_property_capacity(1) + let result = + BuiltinFunctionBuilder::new_intrinsic_constructor::( + agent, realm, + ) + .with_property_capacity(5) .with_prototype_property(instant_prototype.into_object()) + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() .build(); } } @@ -248,7 +251,7 @@ impl Builtin for TemporalInstantFrom { const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from); + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); } struct TemporalInstantFromEpochMilliseconds; @@ -257,7 +260,8 @@ impl Builtin for TemporalInstantFromEpochMilliseconds { const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from_epoch_milliseconds); + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); } struct TemporalInstantFromEpochNanoseconds; @@ -266,7 +270,8 @@ impl Builtin for TemporalInstantFromEpochNanoseconds { const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from_epoch_nanoseconds); + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); } struct TemporalInstantCompare; @@ -275,7 +280,7 @@ impl Builtin for TemporalInstantCompare { const LENGTH: u8 = 2; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::compare); + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); } impl TemporalInstantPrototype { @@ -286,13 +291,9 @@ impl TemporalInstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(5) + .with_property_capacity(1) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() .build(); } } diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 82b81d9b0..ba2fa6e24 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,7 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - TemporalObject, instant::InstantConstructor, instant::TemporalInstantPrototype, + TemporalObject, instant::TemporalInstantConstructor, instant::TemporalInstantPrototype, }; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ @@ -318,7 +318,7 @@ impl Intrinsics { { TemporalObject::create_intrinsic(agent, realm, gc); // Instant - InstantConstructor::create_intrinsic(agent, realm, gc); + TemporalInstantConstructor::create_intrinsic(agent, realm, gc); TemporalInstantPrototype::create_intrinsic(agent, realm, gc); } diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index 7a44b1afb..2e7341991 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -37,8 +37,6 @@ pub(crate) enum IntrinsicObjectIndexes { #[cfg(feature = "temporal")] TemporalObject, #[cfg(feature = "temporal")] - TemporalInstant, - #[cfg(feature = "temporal")] TemporalInstantPrototype, // Text processing #[cfg(feature = "regexp")] From a26ba27c98b3c2b55e6c29b1104b67db4c09f360 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 14:26:35 +0300 Subject: [PATCH 11/42] lint --- .../ecmascript/builtins/temporal/instant.rs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index d92b8ce32..3a4ae7001 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -141,21 +141,20 @@ impl TemporalInstantConstructor { todo!() } - pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); - let result = - BuiltinFunctionBuilder::new_intrinsic_constructor::( - agent, realm, - ) - .with_property_capacity(5) - .with_prototype_property(instant_prototype.into_object()) - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .build(); + BuiltinFunctionBuilder::new_intrinsic_constructor::( + agent, realm, + ) + .with_property_capacity(5) + .with_prototype_property(instant_prototype.into_object()) + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .build(); } } From 3ea2a379b1582b4a964efc65e0d441885288338c Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 03:26:47 +0200 Subject: [PATCH 12/42] fromEpochMillisecondswith sus gc subscoping --- .../ecmascript/builtins/temporal/instant.rs | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 3a4ae7001..a58b5454b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -8,31 +8,27 @@ pub(crate) mod data; use crate::{ ecmascript::{ - abstract_operations::type_conversion::{PreferredType, to_big_int, to_primitive_object}, + abstract_operations::type_conversion::{to_big_int, to_primitive_object, PreferredType}, builders::{ builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder, }, builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, - ordinary::ordinary_create_from_constructor, + ordinary::ordinary_create_from_constructor, ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor }, execution::{ - JsResult, ProtoIntrinsics, Realm, - agent::{Agent, ExceptionType}, + agent::{Agent, ExceptionType}, JsResult, ProtoIntrinsics, Realm }, types::{ - BUILTIN_STRING_MEMORY, BigInt, Function, InternalMethods, InternalSlots, IntoFunction, - IntoObject, IntoValue, Object, OrdinaryObject, Primitive, String, Value, + BigInt, Function, InternalMethods, InternalSlots, IntoFunction, IntoObject, IntoValue, Object, OrdinaryObject, Primitive, String, Value, BUILTIN_STRING_MEMORY }, }, engine::{ - context::{Bindable, GcScope, NoGcScope, bindable_handle}, + context::{bindable_handle, Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, }, heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, + indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues }, }; /// Constructor function object for %Temporal.Instant%. @@ -114,13 +110,36 @@ impl TemporalInstantConstructor { Ok(instant.into_value()) } + /// ### [8.2.3 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds) fn from_epoch_milliseconds<'gc>( - _agent: &mut Agent, + agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - todo!() + let epoch_ms = arguments.get(0).bind(gc.nogc()); + // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). + let epoch_ms_number = epoch_ms.unbind().to_number(agent, gc.subscope())?; + // 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6). + // 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + let epoch_ns = match temporal_rs::Instant::from_epoch_milliseconds( + epoch_ms_number.into_i64(agent) + ) { + Ok(instant) => instant, + Err(_) => { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "epochMilliseconds value out of range", + gc.into_nogc(), + )); + } + }; + + // 5. Return ! CreateTemporalInstant(epochNanoseconds). + let instant = create_temporal_instant(agent, epoch_ns, None, gc)?; + let value = instant.into_value(); + Ok(value) + } fn from_epoch_nanoseconds<'gc>( From b3c232c4fb99e024e4d176968690b6a22a1819c1 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 15:04:51 +0200 Subject: [PATCH 13/42] corrected gc scoping --- .../src/ecmascript/builtins/temporal/instant.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index a58b5454b..6a9a3900f 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -119,7 +119,17 @@ impl TemporalInstantConstructor { ) -> JsResult<'gc, Value<'gc>> { let epoch_ms = arguments.get(0).bind(gc.nogc()); // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). - let epoch_ms_number = epoch_ms.unbind().to_number(agent, gc.subscope())?; + let epoch_ms_number = epoch_ms.unbind() + .to_number(agent, gc.reborrow()).unbind()? + .bind(gc.nogc()); + // 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds). + if !epoch_ms_number.is_integer(agent) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "Can't convert number to BigInt because it isn't an integer", + gc.into_nogc(), + )); + } // 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6). // 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. let epoch_ns = match temporal_rs::Instant::from_epoch_milliseconds( @@ -141,7 +151,7 @@ impl TemporalInstantConstructor { Ok(value) } - + fn from_epoch_nanoseconds<'gc>( _agent: &mut Agent, _this_value: Value, From 3205e8bf265e6c39dab1d291acddec505a36e68e Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 16:05:00 +0200 Subject: [PATCH 14/42] from_epoch_nanoseconds --- .../ecmascript/builtins/temporal/instant.rs | 95 +++++++++++++------ 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 6a9a3900f..dd66299ac 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -8,27 +8,31 @@ pub(crate) mod data; use crate::{ ecmascript::{ - abstract_operations::type_conversion::{to_big_int, to_primitive_object, PreferredType}, + abstract_operations::type_conversion::{PreferredType, to_big_int, to_primitive_object}, builders::{ builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder, }, builtins::{ - ordinary::ordinary_create_from_constructor, ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor + ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, + ordinary::ordinary_create_from_constructor, }, execution::{ - agent::{Agent, ExceptionType}, JsResult, ProtoIntrinsics, Realm + JsResult, ProtoIntrinsics, Realm, + agent::{Agent, ExceptionType}, }, types::{ - BigInt, Function, InternalMethods, InternalSlots, IntoFunction, IntoObject, IntoValue, Object, OrdinaryObject, Primitive, String, Value, BUILTIN_STRING_MEMORY + BUILTIN_STRING_MEMORY, BigInt, Function, InternalMethods, InternalSlots, IntoFunction, + IntoObject, IntoValue, Object, OrdinaryObject, Primitive, String, Value, }, }, engine::{ - context::{bindable_handle, Bindable, GcScope, NoGcScope}, + context::{Bindable, GcScope, NoGcScope, bindable_handle}, rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, }, heap::{ - indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, }, }; /// Constructor function object for %Temporal.Instant%. @@ -119,8 +123,10 @@ impl TemporalInstantConstructor { ) -> JsResult<'gc, Value<'gc>> { let epoch_ms = arguments.get(0).bind(gc.nogc()); // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). - let epoch_ms_number = epoch_ms.unbind() - .to_number(agent, gc.reborrow()).unbind()? + let epoch_ms_number = epoch_ms + .unbind() + .to_number(agent, gc.reborrow()) + .unbind()? .bind(gc.nogc()); // 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds). if !epoch_ms_number.is_integer(agent) { @@ -132,41 +138,70 @@ impl TemporalInstantConstructor { } // 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6). // 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. - let epoch_ns = match temporal_rs::Instant::from_epoch_milliseconds( - epoch_ms_number.into_i64(agent) - ) { - Ok(instant) => instant, - Err(_) => { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "epochMilliseconds value out of range", - gc.into_nogc(), - )); - } - }; - + let epoch_ns = + match temporal_rs::Instant::from_epoch_milliseconds(epoch_ms_number.into_i64(agent)) { + Ok(instant) => instant, + Err(_) => { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "epochMilliseconds value out of range", + gc.into_nogc(), + )); + } + }; + // 5. Return ! CreateTemporalInstant(epochNanoseconds). let instant = create_temporal_instant(agent, epoch_ns, None, gc)?; let value = instant.into_value(); Ok(value) - } - + + /// [8.2.4 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )] (https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds) fn from_epoch_nanoseconds<'gc>( - _agent: &mut Agent, + agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - todo!() + let epoch_nanoseconds = arguments.get(0).bind(gc.nogc()); + // 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds). + let epoch_nanoseconds = if let Ok(epoch_nanoseconds) = BigInt::try_from(epoch_nanoseconds) { + epoch_nanoseconds + } else { + let epoch_nanoseconds = to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + epoch_nanoseconds + }; + // 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + let Some(epoch_nanoseconds) = epoch_nanoseconds + .try_into_i128(agent) + .and_then(|nanoseconds| temporal_rs::Instant::try_new(nanoseconds).ok()) + else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "epochNanoseconds", + gc.into_nogc(), + )); + }; + // 3. Return ! CreateTemporalInstant(epochNanoseconds). + let instant = create_temporal_instant(agent, epoch_nanoseconds, None, gc)?; + let value = instant.into_value(); + Ok(value) } + /// [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) fn compare<'gc>( - _agent: &mut Agent, + agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + // 1. Set one to ? ToTemporalInstant(one). + let one = arguments.get(0).bind(gc.nogc()); + // 2. Set two to ? ToTemporalInstant(two). + let two = arguments.get(1).bind(gc.nogc()); + // 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])). todo!() } From f120a695e7c5392455074a0c0cf998213c20426d Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sat, 25 Oct 2025 14:50:36 +0200 Subject: [PATCH 15/42] implementation of Temporal.Instant.compare --- nova_vm/Cargo.toml | 4 +- nova_vm/src/ecmascript/builtins/temporal.rs | 6 +- .../ecmascript/builtins/temporal/instant.rs | 105 ++++++++++-------- .../builtins/temporal/instant/data.rs | 4 +- .../ecmascript/execution/realm/intrinsics.rs | 9 +- 5 files changed, 69 insertions(+), 59 deletions(-) diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 8b1c89b30..035c21995 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -53,7 +53,7 @@ default = [ "regexp", "set", "annex-b", - "temporal", + "temporal", ] array-buffer = ["ecmascript_atomics"] atomics = ["array-buffer", "shared-array-buffer", "ecmascript_atomics", "ecmascript_futex"] @@ -65,7 +65,7 @@ shared-array-buffer = ["array-buffer", "ecmascript_atomics"] weak-refs = [] set = [] typescript = [] -temporal = ["temporal_rs"] +temporal = ["temporal_rs"] # Enables features defined by [Annex B](https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers) annex-b = ["annex-b-string", "annex-b-global", "annex-b-date", "annex-b-regexp"] diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 4df4d759c..b833296be 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -7,7 +7,7 @@ pub mod instant; use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, - builtins::Builtin, + builtins::{Builtin, temporal::instant::TemporalInstantConstructor}, execution::{Agent, Realm}, types::{BUILTIN_STRING_MEMORY, IntoValue}, }, @@ -40,8 +40,8 @@ impl TemporalObject { builder .with_key(BUILTIN_STRING_MEMORY.Instant.into()) .with_value(temporal_instant_constructor.into_value()) - .with_enumerable(instant::TemporalInstantConstructor::ENUMERABLE) - .with_configurable(instant::TemporalInstantConstructor::CONFIGURABLE) + .with_enumerable(TemporalInstantConstructor::ENUMERABLE) + .with_configurable(TemporalInstantConstructor::CONFIGURABLE) .build() }) .build(); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index dd66299ac..a4f712496 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -35,20 +35,61 @@ use crate::{ IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, }, }; + /// Constructor function object for %Temporal.Instant%. pub(crate) struct TemporalInstantConstructor; + impl Builtin for TemporalInstantConstructor { const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::construct); + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::constructor); } + impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; } +struct TemporalInstantFrom; +impl Builtin for TemporalInstantFrom { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); +} + +struct TemporalInstantFromEpochMilliseconds; +impl Builtin for TemporalInstantFromEpochMilliseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); +} + +struct TemporalInstantFromEpochNanoseconds; +impl Builtin for TemporalInstantFromEpochNanoseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); +} + +struct TemporalInstantCompare; +impl Builtin for TemporalInstantCompare { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; + + const LENGTH: u8 = 2; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); +} + impl TemporalInstantConstructor { /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) - fn construct<'gc>( + fn constructor<'gc>( agent: &mut Agent, _: Value, args: ArgumentsList, @@ -101,10 +142,10 @@ impl TemporalInstantConstructor { fn from<'gc>( agent: &mut Agent, _this_value: Value, - arguments: ArgumentsList, + args: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - let item = arguments.get(0).bind(gc.nogc()); + let item = args.get(0).bind(gc.nogc()); // 1. Return ? ToTemporalInstant(item). let instant = to_temporal_instant(agent, item.unbind(), gc)?; let instant = agent.heap.create(InstantRecord { @@ -118,10 +159,10 @@ impl TemporalInstantConstructor { fn from_epoch_milliseconds<'gc>( agent: &mut Agent, _this_value: Value, - arguments: ArgumentsList, + args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - let epoch_ms = arguments.get(0).bind(gc.nogc()); + let epoch_ms = args.get(0).bind(gc.nogc()); // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). let epoch_ms_number = epoch_ms .unbind() @@ -190,19 +231,23 @@ impl TemporalInstantConstructor { Ok(value) } - /// [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) + /// ### [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) fn compare<'gc>( agent: &mut Agent, _this_value: Value, - arguments: ArgumentsList, + args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + let one = args.get(0).bind(gc.nogc()); + let two = args.get(0).bind(gc.nogc()); + let two = two.scope(agent, gc.nogc()); // 1. Set one to ? ToTemporalInstant(one). - let one = arguments.get(0).bind(gc.nogc()); + let one_instant = to_temporal_instant(agent, one.unbind(), gc.reborrow()).unbind()?; // 2. Set two to ? ToTemporalInstant(two). - let two = arguments.get(1).bind(gc.nogc()); + let two_value = two.get(agent).bind(gc.nogc()); + let two_instant = to_temporal_instant(agent, two_value.unbind(), gc.reborrow()).unbind()?; // 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])). - todo!() + Ok((one_instant.cmp(&two_instant) as i8).into()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { @@ -308,44 +353,6 @@ fn to_temporal_instant<'gc>( /// %Temporal.Instant.Prototype% pub(crate) struct TemporalInstantPrototype; -struct TemporalInstantFrom; -impl Builtin for TemporalInstantFrom { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; - - const LENGTH: u8 = 1; - - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); -} - -struct TemporalInstantFromEpochMilliseconds; -impl Builtin for TemporalInstantFromEpochMilliseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; - - const LENGTH: u8 = 1; - - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); -} - -struct TemporalInstantFromEpochNanoseconds; -impl Builtin for TemporalInstantFromEpochNanoseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; - - const LENGTH: u8 = 1; - - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); -} - -struct TemporalInstantCompare; -impl Builtin for TemporalInstantCompare { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; - - const LENGTH: u8 = 2; - - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); -} - impl TemporalInstantPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index 91d365a58..1cf068d2e 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -2,9 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::engine::context::NoGcScope; use crate::{ ecmascript::types::OrdinaryObject, - engine::context::bindable_handle, + engine::context::{bindable_handle, trivially_bindable}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; @@ -23,6 +24,7 @@ impl InstantRecord<'_> { } } +trivially_bindable!(temporal_rs::Instant); bindable_handle!(InstantRecord); impl HeapMarkAndSweep for InstantRecord<'static> { diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index ba2fa6e24..b56aed4b0 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -1033,15 +1033,16 @@ impl Intrinsics { IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) } + /// %Temporal.Instant.Prototype% + pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) + } + /// %Temporal.Instant% pub(crate) const fn temporal_instant(&self) -> BuiltinFunction<'static> { IntrinsicConstructorIndexes::TemporalInstant .get_builtin_function(self.builtin_function_index_base) } - /// %Temporal.Instant.Prototype% - pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { - IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) - } /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { From 835df6368ceb7e700e6471365d4d7664267c3a56 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sat, 25 Oct 2025 19:25:35 +0200 Subject: [PATCH 16/42] bit of cleanup --- .../src/ecmascript/builtins/temporal/instant.rs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index a4f712496..4dd4aceec 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -52,18 +52,14 @@ impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { struct TemporalInstantFrom; impl Builtin for TemporalInstantFrom { const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); } struct TemporalInstantFromEpochMilliseconds; impl Builtin for TemporalInstantFromEpochMilliseconds { const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); } @@ -71,9 +67,7 @@ impl Builtin for TemporalInstantFromEpochMilliseconds { struct TemporalInstantFromEpochNanoseconds; impl Builtin for TemporalInstantFromEpochNanoseconds { const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); } @@ -81,9 +75,7 @@ impl Builtin for TemporalInstantFromEpochNanoseconds { struct TemporalInstantCompare; impl Builtin for TemporalInstantCompare { const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; - const LENGTH: u8 = 2; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); } @@ -141,7 +133,7 @@ impl TemporalInstantConstructor { /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) fn from<'gc>( agent: &mut Agent, - _this_value: Value, + _: Value, args: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { @@ -158,7 +150,7 @@ impl TemporalInstantConstructor { /// ### [8.2.3 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds) fn from_epoch_milliseconds<'gc>( agent: &mut Agent, - _this_value: Value, + _: Value, args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { @@ -200,7 +192,7 @@ impl TemporalInstantConstructor { /// [8.2.4 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )] (https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds) fn from_epoch_nanoseconds<'gc>( agent: &mut Agent, - _this_value: Value, + _: Value, arguments: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { @@ -234,7 +226,7 @@ impl TemporalInstantConstructor { /// ### [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) fn compare<'gc>( agent: &mut Agent, - _this_value: Value, + _: Value, args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { From a363df6f3a1872b70bd8db9930ce483d7fbf11ef Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 11:26:47 +0100 Subject: [PATCH 17/42] get_epoch_milliseconds and get_epoch_nanoseconds --- nova_vm/src/builtin_strings | 2 + nova_vm/src/ecmascript/builtins/ordinary.rs | 4 +- .../ecmascript/builtins/temporal/instant.rs | 98 ++++++- .../temporal/instant/instant_constructor.rs | 241 ++++++++++++++++++ 4 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index d355c7817..dfbbaf496 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -186,6 +186,8 @@ get size #[cfg(feature = "array-buffer")]getBigUint64 #[cfg(feature = "date")]getDate #[cfg(feature = "date")]getDay +#[cfg(feature = "temporal")]getEpochMilliseconds +#[cfg(feature = "temporal")]getEpochNanoSeconds #[cfg(feature = "proposal-float16array")]getFloat16 #[cfg(feature = "array-buffer")]getFloat32 #[cfg(feature = "array-buffer")]getFloat64 diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index e7409511c..344cd2d66 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -15,10 +15,10 @@ use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; -#[cfg(feature = "array-buffer")] -use crate::ecmascript::types::try_get_result_into_value; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; +#[cfg(feature = "array-buffer")] +use crate::ecmascript::types::try_get_result_into_value; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::{ diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 4dd4aceec..52e97778b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -140,7 +140,7 @@ impl TemporalInstantConstructor { let item = args.get(0).bind(gc.nogc()); // 1. Return ? ToTemporalInstant(item). let instant = to_temporal_instant(agent, item.unbind(), gc)?; - let instant = agent.heap.create(InstantRecord { + let instant = agent.heap.create(InstantHeapData { object_index: None, instant, }); @@ -345,6 +345,22 @@ fn to_temporal_instant<'gc>( /// %Temporal.Instant.Prototype% pub(crate) struct TemporalInstantPrototype; +struct TemporalInstantPrototypeGetEpochMilliseconds; +impl Builtin for TemporalInstantPrototypeGetEpochMilliseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochMilliseconds; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantPrototype::get_epoch_milliseconds); +} + +struct TemporalInstantPrototypeGetEpochNanoSeconds; +impl Builtin for TemporalInstantPrototypeGetEpochNanoSeconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochNanoSeconds; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantPrototype::get_epoch_nanoseconds); +} + impl TemporalInstantPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); @@ -353,18 +369,62 @@ impl TemporalInstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(1) + .with_property_capacity(3) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) + .with_builtin_function_property::() + .with_builtin_function_property::() .build(); } + + /// ### [8.3.3 get Temporal.Instant.prototype.epochMilliseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds) + fn get_epoch_milliseconds<'gc>( + agent: &mut Agent, + this_value: Value, + _: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + // 1. Let instant be the this value. + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Let ns be instant.[[EpochNanoseconds]]. + // 4. Let ms be floor(ℝ(ns) / 10**6). + // 5. Return 𝔽(ms). + let value = instant.inner_instant(agent).epoch_milliseconds(); + Ok(Value::from_i64(agent, value, gc.into_nogc())) + } + + /// ### [8.3.4 get Temporal.Instant.prototype.epochNanoseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds) + fn get_epoch_nanoseconds<'gc>( + agent: &mut Agent, + this_value: Value, + _: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + // 1. Let instant be the this value. + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return instant.[[EpochNanoseconds]]. + let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); + todo!() + } } -use self::data::InstantRecord; +use self::data::InstantHeapData; + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct TemporalInstant<'a>(BaseIndex<'a, InstantRecord<'static>>); +pub struct TemporalInstant<'a>(BaseIndex<'a, InstantHeapData<'static>>); + impl TemporalInstant<'_> { + pub(crate) fn inner_instant(self, agent: &Agent) -> temporal_rs::Instant { + agent[self].instant + } + //TODO pub(crate) const fn _def() -> Self { TemporalInstant(BaseIndex::from_u32_index(0)) @@ -431,7 +491,7 @@ impl<'a> InternalMethods<'a> for TemporalInstant<'a> {} // TODO: get rid of Index impls, replace with get/get_mut/get_direct/get_direct_mut functions impl Index> for Agent { - type Output = InstantRecord<'static>; + type Output = InstantHeapData<'static>; fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { &self.heap.instants[index] @@ -444,8 +504,8 @@ impl IndexMut> for Agent { } } -impl Index> for Vec> { - type Output = InstantRecord<'static>; +impl Index> for Vec> { + type Output = InstantHeapData<'static>; fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { self.get(index.get_index()) @@ -453,7 +513,7 @@ impl Index> for Vec> { } } -impl IndexMut> for Vec> { +impl IndexMut> for Vec> { fn index_mut(&mut self, index: TemporalInstant<'_>) -> &mut Self::Output { self.get_mut(index.get_index()) .expect("heap access out of bounds") @@ -498,10 +558,26 @@ impl HeapSweepWeakReference for TemporalInstant<'static> { } } -impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { - fn create(&mut self, data: InstantRecord<'a>) -> TemporalInstant<'a> { +impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { + fn create(&mut self, data: InstantHeapData<'a>) -> TemporalInstant<'a> { self.instants.push(data.unbind()); - self.alloc_counter += core::mem::size_of::>(); + self.alloc_counter += core::mem::size_of::>(); TemporalInstant(BaseIndex::last_t(&self.instants)) } } + +#[inline(always)] +fn requrire_temporal_instant_internal_slot<'a>( + agent: &mut Agent, + value: Value, + gc: NoGcScope<'a, '_>, +) -> JsResult<'a, TemporalInstant<'a>> { + match value { + Value::Instant(instant) => Ok(instant.bind(gc)), + _ => Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Object is not a Temporal Instant", + gc, + )), + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs new file mode 100644 index 000000000..97efa7aa9 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -0,0 +1,241 @@ +use crate::{ + ecmascript::{ + abstract_operations::type_conversion::to_big_int, + builders::builtin_function_builder::BuiltinFunctionBuilder, + builtins::{ + ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, + temporal::instant::{ + create_temporal_instant, data::InstantRecord, to_temporal_instant, + }, + }, + execution::{Agent, JsResult, Realm, agent::ExceptionType}, + types::{ + BUILTIN_STRING_MEMORY, BigInt, Function, IntoObject, IntoValue, Object, String, Value, + }, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, + }, + heap::{CreateHeapData, IntrinsicConstructorIndexes}, +}; + +/// Constructor function object for %Temporal.Instant%. +pub(crate) struct TemporalInstantConstructor; + +impl Builtin for TemporalInstantConstructor { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::constructor); +} +impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { + const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; +} + +struct TemporalInstantFrom; +impl Builtin for TemporalInstantFrom { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); +} + +struct TemporalInstantFromEpochMilliseconds; +impl Builtin for TemporalInstantFromEpochMilliseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); +} + +struct TemporalInstantFromEpochNanoseconds; +impl Builtin for TemporalInstantFromEpochNanoseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); +} + +struct TemporalInstantCompare; +impl Builtin for TemporalInstantCompare { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; + const LENGTH: u8 = 2; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); +} + +impl TemporalInstantConstructor { + /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) + fn constructor<'gc>( + agent: &mut Agent, + _: Value, + args: ArgumentsList, + new_target: Option, + mut gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let epoch_nanoseconds = args.get(0).bind(gc.nogc()); + let new_target = new_target.bind(gc.nogc()); + // 1. If NewTarget is undefined, throw a TypeError exception. + let Some(new_target) = new_target else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "calling a builtin Temporal.Instant constructor without new is forbidden", + gc.into_nogc(), + )); + }; + let Ok(mut new_target) = Function::try_from(new_target) else { + unreachable!() + }; + // 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds). + let epoch_nanoseconds = if let Ok(epoch_nanoseconds) = BigInt::try_from(epoch_nanoseconds) { + epoch_nanoseconds + } else { + let scoped_new_target = new_target.scope(agent, gc.nogc()); + let epoch_nanoseconds = to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // SAFETY: not shared. + new_target = unsafe { scoped_new_target.take(agent) }.bind(gc.nogc()); + epoch_nanoseconds + }; + // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + let Some(epoch_nanoseconds) = epoch_nanoseconds + .try_into_i128(agent) + .and_then(|nanoseconds| temporal_rs::Instant::try_new(nanoseconds).ok()) + else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "value out of range", + gc.into_nogc(), + )); + }; + // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget). + create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc) + .map(|instant| instant.into_value()) + } + + /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) + fn from<'gc>( + agent: &mut Agent, + _: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let item = args.get(0).bind(gc.nogc()); + // 1. Return ? ToTemporalInstant(item). + let instant = to_temporal_instant(agent, item.unbind(), gc)?; + let instant = agent.heap.create(InstantRecord { + object_index: None, + instant, + }); + Ok(instant.into_value()) + } + + /// ### [8.2.3 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds) + fn from_epoch_milliseconds<'gc>( + agent: &mut Agent, + _: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let epoch_ms = args.get(0).bind(gc.nogc()); + // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). + let epoch_ms_number = epoch_ms + .unbind() + .to_number(agent, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds). + if !epoch_ms_number.is_integer(agent) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "Can't convert number to BigInt because it isn't an integer", + gc.into_nogc(), + )); + } + // 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6). + // 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + let epoch_ns = + match temporal_rs::Instant::from_epoch_milliseconds(epoch_ms_number.into_i64(agent)) { + Ok(instant) => instant, + Err(_) => { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "epochMilliseconds value out of range", + gc.into_nogc(), + )); + } + }; + + // 5. Return ! CreateTemporalInstant(epochNanoseconds). + let instant = create_temporal_instant(agent, epoch_ns, None, gc)?; + let value = instant.into_value(); + Ok(value) + } + + ///### [8.2.4 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )] (https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds) + fn from_epoch_nanoseconds<'gc>( + agent: &mut Agent, + _: Value, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let epoch_nanoseconds = arguments.get(0).bind(gc.nogc()); + // 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds). + let epoch_nanoseconds = if let Ok(epoch_nanoseconds) = BigInt::try_from(epoch_nanoseconds) { + epoch_nanoseconds + } else { + to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()) + }; + // 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + let Some(epoch_nanoseconds) = epoch_nanoseconds + .try_into_i128(agent) + .and_then(|nanoseconds| temporal_rs::Instant::try_new(nanoseconds).ok()) + else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "epochNanoseconds", + gc.into_nogc(), + )); + }; + // 3. Return ! CreateTemporalInstant(epochNanoseconds). + let instant = create_temporal_instant(agent, epoch_nanoseconds, None, gc)?; + let value = instant.into_value(); + Ok(value) + } + + /// ### [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) + fn compare<'gc>( + agent: &mut Agent, + _: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let one = args.get(0).bind(gc.nogc()); + let two = args.get(1).bind(gc.nogc()); + let two = two.scope(agent, gc.nogc()); + // 1. Set one to ? ToTemporalInstant(one). + let one_instant = to_temporal_instant(agent, one.unbind(), gc.reborrow()).unbind()?; + // 2. Set two to ? ToTemporalInstant(two). + let two_value = two.get(agent).bind(gc.nogc()); + let two_instant = to_temporal_instant(agent, two_value.unbind(), gc.reborrow()).unbind()?; + // 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])). + Ok((one_instant.cmp(&two_instant) as i8).into()) + } + + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let instant_prototype = intrinsics.temporal_instant_prototype(); + + BuiltinFunctionBuilder::new_intrinsic_constructor::( + agent, realm, + ) + .with_property_capacity(5) + .with_prototype_property(instant_prototype.into_object()) + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .build(); + } +} From 8f49946f2cbdc590930e192adae5d6278098d9fd Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 12:43:13 +0100 Subject: [PATCH 18/42] add BigInt::from_i128, and add skeleton methods for Seb --- nova_vm/src/builtin_strings | 5 +- .../ecmascript/builtins/temporal/instant.rs | 72 ++++++++++++++++++- .../src/ecmascript/types/language/bigint.rs | 14 ++-- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index dfbbaf496..0af123abd 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -28,7 +28,7 @@ __proto__ #[cfg(feature = "math")]abs #[cfg(feature = "math")]acos #[cfg(feature = "math")]acosh -#[cfg(any(feature = "atomics", feature = "set", feature = "weak-refs"))]add +#[cfg(any(feature = "atomics", feature = "set", feature = "weak-refs", feature = "temporal"))]add AggregateError all allSettled @@ -395,6 +395,7 @@ setPrototypeOf shift #[cfg(feature = "math")]sign #[cfg(feature = "math")]sin +#[cfg(feature = "temporal")]since #[cfg(feature = "math")]sinh size slice @@ -421,6 +422,7 @@ String Iterator #[cfg(feature = "array-buffer")]subarray #[cfg(feature = "annex-b-string")]substr substring +#[cfg(feature = "temporal")]subtract #[cfg(feature = "proposal-math-sum")]sumPrecise #[cfg(feature = "annex-b-string")]sup symbol @@ -493,6 +495,7 @@ undefined unregister unscopables unshift +#[cfg(feature = "temporal")]until URIError #[cfg(feature = "date")]UTC value diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 52e97778b..bbc6606ac 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -361,6 +361,34 @@ impl Builtin for TemporalInstantPrototypeGetEpochNanoSeconds { Behaviour::Regular(TemporalInstantPrototype::get_epoch_nanoseconds); } +struct TemporalInstantPrototypeAdd; +impl Builtin for TemporalInstantPrototypeAdd { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.add; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::add); +} + +struct TemporalInstantPrototypeSubtract; +impl Builtin for TemporalInstantPrototypeSubtract { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.subtract; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::subtract); +} + +struct TemporalInstantPrototypeUntil; +impl Builtin for TemporalInstantPrototypeUntil { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.until; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::until); +} + +struct TemporalInstantPrototypeSince; +impl Builtin for TemporalInstantPrototypeSince { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.since; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::since); +} + impl TemporalInstantPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); @@ -369,11 +397,15 @@ impl TemporalInstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(3) + .with_property_capacity(7) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) .with_builtin_function_property::() .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() .build(); } @@ -410,7 +442,43 @@ impl TemporalInstantPrototype { .bind(gc.nogc()); // 3. Return instant.[[EpochNanoseconds]]. let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); - todo!() + Ok(BigInt::from_i128(agent, value).into()) + } + + fn add<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + fn subtract<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + fn until<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + fn since<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() } } diff --git a/nova_vm/src/ecmascript/types/language/bigint.rs b/nova_vm/src/ecmascript/types/language/bigint.rs index aef659428..606a7949f 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -231,13 +231,6 @@ impl<'a> BigInt<'a> { } } - pub fn try_into_i64(self, agent: &Agent) -> Result> { - match self { - BigInt::BigInt(b) => i64::try_from(&agent[b].data), - BigInt::SmallBigInt(b) => Ok(b.into_i64()), - } - } - #[inline] pub(crate) fn from_num_bigint(agent: &mut Agent, value: num_bigint::BigInt) -> Self { if let Ok(result) = SmallBigInt::try_from(&value) { @@ -247,6 +240,13 @@ impl<'a> BigInt<'a> { } } + pub fn try_into_i64(self, agent: &Agent) -> Result> { + match self { + BigInt::BigInt(b) => i64::try_from(&agent[b].data), + BigInt::SmallBigInt(b) => Ok(b.into_i64()), + } + } + pub fn try_into_i128(self, agent: &Agent) -> Option { match self { BigInt::BigInt(b) => { From 9574528dcb8914ea09077da2b5b4fcb232768251 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 15:04:23 +0100 Subject: [PATCH 19/42] add better structure to the code. introduce new modules instant_prototype.rs and instant_constructor.rs --- nova_vm/src/ecmascript/builtins/temporal.rs | 5 +- .../ecmascript/builtins/temporal/instant.rs | 552 +++--------------- .../temporal/instant/instant_constructor.rs | 3 +- .../temporal/instant/instant_prototype.rs | 156 +++++ .../ecmascript/execution/realm/intrinsics.rs | 17 +- 5 files changed, 262 insertions(+), 471 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index b833296be..26b34cbfe 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -7,7 +7,6 @@ pub mod instant; use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, - builtins::{Builtin, temporal::instant::TemporalInstantConstructor}, execution::{Agent, Realm}, types::{BUILTIN_STRING_MEMORY, IntoValue}, }, @@ -40,8 +39,8 @@ impl TemporalObject { builder .with_key(BUILTIN_STRING_MEMORY.Instant.into()) .with_value(temporal_instant_constructor.into_value()) - .with_enumerable(TemporalInstantConstructor::ENUMERABLE) - .with_configurable(TemporalInstantConstructor::CONFIGURABLE) + .with_enumerable(false) + .with_configurable(false) .build() }) .build(); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index bbc6606ac..381a0b6e7 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -5,483 +5,32 @@ use core::ops::{Index, IndexMut}; pub(crate) mod data; +pub mod instant_constructor; +pub mod instant_prototype; use crate::{ ecmascript::{ - abstract_operations::type_conversion::{PreferredType, to_big_int, to_primitive_object}, - builders::{ - builtin_function_builder::BuiltinFunctionBuilder, - ordinary_object_builder::OrdinaryObjectBuilder, - }, - builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, - ordinary::ordinary_create_from_constructor, - }, + abstract_operations::type_conversion::{PreferredType, to_primitive_object}, + builtins::ordinary::ordinary_create_from_constructor, execution::{ - JsResult, ProtoIntrinsics, Realm, + JsResult, ProtoIntrinsics, agent::{Agent, ExceptionType}, }, types::{ - BUILTIN_STRING_MEMORY, BigInt, Function, InternalMethods, InternalSlots, IntoFunction, - IntoObject, IntoValue, Object, OrdinaryObject, Primitive, String, Value, + Function, InternalMethods, InternalSlots, IntoFunction, Object, OrdinaryObject, + Primitive, String, Value, }, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, - rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, + rootable::{HeapRootData, HeapRootRef, Rootable}, }, heap::{ CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, + WorkQueues, indexes::BaseIndex, }, }; -/// Constructor function object for %Temporal.Instant%. -pub(crate) struct TemporalInstantConstructor; - -impl Builtin for TemporalInstantConstructor { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::constructor); -} - -impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { - const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; -} - -struct TemporalInstantFrom; -impl Builtin for TemporalInstantFrom { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); -} - -struct TemporalInstantFromEpochMilliseconds; -impl Builtin for TemporalInstantFromEpochMilliseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); -} - -struct TemporalInstantFromEpochNanoseconds; -impl Builtin for TemporalInstantFromEpochNanoseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); -} - -struct TemporalInstantCompare; -impl Builtin for TemporalInstantCompare { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; - const LENGTH: u8 = 2; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); -} - -impl TemporalInstantConstructor { - /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) - fn constructor<'gc>( - agent: &mut Agent, - _: Value, - args: ArgumentsList, - new_target: Option, - mut gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - let epoch_nanoseconds = args.get(0).bind(gc.nogc()); - let new_target = new_target.bind(gc.nogc()); - // 1. If NewTarget is undefined, throw a TypeError exception. - let Some(new_target) = new_target else { - return Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, - "calling a builtin Temporal.Instant constructor without new is forbidden", - gc.into_nogc(), - )); - }; - let Ok(mut new_target) = Function::try_from(new_target) else { - unreachable!() - }; - // 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds). - let epoch_nanoseconds = if let Ok(epoch_nanoseconds) = BigInt::try_from(epoch_nanoseconds) { - epoch_nanoseconds - } else { - let scoped_new_target = new_target.scope(agent, gc.nogc()); - let epoch_nanoseconds = to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - // SAFETY: not shared. - new_target = unsafe { scoped_new_target.take(agent) }.bind(gc.nogc()); - epoch_nanoseconds - }; - - // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. - let Some(epoch_nanoseconds) = epoch_nanoseconds - .try_into_i128(agent) - .and_then(|nanoseconds| temporal_rs::Instant::try_new(nanoseconds).ok()) - else { - return Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, - "value out of range", - gc.into_nogc(), - )); - }; - // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget). - create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc) - .map(|instant| instant.into_value()) - } - - /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) - fn from<'gc>( - agent: &mut Agent, - _: Value, - args: ArgumentsList, - gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - let item = args.get(0).bind(gc.nogc()); - // 1. Return ? ToTemporalInstant(item). - let instant = to_temporal_instant(agent, item.unbind(), gc)?; - let instant = agent.heap.create(InstantHeapData { - object_index: None, - instant, - }); - Ok(instant.into_value()) - } - - /// ### [8.2.3 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds) - fn from_epoch_milliseconds<'gc>( - agent: &mut Agent, - _: Value, - args: ArgumentsList, - mut gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - let epoch_ms = args.get(0).bind(gc.nogc()); - // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). - let epoch_ms_number = epoch_ms - .unbind() - .to_number(agent, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - // 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds). - if !epoch_ms_number.is_integer(agent) { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "Can't convert number to BigInt because it isn't an integer", - gc.into_nogc(), - )); - } - // 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6). - // 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. - let epoch_ns = - match temporal_rs::Instant::from_epoch_milliseconds(epoch_ms_number.into_i64(agent)) { - Ok(instant) => instant, - Err(_) => { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "epochMilliseconds value out of range", - gc.into_nogc(), - )); - } - }; - - // 5. Return ! CreateTemporalInstant(epochNanoseconds). - let instant = create_temporal_instant(agent, epoch_ns, None, gc)?; - let value = instant.into_value(); - Ok(value) - } - - /// [8.2.4 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )] (https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds) - fn from_epoch_nanoseconds<'gc>( - agent: &mut Agent, - _: Value, - arguments: ArgumentsList, - mut gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - let epoch_nanoseconds = arguments.get(0).bind(gc.nogc()); - // 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds). - let epoch_nanoseconds = if let Ok(epoch_nanoseconds) = BigInt::try_from(epoch_nanoseconds) { - epoch_nanoseconds - } else { - let epoch_nanoseconds = to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - epoch_nanoseconds - }; - // 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. - let Some(epoch_nanoseconds) = epoch_nanoseconds - .try_into_i128(agent) - .and_then(|nanoseconds| temporal_rs::Instant::try_new(nanoseconds).ok()) - else { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "epochNanoseconds", - gc.into_nogc(), - )); - }; - // 3. Return ! CreateTemporalInstant(epochNanoseconds). - let instant = create_temporal_instant(agent, epoch_nanoseconds, None, gc)?; - let value = instant.into_value(); - Ok(value) - } - - /// ### [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) - fn compare<'gc>( - agent: &mut Agent, - _: Value, - args: ArgumentsList, - mut gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - let one = args.get(0).bind(gc.nogc()); - let two = args.get(0).bind(gc.nogc()); - let two = two.scope(agent, gc.nogc()); - // 1. Set one to ? ToTemporalInstant(one). - let one_instant = to_temporal_instant(agent, one.unbind(), gc.reborrow()).unbind()?; - // 2. Set two to ? ToTemporalInstant(two). - let two_value = two.get(agent).bind(gc.nogc()); - let two_instant = to_temporal_instant(agent, two_value.unbind(), gc.reborrow()).unbind()?; - // 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])). - Ok((one_instant.cmp(&two_instant) as i8).into()) - } - - pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { - let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); - let instant_prototype = intrinsics.temporal_instant_prototype(); - - BuiltinFunctionBuilder::new_intrinsic_constructor::( - agent, realm, - ) - .with_property_capacity(5) - .with_prototype_property(instant_prototype.into_object()) - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .build(); - } -} - -/// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) -/// -/// The abstract operation CreateTemporalInstant takes argument -/// epochNanoseconds (a BigInt) and optional argument newTarget (a constructor) -/// and returns either a normal completion containing a Temporal.Instant or a -/// throw completion. It creates a Temporal.Instant instance and fills the -/// internal slots with valid values. -fn create_temporal_instant<'gc>( - agent: &mut Agent, - epoch_nanoseconds: temporal_rs::Instant, - new_target: Option, - gc: GcScope<'gc, '_>, -) -> JsResult<'gc, TemporalInstant<'gc>> { - // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true. - // 2. If newTarget is not present, set newTarget to %Temporal.Instant%. - let new_target = new_target.unwrap_or_else(|| { - agent - .current_realm_record() - .intrinsics() - .temporal_instant() - .into_function() - }); - // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »). - let Object::Instant(object) = - ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? - else { - unreachable!() - }; - // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. - // SAFETY: initialising Instant. - unsafe { object.set_epoch_nanoseconds(agent, epoch_nanoseconds) }; - // 5. Return object. - Ok(object) -} - -/// ### [8.5.3 ToTemporalInstant ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant) -/// -/// The abstract operation ToTemporalInstant takes argument item (an ECMAScript language value) and -/// returns either a normal completion containing a Temporal.Instant or a throw completion. -/// Converts item to a new Temporal.Instant instance if possible, and throws otherwise. It performs -/// the following steps when called: -fn to_temporal_instant<'gc>( - agent: &mut Agent, - item: Value, - gc: GcScope<'gc, '_>, -) -> JsResult<'gc, temporal_rs::Instant> { - let item = item.bind(gc.nogc()); - // 1. If item is an Object, then - let item = if let Ok(item) = Object::try_from(item) { - // a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] - // internal slot, then TODO: TemporalZonedDateTime::try_from(item) - if let Ok(item) = TemporalInstant::try_from(item) { - // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]). - return Ok(agent[item].instant); - } - // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted. - // c. Set item to ? ToPrimitive(item, string). - to_primitive_object(agent, item.unbind(), Some(PreferredType::String), gc)? - } else { - Primitive::try_from(item).unwrap() - }; - // 2. If item is not a String, throw a TypeError exception. - let Ok(item) = String::try_from(item) else { - todo!() // TypeErrror - }; - // 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »). - // 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or - // parsed.[[TimeZone]].[[Z]] is true, but not both. - // 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let - // offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]). - // 6. If parsed.[[Time]] is start-of-day, let time be MidnightTimeRecord(); else let time be - // parsed.[[Time]]. - // 7. Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], - // time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], - // time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds). - // 8. Perform ? CheckISODaysRange(balanced.[[ISODate]]). - // 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced). - // 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. - // 11. Return ! CreateTemporalInstant(epochNanoseconds). - let parsed = temporal_rs::Instant::from_utf8(item.as_bytes(agent)).unwrap(); - Ok(parsed) -} - -/// %Temporal.Instant.Prototype% -pub(crate) struct TemporalInstantPrototype; - -struct TemporalInstantPrototypeGetEpochMilliseconds; -impl Builtin for TemporalInstantPrototypeGetEpochMilliseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochMilliseconds; - const LENGTH: u8 = 0; - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantPrototype::get_epoch_milliseconds); -} - -struct TemporalInstantPrototypeGetEpochNanoSeconds; -impl Builtin for TemporalInstantPrototypeGetEpochNanoSeconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochNanoSeconds; - const LENGTH: u8 = 0; - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantPrototype::get_epoch_nanoseconds); -} - -struct TemporalInstantPrototypeAdd; -impl Builtin for TemporalInstantPrototypeAdd { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.add; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::add); -} - -struct TemporalInstantPrototypeSubtract; -impl Builtin for TemporalInstantPrototypeSubtract { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.subtract; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::subtract); -} - -struct TemporalInstantPrototypeUntil; -impl Builtin for TemporalInstantPrototypeUntil { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.until; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::until); -} - -struct TemporalInstantPrototypeSince; -impl Builtin for TemporalInstantPrototypeSince { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.since; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::since); -} - -impl TemporalInstantPrototype { - pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { - let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); - let this = intrinsics.temporal_instant_prototype(); - let object_prototype = intrinsics.object_prototype(); - let instant_constructor = intrinsics.temporal_instant(); - - OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(7) - .with_prototype(object_prototype) - .with_constructor_property(instant_constructor) - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .build(); - } - - /// ### [8.3.3 get Temporal.Instant.prototype.epochMilliseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds) - fn get_epoch_milliseconds<'gc>( - agent: &mut Agent, - this_value: Value, - _: ArgumentsList, - gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - // 1. Let instant be the this value. - // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) - .unbind()? - .bind(gc.nogc()); - // 3. Let ns be instant.[[EpochNanoseconds]]. - // 4. Let ms be floor(ℝ(ns) / 10**6). - // 5. Return 𝔽(ms). - let value = instant.inner_instant(agent).epoch_milliseconds(); - Ok(Value::from_i64(agent, value, gc.into_nogc())) - } - - /// ### [8.3.4 get Temporal.Instant.prototype.epochNanoseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds) - fn get_epoch_nanoseconds<'gc>( - agent: &mut Agent, - this_value: Value, - _: ArgumentsList, - gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - // 1. Let instant be the this value. - // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) - .unbind()? - .bind(gc.nogc()); - // 3. Return instant.[[EpochNanoseconds]]. - let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); - Ok(BigInt::from_i128(agent, value).into()) - } - - fn add<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() - } - - fn subtract<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() - } - - fn until<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() - } - - fn since<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, - ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() - } -} - use self::data::InstantHeapData; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -634,6 +183,89 @@ impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { } } +/// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) +/// +/// The abstract operation CreateTemporalInstant takes argument +/// epochNanoseconds (a BigInt) and optional argument newTarget (a constructor) +/// and returns either a normal completion containing a Temporal.Instant or a +/// throw completion. It creates a Temporal.Instant instance and fills the +/// internal slots with valid values. +fn create_temporal_instant<'gc>( + agent: &mut Agent, + epoch_nanoseconds: temporal_rs::Instant, + new_target: Option, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, TemporalInstant<'gc>> { + // 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true. + // 2. If newTarget is not present, set newTarget to %Temporal.Instant%. + let new_target = new_target.unwrap_or_else(|| { + agent + .current_realm_record() + .intrinsics() + .temporal_instant() + .into_function() + }); + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »). + let Object::Instant(object) = + ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? + else { + unreachable!() + }; + // 4. Set object.[[EpochNanoseconds]] to epochNanoseconds. + // SAFETY: initialising Instant. + unsafe { object.set_epoch_nanoseconds(agent, epoch_nanoseconds) }; + // 5. Return object. + Ok(object) +} + +/// ### [8.5.3 ToTemporalInstant ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant) +/// +/// The abstract operation ToTemporalInstant takes argument item (an ECMAScript language value) and +/// returns either a normal completion containing a Temporal.Instant or a throw completion. +/// Converts item to a new Temporal.Instant instance if possible, and throws otherwise. It performs +/// the following steps when called: +fn to_temporal_instant<'gc>( + agent: &mut Agent, + item: Value, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, temporal_rs::Instant> { + let item = item.bind(gc.nogc()); + // 1. If item is an Object, then + let item = if let Ok(item) = Object::try_from(item) { + // a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] + // internal slot, then TODO: TemporalZonedDateTime::try_from(item) + if let Ok(item) = TemporalInstant::try_from(item) { + // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]). + return Ok(agent[item].instant); + } + // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted. + // c. Set item to ? ToPrimitive(item, string). + to_primitive_object(agent, item.unbind(), Some(PreferredType::String), gc)? + } else { + Primitive::try_from(item).unwrap() + }; + // 2. If item is not a String, throw a TypeError exception. + let Ok(item) = String::try_from(item) else { + todo!() // TypeErrror + }; + // 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »). + // 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or + // parsed.[[TimeZone]].[[Z]] is true, but not both. + // 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let + // offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]). + // 6. If parsed.[[Time]] is start-of-day, let time be MidnightTimeRecord(); else let time be + // parsed.[[Time]]. + // 7. Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], + // time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], + // time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds). + // 8. Perform ? CheckISODaysRange(balanced.[[ISODate]]). + // 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced). + // 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. + // 11. Return ! CreateTemporalInstant(epochNanoseconds). + let parsed = temporal_rs::Instant::from_utf8(item.as_bytes(agent)).unwrap(); + Ok(parsed) +} + #[inline(always)] fn requrire_temporal_instant_internal_slot<'a>( agent: &mut Agent, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs index 97efa7aa9..ed23293f9 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -96,6 +96,7 @@ impl TemporalInstantConstructor { new_target = unsafe { scoped_new_target.take(agent) }.bind(gc.nogc()); epoch_nanoseconds }; + // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. let Some(epoch_nanoseconds) = epoch_nanoseconds .try_into_i128(agent) @@ -171,7 +172,7 @@ impl TemporalInstantConstructor { Ok(value) } - ///### [8.2.4 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )] (https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds) + /// ### [8.2.4 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )] (https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds) fn from_epoch_nanoseconds<'gc>( agent: &mut Agent, _: Value, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs new file mode 100644 index 000000000..d1d2328e3 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -0,0 +1,156 @@ +use crate::{ + ecmascript::{ + builders::ordinary_object_builder::OrdinaryObjectBuilder, + builtins::{ + ArgumentsList, Behaviour, Builtin, + temporal::instant::requrire_temporal_instant_internal_slot, + }, + execution::{Agent, JsResult, Realm}, + types::{BUILTIN_STRING_MEMORY, BigInt, String, Value}, + }, + engine::context::{Bindable, GcScope, NoGcScope}, +}; + +/// %Temporal.Instant.Prototype% +pub(crate) struct TemporalInstantPrototype; + +struct TemporalInstantPrototypeGetEpochMilliseconds; +impl Builtin for TemporalInstantPrototypeGetEpochMilliseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochMilliseconds; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantPrototype::get_epoch_milliseconds); +} + +struct TemporalInstantPrototypeGetEpochNanoSeconds; +impl Builtin for TemporalInstantPrototypeGetEpochNanoSeconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochNanoSeconds; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantPrototype::get_epoch_nanoseconds); +} + +struct TemporalInstantPrototypeAdd; +impl Builtin for TemporalInstantPrototypeAdd { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.add; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::add); +} + +struct TemporalInstantPrototypeSubtract; +impl Builtin for TemporalInstantPrototypeSubtract { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.subtract; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::subtract); +} + +struct TemporalInstantPrototypeUntil; +impl Builtin for TemporalInstantPrototypeUntil { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.until; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::until); +} + +struct TemporalInstantPrototypeSince; +impl Builtin for TemporalInstantPrototypeSince { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.since; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::since); +} + +impl TemporalInstantPrototype { + pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let this = intrinsics.temporal_instant_prototype(); + let object_prototype = intrinsics.object_prototype(); + let instant_constructor = intrinsics.temporal_instant(); + + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(7) + .with_prototype(object_prototype) + .with_constructor_property(instant_constructor) + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .build(); + } + + /// ### [8.3.3 get Temporal.Instant.prototype.epochMilliseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds) + fn get_epoch_milliseconds<'gc>( + agent: &mut Agent, + this_value: Value, + _: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + // 1. Let instant be the this value. + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Let ns be instant.[[EpochNanoseconds]]. + // 4. Let ms be floor(ℝ(ns) / 10**6). + // 5. Return 𝔽(ms). + let value = instant.inner_instant(agent).epoch_milliseconds(); + Ok(Value::from_i64(agent, value, gc.into_nogc())) + } + + /// ### [8.3.4 get Temporal.Instant.prototype.epochNanoseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds) + fn get_epoch_nanoseconds<'gc>( + agent: &mut Agent, + this_value: Value, + _: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + // 1. Let instant be the this value. + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return instant.[[EpochNanoseconds]]. + let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); + Ok(BigInt::from_i128(agent, value).into()) + } + + /// ### [8.3.5 Temporal.Instant.prototype.add ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add) + fn add<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ### [8.3.6 Temporal.Instant.prototype.subtract ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract) + fn subtract<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ### [8.3.7 Temporal.Instant.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) + fn until<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ### [Temporal.Instant.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) + fn since<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } +} diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index b56aed4b0..7a84a5671 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,11 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - TemporalObject, instant::TemporalInstantConstructor, instant::TemporalInstantPrototype, + TemporalObject, + instant::{ + instant_constructor::TemporalInstantConstructor, + instant_prototype::TemporalInstantPrototype, + }, }; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ @@ -315,12 +319,11 @@ impl Intrinsics { MathObject::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] - { - TemporalObject::create_intrinsic(agent, realm, gc); - // Instant - TemporalInstantConstructor::create_intrinsic(agent, realm, gc); - TemporalInstantPrototype::create_intrinsic(agent, realm, gc); - } + TemporalObject::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalInstantConstructor::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalInstantPrototype::create_intrinsic(agent, realm, gc); #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); From c14f71f4228fd5ac3b47382882b6d4ac975e6b4d Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:05:27 +0100 Subject: [PATCH 20/42] add skeleton for the rest of Temporal.Instant implementation --- nova_vm/src/builtin_strings | 5 +- .../temporal/instant/instant_prototype.rs | 175 ++++++++++++++++-- 2 files changed, 159 insertions(+), 21 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 0af123abd..a83278c39 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -114,6 +114,7 @@ endsWith entries enumerable EPSILON +#[cfg(feature = "temporal")]equals Error errors #[cfg(any(feature = "annex-b-string", feature = "regexp"))]escape @@ -357,7 +358,7 @@ resolve return reverse revocable -#[cfg(feature = "math")]round +#[cfg(any(feature = "math", feature = "temporal"))]round seal #[cfg(feature = "regexp")]search set @@ -443,6 +444,7 @@ Symbol.toStringTag Symbol.unscopables SyntaxError #[cfg(feature = "temporal")]Temporal +#[cfg(feature = "temporal")]Temporal.Instant #[cfg(feature = "math")]tan #[cfg(feature = "math")]tanh #[cfg(feature = "regexp")]test @@ -471,6 +473,7 @@ toStringTag #[cfg(feature = "date")]toTimeString toUpperCase #[cfg(feature = "date")]toUTCString +#[cfg(feature = "temporal")]toZonedDateTimeISO toWellFormed #[cfg(feature = "array-buffer")]transfer #[cfg(feature = "array-buffer")]transferToFixedLength diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index d1d2328e3..00f79cd28 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -9,9 +9,9 @@ use crate::{ types::{BUILTIN_STRING_MEMORY, BigInt, String, Value}, }, engine::context::{Bindable, GcScope, NoGcScope}, + heap::WellKnownSymbolIndexes, }; -/// %Temporal.Instant.Prototype% pub(crate) struct TemporalInstantPrototype; struct TemporalInstantPrototypeGetEpochMilliseconds; @@ -58,26 +58,57 @@ impl Builtin for TemporalInstantPrototypeSince { const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::since); } -impl TemporalInstantPrototype { - pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { - let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); - let this = intrinsics.temporal_instant_prototype(); - let object_prototype = intrinsics.object_prototype(); - let instant_constructor = intrinsics.temporal_instant(); +struct TemporalInstantPrototypeRound; +impl Builtin for TemporalInstantPrototypeRound { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.round; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::round); +} - OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(7) - .with_prototype(object_prototype) - .with_constructor_property(instant_constructor) - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .build(); - } +struct TemporalInstantPrototypeEquals; +impl Builtin for TemporalInstantPrototypeEquals { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.equals; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::equals); +} + +struct TemporalInstantPrototypeToString; +impl Builtin for TemporalInstantPrototypeToString { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.toString; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::to_string); +} + +struct TemporalInstantPrototypeToLocaleString; +impl Builtin for TemporalInstantPrototypeToLocaleString { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.toLocaleString; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::to_locale_string); +} + +struct TemporalInstantPrototypeToJSON; +impl Builtin for TemporalInstantPrototypeToJSON { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.toJSON; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::to_json); +} + +struct TemporalInstantPrototypeValueOf; +impl Builtin for TemporalInstantPrototypeValueOf { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.valueOf; + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::value_of); +} + +struct TemporalInstantPrototypeToZonedDateTimeISO; +impl Builtin for TemporalInstantPrototypeToZonedDateTimeISO { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.toZonedDateTimeISO; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantPrototype::to_zoned_date_time_iso); +} +impl TemporalInstantPrototype { /// ### [8.3.3 get Temporal.Instant.prototype.epochMilliseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds) fn get_epoch_milliseconds<'gc>( agent: &mut Agent, @@ -144,7 +175,7 @@ impl TemporalInstantPrototype { unimplemented!() } - /// ### [Temporal.Instant.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) + /// ### [8.3.8 Temporal.Instant.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) fn since<'gc>( _agent: &mut Agent, _this_value: Value, @@ -153,4 +184,108 @@ impl TemporalInstantPrototype { ) -> JsResult<'gc, Value<'gc>> { unimplemented!() } + + /// ### [8.3.9 Temporal.Instant.prototype.round ( roundTo )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round) + fn round<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ### [8.3.10 Temporal.Instant.prototype.equals ( other )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals) + fn equals<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ### [8.3.11 Temporal.Instant.prototype.toString ( [ options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring) + fn to_string<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ### [8.3.12 Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring) + fn to_locale_string<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ###[8.3.13 Temporal.Instant.prototype.toJSON ( )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tojson) + fn to_json<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + /// ###[8.3.14 Temporal.Instant.prototype.valueOf ( )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof) + fn value_of<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + // [8.3.15 Temporal.Instant.prototype.toZonedDateTimeISO ( timeZone )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso) + fn to_zoned_date_time_iso<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let this = intrinsics.temporal_instant_prototype(); + let object_prototype = intrinsics.object_prototype(); + let instant_constructor = intrinsics.temporal_instant(); + + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(15) + .with_prototype(object_prototype) + .with_constructor_property(instant_constructor) + .with_property(|builder| { + builder + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal_Instant.into()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .build(); + } } From 6f0114b8719e2d9c92c1666e014d2136fa45cbec Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:21:45 +0100 Subject: [PATCH 21/42] implementation of 8.3.14 Temporal.Instant.prototype.valueOf --- .../temporal/instant/instant_prototype.rs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 00f79cd28..c58a91105 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -3,9 +3,9 @@ use crate::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - temporal::instant::requrire_temporal_instant_internal_slot, + temporal::instant::{requrire_temporal_instant_internal_slot, to_temporal_instant}, }, - execution::{Agent, JsResult, Realm}, + execution::{Agent, JsResult, Realm, agent::ExceptionType}, types::{BUILTIN_STRING_MEMORY, BigInt, String, Value}, }, engine::context::{Bindable, GcScope, NoGcScope}, @@ -237,12 +237,26 @@ impl TemporalInstantPrototype { /// ###[8.3.14 Temporal.Instant.prototype.valueOf ( )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof) fn value_of<'gc>( - _agent: &mut Agent, + agent: &mut Agent, _this_value: Value, _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + // 1. Throw a TypeError exception. + // + // Note: + // This method always throws, because in the absence of valueOf(), expressions with + // arithmetic operators such as instant1 > instant2 would fall back to being equivalent + // to instant1.toString() > instant2.toString(). Lexicographical comparison of + // serialized strings might not seem obviously wrong, because the result would + // sometimes be correct. Implementations are encouraged to phrase the error message to + // point users to Temporal.Instant.compare(), Temporal.Instant.prototype.equals(), + // and/or Temporal.Instant.prototype.toString(). + Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`", + gc.into_nogc(), + )) } // [8.3.15 Temporal.Instant.prototype.toZonedDateTimeISO ( timeZone )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso) From 5f0cd73465d90523ce0bbaee31fc9b0bea3b5492 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:23:23 +0100 Subject: [PATCH 22/42] cargo fmt and cargo clippy --- .../ecmascript/builtins/temporal/instant/instant_prototype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index c58a91105..fc158c4ba 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -3,7 +3,7 @@ use crate::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - temporal::instant::{requrire_temporal_instant_internal_slot, to_temporal_instant}, + temporal::instant::requrire_temporal_instant_internal_slot, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, types::{BUILTIN_STRING_MEMORY, BigInt, String, Value}, From 82b9a740e95acfe52d9853a7b04465205e082e42 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:26:15 +0100 Subject: [PATCH 23/42] ignore unused parameters --- .../ecmascript/builtins/temporal/instant/instant_prototype.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index fc158c4ba..78b7be9cf 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -238,8 +238,8 @@ impl TemporalInstantPrototype { /// ###[8.3.14 Temporal.Instant.prototype.valueOf ( )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof) fn value_of<'gc>( agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, + _: Value, + _: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { // 1. Throw a TypeError exception. From 09274b6e832c2e9ad42f015434b7c43761f73a30 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:59:09 +0100 Subject: [PATCH 24/42] implementation of 8.3.10 Temporal.Instant.prototype.equals --- .../ecmascript/builtins/temporal/instant.rs | 2 +- .../temporal/instant/instant_prototype.rs | 36 ++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 381a0b6e7..d87e8ed92 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -267,7 +267,7 @@ fn to_temporal_instant<'gc>( } #[inline(always)] -fn requrire_temporal_instant_internal_slot<'a>( +fn require_internal_slot_temporal_instant<'a>( agent: &mut Agent, value: Value, gc: NoGcScope<'a, '_>, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 78b7be9cf..9e320f68b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -3,12 +3,15 @@ use crate::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - temporal::instant::requrire_temporal_instant_internal_slot, + temporal::instant::{require_internal_slot_temporal_instant, to_temporal_instant}, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, types::{BUILTIN_STRING_MEMORY, BigInt, String, Value}, }, - engine::context::{Bindable, GcScope, NoGcScope}, + engine::{ + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, + }, heap::WellKnownSymbolIndexes, }; @@ -118,7 +121,7 @@ impl TemporalInstantPrototype { ) -> JsResult<'gc, Value<'gc>> { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) + let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) .unbind()? .bind(gc.nogc()); // 3. Let ns be instant.[[EpochNanoseconds]]. @@ -137,7 +140,7 @@ impl TemporalInstantPrototype { ) -> JsResult<'gc, Value<'gc>> { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = requrire_temporal_instant_internal_slot(agent, this_value, gc.nogc()) + let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) .unbind()? .bind(gc.nogc()); // 3. Return instant.[[EpochNanoseconds]]. @@ -197,12 +200,27 @@ impl TemporalInstantPrototype { /// ### [8.3.10 Temporal.Instant.prototype.equals ( other )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals) fn equals<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + // 1. Let instant be the this value. + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + let instant = instant.scope(agent, gc.nogc()); + // 3. Set other to ? ToTemporalInstant(other). + let other = args.get(0).bind(gc.nogc()); + let other_instant = to_temporal_instant(agent, other.unbind(), gc.reborrow()).unbind()?; + // 4. If instant.[[EpochNanoseconds]] ≠ other.[[EpochNanoseconds]], return false. + let instant_val = instant.get(agent).bind(gc.nogc()); + if instant_val.inner_instant(agent) != other_instant { + return Ok(Value::from(false)); + } + // 5. Return true. + Ok(Value::from(true)) } /// ### [8.3.11 Temporal.Instant.prototype.toString ( [ options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring) From f18bdda5997327a1850a3e2537983a7f1be833e7 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 17:42:59 +0100 Subject: [PATCH 25/42] immediatly scope instant after require internal slot in Instant.prototype.equals --- .../ecmascript/builtins/temporal/instant/instant_prototype.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 9e320f68b..b07dde168 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -209,8 +209,7 @@ impl TemporalInstantPrototype { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) .unbind()? - .bind(gc.nogc()); - let instant = instant.scope(agent, gc.nogc()); + .scope(agent, gc.nogc()); // 3. Set other to ? ToTemporalInstant(other). let other = args.get(0).bind(gc.nogc()); let other_instant = to_temporal_instant(agent, other.unbind(), gc.reborrow()).unbind()?; From fa28d437ba3f42b49b307ad79313ec2be1b5d70d Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Wed, 5 Nov 2025 17:07:25 +0100 Subject: [PATCH 26/42] added instant.proto.add/subtract along with %duration% stubs --- nova_vm/src/ecmascript/builtins/temporal.rs | 1 + .../ecmascript/builtins/temporal/duration.rs | 211 ++++++++++++++++++ .../builtins/temporal/duration/data.rs | 44 ++++ .../ecmascript/builtins/temporal/instant.rs | 42 +++- .../temporal/instant/instant_prototype.rs | 46 +++- 5 files changed, 329 insertions(+), 15 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/duration.rs create mode 100644 nova_vm/src/ecmascript/builtins/temporal/duration/data.rs diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 26b34cbfe..b5cd5a18c 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -3,6 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. pub mod instant; +pub mod duration; use crate::{ ecmascript::{ diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs new file mode 100644 index 000000000..62e459ab9 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -0,0 +1,211 @@ +use temporal_rs::partial::PartialDuration; + +use crate::{ecmascript::{builtins::temporal::instant::TemporalInstant, execution::{Agent, JsResult, agent::ExceptionType}, types::{InternalMethods, Object, Value}}, engine::context::{Bindable, GcScope, NoGcScope, bindable_handle}, heap::indexes::BaseIndex}; +use core::ops::{Index, IndexMut}; + +pub(crate) mod data; +use self::data::DurationHeapData; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct TemporalDuration<'a>(BaseIndex<'a, DurationHeapData<'static>>); + +impl TemporalDuration<'_> { + pub(crate) const fn _def() -> Self { + TemporalDuration(BaseIndex::from_u32_index(0)) + } + pub(crate) const fn get_index(self) -> usize { + self.0.into_index() + } + pub(crate) fn inner_duration(self, agent: &Agent) -> temporal_rs::Duration { + agent[self].duration + } +} + +bindable_handle!(TemporalDuration); + + +impl<'a> From> for Value<'a> { + fn from(value: TemporalDuration<'a>) -> Self { + todo!() + //Value::Duration(value) + } +} +impl<'a> From> for Object<'a> { + fn from(value: TemporalDuration<'a>) -> Self { + todo!() + //Object::Duration(value) + } +} +impl<'a> TryFrom> for TemporalDuration<'a> { + type Error = (); + + fn try_from(value: Value<'a>) -> Result { + todo!() + // match value { + // Value::Duration(idx) => Ok(idx), + // _ => Err(()), + // } + } +} + +impl Index> for Agent { + type Output = DurationHeapData<'static>; + + fn index(&self, _index: TemporalDuration<'_>) -> &Self::Output { + unimplemented!() + //&self.heap.durations[index] + } +} + +impl IndexMut> for Agent { + fn index_mut(&mut self, _index: TemporalDuration) -> &mut Self::Output { + unimplemented!() + //&mut self.heap.durations[index] + } +} + +impl Index> for Vec> { + type Output = DurationHeapData<'static>; + + fn index(&self, index: TemporalDuration<'_>) -> &Self::Output { + self.get(index.get_index()) + .expect("heap access out of bounds") + } +} + +impl IndexMut> for Vec> { + fn index_mut(&mut self, index: TemporalDuration<'_>) -> &mut Self::Output { + self.get_mut(index.get_index()) + .expect("heap access out of bounds") + } +} +/// 7.5.19 CreateTemporalDuration ( years, months, weeks, +/// days, hours, minutes, seconds, +/// milliseconds, microseconds, nanoseconds [ , newTarget ] ) +/// The abstract operation CreateTemporalDuration takes arguments +/// years (an integer), months (an integer), +/// weeks (an integer), days (an integer), +/// hours (an integer), minutes (an integer), +/// seconds (an integer), milliseconds (an integer), +/// microseconds (an integer), and nanoseconds (an integer) +/// and optional argument newTarget (a constructor) +/// and returns either a normal completion containing +/// a Temporal.Duration or a throw completion. +/// It creates a Temporal.Duration instance and fills +/// the internal slots with valid values. +/// It performs the following steps when called: +fn create_temporal_duration<'gc> ( + // years, + // months, + // weeks, + // days, + // hours, + // minutes, + // seconds, + // milliseconds, + // microseconds, + // nanoseconds: , + // new_target: Option, +) -> JsResult<'gc, TemporalDuration<'gc>> { + // 1. If IsValidDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) is false, throw a RangeError exception. + // 2. If newTarget is not present, set newTarget to %Temporal.Duration%. + // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »). + // 4. Set object.[[Years]] to ℝ(𝔽(years)). + // 5. Set object.[[Months]] to ℝ(𝔽(months)). + // 6. Set object.[[Weeks]] to ℝ(𝔽(weeks)). + // 7. Set object.[[Days]] to ℝ(𝔽(days)). + // 8. Set object.[[Hours]] to ℝ(𝔽(hours)). + // 9. Set object.[[Minutes]] to ℝ(𝔽(minutes)). + // 10. Set object.[[Seconds]] to ℝ(𝔽(seconds)). + // 11. Set object.[[Milliseconds]] to ℝ(𝔽(milliseconds)). + // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)). + // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)). + // 14. Return object. + unimplemented!() +} + + +/// Abstract Operations <---> + +/// [7.5.12 ToTemporalDuration ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration) +/// The abstract operation ToTemporalDuration takes argument item +/// (an ECMAScript language value) and returns either a normal completion containing a +/// Temporal.Duration or a throw completion. Converts item to a new Temporal.Duration +/// instance if possible and returns that, and throws otherwise. +/// It performs the following steps when called: +pub(crate) fn to_temporal_duration<'gc> ( + agent: &mut Agent, + item: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, temporal_rs::Duration> { + let item = item.bind(gc.nogc()); + // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then + + if let Ok(obj) = require_internal_slot_temporal_duration(agent, item, gc.nogc()) { + // `require_internal_slot_temporal_duration` already guarantees this is a Duration object, + let obj = Object::try_from(obj); + unimplemented!(); + // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]). + } + // 2. If item is not an Object, then + if let Ok(item) = item.unbind().to_string(agent, gc.reborrow()){ + // b. Return ? ParseTemporalDurationString(item). + let parsed = temporal_rs::Duration::from_utf8(item.as_bytes(agent)).unwrap(); + return Ok(parsed) + } else { + // a. If item is not a String, throw a TypeError exception. + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "item is not a string", + gc.into_nogc(), + )); + } + // 3. Let result be a new Partial Duration Record with each field set to 0. + // 4. Let partial be ? ToTemporalPartialDurationRecord(item). + // 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]]. + // 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]]. + // 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]]. + // 8. If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]]. + // 9. If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]]. + // 10. If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]]. + // 11. If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]]. + // 12. If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]]. + // 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]]. + // 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]]. + // 15. Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). + unimplemented!() +} + +/// [7.5.20 CreateNegatedTemporalDuration ( duration )] (https://tc39.es/proposal-temporal/#sec-temporal-createnegatedtemporalduration) +/// The abstract operation CreateNegatedTemporalDuration takes argument +/// duration (a Temporal.Duration) and returns a Temporal.Duration. +/// It returns a new Temporal.Duration instance that is the +/// negation of duration. +pub(crate) fn create_negated_temporal_duration<'gc> ( + agent: &mut Agent, + item: temporal_rs::Duration, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, temporal_rs::Duration> { + // 1. Return ! CreateTemporalDuration(-duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]). + let duration = temporal_rs::Duration::negated(&item); + //TODO: IMPL create_temporal_duration() + unimplemented!() +} + +#[inline(always)] +fn require_internal_slot_temporal_duration<'a>( + agent: &mut Agent, + value: Value, + gc: NoGcScope<'a, '_>, +) -> JsResult<'a, TemporalDuration<'a>> { + unimplemented!() + // TODO: + // match value { + // Value::Instant(instant) => Ok(instant.bind(gc)), + // _ => Err(agent.throw_exception_with_static_message( + // ExceptionType::TypeError, + // "Object is not a Temporal Instant", + // gc, + // )), + // } +} \ No newline at end of file diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs new file mode 100644 index 000000000..ef77cd728 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs @@ -0,0 +1,44 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::engine::context::NoGcScope; +use crate::{ + ecmascript::types::OrdinaryObject, + engine::context::{bindable_handle, trivially_bindable}, + heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, +}; + +#[derive(Debug, Clone, Copy)] +pub struct DurationHeapData<'a> { + pub(crate) object_index: Option>, + pub(crate) duration: temporal_rs::Duration, +} + +impl DurationHeapData<'_> { + pub fn default() -> Self { + todo!() + } +} + +trivially_bindable!(temporal_rs::Duration); +bindable_handle!(DurationHeapData); + +impl HeapMarkAndSweep for DurationHeapData<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + object_index, + duration: _, + } = self; + + object_index.mark_values(queues); + } + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + object_index, + duration: _, + } = self; + + object_index.sweep_values(compactions); + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index d87e8ed92..fb1e579df 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -8,17 +8,18 @@ pub(crate) mod data; pub mod instant_constructor; pub mod instant_prototype; +use temporal_rs::Instant; + use crate::{ ecmascript::{ abstract_operations::type_conversion::{PreferredType, to_primitive_object}, - builtins::ordinary::ordinary_create_from_constructor, + builtins::{ordinary::ordinary_create_from_constructor, temporal::duration::{create_negated_temporal_duration, to_temporal_duration}}, execution::{ JsResult, ProtoIntrinsics, agent::{Agent, ExceptionType}, }, types::{ - Function, InternalMethods, InternalSlots, IntoFunction, Object, OrdinaryObject, - Primitive, String, Value, + Function, InternalMethods, InternalSlots, IntoFunction, IntoValue, Object, OrdinaryObject, Primitive, String, Value }, }, engine::{ @@ -266,6 +267,41 @@ fn to_temporal_instant<'gc>( Ok(parsed) } + +/// [8.5.10 AddDurationToInstant ( operation, instant, temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoinstant) +/// The abstract operation AddDurationToInstant takes arguments operation +/// (add or subtract), instant (a Temporal.Instant), +/// and temporalDurationLike (an ECMAScript language value) +/// and returns either a normal completion containing a Temporal.Instant +/// or a throw completion. +/// It adds/subtracts temporalDurationLike to/from instant. +/// It performs the following steps when called: +fn add_duration_to_instant<'gc, const IS_ADD: bool> ( + agent: &mut Agent, + instant: TemporalInstant, + duration: Value, + mut gc: GcScope<'gc, '_> +) -> JsResult<'gc, Value<'gc>> { + let duration = duration.bind(gc.nogc()); + let instant = instant.bind(gc.nogc()); + // 1. Let duration be ? ToTemporalDuration(temporalDurationLike). + let instant = instant.unbind(); + let duration = to_temporal_duration(agent, duration.unbind(), gc.reborrow()); + // 2. If operation is subtract, set duration to CreateNegatedTemporalDuration(duration). + // 3. Let largestUnit be DefaultTemporalLargestUnit(duration). + // 4. If TemporalUnitCategory(largestUnit) is date, throw a RangeError exception. + // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). + // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]). + let ns_result = if IS_ADD { + temporal_rs::Instant::add(&agent[instant].instant, &duration.unwrap()).unwrap() + } else { + temporal_rs::Instant::subtract(&agent[instant].instant, &duration.unwrap()).unwrap() + }; + // 7. Return ! CreateTemporalInstant(ns). + let instant = create_temporal_instant(agent, ns_result, None, gc)?; + Ok(instant.into_value()) +} + #[inline(always)] fn require_internal_slot_temporal_instant<'a>( agent: &mut Agent, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index b07dde168..4d813b333 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -3,10 +3,10 @@ use crate::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - temporal::instant::{require_internal_slot_temporal_instant, to_temporal_instant}, + temporal::instant::{self, add_duration_to_instant, require_internal_slot_temporal_instant, to_temporal_instant}, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, BigInt, String, Value}, + types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, String, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope}, @@ -150,22 +150,44 @@ impl TemporalInstantPrototype { /// ### [8.3.5 Temporal.Instant.prototype.add ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add) fn add<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + let duration = args.get(0).bind(gc.nogc()); + // 1. Let instant be the this value. + let instant = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? AddDurationToInstant(add, instant, temporalDurationLike). + const ADD: bool = true; + let result = add_duration_to_instant::(agent, instant.unbind(), duration.unbind(), gc) + .unbind()?; + Ok(result.into_value()) } /// ### [8.3.6 Temporal.Instant.prototype.subtract ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract) fn subtract<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + let duration = args.get(0).bind(gc.nogc()); + // 1. Let instant be the this value. + let instant = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? AddDurationToInstant(subtract, instant, temporalDurationLike). + const SUBTRACT: bool = false; + let result = add_duration_to_instant::(agent, instant.unbind(), duration.unbind(), gc) + .unbind()?; + Ok(result.into_value()) } /// ### [8.3.7 Temporal.Instant.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) From bffe729ea6f5b92eb0664684d4d57f1755257dde Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Sun, 9 Nov 2025 23:31:11 +0100 Subject: [PATCH 27/42] properly impl to_temporal_duration along with some since&untill stuff --- nova_vm/src/builtin_strings | 10 + .../abstract_operations/type_conversion.rs | 30 ++ .../ecmascript/builtins/temporal/duration.rs | 296 +++++++++++++++--- .../ecmascript/builtins/temporal/instant.rs | 66 ++-- .../temporal/instant/instant_prototype.rs | 66 +++- .../src/ecmascript/types/language/number.rs | 11 + 6 files changed, 403 insertions(+), 76 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index a83278c39..bb3e42e86 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -95,6 +95,7 @@ copyWithin create #[cfg(feature = "array-buffer")]DataView #[cfg(feature = "date")]Date +#[cfg(feature = "temporal")]days decodeURI decodeURIComponent default @@ -231,6 +232,7 @@ hasInstance hasOwn hasOwnProperty #[cfg(feature = "math")]hypot +#[cfg(feature = "temporal")]hours #[cfg(feature = "regexp")]ignoreCase #[cfg(feature = "math")]imul includes @@ -288,6 +290,10 @@ MAX_SAFE_INTEGER MAX_VALUE #[cfg(feature = "array-buffer")]maxByteLength message +#[cfg(feature = "temporal")]microseconds +#[cfg(feature = "temporal")]milliseconds +#[cfg(feature = "temporal")]minutes +#[cfg(feature = "temporal")]months #[cfg(feature = "math")]min MIN_SAFE_INTEGER MIN_VALUE @@ -295,6 +301,7 @@ Module #[cfg(feature = "regexp")]multiline name NaN +#[cfg(feature = "temporal")]nanoseconds NEGATIVE_INFINITY next normalize @@ -361,6 +368,7 @@ revocable #[cfg(any(feature = "math", feature = "temporal"))]round seal #[cfg(feature = "regexp")]search +#[cfg(feature = "temporal")]seconds set #[cfg(feature = "set")]Set set [Symbol.toStringTag] @@ -509,7 +517,9 @@ values #[cfg(feature = "weak-refs")]WeakMap #[cfg(feature = "weak-refs")]WeakRef #[cfg(feature = "weak-refs")]WeakSet +#[cfg(feature = "temporal")]weeks with withResolvers writable #[cfg(feature = "atomics")]xor +#[cfg(feature = "temporal")]years diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index b2a1571a5..7e502c5b3 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -1563,6 +1563,36 @@ pub(crate) fn try_to_index<'a>( js_result_into_try(validate_index(agent, integer, gc)) } +/// [14.5.1.1 ToIntegerIfIntegral ( argument )](https://tc39.es/proposal-temporal/#sec-tointegerifintegral) +/// The abstract operation ToIntegerIfIntegral takes argument argument +/// (an ECMAScript language value) and returns either a normal completion containing +/// an integer or a throw completion. +/// It converts argument to an integer representing its Number value, +/// or throws a RangeError when that value is not integral. +pub(crate) fn to_integer_if_integral<'gc>( + agent: &mut Agent, + argument: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Number<'gc>> { + let argument = argument.bind(gc.nogc()); + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // 2. If number is not an integral Number, throw a RangeError exception. + if !number.is_integer(agent) { + Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "Number must be integral", + gc.into_nogc(), + )) + } else { + // 3. Return ℝ(number). + // Ok(number.into_i64(agent)) // TODO: more performant + Ok(number.unbind()) + } +} + /// Helper function to check if a `char` is trimmable. /// /// Copied from Boa JS engine. Source https://github.com/boa-dev/boa/blob/183e763c32710e4e3ea83ba762cf815b7a89cd1f/core/string/src/lib.rs#L51 diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 62e459ab9..0a27295e5 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -1,6 +1,17 @@ -use temporal_rs::partial::PartialDuration; - -use crate::{ecmascript::{builtins::temporal::instant::TemporalInstant, execution::{Agent, JsResult, agent::ExceptionType}, types::{InternalMethods, Object, Value}}, engine::context::{Bindable, GcScope, NoGcScope, bindable_handle}, heap::indexes::BaseIndex}; +use crate::{ + ecmascript::{ + abstract_operations::{ + operations_on_objects::get, type_conversion::to_integer_if_integral, + }, + execution::{Agent, JsResult, agent::ExceptionType}, + types::{BUILTIN_STRING_MEMORY, BigInt, InternalMethods, Object, String, Value}, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope, bindable_handle}, + rootable::Scopable, + }, + heap::indexes::BaseIndex, +}; use core::ops::{Index, IndexMut}; pub(crate) mod data; @@ -23,7 +34,6 @@ impl TemporalDuration<'_> { bindable_handle!(TemporalDuration); - impl<'a> From> for Value<'a> { fn from(value: TemporalDuration<'a>) -> Self { todo!() @@ -82,20 +92,19 @@ impl IndexMut> for Vec> { /// 7.5.19 CreateTemporalDuration ( years, months, weeks, /// days, hours, minutes, seconds, /// milliseconds, microseconds, nanoseconds [ , newTarget ] ) -/// The abstract operation CreateTemporalDuration takes arguments -/// years (an integer), months (an integer), -/// weeks (an integer), days (an integer), -/// hours (an integer), minutes (an integer), -/// seconds (an integer), milliseconds (an integer), -/// microseconds (an integer), and nanoseconds (an integer) -/// and optional argument newTarget (a constructor) -/// and returns either a normal completion containing -/// a Temporal.Duration or a throw completion. -/// It creates a Temporal.Duration instance and fills -/// the internal slots with valid values. +/// The abstract operation CreateTemporalDuration takes arguments +/// years (an integer), months (an integer), +/// weeks (an integer), days (an integer), +/// hours (an integer), minutes (an integer), +/// seconds (an integer), milliseconds (an integer), +/// microseconds (an integer), and nanoseconds (an integer) +/// and optional argument newTarget (a constructor) +/// and returns either a normal completion containing +/// a Temporal.Duration or a throw completion. +/// It creates a Temporal.Duration instance and fills +/// the internal slots with valid values. /// It performs the following steps when called: -fn create_temporal_duration<'gc> ( - // years, +fn create_temporal_duration<'gc>(// years, // months, // weeks, // days, @@ -124,44 +133,43 @@ fn create_temporal_duration<'gc> ( unimplemented!() } - /// Abstract Operations <---> /// [7.5.12 ToTemporalDuration ( item )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration) -/// The abstract operation ToTemporalDuration takes argument item -/// (an ECMAScript language value) and returns either a normal completion containing a -/// Temporal.Duration or a throw completion. Converts item to a new Temporal.Duration -/// instance if possible and returns that, and throws otherwise. +/// The abstract operation ToTemporalDuration takes argument item +/// (an ECMAScript language value) and returns either a normal completion containing a +/// Temporal.Duration or a throw completion. Converts item to a new Temporal.Duration +/// instance if possible and returns that, and throws otherwise. /// It performs the following steps when called: -pub(crate) fn to_temporal_duration<'gc> ( +pub(crate) fn to_temporal_duration<'gc>( agent: &mut Agent, item: Value, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, temporal_rs::Duration> { let item = item.bind(gc.nogc()); // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then - if let Ok(obj) = require_internal_slot_temporal_duration(agent, item, gc.nogc()) { - // `require_internal_slot_temporal_duration` already guarantees this is a Duration object, - let obj = Object::try_from(obj); unimplemented!(); // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]). } // 2. If item is not an Object, then - if let Ok(item) = item.unbind().to_string(agent, gc.reborrow()){ + if !item.is_object() { + let Ok(item) = String::try_from(item) else { + // a. If item is not a String, throw a TypeError exception. + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "item is not a string", + gc.into_nogc(), + )); + }; // b. Return ? ParseTemporalDurationString(item). let parsed = temporal_rs::Duration::from_utf8(item.as_bytes(agent)).unwrap(); - return Ok(parsed) - } else { - // a. If item is not a String, throw a TypeError exception. - return Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, - "item is not a string", - gc.into_nogc(), - )); + return Ok(parsed); } // 3. Let result be a new Partial Duration Record with each field set to 0. // 4. Let partial be ? ToTemporalPartialDurationRecord(item). + let partial = + to_temporal_partial_duration_record(agent, Object::try_from(item).unwrap().unbind(), gc); // 5. If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]]. // 6. If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]]. // 7. If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]]. @@ -173,22 +181,224 @@ pub(crate) fn to_temporal_duration<'gc> ( // 13. If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]]. // 14. If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]]. // 15. Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - unimplemented!() + Ok(temporal_rs::Duration::from_partial_duration(partial.unwrap()).unwrap()) +} + +/// [7.5.18 ToTemporalPartialDurationRecord ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-totemporalpartialdurationrecord) +/// The abstract operation ToTemporalPartialDurationRecord takes argument temporalDurationLike +/// (an ECMAScript language value) and returns either a normal completion containing a +/// partial Duration Record or a throw completion. The returned Record has its fields +/// set according to the properties of temporalDurationLike. +pub(crate) fn to_temporal_partial_duration_record<'gc>( + agent: &mut Agent, + temporal_duration_like: Object, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, temporal_rs::partial::PartialDuration> { + let temporal_duration_like = temporal_duration_like.scope(agent, gc.nogc()); + // 1. If temporalDurationLike is not an Object, then + // a. Throw a TypeError exception. + // 2. Let result be a new partial Duration Record with each field set to undefined. + let mut result = temporal_rs::partial::PartialDuration::empty(); + // 3. NOTE: The following steps read properties and perform independent validation in alphabetical order. + // 4. Let days be ? Get(temporalDurationLike, "days"). + let days = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.days.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 5. If days is not undefined, set result.[[Days]] to ? ToIntegerIfIntegral(days). + if !days.is_undefined() { + let days = to_integer_if_integral(agent, days.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.days = Some(days.into_i64(agent)) + } + // 6. Let hours be ? Get(temporalDurationLike, "hours"). + let hours = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.hours.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 7. If hours is not undefined, set result.[[Hours]] to ? ToIntegerIfIntegral(hours). + if !hours.is_undefined() { + let hours = to_integer_if_integral(agent, hours.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.hours = Some(hours.into_i64(agent)) + } + // 8. Let microseconds be ? Get(temporalDurationLike, "microseconds"). + let microseconds = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.microseconds.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 9. If microseconds is not undefined, set result.[[Microseconds]] to ? ToIntegerIfIntegral(microseconds). + if !microseconds.is_undefined() { + let microseconds = to_integer_if_integral(agent, microseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let microseconds_big = microseconds.to_big_int(agent); // TODO:IMPL FUNCTION PROPERLY + let microseconds_i128 = microseconds_big.try_into_i128(agent).unwrap(); // TODO: handle ? + result.microseconds = Some(microseconds_i128) + } + // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds"). + let milliseconds = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.milliseconds.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 11. If milliseconds is not undefined, set result.[[Milliseconds]] to ? ToIntegerIfIntegral(milliseconds). + if !milliseconds.is_undefined() { + let milliseconds = to_integer_if_integral(agent, milliseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.milliseconds = Some(milliseconds.into_i64(agent)) + } + // 12. Let minutes be ? Get(temporalDurationLike, "minutes"). + let minutes = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.minutes.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 13. If minutes is not undefined, set result.[[Minutes]] to ? ToIntegerIfIntegral(minutes). + if !minutes.is_undefined() { + let minutes = to_integer_if_integral(agent, minutes.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.minutes = Some(minutes.into_i64(agent)) + } + // 14. Let months be ? Get(temporalDurationLike, "months"). + let months = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.months.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 15. If months is not undefined, set result.[[Months]] to ? ToIntegerIfIntegral(months). + if !months.is_undefined() { + let months = to_integer_if_integral(agent, months.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.months = Some(months.into_i64(agent)) + } + // 16. Let nanoseconds be ? Get(temporalDurationLike, "nanoseconds"). + let nanoseconds = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.nanoseconds.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 17. If nanoseconds is not undefined, set result.[[Nanoseconds]] to ? ToIntegerIfIntegral(nanoseconds). + if !nanoseconds.is_undefined() { + let nanoseconds = to_integer_if_integral(agent, nanoseconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let nanoseconds_big = nanoseconds.to_big_int(agent); // TODO:IMPL FUNCTION PROPERLY + let nanoseconds_i128 = nanoseconds_big.try_into_i128(agent).unwrap(); // TODO: handle ? + result.nanoseconds = Some(nanoseconds_i128) + } + // 18. Let seconds be ? Get(temporalDurationLike, "seconds"). + let seconds = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.seconds.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 19. If seconds is not undefined, set result.[[Seconds]] to ? ToIntegerIfIntegral(seconds). + if !seconds.is_undefined() { + let seconds = to_integer_if_integral(agent, seconds.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.seconds = Some(seconds.into_i64(agent)) + } + // 20. Let weeks be ? Get(temporalDurationLike, "weeks"). + let weeks = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.weeks.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 21. If weeks is not undefined, set result.[[Weeks]] to ? ToIntegerIfIntegral(weeks). + if !weeks.is_undefined() { + let weeks = to_integer_if_integral(agent, weeks.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.weeks = Some(weeks.into_i64(agent)) + } + // 22. Let years be ? Get(temporalDurationLike, "years"). + let years = get( + agent, + temporal_duration_like.get(agent), + BUILTIN_STRING_MEMORY.years.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 23. If years is not undefined, set result.[[Years]] to ? ToIntegerIfIntegral(years). + if !years.is_undefined() { + let years = to_integer_if_integral(agent, years.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + result.years = Some(years.into_i64(agent)) + } + // 24. If years is undefined, and months is undefined, and weeks is undefined, and days is undefined, and hours is undefined, and minutes is undefined, and seconds is undefined, and milliseconds is undefined, and microseconds is undefined, and nanoseconds is undefined, throw a TypeError exception. + if result.years.is_none() + && result.months.is_none() + && result.weeks.is_none() + && result.days.is_none() + && result.hours.is_none() + && result.minutes.is_none() + && result.seconds.is_none() + && result.milliseconds.is_none() + && result.microseconds.is_none() + && result.nanoseconds.is_none() + { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Duration must have at least one unit", + gc.into_nogc(), + )); + } + // 25. Return result. + Ok(result) } /// [7.5.20 CreateNegatedTemporalDuration ( duration )] (https://tc39.es/proposal-temporal/#sec-temporal-createnegatedtemporalduration) -/// The abstract operation CreateNegatedTemporalDuration takes argument -/// duration (a Temporal.Duration) and returns a Temporal.Duration. -/// It returns a new Temporal.Duration instance that is the -/// negation of duration. -pub(crate) fn create_negated_temporal_duration<'gc> ( +/// The abstract operation CreateNegatedTemporalDuration takes argument +/// duration (a Temporal.Duration) and returns a Temporal.Duration. +/// It returns a new Temporal.Duration instance that is the +/// negation of duration. +pub(crate) fn create_negated_temporal_duration<'gc>( agent: &mut Agent, item: temporal_rs::Duration, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, temporal_rs::Duration> { // 1. Return ! CreateTemporalDuration(-duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]). let duration = temporal_rs::Duration::negated(&item); - //TODO: IMPL create_temporal_duration() + //TODO: IMPL create_temporal_duration() unimplemented!() } @@ -208,4 +418,4 @@ fn require_internal_slot_temporal_duration<'a>( // gc, // )), // } -} \ No newline at end of file +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index fb1e579df..ca939180d 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -8,12 +8,10 @@ pub(crate) mod data; pub mod instant_constructor; pub mod instant_prototype; -use temporal_rs::Instant; - use crate::{ ecmascript::{ abstract_operations::type_conversion::{PreferredType, to_primitive_object}, - builtins::{ordinary::ordinary_create_from_constructor, temporal::duration::{create_negated_temporal_duration, to_temporal_duration}}, + builtins::{ordinary::ordinary_create_from_constructor, temporal::duration::to_temporal_duration}, execution::{ JsResult, ProtoIntrinsics, agent::{Agent, ExceptionType}, @@ -24,7 +22,7 @@ use crate::{ }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, - rootable::{HeapRootData, HeapRootRef, Rootable}, + rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, }, heap::{ CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, @@ -223,8 +221,7 @@ fn create_temporal_instant<'gc>( /// /// The abstract operation ToTemporalInstant takes argument item (an ECMAScript language value) and /// returns either a normal completion containing a Temporal.Instant or a throw completion. -/// Converts item to a new Temporal.Instant instance if possible, and throws otherwise. It performs -/// the following steps when called: +/// Converts item to a new Temporal.Instant instance if possible, and throws otherwise. fn to_temporal_instant<'gc>( agent: &mut Agent, item: Value, @@ -267,41 +264,70 @@ fn to_temporal_instant<'gc>( Ok(parsed) } - /// [8.5.10 AddDurationToInstant ( operation, instant, temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoinstant) -/// The abstract operation AddDurationToInstant takes arguments operation -/// (add or subtract), instant (a Temporal.Instant), -/// and temporalDurationLike (an ECMAScript language value) -/// and returns either a normal completion containing a Temporal.Instant -/// or a throw completion. -/// It adds/subtracts temporalDurationLike to/from instant. -/// It performs the following steps when called: -fn add_duration_to_instant<'gc, const IS_ADD: bool> ( +/// The abstract operation AddDurationToInstant takes arguments operation +/// (add or subtract), instant (a Temporal.Instant), +/// and temporalDurationLike (an ECMAScript language value) +/// and returns either a normal completion containing a Temporal.Instant +/// or a throw completion. +/// It adds/subtracts temporalDurationLike to/from instant. +fn add_duration_to_instant<'gc, const IS_ADD: bool>( agent: &mut Agent, instant: TemporalInstant, duration: Value, - mut gc: GcScope<'gc, '_> + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { let duration = duration.bind(gc.nogc()); let instant = instant.bind(gc.nogc()); // 1. Let duration be ? ToTemporalDuration(temporalDurationLike). - let instant = instant.unbind(); + let instant = instant.scope(agent, gc.nogc()); let duration = to_temporal_duration(agent, duration.unbind(), gc.reborrow()); // 2. If operation is subtract, set duration to CreateNegatedTemporalDuration(duration). // 3. Let largestUnit be DefaultTemporalLargestUnit(duration). // 4. If TemporalUnitCategory(largestUnit) is date, throw a RangeError exception. // 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration). - // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]). + // 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]). let ns_result = if IS_ADD { - temporal_rs::Instant::add(&agent[instant].instant, &duration.unwrap()).unwrap() + temporal_rs::Instant::add(&agent[instant.get(agent)].instant, &duration.unwrap()).unwrap() } else { - temporal_rs::Instant::subtract(&agent[instant].instant, &duration.unwrap()).unwrap() + temporal_rs::Instant::subtract(&agent[instant.get(agent)].instant, &duration.unwrap()) + .unwrap() }; // 7. Return ! CreateTemporalInstant(ns). let instant = create_temporal_instant(agent, ns_result, None, gc)?; Ok(instant.into_value()) } +/// [8.5.9 DifferenceTemporalInstant ( operation, instant, other, options )]() +/// The abstract operation DifferenceTemporalInstant takes arguments +/// operation (since or until), instant (a Temporal.Instant), +/// other (an ECMAScript language value), and options +/// (an ECMAScript language value) and returns either +/// a normal completion containing a Temporal.Duration or a +/// throw completion. It computes the difference between the +/// two times represented by instant and other, optionally +/// rounds it, and returns it as a Temporal.Duration object. +fn difference_temporal_instant<'gc, const IS_UNTIL: bool>( + agent: &mut Agent, + instant: Value, + other: Value, + options: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Value<'gc>> { + let instant = instant.bind(gc.nogc()); + let other = other.bind(gc.nogc()); + let options = options.bind(gc.nogc()); + // 1. Set other to ? ToTemporalInstant(other). + let other = to_temporal_instant(agent, other.unbind(), gc.reborrow()); + // 2. Let resolvedOptions be ? GetOptionsObject(options). + // 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, time, « », nanosecond, second). + // 4. Let internalDuration be DifferenceInstant(instant.[[EpochNanoseconds]], other.[[EpochNanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + // 5. Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]). + // 6. If operation is since, set result to CreateNegatedTemporalDuration(result). + // 7. Return result. + unimplemented!() +} + #[inline(always)] fn require_internal_slot_temporal_instant<'a>( agent: &mut Agent, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 4d813b333..8952ee688 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -3,7 +3,10 @@ use crate::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - temporal::instant::{self, add_duration_to_instant, require_internal_slot_temporal_instant, to_temporal_instant}, + temporal::instant::{ + add_duration_to_instant, difference_temporal_instant, + require_internal_slot_temporal_instant, to_temporal_instant, + }, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, String, Value}, @@ -185,29 +188,66 @@ impl TemporalInstantPrototype { .bind(gc.nogc()); // 3. Return ? AddDurationToInstant(subtract, instant, temporalDurationLike). const SUBTRACT: bool = false; - let result = add_duration_to_instant::(agent, instant.unbind(), duration.unbind(), gc) - .unbind()?; + let result = + add_duration_to_instant::(agent, instant.unbind(), duration.unbind(), gc) + .unbind()?; Ok(result.into_value()) } /// ### [8.3.7 Temporal.Instant.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) fn until<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + let other = args.get(0).bind(gc.nogc()); + let options = args.get(1).bind(gc.nogc()); + // 1. Let instant be the this value. + let instant = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? DifferenceTemporalInstant(until, instant, other, options). + const UNTIL: bool = true; + let result = difference_temporal_instant::( + agent, + instant.into_value().unbind(), + other.unbind(), + options.unbind(), + gc, + ) + .unbind()?; + Ok(result.into_value()) } /// ### [8.3.8 Temporal.Instant.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) fn since<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + let other = args.get(0).bind(gc.nogc()); + let options = args.get(1).bind(gc.nogc()); + // 1. Let instant be the this value. + let instant = this_value; + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? DifferenceTemporalInstant(since, instant, other, options). + const SINCE: bool = false; + let result = difference_temporal_instant::( + agent, + instant.into_value().unbind(), + other.unbind(), + options.unbind(), + gc, + ) + .unbind()?; + Ok(result.into_value()) } /// ### [8.3.9 Temporal.Instant.prototype.round ( roundTo )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round) diff --git a/nova_vm/src/ecmascript/types/language/number.rs b/nova_vm/src/ecmascript/types/language/number.rs index 3870e6830..3892a5c66 100644 --- a/nova_vm/src/ecmascript/types/language/number.rs +++ b/nova_vm/src/ecmascript/types/language/number.rs @@ -16,6 +16,7 @@ use crate::{ ecmascript::{ abstract_operations::type_conversion::{to_int32_number, to_uint32_number}, execution::Agent, + types::BigInt, }, engine::{ context::{Bindable, NoGcScope, bindable_handle}, @@ -321,6 +322,16 @@ impl<'a> Number<'a> { } } + /// Convert a Number to BigInt + pub fn to_big_int(self, agent: &mut Agent) -> BigInt<'a> { + let f = self.into_f64(agent); + if f.is_nan() || !f.is_finite() || f.fract() != 0.0 { + BigInt::from_i64(agent, 0) + } else { + BigInt::from_i64(agent, f as i64) + } + } + /// Create a Number from a usize. pub fn from_usize(agent: &mut Agent, value: usize, gc: NoGcScope<'a, '_>) -> Self { if let Ok(value) = Number::try_from(value) { From e8fb97ed2d95d058b4d62dedb9a7aeb4f9362b0d Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 11 Nov 2025 13:40:04 +0100 Subject: [PATCH 28/42] remove to_big_int and instead cast to i128 in to_temporal_partial_duration_record --- .../ecmascript/builtins/temporal/duration.rs | 18 +++++++----------- .../ecmascript/builtins/temporal/instant.rs | 2 +- .../src/ecmascript/types/language/number.rs | 10 ---------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 0a27295e5..dc204cf98 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -4,7 +4,7 @@ use crate::{ operations_on_objects::get, type_conversion::to_integer_if_integral, }, execution::{Agent, JsResult, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, BigInt, InternalMethods, Object, String, Value}, + types::{BUILTIN_STRING_MEMORY, Object, String, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -35,13 +35,13 @@ impl TemporalDuration<'_> { bindable_handle!(TemporalDuration); impl<'a> From> for Value<'a> { - fn from(value: TemporalDuration<'a>) -> Self { + fn from(_value: TemporalDuration<'a>) -> Self { todo!() //Value::Duration(value) } } impl<'a> From> for Object<'a> { - fn from(value: TemporalDuration<'a>) -> Self { + fn from(_value: TemporalDuration<'a>) -> Self { todo!() //Object::Duration(value) } @@ -49,7 +49,7 @@ impl<'a> From> for Object<'a> { impl<'a> TryFrom> for TemporalDuration<'a> { type Error = (); - fn try_from(value: Value<'a>) -> Result { + fn try_from(_value: Value<'a>) -> Result { todo!() // match value { // Value::Duration(idx) => Ok(idx), @@ -148,7 +148,7 @@ pub(crate) fn to_temporal_duration<'gc>( ) -> JsResult<'gc, temporal_rs::Duration> { let item = item.bind(gc.nogc()); // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then - if let Ok(obj) = require_internal_slot_temporal_duration(agent, item, gc.nogc()) { + if let Ok(_obj) = require_internal_slot_temporal_duration(agent, item, gc.nogc()) { unimplemented!(); // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]). } @@ -246,9 +246,7 @@ pub(crate) fn to_temporal_partial_duration_record<'gc>( let microseconds = to_integer_if_integral(agent, microseconds.unbind(), gc.reborrow()) .unbind()? .bind(gc.nogc()); - let microseconds_big = microseconds.to_big_int(agent); // TODO:IMPL FUNCTION PROPERLY - let microseconds_i128 = microseconds_big.try_into_i128(agent).unwrap(); // TODO: handle ? - result.microseconds = Some(microseconds_i128) + result.microseconds = Some(microseconds.into_i64(agent) as i128); } // 10. Let milliseconds be ? Get(temporalDurationLike, "milliseconds"). let milliseconds = get( @@ -312,9 +310,7 @@ pub(crate) fn to_temporal_partial_duration_record<'gc>( let nanoseconds = to_integer_if_integral(agent, nanoseconds.unbind(), gc.reborrow()) .unbind()? .bind(gc.nogc()); - let nanoseconds_big = nanoseconds.to_big_int(agent); // TODO:IMPL FUNCTION PROPERLY - let nanoseconds_i128 = nanoseconds_big.try_into_i128(agent).unwrap(); // TODO: handle ? - result.nanoseconds = Some(nanoseconds_i128) + result.nanoseconds = Some(nanoseconds.into_i64(agent) as i128); } // 18. Let seconds be ? Get(temporalDurationLike, "seconds"). let seconds = get( diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index ca939180d..ab13abadc 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -298,7 +298,7 @@ fn add_duration_to_instant<'gc, const IS_ADD: bool>( Ok(instant.into_value()) } -/// [8.5.9 DifferenceTemporalInstant ( operation, instant, other, options )]() +/// [8.5.9 DifferenceTemporalInstant ( operation, instant, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant) /// The abstract operation DifferenceTemporalInstant takes arguments /// operation (since or until), instant (a Temporal.Instant), /// other (an ECMAScript language value), and options diff --git a/nova_vm/src/ecmascript/types/language/number.rs b/nova_vm/src/ecmascript/types/language/number.rs index 3892a5c66..09c6cb476 100644 --- a/nova_vm/src/ecmascript/types/language/number.rs +++ b/nova_vm/src/ecmascript/types/language/number.rs @@ -322,16 +322,6 @@ impl<'a> Number<'a> { } } - /// Convert a Number to BigInt - pub fn to_big_int(self, agent: &mut Agent) -> BigInt<'a> { - let f = self.into_f64(agent); - if f.is_nan() || !f.is_finite() || f.fract() != 0.0 { - BigInt::from_i64(agent, 0) - } else { - BigInt::from_i64(agent, f as i64) - } - } - /// Create a Number from a usize. pub fn from_usize(agent: &mut Agent, value: usize, gc: NoGcScope<'a, '_>) -> Self { if let Ok(value) = Number::try_from(value) { From bddb79de3b95574a9401c24e92cf4a23c706e8eb Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 11 Nov 2025 13:41:31 +0100 Subject: [PATCH 29/42] cargo fmt --- nova_vm/src/ecmascript/builtins/temporal.rs | 2 +- nova_vm/src/ecmascript/builtins/temporal/instant.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index b5cd5a18c..b5d704264 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -2,8 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -pub mod instant; pub mod duration; +pub mod instant; use crate::{ ecmascript::{ diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index ab13abadc..856f8a101 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -11,13 +11,16 @@ pub mod instant_prototype; use crate::{ ecmascript::{ abstract_operations::type_conversion::{PreferredType, to_primitive_object}, - builtins::{ordinary::ordinary_create_from_constructor, temporal::duration::to_temporal_duration}, + builtins::{ + ordinary::ordinary_create_from_constructor, temporal::duration::to_temporal_duration, + }, execution::{ JsResult, ProtoIntrinsics, agent::{Agent, ExceptionType}, }, types::{ - Function, InternalMethods, InternalSlots, IntoFunction, IntoValue, Object, OrdinaryObject, Primitive, String, Value + Function, InternalMethods, InternalSlots, IntoFunction, IntoValue, Object, + OrdinaryObject, Primitive, String, Value, }, }, engine::{ From 49db0c967b7c47c4e0828064450887017c767162 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 12 Nov 2025 04:59:43 +0100 Subject: [PATCH 30/42] some work on instant.prototype.round --- nova_vm/src/builtin_strings | 10 +- nova_vm/src/ecmascript/builtins/temporal.rs | 13 +- .../src/ecmascript/builtins/temporal/error.rs | 33 ++ .../ecmascript/builtins/temporal/instant.rs | 5 +- .../temporal/instant/instant_prototype.rs | 351 ++++++++++++++++-- 5 files changed, 358 insertions(+), 54 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/error.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index bb3e42e86..8c7d062ae 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -335,14 +335,6 @@ prototype Proxy push race -#[cfg(feature = "temporal")]PlainDateTime -#[cfg(feature = "temporal")]PlainDate -#[cfg(feature = "temporal")]PlainTime -#[cfg(feature = "temporal")]PlainYearMonth -#[cfg(feature = "temporal")]PlainMonthDay -#[cfg(feature = "temporal")]Duration -#[cfg(feature = "temporal")]ZonedDateTime -#[cfg(feature = "temporal")]Now #[cfg(feature = "math")]random RangeError raw @@ -366,6 +358,7 @@ return reverse revocable #[cfg(any(feature = "math", feature = "temporal"))]round +#[cfg(feature = "temporal")]roundingIncrement seal #[cfg(feature = "regexp")]search #[cfg(feature = "temporal")]seconds @@ -409,6 +402,7 @@ shift size slice #[cfg(feature = "annex-b-string")]small +#[cfg(feature = "temporal")]smallestUnit some sort #[cfg(feature = "regexp")]source diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index b5d704264..c08709832 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -3,6 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. pub mod duration; +pub mod error; pub mod instant; use crate::{ @@ -30,18 +31,18 @@ impl TemporalObject { .with_prototype(object_prototype) .with_property(|builder| { builder - .with_key(WellKnownSymbolIndexes::ToStringTag.into()) - .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal.into()) + .with_key(BUILTIN_STRING_MEMORY.Instant.into()) + .with_value(temporal_instant_constructor.into_value()) .with_enumerable(false) - .with_configurable(true) + .with_configurable(false) .build() }) .with_property(|builder| { builder - .with_key(BUILTIN_STRING_MEMORY.Instant.into()) - .with_value(temporal_instant_constructor.into_value()) + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal.into()) .with_enumerable(false) - .with_configurable(false) + .with_configurable(true) .build() }) .build(); diff --git a/nova_vm/src/ecmascript/builtins/temporal/error.rs b/nova_vm/src/ecmascript/builtins/temporal/error.rs new file mode 100644 index 000000000..3e81c43e9 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/error.rs @@ -0,0 +1,33 @@ +use crate::{ + ecmascript::execution::{ + Agent, + agent::{ExceptionType, JsError}, + }, + engine::context::NoGcScope, +}; +use temporal_rs::{TemporalError, error::ErrorKind}; + +pub fn temporal_err_to_js_err<'gc>( + agent: &mut Agent, + error: TemporalError, + gc: NoGcScope<'gc, '_>, +) -> JsError<'gc> { + let message = error.into_message(); + match error.kind() { + ErrorKind::Generic => { + agent.throw_exception_with_static_message(ExceptionType::Error, message, gc) + } + ErrorKind::Type => { + agent.throw_exception_with_static_message(ExceptionType::TypeError, message, gc) + } + ErrorKind::Range => { + agent.throw_exception_with_static_message(ExceptionType::RangeError, message, gc) + } + ErrorKind::Syntax => { + agent.throw_exception_with_static_message(ExceptionType::SyntaxError, message, gc) + } + ErrorKind::Assert => { + agent.throw_exception_with_static_message(ExceptionType::Error, message, gc) + } + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 856f8a101..aca1912ce 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -40,8 +40,8 @@ use self::data::InstantHeapData; pub struct TemporalInstant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl TemporalInstant<'_> { - pub(crate) fn inner_instant(self, agent: &Agent) -> temporal_rs::Instant { - agent[self].instant + pub(crate) fn inner_instant(self, agent: &Agent) -> &temporal_rs::Instant { + &agent[self].instant } //TODO @@ -64,6 +64,7 @@ impl TemporalInstant<'_> { agent[self].instant = epoch_nanoseconds; } } + bindable_handle!(TemporalInstant); impl<'a> From> for Value<'a> { diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 8952ee688..c92df5d83 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -1,18 +1,33 @@ +use std::num::NonZeroU32; + +use temporal_rs::options::{RoundingIncrement, RoundingMode, RoundingOptions, Unit}; + use crate::{ ecmascript::{ + abstract_operations::{ + operations_on_objects::{get, try_create_data_property_or_throw}, + type_conversion::to_number, + }, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ ArgumentsList, Behaviour, Builtin, - temporal::instant::{ - add_duration_to_instant, difference_temporal_instant, - require_internal_slot_temporal_instant, to_temporal_instant, + ordinary::ordinary_object_create_with_intrinsics, + temporal::{ + error::temporal_err_to_js_err, + instant::{ + add_duration_to_instant, create_temporal_instant, difference_temporal_instant, + require_internal_slot_temporal_instant, to_temporal_instant, + }, }, }, - execution::{Agent, JsResult, Realm, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, String, Value}, + execution::{ + Agent, JsResult, Realm, + agent::{ExceptionType, unwrap_try}, + }, + types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, String, Value}, }, engine::{ - context::{Bindable, GcScope, NoGcScope}, + context::{Bindable, GcScope, NoGcScope, trivially_bindable}, rootable::Scopable, }, heap::WellKnownSymbolIndexes, @@ -122,16 +137,15 @@ impl TemporalInstantPrototype { _: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + let gc = gc.into_nogc(); // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) - .unbind()? - .bind(gc.nogc()); + let instant = require_internal_slot_temporal_instant(agent, this_value, gc)?; // 3. Let ns be instant.[[EpochNanoseconds]]. // 4. Let ms be floor(ℝ(ns) / 10**6). // 5. Return 𝔽(ms). let value = instant.inner_instant(agent).epoch_milliseconds(); - Ok(Value::from_i64(agent, value, gc.into_nogc())) + Ok(Value::from_i64(agent, value, gc)) } /// ### [8.3.4 get Temporal.Instant.prototype.epochNanoseconds](https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds) @@ -141,11 +155,10 @@ impl TemporalInstantPrototype { _: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + let gc = gc.into_nogc(); // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) - .unbind()? - .bind(gc.nogc()); + let instant = require_internal_slot_temporal_instant(agent, this_value, gc)?; // 3. Return instant.[[EpochNanoseconds]]. let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); Ok(BigInt::from_i128(agent, value).into()) @@ -252,12 +265,108 @@ impl TemporalInstantPrototype { /// ### [8.3.9 Temporal.Instant.prototype.round ( roundTo )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round) fn round<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + let this_value = this_value.bind(gc.nogc()); + let round_to = args.get(0).bind(gc.nogc()); + // 1. Let instant be the this value. + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) + .unbind()? + .scope(agent, gc.nogc()); + + // 3. If roundTo is undefined, then + if round_to.unbind().is_undefined() { + // a. Throw a TypeError exception. + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "roundTo cannot be undefined", + gc.into_nogc(), + )); + } + + // 4. If roundTo is a String, then + let round_to = if let Value::String(round_to) = round_to.unbind() { + // a. Let paramString be roundTo. + let param_string = round_to; + // b. Set roundTo to OrdinaryObjectCreate(null). + let round_to = ordinary_object_create_with_intrinsics(agent, None, None, gc.nogc()); + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). + unwrap_try(try_create_data_property_or_throw( + agent, + round_to, + BUILTIN_STRING_MEMORY.smallestUnit.into(), + param_string.into_value(), + None, + gc.nogc(), + )); + round_to + } else { + // 5. Else, set roundTo to ? GetOptionsObject(roundTo). + get_options_object(agent, round_to.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()) + }; + let round_to = round_to.scope(agent, gc.nogc()); + // 6. NOTE: The following steps read options and perform independent validation in + // alphabetical order (GetRoundingIncrementOption reads "roundingIncrement" and + // GetRoundingModeOption reads "roundingMode"). + let mut options = RoundingOptions::default(); + // 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo). + let rounding_increment = + get_rounding_increment_option(agent, round_to.get(agent), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + options.increment = Some(rounding_increment); + // 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand). + let rounding_mode = get_rounding_mode_option( + agent, + round_to.get(agent), + RoundingMode::default(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + options.rounding_mode = Some(rounding_mode); + // 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", required). + let smallest_unit = get_temporal_unit_valued_option( + agent, + round_to.get(agent), + BUILTIN_STRING_MEMORY.smallestUnit, + DefaultOption::Required, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + options.smallest_unit = Some(smallest_unit); + // 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time). + // 11. If smallestUnit is hour, then + // a. Let maximum be HoursPerDay. + // 12. Else if smallestUnit is minute, then + // a. Let maximum be MinutesPerHour × HoursPerDay. + // 13. Else if smallestUnit is second, then + // a. Let maximum be SecondsPerMinute × MinutesPerHour × HoursPerDay. + // 14. Else if smallestUnit is millisecond, then + // a. Let maximum be ℝ(msPerDay). + // 15. Else if smallestUnit is microsecond, then + // a. Let maximum be 10**3 × ℝ(msPerDay). + // 16. Else, + // a. Assert: smallestUnit is nanosecond. + // b. Let maximum be nsPerDay. + // 17. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true). + // 18. Let roundedNs be RoundTemporalInstant(instant.[[EpochNanoseconds]], roundingIncrement, smallestUnit, roundingMode). + let rounded_ns = instant + .get(agent) + .inner_instant(agent) + .round(options) + .map_err(|e| temporal_err_to_js_err(agent, e, gc.nogc())) + .unbind()? + .bind(gc.nogc()); + // 19. Return ! CreateTemporalInstant(roundedNs). + Ok(create_temporal_instant(agent, rounded_ns, None, gc)?.into_value()) } /// ### [8.3.10 Temporal.Instant.prototype.equals ( other )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals) @@ -267,17 +376,18 @@ impl TemporalInstantPrototype { args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + let other = args.get(0).bind(gc.nogc()); + let this_value = this_value.bind(gc.nogc()); // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, this_value, gc.nogc()) + let instant = require_internal_slot_temporal_instant(agent, this_value.unbind(), gc.nogc()) .unbind()? .scope(agent, gc.nogc()); // 3. Set other to ? ToTemporalInstant(other). - let other = args.get(0).bind(gc.nogc()); let other_instant = to_temporal_instant(agent, other.unbind(), gc.reborrow()).unbind()?; // 4. If instant.[[EpochNanoseconds]] ≠ other.[[EpochNanoseconds]], return false. let instant_val = instant.get(agent).bind(gc.nogc()); - if instant_val.inner_instant(agent) != other_instant { + if *instant_val.inner_instant(agent) != other_instant { return Ok(Value::from(false)); } // 5. Return true. @@ -315,6 +425,14 @@ impl TemporalInstantPrototype { } /// ###[8.3.14 Temporal.Instant.prototype.valueOf ( )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof) + /// Note: + /// This method always throws, because in the absence of valueOf(), expressions with + /// arithmetic operators such as instant1 > instant2 would fall back to being equivalent + /// to instant1.toString() > instant2.toString(). Lexicographical comparison of + /// serialized strings might not seem obviously wrong, because the result would + /// sometimes be correct. Implementations are encouraged to phrase the error message to + /// point users to Temporal.Instant.compare(), Temporal.Instant.prototype.equals(), + /// and/or Temporal.Instant.prototype.toString(). fn value_of<'gc>( agent: &mut Agent, _: Value, @@ -322,15 +440,6 @@ impl TemporalInstantPrototype { gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { // 1. Throw a TypeError exception. - // - // Note: - // This method always throws, because in the absence of valueOf(), expressions with - // arithmetic operators such as instant1 > instant2 would fall back to being equivalent - // to instant1.toString() > instant2.toString(). Lexicographical comparison of - // serialized strings might not seem obviously wrong, because the result would - // sometimes be correct. Implementations are encouraged to phrase the error message to - // point users to Temporal.Instant.compare(), Temporal.Instant.prototype.equals(), - // and/or Temporal.Instant.prototype.toString(). Err(agent.throw_exception_with_static_message( ExceptionType::TypeError, "`valueOf` not supported by Temporal built-ins. See 'compare', 'equals', or `toString`", @@ -358,14 +467,6 @@ impl TemporalInstantPrototype { .with_property_capacity(15) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) - .with_property(|builder| { - builder - .with_key(WellKnownSymbolIndexes::ToStringTag.into()) - .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal_Instant.into()) - .with_enumerable(false) - .with_configurable(true) - .build() - }) .with_builtin_function_property::() .with_builtin_function_property::() .with_builtin_function_property::() @@ -379,6 +480,180 @@ impl TemporalInstantPrototype { .with_builtin_function_property::() .with_builtin_function_property::() .with_builtin_function_property::() + .with_property(|builder| { + builder + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal_Instant.into()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) .build(); } } + +/// ### [14.5.2.1 GetOptionsObject ( options )](https://tc39.es/proposal-temporal/#sec-getoptionsobject) +/// +/// The abstract operation GetOptionsObject takes argument options (an ECMAScript language value) +/// and returns either a normal completion containing an Object or a throw completion. It returns +/// an Object suitable for use with GetOption, either options itself or a default empty Object. It +/// throws a TypeError if options is not undefined and not an Object. It performs the following +/// steps when called: +fn get_options_object<'gc>( + agent: &mut Agent, + options: Value, + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, Object<'gc>> { + match options.unbind() { + // 1. If options is undefined, then + Value::Undefined => { + // a. Return OrdinaryObjectCreate(null). + Ok(ordinary_object_create_with_intrinsics( + agent, None, None, gc, + )) + } + // 2. If options is an Object, then + Value::Object(obj) => { + // a. Return options. + Ok(obj.into()) + } + // 3. Throw a TypeError exception. + _ => Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "options provided to GetOptionsObject is not an object", + gc, + )), + } +} + +trivially_bindable!(RoundingMode); + +/// ### [14.5.2.3 GetRoundingModeOption ( options, fallback)](https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption) +/// +// The abstract operation GetRoundingModeOption takes arguments options (an Object) and fallback (a +// rounding mode) and returns either a normal completion containing a rounding mode, or a throw +// completion. It fetches and validates the "roundingMode" property from options, returning +// fallback as a default if absent. It performs the following steps when called: +fn get_rounding_mode_option<'gc>( + _agent: &mut Agent, + options: Object, + fallback: RoundingMode, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, RoundingMode> { + let _options = options.bind(gc.nogc()); + let _fallback = fallback.bind(gc.nogc()); + // 1. Let allowedStrings be the List of Strings from the "String Identifier" column of Table 28. + // 2. Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column. + // 3. Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback). + // 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column. + todo!() +} + +trivially_bindable!(RoundingIncrement); + +/// ### [14.5.2.4 GetRoundingIncrementOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption) +/// +/// The abstract operation GetRoundingIncrementOption takes argument options (an Object) and returns +/// either a normal completion containing a positive integer in the inclusive interval from 1 to +/// 10**9, or a throw completion. It fetches and validates the "roundingIncrement" property from +/// options, returning a default if absent. It performs the following steps when called: +fn get_rounding_increment_option<'gc>( + agent: &mut Agent, + options: Object, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, RoundingIncrement> { + let options = options.bind(gc.nogc()); + // 1. Let value be ? Get(options, "roundingIncrement"). + let value = get( + agent, + options.unbind(), + BUILTIN_STRING_MEMORY.roundingIncrement.into(), + gc.reborrow(), + ) + .unbind()?; + // 2. If value is undefined, return 1𝔽. + if value.is_undefined() { + return Ok(RoundingIncrement::default()); + } + // 3. Let integerIncrement be ? ToIntegerWithTruncation(value). + let integer_increment = to_integer_with_truncation(agent, value, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + // 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception. + if integer_increment < 1.0 || integer_increment > 1_000_000_000.0 { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "roundingIncrement must be between 1 and 10**9", + gc.into_nogc(), + )); + } + + // Convert safely and return integerIncrement + // NOTE: `as u32` is safe here since we validated it’s in range. + let integer_increment_u32 = integer_increment as u32; + let increment = + NonZeroU32::new(integer_increment_u32).expect("integer_increment >= 1 ensures nonzero"); + + // 5. Return integerIncrement. + Ok(RoundingIncrement::new_unchecked(increment)) +} + +/// ### [13.40 ToIntegerWithTruncation ( argument )] (https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation) +/// +/// The abstract operation ToIntegerWithTruncation takes argument argument (an ECMAScript language +/// value) and returns either a normal completion containing an integer or a throw completion. It +/// converts argument to an integer representing its Number value with fractional part truncated, or +/// throws a RangeError when that value is not finite. It performs the following steps when called: +fn to_integer_with_truncation<'gc>( + agent: &mut Agent, + argument: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, f64> { + let argument = argument.bind(gc.nogc()); + // 1. Let number be ? ToNumber(argument). + let number = to_number(agent, argument.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + // 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception. + if number.is_nan(agent) || number.is_pos_infinity(agent) || number.is_neg_infinity(agent) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Number cannot be NaN, positive infinity, or negative infinity", + gc.into_nogc(), + )); + } + + // 3. Return truncate(ℝ(number)). + Ok(number.into_f64(agent).trunc()) +} + +trivially_bindable!(Unit); + +/// ### [13.17 GetTemporalUnitValuedOption ( options, key, default )] (https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunitvaluedoption) +/// +/// The abstract operation GetTemporalUnitValuedOption takes arguments options (an Object), key (a +/// property key), and default (required or unset) and returns either a normal completion +/// containing either a Temporal unit, unset, or auto, or a throw completion. It attempts to read a +/// Temporal unit from the specified property of options. +fn get_temporal_unit_valued_option<'gc>( + _agent: &mut Agent, + options: Object, + key: String<'static>, + default: DefaultOption, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Unit> { + let _options = options.bind(gc.nogc()); + let _key = key.bind(gc.nogc()); + let _default = default.bind(gc.nogc()); + todo!() +} + +#[allow(dead_code)] +enum DefaultOption { + Required, + Unset, +} + +trivially_bindable!(DefaultOption); From 90464650e3233fb9da6fceabe4a1231504f4f3d9 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 12 Nov 2025 05:12:24 +0100 Subject: [PATCH 31/42] use PropertyKey instead of String<'static> as key --- .../builtins/temporal/instant/instant_prototype.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index c92df5d83..ec06fd5c8 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -24,7 +24,7 @@ use crate::{ Agent, JsResult, Realm, agent::{ExceptionType, unwrap_try}, }, - types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, String, Value}, + types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, PropertyKey, String, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope, trivially_bindable}, @@ -335,7 +335,7 @@ impl TemporalInstantPrototype { let smallest_unit = get_temporal_unit_valued_option( agent, round_to.get(agent), - BUILTIN_STRING_MEMORY.smallestUnit, + BUILTIN_STRING_MEMORY.smallestUnit.into(), DefaultOption::Required, gc.reborrow(), ) @@ -640,12 +640,11 @@ trivially_bindable!(Unit); fn get_temporal_unit_valued_option<'gc>( _agent: &mut Agent, options: Object, - key: String<'static>, + key: PropertyKey, default: DefaultOption, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Unit> { let _options = options.bind(gc.nogc()); - let _key = key.bind(gc.nogc()); let _default = default.bind(gc.nogc()); todo!() } From 91c8886809d9b48df5358ce7cefd38bb7f852002 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Mon, 17 Nov 2025 19:43:36 +0100 Subject: [PATCH 32/42] bring up test262 test passing results, bug fixing, refactoring, groundwork for Temporal.PlainTime, WIP Temporal.prototype.round implementations.. probably should have committed more frequently lol --- nova_vm/src/builtin_strings | 11 +- nova_vm/src/ecmascript/builtins/ordinary.rs | 6 + nova_vm/src/ecmascript/builtins/temporal.rs | 32 ++- .../ecmascript/builtins/temporal/instant.rs | 46 +++-- .../temporal/instant/instant_constructor.rs | 1 - .../temporal/instant/instant_prototype.rs | 142 ++------------ .../ecmascript/builtins/temporal/options.rs | 184 ++++++++++++++++++ .../builtins/temporal/plain_time.rs | 126 ++++++++++++ .../builtins/temporal/plain_time/data.rs | 47 +++++ .../ecmascript/execution/realm/intrinsics.rs | 32 ++- nova_vm/src/ecmascript/execution/weak_key.rs | 17 +- .../src/ecmascript/types/language/number.rs | 1 - .../src/ecmascript/types/language/object.rs | 16 ++ .../src/ecmascript/types/language/value.rs | 23 ++- nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 13 +- nova_vm/src/heap.rs | 11 +- nova_vm/src/heap/heap_bits.rs | 22 +++ nova_vm/src/heap/heap_constants.rs | 7 +- nova_vm/src/heap/heap_gc.rs | 30 ++- 20 files changed, 599 insertions(+), 170 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/options.rs create mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time.rs create mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 8c7d062ae..297b67c35 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -114,6 +114,8 @@ encodeURIComponent endsWith entries enumerable +#[cfg(feature = "temporal")]epochMilliseconds +#[cfg(feature = "temporal")]epochNanoseconds EPSILON #[cfg(feature = "temporal")]equals Error @@ -150,8 +152,8 @@ for forEach freeze from -fromEpochNanoseconds -fromEpochMilliseconds +#[cfg(feature = "temporal")]fromEpochNanoseconds +#[cfg(feature = "temporal")]fromEpochMilliseconds fromCharCode fromCodePoint fromEntries @@ -188,8 +190,8 @@ get size #[cfg(feature = "array-buffer")]getBigUint64 #[cfg(feature = "date")]getDate #[cfg(feature = "date")]getDay -#[cfg(feature = "temporal")]getEpochMilliseconds -#[cfg(feature = "temporal")]getEpochNanoSeconds +#[cfg(feature = "temporal")]get epochMilliseconds +#[cfg(feature = "temporal")]get epochNanoseconds #[cfg(feature = "proposal-float16array")]getFloat16 #[cfg(feature = "array-buffer")]getFloat32 #[cfg(feature = "array-buffer")]getFloat64 @@ -324,6 +326,7 @@ parseFloat parseInt #[cfg(feature = "proposal-atomics-microwait")]pause #[cfg(feature = "math")]PI +#[cfg(feature = "temporal")]PlainTime pop POSITIVE_INFINITY #[cfg(feature = "math")]pow diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 344cd2d66..130e7cbfa 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -1691,6 +1691,8 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( ProtoIntrinsics::TemporalInstant => { agent.heap.create(InstantRecord::default()).into_object() } + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalPlainTime => todo!(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) @@ -2081,6 +2083,10 @@ fn get_intrinsic_constructor<'a>( ProtoIntrinsics::WeakSet => Some(intrinsics.weak_set().into_function()), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into_function()), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalPlainTime => { + Some(intrinsics.temporal_plain_time().into_function()) + } } } diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index c08709832..ec87110c8 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -5,6 +5,8 @@ pub mod duration; pub mod error; pub mod instant; +pub mod options; +pub mod plain_time; use crate::{ ecmascript::{ @@ -16,27 +18,45 @@ use crate::{ heap::WellKnownSymbolIndexes, }; -pub(crate) struct TemporalObject; +pub(crate) struct Temporal; -impl TemporalObject { +impl Temporal { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let object_prototype = intrinsics.object_prototype(); let this = intrinsics.temporal(); - let temporal_instant_constructor = intrinsics.temporal_instant(); + let instant_constructor = intrinsics.temporal_instant(); + let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(2) + .with_property_capacity(3) .with_prototype(object_prototype) + // 1.2.1 Temporal.Instant ( . . . ) .with_property(|builder| { builder .with_key(BUILTIN_STRING_MEMORY.Instant.into()) - .with_value(temporal_instant_constructor.into_value()) + .with_value(instant_constructor.into_value()) .with_enumerable(false) - .with_configurable(false) + .with_configurable(true) + .build() + }) + // 1.2.2 Temporal.PlainDateTime ( . . . ) + // 1.2.3 Temporal.PlainDate ( . . . ) + // 1.2.4 Temporal.PlainTime ( . . . ) + .with_property(|builder| { + builder + .with_key(BUILTIN_STRING_MEMORY.PlainTime.into()) + .with_value(plain_time_constructor.into_value()) + .with_enumerable(false) + .with_configurable(true) .build() }) + // 1.2.5 Temporal.PlainYearMonth ( . . . ) + // 1.2.6 Temporal.PlainMonthDay ( . . . ) + // 1.2.7 Temporal.Duration ( . . . ) + // 1.2.8 Temporal.ZonedDateTime ( . . . ) + // 1.3.1 Temporal.Now .with_property(|builder| { builder .with_key(WellKnownSymbolIndexes::ToStringTag.into()) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index aca1912ce..78aa8c891 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -12,7 +12,8 @@ use crate::{ ecmascript::{ abstract_operations::type_conversion::{PreferredType, to_primitive_object}, builtins::{ - ordinary::ordinary_create_from_constructor, temporal::duration::to_temporal_duration, + ordinary::ordinary_create_from_constructor, + temporal::{duration::to_temporal_duration, error::temporal_err_to_js_err}, }, execution::{ JsResult, ProtoIntrinsics, @@ -33,11 +34,11 @@ use crate::{ }, }; -use self::data::InstantHeapData; +use self::data::InstantRecord; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct TemporalInstant<'a>(BaseIndex<'a, InstantHeapData<'static>>); +pub struct TemporalInstant<'a>(BaseIndex<'a, InstantRecord<'static>>); impl TemporalInstant<'_> { pub(crate) fn inner_instant(self, agent: &Agent) -> &temporal_rs::Instant { @@ -111,7 +112,7 @@ impl<'a> InternalMethods<'a> for TemporalInstant<'a> {} // TODO: get rid of Index impls, replace with get/get_mut/get_direct/get_direct_mut functions impl Index> for Agent { - type Output = InstantHeapData<'static>; + type Output = InstantRecord<'static>; fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { &self.heap.instants[index] @@ -124,8 +125,8 @@ impl IndexMut> for Agent { } } -impl Index> for Vec> { - type Output = InstantHeapData<'static>; +impl Index> for Vec> { + type Output = InstantRecord<'static>; fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { self.get(index.get_index()) @@ -133,7 +134,7 @@ impl Index> for Vec> { } } -impl IndexMut> for Vec> { +impl IndexMut> for Vec> { fn index_mut(&mut self, index: TemporalInstant<'_>) -> &mut Self::Output { self.get_mut(index.get_index()) .expect("heap access out of bounds") @@ -178,11 +179,11 @@ impl HeapSweepWeakReference for TemporalInstant<'static> { } } -impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { - fn create(&mut self, data: InstantHeapData<'a>) -> TemporalInstant<'a> { +impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { + fn create(&mut self, data: InstantRecord<'a>) -> TemporalInstant<'a> { self.instants.push(data.unbind()); - self.alloc_counter += core::mem::size_of::>(); - TemporalInstant(BaseIndex::last_t(&self.instants)) + self.alloc_counter += core::mem::size_of::>(); + TemporalInstant(BaseIndex::last(&self.instants)) } } @@ -229,26 +230,38 @@ fn create_temporal_instant<'gc>( fn to_temporal_instant<'gc>( agent: &mut Agent, item: Value, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, temporal_rs::Instant> { let item = item.bind(gc.nogc()); // 1. If item is an Object, then let item = if let Ok(item) = Object::try_from(item) { // a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] - // internal slot, then TODO: TemporalZonedDateTime::try_from(item) + // internal slot, then + // TODO: TemporalZonedDateTime::try_from(item) if let Ok(item) = TemporalInstant::try_from(item) { // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]). return Ok(agent[item].instant); } // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted. // c. Set item to ? ToPrimitive(item, string). - to_primitive_object(agent, item.unbind(), Some(PreferredType::String), gc)? + to_primitive_object( + agent, + item.unbind(), + Some(PreferredType::String), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()) } else { Primitive::try_from(item).unwrap() }; // 2. If item is not a String, throw a TypeError exception. let Ok(item) = String::try_from(item) else { - todo!() // TypeErrror + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Item is not a String", + gc.into_nogc(), + )); }; // 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »). // 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or @@ -264,7 +277,8 @@ fn to_temporal_instant<'gc>( // 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced). // 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. // 11. Return ! CreateTemporalInstant(epochNanoseconds). - let parsed = temporal_rs::Instant::from_utf8(item.as_bytes(agent)).unwrap(); + let parsed = temporal_rs::Instant::from_utf8(item.as_bytes(agent)) + .map_err(|e| temporal_err_to_js_err(agent, e, gc.into_nogc()))?; Ok(parsed) } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs index ed23293f9..cfe8f385e 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -96,7 +96,6 @@ impl TemporalInstantConstructor { new_target = unsafe { scoped_new_target.take(agent) }.bind(gc.nogc()); epoch_nanoseconds }; - // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. let Some(epoch_nanoseconds) = epoch_nanoseconds .try_into_i128(agent) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index ec06fd5c8..fbb6bb00b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -1,16 +1,13 @@ -use std::num::NonZeroU32; - -use temporal_rs::options::{RoundingIncrement, RoundingMode, RoundingOptions, Unit}; +use temporal_rs::options::{RoundingMode, RoundingOptions, Unit}; use crate::{ ecmascript::{ abstract_operations::{ - operations_on_objects::{get, try_create_data_property_or_throw}, - type_conversion::to_number, + operations_on_objects::try_create_data_property_or_throw, type_conversion::to_number, }, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ - ArgumentsList, Behaviour, Builtin, + ArgumentsList, Behaviour, Builtin, BuiltinGetter, ordinary::ordinary_object_create_with_intrinsics, temporal::{ error::temporal_err_to_js_err, @@ -18,6 +15,9 @@ use crate::{ add_duration_to_instant, create_temporal_instant, difference_temporal_instant, require_internal_slot_temporal_instant, to_temporal_instant, }, + options::{ + get_options_object, get_rounding_increment_option, get_rounding_mode_option, + }, }, }, execution::{ @@ -37,19 +37,25 @@ pub(crate) struct TemporalInstantPrototype; struct TemporalInstantPrototypeGetEpochMilliseconds; impl Builtin for TemporalInstantPrototypeGetEpochMilliseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochMilliseconds; + const NAME: String<'static> = BUILTIN_STRING_MEMORY.get_epochMilliseconds; + const KEY: Option> = + Some(BUILTIN_STRING_MEMORY.epochMilliseconds.to_property_key()); const LENGTH: u8 = 0; const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::get_epoch_milliseconds); } +impl BuiltinGetter for TemporalInstantPrototypeGetEpochMilliseconds {} struct TemporalInstantPrototypeGetEpochNanoSeconds; impl Builtin for TemporalInstantPrototypeGetEpochNanoSeconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.getEpochNanoSeconds; + const NAME: String<'static> = BUILTIN_STRING_MEMORY.get_epochNanoseconds; + const KEY: Option> = + Some(BUILTIN_STRING_MEMORY.epochNanoseconds.to_property_key()); const LENGTH: u8 = 0; const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::get_epoch_nanoseconds); } +impl BuiltinGetter for TemporalInstantPrototypeGetEpochNanoSeconds {} struct TemporalInstantPrototypeAdd; impl Builtin for TemporalInstantPrototypeAdd { @@ -161,7 +167,7 @@ impl TemporalInstantPrototype { let instant = require_internal_slot_temporal_instant(agent, this_value, gc)?; // 3. Return instant.[[EpochNanoseconds]]. let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); - Ok(BigInt::from_i128(agent, value).into()) + Ok(BigInt::from_i128(agent, value, gc).into()) } /// ### [8.3.5 Temporal.Instant.prototype.add ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add) @@ -467,8 +473,8 @@ impl TemporalInstantPrototype { .with_property_capacity(15) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) - .with_builtin_function_property::() - .with_builtin_function_property::() + .with_builtin_function_getter_property::() + .with_builtin_function_getter_property::() .with_builtin_function_property::() .with_builtin_function_property::() .with_builtin_function_property::() @@ -492,120 +498,13 @@ impl TemporalInstantPrototype { } } -/// ### [14.5.2.1 GetOptionsObject ( options )](https://tc39.es/proposal-temporal/#sec-getoptionsobject) -/// -/// The abstract operation GetOptionsObject takes argument options (an ECMAScript language value) -/// and returns either a normal completion containing an Object or a throw completion. It returns -/// an Object suitable for use with GetOption, either options itself or a default empty Object. It -/// throws a TypeError if options is not undefined and not an Object. It performs the following -/// steps when called: -fn get_options_object<'gc>( - agent: &mut Agent, - options: Value, - gc: NoGcScope<'gc, '_>, -) -> JsResult<'gc, Object<'gc>> { - match options.unbind() { - // 1. If options is undefined, then - Value::Undefined => { - // a. Return OrdinaryObjectCreate(null). - Ok(ordinary_object_create_with_intrinsics( - agent, None, None, gc, - )) - } - // 2. If options is an Object, then - Value::Object(obj) => { - // a. Return options. - Ok(obj.into()) - } - // 3. Throw a TypeError exception. - _ => Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, - "options provided to GetOptionsObject is not an object", - gc, - )), - } -} - -trivially_bindable!(RoundingMode); - -/// ### [14.5.2.3 GetRoundingModeOption ( options, fallback)](https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption) -/// -// The abstract operation GetRoundingModeOption takes arguments options (an Object) and fallback (a -// rounding mode) and returns either a normal completion containing a rounding mode, or a throw -// completion. It fetches and validates the "roundingMode" property from options, returning -// fallback as a default if absent. It performs the following steps when called: -fn get_rounding_mode_option<'gc>( - _agent: &mut Agent, - options: Object, - fallback: RoundingMode, - gc: GcScope<'gc, '_>, -) -> JsResult<'gc, RoundingMode> { - let _options = options.bind(gc.nogc()); - let _fallback = fallback.bind(gc.nogc()); - // 1. Let allowedStrings be the List of Strings from the "String Identifier" column of Table 28. - // 2. Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column. - // 3. Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback). - // 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column. - todo!() -} - -trivially_bindable!(RoundingIncrement); - -/// ### [14.5.2.4 GetRoundingIncrementOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption) -/// -/// The abstract operation GetRoundingIncrementOption takes argument options (an Object) and returns -/// either a normal completion containing a positive integer in the inclusive interval from 1 to -/// 10**9, or a throw completion. It fetches and validates the "roundingIncrement" property from -/// options, returning a default if absent. It performs the following steps when called: -fn get_rounding_increment_option<'gc>( - agent: &mut Agent, - options: Object, - mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, RoundingIncrement> { - let options = options.bind(gc.nogc()); - // 1. Let value be ? Get(options, "roundingIncrement"). - let value = get( - agent, - options.unbind(), - BUILTIN_STRING_MEMORY.roundingIncrement.into(), - gc.reborrow(), - ) - .unbind()?; - // 2. If value is undefined, return 1𝔽. - if value.is_undefined() { - return Ok(RoundingIncrement::default()); - } - // 3. Let integerIncrement be ? ToIntegerWithTruncation(value). - let integer_increment = to_integer_with_truncation(agent, value, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - - // 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception. - if integer_increment < 1.0 || integer_increment > 1_000_000_000.0 { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "roundingIncrement must be between 1 and 10**9", - gc.into_nogc(), - )); - } - - // Convert safely and return integerIncrement - // NOTE: `as u32` is safe here since we validated it’s in range. - let integer_increment_u32 = integer_increment as u32; - let increment = - NonZeroU32::new(integer_increment_u32).expect("integer_increment >= 1 ensures nonzero"); - - // 5. Return integerIncrement. - Ok(RoundingIncrement::new_unchecked(increment)) -} - /// ### [13.40 ToIntegerWithTruncation ( argument )] (https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation) /// /// The abstract operation ToIntegerWithTruncation takes argument argument (an ECMAScript language /// value) and returns either a normal completion containing an integer or a throw completion. It /// converts argument to an integer representing its Number value with fractional part truncated, or /// throws a RangeError when that value is not finite. It performs the following steps when called: -fn to_integer_with_truncation<'gc>( +pub(crate) fn to_integer_with_truncation<'gc>( agent: &mut Agent, argument: Value, mut gc: GcScope<'gc, '_>, @@ -637,7 +536,7 @@ trivially_bindable!(Unit); /// property key), and default (required or unset) and returns either a normal completion /// containing either a Temporal unit, unset, or auto, or a throw completion. It attempts to read a /// Temporal unit from the specified property of options. -fn get_temporal_unit_valued_option<'gc>( +pub(crate) fn get_temporal_unit_valued_option<'gc>( _agent: &mut Agent, options: Object, key: PropertyKey, @@ -646,11 +545,12 @@ fn get_temporal_unit_valued_option<'gc>( ) -> JsResult<'gc, Unit> { let _options = options.bind(gc.nogc()); let _default = default.bind(gc.nogc()); + let _key = key.bind(gc.nogc()); todo!() } #[allow(dead_code)] -enum DefaultOption { +pub(crate) enum DefaultOption { Required, Unset, } diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs new file mode 100644 index 000000000..4e1d6b94a --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -0,0 +1,184 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::num::NonZeroU32; + +use temporal_rs::options::{RoundingIncrement, RoundingMode}; + +use crate::{ + ecmascript::{ + abstract_operations::{ + operations_on_objects::get, + type_conversion::{to_boolean, to_string}, + }, + builtins::{ + ordinary::ordinary_object_create_with_intrinsics, + temporal::instant::instant_prototype::to_integer_with_truncation, + }, + execution::{Agent, JsResult, agent::ExceptionType}, + types::{BUILTIN_STRING_MEMORY, Object, PropertyKey, String, Value}, + }, + engine::context::{Bindable, GcScope, NoGcScope, trivially_bindable}, +}; + +pub(crate) enum OptionType { + Boolean(bool), + String(String<'static>), +} + +trivially_bindable!(OptionType); +trivially_bindable!(RoundingMode); +trivially_bindable!(RoundingIncrement); + +/// ### [14.5.2.1 GetOptionsObject ( options )](https://tc39.es/proposal-temporal/#sec-getoptionsobject) +/// +/// The abstract operation GetOptionsObject takes argument options (an ECMAScript language value) +/// and returns either a normal completion containing an Object or a throw completion. It returns +/// an Object suitable for use with GetOption, either options itself or a default empty Object. It +/// throws a TypeError if options is not undefined and not an Object. It performs the following +/// steps when called: +pub(crate) fn get_options_object<'gc>( + agent: &mut Agent, + options: Value, + gc: NoGcScope<'gc, '_>, +) -> JsResult<'gc, Object<'gc>> { + match options.unbind() { + // 1. If options is undefined, then + Value::Undefined => { + // a. Return OrdinaryObjectCreate(null). + Ok(ordinary_object_create_with_intrinsics( + agent, None, None, gc, + )) + } + // 2. If options is an Object, then + Value::Object(obj) => { + // a. Return options. + Ok(obj.into()) + } + // 3. Throw a TypeError exception. + _ => Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "options provided to GetOptionsObject is not an object", + gc, + )), + } +} + +/// ### [14.5.2.2 GetOption ( options, property, type, values, default )](https://tc39.es/proposal-temporal/#sec-getoption) +/// +/// The abstract operation GetOption takes arguments options (an Object), property (a property +/// key), type (boolean or string), values (empty or a List of ECMAScript language values), and +/// default (required or an ECMAScript language value) and returns either a normal completion +/// containing an ECMAScript language value or a throw completion. It extracts the value of the +/// specified property of options, converts it to the required type, checks whether it is allowed +/// by values if values is not empty, and substitutes default if the value is undefined. It +/// performs the following steps when called: +pub(crate) fn get_option<'gc>( + agent: &mut Agent, + options: Object, + property: PropertyKey, + typee: OptionType, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, OptionType> { + let options = options.bind(gc.nogc()); + let property = property.bind(gc.nogc()); + // 1. Let value be ? Get(options, property). + let value = get(agent, options.unbind(), property.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // 2. If value is undefined, then + if value.is_undefined() { + // a. If default is required, throw a RangeError exception. + // b. Return default. + todo!() + } + match typee { + // 3. If type is boolean, then + OptionType::Boolean(_) => { + // a. Set value to ToBoolean(value). + let value = to_boolean(agent, value); + } + // 4. Else, + OptionType::String(_) => { + // a. Assert: type is string. + // b. Set value to ? ToString(value). + let value = to_string(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + } + } + + // 5. If values is not empty and values does not contain value, throw a RangeError exception. + // 6. Return value. + todo!() +} + +/// ### [14.5.2.3 GetRoundingModeOption ( options, fallback)](https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption) +/// +// The abstract operation GetRoundingModeOption takes arguments options (an Object) and fallback (a +// rounding mode) and returns either a normal completion containing a rounding mode, or a throw +// completion. It fetches and validates the "roundingMode" property from options, returning +// fallback as a default if absent. It performs the following steps when called: +pub(crate) fn get_rounding_mode_option<'gc>( + _agent: &mut Agent, + options: Object, + fallback: RoundingMode, + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, RoundingMode> { + let _options = options.bind(gc.nogc()); + let _fallback = fallback.bind(gc.nogc()); + // 1. Let allowedStrings be the List of Strings from the "String Identifier" column of Table 28. + // 2. Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column. + // 3. Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback). + // 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column. + todo!() +} + +/// ### [14.5.2.4 GetRoundingIncrementOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption) +/// +/// The abstract operation GetRoundingIncrementOption takes argument options (an Object) and returns +/// either a normal completion containing a positive integer in the inclusive interval from 1 to +/// 10**9, or a throw completion. It fetches and validates the "roundingIncrement" property from +/// options, returning a default if absent. It performs the following steps when called: +pub(crate) fn get_rounding_increment_option<'gc>( + agent: &mut Agent, + options: Object, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, RoundingIncrement> { + let options = options.bind(gc.nogc()); + // 1. Let value be ? Get(options, "roundingIncrement"). + let value = get( + agent, + options.unbind(), + BUILTIN_STRING_MEMORY.roundingIncrement.into(), + gc.reborrow(), + ) + .unbind()?; + // 2. If value is undefined, return 1𝔽. + if value.is_undefined() { + return Ok(RoundingIncrement::default()); + } + // 3. Let integerIncrement be ? ToIntegerWithTruncation(value). + let integer_increment = to_integer_with_truncation(agent, value, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + // 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception. + if integer_increment < 1.0 || integer_increment > 1_000_000_000.0 { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "roundingIncrement must be between 1 and 10**9", + gc.into_nogc(), + )); + } + + // Convert safely and return integerIncrement + // NOTE: `as u32` is safe here since we validated it’s in range. + let integer_increment_u32 = integer_increment as u32; + let increment = + NonZeroU32::new(integer_increment_u32).expect("integer_increment >= 1 ensures nonzero"); + + // 5. Return integerIncrement. + Ok(RoundingIncrement::new_unchecked(increment)) +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs new file mode 100644 index 000000000..24c60bb41 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -0,0 +1,126 @@ +use std::ops::{Index, IndexMut}; + +use crate::{ + ecmascript::{ + builtins::temporal::plain_time::data::PlainTimeHeapData, + execution::{Agent, ProtoIntrinsics}, + types::{InternalMethods, InternalSlots, Object, OrdinaryObject, Value}, + }, + engine::context::bindable_handle, + heap::{ + CompactionLists, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, indexes::BaseIndex, + }, +}; + +pub(crate) mod data; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct TemporalPlainTime<'a>(BaseIndex<'a, PlainTimeHeapData<'static>>); + +impl TemporalPlainTime<'_> { + pub(crate) fn inner_plain_date_time(self, agent: &Agent) -> &temporal_rs::PlainTime { + &agent[self].plain_time + } + + //TODO + pub(crate) const fn _def() -> Self { + TemporalPlainTime(BaseIndex::from_u32_index(0)) + } + + pub(crate) const fn get_index(self) -> usize { + self.0.into_index() + } +} + +bindable_handle!(TemporalPlainTime); + +impl<'a> From> for Value<'a> { + fn from(value: TemporalPlainTime<'a>) -> Self { + Value::PlainTime(value) + } +} +impl<'a> From> for Object<'a> { + fn from(value: TemporalPlainTime<'a>) -> Self { + Object::PlainTime(value) + } +} +impl<'a> TryFrom> for TemporalPlainTime<'a> { + type Error = (); + + fn try_from(value: Value<'a>) -> Result { + match value { + Value::PlainTime(idx) => Ok(idx), + _ => Err(()), + } + } +} +impl<'a> TryFrom> for TemporalPlainTime<'a> { + type Error = (); + + fn try_from(object: Object<'a>) -> Result { + match object { + Object::PlainTime(idx) => Ok(idx), + _ => Err(()), + } + } +} + +impl<'a> InternalSlots<'a> for TemporalPlainTime<'a> { + const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalPlainTime; + fn get_backing_object(self, agent: &Agent) -> Option> { + agent[self].object_index + } + fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { + assert!(agent[self].object_index.replace(backing_object).is_none()); + } +} + +impl<'a> InternalMethods<'a> for TemporalPlainTime<'a> {} + +// TODO: get rid of Index impls, replace with get/get_mut/get_direct/get_direct_mut functions +impl Index> for Agent { + type Output = PlainTimeHeapData<'static>; + + fn index(&self, index: TemporalPlainTime<'_>) -> &Self::Output { + &self.heap.plain_times[index] + } +} + +impl IndexMut> for Agent { + fn index_mut(&mut self, index: TemporalPlainTime<'_>) -> &mut Self::Output { + &mut self.heap.plain_times[index] + } +} + +impl Index> for Vec> { + type Output = PlainTimeHeapData<'static>; + + fn index(&self, index: TemporalPlainTime<'_>) -> &Self::Output { + self.get(index.get_index()) + .expect("heap acess out of bounds") + } +} + +impl IndexMut> for Vec> { + fn index_mut(&mut self, index: TemporalPlainTime<'_>) -> &mut Self::Output { + self.get_mut(index.get_index()) + .expect("heap access out of bounds") + } +} + +impl HeapMarkAndSweep for TemporalPlainTime<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.plain_times.push(*self); + } + + fn sweep_values(&mut self, compactions: &CompactionLists) { + compactions.plain_times.shift_index(&mut self.0); + } +} + +impl HeapSweepWeakReference for TemporalPlainTime<'static> { + fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { + compactions.plain_times.shift_weak_index(self.0).map(Self) + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs new file mode 100644 index 000000000..0c260e60e --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs @@ -0,0 +1,47 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::engine::context::NoGcScope; +use crate::heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}; +use crate::{ + ecmascript::types::OrdinaryObject, + engine::context::{bindable_handle, trivially_bindable}, +}; + +#[derive(Debug, Clone, Copy)] +pub struct PlainTimeHeapData<'a> { + pub(crate) object_index: Option>, + pub(crate) plain_time: temporal_rs::PlainTime, +} + +impl PlainTimeHeapData<'_> { + pub fn default() -> Self { + Self { + object_index: None, + plain_time: temporal_rs::PlainTime::new(0, 0, 0, 0, 0, 0).unwrap(), + } + } +} + +trivially_bindable!(temporal_rs::PlainTime); +bindable_handle!(PlainTimeHeapData); + +impl HeapMarkAndSweep for PlainTimeHeapData<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + object_index, + plain_time: _, + } = self; + + object_index.mark_values(queues); + } + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + object_index, + plain_time: _, + } = self; + + object_index.sweep_values(compactions); + } +} diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 7a84a5671..1b329e9c1 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,7 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - TemporalObject, + Temporal, instant::{ instant_constructor::TemporalInstantConstructor, instant_prototype::TemporalInstantPrototype, @@ -237,6 +237,8 @@ pub enum ProtoIntrinsics { SyntaxError, #[cfg(feature = "temporal")] TemporalInstant, + #[cfg(feature = "temporal")] + TemporalPlainTime, TypeError, #[cfg(feature = "array-buffer")] Uint16Array, @@ -319,11 +321,11 @@ impl Intrinsics { MathObject::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] - TemporalObject::create_intrinsic(agent, realm, gc); - #[cfg(feature = "temporal")] - TemporalInstantConstructor::create_intrinsic(agent, realm, gc); + Temporal::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] TemporalInstantPrototype::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalInstantConstructor::create_intrinsic(agent, realm, gc); #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); @@ -434,7 +436,10 @@ impl Intrinsics { ProtoIntrinsics::String => self.string().into(), ProtoIntrinsics::Symbol => self.symbol().into(), ProtoIntrinsics::SyntaxError => self.syntax_error().into(), + #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => self.temporal_instant().into(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalPlainTime => self.temporal_plain_time().into(), ProtoIntrinsics::TypeError => self.type_error().into(), ProtoIntrinsics::URIError => self.uri_error().into(), ProtoIntrinsics::AggregateError => self.aggregate_error().into(), @@ -524,7 +529,10 @@ impl Intrinsics { ProtoIntrinsics::String => self.string_prototype().into(), ProtoIntrinsics::Symbol => self.symbol_prototype().into(), ProtoIntrinsics::SyntaxError => self.syntax_error_prototype().into(), - ProtoIntrinsics::TemporalInstant => self.temporal().into(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => self.temporal_instant_prototype().into(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalPlainTime => self.temporal_plain_time_prototype().into(), ProtoIntrinsics::TypeError => self.type_error_prototype().into(), ProtoIntrinsics::URIError => self.uri_error_prototype().into(), ProtoIntrinsics::AggregateError => self.aggregate_error_prototype().into(), @@ -1033,7 +1041,7 @@ impl Intrinsics { /// %Temporal% pub(crate) const fn temporal(&self) -> OrdinaryObject<'static> { - IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) + IntrinsicObjectIndexes::Temporal.get_backing_object(self.object_index_base) } /// %Temporal.Instant.Prototype% @@ -1047,6 +1055,18 @@ impl Intrinsics { .get_builtin_function(self.builtin_function_index_base) } + /// %Temporal.PlainTime.prototype% + pub(crate) const fn temporal_plain_time_prototype(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalPlainTimePrototype + .get_backing_object(self.object_index_base) + } + + /// %Temporal.PlainTime% + pub(crate) const fn temporal_plain_time(&self) -> BuiltinFunction<'static> { + IntrinsicConstructorIndexes::TemporalPlainTime + .get_builtin_function(self.builtin_function_index_base) + } + /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { IntrinsicPrimitiveObjectIndexes::NumberPrototype diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index 42b931ee3..89483a637 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -16,7 +16,8 @@ use crate::ecmascript::types::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::{ - builtins::temporal::instant::TemporalInstant, types::INSTANT_DISCRIMINANT, + builtins::temporal::{instant::TemporalInstant, plain_time::TemporalPlainTime}, + types::{INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; @@ -147,6 +148,8 @@ pub(crate) enum WeakKey<'a> { Date(Date<'a>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, + #[cfg(feature = "temporal")] + PlainTime(TemporalPlainTime<'a>) = PLAIN_TIME_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -262,6 +265,8 @@ impl<'a> From> for Value<'a> { WeakKey::Date(d) => Self::Date(d), #[cfg(feature = "temporal")] WeakKey::Instant(d) => Self::Instant(d), + #[cfg(feature = "temporal")] + WeakKey::PlainTime(d) => Self::PlainTime(d), WeakKey::Error(d) => Self::Error(d), WeakKey::FinalizationRegistry(d) => Self::FinalizationRegistry(d), WeakKey::Map(d) => Self::Map(d), @@ -371,6 +376,8 @@ impl<'a> From> for WeakKey<'a> { Object::Date(d) => Self::Date(d), #[cfg(feature = "temporal")] Object::Instant(d) => Self::Instant(d), + #[cfg(feature = "temporal")] + Object::PlainTime(d) => Self::PlainTime(d), Object::Error(d) => Self::Error(d), Object::FinalizationRegistry(d) => Self::FinalizationRegistry(d), Object::Map(d) => Self::Map(d), @@ -484,6 +491,8 @@ impl<'a> TryFrom> for Object<'a> { WeakKey::Date(d) => Ok(Self::Date(d)), #[cfg(feature = "temporal")] WeakKey::Instant(d) => Ok(Self::Instant(d)), + #[cfg(feature = "temporal")] + WeakKey::PlainTime(d) => Ok(Self::PlainTime(d)), WeakKey::Error(d) => Ok(Self::Error(d)), WeakKey::FinalizationRegistry(d) => Ok(Self::FinalizationRegistry(d)), WeakKey::Map(d) => Ok(Self::Map(d)), @@ -628,6 +637,8 @@ impl HeapMarkAndSweep for WeakKey<'static> { Self::Date(d) => d.mark_values(queues), #[cfg(feature = "temporal")] Self::Instant(d) => d.mark_values(queues), + #[cfg(feature = "temporal")] + Self::PlainTime(d) => d.mark_values(queues), Self::Error(d) => d.mark_values(queues), Self::FinalizationRegistry(d) => d.mark_values(queues), Self::Map(d) => d.mark_values(queues), @@ -735,6 +746,8 @@ impl HeapMarkAndSweep for WeakKey<'static> { Self::Date(d) => d.sweep_values(compactions), #[cfg(feature = "temporal")] Self::Instant(d) => d.sweep_values(compactions), + #[cfg(feature = "temporal")] + Self::PlainTime(d) => d.sweep_values(compactions), Self::Error(d) => d.sweep_values(compactions), Self::FinalizationRegistry(d) => d.sweep_values(compactions), Self::Map(d) => d.sweep_values(compactions), @@ -858,6 +871,8 @@ impl HeapSweepWeakReference for WeakKey<'static> { Self::Date(data) => data.sweep_weak_reference(compactions).map(Self::Date), #[cfg(feature = "temporal")] Self::Instant(data) => data.sweep_weak_reference(compactions).map(Self::Instant), + #[cfg(feature = "temporal")] + Self::PlainTime(data) => data.sweep_weak_reference(compactions).map(Self::PlainTime), Self::Error(data) => data.sweep_weak_reference(compactions).map(Self::Error), Self::FinalizationRegistry(data) => data .sweep_weak_reference(compactions) diff --git a/nova_vm/src/ecmascript/types/language/number.rs b/nova_vm/src/ecmascript/types/language/number.rs index 09c6cb476..3870e6830 100644 --- a/nova_vm/src/ecmascript/types/language/number.rs +++ b/nova_vm/src/ecmascript/types/language/number.rs @@ -16,7 +16,6 @@ use crate::{ ecmascript::{ abstract_operations::type_conversion::{to_int32_number, to_uint32_number}, execution::Agent, - types::BigInt, }, engine::{ context::{Bindable, NoGcScope, bindable_handle}, diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 66711b8b2..c4a3bba69 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -38,6 +38,10 @@ use super::{ use crate::ecmascript::builtins::date::Date; #[cfg(feature = "weak-refs")] use crate::ecmascript::builtins::{weak_map::WeakMap, weak_ref::WeakRef, weak_set::WeakSet}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{ + builtins::temporal::plain_time::TemporalPlainTime, types::PLAIN_TIME_DISCRIMINANT, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -182,6 +186,8 @@ pub enum Object<'a> { Date(Date<'a>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, + #[cfg(feature = "temporal")] + PlainTime(TemporalPlainTime<'a>) = PLAIN_TIME_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -782,6 +788,8 @@ impl<'a> From> for Value<'a> { Object::Date(data) => Self::Date(data), #[cfg(feature = "temporal")] Object::Instant(data) => Value::Instant(data), + #[cfg(feature = "temporal")] + Object::PlainTime(data) => Value::PlainTime(data), Object::Error(data) => Self::Error(data), Object::FinalizationRegistry(data) => Self::FinalizationRegistry(data), Object::Map(data) => Self::Map(data), @@ -892,6 +900,8 @@ impl<'a> TryFrom> for Object<'a> { Value::Date(x) => Ok(Self::Date(x)), #[cfg(feature = "temporal")] Value::Instant(x) => Ok(Self::Instant(x)), + #[cfg(feature = "temporal")] + Value::PlainTime(x) => Ok(Self::PlainTime(x)), Value::Error(x) => Ok(Self::from(x)), Value::BoundFunction(x) => Ok(Self::from(x)), Value::BuiltinFunction(x) => Ok(Self::from(x)), @@ -1010,6 +1020,8 @@ macro_rules! object_delegate { Self::Date(data) => data.$method($($arg),+), #[cfg(feature = "temporal")] Object::Instant(data) => data.$method($($arg),+), + #[cfg(feature = "temporal")] + Object::PlainTime(data) => data.$method($($arg),+), Self::Error(data) => data.$method($($arg),+), Self::BoundFunction(data) => data.$method($($arg),+), Self::BuiltinFunction(data) => data.$method($($arg),+), @@ -1442,6 +1454,8 @@ impl HeapSweepWeakReference for Object<'static> { Self::Date(data) => data.sweep_weak_reference(compactions).map(Self::Date), #[cfg(feature = "temporal")] Self::Instant(data) => data.sweep_weak_reference(compactions).map(Self::Instant), + #[cfg(feature = "temporal")] + Self::PlainTime(data) => data.sweep_weak_reference(compactions).map(Self::PlainTime), Self::Error(data) => data.sweep_weak_reference(compactions).map(Self::Error), Self::BoundFunction(data) => data .sweep_weak_reference(compactions) @@ -1680,6 +1694,8 @@ impl TryFrom for Object<'_> { HeapRootData::Date(date) => Ok(Self::Date(date)), #[cfg(feature = "temporal")] HeapRootData::Instant(instant) => Ok(Self::Instant(instant)), + #[cfg(feature = "temporal")] + HeapRootData::PlainTime(plain_time) => Ok(Self::PlainTime(plain_time)), HeapRootData::Error(error) => Ok(Self::Error(error)), HeapRootData::FinalizationRegistry(finalization_registry) => { Ok(Self::FinalizationRegistry(finalization_registry)) diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index e63843624..5716dfaab 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -12,7 +12,9 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::TemporalInstant; +use crate::ecmascript::builtins::temporal::{ + instant::TemporalInstant, plain_time::TemporalPlainTime, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::builtins::typed_array::Float16Array; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -185,6 +187,8 @@ pub enum Value<'a> { Date(Date<'a>), #[cfg(feature = "temporal")] Instant(TemporalInstant<'a>), + #[cfg(feature = "temporal")] + PlainTime(TemporalPlainTime<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), Map(Map<'a>), @@ -324,6 +328,9 @@ pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_d #[cfg(feature = "temporal")] pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(TemporalInstant::_def())); +#[cfg(feature = "temporal")] +pub(crate) const PLAIN_TIME_DISCRIMINANT: u8 = + value_discriminant(Value::PlainTime(TemporalPlainTime::_def())); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_def())); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_def())); @@ -1000,6 +1007,8 @@ impl Rootable for Value<'_> { Self::Date(date) => Err(HeapRootData::Date(date.unbind())), #[cfg(feature = "temporal")] Self::Instant(instant) => Err(HeapRootData::Instant(instant.unbind())), + #[cfg(feature = "temporal")] + Self::PlainTime(plain_time) => Err(HeapRootData::PlainTime(plain_time.unbind())), Self::Error(error) => Err(HeapRootData::Error(error.unbind())), Self::FinalizationRegistry(finalization_registry) => Err( HeapRootData::FinalizationRegistry(finalization_registry.unbind()), @@ -1163,6 +1172,8 @@ impl Rootable for Value<'_> { HeapRootData::Date(date) => Some(Self::Date(date)), #[cfg(feature = "temporal")] HeapRootData::Instant(instant) => Some(Self::Instant(instant)), + #[cfg(feature = "temporal")] + HeapRootData::PlainTime(plain_time) => Some(Self::PlainTime(plain_time)), HeapRootData::Error(error) => Some(Self::Error(error)), HeapRootData::FinalizationRegistry(finalization_registry) => { Some(Self::FinalizationRegistry(finalization_registry)) @@ -1312,7 +1323,9 @@ impl HeapMarkAndSweep for Value<'static> { #[cfg(feature = "date")] Self::Date(dv) => dv.mark_values(queues), #[cfg(feature = "temporal")] - Self::Instant(dv) => dv.mark_values(queues), + Self::Instant(data) => data.mark_values(queues), + #[cfg(feature = "temporal")] + Self::PlainTime(data) => data.mark_values(queues), Self::Error(data) => data.mark_values(queues), Self::BoundFunction(data) => data.mark_values(queues), Self::BuiltinFunction(data) => data.mark_values(queues), @@ -1428,7 +1441,9 @@ impl HeapMarkAndSweep for Value<'static> { #[cfg(feature = "date")] Self::Date(data) => data.sweep_values(compactions), #[cfg(feature = "temporal")] - Self::Instant(dv) => dv.sweep_values(compactions), + Self::Instant(data) => data.sweep_values(compactions), + #[cfg(feature = "temporal")] + Self::PlainTime(data) => data.sweep_values(compactions), Self::Error(data) => data.sweep_values(compactions), Self::BoundFunction(data) => data.sweep_values(compactions), Self::BuiltinFunction(data) => data.sweep_values(compactions), @@ -1582,6 +1597,8 @@ fn map_object_to_static_string_repr(value: Value) -> String<'static> { Object::Date(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "temporal")] Object::Instant(_) => BUILTIN_STRING_MEMORY._object_Object_, + #[cfg(feature = "temporal")] + Object::PlainTime(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "set")] Object::Set(_) | Object::SetIterator(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "weak-refs")] diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 50c157081..d903f1ae6 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1328,6 +1328,8 @@ fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> String<'static> Value::Date(_) => BUILTIN_STRING_MEMORY.object, #[cfg(feature = "temporal")] Value::Instant(_) => BUILTIN_STRING_MEMORY.object, + #[cfg(feature = "temporal")] + Value::PlainTime(_) => BUILTIN_STRING_MEMORY.object, // 13. If val has a [[Call]] internal slot, return "function". Value::BoundFunction(_) | Value::BuiltinFunction(_) | Value::ECMAScriptFunction(_) | Value::BuiltinConstructorFunction(_) | diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index ff7f4c408..dcffc4d15 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -19,7 +19,8 @@ use crate::ecmascript::types::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::{ - builtins::temporal::instant::TemporalInstant, types::INSTANT_DISCRIMINANT, + builtins::temporal::{instant::TemporalInstant, plain_time::TemporalPlainTime}, + types::{INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; @@ -554,6 +555,8 @@ pub enum HeapRootData { Date(Date<'static>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] Instant(TemporalInstant<'static>) = INSTANT_DISCRIMINANT, + #[cfg(feature = "temporal")] + PlainTime(TemporalPlainTime<'static>) = PLAIN_TIME_DISCRIMINANT, Error(Error<'static>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'static>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'static>) = MAP_DISCRIMINANT, @@ -690,6 +693,8 @@ impl From> for HeapRootData { Object::Date(date) => Self::Date(date), #[cfg(feature = "temporal")] Object::Instant(instant) => Self::Instant(instant), + #[cfg(feature = "temporal")] + Object::PlainTime(plain_time) => Self::PlainTime(plain_time), Object::Error(error) => Self::Error(error), Object::FinalizationRegistry(finalization_registry) => { Self::FinalizationRegistry(finalization_registry) @@ -851,6 +856,8 @@ impl HeapMarkAndSweep for HeapRootData { Self::Date(date) => date.mark_values(queues), #[cfg(feature = "temporal")] Self::Instant(instant) => instant.mark_values(queues), + #[cfg(feature = "temporal")] + Self::PlainTime(plain_time) => plain_time.mark_values(queues), Self::Error(error) => error.mark_values(queues), Self::FinalizationRegistry(finalization_registry) => { finalization_registry.mark_values(queues) @@ -1001,7 +1008,9 @@ impl HeapMarkAndSweep for HeapRootData { #[cfg(feature = "date")] Self::Date(date) => date.sweep_values(compactions), #[cfg(feature = "temporal")] - Self::Instant(date) => date.sweep_values(compactions), + Self::Instant(instant) => instant.sweep_values(compactions), + #[cfg(feature = "temporal")] + Self::PlainTime(plain_time) => plain_time.sweep_values(compactions), Self::Error(error) => error.sweep_values(compactions), Self::FinalizationRegistry(finalization_registry) => { finalization_registry.sweep_values(compactions) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 8ee6fe8ba..04fffd240 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -30,7 +30,9 @@ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; +use crate::ecmascript::builtins::temporal::{ + instant::data::InstantRecord, plain_time::data::PlainTimeHeapData, +}; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, @@ -149,6 +151,8 @@ pub(crate) struct Heap { pub(crate) dates: Vec>, #[cfg(feature = "temporal")] pub(crate) instants: Vec>, + #[cfg(feature = "temporal")] + pub(crate) plain_times: Vec>, pub(crate) ecmascript_functions: Vec>, /// ElementsArrays is where all keys and values arrays live; /// Element arrays are static arrays of Values plus @@ -296,8 +300,11 @@ impl Heap { caches: Caches::with_capacity(1024), #[cfg(feature = "date")] dates: Vec::with_capacity(1024), + // TODO: assign appropriate value for Temporal objects. + #[cfg(feature = "temporal")] + instants: Vec::with_capacity(1024), #[cfg(feature = "temporal")] - instants: Vec::with_capacity(1024), // todo: assign appropriate value for instants + plain_times: Vec::with_capacity(1024), ecmascript_functions: Vec::with_capacity(1024), elements: ElementArrays { e2pow1: ElementArray2Pow1::with_capacity(1024), diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index f67cda379..922517b05 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -72,6 +72,7 @@ use crate::ecmascript::{ promise_group_record::PromiseGroup, }, proxy::Proxy, + temporal::plain_time::TemporalPlainTime, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{ @@ -457,6 +458,8 @@ pub(crate) struct HeapBits { pub(super) dates: BitRange, #[cfg(feature = "temporal")] pub(super) instants: BitRange, + #[cfg(feature = "temporal")] + pub(super) plain_times: BitRange, pub(super) declarative_environments: BitRange, pub(super) ecmascript_functions: BitRange, pub(super) embedder_objects: BitRange, @@ -535,6 +538,7 @@ pub(crate) struct WorkQueues<'a> { pub(crate) dates: Vec>, #[cfg(feature = "temporal")] pub(crate) instants: Vec>, + pub(crate) plain_times: Vec>, pub(crate) declarative_environments: Vec>, pub(crate) e_2_1: Vec>, pub(crate) e_2_2: Vec>, @@ -684,7 +688,10 @@ impl HeapBits { let data_views = BitRange::from_bit_count_and_len(&mut bit_count, heap.data_views.len()); #[cfg(feature = "date")] let dates = BitRange::from_bit_count_and_len(&mut bit_count, heap.dates.len()); + #[cfg(feature = "date")] let instants = BitRange::from_bit_count_and_len(&mut bit_count, heap.instants.len()); + #[cfg(feature = "date")] + let plain_times = BitRange::from_bit_count_and_len(&mut bit_count, heap.plain_times.len()); let declarative_environments = BitRange::from_bit_count_and_len(&mut bit_count, heap.environments.declarative.len()); let ecmascript_functions = @@ -794,6 +801,8 @@ impl HeapBits { dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, declarative_environments, e_2_1, e_2_2, @@ -904,7 +913,10 @@ impl HeapBits { WeakKey::Array(d) => self.arrays.get_bit(d.get_index(), &self.bits), #[cfg(feature = "date")] WeakKey::Date(d) => self.dates.get_bit(d.get_index(), &self.bits), + #[cfg(feature = "temporal")] WeakKey::Instant(d) => self.instants.get_bit(d.get_index(), &self.bits), + #[cfg(feature = "temporal")] + WeakKey::PlainTime(d) => self.plain_times.get_bit(d.get_index(), &self.bits), WeakKey::Error(d) => self.errors.get_bit(d.get_index(), &self.bits), WeakKey::FinalizationRegistry(d) => self .finalization_registrys @@ -1044,6 +1056,8 @@ impl<'a> WorkQueues<'a> { dates: Vec::with_capacity(heap.dates.len() / 4), #[cfg(feature = "temporal")] instants: Vec::with_capacity(heap.instants.len() / 4), + #[cfg(feature = "temporal")] + plain_times: Vec::with_capacity(heap.plain_times.len() / 4), declarative_environments: Vec::with_capacity(heap.environments.declarative.len() / 4), e_2_1: Vec::with_capacity(heap.elements.e2pow1.values.len() / 4), e_2_2: Vec::with_capacity(heap.elements.e2pow2.values.len() / 4), @@ -1152,6 +1166,8 @@ impl<'a> WorkQueues<'a> { dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, declarative_environments, e_2_1, e_2_2, @@ -1231,6 +1247,7 @@ impl<'a> WorkQueues<'a> { weak_sets, } = self; + #[cfg(not(feature = "temporal"))] #[cfg(not(feature = "date"))] let dates: &[bool; 0] = &[]; #[cfg(not(feature = "array-buffer"))] @@ -1272,6 +1289,7 @@ impl<'a> WorkQueues<'a> { && data_views.is_empty() && dates.is_empty() && instants.is_empty() + && plain_times.is_empty() && declarative_environments.is_empty() && e_2_1.is_empty() && e_2_2.is_empty() @@ -1633,6 +1651,8 @@ pub(crate) struct CompactionLists { pub(crate) dates: CompactionList, #[cfg(feature = "temporal")] pub(crate) instants: CompactionList, + #[cfg(feature = "temporal")] + pub(crate) plain_times: CompactionList, pub(crate) declarative_environments: CompactionList, pub(crate) e_2_1: CompactionList, pub(crate) e_2_2: CompactionList, @@ -1790,6 +1810,8 @@ impl CompactionLists { dates: CompactionList::from_mark_bits(&bits.dates, &bits.bits), #[cfg(feature = "temporal")] instants: CompactionList::from_mark_bits(&bits.instants, &bits.bits), + #[cfg(feature = "temporal")] + plain_times: CompactionList::from_mark_bits(&bits.plain_times, &bits.bits), errors: CompactionList::from_mark_bits(&bits.errors, &bits.bits), executables: CompactionList::from_mark_bits(&bits.executables, &bits.bits), maps: CompactionList::from_mark_bits(&bits.maps, &bits.bits), diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index 2e7341991..f957ab01d 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -35,9 +35,12 @@ pub(crate) enum IntrinsicObjectIndexes { #[cfg(feature = "date")] DatePrototype, #[cfg(feature = "temporal")] - TemporalObject, + Temporal, #[cfg(feature = "temporal")] TemporalInstantPrototype, + #[cfg(feature = "temporal")] + TemporalPlainTimePrototype, + // Text processing #[cfg(feature = "regexp")] RegExpPrototype, @@ -176,6 +179,8 @@ pub(crate) enum IntrinsicConstructorIndexes { Date, #[cfg(feature = "temporal")] TemporalInstant, + #[cfg(feature = "temporal")] + TemporalPlainTime, // Text processing String, diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index e3fb7ed48..ecd83ca66 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -20,6 +20,8 @@ use super::{ use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::instant::TemporalInstant; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::plain_time::TemporalPlainTime; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::VoidArray}; #[cfg(feature = "shared-array-buffer")] @@ -132,6 +134,8 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, ecmascript_functions, elements, embedder_objects, @@ -558,15 +562,21 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc instant_marks.sort(); instant_marks.iter().for_each(|&idx| { let index = idx.get_index(); - if let Some(marked) = bits.instants.get_mut(index) { - if *marked { - // Already marked, ignore - return; - } - *marked = true; + if bits.instants.set_bit(index, &bits.bits) { + // Did mark. instants.get(index).mark_values(&mut queues); } }); + let mut plain_time_marks: Box<[TemporalPlainTime]> = + queues.plain_times.drain(..).collect(); + plain_time_marks.sort(); + plain_time_marks.iter().for_each(|&idx| { + let index = idx.get_index(); + if bits.plain_times.set_bit(index, &bits.bits) { + // Did mark. + plain_times.get(index).mark_values(&mut queues); + } + }); } if !queues.embedder_objects.is_empty() { @@ -1273,6 +1283,8 @@ fn sweep( dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, ecmascript_functions, elements, embedder_objects, @@ -1732,6 +1744,12 @@ fn sweep( sweep_heap_vector_values(instants, &compactions, &bits.instants, &bits.bits); }); } + #[cfg(feature = "temporal")] + if !plain_times.is_empty() { + s.spawn(|| { + sweep_heap_vector_values(plain_times, &compactions, &bits.plain_times, &bits.bits); + }); + } if !declarative.is_empty() { s.spawn(|| { sweep_heap_vector_values( From 186855c5ea839c04696308a04641ab56f4deb75a Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Mon, 17 Nov 2025 20:28:58 +0100 Subject: [PATCH 33/42] fix oversights in previous commit --- nova_vm/src/ecmascript/builtins/ordinary.rs | 9 +++- .../builtins/temporal/plain_time.rs | 43 ++++++++++++++++--- nova_vm/src/engine/rootable.rs | 6 ++- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 130e7cbfa..535589c96 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -16,7 +16,9 @@ use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; +use crate::ecmascript::builtins::temporal::{ + instant::data::InstantRecord, plain_time::data::PlainTimeHeapData, +}; #[cfg(feature = "array-buffer")] use crate::ecmascript::types::try_get_result_into_value; use crate::{ @@ -1692,7 +1694,10 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( agent.heap.create(InstantRecord::default()).into_object() } #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalPlainTime => todo!(), + ProtoIntrinsics::TemporalPlainTime => agent + .heap + .create(PlainTimeHeapData::default()) + .into_object(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index 24c60bb41..f451d8e65 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -6,9 +6,13 @@ use crate::{ execution::{Agent, ProtoIntrinsics}, types::{InternalMethods, InternalSlots, Object, OrdinaryObject, Value}, }, - engine::context::bindable_handle, + engine::{ + context::{Bindable, bindable_handle}, + rootable::{HeapRootData, HeapRootRef, Rootable}, + }, heap::{ - CompactionLists, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, indexes::BaseIndex, + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + WorkQueues, indexes::BaseIndex, }, }; @@ -19,10 +23,6 @@ pub(crate) mod data; pub struct TemporalPlainTime<'a>(BaseIndex<'a, PlainTimeHeapData<'static>>); impl TemporalPlainTime<'_> { - pub(crate) fn inner_plain_date_time(self, agent: &Agent) -> &temporal_rs::PlainTime { - &agent[self].plain_time - } - //TODO pub(crate) const fn _def() -> Self { TemporalPlainTime(BaseIndex::from_u32_index(0)) @@ -109,6 +109,29 @@ impl IndexMut> for Vec> { } } +impl Rootable for TemporalPlainTime<'_> { + type RootRepr = HeapRootRef; + + fn to_root_repr(value: Self) -> Result { + Err(HeapRootData::PlainTime(value.unbind())) + } + + fn from_root_repr(value: &Self::RootRepr) -> Result { + Err(*value) + } + + fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr { + heap_ref + } + + fn from_heap_data(heap_data: HeapRootData) -> Option { + match heap_data { + HeapRootData::PlainTime(object) => Some(object), + _ => None, + } + } +} + impl HeapMarkAndSweep for TemporalPlainTime<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.plain_times.push(*self); @@ -124,3 +147,11 @@ impl HeapSweepWeakReference for TemporalPlainTime<'static> { compactions.plain_times.shift_weak_index(self.0).map(Self) } } + +impl<'a> CreateHeapData, TemporalPlainTime<'a>> for Heap { + fn create(&mut self, data: PlainTimeHeapData<'a>) -> TemporalPlainTime<'a> { + self.plain_times.push(data.unbind()); + self.alloc_counter += core::mem::size_of::>(); + TemporalPlainTime(BaseIndex::last(&self.plain_times)) + } +} diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index dcffc4d15..a9855c467 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -141,7 +141,9 @@ pub mod private { #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] - use crate::ecmascript::builtins::temporal::instant::TemporalInstant; + use crate::ecmascript::builtins::temporal::{ + instant::TemporalInstant, plain_time::TemporalPlainTime, + }; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::{ data_view::SharedDataView, @@ -239,6 +241,8 @@ pub mod private { impl RootableSealed for Date<'_> {} #[cfg(feature = "temporal")] impl RootableSealed for TemporalInstant<'_> {} + #[cfg(feature = "temporal")] + impl RootableSealed for TemporalPlainTime<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} impl RootableSealed for Error<'_> {} From fed7aa87fcf5d7db48aab96f7f076d2174b1baf8 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Mon, 17 Nov 2025 20:56:10 +0100 Subject: [PATCH 34/42] difference_temporal_instant and uncompleted get_difference_settings --- nova_vm/src/builtin_strings | 1 + nova_vm/src/ecmascript/builtins/temporal.rs | 101 +++++++++++++++++- .../ecmascript/builtins/temporal/duration.rs | 2 +- .../ecmascript/builtins/temporal/instant.rs | 60 +++++++++-- .../temporal/instant/instant_prototype.rs | 4 +- 5 files changed, 154 insertions(+), 14 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 297b67c35..38ca9145d 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -261,6 +261,7 @@ isSealed isWellFormed #[cfg(feature = "annex-b-string")]italics #[cfg(feature = "temporal")]Instant +#[cfg(feature = "temporal")]largestUnit Iterator iterator join diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index ec87110c8..c0ec57e56 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -8,13 +8,22 @@ pub mod instant; pub mod options; pub mod plain_time; +use temporal_rs::options::{DifferenceSettings, RoundingMode, Unit, UnitGroup}; + use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, - execution::{Agent, Realm}, - types::{BUILTIN_STRING_MEMORY, IntoValue}, + builtins::temporal::{ + instant::instant_prototype::{DefaultOption, get_temporal_unit_valued_option}, + options::{get_rounding_increment_option, get_rounding_mode_option}, + }, + execution::{Agent, JsResult, Realm}, + types::{BUILTIN_STRING_MEMORY, IntoValue, Object}, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope, trivially_bindable}, + rootable::Scopable, }, - engine::context::NoGcScope, heap::WellKnownSymbolIndexes, }; @@ -68,3 +77,89 @@ impl Temporal { .build(); } } + +trivially_bindable!(DifferenceSettings); +trivially_bindable!(UnitGroup); +trivially_bindable!(Unit); + +/// [13.42 GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )](https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings) +/// The abstract operation GetDifferenceSettings takes arguments operation (since or until), +/// options (an Object), unitGroup (date, time, or datetime), disallowedUnits (a List of Temporal units), +/// fallbackSmallestUnit (a Temporal unit), and smallestLargestDefaultUnit (a Temporal unit) and returns either +/// a normal completion containing a Record with fields [[SmallestUnit]] (a Temporal unit), +/// [[LargestUnit]] (a Temporal unit), [[RoundingMode]] (a rounding mode), +/// and [[RoundingIncrement]] (an integer in the inclusive interval from 1 to 10**9), +/// or a throw completion. It reads unit and rounding options needed by difference operations. +pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>( + agent: &mut Agent, + options: Object<'gc>, // options (an Object) + _unit_group: UnitGroup, // unitGroup (date, time, or datetime) + _disallowed_units: Vec, // disallowedUnits (todo:a List of Temporal units) + _fallback_smallest_unit: Unit, // fallbackSmallestUnit (a Temporal unit) + _smallest_largest_default_unit: Unit, // smallestLargestDefaultUnit (a Temporal unit) + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, DifferenceSettings> { + let _unit_group = _unit_group.bind(gc.nogc()); + let _disallowed_units = _disallowed_units.bind(gc.nogc()); + let _fallback_smallest_unit = _fallback_smallest_unit.bind(gc.nogc()); + let _smallest_largest_default_unit = _smallest_largest_default_unit.bind(gc.nogc()); + + let options = options.scope(agent, gc.nogc()); + // 1. NOTE: The following steps read options and perform independent validation in alphabetical order. + // 2. Let largestUnit be ? GetTemporalUnitValuedOption(options, "largestUnit", unset). + let largest_unit = get_temporal_unit_valued_option( + agent, + options.get(agent), + BUILTIN_STRING_MEMORY.largestUnit.to_property_key(), + DefaultOption::Unset, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 3. Let roundingIncrement be ? GetRoundingIncrementOption(options). + let rounding_increment = + get_rounding_increment_option(agent, options.get(agent), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // 4. Let roundingMode be ? GetRoundingModeOption(options, trunc). + let rounding_mode = get_rounding_mode_option( + agent, + options.get(agent), + RoundingMode::Trunc, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 5. Let smallestUnit be ? GetTemporalUnitValuedOption(options, "smallestUnit", unset). + let smallest_unit = get_temporal_unit_valued_option( + agent, + options.get(agent), + BUILTIN_STRING_MEMORY.smallestUnit.to_property_key(), + DefaultOption::Unset, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 6. Perform ? ValidateTemporalUnitValue(largestUnit, unitGroup, « auto »). + // 7. If largestUnit is unset, then + // a. Set largestUnit to auto. + // 8. If disallowedUnits contains largestUnit, throw a RangeError exception. + // 9. If operation is since, then + // a. Set roundingMode to NegateRoundingMode(roundingMode). + // 10. Perform ? ValidateTemporalUnitValue(smallestUnit, unitGroup). + // 11. If smallestUnit is unset, then + // a. Set smallestUnit to fallbackSmallestUnit. + // 12. If disallowedUnits contains smallestUnit, throw a RangeError exception. + // 13. Let defaultLargestUnit be LargerOfTwoTemporalUnits(smallestLargestDefaultUnit, smallestUnit). + // 14. If largestUnit is auto, set largestUnit to defaultLargestUnit. + // 15. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. + // 16. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). + // 17. If maximum is not unset, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + // 18. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, }. + let mut diff_settings = temporal_rs::options::DifferenceSettings::default(); + diff_settings.largest_unit = Some(largest_unit); + diff_settings.smallest_unit = Some(smallest_unit); + diff_settings.rounding_mode = Some(rounding_mode); + diff_settings.increment = Some(rounding_increment); + Ok(diff_settings) +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index dc204cf98..13ef0b9ad 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -104,7 +104,7 @@ impl IndexMut> for Vec> { /// It creates a Temporal.Duration instance and fills /// the internal slots with valid values. /// It performs the following steps when called: -fn create_temporal_duration<'gc>(// years, +pub(crate) fn create_temporal_duration<'gc>(// years, // months, // weeks, // days, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 78aa8c891..71ab99fdf 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -8,12 +8,19 @@ pub(crate) mod data; pub mod instant_constructor; pub mod instant_prototype; +use temporal_rs::options::{Unit, UnitGroup}; + use crate::{ ecmascript::{ abstract_operations::type_conversion::{PreferredType, to_primitive_object}, builtins::{ ordinary::ordinary_create_from_constructor, - temporal::{duration::to_temporal_duration, error::temporal_err_to_js_err}, + temporal::{ + duration::{TemporalDuration, create_temporal_duration, to_temporal_duration}, + error::temporal_err_to_js_err, + get_difference_settings, + options::get_options_object, + }, }, execution::{ JsResult, ProtoIntrinsics, @@ -327,23 +334,60 @@ fn add_duration_to_instant<'gc, const IS_ADD: bool>( /// rounds it, and returns it as a Temporal.Duration object. fn difference_temporal_instant<'gc, const IS_UNTIL: bool>( agent: &mut Agent, - instant: Value, + instant: TemporalInstant, other: Value, options: Value, mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Value<'gc>> { - let instant = instant.bind(gc.nogc()); +) -> JsResult<'gc, TemporalDuration<'gc>> { + let instant = instant.scope(agent, gc.nogc()); let other = other.bind(gc.nogc()); - let options = options.bind(gc.nogc()); + let options = options.scope(agent, gc.nogc()); // 1. Set other to ? ToTemporalInstant(other). - let other = to_temporal_instant(agent, other.unbind(), gc.reborrow()); + let other = to_temporal_instant(agent, other.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); // 2. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(agent, options.get(agent), gc.nogc()) + .unbind()? + .bind(gc.nogc()); // 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, time, « », nanosecond, second). + let _result; + if IS_UNTIL { + const UNTIL: bool = true; + let settings = get_difference_settings::( + agent, + resolved_options.unbind(), + UnitGroup::Time, + vec![], + Unit::Nanosecond, + Unit::Second, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + _result = + temporal_rs::Instant::until(&instant.get(agent).inner_instant(agent), &other, settings); + } else { + const SINCE: bool = false; + let settings = get_difference_settings::( + agent, + resolved_options.unbind(), + UnitGroup::Time, + vec![], + Unit::Nanosecond, + Unit::Second, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + _result = + temporal_rs::Instant::since(&instant.get(agent).inner_instant(agent), &other, settings); + } // 4. Let internalDuration be DifferenceInstant(instant.[[EpochNanoseconds]], other.[[EpochNanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). // 5. Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]). - // 6. If operation is since, set result to CreateNegatedTemporalDuration(result). + // 6. If operation is since, set result to CreateNegatedTemporsalDuration(result). // 7. Return result. - unimplemented!() + create_temporal_duration() // skip CreateNegatedTemporsalDuration and just CreateTemporsalDuration() with result } #[inline(always)] diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index fbb6bb00b..ef0f5c1ca 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -232,7 +232,7 @@ impl TemporalInstantPrototype { const UNTIL: bool = true; let result = difference_temporal_instant::( agent, - instant.into_value().unbind(), + instant.unbind(), other.unbind(), options.unbind(), gc, @@ -260,7 +260,7 @@ impl TemporalInstantPrototype { const SINCE: bool = false; let result = difference_temporal_instant::( agent, - instant.into_value().unbind(), + instant.unbind(), other.unbind(), options.unbind(), gc, From 01baa0b8032f633d6a579a3b5eb3b2ae6fa1cbd7 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 19 Nov 2025 01:37:17 +0100 Subject: [PATCH 35/42] awful awful code so far, WIP --- nova_vm/src/builtin_strings | 1 + .../temporal/instant/instant_prototype.rs | 74 ++++++++++++-- .../ecmascript/builtins/temporal/options.rs | 96 ++++++++++++++----- 3 files changed, 140 insertions(+), 31 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 38ca9145d..2d26b89e0 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -362,6 +362,7 @@ return reverse revocable #[cfg(any(feature = "math", feature = "temporal"))]round +#[cfg(feature = "temporal")]roundingMode #[cfg(feature = "temporal")]roundingIncrement seal #[cfg(feature = "regexp")]search diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index ef0f5c1ca..2b8df1c1b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use temporal_rs::options::{RoundingMode, RoundingOptions, Unit}; use crate::{ @@ -16,7 +18,8 @@ use crate::{ require_internal_slot_temporal_instant, to_temporal_instant, }, options::{ - get_options_object, get_rounding_increment_option, get_rounding_mode_option, + OptionType, get_option, get_options_object, get_rounding_increment_option, + get_rounding_mode_option, }, }, }, @@ -528,8 +531,6 @@ pub(crate) fn to_integer_with_truncation<'gc>( Ok(number.into_f64(agent).trunc()) } -trivially_bindable!(Unit); - /// ### [13.17 GetTemporalUnitValuedOption ( options, key, default )] (https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunitvaluedoption) /// /// The abstract operation GetTemporalUnitValuedOption takes arguments options (an Object), key (a @@ -537,16 +538,71 @@ trivially_bindable!(Unit); /// containing either a Temporal unit, unset, or auto, or a throw completion. It attempts to read a /// Temporal unit from the specified property of options. pub(crate) fn get_temporal_unit_valued_option<'gc>( - _agent: &mut Agent, + agent: &mut Agent, options: Object, key: PropertyKey, default: DefaultOption, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Unit> { - let _options = options.bind(gc.nogc()); - let _default = default.bind(gc.nogc()); - let _key = key.bind(gc.nogc()); - todo!() + let options = options.bind(gc.nogc()); + let default = default.bind(gc.nogc()); + let key = key.bind(gc.nogc()); + // 1. Let allowedStrings be a List containing all values in the "Singular property name" and + // "Plural property name" columns of Table 21, except the header row. + const ALLOWED: &[&str] = &[ + "year", + "years", + "month", + "months", + "week", + "weeks", + "day", + "days,", + "hour", + "hours", + "minute", + "minutes", + "second", + "seconds", + "millisecond", + "milliseconds", + "microsecond", + "microseconds", + "nanosecond", + "nanoseconds", + "auto", + ]; + // 2. Append "auto" to allowedStrings. + // 3. NOTE: For each singular Temporal unit name that is contained within allowedStrings, the + // corresponding plural name is also contained within it. + // 4. If default is unset, then + // a. Let defaultValue be undefined. + // 5. Else, + // a. Let defaultValue be default. + // 6. Let value be ? GetOption(options, key, string, allowedStrings, defaultValue). + let string_value = get_option( + agent, + options.unbind(), + key.unbind(), + OptionType::String, + ALLOWED, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + let js_str = string_value + .unbind() + .to_string(agent, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + let rust_str = js_str.as_str(agent).expect("aaa"); + // 7. If value is undefined, return unset. + // 8. If value is "auto", return auto. + // 9. Return the value in the "Value" column of Table 21 corresponding to the row with value in + // its "Singular property name" or "Plural property name" column. + Ok(Unit::from_str(rust_str).unwrap()) } #[allow(dead_code)] diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs index 4e1d6b94a..e89abc882 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/options.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::num::NonZeroU32; +use std::{num::NonZeroU32, str::FromStr}; use temporal_rs::options::{RoundingIncrement, RoundingMode}; @@ -14,17 +14,21 @@ use crate::{ }, builtins::{ ordinary::ordinary_object_create_with_intrinsics, - temporal::instant::instant_prototype::to_integer_with_truncation, + temporal::{ + error::temporal_err_to_js_err, + instant::instant_prototype::to_integer_with_truncation, + }, }, execution::{Agent, JsResult, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, Object, PropertyKey, String, Value}, + types::{BUILTIN_STRING_MEMORY, IntoValue, Object, PropertyKey, Value}, }, engine::context::{Bindable, GcScope, NoGcScope, trivially_bindable}, }; +#[derive(Debug)] pub(crate) enum OptionType { - Boolean(bool), - String(String<'static>), + Boolean, + String, } trivially_bindable!(OptionType); @@ -78,40 +82,60 @@ pub(crate) fn get_option<'gc>( agent: &mut Agent, options: Object, property: PropertyKey, - typee: OptionType, + type_: OptionType, + values: &[&str], mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, OptionType> { +) -> JsResult<'gc, Value<'gc>> { let options = options.bind(gc.nogc()); let property = property.bind(gc.nogc()); + // 1. Let value be ? Get(options, property). let value = get(agent, options.unbind(), property.unbind(), gc.reborrow()) .unbind()? .bind(gc.nogc()); + // 2. If value is undefined, then if value.is_undefined() { // a. If default is required, throw a RangeError exception. // b. Return default. todo!() } - match typee { + + let value = match type_ { // 3. If type is boolean, then - OptionType::Boolean(_) => { + OptionType::Boolean => { // a. Set value to ToBoolean(value). - let value = to_boolean(agent, value); + Value::from(to_boolean(agent, value)) } // 4. Else, - OptionType::String(_) => { + OptionType::String => { // a. Assert: type is string. // b. Set value to ? ToString(value). - let value = to_string(agent, value.unbind(), gc.reborrow()) - .unbind()? - .bind(gc.nogc()); + let str = to_string(agent, value.unbind(), gc.reborrow()).unbind()?; + str.into_value() } - } + }; + let gc = gc.into_nogc(); // 5. If values is not empty and values does not contain value, throw a RangeError exception. + if !values.is_empty() { + dbg!(value); + let str = match value.unbind() { + Value::SmallString(s) => s, + _ => unreachable!(), + }; + let rust_str = unsafe { str.as_str_unchecked() }; + if !values.contains(&rust_str) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "Invalid option value", + gc, + )); + } + } + // 6. Return value. - todo!() + Ok(value.bind(gc)) } /// ### [14.5.2.3 GetRoundingModeOption ( options, fallback)](https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption) @@ -121,18 +145,47 @@ pub(crate) fn get_option<'gc>( // completion. It fetches and validates the "roundingMode" property from options, returning // fallback as a default if absent. It performs the following steps when called: pub(crate) fn get_rounding_mode_option<'gc>( - _agent: &mut Agent, + agent: &mut Agent, options: Object, fallback: RoundingMode, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, RoundingMode> { - let _options = options.bind(gc.nogc()); - let _fallback = fallback.bind(gc.nogc()); + let options = options.bind(gc.nogc()); + let fallback = fallback.bind(gc.nogc()); // 1. Let allowedStrings be the List of Strings from the "String Identifier" column of Table 28. + const ALLOWED: &[&str] = &[ + "ceil", + "floor", + "trunc", + "halfCeil", + "halfFloor", + "halfTrunc", + "halfExpand", + ]; // 2. Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column. + let string_fallback = fallback.unbind().to_string(); // 3. Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback). + let string_value = get_option( + agent, + options.unbind(), + BUILTIN_STRING_MEMORY.roundingMode.into(), + OptionType::String, + ALLOWED, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + let js_str = string_value + .unbind() + .to_string(agent, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + let rust_str = js_str.as_str(agent).expect("aaa"); + // 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column. - todo!() + RoundingMode::from_str(rust_str).map_err(|e| temporal_err_to_js_err(agent, e, gc.into_nogc())) } /// ### [14.5.2.4 GetRoundingIncrementOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption) @@ -173,7 +226,6 @@ pub(crate) fn get_rounding_increment_option<'gc>( )); } - // Convert safely and return integerIncrement // NOTE: `as u32` is safe here since we validated it’s in range. let integer_increment_u32 = integer_increment as u32; let increment = From f9a5f11c5e1b961947981579f372d6a0c62be091 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 19 Nov 2025 23:32:56 +0100 Subject: [PATCH 36/42] Instant.prototype.round tests now passing --- nova_vm/src/ecmascript/builtins/temporal.rs | 12 +- .../temporal/instant/instant_prototype.rs | 89 ++-------- .../ecmascript/builtins/temporal/options.rs | 154 ++++++++---------- 3 files changed, 86 insertions(+), 169 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index c0ec57e56..05009796b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -8,13 +8,13 @@ pub mod instant; pub mod options; pub mod plain_time; -use temporal_rs::options::{DifferenceSettings, RoundingMode, Unit, UnitGroup}; +use temporal_rs::options::{DifferenceSettings, RoundingIncrement, RoundingMode, Unit, UnitGroup}; use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::temporal::{ - instant::instant_prototype::{DefaultOption, get_temporal_unit_valued_option}, + instant::instant_prototype::get_temporal_unit_valued_option, options::{get_rounding_increment_option, get_rounding_mode_option}, }, execution::{Agent, JsResult, Realm}, @@ -81,6 +81,8 @@ impl Temporal { trivially_bindable!(DifferenceSettings); trivially_bindable!(UnitGroup); trivially_bindable!(Unit); +trivially_bindable!(RoundingMode); +trivially_bindable!(RoundingIncrement); /// [13.42 GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )](https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings) /// The abstract operation GetDifferenceSettings takes arguments operation (since or until), @@ -111,7 +113,6 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>( agent, options.get(agent), BUILTIN_STRING_MEMORY.largestUnit.to_property_key(), - DefaultOption::Unset, gc.reborrow(), ) .unbind()? @@ -135,7 +136,6 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>( agent, options.get(agent), BUILTIN_STRING_MEMORY.smallestUnit.to_property_key(), - DefaultOption::Unset, gc.reborrow(), ) .unbind()? @@ -157,8 +157,8 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>( // 17. If maximum is not unset, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). // 18. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, }. let mut diff_settings = temporal_rs::options::DifferenceSettings::default(); - diff_settings.largest_unit = Some(largest_unit); - diff_settings.smallest_unit = Some(smallest_unit); + diff_settings.largest_unit = largest_unit; + diff_settings.smallest_unit = smallest_unit; diff_settings.rounding_mode = Some(rounding_mode); diff_settings.increment = Some(rounding_increment); Ok(diff_settings) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 2b8df1c1b..ad84e6730 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use temporal_rs::options::{RoundingMode, RoundingOptions, Unit}; use crate::{ @@ -18,7 +16,7 @@ use crate::{ require_internal_slot_temporal_instant, to_temporal_instant, }, options::{ - OptionType, get_option, get_options_object, get_rounding_increment_option, + get_option, get_options_object, get_rounding_increment_option, get_rounding_mode_option, }, }, @@ -30,7 +28,7 @@ use crate::{ types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, PropertyKey, String, Value}, }, engine::{ - context::{Bindable, GcScope, NoGcScope, trivially_bindable}, + context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, }, heap::WellKnownSymbolIndexes, @@ -298,7 +296,7 @@ impl TemporalInstantPrototype { } // 4. If roundTo is a String, then - let round_to = if let Value::String(round_to) = round_to.unbind() { + let round_to = if round_to.unbind().is_string() { // a. Let paramString be roundTo. let param_string = round_to; // b. Set roundTo to OrdinaryObjectCreate(null). @@ -319,17 +317,21 @@ impl TemporalInstantPrototype { .unbind()? .bind(gc.nogc()) }; + let round_to = round_to.scope(agent, gc.nogc()); + // 6. NOTE: The following steps read options and perform independent validation in // alphabetical order (GetRoundingIncrementOption reads "roundingIncrement" and // GetRoundingModeOption reads "roundingMode"). let mut options = RoundingOptions::default(); + // 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo). let rounding_increment = get_rounding_increment_option(agent, round_to.get(agent), gc.reborrow()) .unbind()? .bind(gc.nogc()); options.increment = Some(rounding_increment); + // 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand). let rounding_mode = get_rounding_mode_option( agent, @@ -340,17 +342,18 @@ impl TemporalInstantPrototype { .unbind()? .bind(gc.nogc()); options.rounding_mode = Some(rounding_mode); + // 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", required). let smallest_unit = get_temporal_unit_valued_option( agent, round_to.get(agent), BUILTIN_STRING_MEMORY.smallestUnit.into(), - DefaultOption::Required, gc.reborrow(), ) .unbind()? .bind(gc.nogc()); - options.smallest_unit = Some(smallest_unit); + options.smallest_unit = smallest_unit; + // 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time). // 11. If smallestUnit is hour, then // a. Let maximum be HoursPerDay. @@ -521,7 +524,7 @@ pub(crate) fn to_integer_with_truncation<'gc>( // 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception. if number.is_nan(agent) || number.is_pos_infinity(agent) || number.is_neg_infinity(agent) { return Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, + ExceptionType::RangeError, "Number cannot be NaN, positive infinity, or negative infinity", gc.into_nogc(), )); @@ -541,74 +544,12 @@ pub(crate) fn get_temporal_unit_valued_option<'gc>( agent: &mut Agent, options: Object, key: PropertyKey, - default: DefaultOption, - mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Unit> { + gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Option> { let options = options.bind(gc.nogc()); - let default = default.bind(gc.nogc()); let key = key.bind(gc.nogc()); - // 1. Let allowedStrings be a List containing all values in the "Singular property name" and - // "Plural property name" columns of Table 21, except the header row. - const ALLOWED: &[&str] = &[ - "year", - "years", - "month", - "months", - "week", - "weeks", - "day", - "days,", - "hour", - "hours", - "minute", - "minutes", - "second", - "seconds", - "millisecond", - "milliseconds", - "microsecond", - "microseconds", - "nanosecond", - "nanoseconds", - "auto", - ]; - // 2. Append "auto" to allowedStrings. - // 3. NOTE: For each singular Temporal unit name that is contained within allowedStrings, the - // corresponding plural name is also contained within it. - // 4. If default is unset, then - // a. Let defaultValue be undefined. - // 5. Else, - // a. Let defaultValue be default. - // 6. Let value be ? GetOption(options, key, string, allowedStrings, defaultValue). - let string_value = get_option( - agent, - options.unbind(), - key.unbind(), - OptionType::String, - ALLOWED, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - - let js_str = string_value - .unbind() - .to_string(agent, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - let rust_str = js_str.as_str(agent).expect("aaa"); - // 7. If value is undefined, return unset. - // 8. If value is "auto", return auto. - // 9. Return the value in the "Value" column of Table 21 corresponding to the row with value in - // its "Singular property name" or "Plural property name" column. - Ok(Unit::from_str(rust_str).unwrap()) -} + let opt = get_option::(agent, options.unbind(), key.unbind(), gc)?; -#[allow(dead_code)] -pub(crate) enum DefaultOption { - Required, - Unset, + Ok(opt) } - -trivially_bindable!(DefaultOption); diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs index e89abc882..da31d153a 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/options.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -4,36 +4,36 @@ use std::{num::NonZeroU32, str::FromStr}; -use temporal_rs::options::{RoundingIncrement, RoundingMode}; +use temporal_rs::options::{RoundingIncrement, RoundingMode, Unit}; use crate::{ ecmascript::{ - abstract_operations::{ - operations_on_objects::get, - type_conversion::{to_boolean, to_string}, - }, + abstract_operations::{operations_on_objects::get, type_conversion::to_string}, builtins::{ ordinary::ordinary_object_create_with_intrinsics, - temporal::{ - error::temporal_err_to_js_err, - instant::instant_prototype::to_integer_with_truncation, - }, + temporal::instant::instant_prototype::to_integer_with_truncation, }, execution::{Agent, JsResult, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, IntoValue, Object, PropertyKey, Value}, + types::{BUILTIN_STRING_MEMORY, Object, PropertyKey, Value}, }, - engine::context::{Bindable, GcScope, NoGcScope, trivially_bindable}, + engine::context::{Bindable, GcScope, NoGcScope}, }; -#[derive(Debug)] -pub(crate) enum OptionType { - Boolean, - String, +pub trait OptionType: Sized { + fn from_string(s: &str) -> Result; } -trivially_bindable!(OptionType); -trivially_bindable!(RoundingMode); -trivially_bindable!(RoundingIncrement); +impl OptionType for RoundingMode { + fn from_string(s: &str) -> Result { + RoundingMode::from_str(s).map_err(|e| e.to_string()) + } +} + +impl OptionType for Unit { + fn from_string(s: &str) -> Result { + Unit::from_str(s).map_err(|e| e.to_string()) + } +} /// ### [14.5.2.1 GetOptionsObject ( options )](https://tc39.es/proposal-temporal/#sec-getoptionsobject) /// @@ -56,9 +56,11 @@ pub(crate) fn get_options_object<'gc>( )) } // 2. If options is an Object, then - Value::Object(obj) => { + value if value.is_object() => { // a. Return options. - Ok(obj.into()) + // TODO: remove unwrap; Although safe because value is an object + let obj = Object::try_from(value).unwrap(); + Ok(obj) } // 3. Throw a TypeError exception. _ => Err(agent.throw_exception_with_static_message( @@ -78,64 +80,58 @@ pub(crate) fn get_options_object<'gc>( /// specified property of options, converts it to the required type, checks whether it is allowed /// by values if values is not empty, and substitutes default if the value is undefined. It /// performs the following steps when called: -pub(crate) fn get_option<'gc>( +pub(crate) fn get_option<'gc, T>( agent: &mut Agent, options: Object, property: PropertyKey, - type_: OptionType, - values: &[&str], mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Value<'gc>> { +) -> JsResult<'gc, Option> +where + T: OptionType, +{ let options = options.bind(gc.nogc()); let property = property.bind(gc.nogc()); - // 1. Let value be ? Get(options, property). let value = get(agent, options.unbind(), property.unbind(), gc.reborrow()) .unbind()? .bind(gc.nogc()); - // 2. If value is undefined, then if value.is_undefined() { // a. If default is required, throw a RangeError exception. // b. Return default. - todo!() + return Ok(None); } - let value = match type_ { - // 3. If type is boolean, then - OptionType::Boolean => { - // a. Set value to ToBoolean(value). - Value::from(to_boolean(agent, value)) - } - // 4. Else, - OptionType::String => { - // a. Assert: type is string. - // b. Set value to ? ToString(value). - let str = to_string(agent, value.unbind(), gc.reborrow()).unbind()?; - str.into_value() - } - }; - - let gc = gc.into_nogc(); + // 3. If type is boolean, then + // a. Set value to ToBoolean(value). + // 4. Else, + // a. Assert: type is string. + // b. Set value to ? ToString(value). // 5. If values is not empty and values does not contain value, throw a RangeError exception. - if !values.is_empty() { - dbg!(value); - let str = match value.unbind() { - Value::SmallString(s) => s, - _ => unreachable!(), - }; - let rust_str = unsafe { str.as_str_unchecked() }; - if !values.contains(&rust_str) { - return Err(agent.throw_exception_with_static_message( - ExceptionType::RangeError, - "Invalid option value", - gc, - )); - } - } + + // TODO: Currently only works for temporal_rs::Unit, and temporal_rs::RoundingMode. + // + // Should be extended to work with + // 1. ecmascript::types::String + // 2. bool + // 3. Potentially other temporal_rs types. + + let js_str = to_string(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + let rust_str = js_str.as_str(agent).unwrap(); + + let parsed = T::from_string(rust_str).map_err(|msg| { + agent.throw_exception_with_static_message( + ExceptionType::RangeError, + Box::leak(msg.into_boxed_str()), + gc.into_nogc(), + ) + })?; // 6. Return value. - Ok(value.bind(gc)) + Ok(Some(parsed)) } /// ### [14.5.2.3 GetRoundingModeOption ( options, fallback)](https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption) @@ -148,44 +144,24 @@ pub(crate) fn get_rounding_mode_option<'gc>( agent: &mut Agent, options: Object, fallback: RoundingMode, - mut gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, RoundingMode> { let options = options.bind(gc.nogc()); let fallback = fallback.bind(gc.nogc()); + // 1. Let allowedStrings be the List of Strings from the "String Identifier" column of Table 28. - const ALLOWED: &[&str] = &[ - "ceil", - "floor", - "trunc", - "halfCeil", - "halfFloor", - "halfTrunc", - "halfExpand", - ]; // 2. Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column. - let string_fallback = fallback.unbind().to_string(); // 3. Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback). - let string_value = get_option( + // 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column. + match get_option::( agent, options.unbind(), BUILTIN_STRING_MEMORY.roundingMode.into(), - OptionType::String, - ALLOWED, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - - let js_str = string_value - .unbind() - .to_string(agent, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - - let rust_str = js_str.as_str(agent).expect("aaa"); - - // 4. Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column. - RoundingMode::from_str(rust_str).map_err(|e| temporal_err_to_js_err(agent, e, gc.into_nogc())) + gc, + )? { + Some(mode) => Ok(mode), + None => Ok(fallback), + } } /// ### [14.5.2.4 GetRoundingIncrementOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption) @@ -218,7 +194,7 @@ pub(crate) fn get_rounding_increment_option<'gc>( .bind(gc.nogc()); // 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception. - if integer_increment < 1.0 || integer_increment > 1_000_000_000.0 { + if !(1.0..=1_000_000_000.0).contains(&integer_increment) { return Err(agent.throw_exception_with_static_message( ExceptionType::RangeError, "roundingIncrement must be between 1 and 10**9", From 9aa1db5835cf131f71480edce9e597009d445476 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 19 Nov 2025 23:35:27 +0100 Subject: [PATCH 37/42] add comment --- nova_vm/src/ecmascript/builtins/temporal/options.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs index da31d153a..06bf109c1 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/options.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -120,6 +120,7 @@ where .unbind()? .bind(gc.nogc()); + // TODO: Fix this code.. None case is unreachable but code sucks rn.. let rust_str = js_str.as_str(agent).unwrap(); let parsed = T::from_string(rust_str).map_err(|msg| { From 1fc9a5fef0d2bd8378bfad95f3bf3169149144c0 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 20 Nov 2025 17:14:12 +0100 Subject: [PATCH 38/42] added proper duration backing --- nova_vm/src/builtin_strings | 2 + nova_vm/src/ecmascript/builtins/ordinary.rs | 9 +- nova_vm/src/ecmascript/builtins/temporal.rs | 9 ++ .../ecmascript/builtins/temporal/duration.rs | 124 +++++++++++++----- .../temporal/duration/duration_constructor.rs | 47 +++++++ .../temporal/duration/duration_prototype.rs | 33 +++++ .../ecmascript/builtins/temporal/instant.rs | 9 +- .../ecmascript/execution/realm/intrinsics.rs | 27 +++- nova_vm/src/ecmascript/execution/weak_key.rs | 20 ++- .../src/ecmascript/types/language/object.rs | 20 ++- .../src/ecmascript/types/language/value.rs | 20 ++- nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 18 ++- nova_vm/src/heap.rs | 7 +- nova_vm/src/heap/heap_bits.rs | 24 +++- nova_vm/src/heap/heap_constants.rs | 4 + nova_vm/src/heap/heap_gc.rs | 21 +++ 17 files changed, 345 insertions(+), 51 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs create mode 100644 nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 2d26b89e0..3885f009e 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -108,6 +108,7 @@ description #[cfg(feature = "array-buffer")]detached done #[cfg(feature = "regexp")]dotAll +#[cfg(feature="temporal")]Duration #[cfg(feature = "math")]E encodeURI encodeURIComponent @@ -451,6 +452,7 @@ Symbol.toStringTag Symbol.unscopables SyntaxError #[cfg(feature = "temporal")]Temporal +#[cfg(feature = "temporal")]Temporal.Duration #[cfg(feature = "temporal")]Temporal.Instant #[cfg(feature = "math")]tan #[cfg(feature = "math")]tanh diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 535589c96..aec9db8e4 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -17,7 +17,8 @@ use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - instant::data::InstantRecord, plain_time::data::PlainTimeHeapData, + duration::data::DurationHeapData, instant::data::InstantRecord, + plain_time::data::PlainTimeHeapData, }; #[cfg(feature = "array-buffer")] use crate::ecmascript::types::try_get_result_into_value; @@ -1694,6 +1695,10 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( agent.heap.create(InstantRecord::default()).into_object() } #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalDuration => { + agent.heap.create(DurationHeapData::default()).into_object() + } + #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalPlainTime => agent .heap .create(PlainTimeHeapData::default()) @@ -2089,6 +2094,8 @@ fn get_intrinsic_constructor<'a>( #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into_function()), #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalDuration => Some(intrinsics.temporal_duration().into_function()), + #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalPlainTime => { Some(intrinsics.temporal_plain_time().into_function()) } diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 05009796b..e398a9d96 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -36,6 +36,7 @@ impl Temporal { let this = intrinsics.temporal(); let instant_constructor = intrinsics.temporal_instant(); + let duration_constructor = intrinsics.temporal_duration(); let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) @@ -64,6 +65,14 @@ impl Temporal { // 1.2.5 Temporal.PlainYearMonth ( . . . ) // 1.2.6 Temporal.PlainMonthDay ( . . . ) // 1.2.7 Temporal.Duration ( . . . ) + .with_property(|builder| { + builder + .with_key(BUILTIN_STRING_MEMORY.Duration.into()) + .with_value(duration_constructor.into_value()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) // 1.2.8 Temporal.ZonedDateTime ( . . . ) // 1.3.1 Temporal.Now .with_property(|builder| { diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 13ef0b9ad..0c8292b26 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -1,20 +1,29 @@ +pub(crate) mod data; +pub mod duration_constructor; +pub mod duration_prototype; + use crate::{ ecmascript::{ abstract_operations::{ operations_on_objects::get, type_conversion::to_integer_if_integral, }, - execution::{Agent, JsResult, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, Object, String, Value}, + execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, + types::{ + BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, Object, OrdinaryObject, String, + Value, + }, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, - rootable::Scopable, + rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, + }, + heap::{ + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + WorkQueues, indexes::BaseIndex, }, - heap::indexes::BaseIndex, }; use core::ops::{Index, IndexMut}; -pub(crate) mod data; use self::data::DurationHeapData; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] @@ -35,42 +44,49 @@ impl TemporalDuration<'_> { bindable_handle!(TemporalDuration); impl<'a> From> for Value<'a> { - fn from(_value: TemporalDuration<'a>) -> Self { - todo!() - //Value::Duration(value) + fn from(value: TemporalDuration<'a>) -> Self { + Value::Duration(value) } } impl<'a> From> for Object<'a> { - fn from(_value: TemporalDuration<'a>) -> Self { - todo!() - //Object::Duration(value) + fn from(value: TemporalDuration<'a>) -> Self { + Object::Duration(value) } } impl<'a> TryFrom> for TemporalDuration<'a> { type Error = (); - fn try_from(_value: Value<'a>) -> Result { - todo!() - // match value { - // Value::Duration(idx) => Ok(idx), - // _ => Err(()), - // } + fn try_from(value: Value<'a>) -> Result { + match value { + Value::Duration(idx) => Ok(idx), + _ => Err(()), + } + } +} + +impl<'a> InternalSlots<'a> for TemporalDuration<'a> { + const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalDuration; + fn get_backing_object(self, agent: &Agent) -> Option> { + agent[self].object_index + } + fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { + assert!(agent[self].object_index.replace(backing_object).is_none()); } } +impl<'a> InternalMethods<'a> for TemporalDuration<'a> {} + impl Index> for Agent { type Output = DurationHeapData<'static>; - fn index(&self, _index: TemporalDuration<'_>) -> &Self::Output { - unimplemented!() - //&self.heap.durations[index] + fn index(&self, index: TemporalDuration<'_>) -> &Self::Output { + &self.heap.durations[index] } } impl IndexMut> for Agent { - fn index_mut(&mut self, _index: TemporalDuration) -> &mut Self::Output { - unimplemented!() - //&mut self.heap.durations[index] + fn index_mut(&mut self, index: TemporalDuration) -> &mut Self::Output { + &mut self.heap.durations[index] } } @@ -89,6 +105,52 @@ impl IndexMut> for Vec> { .expect("heap access out of bounds") } } + +impl Rootable for TemporalDuration<'_> { + type RootRepr = HeapRootRef; + + fn to_root_repr(value: Self) -> Result { + Err(HeapRootData::Duration(value.unbind())) + } + + fn from_root_repr(value: &Self::RootRepr) -> Result { + Err(*value) + } + + fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr { + heap_ref + } + + fn from_heap_data(heap_data: HeapRootData) -> Option { + match heap_data { + HeapRootData::Duration(object) => Some(object), + _ => None, + } + } +} + +impl HeapMarkAndSweep for TemporalDuration<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.durations.push(*self); + } + fn sweep_values(&mut self, compactions: &CompactionLists) { + compactions.durations.shift_index(&mut self.0); + } +} + +impl HeapSweepWeakReference for TemporalDuration<'static> { + fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { + compactions.durations.shift_weak_index(self.0).map(Self) + } +} + +impl<'a> CreateHeapData, TemporalDuration<'a>> for Heap { + fn create(&mut self, data: DurationHeapData<'a>) -> TemporalDuration<'a> { + self.durations.push(data.unbind()); + self.alloc_counter += core::mem::size_of::>(); + TemporalDuration(BaseIndex::last(&self.durations)) + } +} /// 7.5.19 CreateTemporalDuration ( years, months, weeks, /// days, hours, minutes, seconds, /// milliseconds, microseconds, nanoseconds [ , newTarget ] ) @@ -144,7 +206,7 @@ pub(crate) fn create_temporal_duration<'gc>(// years, pub(crate) fn to_temporal_duration<'gc>( agent: &mut Agent, item: Value, - mut gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, temporal_rs::Duration> { let item = item.bind(gc.nogc()); // 1. If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then @@ -388,21 +450,19 @@ pub(crate) fn to_temporal_partial_duration_record<'gc>( /// It returns a new Temporal.Duration instance that is the /// negation of duration. pub(crate) fn create_negated_temporal_duration<'gc>( - agent: &mut Agent, - item: temporal_rs::Duration, - mut gc: GcScope<'gc, '_>, + _agent: &mut Agent, + _item: temporal_rs::Duration, + mut _gc: GcScope<'gc, '_>, ) -> JsResult<'gc, temporal_rs::Duration> { // 1. Return ! CreateTemporalDuration(-duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]). - let duration = temporal_rs::Duration::negated(&item); - //TODO: IMPL create_temporal_duration() unimplemented!() } #[inline(always)] fn require_internal_slot_temporal_duration<'a>( - agent: &mut Agent, - value: Value, - gc: NoGcScope<'a, '_>, + _agent: &mut Agent, + _value: Value, + _gc: NoGcScope<'a, '_>, ) -> JsResult<'a, TemporalDuration<'a>> { unimplemented!() // TODO: diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs new file mode 100644 index 000000000..72b5bc2bf --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -0,0 +1,47 @@ +use crate::{ + ecmascript::{ + builders::builtin_function_builder::BuiltinFunctionBuilder, + builtins::{ + ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor + }, + execution::{Agent, JsResult, Realm}, + types::{BUILTIN_STRING_MEMORY, IntoObject, Object, String, Value}, + }, + engine::context::{GcScope, NoGcScope}, + heap::IntrinsicConstructorIndexes, +}; + +pub(crate) struct TemporalDurationConstructor; + +impl Builtin for TemporalDurationConstructor { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.Duration; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalDurationConstructor::constructor); +} +impl BuiltinIntrinsicConstructor for TemporalDurationConstructor { + const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalDuration; +} + +impl TemporalDurationConstructor { + fn constructor<'gc>( + _agent: &mut Agent, + _: Value, + _args: ArgumentsList, + _new_target: Option, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let duration_prototype = intrinsics.temporal_duration_prototype(); + + BuiltinFunctionBuilder::new_intrinsic_constructor::( + agent, realm, + ) + .with_property_capacity(5) + .with_prototype_property(duration_prototype.into_object()) + .build(); + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs new file mode 100644 index 000000000..8f7c956b7 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs @@ -0,0 +1,33 @@ +use crate::{ + ecmascript::{ + builders::ordinary_object_builder::OrdinaryObjectBuilder, + execution::{Agent, Realm}, + types::BUILTIN_STRING_MEMORY, + }, + engine::context::NoGcScope, + heap::WellKnownSymbolIndexes, +}; + +pub(crate) struct TemporalDurationPrototype; +impl TemporalDurationPrototype { + pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let this = intrinsics.temporal_instant_prototype(); + let object_prototype = intrinsics.object_prototype(); + let duration_constructor = intrinsics.temporal_duration(); + + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(15) + .with_prototype(object_prototype) + .with_constructor_property(duration_constructor) + .with_property(|builder| { + builder + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal_Duration.into()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) + .build(); + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 71ab99fdf..1079ef0f2 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -16,7 +16,7 @@ use crate::{ builtins::{ ordinary::ordinary_create_from_constructor, temporal::{ - duration::{TemporalDuration, create_temporal_duration, to_temporal_duration}, + duration::{TemporalDuration, data::DurationHeapData, to_temporal_duration}, error::temporal_err_to_js_err, get_difference_settings, options::get_options_object, @@ -387,7 +387,12 @@ fn difference_temporal_instant<'gc, const IS_UNTIL: bool>( // 5. Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]). // 6. If operation is since, set result to CreateNegatedTemporsalDuration(result). // 7. Return result. - create_temporal_duration() // skip CreateNegatedTemporsalDuration and just CreateTemporsalDuration() with result + let result: temporal_rs::Duration = Default::default(); // TODO + // 7. Return result. + Ok(agent.heap.create(DurationHeapData { + object_index: None, + duration: result, + })) } #[inline(always)] diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 1b329e9c1..c59519b33 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -42,11 +42,16 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ Temporal, + duration::{ + duration_constructor::TemporalDurationConstructor, + duration_prototype::TemporalDurationPrototype, + }, instant::{ instant_constructor::TemporalInstantConstructor, instant_prototype::TemporalInstantPrototype, }, }; + #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ regexp_constructor::RegExpConstructor, regexp_prototype::RegExpPrototype, @@ -238,6 +243,8 @@ pub enum ProtoIntrinsics { #[cfg(feature = "temporal")] TemporalInstant, #[cfg(feature = "temporal")] + TemporalDuration, + #[cfg(feature = "temporal")] TemporalPlainTime, TypeError, #[cfg(feature = "array-buffer")] @@ -326,7 +333,10 @@ impl Intrinsics { TemporalInstantPrototype::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] TemporalInstantConstructor::create_intrinsic(agent, realm, gc); - + #[cfg(feature = "temporal")] + TemporalDurationPrototype::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalDurationConstructor::create_intrinsic(agent, realm, gc); #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); #[cfg(feature = "date")] @@ -439,6 +449,8 @@ impl Intrinsics { #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => self.temporal_instant().into(), #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalDuration => self.temporal_duration().into(), + #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalPlainTime => self.temporal_plain_time().into(), ProtoIntrinsics::TypeError => self.type_error().into(), ProtoIntrinsics::URIError => self.uri_error().into(), @@ -532,6 +544,8 @@ impl Intrinsics { #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => self.temporal_instant_prototype().into(), #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalDuration => self.temporal_duration_prototype().into(), + #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalPlainTime => self.temporal_plain_time_prototype().into(), ProtoIntrinsics::TypeError => self.type_error_prototype().into(), ProtoIntrinsics::URIError => self.uri_error_prototype().into(), @@ -1044,6 +1058,17 @@ impl Intrinsics { IntrinsicObjectIndexes::Temporal.get_backing_object(self.object_index_base) } + /// %Temporal.Instant.Prototype% + pub(crate) const fn temporal_duration_prototype(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalDurationPrototype.get_backing_object(self.object_index_base) + } + + /// %Temporal.Instant% + pub(crate) const fn temporal_duration(&self) -> BuiltinFunction<'static> { + IntrinsicConstructorIndexes::TemporalDuration + .get_builtin_function(self.builtin_function_index_base) + } + /// %Temporal.Instant.Prototype% pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index 89483a637..7dee2ee8e 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -16,8 +16,10 @@ use crate::ecmascript::types::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::{ - builtins::temporal::{instant::TemporalInstant, plain_time::TemporalPlainTime}, - types::{INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, + builtins::temporal::{ + duration::TemporalDuration, instant::TemporalInstant, plain_time::TemporalPlainTime, + }, + types::{DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; @@ -149,6 +151,8 @@ pub(crate) enum WeakKey<'a> { #[cfg(feature = "temporal")] Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, #[cfg(feature = "temporal")] + Duration(TemporalDuration<'a>) = DURATION_DISCRIMINANT, + #[cfg(feature = "temporal")] PlainTime(TemporalPlainTime<'a>) = PLAIN_TIME_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, @@ -266,6 +270,8 @@ impl<'a> From> for Value<'a> { #[cfg(feature = "temporal")] WeakKey::Instant(d) => Self::Instant(d), #[cfg(feature = "temporal")] + WeakKey::Duration(d) => Self::Duration(d), + #[cfg(feature = "temporal")] WeakKey::PlainTime(d) => Self::PlainTime(d), WeakKey::Error(d) => Self::Error(d), WeakKey::FinalizationRegistry(d) => Self::FinalizationRegistry(d), @@ -377,6 +383,8 @@ impl<'a> From> for WeakKey<'a> { #[cfg(feature = "temporal")] Object::Instant(d) => Self::Instant(d), #[cfg(feature = "temporal")] + Object::Duration(d) => Self::Duration(d), + #[cfg(feature = "temporal")] Object::PlainTime(d) => Self::PlainTime(d), Object::Error(d) => Self::Error(d), Object::FinalizationRegistry(d) => Self::FinalizationRegistry(d), @@ -492,6 +500,8 @@ impl<'a> TryFrom> for Object<'a> { #[cfg(feature = "temporal")] WeakKey::Instant(d) => Ok(Self::Instant(d)), #[cfg(feature = "temporal")] + WeakKey::Duration(d) => Ok(Self::Duration(d)), + #[cfg(feature = "temporal")] WeakKey::PlainTime(d) => Ok(Self::PlainTime(d)), WeakKey::Error(d) => Ok(Self::Error(d)), WeakKey::FinalizationRegistry(d) => Ok(Self::FinalizationRegistry(d)), @@ -638,6 +648,8 @@ impl HeapMarkAndSweep for WeakKey<'static> { #[cfg(feature = "temporal")] Self::Instant(d) => d.mark_values(queues), #[cfg(feature = "temporal")] + Self::Duration(d) => d.mark_values(queues), + #[cfg(feature = "temporal")] Self::PlainTime(d) => d.mark_values(queues), Self::Error(d) => d.mark_values(queues), Self::FinalizationRegistry(d) => d.mark_values(queues), @@ -747,6 +759,8 @@ impl HeapMarkAndSweep for WeakKey<'static> { #[cfg(feature = "temporal")] Self::Instant(d) => d.sweep_values(compactions), #[cfg(feature = "temporal")] + Self::Duration(d) => d.sweep_values(compactions), + #[cfg(feature = "temporal")] Self::PlainTime(d) => d.sweep_values(compactions), Self::Error(d) => d.sweep_values(compactions), Self::FinalizationRegistry(d) => d.sweep_values(compactions), @@ -872,6 +886,8 @@ impl HeapSweepWeakReference for WeakKey<'static> { #[cfg(feature = "temporal")] Self::Instant(data) => data.sweep_weak_reference(compactions).map(Self::Instant), #[cfg(feature = "temporal")] + Self::Duration(data) => data.sweep_weak_reference(compactions).map(Self::Duration), + #[cfg(feature = "temporal")] Self::PlainTime(data) => data.sweep_weak_reference(compactions).map(Self::PlainTime), Self::Error(data) => data.sweep_weak_reference(compactions).map(Self::Error), Self::FinalizationRegistry(data) => data diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index c4a3bba69..50904cf66 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -40,7 +40,10 @@ use crate::ecmascript::builtins::date::Date; use crate::ecmascript::builtins::{weak_map::WeakMap, weak_ref::WeakRef, weak_set::WeakSet}; #[cfg(feature = "temporal")] use crate::ecmascript::{ - builtins::temporal::plain_time::TemporalPlainTime, types::PLAIN_TIME_DISCRIMINANT, + builtins::temporal::{ + duration::TemporalDuration, instant::TemporalInstant, plain_time::TemporalPlainTime, + }, + types::{DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; @@ -128,11 +131,10 @@ use crate::{ promise::Promise, promise_objects::promise_abstract_operations::promise_finally_functions::BuiltinPromiseFinallyFunction, proxy::Proxy, - temporal::instant::TemporalInstant, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{Agent, JsResult, ProtoIntrinsics, agent::TryResult}, - types::{INSTANT_DISCRIMINANT, PropertyDescriptor}, + types::PropertyDescriptor, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -187,6 +189,8 @@ pub enum Object<'a> { #[cfg(feature = "temporal")] Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, #[cfg(feature = "temporal")] + Duration(TemporalDuration<'a>) = DURATION_DISCRIMINANT, + #[cfg(feature = "temporal")] PlainTime(TemporalPlainTime<'a>) = PLAIN_TIME_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, @@ -789,6 +793,8 @@ impl<'a> From> for Value<'a> { #[cfg(feature = "temporal")] Object::Instant(data) => Value::Instant(data), #[cfg(feature = "temporal")] + Object::Duration(data) => Value::Duration(data), + #[cfg(feature = "temporal")] Object::PlainTime(data) => Value::PlainTime(data), Object::Error(data) => Self::Error(data), Object::FinalizationRegistry(data) => Self::FinalizationRegistry(data), @@ -901,6 +907,8 @@ impl<'a> TryFrom> for Object<'a> { #[cfg(feature = "temporal")] Value::Instant(x) => Ok(Self::Instant(x)), #[cfg(feature = "temporal")] + Value::Duration(x) => Ok(Self::Duration(x)), + #[cfg(feature = "temporal")] Value::PlainTime(x) => Ok(Self::PlainTime(x)), Value::Error(x) => Ok(Self::from(x)), Value::BoundFunction(x) => Ok(Self::from(x)), @@ -1021,6 +1029,8 @@ macro_rules! object_delegate { #[cfg(feature = "temporal")] Object::Instant(data) => data.$method($($arg),+), #[cfg(feature = "temporal")] + Object::Duration(data) => data.$method($($arg),+), + #[cfg(feature = "temporal")] Object::PlainTime(data) => data.$method($($arg),+), Self::Error(data) => data.$method($($arg),+), Self::BoundFunction(data) => data.$method($($arg),+), @@ -1455,6 +1465,8 @@ impl HeapSweepWeakReference for Object<'static> { #[cfg(feature = "temporal")] Self::Instant(data) => data.sweep_weak_reference(compactions).map(Self::Instant), #[cfg(feature = "temporal")] + Self::Duration(data) => data.sweep_weak_reference(compactions).map(Self::Duration), + #[cfg(feature = "temporal")] Self::PlainTime(data) => data.sweep_weak_reference(compactions).map(Self::PlainTime), Self::Error(data) => data.sweep_weak_reference(compactions).map(Self::Error), Self::BoundFunction(data) => data @@ -1695,6 +1707,8 @@ impl TryFrom for Object<'_> { #[cfg(feature = "temporal")] HeapRootData::Instant(instant) => Ok(Self::Instant(instant)), #[cfg(feature = "temporal")] + HeapRootData::Duration(duration) => Ok(Self::Duration(duration)), + #[cfg(feature = "temporal")] HeapRootData::PlainTime(plain_time) => Ok(Self::PlainTime(plain_time)), HeapRootData::Error(error) => Ok(Self::Error(error)), HeapRootData::FinalizationRegistry(finalization_registry) => { diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 5716dfaab..89a240eb8 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -2,9 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -//#[cfg(feature = "temporal")] -//use temporal_rs::Instant as Instant; // shadow regular instant, should probably change name to TemporalInstant - use super::{ BigInt, BigIntHeapData, IntoValue, Number, Numeric, OrdinaryObject, Primitive, String, StringRecord, Symbol, bigint::HeapBigInt, number::HeapNumber, string::HeapString, @@ -13,7 +10,7 @@ use super::{ use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - instant::TemporalInstant, plain_time::TemporalPlainTime, + duration::TemporalDuration, instant::TemporalInstant, plain_time::TemporalPlainTime, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::builtins::typed_array::Float16Array; @@ -188,6 +185,8 @@ pub enum Value<'a> { #[cfg(feature = "temporal")] Instant(TemporalInstant<'a>), #[cfg(feature = "temporal")] + Duration(TemporalDuration<'a>), + #[cfg(feature = "temporal")] PlainTime(TemporalPlainTime<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), @@ -329,6 +328,9 @@ pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_d pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(TemporalInstant::_def())); #[cfg(feature = "temporal")] +pub(crate) const DURATION_DISCRIMINANT: u8 = + value_discriminant(Value::Duration(TemporalDuration::_def())); +#[cfg(feature = "temporal")] pub(crate) const PLAIN_TIME_DISCRIMINANT: u8 = value_discriminant(Value::PlainTime(TemporalPlainTime::_def())); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_def())); @@ -1008,6 +1010,8 @@ impl Rootable for Value<'_> { #[cfg(feature = "temporal")] Self::Instant(instant) => Err(HeapRootData::Instant(instant.unbind())), #[cfg(feature = "temporal")] + Self::Duration(duration) => Err(HeapRootData::Duration(duration.unbind())), + #[cfg(feature = "temporal")] Self::PlainTime(plain_time) => Err(HeapRootData::PlainTime(plain_time.unbind())), Self::Error(error) => Err(HeapRootData::Error(error.unbind())), Self::FinalizationRegistry(finalization_registry) => Err( @@ -1173,6 +1177,8 @@ impl Rootable for Value<'_> { #[cfg(feature = "temporal")] HeapRootData::Instant(instant) => Some(Self::Instant(instant)), #[cfg(feature = "temporal")] + HeapRootData::Duration(duration) => Some(Self::Duration(duration)), + #[cfg(feature = "temporal")] HeapRootData::PlainTime(plain_time) => Some(Self::PlainTime(plain_time)), HeapRootData::Error(error) => Some(Self::Error(error)), HeapRootData::FinalizationRegistry(finalization_registry) => { @@ -1325,6 +1331,8 @@ impl HeapMarkAndSweep for Value<'static> { #[cfg(feature = "temporal")] Self::Instant(data) => data.mark_values(queues), #[cfg(feature = "temporal")] + Self::Duration(data) => data.mark_values(queues), + #[cfg(feature = "temporal")] Self::PlainTime(data) => data.mark_values(queues), Self::Error(data) => data.mark_values(queues), Self::BoundFunction(data) => data.mark_values(queues), @@ -1443,6 +1451,8 @@ impl HeapMarkAndSweep for Value<'static> { #[cfg(feature = "temporal")] Self::Instant(data) => data.sweep_values(compactions), #[cfg(feature = "temporal")] + Self::Duration(data) => data.sweep_values(compactions), + #[cfg(feature = "temporal")] Self::PlainTime(data) => data.sweep_values(compactions), Self::Error(data) => data.sweep_values(compactions), Self::BoundFunction(data) => data.sweep_values(compactions), @@ -1598,6 +1608,8 @@ fn map_object_to_static_string_repr(value: Value) -> String<'static> { #[cfg(feature = "temporal")] Object::Instant(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "temporal")] + Object::Duration(_) => BUILTIN_STRING_MEMORY._object_Object_, + #[cfg(feature = "temporal")] Object::PlainTime(_) => BUILTIN_STRING_MEMORY._object_Object_, #[cfg(feature = "set")] Object::Set(_) | Object::SetIterator(_) => BUILTIN_STRING_MEMORY._object_Object_, diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index d903f1ae6..deeb7363a 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1329,6 +1329,8 @@ fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> String<'static> #[cfg(feature = "temporal")] Value::Instant(_) => BUILTIN_STRING_MEMORY.object, #[cfg(feature = "temporal")] + Value::Duration(_) => BUILTIN_STRING_MEMORY.object, + #[cfg(feature = "temporal")] Value::PlainTime(_) => BUILTIN_STRING_MEMORY.object, // 13. If val has a [[Call]] internal slot, return "function". Value::BoundFunction(_) | Value::BuiltinFunction(_) | Value::ECMAScriptFunction(_) | diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index a9855c467..0bd8c3e8c 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -19,8 +19,10 @@ use crate::ecmascript::types::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::{ - builtins::temporal::{instant::TemporalInstant, plain_time::TemporalPlainTime}, - types::{INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, + builtins::temporal::{ + duration::TemporalDuration, instant::TemporalInstant, plain_time::TemporalPlainTime, + }, + types::{DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT}, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; @@ -142,7 +144,7 @@ pub mod private { use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - instant::TemporalInstant, plain_time::TemporalPlainTime, + duration::TemporalDuration, instant::TemporalInstant, plain_time::TemporalPlainTime, }; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::{ @@ -242,6 +244,8 @@ pub mod private { #[cfg(feature = "temporal")] impl RootableSealed for TemporalInstant<'_> {} #[cfg(feature = "temporal")] + impl RootableSealed for TemporalDuration<'_> {} + #[cfg(feature = "temporal")] impl RootableSealed for TemporalPlainTime<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} @@ -560,6 +564,8 @@ pub enum HeapRootData { #[cfg(feature = "temporal")] Instant(TemporalInstant<'static>) = INSTANT_DISCRIMINANT, #[cfg(feature = "temporal")] + Duration(TemporalDuration<'static>) = DURATION_DISCRIMINANT, + #[cfg(feature = "temporal")] PlainTime(TemporalPlainTime<'static>) = PLAIN_TIME_DISCRIMINANT, Error(Error<'static>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'static>) = FINALIZATION_REGISTRY_DISCRIMINANT, @@ -698,6 +704,8 @@ impl From> for HeapRootData { #[cfg(feature = "temporal")] Object::Instant(instant) => Self::Instant(instant), #[cfg(feature = "temporal")] + Object::Duration(duration) => Self::Duration(duration), + #[cfg(feature = "temporal")] Object::PlainTime(plain_time) => Self::PlainTime(plain_time), Object::Error(error) => Self::Error(error), Object::FinalizationRegistry(finalization_registry) => { @@ -861,6 +869,8 @@ impl HeapMarkAndSweep for HeapRootData { #[cfg(feature = "temporal")] Self::Instant(instant) => instant.mark_values(queues), #[cfg(feature = "temporal")] + Self::Duration(duration) => duration.mark_values(queues), + #[cfg(feature = "temporal")] Self::PlainTime(plain_time) => plain_time.mark_values(queues), Self::Error(error) => error.mark_values(queues), Self::FinalizationRegistry(finalization_registry) => { @@ -1014,6 +1024,8 @@ impl HeapMarkAndSweep for HeapRootData { #[cfg(feature = "temporal")] Self::Instant(instant) => instant.sweep_values(compactions), #[cfg(feature = "temporal")] + Self::Duration(duration) => duration.sweep_values(compactions), + #[cfg(feature = "temporal")] Self::PlainTime(plain_time) => plain_time.sweep_values(compactions), Self::Error(error) => error.sweep_values(compactions), Self::FinalizationRegistry(finalization_registry) => { diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 04fffd240..0ecfcce5a 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -31,7 +31,8 @@ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - instant::data::InstantRecord, plain_time::data::PlainTimeHeapData, + duration::data::DurationHeapData, instant::data::InstantRecord, + plain_time::data::PlainTimeHeapData, }; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ @@ -152,6 +153,8 @@ pub(crate) struct Heap { #[cfg(feature = "temporal")] pub(crate) instants: Vec>, #[cfg(feature = "temporal")] + pub(crate) durations: Vec>, + #[cfg(feature = "temporal")] pub(crate) plain_times: Vec>, pub(crate) ecmascript_functions: Vec>, /// ElementsArrays is where all keys and values arrays live; @@ -304,6 +307,8 @@ impl Heap { #[cfg(feature = "temporal")] instants: Vec::with_capacity(1024), #[cfg(feature = "temporal")] + durations: Vec::with_capacity(1024), + #[cfg(feature = "temporal")] plain_times: Vec::with_capacity(1024), ecmascript_functions: Vec::with_capacity(1024), elements: ElementArrays { diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 922517b05..fb082552c 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -26,7 +26,9 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::TemporalInstant; +use crate::ecmascript::builtins::temporal::{ + duration::TemporalDuration, instant::TemporalInstant, plain_time::TemporalPlainTime, +}; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView, typed_array::VoidArray}; #[cfg(feature = "shared-array-buffer")] @@ -72,7 +74,6 @@ use crate::ecmascript::{ promise_group_record::PromiseGroup, }, proxy::Proxy, - temporal::plain_time::TemporalPlainTime, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{ @@ -460,6 +461,8 @@ pub(crate) struct HeapBits { pub(super) instants: BitRange, #[cfg(feature = "temporal")] pub(super) plain_times: BitRange, + #[cfg(feature = "temporal")] + pub(super) durations: BitRange, pub(super) declarative_environments: BitRange, pub(super) ecmascript_functions: BitRange, pub(super) embedder_objects: BitRange, @@ -538,7 +541,10 @@ pub(crate) struct WorkQueues<'a> { pub(crate) dates: Vec>, #[cfg(feature = "temporal")] pub(crate) instants: Vec>, + #[cfg(feature = "temporal")] pub(crate) plain_times: Vec>, + #[cfg(feature = "temporal")] + pub(crate) durations: Vec>, pub(crate) declarative_environments: Vec>, pub(crate) e_2_1: Vec>, pub(crate) e_2_2: Vec>, @@ -692,6 +698,7 @@ impl HeapBits { let instants = BitRange::from_bit_count_and_len(&mut bit_count, heap.instants.len()); #[cfg(feature = "date")] let plain_times = BitRange::from_bit_count_and_len(&mut bit_count, heap.plain_times.len()); + let durations = BitRange::from_bit_count_and_len(&mut bit_count, heap.durations.len()); let declarative_environments = BitRange::from_bit_count_and_len(&mut bit_count, heap.environments.declarative.len()); let ecmascript_functions = @@ -803,6 +810,8 @@ impl HeapBits { instants, #[cfg(feature = "temporal")] plain_times, + #[cfg(feature = "temporal")] + durations, declarative_environments, e_2_1, e_2_2, @@ -917,6 +926,8 @@ impl HeapBits { WeakKey::Instant(d) => self.instants.get_bit(d.get_index(), &self.bits), #[cfg(feature = "temporal")] WeakKey::PlainTime(d) => self.plain_times.get_bit(d.get_index(), &self.bits), + #[cfg(feature = "temporal")] + WeakKey::Duration(d) => self.durations.get_bit(d.get_index(), &self.bits), WeakKey::Error(d) => self.errors.get_bit(d.get_index(), &self.bits), WeakKey::FinalizationRegistry(d) => self .finalization_registrys @@ -1057,6 +1068,8 @@ impl<'a> WorkQueues<'a> { #[cfg(feature = "temporal")] instants: Vec::with_capacity(heap.instants.len() / 4), #[cfg(feature = "temporal")] + durations: Vec::with_capacity(heap.durations.len() / 4), + #[cfg(feature = "temporal")] plain_times: Vec::with_capacity(heap.plain_times.len() / 4), declarative_environments: Vec::with_capacity(heap.environments.declarative.len() / 4), e_2_1: Vec::with_capacity(heap.elements.e2pow1.values.len() / 4), @@ -1167,6 +1180,8 @@ impl<'a> WorkQueues<'a> { #[cfg(feature = "temporal")] instants, #[cfg(feature = "temporal")] + durations, + #[cfg(feature = "temporal")] plain_times, declarative_environments, e_2_1, @@ -1289,6 +1304,7 @@ impl<'a> WorkQueues<'a> { && data_views.is_empty() && dates.is_empty() && instants.is_empty() + && durations.is_empty() && plain_times.is_empty() && declarative_environments.is_empty() && e_2_1.is_empty() @@ -1653,6 +1669,8 @@ pub(crate) struct CompactionLists { pub(crate) instants: CompactionList, #[cfg(feature = "temporal")] pub(crate) plain_times: CompactionList, + #[cfg(feature = "temporal")] + pub(crate) durations: CompactionList, pub(crate) declarative_environments: CompactionList, pub(crate) e_2_1: CompactionList, pub(crate) e_2_2: CompactionList, @@ -1812,6 +1830,8 @@ impl CompactionLists { instants: CompactionList::from_mark_bits(&bits.instants, &bits.bits), #[cfg(feature = "temporal")] plain_times: CompactionList::from_mark_bits(&bits.plain_times, &bits.bits), + #[cfg(feature = "temporal")] + durations: CompactionList::from_mark_bits(&bits.durations, &bits.bits), errors: CompactionList::from_mark_bits(&bits.errors, &bits.bits), executables: CompactionList::from_mark_bits(&bits.executables, &bits.bits), maps: CompactionList::from_mark_bits(&bits.maps, &bits.bits), diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index f957ab01d..9b97a4015 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -39,6 +39,8 @@ pub(crate) enum IntrinsicObjectIndexes { #[cfg(feature = "temporal")] TemporalInstantPrototype, #[cfg(feature = "temporal")] + TemporalDurationPrototype, + #[cfg(feature = "temporal")] TemporalPlainTimePrototype, // Text processing @@ -180,6 +182,8 @@ pub(crate) enum IntrinsicConstructorIndexes { #[cfg(feature = "temporal")] TemporalInstant, #[cfg(feature = "temporal")] + TemporalDuration, + #[cfg(feature = "temporal")] TemporalPlainTime, // Text processing diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index ecd83ca66..c81e5fd02 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -19,6 +19,8 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::duration::TemporalDuration; +#[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::plain_time::TemporalPlainTime; @@ -135,6 +137,8 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc #[cfg(feature = "temporal")] instants, #[cfg(feature = "temporal")] + durations, + #[cfg(feature = "temporal")] plain_times, ecmascript_functions, elements, @@ -567,6 +571,15 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc instants.get(index).mark_values(&mut queues); } }); + let mut duration_marks: Box<[TemporalDuration]> = queues.durations.drain(..).collect(); + duration_marks.sort(); + duration_marks.iter().for_each(|&idx| { + let index = idx.get_index(); + if bits.durations.set_bit(index, &bits.bits) { + // Did mark. + durations.get(index).mark_values(&mut queues); + } + }); let mut plain_time_marks: Box<[TemporalPlainTime]> = queues.plain_times.drain(..).collect(); plain_time_marks.sort(); @@ -1284,6 +1297,8 @@ fn sweep( #[cfg(feature = "temporal")] instants, #[cfg(feature = "temporal")] + durations, + #[cfg(feature = "temporal")] plain_times, ecmascript_functions, elements, @@ -1745,6 +1760,12 @@ fn sweep( }); } #[cfg(feature = "temporal")] + if !durations.is_empty() { + s.spawn(|| { + sweep_heap_vector_values(durations, &compactions, &bits.durations, &bits.bits); + }); + } + #[cfg(feature = "temporal")] if !plain_times.is_empty() { s.spawn(|| { sweep_heap_vector_values(plain_times, &compactions, &bits.plain_times, &bits.bits); From 0abe18f21fef247396a936d670878c3c1f85d547 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 20 Nov 2025 14:38:27 +0100 Subject: [PATCH 39/42] instant.proto.to_string --- nova_vm/src/builtin_strings | 3 + nova_vm/src/ecmascript/builtins/temporal.rs | 76 +++++++++++++++++- .../temporal/instant/instant_prototype.rs | 77 +++++++++++++++++-- 3 files changed, 147 insertions(+), 9 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 3885f009e..44e54b803 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -39,6 +39,7 @@ any apply arguments Array +#[cfg(feature="temporal")]auto Array Iterator #[cfg(feature = "array-buffer")]ArrayBuffer #[cfg(feature = "math")]asin @@ -139,6 +140,7 @@ find findIndex findLast findLastIndex +#[cfg(feature = "temporal")]fractionalSecondDigits #[cfg(feature = "annex-b-string")]fixed #[cfg(feature = "regexp")]flags flat @@ -460,6 +462,7 @@ SyntaxError then throw #[cfg(feature = "atomics")]timed-out +#[cfg(feature = "temporal")]timeZone toArray #[cfg(feature = "date")]toDateString toExponential diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index e398a9d96..a30e85a46 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -12,13 +12,14 @@ use temporal_rs::options::{DifferenceSettings, RoundingIncrement, RoundingMode, use crate::{ ecmascript::{ + abstract_operations::operations_on_objects::get, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::temporal::{ - instant::instant_prototype::get_temporal_unit_valued_option, + instant::instant_prototype::{DefaultOption, get_temporal_unit_valued_option}, options::{get_rounding_increment_option, get_rounding_mode_option}, }, - execution::{Agent, JsResult, Realm}, - types::{BUILTIN_STRING_MEMORY, IntoValue, Object}, + execution::{Agent, JsResult, Realm, agent::ExceptionType}, + types::{BUILTIN_STRING_MEMORY, IntoValue, Number, Object, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope, trivially_bindable}, @@ -93,6 +94,75 @@ trivially_bindable!(Unit); trivially_bindable!(RoundingMode); trivially_bindable!(RoundingIncrement); +/// [13.15 GetTemporalFractionalSecondDigitsOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-gettemporalfractionalseconddigitsoption) +/// The abstract operation GetTemporalFractionalSecondDigitsOption takes argument +/// options (an Object) and returns either a normal completion containing +/// either auto or an integer in the inclusive interval from 0 to 9, +/// or a throw completion. It fetches and validates the "fractionalSecondDigits" +/// property from options, returning a default if absent. +pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( + agent: &mut Agent, + options: Object<'gc>, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Value<'gc>> { + let options = options.bind(gc.nogc()); + // 1. Let digitsValue be ? Get(options, "fractionalSecondDigits"). + let digits_value = get( + agent, + options.unbind(), + BUILTIN_STRING_MEMORY + .fractionalSecondDigits + .to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 2. If digitsValue is undefined, return auto. + if digits_value.is_undefined() { + return Ok(BUILTIN_STRING_MEMORY.auto.bind(gc.nogc()).into_value()); + } + // 3. If digitsValue is not a Number, then + if !digits_value.is_number() { + // a. If ? ToString(digitsValue) is not "auto", throw a RangeError exception. + if digits_value + .to_string(agent, gc.reborrow()) + .unbind()? + .as_bytes(agent) + != b"auto" + { + // b. Return auto. + return Ok(BUILTIN_STRING_MEMORY.auto.bind(gc.nogc()).into_value()); + } + } + // 4. If digitsValue is NaN, +∞𝔽, or -∞𝔽, throw a RangeError exception. + if digits_value.is_nan(agent) + || digits_value.is_pos_infinity(agent) + || digits_value.is_neg_infinity(agent) + { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "fractionalSecondDigits must be a finite number or \"auto\"", + gc.into_nogc(), + )); + } + // 5. Let digitCount be floor(ℝ(digitsValue)). + let digit_count = digits_value + .to_number(agent, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let digit_count = digit_count.into_f64(agent).floor() as i32; + // 6. If digitCount < 0 or digitCount > 9, throw a RangeError exception. + if digit_count < 0 || digit_count > 9 { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "fractionalSecondDigits must be between 0 and 9", + gc.into_nogc(), + )); + } + // 7. Return digitCount. + Ok(Number::from_i64(agent, digit_count.into(), gc.nogc()).into_value()) +} + /// [13.42 GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )](https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings) /// The abstract operation GetDifferenceSettings takes arguments operation (since or until), /// options (an Object), unitGroup (date, time, or datetime), disallowedUnits (a List of Temporal units), diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index ad84e6730..d9696c420 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -3,7 +3,8 @@ use temporal_rs::options::{RoundingMode, RoundingOptions, Unit}; use crate::{ ecmascript::{ abstract_operations::{ - operations_on_objects::try_create_data_property_or_throw, type_conversion::to_number, + operations_on_objects::{get, try_create_data_property_or_throw}, + type_conversion::to_number, }, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::{ @@ -11,6 +12,7 @@ use crate::{ ordinary::ordinary_object_create_with_intrinsics, temporal::{ error::temporal_err_to_js_err, + get_temporal_fractional_second_digits_option, instant::{ add_duration_to_instant, create_temporal_instant, difference_temporal_instant, require_internal_slot_temporal_instant, to_temporal_instant, @@ -28,7 +30,7 @@ use crate::{ types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, PropertyKey, String, Value}, }, engine::{ - context::{Bindable, GcScope, NoGcScope}, + context::{Bindable, GcScope, NoGcScope, trivially_bindable}, rootable::Scopable, }, heap::WellKnownSymbolIndexes, @@ -406,13 +408,76 @@ impl TemporalInstantPrototype { Ok(Value::from(true)) } + trivially_bindable!(RoundingMode); /// ### [8.3.11 Temporal.Instant.prototype.toString ( [ options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring) fn to_string<'gc>( - _agent: &mut Agent, - _this_value: Value, - _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + // 1. Let instant be the this value. + let options = args.get(0).bind(gc.nogc()); + let instant = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, instant, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(agent, options, gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 4. NOTE: The following steps read options and perform independent validation in alphabetical order (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode"). + // 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions). + let digits = get_temporal_fractional_second_digits_option( + agent, + resolved_options.unbind(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc). + let rounding_mode = + get_rounding_mode_option(agent, resolved_options, RoundingMode::Trunc, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + // 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", unset). + let smallest_unit = get_temporal_unit_valued_option( + agent, + resolved_options, + BUILTIN_STRING_MEMORY.smallestUnit.to_property_key(), + DefaultOption::Unset, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 8. Let timeZone be ? Get(resolvedOptions, "timeZone"). + let tz = get( + agent, + resolved_options, + BUILTIN_STRING_MEMORY.timeZone.to_property_key(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 9. Perform ? ValidateTemporalUnitValue(smallestUnit, time). + // 10. If smallestUnit is hour, throw a RangeError exception. + if smallest_unit == Unit::Hour { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "smallestUnit is hour", + gc.into_nogc(), + )); + } + // 11. If timeZone is not undefined, then + let tz = if !tz.is_undefined() { + // a. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone). + todo!() + }; + // 12. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits). + // 13. Let roundedNs be RoundTemporalInstant(instant.[[EpochNanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode). + // 14. Let roundedInstant be ! CreateTemporalInstant(roundedNs). + // 15. Return TemporalInstantToString(roundedInstant, timeZone, precision.[[Precision]]). unimplemented!() } From 6c9f50b5f5695a150e99c327f0098e81c2ffe7a6 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 20 Nov 2025 17:35:52 +0100 Subject: [PATCH 40/42] small check fixes --- .../ecmascript/builtins/temporal/duration.rs | 6 +++--- .../temporal/instant/instant_prototype.rs | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 0c8292b26..aa18290d8 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -36,9 +36,9 @@ impl TemporalDuration<'_> { pub(crate) const fn get_index(self) -> usize { self.0.into_index() } - pub(crate) fn inner_duration(self, agent: &Agent) -> temporal_rs::Duration { - agent[self].duration - } + // pub(crate) fn inner_duration(self, agent: &Agent) -> temporal_rs::Duration { + // agent[self].duration + // } } bindable_handle!(TemporalDuration); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index d9696c420..0bdf5cad7 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -420,7 +420,11 @@ impl TemporalInstantPrototype { let options = args.get(0).bind(gc.nogc()); let instant = this_value.bind(gc.nogc()); // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). +<<<<<<< HEAD let instant = require_internal_slot_temporal_instant(agent, instant, gc.nogc()) +======= + let _instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) +>>>>>>> cf818d02 (small check fixes) .unbind()? .bind(gc.nogc()); // 3. Let resolvedOptions be ? GetOptionsObject(options). @@ -429,7 +433,7 @@ impl TemporalInstantPrototype { .bind(gc.nogc()); // 4. NOTE: The following steps read options and perform independent validation in alphabetical order (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode"). // 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions). - let digits = get_temporal_fractional_second_digits_option( + let _digits = get_temporal_fractional_second_digits_option( agent, resolved_options.unbind(), gc.reborrow(), @@ -437,10 +441,21 @@ impl TemporalInstantPrototype { .unbind()? .bind(gc.nogc()); // 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc). +<<<<<<< HEAD let rounding_mode = get_rounding_mode_option(agent, resolved_options, RoundingMode::Trunc, gc.reborrow()) .unbind()? .bind(gc.nogc()); +======= + let _rounding_mode = get_rounding_mode_option( + agent, + resolved_options.get(agent), + RoundingMode::Trunc, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); +>>>>>>> cf818d02 (small check fixes) // 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", unset). let smallest_unit = get_temporal_unit_valued_option( agent, From 61b8e1d163ece63d45fd3d1fb3d2ecd7d46a1abe Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 27 Nov 2025 12:04:40 +0200 Subject: [PATCH 41/42] fix: finish Temporal.Instant.prototype.toString impl --- nova_vm/src/ecmascript/builtins/temporal.rs | 35 ++++++--- .../temporal/duration/duration_constructor.rs | 6 +- .../temporal/duration/duration_prototype.rs | 4 +- .../temporal/instant/instant_prototype.rs | 76 ++++++++++++------- 4 files changed, 77 insertions(+), 44 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index a30e85a46..f923400c1 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -8,14 +8,17 @@ pub mod instant; pub mod options; pub mod plain_time; -use temporal_rs::options::{DifferenceSettings, RoundingIncrement, RoundingMode, Unit, UnitGroup}; +use temporal_rs::{ + options::{DifferenceSettings, RoundingIncrement, RoundingMode, Unit, UnitGroup}, + parsers::Precision, +}; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::get, builders::ordinary_object_builder::OrdinaryObjectBuilder, builtins::temporal::{ - instant::instant_prototype::{DefaultOption, get_temporal_unit_valued_option}, + instant::instant_prototype::get_temporal_unit_valued_option, options::{get_rounding_increment_option, get_rounding_mode_option}, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, @@ -41,7 +44,7 @@ impl Temporal { let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(3) + .with_property_capacity(4) .with_prototype(object_prototype) // 1.2.1 Temporal.Instant ( . . . ) .with_property(|builder| { @@ -93,6 +96,7 @@ trivially_bindable!(UnitGroup); trivially_bindable!(Unit); trivially_bindable!(RoundingMode); trivially_bindable!(RoundingIncrement); +trivially_bindable!(Precision); /// [13.15 GetTemporalFractionalSecondDigitsOption ( options )](https://tc39.es/proposal-temporal/#sec-temporal-gettemporalfractionalseconddigitsoption) /// The abstract operation GetTemporalFractionalSecondDigitsOption takes argument @@ -104,10 +108,10 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( agent: &mut Agent, options: Object<'gc>, mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Value<'gc>> { +) -> JsResult<'gc, temporal_rs::parsers::Precision> { let options = options.bind(gc.nogc()); // 1. Let digitsValue be ? Get(options, "fractionalSecondDigits"). - let digits_value = get( + let mut digits_value = get( agent, options.unbind(), BUILTIN_STRING_MEMORY @@ -119,20 +123,31 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( .bind(gc.nogc()); // 2. If digitsValue is undefined, return auto. if digits_value.is_undefined() { - return Ok(BUILTIN_STRING_MEMORY.auto.bind(gc.nogc()).into_value()); + return Ok(temporal_rs::parsers::Precision::Auto); + } + if let Value::Integer(digits_value) = digits_value + && (0..=9).contains(&digits_value.into_i64()) + { + return Ok(temporal_rs::parsers::Precision::Digit( + digits_value.into_i64() as u8, + )); } // 3. If digitsValue is not a Number, then if !digits_value.is_number() { + let scoped_digits_value = digits_value.scope(agent, gc.nogc()); // a. If ? ToString(digitsValue) is not "auto", throw a RangeError exception. if digits_value + .unbind() .to_string(agent, gc.reborrow()) .unbind()? .as_bytes(agent) != b"auto" { // b. Return auto. - return Ok(BUILTIN_STRING_MEMORY.auto.bind(gc.nogc()).into_value()); + return Ok(temporal_rs::parsers::Precision::Auto); } + // SAFETY: not shared. + digits_value = unsafe { scoped_digits_value.take(agent) }.bind(gc.nogc()); } // 4. If digitsValue is NaN, +∞𝔽, or -∞𝔽, throw a RangeError exception. if digits_value.is_nan(agent) @@ -150,9 +165,9 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( .to_number(agent, gc.reborrow()) .unbind()? .bind(gc.nogc()); - let digit_count = digit_count.into_f64(agent).floor() as i32; + let digit_count = digit_count.into_f64(agent).floor(); // 6. If digitCount < 0 or digitCount > 9, throw a RangeError exception. - if digit_count < 0 || digit_count > 9 { + if digit_count < 0.0 || digit_count > 9.0 { return Err(agent.throw_exception_with_static_message( ExceptionType::RangeError, "fractionalSecondDigits must be between 0 and 9", @@ -160,7 +175,7 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( )); } // 7. Return digitCount. - Ok(Number::from_i64(agent, digit_count.into(), gc.nogc()).into_value()) + Ok(temporal_rs::parsers::Precision::Digit(digit_count as u8)) } /// [13.42 GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, fallbackSmallestUnit, smallestLargestDefaultUnit )](https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs index 72b5bc2bf..80c17eb07 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -1,9 +1,7 @@ use crate::{ ecmascript::{ builders::builtin_function_builder::BuiltinFunctionBuilder, - builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor - }, + builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor}, execution::{Agent, JsResult, Realm}, types::{BUILTIN_STRING_MEMORY, IntoObject, Object, String, Value}, }, @@ -40,7 +38,7 @@ impl TemporalDurationConstructor { BuiltinFunctionBuilder::new_intrinsic_constructor::( agent, realm, ) - .with_property_capacity(5) + .with_property_capacity(1) .with_prototype_property(duration_prototype.into_object()) .build(); } diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs index 8f7c956b7..5d338a663 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs @@ -12,12 +12,12 @@ pub(crate) struct TemporalDurationPrototype; impl TemporalDurationPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); - let this = intrinsics.temporal_instant_prototype(); + let this = intrinsics.temporal_duration_prototype(); let object_prototype = intrinsics.object_prototype(); let duration_constructor = intrinsics.temporal_duration(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(15) + .with_property_capacity(2) .with_prototype(object_prototype) .with_constructor_property(duration_constructor) .with_property(|builder| { diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index 0bdf5cad7..e84da608c 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -1,4 +1,7 @@ -use temporal_rs::options::{RoundingMode, RoundingOptions, Unit}; +use temporal_rs::{ + TimeZone, + options::{RoundingMode, RoundingOptions, ToStringRoundingOptions, Unit}, +}; use crate::{ ecmascript::{ @@ -420,34 +423,27 @@ impl TemporalInstantPrototype { let options = args.get(0).bind(gc.nogc()); let instant = this_value.bind(gc.nogc()); // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). -<<<<<<< HEAD - let instant = require_internal_slot_temporal_instant(agent, instant, gc.nogc()) -======= - let _instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) ->>>>>>> cf818d02 (small check fixes) + let instant = require_internal_slot_temporal_instant(agent, instant.unbind(), gc.nogc()) .unbind()? - .bind(gc.nogc()); + .scope(agent, gc.nogc()); // 3. Let resolvedOptions be ? GetOptionsObject(options). let resolved_options = get_options_object(agent, options, gc.nogc()) .unbind()? - .bind(gc.nogc()); - // 4. NOTE: The following steps read options and perform independent validation in alphabetical order (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode"). + .scope(agent, gc.nogc()); + // 4. NOTE: The following steps read options and perform independent + // validation in alphabetical order + // (GetTemporalFractionalSecondDigitsOption reads + // "fractionalSecondDigits" and GetRoundingModeOption reads + // "roundingMode"). // 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions). - let _digits = get_temporal_fractional_second_digits_option( + let digits = get_temporal_fractional_second_digits_option( agent, resolved_options.unbind(), gc.reborrow(), ) - .unbind()? - .bind(gc.nogc()); + .unbind()?; // 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc). -<<<<<<< HEAD - let rounding_mode = - get_rounding_mode_option(agent, resolved_options, RoundingMode::Trunc, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); -======= - let _rounding_mode = get_rounding_mode_option( + let rounding_mode = get_rounding_mode_option( agent, resolved_options.get(agent), RoundingMode::Trunc, @@ -455,13 +451,11 @@ impl TemporalInstantPrototype { ) .unbind()? .bind(gc.nogc()); ->>>>>>> cf818d02 (small check fixes) // 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", unset). let smallest_unit = get_temporal_unit_valued_option( agent, - resolved_options, + resolved_options.get(agent), BUILTIN_STRING_MEMORY.smallestUnit.to_property_key(), - DefaultOption::Unset, gc.reborrow(), ) .unbind()? @@ -469,15 +463,22 @@ impl TemporalInstantPrototype { // 8. Let timeZone be ? Get(resolvedOptions, "timeZone"). let tz = get( agent, - resolved_options, + resolved_options.get(agent), BUILTIN_STRING_MEMORY.timeZone.to_property_key(), gc.reborrow(), ) .unbind()? .bind(gc.nogc()); // 9. Perform ? ValidateTemporalUnitValue(smallestUnit, time). + if !smallest_unit.is_none_or(|su| su.is_time_unit()) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "smallestUnit is not a valid time unit", + gc.into_nogc(), + )); + } // 10. If smallestUnit is hour, throw a RangeError exception. - if smallest_unit == Unit::Hour { + if smallest_unit == Some(Unit::Hour) { return Err(agent.throw_exception_with_static_message( ExceptionType::RangeError, "smallestUnit is hour", @@ -485,15 +486,34 @@ impl TemporalInstantPrototype { )); } // 11. If timeZone is not undefined, then - let tz = if !tz.is_undefined() { + let time_zone = if !tz.is_undefined() { // a. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone). - todo!() + Some(TimeZone::utc()) + } else { + None }; + let instant = unsafe { instant.take(agent) }.bind(gc.nogc()); // 12. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits). - // 13. Let roundedNs be RoundTemporalInstant(instant.[[EpochNanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode). + // 13. Let roundedNs be RoundTemporalInstant( + // instant.[[EpochNanoseconds]], + // precision.[[Increment]], + // precision.[[Unit]], + // roundingMode + // ). // 14. Let roundedInstant be ! CreateTemporalInstant(roundedNs). // 15. Return TemporalInstantToString(roundedInstant, timeZone, precision.[[Precision]]). - unimplemented!() + let options = ToStringRoundingOptions { + precision: digits, + smallest_unit, + rounding_mode: Some(rounding_mode), + }; + match instant + .inner_instant(agent) + .to_ixdtf_string(time_zone, options) + { + Ok(string) => Ok(Value::from_string(agent, string, gc.into_nogc())), + Err(err) => Err(temporal_err_to_js_err(agent, err, gc.into_nogc())), + } } /// ### [8.3.12 Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring) From 20d44737c9056d48a18ea43ca3cde1160f8b8345 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 27 Nov 2025 13:24:22 +0200 Subject: [PATCH 42/42] fix errors from rebase --- nova_vm/src/builtin_strings | 1 - nova_vm/src/ecmascript/builtins/temporal.rs | 3 ++- .../builtins/temporal/instant/instant_prototype.rs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 44e54b803..6fb5c7a8b 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -39,7 +39,6 @@ any apply arguments Array -#[cfg(feature="temporal")]auto Array Iterator #[cfg(feature = "array-buffer")]ArrayBuffer #[cfg(feature = "math")]asin diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index f923400c1..52b2e336c 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -22,7 +22,7 @@ use crate::{ options::{get_rounding_increment_option, get_rounding_mode_option}, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, IntoValue, Number, Object, Value}, + types::{BUILTIN_STRING_MEMORY, IntoValue, Object, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope, trivially_bindable}, @@ -162,6 +162,7 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( } // 5. Let digitCount be floor(ℝ(digitsValue)). let digit_count = digits_value + .unbind() .to_number(agent, gc.reborrow()) .unbind()? .bind(gc.nogc()); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs index e84da608c..9d0289e08 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -33,7 +33,7 @@ use crate::{ types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, PropertyKey, String, Value}, }, engine::{ - context::{Bindable, GcScope, NoGcScope, trivially_bindable}, + context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, }, heap::WellKnownSymbolIndexes, @@ -411,7 +411,6 @@ impl TemporalInstantPrototype { Ok(Value::from(true)) } - trivially_bindable!(RoundingMode); /// ### [8.3.11 Temporal.Instant.prototype.toString ( [ options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring) fn to_string<'gc>( agent: &mut Agent, @@ -438,7 +437,7 @@ impl TemporalInstantPrototype { // 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions). let digits = get_temporal_fractional_second_digits_option( agent, - resolved_options.unbind(), + resolved_options.get(agent), gc.reborrow(), ) .unbind()?;