From d05c1ed628d62f0dc3cc1187a9c6e2a990afc3ac Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 30 Sep 2025 16:13:51 +0200 Subject: [PATCH 01/55] added temporal intrinsic --- Cargo.toml | 1 + nova_vm/Cargo.toml | 13 ++- nova_vm/src/builtin_strings | 1 + nova_vm/src/ecmascript/builtins.rs | 4 + nova_vm/src/ecmascript/builtins/temporal.rs | 105 ++++++++++++++++++ .../ecmascript/execution/realm/intrinsics.rs | 11 ++ nova_vm/src/heap/heap_constants.rs | 3 +- 7 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal.rs diff --git a/Cargo.toml b/Cargo.toml index 7d531ef63..4efb57a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ unicode-normalization = "0.1.24" usdt = { git = "https://github.com/aapoalas/usdt.git", branch = "nova-aarch64-branch" } wtf8 = "0.1" xsum = "0.1.6" +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 871fb69f3..18669d576 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -40,6 +40,7 @@ unicode-normalization = { workspace = true } usdt = { workspace = true } wtf8 = { workspace = true } xsum = { workspace = true } +temporal_rs = { workspace = true, optional = true } [features] default = [ @@ -53,9 +54,15 @@ default = [ "regexp", "set", "annex-b", + "temporal", ] array-buffer = ["ecmascript_atomics"] -atomics = ["array-buffer", "shared-array-buffer", "ecmascript_atomics", "ecmascript_futex"] +atomics = [ + "array-buffer", + "shared-array-buffer", + "ecmascript_atomics", + "ecmascript_futex", +] date = [] json = ["dep:sonic-rs"] math = [] @@ -64,6 +71,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 +92,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"] @@ -93,6 +102,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 8c54aa3e9..9b106bb8f 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 7fd15a8fc..1d90f5363 100644 --- a/nova_vm/src/ecmascript/builtins.rs +++ b/nova_vm/src/ecmascript/builtins.rs @@ -66,6 +66,8 @@ mod set; #[cfg(feature = "shared-array-buffer")] mod shared_array_buffer; mod structured_data; +#[cfg(feature = "temporal")] +mod temporal; mod text_processing; #[cfg(feature = "array-buffer")] mod typed_array; @@ -112,6 +114,8 @@ pub use set::*; #[cfg(feature = "shared-array-buffer")] pub use shared_array_buffer::*; pub(crate) use structured_data::*; +#[cfg(feature = "temporal")] +pub use temporal::*; pub use text_processing::*; #[cfg(feature = "array-buffer")] pub use 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 0577fc06a..822a6cdc4 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -11,6 +11,8 @@ use crate::ecmascript::AtomicsObject; use crate::ecmascript::JSONObject; #[cfg(feature = "math")] use crate::ecmascript::MathObject; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::TemporalObject; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBufferConstructor, ArrayBufferPrototype, DataViewConstructor, DataViewPrototype, @@ -210,6 +212,10 @@ 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")] @@ -914,6 +920,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 2fbb5badb..a77e6e3f1 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -33,7 +33,8 @@ pub(crate) enum IntrinsicObjectIndexes { MathObject, #[cfg(feature = "date")] DatePrototype, - + #[cfg(feature = "temporal")] + TemporalObject, // Text processing #[cfg(feature = "regexp")] RegExpPrototype, From 8e2bd769bb1c8224d9fed147a5a7db8df10de094 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Wed, 8 Oct 2025 17:12:27 +0200 Subject: [PATCH 02/55] 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 | 21 ++- nova_vm/src/heap/heap_constants.rs | 6 + 5 files changed, 181 insertions(+), 61 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 9b106bb8f..cb3aae3e0 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 822a6cdc4..279a5c1a1 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -12,7 +12,9 @@ use crate::ecmascript::JSONObject; #[cfg(feature = "math")] use crate::ecmascript::MathObject; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::TemporalObject; +use crate::ecmascript::builtins::{ + TemporalObject, instant::InstantConstructor, instant::InstantPrototype, +}; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBufferConstructor, ArrayBufferPrototype, DataViewConstructor, DataViewPrototype, @@ -214,7 +216,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); @@ -925,6 +932,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 a77e6e3f1..036e31fac 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -35,6 +35,10 @@ pub(crate) enum IntrinsicObjectIndexes { DatePrototype, #[cfg(feature = "temporal")] TemporalObject, + #[cfg(feature = "temporal")] + TemporalInstant, + #[cfg(feature = "temporal")] + TemporalInstantPrototype, // Text processing #[cfg(feature = "regexp")] RegExpPrototype, @@ -171,6 +175,8 @@ pub(crate) enum IntrinsicConstructorIndexes { BigInt, #[cfg(feature = "date")] Date, + #[cfg(feature = "temporal")] + TemporalInstant, // Text processing String, From 4a2b4595aeb76744d01f6a266ddaa2daa6915637 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 16 Oct 2025 16:43:26 +0200 Subject: [PATCH 03/55] 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 | 7 ++ .../src/ecmascript/types/language/value.rs | 10 +++ nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 8 ++ 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, 150 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4efb57a21..ec41cf881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ unicode-normalization = "0.1.24" usdt = { git = "https://github.com/aapoalas/usdt.git", branch = "nova-aarch64-branch" } wtf8 = "0.1" xsum = "0.1.6" -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 bb16b834b..1741a3202 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -94,6 +94,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, @@ -208,6 +210,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), @@ -315,6 +319,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), @@ -426,6 +432,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)), @@ -568,6 +576,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), @@ -673,6 +683,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), @@ -794,6 +806,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 afd5232ee..e20bc3d9f 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -119,6 +119,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, @@ -645,6 +646,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), @@ -769,6 +772,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),+), @@ -1534,6 +1539,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 5bb981d67..12aa52f2c 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -137,6 +137,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>), @@ -273,6 +275,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)); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_DEF)); +#[cfg(feature = "temporal")] +pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(Instant::_DEF)); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_DEF)); pub(crate) const ECMASCRIPT_FUNCTION_DISCRIMINANT: u8 = @@ -916,6 +920,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()), @@ -1077,6 +1083,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)) @@ -1493,6 +1501,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 7160b5925..52af4fa92 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1298,6 +1298,8 @@ pub(crate) fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> Strin 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 bf404db42..6d6cacaca 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -127,6 +127,8 @@ pub(crate) 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<'_> {} @@ -438,6 +440,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, @@ -616,6 +620,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) @@ -765,6 +771,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 d56114a25..17166897d 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -21,6 +21,8 @@ use std::ops::Deref; #[cfg(feature = "date")] use crate::ecmascript::DateHeapData; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBuffer, ArrayBufferHeapData, DataView, DataViewRecord, DetachKey, TypedArrayRecord, @@ -75,6 +77,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 @@ -222,6 +226,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 fca4e4754..682b02f78 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -21,6 +21,8 @@ use soavec_derive::SoAble; #[cfg(feature = "date")] use crate::ecmascript::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::Instant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ArrayBuffer, DataView, VoidArray}; #[cfg(feature = "regexp")] @@ -491,6 +493,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>, @@ -994,6 +998,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), @@ -1100,6 +1106,8 @@ impl<'a> WorkQueues<'a> { data_views, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, declarative_environments, e_2_1, e_2_2, @@ -1219,6 +1227,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() @@ -1578,6 +1587,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, @@ -1733,6 +1744,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 c0113e435..b146e7650 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -6,6 +6,8 @@ use std::thread; #[cfg(feature = "date")] use crate::ecmascript::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::Instant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ArrayBuffer, DataView, VoidArray}; #[cfg(feature = "regexp")] @@ -81,6 +83,8 @@ pub(crate) fn heap_gc(agent: &mut Agent, root_realms: &mut [Option = 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]> = @@ -1207,6 +1227,8 @@ fn sweep( caches, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, ecmascript_functions, elements, embedder_objects, @@ -1660,6 +1682,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 b371cecd5ed23486a51109a056657fded975e7c0 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Sun, 19 Oct 2025 10:02:45 +0200 Subject: [PATCH 04/55] added data.rs --- nova_vm/src/ecmascript/builtins/ordinary.rs | 3 + .../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, 105 insertions(+), 56 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 56bb00750..22f27faf0 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -11,6 +11,7 @@ pub use shape::*; use std::{ collections::{TryReserveError, hash_map::Entry}, ops::ControlFlow, + time::Instant, vec, }; @@ -1651,6 +1652,8 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .heap .create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None)) .into(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantHeapData::default()).into(), 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 279a5c1a1..7c4737e15 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -134,6 +134,8 @@ pub enum ProtoIntrinsics { RegExpStringIterator, Symbol, SyntaxError, + #[cfg(feature = "temporal")] + TemporalInstant, TypeError, #[cfg(feature = "array-buffer")] Uint16Array, @@ -332,6 +334,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(), @@ -421,6 +424,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 17166897d..1811fac8a 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -22,7 +22,7 @@ use std::ops::Deref; #[cfg(feature = "date")] use crate::ecmascript::DateHeapData; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::InstantHeapData; +use crate::ecmascript::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBuffer, ArrayBufferHeapData, DataView, DataViewRecord, DetachKey, TypedArrayRecord, From 4323b26bc7d4cde88b54a019dae8ecfebe6264d2 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:24:03 +0300 Subject: [PATCH 05/55] fix errors after rebase --- nova_vm/src/ecmascript/builtins/ordinary.rs | 5 +- .../ecmascript/builtins/temporal/instant.rs | 63 +++++++++++-------- nova_vm/src/ecmascript/execution/weak_key.rs | 2 + .../src/ecmascript/types/language/object.rs | 5 +- .../src/ecmascript/types/language/value.rs | 9 ++- nova_vm/src/engine/rootable.rs | 4 ++ 6 files changed, 57 insertions(+), 31 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 22f27faf0..38509cdac 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -11,12 +11,13 @@ pub use shape::*; use std::{ collections::{TryReserveError, hash_map::Entry}, ops::ControlFlow, - time::Instant, vec, }; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::SharedDataViewRecord; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::try_get_result_into_value; use crate::{ @@ -2087,6 +2088,8 @@ fn get_intrinsic_constructor<'a>( ProtoIntrinsics::WeakRef => Some(intrinsics.weak_ref().into()), #[cfg(feature = "weak-refs")] ProtoIntrinsics::WeakSet => Some(intrinsics.weak_set().into()), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into()), } } 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 1741a3202..27534e135 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -20,6 +20,8 @@ use crate::ecmascript::{ }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{INSTANT_DISCRIMINANT, Instant}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index e20bc3d9f..e1986596f 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -74,7 +74,7 @@ use crate::{ Agent, ArgumentsList, Array, ArrayIterator, AsyncGenerator, BoundFunction, BuiltinConstructorFunction, BuiltinFunction, BuiltinPromiseFinallyFunction, BuiltinPromiseResolvingFunction, ECMAScriptFunction, EmbedderObject, Error, - FinalizationRegistry, Generator, JsResult, Map, MapIterator, Module, ObjectShape, + FinalizationRegistry, Generator, Instant, JsResult, Map, MapIterator, Module, ObjectShape, ObjectShapeRecord, PrimitiveObject, Promise, PropertyDescriptor, PropertyLookupCache, PropertyOffset, ProtoIntrinsics, Proxy, StringIterator, TryResult, ordinary_object_create_with_intrinsics, @@ -119,6 +119,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, @@ -1204,6 +1205,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 12aa52f2c..97ebddaf9 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -6,6 +6,8 @@ use crate::ecmascript::Date; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::Float16Array; +#[cfg(feature = "temporal")] +use crate::ecmascript::Instant; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] use crate::ecmascript::SharedFloat16Array; #[cfg(feature = "array-buffer")] @@ -1233,6 +1235,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), @@ -1253,7 +1257,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")] @@ -1282,7 +1285,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")] @@ -1311,7 +1313,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), @@ -1350,6 +1351,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 6d6cacaca..ef7d26394 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -26,6 +26,8 @@ use crate::ecmascript::{ }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{INSTANT_DISCRIMINANT, Instant}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -81,6 +83,8 @@ pub(crate) mod private { #[cfg(feature = "date")] use crate::ecmascript::Date; + #[cfg(feature = "temporal")] + use crate::ecmascript::Instant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ AnyArrayBuffer, AnyDataView, AnyTypedArray, ArrayBuffer, DataView, GenericTypedArray, From a1839e69b3ccd8310e18fd33007a0ebd97d2f76f Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:34:51 +0300 Subject: [PATCH 06/55] cleanup --- nova_vm/src/ecmascript/builtins/ordinary.rs | 6 +-- .../ecmascript/builtins/temporal/instant.rs | 45 +++++++++---------- .../builtins/temporal/instant/data.rs | 37 ++++++++++----- nova_vm/src/heap.rs | 4 +- 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 38509cdac..c80ae6c65 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -14,10 +14,10 @@ use std::{ vec, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::InstantRecord; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::SharedDataViewRecord; -#[cfg(feature = "temporal")] -use crate::ecmascript::builtins::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::try_get_result_into_value; use crate::{ @@ -1654,7 +1654,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None)) .into(), #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantHeapData::default()).into(), + ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantRecord::default()).into(), 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 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 1811fac8a..1859f6392 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -22,7 +22,7 @@ use std::ops::Deref; #[cfg(feature = "date")] use crate::ecmascript::DateHeapData; #[cfg(feature = "temporal")] -use crate::ecmascript::InstantHeapData; +use crate::ecmascript::InstantRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBuffer, ArrayBufferHeapData, DataView, DataViewRecord, DetachKey, TypedArrayRecord, @@ -78,7 +78,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 453176ed7a639a5b93102c98853f20dfff2301d6 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:39:07 +0300 Subject: [PATCH 07/55] 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 c90b0a7f6..3578bcf13 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -572,6 +572,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 3012401759be40720227958586fd438812ad94f1 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 12:15:46 +0300 Subject: [PATCH 08/55] 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 2d105b0cf..abcfd670c 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -214,6 +214,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 f3ab3dc67b9c26c82f3d6c5034fa67a40333ee7b Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sun, 19 Oct 2025 13:07:16 +0200 Subject: [PATCH 09/55] add prototype methods --- nova_vm/src/builtin_strings | 3 + .../ecmascript/builtins/temporal/instant.rs | 197 +++++++++++++++--- .../ecmascript/execution/realm/intrinsics.rs | 8 +- nova_vm/src/ecmascript/execution/weak_key.rs | 4 +- .../src/ecmascript/types/language/object.rs | 4 +- .../src/ecmascript/types/language/value.rs | 7 +- nova_vm/src/engine/rootable.rs | 8 +- nova_vm/src/heap/heap_bits.rs | 12 +- nova_vm/src/heap/heap_gc.rs | 4 +- 9 files changed, 195 insertions(+), 52 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index cb3aae3e0..86e29c3e2 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 7c4737e15..fc897da3c 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -11,10 +11,6 @@ use crate::ecmascript::AtomicsObject; use crate::ecmascript::JSONObject; #[cfg(feature = "math")] use crate::ecmascript::MathObject; -#[cfg(feature = "temporal")] -use crate::ecmascript::builtins::{ - TemporalObject, instant::InstantConstructor, instant::InstantPrototype, -}; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBufferConstructor, ArrayBufferPrototype, DataViewConstructor, DataViewPrototype, @@ -22,6 +18,8 @@ use crate::ecmascript::{ }; #[cfg(feature = "date")] use crate::ecmascript::{DateConstructor, DatePrototype}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{InstantConstructor, TemporalInstantPrototype, TemporalObject}; #[cfg(feature = "regexp")] use crate::ecmascript::{RegExpConstructor, RegExpPrototype, RegExpStringIteratorPrototype}; #[cfg(feature = "set")] @@ -222,7 +220,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 27534e135..a463ef27b 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -21,7 +21,7 @@ use crate::ecmascript::{ #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "temporal")] -use crate::ecmascript::{INSTANT_DISCRIMINANT, Instant}; +use crate::ecmascript::{INSTANT_DISCRIMINANT, TemporalInstant}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -97,7 +97,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 e1986596f..b13e87a92 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -76,7 +76,7 @@ use crate::{ BuiltinPromiseResolvingFunction, ECMAScriptFunction, EmbedderObject, Error, FinalizationRegistry, Generator, Instant, JsResult, Map, MapIterator, Module, ObjectShape, ObjectShapeRecord, PrimitiveObject, Promise, PropertyDescriptor, PropertyLookupCache, - PropertyOffset, ProtoIntrinsics, Proxy, StringIterator, TryResult, + PropertyOffset, ProtoIntrinsics, Proxy, StringIterator, TemporalInstant, TryResult, ordinary_object_create_with_intrinsics, }, engine::{Bindable, GcScope, HeapRootData, NoGcScope, bindable_handle}, @@ -120,7 +120,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 97ebddaf9..1dc5001de 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -10,6 +10,8 @@ use crate::ecmascript::Float16Array; use crate::ecmascript::Instant; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] use crate::ecmascript::SharedFloat16Array; +#[cfg(feature = "temporal")] +use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBuffer, BigInt64Array, BigUint64Array, DataView, Float32Array, Float64Array, Int8Array, @@ -140,7 +142,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>), @@ -278,7 +280,8 @@ pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array: pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_DEF)); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_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 BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_DEF)); pub(crate) const ECMASCRIPT_FUNCTION_DISCRIMINANT: u8 = diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index ef7d26394..cb5c062fb 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -28,6 +28,8 @@ use crate::ecmascript::{ use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "temporal")] use crate::ecmascript::{INSTANT_DISCRIMINANT, Instant}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{INSTANT_DISCRIMINANT, TemporalInstant}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -84,7 +86,7 @@ pub(crate) mod private { #[cfg(feature = "date")] use crate::ecmascript::Date; #[cfg(feature = "temporal")] - use crate::ecmascript::Instant; + use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ AnyArrayBuffer, AnyDataView, AnyTypedArray, ArrayBuffer, DataView, GenericTypedArray, @@ -132,7 +134,7 @@ pub(crate) 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<'_> {} @@ -445,7 +447,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 682b02f78..54bd56577 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -22,7 +22,7 @@ use soavec_derive::SoAble; #[cfg(feature = "date")] use crate::ecmascript::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::Instant; +use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ArrayBuffer, DataView, VoidArray}; #[cfg(feature = "regexp")] @@ -417,6 +417,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, @@ -493,8 +495,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>, @@ -644,6 +646,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 = @@ -751,6 +754,8 @@ impl HeapBits { data_views, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, declarative_environments, e_2_1, e_2_2, @@ -861,6 +866,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 b146e7650..e822b2f2e 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -7,7 +7,7 @@ use std::thread; #[cfg(feature = "date")] use crate::ecmascript::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::Instant; +use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ArrayBuffer, DataView, VoidArray}; #[cfg(feature = "regexp")] @@ -507,7 +507,7 @@ pub(crate) fn heap_gc(agent: &mut Agent, root_realms: &mut [Option = 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 82ca41906660caf7b5270d1cae6f7bc78aa223ab Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 14:26:20 +0300 Subject: [PATCH 10/55] get it working --- nova_vm/src/ecmascript/builtins/temporal.rs | 14 +++++- .../ecmascript/builtins/temporal/instant.rs | 47 ++++++++++--------- .../ecmascript/execution/realm/intrinsics.rs | 6 +-- nova_vm/src/heap/heap_constants.rs | 2 - 4 files changed, 39 insertions(+), 30 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 fc897da3c..7d6f0b1c1 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -18,14 +18,14 @@ use crate::ecmascript::{ }; #[cfg(feature = "date")] use crate::ecmascript::{DateConstructor, DatePrototype}; -#[cfg(feature = "temporal")] -use crate::ecmascript::{InstantConstructor, TemporalInstantPrototype, TemporalObject}; #[cfg(feature = "regexp")] use crate::ecmascript::{RegExpConstructor, RegExpPrototype, RegExpStringIteratorPrototype}; #[cfg(feature = "set")] use crate::ecmascript::{SetConstructor, SetIteratorPrototype, SetPrototype}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::{SharedArrayBufferConstructor, SharedArrayBufferPrototype}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{TemporalInstantConstructor, TemporalInstantPrototype, TemporalObject}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{ WeakMapConstructor, WeakMapPrototype, WeakSetConstructor, WeakSetPrototype, @@ -219,7 +219,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 036e31fac..329220aaa 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -36,8 +36,6 @@ pub(crate) enum IntrinsicObjectIndexes { #[cfg(feature = "temporal")] TemporalObject, #[cfg(feature = "temporal")] - TemporalInstant, - #[cfg(feature = "temporal")] TemporalInstantPrototype, // Text processing #[cfg(feature = "regexp")] From 8ba37bb5e3843bb202e662d48ba09e4b9d368616 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 14:26:35 +0300 Subject: [PATCH 11/55] 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 e1a417aea5c3fb66d3692bc3dcf1a14413b18d0e Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 03:26:47 +0200 Subject: [PATCH 12/55] 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 8f09370de8f679dc6c2d53c4da3fdddfbdf2b930 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 15:04:51 +0200 Subject: [PATCH 13/55] 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 dcc24d729e290568f39a5cb56dd99b9c4bffb832 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 16:05:00 +0200 Subject: [PATCH 14/55] 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 8115549020e5f2fd212adcb1e534319c7ce3d6d3 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sat, 25 Oct 2025 14:50:36 +0200 Subject: [PATCH 15/55] implementation of Temporal.Instant.compare --- 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 +- 4 files changed, 67 insertions(+), 57 deletions(-) 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 7d6f0b1c1..17d7380a7 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -934,15 +934,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 8b7700b4bf8e6f93c92da8337a0fbd3251af66d1 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sat, 25 Oct 2025 19:25:35 +0200 Subject: [PATCH 16/55] 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 5596f325065cbb8e79a693d9e9e1808cef8aa4f8 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 11:26:47 +0100 Subject: [PATCH 17/55] get_epoch_milliseconds and get_epoch_nanoseconds --- nova_vm/src/builtin_strings | 2 + .../ecmascript/builtins/temporal/instant.rs | 98 ++++++- .../temporal/instant/instant_constructor.rs | 241 ++++++++++++++++++ 3 files changed, 330 insertions(+), 11 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 86e29c3e2..d3ebcc5d2 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/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 100790eda3a856606db871e77eae8e5998d41423 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 12:43:13 +0100 Subject: [PATCH 18/55] add BigInt::from_i128, and add skeleton methods for Seb --- nova_vm/src/builtin_strings | 5 +- .../ecmascript/builtins/temporal/instant.rs | 72 ++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index d3ebcc5d2..ae58f49ac 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 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!() } } From 2611dd2456e7ecce9cc30c425789943a55fab820 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 15:04:23 +0100 Subject: [PATCH 19/55] 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 | 11 +- 5 files changed, 257 insertions(+), 470 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 17d7380a7..30007f331 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -216,12 +216,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 6561cef295bc681af8f4e446943e81b4b01018b4 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:05:27 +0100 Subject: [PATCH 20/55] 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 ae58f49ac..e0dfebbd2 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 e638530f3e5229cd7f737540e6eda02849369872 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:21:45 +0100 Subject: [PATCH 21/55] 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 2d6f399d6a809556ab5cb338d39e34e99b6e8764 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:23:23 +0100 Subject: [PATCH 22/55] 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 93f00be88206b9d662917e91411b52b34da1cc3f Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:26:15 +0100 Subject: [PATCH 23/55] 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 973e27148ef398375303329f3d92c044a2673c19 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:59:09 +0100 Subject: [PATCH 24/55] 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 cad0f5d346af88cc6f40aac4f4e7cf69f47711f8 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 17:42:59 +0100 Subject: [PATCH 25/55] 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 dd306af497a853812379a689036cb26dc402eb8b Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Wed, 5 Nov 2025 17:07:25 +0100 Subject: [PATCH 26/55] 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 0206ab5fbe36f75c5fd3e311c8068b9e754b69cd Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Sun, 9 Nov 2025 23:31:11 +0100 Subject: [PATCH 27/55] 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 +++- 5 files changed, 392 insertions(+), 76 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index e0dfebbd2..479db551a 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 ea059bcbc..efc39ae33 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -1553,6 +1553,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) From eeebd855653a75fe1fe2205c8895acecca596419 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 11 Nov 2025 13:40:04 +0100 Subject: [PATCH 28/55] 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 +- 2 files changed, 8 insertions(+), 12 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 From b37b1fe8c3fc9e08500d5b4a5c7945fd7ed907a4 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 11 Nov 2025 13:41:31 +0100 Subject: [PATCH 29/55] 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 350834a924b7b14705d1c60a1f859bd78a6dd0bf Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 12 Nov 2025 04:59:43 +0100 Subject: [PATCH 30/55] 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 479db551a..21a4027c2 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 bdafc7c9be10c5eb7e5f5cce488a88311542b66b Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 12 Nov 2025 05:12:24 +0100 Subject: [PATCH 31/55] 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 f1400941925204fd36777773e13d76ab988021db Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Mon, 17 Nov 2025 19:43:36 +0100 Subject: [PATCH 32/55] 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 | 4 + 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 | 30 ++- nova_vm/src/ecmascript/execution/weak_key.rs | 18 +- .../src/ecmascript/types/language/object.rs | 12 ++ .../src/ecmascript/types/language/value.rs | 25 ++- nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 14 +- nova_vm/src/heap.rs | 11 +- nova_vm/src/heap/heap_bits.rs | 21 ++ nova_vm/src/heap/heap_constants.rs | 7 +- nova_vm/src/heap/heap_gc.rs | 32 ++- 19 files changed, 590 insertions(+), 175 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 21a4027c2..1540d5f30 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 c80ae6c65..78284e259 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -1655,6 +1655,8 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .into(), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantRecord::default()).into(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalPlainTime => todo!(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) @@ -2090,6 +2092,8 @@ fn get_intrinsic_constructor<'a>( ProtoIntrinsics::WeakSet => Some(intrinsics.weak_set().into()), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into()), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalPlainTime => Some(intrinsics.temporal_plain_time().into()), } } 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 30007f331..916b3d424 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -134,6 +134,8 @@ pub enum ProtoIntrinsics { SyntaxError, #[cfg(feature = "temporal")] TemporalInstant, + #[cfg(feature = "temporal")] + TemporalPlainTime, TypeError, #[cfg(feature = "array-buffer")] Uint16Array, @@ -216,11 +218,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); @@ -331,7 +333,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(), @@ -421,7 +426,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(), @@ -930,7 +938,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% @@ -944,6 +952,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 a463ef27b..bb800ce4c 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -21,7 +21,9 @@ use crate::ecmascript::{ #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "temporal")] -use crate::ecmascript::{INSTANT_DISCRIMINANT, TemporalInstant}; +use crate::ecmascript::{ + INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalInstant, TemporalPlainTime, +}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -98,6 +100,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, @@ -214,6 +218,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), @@ -323,6 +329,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), @@ -436,6 +444,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)), @@ -580,6 +590,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), @@ -687,6 +699,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), @@ -810,6 +824,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/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index b13e87a92..a81d0736b 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -44,6 +44,8 @@ use crate::ecmascript::{ use crate::ecmascript::{DATE_DISCRIMINANT, Date}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{PLAIN_TIME_DISCRIMINANT, TemporalPlainTime}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -121,6 +123,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, @@ -649,6 +653,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), @@ -775,6 +781,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),+), @@ -1207,6 +1215,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) @@ -1544,6 +1554,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 1dc5001de..5b64f0a64 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -10,8 +10,6 @@ use crate::ecmascript::Float16Array; use crate::ecmascript::Instant; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] use crate::ecmascript::SharedFloat16Array; -#[cfg(feature = "temporal")] -use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBuffer, BigInt64Array, BigUint64Array, DataView, Float32Array, Float64Array, Int8Array, @@ -27,6 +25,8 @@ use crate::ecmascript::{ SharedFloat32Array, SharedFloat64Array, SharedInt8Array, SharedInt16Array, SharedInt32Array, SharedUint8Array, SharedUint8ClampedArray, SharedUint16Array, SharedUint32Array, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{TemporalInstant, TemporalPlainTime}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakMap, WeakRef, WeakSet}; use crate::{ @@ -143,6 +143,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>), @@ -278,10 +280,13 @@ pub(crate) const OBJECT_DISCRIMINANT: u8 = value_discriminant(Value::Object(Ordi 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)); -pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_DEF)); #[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)); pub(crate) const ECMASCRIPT_FUNCTION_DISCRIMINANT: u8 = @@ -927,6 +932,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()), @@ -1090,6 +1097,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)) @@ -1239,7 +1248,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), @@ -1355,7 +1366,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), @@ -1509,6 +1522,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 52af4fa92..6a992d04a 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1300,6 +1300,8 @@ pub(crate) fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> Strin 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 cb5c062fb..1fcd4d2db 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -27,9 +27,9 @@ use crate::ecmascript::{ #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "temporal")] -use crate::ecmascript::{INSTANT_DISCRIMINANT, Instant}; -#[cfg(feature = "temporal")] -use crate::ecmascript::{INSTANT_DISCRIMINANT, TemporalInstant}; +use crate::ecmascript::{ + INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalInstant, TemporalPlainTime, +}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -448,6 +448,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, @@ -628,6 +630,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) @@ -778,7 +782,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 1859f6392..3e86ac39f 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -21,13 +21,13 @@ use std::ops::Deref; #[cfg(feature = "date")] use crate::ecmascript::DateHeapData; -#[cfg(feature = "temporal")] -use crate::ecmascript::InstantRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ ArrayBuffer, ArrayBufferHeapData, DataView, DataViewRecord, DetachKey, TypedArrayRecord, VoidArray, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{InstantRecord, PlainTimeHeapData}; #[cfg(feature = "regexp")] use crate::ecmascript::{RegExpHeapData, RegExpStringIteratorRecord}; #[cfg(feature = "set")] @@ -79,6 +79,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 @@ -226,8 +228,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 54bd56577..a90f4f0f2 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -419,6 +419,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, @@ -497,6 +499,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>, @@ -646,7 +649,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 = @@ -756,6 +762,8 @@ impl HeapBits { dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, declarative_environments, e_2_1, e_2_2, @@ -866,7 +874,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 @@ -1006,6 +1017,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), @@ -1114,6 +1127,8 @@ impl<'a> WorkQueues<'a> { dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, declarative_environments, e_2_1, e_2_2, @@ -1193,6 +1208,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"))] @@ -1234,6 +1250,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() @@ -1595,6 +1612,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, @@ -1752,6 +1771,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 329220aaa..0ba3ee443 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -34,9 +34,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, @@ -175,6 +178,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 e822b2f2e..e81bcc189 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -6,8 +6,6 @@ use std::thread; #[cfg(feature = "date")] use crate::ecmascript::Date; -#[cfg(feature = "temporal")] -use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ArrayBuffer, DataView, VoidArray}; #[cfg(feature = "regexp")] @@ -16,6 +14,8 @@ use crate::ecmascript::{RegExp, RegExpStringIterator}; use crate::ecmascript::{Set, SetIterator}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::{SharedArrayBuffer, SharedDataView, SharedVoidArray}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{TemporalInstant, TemporalPlainTime}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakMap, WeakRef, WeakSet}; #[cfg(feature = "array-buffer")] @@ -85,6 +85,8 @@ pub(crate) fn heap_gc(agent: &mut Agent, root_realms: &mut [Option = + 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() { @@ -1229,6 +1237,8 @@ fn sweep( dates, #[cfg(feature = "temporal")] instants, + #[cfg(feature = "temporal")] + plain_times, ecmascript_functions, elements, embedder_objects, @@ -1688,6 +1698,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 00c7daeae9e43e19ecea30cadb5d8842151c5da9 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Mon, 17 Nov 2025 20:28:58 +0100 Subject: [PATCH 33/55] 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, 47 insertions(+), 11 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 78284e259..f80130f59 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -14,12 +14,12 @@ use std::{ vec, }; -#[cfg(feature = "temporal")] -use crate::ecmascript::InstantRecord; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::SharedDataViewRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::try_get_result_into_value; +#[cfg(feature = "temporal")] +use crate::ecmascript::{InstantRecord, PlainTimeHeapData}; use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods, @@ -1656,7 +1656,10 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantRecord::default()).into(), #[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 1fcd4d2db..6efcbf0be 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -85,8 +85,6 @@ pub(crate) mod private { #[cfg(feature = "date")] use crate::ecmascript::Date; - #[cfg(feature = "temporal")] - use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ AnyArrayBuffer, AnyDataView, AnyTypedArray, ArrayBuffer, DataView, GenericTypedArray, @@ -100,6 +98,8 @@ pub(crate) mod private { use crate::ecmascript::{RegExp, RegExpStringIterator}; #[cfg(feature = "set")] use crate::ecmascript::{Set, SetIterator}; + #[cfg(feature = "temporal")] + use crate::ecmascript::{TemporalInstant, TemporalPlainTime}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakKey, WeakMap, WeakRef, WeakSet}; use crate::{ @@ -135,6 +135,8 @@ pub(crate) 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 fd03c63bd639f626dce7abfced20df6108e08114 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Mon, 17 Nov 2025 20:56:10 +0100 Subject: [PATCH 34/55] 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 1540d5f30..0f172ac32 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 7db97c4056523a3e44038ae42ade78837763fa88 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 19 Nov 2025 01:37:17 +0100 Subject: [PATCH 35/55] 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 0f172ac32..dceecf826 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 93ed84255ac56e2d462db30c466bd04041adb615 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 19 Nov 2025 23:32:56 +0100 Subject: [PATCH 36/55] 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 764ea2e39a93192071399550cfbb1edd5facda9c Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 19 Nov 2025 23:35:27 +0100 Subject: [PATCH 37/55] 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 364373ecc6078cd97543cb013677598e43d26cf1 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 20 Nov 2025 17:14:12 +0100 Subject: [PATCH 38/55] added proper duration backing --- nova_vm/src/builtin_strings | 2 + nova_vm/src/ecmascript/builtins/ordinary.rs | 8 +- 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 | 21 ++- .../src/ecmascript/types/language/object.rs | 16 ++- .../src/ecmascript/types/language/value.rs | 19 ++- nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 8 ++ nova_vm/src/heap.rs | 6 +- nova_vm/src/heap/heap_bits.rs | 19 +++ nova_vm/src/heap/heap_constants.rs | 4 + nova_vm/src/heap/heap_gc.rs | 19 +++ 17 files changed, 327 insertions(+), 46 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 dceecf826..6097978b2 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 f80130f59..dd1b1b133 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -19,7 +19,7 @@ use crate::ecmascript::SharedDataViewRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::try_get_result_into_value; #[cfg(feature = "temporal")] -use crate::ecmascript::{InstantRecord, PlainTimeHeapData}; +use crate::ecmascript::{DurationHeapData, InstantRecord, PlainTimeHeapData}; use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods, @@ -1656,6 +1656,10 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantRecord::default()).into(), #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalDuration => { + agent.heap.create(DurationHeapData::default()).into_object() + } + #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalPlainTime => agent .heap .create(PlainTimeHeapData::default()) @@ -2097,6 +2101,8 @@ fn get_intrinsic_constructor<'a>( ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into()), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalPlainTime => Some(intrinsics.temporal_plain_time().into()), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalDuration => Some(intrinsics.temporal_duration().into()), } } 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 916b3d424..c6c168ca2 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -25,7 +25,10 @@ use crate::ecmascript::{SetConstructor, SetIteratorPrototype, SetPrototype}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::{SharedArrayBufferConstructor, SharedArrayBufferPrototype}; #[cfg(feature = "temporal")] -use crate::ecmascript::{TemporalInstantConstructor, TemporalInstantPrototype, TemporalObject}; +use crate::ecmascript::{ + TemporalDurationConstructor, TemporalDurationPrototype, TemporalInstantConstructor, + TemporalInstantPrototype, TemporalObject, +}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{ WeakMapConstructor, WeakMapPrototype, WeakSetConstructor, WeakSetPrototype, @@ -135,6 +138,8 @@ pub enum ProtoIntrinsics { #[cfg(feature = "temporal")] TemporalInstant, #[cfg(feature = "temporal")] + TemporalDuration, + #[cfg(feature = "temporal")] TemporalPlainTime, TypeError, #[cfg(feature = "array-buffer")] @@ -223,7 +228,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")] @@ -336,6 +344,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(), @@ -429,6 +439,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(), @@ -941,6 +953,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 bb800ce4c..b88950594 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -18,12 +18,13 @@ use crate::ecmascript::{ UINT_16_ARRAY_DISCRIMINANT, UINT_32_ARRAY_DISCRIMINANT, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array, }; -#[cfg(feature = "proposal-float16array")] -use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "temporal")] use crate::ecmascript::{ - INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalInstant, TemporalPlainTime, + DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalDuration, + TemporalInstant, TemporalPlainTime, }; +#[cfg(feature = "proposal-float16array")] +use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -101,6 +102,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, @@ -219,6 +222,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), @@ -330,6 +335,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), @@ -445,6 +452,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)), @@ -591,6 +600,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), @@ -700,6 +711,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), @@ -825,6 +838,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 a81d0736b..a3d8d94b4 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -42,10 +42,12 @@ use crate::ecmascript::{ }; #[cfg(feature = "date")] use crate::ecmascript::{DATE_DISCRIMINANT, Date}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{ + DURATION_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalDuration, TemporalPlainTime, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; -#[cfg(feature = "temporal")] -use crate::ecmascript::{PLAIN_TIME_DISCRIMINANT, TemporalPlainTime}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -124,6 +126,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, @@ -654,6 +658,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), @@ -782,6 +788,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),+), @@ -1216,6 +1224,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 @@ -1555,6 +1565,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 5b64f0a64..807314376 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -6,8 +6,6 @@ use crate::ecmascript::Date; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::Float16Array; -#[cfg(feature = "temporal")] -use crate::ecmascript::Instant; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] use crate::ecmascript::SharedFloat16Array; #[cfg(feature = "array-buffer")] @@ -26,7 +24,7 @@ use crate::ecmascript::{ SharedUint8Array, SharedUint8ClampedArray, SharedUint16Array, SharedUint32Array, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{TemporalInstant, TemporalPlainTime}; +use crate::ecmascript::{TemporalDuration, TemporalInstant, TemporalPlainTime}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakMap, WeakRef, WeakSet}; use crate::{ @@ -144,6 +142,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>), @@ -284,6 +284,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)); @@ -933,6 +936,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( @@ -1098,6 +1103,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) => { @@ -1250,6 +1257,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), @@ -1368,6 +1377,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), @@ -1523,6 +1534,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 6a992d04a..8ecdec956 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1301,6 +1301,8 @@ pub(crate) fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> Strin #[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 6efcbf0be..fbaa82bbf 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -136,6 +136,8 @@ pub(crate) 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<'_> {} @@ -451,6 +453,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, @@ -633,6 +637,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) => { @@ -786,6 +792,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 3e86ac39f..83d0bf40e 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -27,7 +27,7 @@ use crate::ecmascript::{ VoidArray, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{InstantRecord, PlainTimeHeapData}; +use crate::ecmascript::{DurationHeapData, InstantRecord, PlainTimeHeapData}; #[cfg(feature = "regexp")] use crate::ecmascript::{RegExpHeapData, RegExpStringIteratorRecord}; #[cfg(feature = "set")] @@ -80,6 +80,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; @@ -232,6 +234,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 a90f4f0f2..5cd27c79d 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -421,6 +421,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, @@ -499,7 +501,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>, @@ -653,6 +658,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 = @@ -764,6 +770,8 @@ impl HeapBits { instants, #[cfg(feature = "temporal")] plain_times, + #[cfg(feature = "temporal")] + durations, declarative_environments, e_2_1, e_2_2, @@ -878,6 +886,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 @@ -1018,6 +1028,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), @@ -1128,6 +1140,8 @@ impl<'a> WorkQueues<'a> { #[cfg(feature = "temporal")] instants, #[cfg(feature = "temporal")] + durations, + #[cfg(feature = "temporal")] plain_times, declarative_environments, e_2_1, @@ -1250,6 +1264,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() @@ -1614,6 +1629,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, @@ -1773,6 +1790,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 0ba3ee443..536003343 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -38,6 +38,8 @@ pub(crate) enum IntrinsicObjectIndexes { #[cfg(feature = "temporal")] TemporalInstantPrototype, #[cfg(feature = "temporal")] + TemporalDurationPrototype, + #[cfg(feature = "temporal")] TemporalPlainTimePrototype, // Text processing @@ -179,6 +181,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 e81bcc189..15b748163 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -86,6 +86,8 @@ pub(crate) fn heap_gc(agent: &mut Agent, root_realms: &mut [Option = 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(); @@ -1238,6 +1249,8 @@ fn sweep( #[cfg(feature = "temporal")] instants, #[cfg(feature = "temporal")] + durations, + #[cfg(feature = "temporal")] plain_times, ecmascript_functions, elements, @@ -1699,6 +1712,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 8998d1a282f684c1fbb79dedf187678e4fd17482 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 20 Nov 2025 14:38:27 +0100 Subject: [PATCH 39/55] 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 6097978b2..3beebfe82 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 dbf3cdd3ff71a71e1c528cd98b21bee2d5d08d6c Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 20 Nov 2025 17:35:52 +0100 Subject: [PATCH 40/55] 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 62899a355961fd54c6b7f71227fea7229f916257 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 27 Nov 2025 12:04:40 +0200 Subject: [PATCH 41/55] 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 9054c0c539b7898f55fdfa162b6106d69f23b5e4 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 27 Nov 2025 13:24:22 +0200 Subject: [PATCH 42/55] 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 3beebfe82..79d4f1ac9 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()?; From 3e386346e27b990c10db9453e823c0450fb69a91 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 27 Nov 2025 14:23:44 +0100 Subject: [PATCH 43/55] plain_time constructor and prototype structs and stubs --- nova_vm/src/builtin_strings | 1 + .../ecmascript/builtins/temporal/duration.rs | 4 +- .../builtins/temporal/plain_time.rs | 2 + .../plain_time/plain_time_constructor.rs | 46 +++++++++++++++++++ .../plain_time/plain_time_prototype.rs | 33 +++++++++++++ .../ecmascript/execution/realm/intrinsics.rs | 4 ++ 6 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs create mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 79d4f1ac9..14fbf413c 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -455,6 +455,7 @@ SyntaxError #[cfg(feature = "temporal")]Temporal #[cfg(feature = "temporal")]Temporal.Duration #[cfg(feature = "temporal")]Temporal.Instant +#[cfg(feature = "temporal")]Temporal.PlainTime #[cfg(feature = "math")]tan #[cfg(feature = "math")]tanh #[cfg(feature = "regexp")]test diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index aa18290d8..7bbd45e37 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -166,7 +166,7 @@ impl<'a> CreateHeapData, TemporalDuration<'a>> for Heap { /// It creates a Temporal.Duration instance and fills /// the internal slots with valid values. /// It performs the following steps when called: -pub(crate) fn create_temporal_duration<'gc>(// years, +pub(crate) fn _create_temporal_duration<'gc>(// years, // months, // weeks, // days, @@ -449,7 +449,7 @@ pub(crate) fn to_temporal_partial_duration_record<'gc>( /// 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>( +pub(crate) fn _create_negated_temporal_duration<'gc>( _agent: &mut Agent, _item: temporal_rs::Duration, mut _gc: GcScope<'gc, '_>, diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index f451d8e65..2feced6ca 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -17,6 +17,8 @@ use crate::{ }; pub(crate) mod data; +pub mod plain_time_constructor; +pub mod plain_time_prototype; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs new file mode 100644 index 000000000..d3dcfff1a --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs @@ -0,0 +1,46 @@ +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 TemporalPlainTimeConstructor; + +impl Builtin for TemporalPlainTimeConstructor { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalPlainTimeConstructor::constructor); +} + +impl BuiltinIntrinsicConstructor for TemporalPlainTimeConstructor { + const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalPlainTime; +} + +impl TemporalPlainTimeConstructor { + 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 plain_time_prototype = intrinsics.temporal_plain_time_prototype(); + + BuiltinFunctionBuilder::new_intrinsic_constructor::( + agent, realm, + ) + .with_property_capacity(1) + .with_prototype_property(plain_time_prototype.into_object()) + .build(); + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs new file mode 100644 index 000000000..d457819d8 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_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 TemporalPlainTimePrototype; +impl TemporalPlainTimePrototype { + 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_plain_time_prototype(); + let object_prototype = intrinsics.object_prototype(); + let plain_time_constructor = intrinsics.temporal_plain_time(); + + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(2) + .with_prototype(object_prototype) + .with_constructor_property(plain_time_constructor) + .with_property(|builder| { + builder + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal_PlainTime.into()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) + .build(); + } +} diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index c6c168ca2..1ef5cff02 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -232,6 +232,10 @@ impl Intrinsics { TemporalDurationPrototype::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] TemporalDurationConstructor::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalPlainTimePrototype::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalPlainTimeConstructor::create_intrinsic(agent, realm, gc); #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); #[cfg(feature = "date")] From c74f2d1e2aae9a650072a6ee764d748ec66fe44a Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 2 Dec 2025 15:11:56 +0100 Subject: [PATCH 44/55] to_local_string, to_json --- .../temporal/instant/instant_prototype.rs | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 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 9d0289e08..2982d0063 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -516,23 +516,53 @@ impl TemporalInstantPrototype { } /// ### [8.3.12 Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring) + /// An ECMAScript implementation that includes the ECMA-402 Internationalization API + /// must implement this method as specified in the ECMA-402 specification. + /// If an ECMAScript implementation does not include the ECMA-402 API the following specification of this method is used. + /// + /// The meanings of the optional parameters to this method are defined in the ECMA-402 specification; + /// implementations that do not include ECMA-402 support must not use those parameter positions for anything else. fn to_locale_string<'gc>( - _agent: &mut Agent, - _this_value: Value, + agent: &mut Agent, + this_value: Value, _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + // 1. Let instant be the this value. + let value = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, value, gc.nogc()).bind(gc.nogc()); + // 3. Return TemporalInstantToString(instant, undefined, AUTO). + let options: ToStringRoundingOptions = ToStringRoundingOptions::default(); // defaults Precision to Auto + match instant.unbind()? + .inner_instant(agent) + .to_ixdtf_string(Some(TimeZone::utc()), 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.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, + agent: &mut Agent, + this_value: Value, _args: ArgumentsList, - mut _gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + // 1. Let instant be the this value. + let value = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()).bind(gc.nogc()); + // 3. Return TemporalInstantToString(instant, undefined, AUTO). + let options: ToStringRoundingOptions = ToStringRoundingOptions::default(); + match instant.unbind()? + .inner_instant(agent) + .to_ixdtf_string(Some(TimeZone::utc()), 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.14 Temporal.Instant.prototype.valueOf ( )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof) @@ -560,11 +590,18 @@ impl TemporalInstantPrototype { // [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, '_>, + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + // 1. Let instant be the this value. + let value = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). + let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()).bind(gc.nogc()); + // 3. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone). + let time_zone = Some(TimeZone::utc()); + // 4. Return ! CreateTemporalZonedDateTime(instant.[[EpochNanoseconds]], timeZone, "iso8601"). unimplemented!() } From a888e94c96a0b75829115e8fc2515de0baea352c Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 2 Dec 2025 17:01:00 +0100 Subject: [PATCH 45/55] cargo fmt --- .../temporal/instant/instant_prototype.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 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 2982d0063..5eec06491 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -516,11 +516,11 @@ impl TemporalInstantPrototype { } /// ### [8.3.12 Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring) - /// An ECMAScript implementation that includes the ECMA-402 Internationalization API - /// must implement this method as specified in the ECMA-402 specification. + /// An ECMAScript implementation that includes the ECMA-402 Internationalization API + /// must implement this method as specified in the ECMA-402 specification. /// If an ECMAScript implementation does not include the ECMA-402 API the following specification of this method is used. /// - /// The meanings of the optional parameters to this method are defined in the ECMA-402 specification; + /// The meanings of the optional parameters to this method are defined in the ECMA-402 specification; /// implementations that do not include ECMA-402 support must not use those parameter positions for anything else. fn to_locale_string<'gc>( agent: &mut Agent, @@ -531,10 +531,12 @@ impl TemporalInstantPrototype { // 1. Let instant be the this value. let value = this_value.bind(gc.nogc()); // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, value, gc.nogc()).bind(gc.nogc()); + let instant = + require_internal_slot_temporal_instant(agent, value, gc.nogc()).bind(gc.nogc()); // 3. Return TemporalInstantToString(instant, undefined, AUTO). let options: ToStringRoundingOptions = ToStringRoundingOptions::default(); // defaults Precision to Auto - match instant.unbind()? + match instant + .unbind()? .inner_instant(agent) .to_ixdtf_string(Some(TimeZone::utc()), options) { @@ -553,10 +555,12 @@ impl TemporalInstantPrototype { // 1. Let instant be the this value. let value = this_value.bind(gc.nogc()); // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()).bind(gc.nogc()); + let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()) + .bind(gc.nogc()); // 3. Return TemporalInstantToString(instant, undefined, AUTO). let options: ToStringRoundingOptions = ToStringRoundingOptions::default(); - match instant.unbind()? + match instant + .unbind()? .inner_instant(agent) .to_ixdtf_string(Some(TimeZone::utc()), options) { @@ -598,7 +602,8 @@ impl TemporalInstantPrototype { // 1. Let instant be the this value. let value = this_value.bind(gc.nogc()); // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()).bind(gc.nogc()); + let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()) + .bind(gc.nogc()); // 3. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone). let time_zone = Some(TimeZone::utc()); // 4. Return ! CreateTemporalZonedDateTime(instant.[[EpochNanoseconds]], timeZone, "iso8601"). From a2472edeebac915db3227edebeefd12af3799365 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 2 Dec 2025 17:08:35 +0100 Subject: [PATCH 46/55] _ in front of unused --- .../builtins/temporal/instant/instant_prototype.rs | 8 ++++---- 1 file changed, 4 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 5eec06491..7d86e844c 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -596,16 +596,16 @@ impl TemporalInstantPrototype { fn to_zoned_date_time_iso<'gc>( agent: &mut Agent, this_value: Value, - args: ArgumentsList, - mut gc: GcScope<'gc, '_>, + _args: ArgumentsList, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { // 1. Let instant be the this value. let value = this_value.bind(gc.nogc()); // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()) + let _instant = require_internal_slot_temporal_instant(agent, value.unbind(), gc.nogc()) .bind(gc.nogc()); // 3. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone). - let time_zone = Some(TimeZone::utc()); + let _time_zone = Some(TimeZone::utc()); // 4. Return ! CreateTemporalZonedDateTime(instant.[[EpochNanoseconds]], timeZone, "iso8601"). unimplemented!() } From ffc6180a720a40d4cdd30082ced4efcb01577867 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Fri, 5 Dec 2025 17:31:43 +0100 Subject: [PATCH 47/55] duration constructor, missing ao create_temporal_duration --- .../ecmascript/builtins/temporal/duration.rs | 41 ++++--- .../temporal/duration/duration_constructor.rs | 109 ++++++++++++++++-- 2 files changed, 119 insertions(+), 31 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 7bbd45e37..991c7b632 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -6,12 +6,9 @@ use crate::{ ecmascript::{ abstract_operations::{ operations_on_objects::get, type_conversion::to_integer_if_integral, - }, - execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, - types::{ - BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, Object, OrdinaryObject, String, - Value, - }, + }, builtins::ordinary::ordinary_create_from_constructor, execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, types::{ + BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, Object, OrdinaryObject, String, Value + } }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -151,9 +148,7 @@ impl<'a> CreateHeapData, TemporalDuration<'a>> for Heap { TemporalDuration(BaseIndex::last(&self.durations)) } } -/// 7.5.19 CreateTemporalDuration ( years, months, weeks, -/// days, hours, minutes, seconds, -/// milliseconds, microseconds, nanoseconds [ , newTarget ] ) +/// [7.5.19 CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds [ , newTarget ] )](https://tc39.es/proposal-temporal/#sec-temporal-createtemporalduration) /// The abstract operation CreateTemporalDuration takes arguments /// years (an integer), months (an integer), /// weeks (an integer), days (an integer), @@ -165,22 +160,26 @@ impl<'a> CreateHeapData, TemporalDuration<'a>> for Heap { /// 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: -pub(crate) fn _create_temporal_duration<'gc>(// years, - // months, - // weeks, - // days, - // hours, - // minutes, - // seconds, - // milliseconds, - // microseconds, - // nanoseconds: , - // new_target: Option, +pub(crate) fn create_temporal_duration<'gc>(// years, + agent: &mut Agent, + duration: temporal_rs::Duration, + new_target: Option, + gc: GcScope<'gc, '_>, ) -> 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%. + let new_target = new_target.unwrap_or_else(|| { + agent + .current_realm_record() + .intrinsics() + .temporal_duration() + .into_function() + }); // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »). + let Object::Duration(object) = ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? + else { + unreachable!() + }; // 4. Set object.[[Years]] to ℝ(𝔽(years)). // 5. Set object.[[Months]] to ℝ(𝔽(months)). // 6. Set object.[[Weeks]] to ℝ(𝔽(weeks)). 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 80c17eb07..5bf1b0cd5 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -1,11 +1,10 @@ +use std::time::Duration; + 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}, + abstract_operations::type_conversion::to_integer_if_integral, builders::builtin_function_builder::BuiltinFunctionBuilder, builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, temporal::duration::create_temporal_duration}, execution::{Agent, JsResult, Realm, agent::ExceptionType}, types::{BUILTIN_STRING_MEMORY, BigInt, Function, IntoObject, IntoValue, Object, String, Value} }, - engine::context::{GcScope, NoGcScope}, + engine::context::{Bindable, GcScope, NoGcScope}, heap::IntrinsicConstructorIndexes, }; @@ -21,14 +20,104 @@ impl BuiltinIntrinsicConstructor for TemporalDurationConstructor { } impl TemporalDurationConstructor { + /// [7.1.1 Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ , minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ] ] ] ] ] ] ] )](https://tc39.es/proposal-temporal/#sec-temporal.duration) fn constructor<'gc>( - _agent: &mut Agent, + agent: &mut Agent, _: Value, - _args: ArgumentsList, - _new_target: Option, - mut _gc: GcScope<'gc, '_>, + args: ArgumentsList, + new_target: Option, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - unimplemented!() + let args = args.bind(gc.nogc()); + let new_target = new_target.bind(gc.nogc()); + + let years = args.get(1).bind(gc.nogc()); + let months = args.get(2).bind(gc.nogc()); + let weeks: Value<'_> = args.get(3).bind(gc.nogc()); + let days = args.get(4).bind(gc.nogc()); + let hours = args.get(5).bind(gc.nogc()); + let minutes = args.get(6).bind(gc.nogc()); + let seconds = args.get(7).bind(gc.nogc()); + let milliseconds = args.get(8).bind(gc.nogc()); + let microseconds = args.get(9).bind(gc.nogc()); + let nanoseconds = args.get(10).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.Duration constructor without new is forbidden", + gc.into_nogc(), + )); + }; + let Ok(mut new_target) = Function::try_from(new_target) else { + unreachable!() + }; + + // 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years). + let y = if years.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, years.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months). + let mo = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks). + let w = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days). + let d = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours). + let h = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes). + let m = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds). + let s = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds). + let ms = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) + }; + // 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds). + let mis = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_f64(agent) as i128 + }; + // 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds). + let ns = if months.is_undefined() { + 0 + } else { + to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_f64(agent) as i128 + }; + // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget). + let duration = temporal_rs::Duration::new(y, mo, w, d, h, m, s, ms, mis, ns)?; + create_temporal_duration(agent, duration, Some(new_target.unbind()), gc) + .map(|duration| duration.into_value()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { From 6f06887354140f0d9aaec596e649a3629f474e5a Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 9 Dec 2025 12:54:33 +0100 Subject: [PATCH 48/55] changed duration constructor to gc scope instead of gc bind --- .../ecmascript/builtins/temporal/duration.rs | 16 +- .../temporal/duration/duration_constructor.rs | 153 ++++++++++++------ 2 files changed, 113 insertions(+), 56 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 991c7b632..6b0a8ea4d 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -6,9 +6,13 @@ use crate::{ ecmascript::{ abstract_operations::{ operations_on_objects::get, type_conversion::to_integer_if_integral, - }, builtins::ordinary::ordinary_create_from_constructor, execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, types::{ - BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, Object, OrdinaryObject, String, Value - } + }, + builtins::ordinary::ordinary_create_from_constructor, + execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, + types::{ + BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, Object, + OrdinaryObject, String, Value, + }, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -160,7 +164,8 @@ impl<'a> CreateHeapData, TemporalDuration<'a>> for Heap { /// a Temporal.Duration or a throw completion. /// It creates a Temporal.Duration instance and fills /// the internal slots with valid values. -pub(crate) fn create_temporal_duration<'gc>(// years, +pub(crate) fn create_temporal_duration<'gc>( + // years, agent: &mut Agent, duration: temporal_rs::Duration, new_target: Option, @@ -176,7 +181,8 @@ pub(crate) fn create_temporal_duration<'gc>(// years, .into_function() }); // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »). - let Object::Duration(object) = ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? + let Object::Duration(object) = + ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? else { unreachable!() }; 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 5bf1b0cd5..619084b7d 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -1,10 +1,22 @@ -use std::time::Duration; +use std::{thread::scope, time::Duration}; use crate::{ ecmascript::{ - abstract_operations::type_conversion::to_integer_if_integral, builders::builtin_function_builder::BuiltinFunctionBuilder, builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, temporal::duration::create_temporal_duration}, execution::{Agent, JsResult, Realm, agent::ExceptionType}, types::{BUILTIN_STRING_MEMORY, BigInt, Function, IntoObject, IntoValue, Object, String, Value} + abstract_operations::type_conversion::to_integer_if_integral, + builders::builtin_function_builder::BuiltinFunctionBuilder, + builtins::{ + ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, + temporal::{duration::create_temporal_duration, error::temporal_err_to_js_err}, + }, + execution::{Agent, JsResult, Realm, agent::ExceptionType}, + types::{ + BUILTIN_STRING_MEMORY, BigInt, Function, IntoObject, IntoValue, Object, String, Value, + }, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, }, - engine::context::{Bindable, GcScope, NoGcScope}, heap::IntrinsicConstructorIndexes, }; @@ -28,95 +40,134 @@ impl TemporalDurationConstructor { new_target: Option, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - let args = args.bind(gc.nogc()); + let years = args.get(1).scope(agent, gc.nogc()); + let months = args.get(2).scope(agent, gc.nogc()); + let weeks = args.get(3).scope(agent, gc.nogc()); + let days = args.get(4).scope(agent, gc.nogc()); + let hours = args.get(5).scope(agent, gc.nogc()); + let minutes = args.get(6).scope(agent, gc.nogc()); + let seconds = args.get(7).scope(agent, gc.nogc()); + let milliseconds = args.get(8).scope(agent, gc.nogc()); + let microseconds = args.get(9).scope(agent, gc.nogc()); + let nanoseconds = args.get(10).scope(agent, gc.nogc()); let new_target = new_target.bind(gc.nogc()); - - let years = args.get(1).bind(gc.nogc()); - let months = args.get(2).bind(gc.nogc()); - let weeks: Value<'_> = args.get(3).bind(gc.nogc()); - let days = args.get(4).bind(gc.nogc()); - let hours = args.get(5).bind(gc.nogc()); - let minutes = args.get(6).bind(gc.nogc()); - let seconds = args.get(7).bind(gc.nogc()); - let milliseconds = args.get(8).bind(gc.nogc()); - let microseconds = args.get(9).bind(gc.nogc()); - let nanoseconds = args.get(10).bind(gc.nogc()); - // 1. If NewTarget is undefined, throw a TypeError exception. - let Some (new_target) = new_target else { + let Some(new_target) = new_target else { return Err(agent.throw_exception_with_static_message( ExceptionType::TypeError, "calling a builtin Temporal.Duration constructor without new is forbidden", gc.into_nogc(), )); }; - let Ok(mut new_target) = Function::try_from(new_target) else { + let Ok(new_target) = Function::try_from(new_target.unbind()) else { unreachable!() }; - + let new_target = new_target.scope(agent, gc.nogc()); // 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years). - let y = if years.is_undefined() { + let y = if years.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, years.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, years.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months). - let mo = if months.is_undefined() { + let mo = if months.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, months.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks). - let w = if months.is_undefined() { + let w = if weeks.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, weeks.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days). - let d = if months.is_undefined() { + let d = if days.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, days.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours). - let h = if months.is_undefined() { + let h = if hours.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, hours.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes). - let m = if months.is_undefined() { + let m = if minutes.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, minutes.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds). - let s = if months.is_undefined() { + let s = if seconds.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, seconds.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds). - let ms = if months.is_undefined() { + let ms = if milliseconds.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_i64(agent) - }; + to_integer_if_integral(agent, milliseconds.get(agent), gc.reborrow()) + .unbind()? + .into_i64(agent) + } + .bind(gc.nogc()); + // 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds). - let mis = if months.is_undefined() { + let mis = if microseconds.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_f64(agent) as i128 - }; + to_integer_if_integral(agent, microseconds.get(agent), gc.reborrow()) + .unbind()? + .into_f64(agent) as i128 + } + .bind(gc.nogc()); + // 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds). - let ns = if months.is_undefined() { + let ns = if nanoseconds.get(agent).is_undefined() { 0 } else { - to_integer_if_integral(agent, months.unbind(), gc.reborrow())?.into_f64(agent) as i128 - }; + to_integer_if_integral(agent, nanoseconds.get(agent), gc.reborrow()) + .unbind()? + .into_f64(agent) as i128 + } + .bind(gc.nogc()); // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget). - let duration = temporal_rs::Duration::new(y, mo, w, d, h, m, s, ms, mis, ns)?; - create_temporal_duration(agent, duration, Some(new_target.unbind()), gc) + let duration = temporal_rs::Duration::new(y, mo, w, d, h, m, s, ms, mis, ns) + .map_err(|e| temporal_err_to_js_err(agent, e, gc.nogc())) + .unbind()? + .bind(gc.nogc()); + create_temporal_duration(agent, duration.unbind(), Some(new_target.get(agent)), gc) .map(|duration| duration.into_value()) } From 9ca1b7bea07ca171af4af823175bcf100299619a Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 9 Dec 2025 13:45:55 +0100 Subject: [PATCH 49/55] create_temporal_duration and require_internal_slot_temporal_duration --- .../ecmascript/builtins/temporal/duration.rs | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 6b0a8ea4d..f70a2f812 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -2,6 +2,8 @@ pub(crate) mod data; pub mod duration_constructor; pub mod duration_prototype; +use temporal_rs::Duration; + use crate::{ ecmascript::{ abstract_operations::{ @@ -37,9 +39,15 @@ 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 + } + /// # Safety + /// + /// Should be only called once; reinitialising the value is not allowed. + unsafe fn set_duration(self, agent: &mut Agent, duration: temporal_rs::Duration) { + agent[self].duration = duration; + } } bindable_handle!(TemporalDuration); @@ -196,8 +204,9 @@ pub(crate) fn create_temporal_duration<'gc>( // 11. Set object.[[Milliseconds]] to ℝ(𝔽(milliseconds)). // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)). // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)). + unsafe { object.set_duration(agent, duration) }; // 14. Return object. - unimplemented!() + Ok(object) } /// Abstract Operations <---> @@ -215,10 +224,11 @@ 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()) { - unimplemented!(); + if let Ok(obj) = require_internal_slot_temporal_duration(agent, item, gc.nogc()) { // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]). + return Ok(agent[obj].duration); } + // 2. If item is not an Object, then if !item.is_object() { let Ok(item) = String::try_from(item) else { @@ -465,18 +475,16 @@ pub(crate) fn _create_negated_temporal_duration<'gc>( #[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: - // 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, - // )), - // } + match value { + Value::Duration(duration) => Ok(duration.bind(gc)), + _ => Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Object is not a Temporal Instant", + gc, + )), + } } From 654a34ea0ba8076bbc0061ab5f86305d4f185e99 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 9 Dec 2025 14:23:14 +0100 Subject: [PATCH 50/55] fixes: small changes to imports, instant->duration in duration and proper default() for duration/data.rs --- nova_vm/src/ecmascript/builtins/temporal/duration.rs | 6 ++---- nova_vm/src/ecmascript/builtins/temporal/duration/data.rs | 5 ++++- .../builtins/temporal/duration/duration_constructor.rs | 6 +----- nova_vm/src/ecmascript/execution/realm/intrinsics.rs | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index f70a2f812..132c08a24 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -2,8 +2,6 @@ pub(crate) mod data; pub mod duration_constructor; pub mod duration_prototype; -use temporal_rs::Duration; - use crate::{ ecmascript::{ abstract_operations::{ @@ -190,7 +188,7 @@ pub(crate) fn create_temporal_duration<'gc>( }); // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »). let Object::Duration(object) = - ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalInstant, gc)? + ordinary_create_from_constructor(agent, new_target, ProtoIntrinsics::TemporalDuration, gc)? else { unreachable!() }; @@ -483,7 +481,7 @@ fn require_internal_slot_temporal_duration<'a>( Value::Duration(duration) => Ok(duration.bind(gc)), _ => Err(agent.throw_exception_with_static_message( ExceptionType::TypeError, - "Object is not a Temporal Instant", + "Object is not a Temporal Duration", gc, )), } diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs index ef77cd728..d89087e6a 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs @@ -17,7 +17,10 @@ pub struct DurationHeapData<'a> { impl DurationHeapData<'_> { pub fn default() -> Self { - todo!() + Self { + object_index: None, + duration: temporal_rs::Duration::new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).unwrap(), + } } } 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 619084b7d..c7a5f0e3e 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -1,5 +1,3 @@ -use std::{thread::scope, time::Duration}; - use crate::{ ecmascript::{ abstract_operations::type_conversion::to_integer_if_integral, @@ -9,9 +7,7 @@ use crate::{ temporal::{duration::create_temporal_duration, error::temporal_err_to_js_err}, }, execution::{Agent, JsResult, Realm, agent::ExceptionType}, - types::{ - BUILTIN_STRING_MEMORY, BigInt, Function, IntoObject, IntoValue, Object, String, Value, - }, + types::{BUILTIN_STRING_MEMORY, Function, IntoObject, IntoValue, Object, String, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope}, diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 1ef5cff02..cf3d4f8a4 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -957,12 +957,12 @@ impl Intrinsics { IntrinsicObjectIndexes::Temporal.get_backing_object(self.object_index_base) } - /// %Temporal.Instant.Prototype% + /// %Temporal.Duration.Prototype% pub(crate) const fn temporal_duration_prototype(&self) -> OrdinaryObject<'static> { IntrinsicObjectIndexes::TemporalDurationPrototype.get_backing_object(self.object_index_base) } - /// %Temporal.Instant% + /// %Temporal.Duration% pub(crate) const fn temporal_duration(&self) -> BuiltinFunction<'static> { IntrinsicConstructorIndexes::TemporalDuration .get_builtin_function(self.builtin_function_index_base) From 24a6f4d8b3e06b99461576530a7356f5d85d69c7 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Fri, 5 Dec 2025 16:24:22 +0100 Subject: [PATCH 51/55] remove references to plaintime object. since it is outside of the scope of the pull request --- nova_vm/src/builtin_strings | 2 - nova_vm/src/ecmascript/builtins/ordinary.rs | 7 - nova_vm/src/ecmascript/builtins/temporal.rs | 10 -- .../builtins/temporal/plain_time.rs | 159 ------------------ .../builtins/temporal/plain_time/data.rs | 47 ------ .../plain_time/plain_time_constructor.rs | 46 ----- .../plain_time/plain_time_prototype.rs | 33 ---- .../ecmascript/execution/realm/intrinsics.rs | 22 --- nova_vm/src/ecmascript/execution/weak_key.rs | 17 +- .../src/ecmascript/types/language/object.rs | 10 -- .../src/ecmascript/types/language/value.rs | 17 +- nova_vm/src/engine/bytecode/vm.rs | 2 - nova_vm/src/engine/rootable.rs | 6 - nova_vm/src/heap.rs | 4 - nova_vm/src/heap/heap_bits.rs | 26 +-- nova_vm/src/heap/heap_constants.rs | 4 - nova_vm/src/heap/heap_gc.rs | 22 +-- 17 files changed, 7 insertions(+), 427 deletions(-) delete mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time.rs delete mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs delete mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs delete mode 100644 nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 14fbf413c..694d430f1 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -329,7 +329,6 @@ parseFloat parseInt #[cfg(feature = "proposal-atomics-microwait")]pause #[cfg(feature = "math")]PI -#[cfg(feature = "temporal")]PlainTime pop POSITIVE_INFINITY #[cfg(feature = "math")]pow @@ -455,7 +454,6 @@ SyntaxError #[cfg(feature = "temporal")]Temporal #[cfg(feature = "temporal")]Temporal.Duration #[cfg(feature = "temporal")]Temporal.Instant -#[cfg(feature = "temporal")]Temporal.PlainTime #[cfg(feature = "math")]tan #[cfg(feature = "math")]tanh #[cfg(feature = "regexp")]test diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index dd1b1b133..b5822e2e1 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -1659,11 +1659,6 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( ProtoIntrinsics::TemporalDuration => { agent.heap.create(DurationHeapData::default()).into_object() } - #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalPlainTime => agent - .heap - .create(PlainTimeHeapData::default()) - .into_object(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) @@ -2100,8 +2095,6 @@ fn get_intrinsic_constructor<'a>( #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into()), #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalPlainTime => Some(intrinsics.temporal_plain_time().into()), - #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalDuration => Some(intrinsics.temporal_duration().into()), } } diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 52b2e336c..067f77268 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -6,7 +6,6 @@ pub mod duration; pub mod error; pub mod instant; pub mod options; -pub mod plain_time; use temporal_rs::{ options::{DifferenceSettings, RoundingIncrement, RoundingMode, Unit, UnitGroup}, @@ -41,7 +40,6 @@ impl 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) .with_property_capacity(4) @@ -58,14 +56,6 @@ impl Temporal { // 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 ( . . . ) diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs deleted file mode 100644 index 2feced6ca..000000000 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ /dev/null @@ -1,159 +0,0 @@ -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, bindable_handle}, - rootable::{HeapRootData, HeapRootRef, Rootable}, - }, - heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - WorkQueues, indexes::BaseIndex, - }, -}; - -pub(crate) mod data; -pub mod plain_time_constructor; -pub mod plain_time_prototype; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct TemporalPlainTime<'a>(BaseIndex<'a, PlainTimeHeapData<'static>>); - -impl TemporalPlainTime<'_> { - //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 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); - } - - 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) - } -} - -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/ecmascript/builtins/temporal/plain_time/data.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs deleted file mode 100644 index 0c260e60e..000000000 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs +++ /dev/null @@ -1,47 +0,0 @@ -// 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/builtins/temporal/plain_time/plain_time_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs deleted file mode 100644 index d3dcfff1a..000000000 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs +++ /dev/null @@ -1,46 +0,0 @@ -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 TemporalPlainTimeConstructor; - -impl Builtin for TemporalPlainTimeConstructor { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainTime; - const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalPlainTimeConstructor::constructor); -} - -impl BuiltinIntrinsicConstructor for TemporalPlainTimeConstructor { - const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalPlainTime; -} - -impl TemporalPlainTimeConstructor { - 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 plain_time_prototype = intrinsics.temporal_plain_time_prototype(); - - BuiltinFunctionBuilder::new_intrinsic_constructor::( - agent, realm, - ) - .with_property_capacity(1) - .with_prototype_property(plain_time_prototype.into_object()) - .build(); - } -} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs deleted file mode 100644 index d457819d8..000000000 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{ - ecmascript::{ - builders::ordinary_object_builder::OrdinaryObjectBuilder, - execution::{Agent, Realm}, - types::BUILTIN_STRING_MEMORY, - }, - engine::context::NoGcScope, - heap::WellKnownSymbolIndexes, -}; - -pub(crate) struct TemporalPlainTimePrototype; -impl TemporalPlainTimePrototype { - 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_plain_time_prototype(); - let object_prototype = intrinsics.object_prototype(); - let plain_time_constructor = intrinsics.temporal_plain_time(); - - OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(2) - .with_prototype(object_prototype) - .with_constructor_property(plain_time_constructor) - .with_property(|builder| { - builder - .with_key(WellKnownSymbolIndexes::ToStringTag.into()) - .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal_PlainTime.into()) - .with_enumerable(false) - .with_configurable(true) - .build() - }) - .build(); - } -} diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index cf3d4f8a4..7ece122ce 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -139,8 +139,6 @@ pub enum ProtoIntrinsics { TemporalInstant, #[cfg(feature = "temporal")] TemporalDuration, - #[cfg(feature = "temporal")] - TemporalPlainTime, TypeError, #[cfg(feature = "array-buffer")] Uint16Array, @@ -232,10 +230,6 @@ impl Intrinsics { TemporalDurationPrototype::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] TemporalDurationConstructor::create_intrinsic(agent, realm, gc); - #[cfg(feature = "temporal")] - TemporalPlainTimePrototype::create_intrinsic(agent, realm, gc); - #[cfg(feature = "temporal")] - TemporalPlainTimeConstructor::create_intrinsic(agent, realm, gc); #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); #[cfg(feature = "date")] @@ -349,8 +343,6 @@ impl Intrinsics { 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(), ProtoIntrinsics::AggregateError => self.aggregate_error().into(), @@ -444,8 +436,6 @@ impl Intrinsics { 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(), ProtoIntrinsics::AggregateError => self.aggregate_error_prototype().into(), @@ -979,18 +969,6 @@ 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 b88950594..51a5fa4a8 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -20,8 +20,7 @@ use crate::ecmascript::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::{ - DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalDuration, - TemporalInstant, TemporalPlainTime, + DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, TemporalDuration, TemporalInstant, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; @@ -103,8 +102,6 @@ pub(crate) enum WeakKey<'a> { 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, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -223,8 +220,6 @@ impl<'a> From> for Value<'a> { 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), WeakKey::Map(d) => Self::Map(d), @@ -336,8 +331,6 @@ impl<'a> From> for WeakKey<'a> { 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), Object::Map(d) => Self::Map(d), @@ -453,8 +446,6 @@ impl<'a> TryFrom> for Object<'a> { 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)), WeakKey::Map(d) => Ok(Self::Map(d)), @@ -601,8 +592,6 @@ impl HeapMarkAndSweep for WeakKey<'static> { 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), Self::Map(d) => d.mark_values(queues), @@ -712,8 +701,6 @@ impl HeapMarkAndSweep for WeakKey<'static> { 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), Self::Map(d) => d.sweep_values(compactions), @@ -839,8 +826,6 @@ impl HeapSweepWeakReference for WeakKey<'static> { 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 .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 a3d8d94b4..95844ac61 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -127,8 +127,6 @@ pub enum Object<'a> { 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, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -659,8 +657,6 @@ impl<'a> From> for Value<'a> { 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), Object::Map(data) => Self::Map(data), @@ -789,8 +785,6 @@ macro_rules! object_delegate { 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),+), Self::BuiltinFunction(data) => data.$method($($arg),+), @@ -1225,8 +1219,6 @@ impl HeapSweepWeakReference for Object<'static> { 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 .sweep_weak_reference(compactions) @@ -1566,8 +1558,6 @@ impl TryFrom for Object<'_> { 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) => { 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 807314376..f2adba198 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -24,7 +24,7 @@ use crate::ecmascript::{ SharedUint8Array, SharedUint8ClampedArray, SharedUint16Array, SharedUint32Array, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{TemporalDuration, TemporalInstant, TemporalPlainTime}; +use crate::ecmascript::{TemporalDuration, TemporalInstant}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakMap, WeakRef, WeakSet}; use crate::{ @@ -143,8 +143,6 @@ pub enum Value<'a> { Instant(TemporalInstant<'a>), #[cfg(feature = "temporal")] Duration(TemporalDuration<'a>), - #[cfg(feature = "temporal")] - PlainTime(TemporalPlainTime<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), Map(Map<'a>), @@ -286,9 +284,6 @@ pub(crate) const INSTANT_DISCRIMINANT: u8 = #[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)); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_DEF)); @@ -937,8 +932,6 @@ impl Rootable for Value<'_> { 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( HeapRootData::FinalizationRegistry(finalization_registry.unbind()), @@ -1104,8 +1097,6 @@ impl Rootable for Value<'_> { 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) => { Some(Self::FinalizationRegistry(finalization_registry)) @@ -1258,8 +1249,6 @@ impl HeapMarkAndSweep for Value<'static> { 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), Self::BuiltinFunction(data) => data.mark_values(queues), @@ -1378,8 +1367,6 @@ impl HeapMarkAndSweep for Value<'static> { 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), Self::BuiltinFunction(data) => data.sweep_values(compactions), @@ -1535,8 +1522,6 @@ fn map_object_to_static_string_repr(value: Value) -> String<'static> { 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_, #[cfg(feature = "weak-refs")] diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 8ecdec956..d754220e8 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1302,8 +1302,6 @@ pub(crate) fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> Strin 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(_) | Value::BuiltinConstructorFunction(_) | diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index fbaa82bbf..f27850c54 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -138,7 +138,6 @@ pub(crate) mod private { #[cfg(feature = "temporal")] impl RootableSealed for TemporalDuration<'_> {} #[cfg(feature = "temporal")] - impl RootableSealed for TemporalPlainTime<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} impl RootableSealed for Error<'_> {} @@ -455,7 +454,6 @@ pub enum HeapRootData { #[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, Map(Map<'static>) = MAP_DISCRIMINANT, @@ -638,8 +636,6 @@ impl HeapMarkAndSweep for HeapRootData { 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) => { finalization_registry.mark_values(queues) @@ -793,8 +789,6 @@ impl HeapMarkAndSweep for HeapRootData { 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) => { finalization_registry.sweep_values(compactions) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 83d0bf40e..3b2b1ffdd 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -81,8 +81,6 @@ pub(crate) struct Heap { 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; /// Element arrays are static arrays of Values plus @@ -235,8 +233,6 @@ impl Heap { 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 { e2pow1: ElementArray2Pow1::with_capacity(1024), diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 5cd27c79d..950bb2c14 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -21,8 +21,6 @@ use soavec_derive::SoAble; #[cfg(feature = "date")] use crate::ecmascript::Date; -#[cfg(feature = "temporal")] -use crate::ecmascript::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::{ArrayBuffer, DataView, VoidArray}; #[cfg(feature = "regexp")] @@ -31,6 +29,8 @@ use crate::ecmascript::{RegExp, RegExpStringIterator}; use crate::ecmascript::{Set, SetIterator}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::{SharedArrayBuffer, SharedDataView, SharedVoidArray}; +#[cfg(feature = "temporal")] +use crate::ecmascript::{TemporalDuration, TemporalInstant}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakMap, WeakRef, WeakSet}; use crate::{ @@ -420,8 +420,6 @@ pub(crate) struct HeapBits { #[cfg(feature = "temporal")] 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, @@ -502,8 +500,6 @@ pub(crate) struct WorkQueues<'a> { #[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>, @@ -654,10 +650,9 @@ 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")] + #[cfg(feature = "temporal")] 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()); + #[cfg(feature = "temporal")] 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()); @@ -769,8 +764,6 @@ impl HeapBits { #[cfg(feature = "temporal")] instants, #[cfg(feature = "temporal")] - plain_times, - #[cfg(feature = "temporal")] durations, declarative_environments, e_2_1, @@ -885,8 +878,6 @@ impl HeapBits { #[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), - #[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 @@ -1029,8 +1020,6 @@ impl<'a> WorkQueues<'a> { 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), e_2_2: Vec::with_capacity(heap.elements.e2pow2.values.len() / 4), @@ -1141,8 +1130,6 @@ impl<'a> WorkQueues<'a> { instants, #[cfg(feature = "temporal")] durations, - #[cfg(feature = "temporal")] - plain_times, declarative_environments, e_2_1, e_2_2, @@ -1265,7 +1252,6 @@ impl<'a> WorkQueues<'a> { && dates.is_empty() && instants.is_empty() && durations.is_empty() - && plain_times.is_empty() && declarative_environments.is_empty() && e_2_1.is_empty() && e_2_2.is_empty() @@ -1628,8 +1614,6 @@ pub(crate) struct CompactionLists { #[cfg(feature = "temporal")] 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, @@ -1789,8 +1773,6 @@ impl CompactionLists { #[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), - #[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), diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index 536003343..d26a8d60b 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -39,8 +39,6 @@ pub(crate) enum IntrinsicObjectIndexes { TemporalInstantPrototype, #[cfg(feature = "temporal")] TemporalDurationPrototype, - #[cfg(feature = "temporal")] - TemporalPlainTimePrototype, // Text processing #[cfg(feature = "regexp")] @@ -182,8 +180,6 @@ pub(crate) enum IntrinsicConstructorIndexes { TemporalInstant, #[cfg(feature = "temporal")] TemporalDuration, - #[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 15b748163..eaf8e78f7 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -15,7 +15,7 @@ use crate::ecmascript::{Set, SetIterator}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::{SharedArrayBuffer, SharedDataView, SharedVoidArray}; #[cfg(feature = "temporal")] -use crate::ecmascript::{TemporalInstant, TemporalPlainTime}; +use crate::ecmascript::{TemporalDuration, TemporalInstant}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakMap, WeakRef, WeakSet}; #[cfg(feature = "array-buffer")] @@ -87,8 +87,6 @@ pub(crate) fn heap_gc(agent: &mut Agent, root_realms: &mut [Option = - 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() { @@ -1250,8 +1238,6 @@ fn sweep( instants, #[cfg(feature = "temporal")] durations, - #[cfg(feature = "temporal")] - plain_times, ecmascript_functions, elements, embedder_objects, @@ -1717,12 +1703,6 @@ fn sweep( 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); - }); - } if !declarative.is_empty() { s.spawn(|| { sweep_heap_vector_values( From 9a29cbfc6fba017080f66b74bd73040615ac8035 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Fri, 5 Dec 2025 17:04:30 +0100 Subject: [PATCH 52/55] change property capapcity of temporal 4->3 --- nova_vm/src/ecmascript/builtins/temporal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 067f77268..998b45768 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -42,7 +42,7 @@ impl Temporal { let duration_constructor = intrinsics.temporal_duration(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(4) + .with_property_capacity(3) .with_prototype(object_prototype) // 1.2.1 Temporal.Instant ( . . . ) .with_property(|builder| { From a63ac302c69a360731a0fb6dd33565f94c599bd1 Mon Sep 17 00:00:00 2001 From: jesper Date: Sun, 8 Feb 2026 14:20:04 +0100 Subject: [PATCH 53/55] Rebase with main and fix imports --- nova_vm/src/ecmascript/builtins/ordinary.rs | 6 +- nova_vm/src/ecmascript/builtins/temporal.rs | 32 ++-- .../ecmascript/builtins/temporal/duration.rs | 145 ++++----------- .../builtins/temporal/duration/data.rs | 7 +- .../temporal/duration/duration_constructor.rs | 21 +-- .../temporal/duration/duration_prototype.rs | 8 +- .../src/ecmascript/builtins/temporal/error.rs | 7 +- .../ecmascript/builtins/temporal/instant.rs | 176 ++++-------------- .../builtins/temporal/instant/data.rs | 6 +- .../temporal/instant/instant_constructor.rs | 31 +-- .../temporal/instant/instant_prototype.rs | 57 ++---- .../ecmascript/builtins/temporal/options.rs | 15 +- .../ecmascript/execution/realm/intrinsics.rs | 4 +- .../src/ecmascript/types/language/bigint.rs | 2 +- .../src/ecmascript/types/language/object.rs | 10 +- .../src/ecmascript/types/language/value.rs | 2 +- nova_vm/src/engine/rootable.rs | 8 +- nova_vm/src/heap.rs | 2 +- 18 files changed, 158 insertions(+), 381 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index b5822e2e1..21f676567 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -19,7 +19,7 @@ use crate::ecmascript::SharedDataViewRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::try_get_result_into_value; #[cfg(feature = "temporal")] -use crate::ecmascript::{DurationHeapData, InstantRecord, PlainTimeHeapData}; +use crate::ecmascript::{DurationHeapData, InstantRecord}; use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods, @@ -1656,9 +1656,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => agent.heap.create(InstantRecord::default()).into(), #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalDuration => { - agent.heap.create(DurationHeapData::default()).into_object() - } + ProtoIntrinsics::TemporalDuration => agent.heap.create(DurationHeapData::default()).into(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 998b45768..972793beb 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -2,10 +2,15 @@ // 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 duration; -pub mod error; -pub mod instant; -pub mod options; +mod duration; +mod error; +mod instant; +mod options; + +pub use duration::*; +pub use error::*; +pub use instant::*; +pub use options::*; use temporal_rs::{ options::{DifferenceSettings, RoundingIncrement, RoundingMode, Unit, UnitGroup}, @@ -14,19 +19,10 @@ use temporal_rs::{ use crate::{ ecmascript::{ - abstract_operations::operations_on_objects::get, - builders::ordinary_object_builder::OrdinaryObjectBuilder, - builtins::temporal::{ - instant::instant_prototype::get_temporal_unit_valued_option, - options::{get_rounding_increment_option, get_rounding_mode_option}, - }, - execution::{Agent, JsResult, Realm, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, IntoValue, Object, Value}, - }, - engine::{ - context::{Bindable, GcScope, NoGcScope, trivially_bindable}, - rootable::Scopable, + Agent, BUILTIN_STRING_MEMORY, ExceptionType, JsResult, Object, Realm, Value, + builders::OrdinaryObjectBuilder, get, }, + engine::{Bindable, GcScope, NoGcScope, Scopable, trivially_bindable}, heap::WellKnownSymbolIndexes, }; @@ -48,7 +44,7 @@ impl Temporal { .with_property(|builder| { builder .with_key(BUILTIN_STRING_MEMORY.Instant.into()) - .with_value(instant_constructor.into_value()) + .with_value(instant_constructor.into()) .with_enumerable(false) .with_configurable(true) .build() @@ -62,7 +58,7 @@ impl Temporal { .with_property(|builder| { builder .with_key(BUILTIN_STRING_MEMORY.Duration.into()) - .with_value(duration_constructor.into_value()) + .with_value(duration_constructor.into()) .with_enumerable(false) .with_configurable(true) .build() diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration.rs b/nova_vm/src/ecmascript/builtins/temporal/duration.rs index 132c08a24..9266821ee 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration.rs @@ -1,141 +1,68 @@ -pub(crate) mod data; -pub mod duration_constructor; -pub mod duration_prototype; +// 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/. + +mod data; +mod duration_constructor; +mod duration_prototype; + +pub(crate) use data::*; +pub(crate) use duration_constructor::*; +pub(crate) use duration_prototype::*; use crate::{ ecmascript::{ - abstract_operations::{ - operations_on_objects::get, type_conversion::to_integer_if_integral, - }, - builtins::ordinary::ordinary_create_from_constructor, - execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType}, - types::{ - BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction, Object, - OrdinaryObject, String, Value, - }, - }, - engine::{ - context::{Bindable, GcScope, NoGcScope, bindable_handle}, - rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, + Agent, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods, InternalSlots, + JsResult, Object, OrdinaryObject, ProtoIntrinsics, String, Value, get, object_handle, + ordinary_create_from_constructor, to_integer_if_integral, }, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - WorkQueues, indexes::BaseIndex, + ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, + HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, }, }; -use core::ops::{Index, IndexMut}; -use self::data::DurationHeapData; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct TemporalDuration<'a>(BaseIndex<'a, DurationHeapData<'static>>); +object_handle!(TemporalDuration, Duration); +arena_vec_access!( + TemporalDuration, + 'a, + DurationHeapData, + durations +); 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 + pub(crate) fn inner_duration(self, agent: &Agent) -> &temporal_rs::Duration { + &self.unbind().get(agent).duration } /// # Safety /// /// Should be only called once; reinitialising the value is not allowed. unsafe fn set_duration(self, agent: &mut Agent, duration: temporal_rs::Duration) { - agent[self].duration = duration; - } -} - -bindable_handle!(TemporalDuration); - -impl<'a> From> for Value<'a> { - fn from(value: TemporalDuration<'a>) -> Self { - Value::Duration(value) - } -} -impl<'a> From> for Object<'a> { - fn from(value: TemporalDuration<'a>) -> Self { - Object::Duration(value) - } -} -impl<'a> TryFrom> for TemporalDuration<'a> { - type Error = (); - - fn try_from(value: Value<'a>) -> Result { - match value { - Value::Duration(idx) => Ok(idx), - _ => Err(()), - } + self.get_mut(agent).duration = duration; } } 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 + self.unbind().get(agent).object_index } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { - assert!(agent[self].object_index.replace(backing_object).is_none()); + assert!( + self.get_mut(agent) + .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 { - &self.heap.durations[index] - } -} - -impl IndexMut> for Agent { - fn index_mut(&mut self, index: TemporalDuration) -> &mut Self::Output { - &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") - } -} - -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); @@ -184,7 +111,7 @@ pub(crate) fn create_temporal_duration<'gc>( .current_realm_record() .intrinsics() .temporal_duration() - .into_function() + .into() }); // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Duration.prototype%", « [[InitializedTemporalDuration]], [[Years]], [[Months]], [[Weeks]], [[Days]], [[Hours]], [[Minutes]], [[Seconds]], [[Milliseconds]], [[Microseconds]], [[Nanoseconds]] »). let Object::Duration(object) = @@ -224,7 +151,7 @@ pub(crate) fn to_temporal_duration<'gc>( // 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()) { // a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]). - return Ok(agent[obj].duration); + return Ok(*obj.inner_duration(agent)); } // 2. If item is not an Object, then diff --git a/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs b/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs index d89087e6a..fdc419fd6 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/data.rs @@ -2,15 +2,14 @@ // 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}, + ecmascript::OrdinaryObject, + engine::{NoGcScope, bindable_handle, trivially_bindable}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; #[derive(Debug, Clone, Copy)] -pub struct DurationHeapData<'a> { +pub(crate) struct DurationHeapData<'a> { pub(crate) object_index: Option>, pub(crate) duration: temporal_rs::Duration, } 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 c7a5f0e3e..489370510 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -1,18 +1,11 @@ use crate::{ ecmascript::{ - abstract_operations::type_conversion::to_integer_if_integral, - builders::builtin_function_builder::BuiltinFunctionBuilder, - builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor, - temporal::{duration::create_temporal_duration, error::temporal_err_to_js_err}, - }, - execution::{Agent, JsResult, Realm, agent::ExceptionType}, - types::{BUILTIN_STRING_MEMORY, Function, IntoObject, IntoValue, Object, String, Value}, - }, - engine::{ - context::{Bindable, GcScope, NoGcScope}, - rootable::Scopable, + Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, + BuiltinIntrinsicConstructor, ExceptionType, Function, JsResult, Object, Realm, String, + Value, builders::BuiltinFunctionBuilder, create_temporal_duration, temporal_err_to_js_err, + to_integer_if_integral, }, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::IntrinsicConstructorIndexes, }; @@ -164,7 +157,7 @@ impl TemporalDurationConstructor { .unbind()? .bind(gc.nogc()); create_temporal_duration(agent, duration.unbind(), Some(new_target.get(agent)), gc) - .map(|duration| duration.into_value()) + .map(|duration| duration.into()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { @@ -175,7 +168,7 @@ impl TemporalDurationConstructor { agent, realm, ) .with_property_capacity(1) - .with_prototype_property(duration_prototype.into_object()) + .with_prototype_property(duration_prototype.into()) .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 5d338a663..b43d87518 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_prototype.rs @@ -1,10 +1,6 @@ use crate::{ - ecmascript::{ - builders::ordinary_object_builder::OrdinaryObjectBuilder, - execution::{Agent, Realm}, - types::BUILTIN_STRING_MEMORY, - }, - engine::context::NoGcScope, + ecmascript::{Agent, BUILTIN_STRING_MEMORY, Realm, builders::OrdinaryObjectBuilder}, + engine::NoGcScope, heap::WellKnownSymbolIndexes, }; diff --git a/nova_vm/src/ecmascript/builtins/temporal/error.rs b/nova_vm/src/ecmascript/builtins/temporal/error.rs index 3e81c43e9..48786dc47 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/error.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/error.rs @@ -1,9 +1,6 @@ use crate::{ - ecmascript::execution::{ - Agent, - agent::{ExceptionType, JsError}, - }, - engine::context::NoGcScope, + ecmascript::{Agent, ExceptionType, JsError}, + engine::NoGcScope, }; use temporal_rs::{TemporalError, error::ErrorKind}; diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 1079ef0f2..cf19c3e5a 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -2,63 +2,45 @@ // 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}; +mod data; +mod instant_constructor; +mod instant_prototype; -pub(crate) mod data; -pub mod instant_constructor; -pub mod instant_prototype; +pub(crate) use data::*; +pub(crate) use instant_constructor::*; +pub(crate) use 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::{TemporalDuration, data::DurationHeapData, to_temporal_duration}, - error::temporal_err_to_js_err, - get_difference_settings, - options::get_options_object, - }, - }, - execution::{ - JsResult, ProtoIntrinsics, - agent::{Agent, ExceptionType}, - }, - types::{ - Function, InternalMethods, InternalSlots, IntoFunction, IntoValue, Object, - OrdinaryObject, Primitive, String, Value, - }, - }, - engine::{ - context::{Bindable, GcScope, NoGcScope, bindable_handle}, - rootable::{HeapRootData, HeapRootRef, Rootable, Scopable}, + Agent, DurationHeapData, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, + Object, OrdinaryObject, PreferredType, Primitive, ProtoIntrinsics, String, + TemporalDuration, Value, get_difference_settings, get_options_object, object_handle, + ordinary_create_from_constructor, temporal_err_to_js_err, to_primitive_object, + to_temporal_duration, }, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{ - CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, - WorkQueues, indexes::BaseIndex, + ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, + HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, }, }; -use self::data::InstantRecord; - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct TemporalInstant<'a>(BaseIndex<'a, InstantRecord<'static>>); +object_handle!(TemporalInstant, Instant); +arena_vec_access!( + TemporalInstant, + 'a, + InstantRecord, + instants +); 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)) - } - - pub(crate) const fn get_index(self) -> usize { - self.0.into_index() + pub(crate) fn inner_instant<'a>(self, agent: &'a Agent) -> &'a temporal_rs::Instant { + &self.unbind().get(agent).instant } /// # Safety @@ -69,108 +51,27 @@ impl TemporalInstant<'_> { agent: &mut Agent, epoch_nanoseconds: temporal_rs::Instant, ) { - agent[self].instant = epoch_nanoseconds; - } -} - -bindable_handle!(TemporalInstant); - -impl<'a> From> for Value<'a> { - fn from(value: TemporalInstant<'a>) -> Self { - Value::Instant(value) - } -} -impl<'a> From> for Object<'a> { - fn from(value: TemporalInstant<'a>) -> Self { - Object::Instant(value) - } -} -impl<'a> TryFrom> for TemporalInstant<'a> { - type Error = (); - - fn try_from(value: Value<'a>) -> Result { - match value { - Value::Instant(idx) => Ok(idx), - _ => Err(()), - } - } -} -impl<'a> TryFrom> for TemporalInstant<'a> { - type Error = (); - fn try_from(object: Object<'a>) -> Result { - match object { - Object::Instant(idx) => Ok(idx), - _ => Err(()), - } + self.get_mut(agent).instant = epoch_nanoseconds; } } 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 + self.get(agent).object_index.unbind() } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { - assert!(agent[self].object_index.replace(backing_object).is_none()); + assert!( + self.get_mut(agent) + .object_index + .replace(backing_object) + .is_none() + ); } } 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>; - - fn index(&self, index: TemporalInstant<'_>) -> &Self::Output { - &self.heap.instants[index] - } -} - -impl IndexMut> for Agent { - fn index_mut(&mut self, index: TemporalInstant) -> &mut Self::Output { - &mut self.heap.instants[index] - } -} - -impl Index> for Vec> { - type Output = InstantRecord<'static>; - - 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: TemporalInstant<'_>) -> &mut Self::Output { - self.get_mut(index.get_index()) - .expect("heap access out of bounds") - } -} - -impl Rootable for TemporalInstant<'_> { - type RootRepr = HeapRootRef; - - fn to_root_repr(value: Self) -> Result { - Err(HeapRootData::Instant(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::Instant(object) => Some(object), - _ => None, - } - } -} - impl HeapMarkAndSweep for TemporalInstant<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.instants.push(*self); @@ -201,7 +102,7 @@ impl<'a> CreateHeapData, TemporalInstant<'a>> for Heap { /// 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>( +pub(crate) fn create_temporal_instant<'gc>( agent: &mut Agent, epoch_nanoseconds: temporal_rs::Instant, new_target: Option, @@ -214,7 +115,7 @@ fn create_temporal_instant<'gc>( .current_realm_record() .intrinsics() .temporal_instant() - .into_function() + .into() }); // 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »). let Object::Instant(object) = @@ -234,7 +135,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. -fn to_temporal_instant<'gc>( +pub(crate) fn to_temporal_instant<'gc>( agent: &mut Agent, item: Value, mut gc: GcScope<'gc, '_>, @@ -247,7 +148,7 @@ fn to_temporal_instant<'gc>( // TODO: TemporalZonedDateTime::try_from(item) if let Ok(item) = TemporalInstant::try_from(item) { // i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]). - return Ok(agent[item].instant); + return Ok(*item.inner_instant(agent)); } // b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted. // c. Set item to ? ToPrimitive(item, string). @@ -313,14 +214,15 @@ fn add_duration_to_instant<'gc, const IS_ADD: bool>( // 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.get(agent)].instant, &duration.unwrap()).unwrap() + temporal_rs::Instant::add(instant.get(agent).inner_instant(agent), &duration.unwrap()) + .unwrap() } else { - temporal_rs::Instant::subtract(&agent[instant.get(agent)].instant, &duration.unwrap()) + temporal_rs::Instant::subtract(instant.get(agent).inner_instant(agent), &duration.unwrap()) .unwrap() }; // 7. Return ! CreateTemporalInstant(ns). let instant = create_temporal_instant(agent, ns_result, None, gc)?; - Ok(instant.into_value()) + Ok(instant.into()) } /// [8.5.9 DifferenceTemporalInstant ( operation, instant, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index 1cf068d2e..63e8bb840 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -2,10 +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::engine::NoGcScope; use crate::{ - ecmascript::types::OrdinaryObject, - engine::context::{bindable_handle, trivially_bindable}, + ecmascript::OrdinaryObject, + engine::{bindable_handle, trivially_bindable}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; 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 cfe8f385e..9ad258203 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -1,22 +1,11 @@ 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, + Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, BigInt, Builtin, + BuiltinIntrinsicConstructor, ExceptionType, Function, InstantRecord, JsResult, Object, + Realm, String, Value, builders::BuiltinFunctionBuilder, create_temporal_instant, + to_big_int, to_temporal_instant, }, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{CreateHeapData, IntrinsicConstructorIndexes}, }; @@ -109,7 +98,7 @@ impl TemporalInstantConstructor { }; // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget). create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc) - .map(|instant| instant.into_value()) + .map(|instant| instant.into()) } /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) @@ -126,7 +115,7 @@ impl TemporalInstantConstructor { object_index: None, instant, }); - Ok(instant.into_value()) + Ok(instant.into()) } /// ### [8.2.3 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds) @@ -167,7 +156,7 @@ impl TemporalInstantConstructor { // 5. Return ! CreateTemporalInstant(epochNanoseconds). let instant = create_temporal_instant(agent, epoch_ns, None, gc)?; - let value = instant.into_value(); + let value = instant.into(); Ok(value) } @@ -200,7 +189,7 @@ impl TemporalInstantConstructor { }; // 3. Return ! CreateTemporalInstant(epochNanoseconds). let instant = create_temporal_instant(agent, epoch_nanoseconds, None, gc)?; - let value = instant.into_value(); + let value = instant.into(); Ok(value) } @@ -231,7 +220,7 @@ impl TemporalInstantConstructor { agent, realm, ) .with_property_capacity(5) - .with_prototype_property(instant_prototype.into_object()) + .with_prototype_property(instant_prototype.into()) .with_builtin_function_property::() .with_builtin_function_property::() .with_builtin_function_property::() 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 7d86e844c..64ca4e51a 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -5,37 +5,20 @@ use temporal_rs::{ use crate::{ ecmascript::{ - abstract_operations::{ - operations_on_objects::{get, try_create_data_property_or_throw}, - type_conversion::to_number, + Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, BigInt, Builtin, BuiltinGetter, + ExceptionType, JsResult, Object, PropertyKey, Realm, String, Value, + builders::OrdinaryObjectBuilder, + builtins::temporal::instant::{ + add_duration_to_instant, difference_temporal_instant, + require_internal_slot_temporal_instant, }, - builders::ordinary_object_builder::OrdinaryObjectBuilder, - builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinGetter, - 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, - }, - options::{ - get_option, get_options_object, get_rounding_increment_option, - get_rounding_mode_option, - }, - }, - }, - execution::{ - Agent, JsResult, Realm, - agent::{ExceptionType, unwrap_try}, - }, - types::{BUILTIN_STRING_MEMORY, BigInt, IntoValue, Object, PropertyKey, String, Value}, - }, - engine::{ - context::{Bindable, GcScope, NoGcScope}, - rootable::Scopable, + create_temporal_instant, get, get_option, get_options_object, + get_rounding_increment_option, get_rounding_mode_option, + get_temporal_fractional_second_digits_option, ordinary_object_create_null, + temporal_err_to_js_err, to_number, to_temporal_instant, try_create_data_property_or_throw, + unwrap_try, }, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::WellKnownSymbolIndexes, }; @@ -194,7 +177,7 @@ impl TemporalInstantPrototype { const ADD: bool = true; let result = add_duration_to_instant::(agent, instant.unbind(), duration.unbind(), gc) .unbind()?; - Ok(result.into_value()) + Ok(result.into()) } /// ### [8.3.6 Temporal.Instant.prototype.subtract ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract) @@ -216,7 +199,7 @@ impl TemporalInstantPrototype { let result = add_duration_to_instant::(agent, instant.unbind(), duration.unbind(), gc) .unbind()?; - Ok(result.into_value()) + Ok(result.into()) } /// ### [8.3.7 Temporal.Instant.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) @@ -244,7 +227,7 @@ impl TemporalInstantPrototype { gc, ) .unbind()?; - Ok(result.into_value()) + Ok(result.into()) } /// ### [8.3.8 Temporal.Instant.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until) @@ -272,7 +255,7 @@ impl TemporalInstantPrototype { gc, ) .unbind()?; - Ok(result.into_value()) + Ok(result.into()) } /// ### [8.3.9 Temporal.Instant.prototype.round ( roundTo )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round) @@ -305,17 +288,17 @@ impl TemporalInstantPrototype { // 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()); + let round_to = ordinary_object_create_null(agent, 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(), + param_string.into(), None, gc.nogc(), )); - round_to + round_to.into() } else { // 5. Else, set roundTo to ? GetOptionsObject(roundTo). get_options_object(agent, round_to.unbind(), gc.nogc()) @@ -383,7 +366,7 @@ impl TemporalInstantPrototype { .unbind()? .bind(gc.nogc()); // 19. Return ! CreateTemporalInstant(roundedNs). - Ok(create_temporal_instant(agent, rounded_ns, None, gc)?.into_value()) + Ok(create_temporal_instant(agent, rounded_ns, None, gc)?.into()) } /// ### [8.3.10 Temporal.Instant.prototype.equals ( other )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals) diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs index 06bf109c1..061dd7cda 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/options.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -8,15 +8,10 @@ use temporal_rs::options::{RoundingIncrement, RoundingMode, Unit}; use crate::{ ecmascript::{ - abstract_operations::{operations_on_objects::get, type_conversion::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, Value}, + Agent, BUILTIN_STRING_MEMORY, ExceptionType, JsResult, Object, PropertyKey, Value, get, + ordinary_object_create_null, to_integer_with_truncation, to_string, }, - engine::context::{Bindable, GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope}, }; pub trait OptionType: Sized { @@ -51,9 +46,7 @@ pub(crate) fn get_options_object<'gc>( // 1. If options is undefined, then Value::Undefined => { // a. Return OrdinaryObjectCreate(null). - Ok(ordinary_object_create_with_intrinsics( - agent, None, None, gc, - )) + Ok(ordinary_object_create_null(agent, gc).into()) } // 2. If options is an Object, then value if value.is_object() => { diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 7ece122ce..172e2d9c3 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -26,8 +26,8 @@ use crate::ecmascript::{SetConstructor, SetIteratorPrototype, SetPrototype}; use crate::ecmascript::{SharedArrayBufferConstructor, SharedArrayBufferPrototype}; #[cfg(feature = "temporal")] use crate::ecmascript::{ - TemporalDurationConstructor, TemporalDurationPrototype, TemporalInstantConstructor, - TemporalInstantPrototype, TemporalObject, + Temporal, TemporalDurationConstructor, TemporalDurationPrototype, TemporalInstantConstructor, + TemporalInstantPrototype, }; #[cfg(feature = "weak-refs")] use crate::ecmascript::{ diff --git a/nova_vm/src/ecmascript/types/language/bigint.rs b/nova_vm/src/ecmascript/types/language/bigint.rs index abcfd670c..9e2c2b529 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -217,7 +217,7 @@ impl<'a> BigInt<'a> { pub fn try_into_i128(self, agent: &Agent) -> Option { match self { BigInt::BigInt(b) => { - let data = &agent[b].data; + let data = &b.get(agent).data; let sign = data.sign(); let mut digits = data.iter_u64_digits(); if digits.len() > 2 { diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 95844ac61..c5df1d41a 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -44,7 +44,7 @@ use crate::ecmascript::{ use crate::ecmascript::{DATE_DISCRIMINANT, Date}; #[cfg(feature = "temporal")] use crate::ecmascript::{ - DURATION_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalDuration, TemporalPlainTime, + DURATION_DISCRIMINANT, INSTANT_DISCRIMINANT, TemporalDuration, TemporalInstant, }; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; @@ -78,9 +78,9 @@ use crate::{ Agent, ArgumentsList, Array, ArrayIterator, AsyncGenerator, BoundFunction, BuiltinConstructorFunction, BuiltinFunction, BuiltinPromiseFinallyFunction, BuiltinPromiseResolvingFunction, ECMAScriptFunction, EmbedderObject, Error, - FinalizationRegistry, Generator, Instant, JsResult, Map, MapIterator, Module, ObjectShape, + FinalizationRegistry, Generator, JsResult, Map, MapIterator, Module, ObjectShape, ObjectShapeRecord, PrimitiveObject, Promise, PropertyDescriptor, PropertyLookupCache, - PropertyOffset, ProtoIntrinsics, Proxy, StringIterator, TemporalInstant, TryResult, + PropertyOffset, ProtoIntrinsics, Proxy, StringIterator, TryResult, ordinary_object_create_with_intrinsics, }, engine::{Bindable, GcScope, HeapRootData, NoGcScope, bindable_handle}, @@ -1425,6 +1425,10 @@ impl From> for HeapRootData { Object::Array(d) => Self::Array(d.unbind()), #[cfg(feature = "date")] Object::Date(d) => Self::Date(d.unbind()), + #[cfg(feature = "temporal")] + Object::Duration(d) => Self::Duration(d.unbind()), + #[cfg(feature = "temporal")] + Object::Instant(d) => Self::Instant(d.unbind()), Object::Error(d) => Self::Error(d.unbind()), Object::FinalizationRegistry(d) => Self::FinalizationRegistry(d.unbind()), Object::Map(d) => Self::Map(d.unbind()), diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index f2adba198..68dca3b28 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -283,7 +283,7 @@ 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())); + value_discriminant(Value::Duration(TemporalDuration::_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 f27850c54..1831fa616 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -24,12 +24,12 @@ use crate::ecmascript::{ UINT_16_ARRAY_DISCRIMINANT, UINT_32_ARRAY_DISCRIMINANT, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{DURATION_DISCRIMINANT, TemporalDuration}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{FLOAT_16_ARRAY_DISCRIMINANT, Float16Array}; #[cfg(feature = "temporal")] -use crate::ecmascript::{ - INSTANT_DISCRIMINANT, PLAIN_TIME_DISCRIMINANT, TemporalInstant, TemporalPlainTime, -}; +use crate::ecmascript::{INSTANT_DISCRIMINANT, TemporalInstant}; #[cfg(feature = "regexp")] use crate::ecmascript::{ REGEXP_DISCRIMINANT, REGEXP_STRING_ITERATOR_DISCRIMINANT, RegExp, RegExpStringIterator, @@ -99,7 +99,7 @@ pub(crate) mod private { #[cfg(feature = "set")] use crate::ecmascript::{Set, SetIterator}; #[cfg(feature = "temporal")] - use crate::ecmascript::{TemporalInstant, TemporalPlainTime}; + use crate::ecmascript::{TemporalDuration, TemporalInstant}; #[cfg(feature = "weak-refs")] use crate::ecmascript::{WeakKey, WeakMap, WeakRef, WeakSet}; use crate::{ diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 3b2b1ffdd..bddc1126c 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -27,7 +27,7 @@ use crate::ecmascript::{ VoidArray, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{DurationHeapData, InstantRecord, PlainTimeHeapData}; +use crate::ecmascript::{DurationHeapData, InstantRecord}; #[cfg(feature = "regexp")] use crate::ecmascript::{RegExpHeapData, RegExpStringIteratorRecord}; #[cfg(feature = "set")] From 7baacdb4b15b06e160943ddcc3d7622579eef567 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Mon, 9 Feb 2026 14:42:23 +0100 Subject: [PATCH 54/55] Fix comments in PR or make todos for stuff we dont understand --- nova_vm/Cargo.toml | 1 + .../abstract_operations/type_conversion.rs | 1 + nova_vm/src/ecmascript/builtins/temporal.rs | 44 +++++++++---------- .../temporal/duration/duration_constructor.rs | 8 +++- .../src/ecmascript/builtins/temporal/error.rs | 6 ++- .../ecmascript/builtins/temporal/instant.rs | 12 ++++- .../temporal/instant/instant_constructor.rs | 5 +++ .../temporal/instant/instant_prototype.rs | 12 ++--- .../ecmascript/builtins/temporal/options.rs | 2 + nova_vm/src/heap.rs | 6 +-- 10 files changed, 61 insertions(+), 36 deletions(-) diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 18669d576..9dbbd900a 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -71,6 +71,7 @@ shared-array-buffer = ["array-buffer", "ecmascript_atomics"] weak-refs = [] set = [] typescript = [] +# TODO(jesper): what does doubled feature mean here? temporal = ["temporal_rs"] # Enables features defined by [Annex B](https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers) diff --git a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs index efc39ae33..97d91e629 100644 --- a/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs +++ b/nova_vm/src/ecmascript/abstract_operations/type_conversion.rs @@ -1559,6 +1559,7 @@ pub(crate) fn try_to_index<'a>( /// 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. +#[cfg(feature = "array-buffer")] pub(crate) fn to_integer_if_integral<'gc>( agent: &mut Agent, argument: Value, diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 972793beb..f49af3d2b 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -19,7 +19,7 @@ use temporal_rs::{ use crate::{ ecmascript::{ - Agent, BUILTIN_STRING_MEMORY, ExceptionType, JsResult, Object, Realm, Value, + Agent, BUILTIN_STRING_MEMORY, ExceptionType, JsResult, Number, Object, Realm, Value, builders::OrdinaryObjectBuilder, get, }, engine::{Bindable, GcScope, NoGcScope, Scopable, trivially_bindable}, @@ -111,6 +111,7 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( if digits_value.is_undefined() { return Ok(temporal_rs::parsers::Precision::Auto); } + // If already a valid single digit in the range 0-9, return early. if let Value::Integer(digits_value) = digits_value && (0..=9).contains(&digits_value.into_i64()) { @@ -119,8 +120,7 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( )); } // 3. If digitsValue is not a Number, then - if !digits_value.is_number() { - let scoped_digits_value = digits_value.scope(agent, gc.nogc()); + let Ok(digits_number) = Number::try_from(digits_value) else { // a. If ? ToString(digitsValue) is not "auto", throw a RangeError exception. if digits_value .unbind() @@ -129,16 +129,21 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( .as_bytes(agent) != b"auto" { - // b. Return auto. - return Ok(temporal_rs::parsers::Precision::Auto); + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + // TODO(jesper): is the message correct? + "fractionalSecondDigits must be a finite number or \"auto\"", + gc.into_nogc(), + )); } - // SAFETY: not shared. - digits_value = unsafe { scoped_digits_value.take(agent) }.bind(gc.nogc()); - } + // b. Return auto. + return Ok(temporal_rs::parsers::Precision::Auto); + }; + // 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) + if digits_number.is_nan(agent) + || digits_number.is_pos_infinity(agent) + || digits_number.is_neg_infinity(agent) { return Err(agent.throw_exception_with_static_message( ExceptionType::RangeError, @@ -147,12 +152,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()); - let digit_count = digit_count.into_f64(agent).floor(); + let digit_count = digits_number.into_f64(agent).floor(); // 6. If digitCount < 0 or digitCount > 9, throw a RangeError exception. if digit_count < 0.0 || digit_count > 9.0 { return Err(agent.throw_exception_with_static_message( @@ -175,18 +175,13 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( /// 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) + options: Object, // options (an Object) _unit_group: UnitGroup, // unitGroup (date, time, or datetime) - _disallowed_units: Vec, // disallowedUnits (todo:a List of Temporal units) + _disallowed_units: &[Unit], // 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). @@ -221,6 +216,7 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>( ) .unbind()? .bind(gc.nogc()); + // TODO(jesper): good starting point adding these? // 6. Perform ? ValidateTemporalUnitValue(largestUnit, unitGroup, « auto »). // 7. If largestUnit is unset, then // a. Set largestUnit to auto. 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 489370510..8e2cf86e5 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -48,6 +48,12 @@ impl TemporalDurationConstructor { gc.into_nogc(), )); }; + // TODO(jesper): what does this mean? + // note: Here you do not want to unbind new_target before calling try_from: if left bound then the + // Ok(new_target) would still be bound to the GC lifetime and would be safe. If you didn't scope the + // result into a variable shadowing this name, it'd be possible for someone to accidentally use + // new_target after free. + let Ok(new_target) = Function::try_from(new_target.unbind()) else { unreachable!() }; @@ -156,7 +162,7 @@ impl TemporalDurationConstructor { .map_err(|e| temporal_err_to_js_err(agent, e, gc.nogc())) .unbind()? .bind(gc.nogc()); - create_temporal_duration(agent, duration.unbind(), Some(new_target.get(agent)), gc) + create_temporal_duration(agent, duration, Some(new_target.get(agent)), gc) .map(|duration| duration.into()) } diff --git a/nova_vm/src/ecmascript/builtins/temporal/error.rs b/nova_vm/src/ecmascript/builtins/temporal/error.rs index 48786dc47..6147469c1 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/error.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/error.rs @@ -1,10 +1,14 @@ +// 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::{Agent, ExceptionType, JsError}, engine::NoGcScope, }; use temporal_rs::{TemporalError, error::ErrorKind}; -pub fn temporal_err_to_js_err<'gc>( +pub(crate) fn temporal_err_to_js_err<'gc>( agent: &mut Agent, error: TemporalError, gc: NoGcScope<'gc, '_>, diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index cf19c3e5a..c2cd691d8 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -207,6 +207,14 @@ fn add_duration_to_instant<'gc, const IS_ADD: bool>( let instant = instant.bind(gc.nogc()); // 1. Let duration be ? ToTemporalDuration(temporalDurationLike). let instant = instant.scope(agent, gc.nogc()); + // TODO(jesper): added ? here and removed the unwraps, but got gc lifetime error + // TODO(jesper): how to check if already Duration? no need to copy + // let duration = if let Value::Duration(duration) = duration { + // duration.unbind().get(agent).duration + // } else { + // to_temporal_duration(agent, duration.unbind(), gc.reborrow())? + // }; + 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). @@ -260,7 +268,7 @@ fn difference_temporal_instant<'gc, const IS_UNTIL: bool>( agent, resolved_options.unbind(), UnitGroup::Time, - vec![], + &vec![], Unit::Nanosecond, Unit::Second, gc.reborrow(), @@ -275,7 +283,7 @@ fn difference_temporal_instant<'gc, const IS_UNTIL: bool>( agent, resolved_options.unbind(), UnitGroup::Time, - vec![], + &vec![], Unit::Nanosecond, Unit::Second, gc.reborrow(), 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 9ad258203..fa36f77cd 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.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::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, BigInt, Builtin, @@ -203,6 +207,7 @@ impl TemporalInstantConstructor { let one = args.get(0).bind(gc.nogc()); let two = args.get(1).bind(gc.nogc()); let two = two.scope(agent, gc.nogc()); + // TODO(jesper): how to check if already instant and compare for early return? // 1. Set one to ? ToTemporalInstant(one). let one_instant = to_temporal_instant(agent, one.unbind(), gc.reborrow()).unbind()?; // 2. Set two to ? ToTemporalInstant(two). 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 64ca4e51a..b2ea971eb 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -240,7 +240,7 @@ impl TemporalInstantPrototype { 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; + 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()? @@ -274,7 +274,7 @@ impl TemporalInstantPrototype { .scope(agent, gc.nogc()); // 3. If roundTo is undefined, then - if round_to.unbind().is_undefined() { + if round_to.is_undefined() { // a. Throw a TypeError exception. return Err(agent.throw_exception_with_static_message( ExceptionType::TypeError, @@ -284,7 +284,7 @@ impl TemporalInstantPrototype { } // 4. If roundTo is a String, then - let round_to = if round_to.unbind().is_string() { + let round_to = if round_to.is_string() { // a. Let paramString be roundTo. let param_string = round_to; // b. Set roundTo to OrdinaryObjectCreate(null). @@ -301,7 +301,7 @@ impl TemporalInstantPrototype { round_to.into() } else { // 5. Else, set roundTo to ? GetOptionsObject(roundTo). - get_options_object(agent, round_to.unbind(), gc.nogc()) + get_options_object(agent, round_to, gc.nogc()) .unbind()? .bind(gc.nogc()) }; @@ -366,7 +366,9 @@ impl TemporalInstantPrototype { .unbind()? .bind(gc.nogc()); // 19. Return ! CreateTemporalInstant(roundedNs). - Ok(create_temporal_instant(agent, rounded_ns, None, gc)?.into()) + Ok(create_temporal_instant(agent, rounded_ns, None, gc) + .unwrap() + .into()) } /// ### [8.3.10 Temporal.Instant.prototype.equals ( other )](https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals) diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs index 061dd7cda..bfeede00d 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/options.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -187,6 +187,8 @@ pub(crate) fn get_rounding_increment_option<'gc>( .unbind()? .bind(gc.nogc()); + // TODO(jesper): https://github.com/trynova/nova/pull/876#discussion_r2611571860 + // 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception. if !(1.0..=1_000_000_000.0).contains(&integer_increment) { return Err(agent.throw_exception_with_static_message( diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index bddc1126c..38d593e25 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -228,11 +228,11 @@ impl Heap { caches: Caches::with_capacity(1024), #[cfg(feature = "date")] dates: Vec::with_capacity(1024), - // TODO: assign appropriate value for Temporal objects. + // TODO(jesper): why 0? #[cfg(feature = "temporal")] - instants: Vec::with_capacity(1024), + instants: Vec::with_capacity(0), #[cfg(feature = "temporal")] - durations: Vec::with_capacity(1024), + durations: Vec::with_capacity(0), ecmascript_functions: Vec::with_capacity(1024), elements: ElementArrays { e2pow1: ElementArray2Pow1::with_capacity(1024), From 2d8a4c40d8412208dfb0a5276a8da95af797bc27 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Tue, 10 Feb 2026 15:46:22 +0100 Subject: [PATCH 55/55] Fix odos --- nova_vm/Cargo.toml | 1 - nova_vm/src/ecmascript/builtins/temporal.rs | 3 +- .../temporal/duration/duration_constructor.rs | 7 +--- .../ecmascript/builtins/temporal/instant.rs | 33 +++++++++++-------- .../temporal/instant/instant_constructor.rs | 31 ++++++++++------- .../temporal/instant/instant_prototype.rs | 4 +-- .../ecmascript/builtins/temporal/options.rs | 4 +-- nova_vm/src/heap.rs | 1 - 8 files changed, 44 insertions(+), 40 deletions(-) diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 9dbbd900a..18669d576 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -71,7 +71,6 @@ shared-array-buffer = ["array-buffer", "ecmascript_atomics"] weak-refs = [] set = [] typescript = [] -# TODO(jesper): what does doubled feature mean here? temporal = ["temporal_rs"] # Enables features defined by [Annex B](https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index f49af3d2b..df3397f13 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -131,7 +131,6 @@ pub(crate) fn get_temporal_fractional_second_digits_option<'gc>( { return Err(agent.throw_exception_with_static_message( ExceptionType::RangeError, - // TODO(jesper): is the message correct? "fractionalSecondDigits must be a finite number or \"auto\"", gc.into_nogc(), )); @@ -216,7 +215,7 @@ pub(crate) fn get_difference_settings<'gc, const IS_UNTIL: bool>( ) .unbind()? .bind(gc.nogc()); - // TODO(jesper): good starting point adding these? + // TODO(jesper): good starting point! check all data and if the values make sense :) // 6. Perform ? ValidateTemporalUnitValue(largestUnit, unitGroup, « auto »). // 7. If largestUnit is unset, then // a. Set largestUnit to auto. 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 8e2cf86e5..980476bdd 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/duration/duration_constructor.rs @@ -48,13 +48,8 @@ impl TemporalDurationConstructor { gc.into_nogc(), )); }; - // TODO(jesper): what does this mean? - // note: Here you do not want to unbind new_target before calling try_from: if left bound then the - // Ok(new_target) would still be bound to the GC lifetime and would be safe. If you didn't scope the - // result into a variable shadowing this name, it'd be possible for someone to accidentally use - // new_target after free. - let Ok(new_target) = Function::try_from(new_target.unbind()) else { + let Ok(new_target) = Function::try_from(new_target) else { unreachable!() }; let new_target = new_target.scope(agent, gc.nogc()); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index c2cd691d8..bf9711177 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -204,29 +204,34 @@ fn add_duration_to_instant<'gc, const IS_ADD: bool>( mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { let duration = duration.bind(gc.nogc()); - let instant = instant.bind(gc.nogc()); + let mut instant = instant.bind(gc.nogc()); // 1. Let duration be ? ToTemporalDuration(temporalDurationLike). - let instant = instant.scope(agent, gc.nogc()); - // TODO(jesper): added ? here and removed the unwraps, but got gc lifetime error - // TODO(jesper): how to check if already Duration? no need to copy - // let duration = if let Value::Duration(duration) = duration { - // duration.unbind().get(agent).duration - // } else { - // to_temporal_duration(agent, duration.unbind(), gc.reborrow())? - // }; - let duration = to_temporal_duration(agent, duration.unbind(), gc.reborrow()); + let duration = if let Value::Duration(duration) = duration { + duration.unbind().get(agent).duration + } else { + let scoped_instant = instant.scope(agent, gc.nogc()); + let res = to_temporal_duration(agent, duration.unbind(), gc.reborrow()).unbind()?; + // SAFETY: not shared + unsafe { + instant = scoped_instant.take(agent); + } + res + }; + // 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(instant.get(agent).inner_instant(agent), &duration.unwrap()) - .unwrap() + temporal_rs::Instant::add(instant.inner_instant(agent), &duration) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc())) + .unbind()? } else { - temporal_rs::Instant::subtract(instant.get(agent).inner_instant(agent), &duration.unwrap()) - .unwrap() + temporal_rs::Instant::subtract(instant.inner_instant(agent), &duration) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc())) + .unbind()? }; // 7. Return ! CreateTemporalInstant(ns). let instant = create_temporal_instant(agent, ns_result, None, 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 index fa36f77cd..3748a8f34 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -5,9 +5,9 @@ use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, BigInt, Builtin, - BuiltinIntrinsicConstructor, ExceptionType, Function, InstantRecord, JsResult, Object, - Realm, String, Value, builders::BuiltinFunctionBuilder, create_temporal_instant, - to_big_int, to_temporal_instant, + BuiltinIntrinsicConstructor, ExceptionType, Function, InstantRecord, InternalMethods, + JsResult, Object, Realm, String, Value, builders::BuiltinFunctionBuilder, + create_temporal_instant, to_big_int, to_temporal_instant, }, engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{CreateHeapData, IntrinsicConstructorIndexes}, @@ -206,15 +206,24 @@ impl TemporalInstantConstructor { ) -> 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()); - // TODO(jesper): how to check if already instant and compare for early return? - // 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()?; + + let res = if let (Value::Instant(one), Value::Instant(two)) = (one, two) { + one.inner_instant(agent).cmp(two.inner_instant(agent)) + } else { + // TODO(jesper): in the case of one being an instant and not the other, only create one temporal_rs::Instant + 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()?; + + one_instant.cmp(&two_instant) + }; + // 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])). - Ok((one_instant.cmp(&two_instant) as i8).into()) + Ok((res as i8).into()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { 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 b2ea971eb..ebfffff57 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_prototype.rs @@ -640,7 +640,7 @@ pub(crate) fn to_integer_with_truncation<'gc>( agent: &mut Agent, argument: Value, mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, f64> { +) -> JsResult<'gc, i64> { let argument = argument.bind(gc.nogc()); // 1. Let number be ? ToNumber(argument). let number = to_number(agent, argument.unbind(), gc.reborrow()) @@ -657,7 +657,7 @@ pub(crate) fn to_integer_with_truncation<'gc>( } // 3. Return truncate(ℝ(number)). - Ok(number.into_f64(agent).trunc()) + Ok(number.into_f64(agent).trunc() as i64) } /// ### [13.17 GetTemporalUnitValuedOption ( options, key, default )] (https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunitvaluedoption) diff --git a/nova_vm/src/ecmascript/builtins/temporal/options.rs b/nova_vm/src/ecmascript/builtins/temporal/options.rs index bfeede00d..a149eedf3 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/options.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/options.rs @@ -187,10 +187,8 @@ pub(crate) fn get_rounding_increment_option<'gc>( .unbind()? .bind(gc.nogc()); - // TODO(jesper): https://github.com/trynova/nova/pull/876#discussion_r2611571860 - // 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception. - if !(1.0..=1_000_000_000.0).contains(&integer_increment) { + if !(1..=1_000_000_000).contains(&integer_increment) { return Err(agent.throw_exception_with_static_message( ExceptionType::RangeError, "roundingIncrement must be between 1 and 10**9", diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 38d593e25..6bd8c7275 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -228,7 +228,6 @@ impl Heap { caches: Caches::with_capacity(1024), #[cfg(feature = "date")] dates: Vec::with_capacity(1024), - // TODO(jesper): why 0? #[cfg(feature = "temporal")] instants: Vec::with_capacity(0), #[cfg(feature = "temporal")]