From f99728e9110d2aa95378580be615b3f06a725d7d Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Tue, 30 Sep 2025 16:13:51 +0200 Subject: [PATCH 01/25] added temporal intrinsic --- Cargo.toml | 1 + nova_vm/Cargo.toml | 6 + nova_vm/src/builtin_strings | 1 + nova_vm/src/ecmascript/builtins.rs | 2 + nova_vm/src/ecmascript/builtins/temporal.rs | 105 ++++++++++++++++++ .../ecmascript/execution/realm/intrinsics.rs | 12 ++ nova_vm/src/heap/heap_constants.rs | 3 +- 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal.rs diff --git a/Cargo.toml b/Cargo.toml index f81f36f26..2a0c67584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ sonic-rs = "0.3.17" unicode-normalization = "0.1.24" usdt = { git = "https://github.com/aapoalas/usdt.git", branch = "nova-aarch64-branch" } wtf8 = "0.1" +temporal_rs = "0.0.16" [workspace.metadata.dylint] libraries = [{ path = "nova_lint" }] diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 72b70e451..84f893b83 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -38,6 +38,7 @@ sonic-rs = { workspace = true, optional = true } unicode-normalization = { workspace = true } usdt = { workspace = true } wtf8 = { workspace = true } +temporal_rs = { workspace = true, optional = true } [features] default = [ @@ -51,6 +52,7 @@ default = [ "regexp", "set", "annex-b", + "temporal", ] array-buffer = [] atomics = ["array-buffer", "shared-array-buffer"] @@ -62,6 +64,7 @@ shared-array-buffer = ["array-buffer"] 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"] @@ -83,6 +86,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"] @@ -94,6 +98,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 e6c73e32e..4f02b8ced 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -419,6 +419,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 3070296ca..3ac4deb9d 100644 --- a/nova_vm/src/ecmascript/builtins.rs +++ b/nova_vm/src/ecmascript/builtins.rs @@ -66,6 +66,8 @@ pub(crate) mod set; #[cfg(feature = "shared-array-buffer")] pub(crate) mod shared_array_buffer; pub(crate) mod structured_data; +#[cfg(feature = "temporal")] +pub mod temporal; pub(crate) mod text_processing; #[cfg(feature = "array-buffer")] pub(crate) mod typed_array; diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs new file mode 100644 index 000000000..d1655d19b --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -0,0 +1,105 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + + +use temporal_rs::{Instant, Duration, /* etc */}; + +use core::f64::consts; +use std::thread::Builder; + +use crate::{ + ecmascript::{ + abstract_operations::type_conversion::{to_big_int, to_number, to_number_primitive, to_uint32}, + builders::{self, ordinary_object_builder::OrdinaryObjectBuilder}, + builtins::{ArgumentsList, Behaviour, Builtin}, + execution::{agent, Agent, JsResult, Realm}, + types::{IntoValue, Number, Primitive, String, Value, BUILTIN_STRING_MEMORY}, + }, + engine::{ + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, + }, + heap::WellKnownSymbolIndexes, +}; + + +pub(crate) struct TemporalObject; + + +impl TemporalObject { + pub fn create_intrinsic( + agent: &mut Agent, + realm: Realm<'static>, + gc: NoGcScope, + ) { + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let object_prototype = intrinsics.object_prototype(); + let this = intrinsics.temporal(); + + let builders = OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) + .with_property_capacity(1) + .with_prototype(object_prototype) + .with_property(|builder| { + builder + .with_key(WellKnownSymbolIndexes::ToStringTag.into()) + .with_value_readonly(BUILTIN_STRING_MEMORY.Temporal.into()) + .with_enumerable(false) + .with_configurable(true) + .build() + }) + .build(); + } +} + + + +/* +struct TemporalPlainDateTime; +impl Builtin for TemporalPlainDateTime { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainDateTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainDateTime); +} + +struct TemporalPlainDate; +impl Builtin for TemporalPlainDate { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainDate; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainDate); +} + +struct TemporalPlainTime; +impl Builtin for TemporalPlainTime { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainTime); +} + +struct TemporalPlainYearMonth; +impl Builtin for TemporalPlainYearMonth { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainYearMonth; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainYearMonth); +} + +struct TemporalPlainMonthDay; +impl Builtin for TemporalPlainMonthDay { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.PlainMonthDay; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::PlainMonthDay); +} + +struct TemporalDuration; +impl Builtin for TemporalDuration { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.Duration; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::Duration); +} + +struct TemporalZonedDateTime; +impl Builtin for TemporalZonedDateTime { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.ZonedDateTime; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalObject::ZonedDateTime); +}*/ \ No newline at end of file diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 21509a668..530f52449 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -39,6 +39,8 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ shared_array_buffer_constructor::SharedArrayBufferConstructor, shared_array_buffer_prototype::SharedArrayBufferPrototype, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::TemporalObject; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ regexp_constructor::RegExpConstructor, regexp_prototype::RegExpPrototype, @@ -307,6 +309,11 @@ impl Intrinsics { BigIntConstructor::create_intrinsic(agent, realm); #[cfg(feature = "math")] MathObject::create_intrinsic(agent, realm, gc); + + #[cfg(feature = "temporal")] + TemporalObject::create_intrinsic(agent, realm, gc); + + #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); #[cfg(feature = "date")] @@ -1011,6 +1018,11 @@ impl Intrinsics { IntrinsicObjectIndexes::MathObject.get_backing_object(self.object_index_base) } + /// %Temporal% + pub(crate) const fn temporal(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) + } + /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { IntrinsicPrimitiveObjectIndexes::NumberPrototype diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index 0afac0dfa..c76823a05 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -34,7 +34,8 @@ pub(crate) enum IntrinsicObjectIndexes { MathObject, #[cfg(feature = "date")] DatePrototype, - + #[cfg(feature = "temporal")] + TemporalObject, // Text processing #[cfg(feature = "regexp")] RegExpPrototype, From f1e4320ed0c674a53ef0ff6087264884f6e5aa1e Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Wed, 8 Oct 2025 17:12:27 +0200 Subject: [PATCH 02/25] 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 | 47 +++--- nova_vm/src/heap/heap_constants.rs | 6 + 5 files changed, 186 insertions(+), 82 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 4f02b8ced..96b6b8a11 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -248,6 +248,7 @@ isSealed #[cfg(feature = "array-buffer")]isView isWellFormed #[cfg(feature = "annex-b-string")]italics +#[cfg(feature = "temporal")]Instant Iterator iterator join @@ -317,6 +318,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 530f52449..40af86f3c 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -40,7 +40,9 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ shared_array_buffer_prototype::SharedArrayBufferPrototype, }; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::TemporalObject; +use crate::ecmascript::builtins::temporal::{ + instant::InstantConstructor, instant::InstantPrototype, TemporalObject +}; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ regexp_constructor::RegExpConstructor, regexp_prototype::RegExpPrototype, @@ -65,7 +67,6 @@ use crate::ecmascript::builtins::{ use crate::{ ecmascript::{ builtins::{ - Array, BuiltinFunction, control_abstraction_objects::{ async_function_objects::{ async_function_constructor::AsyncFunctionConstructor, @@ -88,31 +89,22 @@ use crate::{ promise_objects::{ promise_constructor::PromiseConstructor, promise_prototype::PromisePrototype, }, - }, - global_object::GlobalObject, - indexed_collections::array_objects::{ + }, global_object::GlobalObject, indexed_collections::array_objects::{ array_constructor::ArrayConstructor, array_iterator_objects::array_iterator_prototype::ArrayIteratorPrototype, array_prototype::ArrayPrototype, - }, - iteration::iterator_constructor::IteratorConstructor, - keyed_collections::map_objects::{ + }, iteration::iterator_constructor::IteratorConstructor, keyed_collections::map_objects::{ map_constructor::MapConstructor, map_iterator_objects::map_iterator_prototype::MapIteratorPrototype, map_prototype::MapPrototype, - }, - managing_memory::finalization_registry_objects::{ + }, managing_memory::finalization_registry_objects::{ finalization_registry_constructor::FinalizationRegistryConstructor, finalization_registry_prototype::FinalizationRegistryPrototype, - }, - ordinary::shape::ObjectShape, - primitive_objects::{PrimitiveObject, PrimitiveObjectHeapData}, - reflection::{proxy_constructor::ProxyConstructor, reflect_object::ReflectObject}, - text_processing::string_objects::{ + }, ordinary::shape::ObjectShape, primitive_objects::{PrimitiveObject, PrimitiveObjectHeapData}, reflection::{proxy_constructor::ProxyConstructor, reflect_object::ReflectObject}, text_processing::string_objects::{ string_constructor::StringConstructor, string_iterator_objects::StringIteratorPrototype, string_prototype::StringPrototype, - }, + }, Array, BuiltinFunction }, execution::Agent, fundamental_objects::{ @@ -148,10 +140,7 @@ use crate::{ }, engine::context::NoGcScope, heap::{ - CompactionLists, HeapMarkAndSweep, IntrinsicConstructorIndexes, IntrinsicFunctionIndexes, - IntrinsicObjectIndexes, IntrinsicObjectShapes, IntrinsicPrimitiveObjectIndexes, WorkQueues, - indexes::BaseIndex, intrinsic_function_count, intrinsic_object_count, - intrinsic_primitive_object_count, + indexes::BaseIndex, intrinsic_function_count, intrinsic_object_count, intrinsic_primitive_object_count, CompactionLists, HeapMarkAndSweep, IntrinsicConstructorIndexes, IntrinsicFunctionIndexes, IntrinsicObjectIndexes, IntrinsicObjectShapes, IntrinsicPrimitiveObjectIndexes, WorkQueues }, }; #[derive(Debug, Clone)] @@ -310,9 +299,12 @@ impl Intrinsics { #[cfg(feature = "math")] MathObject::create_intrinsic(agent, realm, gc); - #[cfg(feature = "temporal")] - TemporalObject::create_intrinsic(agent, realm, gc); - + #[cfg(feature = "temporal")] { + TemporalObject::create_intrinsic(agent, realm, gc); + // Instant + InstantConstructor::create_intrinsic(agent, realm, gc); + InstantPrototype::create_intrinsic(agent, realm, gc); + } #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); @@ -1023,6 +1015,15 @@ impl Intrinsics { IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) } + /// %Temporal.Instant% + pub(crate) const fn temporal_instant(&self) -> BuiltinFunction<'static> { + IntrinsicConstructorIndexes::TemporalInstant.get_builtin_function(self.builtin_function_index_base) + } + /// %Temporal.Instant.Prototype% + pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) + } + /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { IntrinsicPrimitiveObjectIndexes::NumberPrototype diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index c76823a05..7a44b1afb 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -36,6 +36,10 @@ pub(crate) enum IntrinsicObjectIndexes { DatePrototype, #[cfg(feature = "temporal")] TemporalObject, + #[cfg(feature = "temporal")] + TemporalInstant, + #[cfg(feature = "temporal")] + TemporalInstantPrototype, // Text processing #[cfg(feature = "regexp")] RegExpPrototype, @@ -172,6 +176,8 @@ pub(crate) enum IntrinsicConstructorIndexes { BigInt, #[cfg(feature = "date")] Date, + #[cfg(feature = "temporal")] + TemporalInstant, // Text processing String, From d48454565336e36bfde911c8825ca791983ea559 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 16 Oct 2025 16:43:26 +0200 Subject: [PATCH 03/25] added engine and heap backing to %temporal.instant% --- Cargo.toml | 2 +- .../ecmascript/builtins/temporal/instant.rs | 75 +++++++++++++++---- nova_vm/src/ecmascript/execution/weak_key.rs | 14 ++++ .../src/ecmascript/types/language/object.rs | 9 +++ .../src/ecmascript/types/language/value.rs | 13 ++++ nova_vm/src/engine/bytecode/vm.rs | 2 + nova_vm/src/engine/rootable.rs | 10 +++ nova_vm/src/heap.rs | 6 ++ nova_vm/src/heap/heap_bits.rs | 19 +++++ nova_vm/src/heap/heap_gc.rs | 28 +++++++ 10 files changed, 163 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a0c67584..b1dd1eecc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ sonic-rs = "0.3.17" unicode-normalization = "0.1.24" usdt = { git = "https://github.com/aapoalas/usdt.git", branch = "nova-aarch64-branch" } wtf8 = "0.1" -temporal_rs = "0.0.16" +temporal_rs = "0.1.0" [workspace.metadata.dylint] libraries = [{ path = "nova_lint" }] diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index d729b446f..0419de163 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -1,16 +1,18 @@ +use core::ops::{Index, IndexMut}; + use crate::{ ecmascript::{ builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, builtins::{ ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor }, - execution::{agent::{Agent}, JsResult, Realm}, + execution::{agent::Agent, JsResult, Realm}, types::{ - InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY + InternalMethods, InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY }, }, - engine::context::{bindable_handle, GcScope, NoGcScope}, - heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, IntrinsicConstructorIndexes, WorkQueues}, + engine::{context::{bindable_handle, GcScope, NoGcScope}, rootable::{HeapRootRef, Rootable}}, + heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues}, }; /// Constructor function object for %Temporal.Instant%. pub(crate) struct InstantConstructor; @@ -56,7 +58,7 @@ impl InstantPrototype { .build(); } } -/// HEAP DATA +/// HEAP DATA -- Move to internal instant/data.rs #[derive(Debug, Clone, Copy)] pub(crate) struct InstantValue(/*TODO:BigInt*/); @@ -84,33 +86,48 @@ impl HeapMarkAndSweep for InstantHeapData<'static> { } } -// HANDLES +// HANDLES -- Keep public facing within instant.rs #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl Instant<'_> { //TODO pub(crate) const fn _def() -> Self { - todo!() + Instant(BaseIndex::from_u32_index(0)) + } + + pub(crate) const fn get_index(self) -> usize { + self.0.into_index() } } bindable_handle!(Instant); impl<'a> From> for Value<'a> { - fn from(v: Instant<'a>) -> Self { todo!() } + fn from(value: Instant<'a>) -> Self { + Value::Instant(value) // todo: add to value.rs + } } impl<'a> From> for Object<'a> { - fn from(v: Instant<'a>) -> Self { todo!() } + fn from(value: Instant<'a>) -> Self { + Object::Instant(value) // todo: add to object.rs + } } impl<'a> TryFrom> for Instant<'a> { type Error = (); - fn try_from(v: Value<'a>) -> Result { - todo!() + + fn try_from(value: Value<'a>) -> Result { + match value { + Value::Instant(idx) => Ok(idx), // todo: add to value.rs + _ => Err(()), + } } } impl<'a> TryFrom> for Instant<'a> { type Error = (); - fn try_from(o: Object<'a>) -> Result { - todo!() + fn try_from(object: Object<'a>) -> Result { + match object { + Object::Instant(idx) => Ok(idx), // todo: add to object.rs + _ => Err(()), + } } } @@ -127,6 +144,8 @@ impl<'a> InternalSlots<'a> for Instant<'a> { } +impl<'a> InternalMethods<'a> for Instant<'a> {} + impl HeapMarkAndSweep for Instant<'static> { fn mark_values(&self, queues: &mut WorkQueues) { todo!() @@ -136,8 +155,36 @@ impl HeapMarkAndSweep for Instant<'static> { } } +impl HeapSweepWeakReference for Instant<'static> { + fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { + compactions.dates.shift_weak_index(self.0).map(Self) + } +} + impl<'a> CreateHeapData, Instant<'a>> for Heap { fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { todo!() } -} \ No newline at end of file +} + +/* todo - impl keep public facing in temporal/instant.rs +impl Rootable for Instant<'_> { + type RootRepr = HeapRootRef; + + fn to_root_repr(value: Self) -> Result { + todo!() + } + + fn from_root_repr(value: &Self::RootRepr) -> Result { + todo!() + } + + fn from_heap_ref(heap_ref: crate::engine::rootable::HeapRootRef) -> Self::RootRepr { + todo!() + } + + fn from_heap_data(heap_data: crate::engine::rootable::HeapRootData) -> Option { + todo!() + } +} +*/ \ No newline at end of file diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index a9863df29..1bc2922a7 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -141,6 +141,8 @@ pub(crate) enum WeakKey<'a> { Array(Array<'a>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, + #[cfg(feature = "temporal")] + Instant(Instant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -253,6 +255,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), @@ -360,6 +364,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), @@ -471,6 +477,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)), @@ -613,6 +621,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), @@ -718,6 +728,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), @@ -839,6 +851,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 eab226908..4d05f0c2d 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -179,6 +179,7 @@ pub enum Object<'a> { Array(Array<'a>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, + Instant(Instant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, @@ -775,6 +776,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), @@ -883,6 +886,8 @@ impl<'a> TryFrom> for Object<'a> { Value::Array(x) => Ok(Self::from(x)), #[cfg(feature = "date")] Value::Date(x) => Ok(Self::Date(x)), + #[cfg(feature = "temporal")] + Value::Instant(x) => Ok(Self::Instant(x)), Value::Error(x) => Ok(Self::from(x)), Value::BoundFunction(x) => Ok(Self::from(x)), Value::BuiltinFunction(x) => Ok(Self::from(x)), @@ -999,6 +1004,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),+), @@ -1665,6 +1672,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 d9c23833f..48979cb61 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -2,6 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//#[cfg(feature = "temporal")] +//use temporal_rs::Instant as Instant; // shadow regular instant, should probably change name to TemporalInstant + use super::{ BigInt, BigIntHeapData, IntoValue, Number, Numeric, OrdinaryObject, Primitive, String, StringHeapData, Symbol, bigint::HeapBigInt, number::HeapNumber, string::HeapString, @@ -178,6 +181,8 @@ pub enum Value<'a> { Array(Array<'a>), #[cfg(feature = "date")] Date(Date<'a>), + #[cfg(feature = "temporal")] + Instant(Instant<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), Map(Map<'a>), @@ -313,6 +318,8 @@ pub(crate) const OBJECT_DISCRIMINANT: u8 = pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array::_def())); #[cfg(feature = "date")] pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_def())); +#[cfg(feature = "temporal")] +pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(Instant::_def())); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_def())); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_def())); @@ -986,6 +993,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()), @@ -1147,6 +1156,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)) @@ -1563,6 +1574,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 bc90c54b4..67e732860 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1326,6 +1326,8 @@ fn typeof_operator(agent: &Agent, val: Value, gc: NoGcScope) -> String<'static> Value::SharedFloat16Array(_) => BUILTIN_STRING_MEMORY.object, #[cfg(feature = "date")] Value::Date(_) => BUILTIN_STRING_MEMORY.object, + #[cfg(feature = "temporal")] + Value::Instant(_) => BUILTIN_STRING_MEMORY.object, // 13. If val has a [[Call]] internal slot, return "function". Value::BoundFunction(_) | Value::BuiltinFunction(_) | Value::ECMAScriptFunction(_) | Value::BuiltinConstructorFunction(_) | diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index bfafe0c72..b7f06a909 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -229,6 +229,8 @@ pub mod private { impl RootableSealed for BuiltinPromiseFinallyFunction<'_> {} #[cfg(feature = "date")] impl RootableSealed for Date<'_> {} + #[cfg(feature = "temporal")] + impl RootableSealed for Instant<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} impl RootableSealed for Error<'_> {} @@ -543,6 +545,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, @@ -676,6 +680,8 @@ impl From> for HeapRootData { Object::Array(array) => Self::Array(array), #[cfg(feature = "date")] Object::Date(date) => Self::Date(date), + #[cfg(feature = "temporal")] + Object::Instant(instant) => Self::Instant(instant), Object::Error(error) => Self::Error(error), Object::FinalizationRegistry(finalization_registry) => { Self::FinalizationRegistry(finalization_registry) @@ -835,6 +841,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) @@ -984,6 +992,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 fb575d1fc..33a201e27 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -31,6 +31,8 @@ pub(crate) use self::object_entry::{ObjectEntry, ObjectEntryPropertyDescriptor}; use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::data::SharedArrayBufferRecord; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, @@ -141,6 +143,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 @@ -288,6 +292,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 6d0391e6f..1fe0c0a28 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -16,6 +16,8 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::SharedArrayBuffer; #[cfg(feature = "array-buffer")] @@ -94,6 +96,8 @@ pub struct HeapBits { pub data_views: Box<[bool]>, #[cfg(feature = "date")] pub dates: Box<[bool]>, + #[cfg(feature = "temporal")] + pub instants: Box<[bool]>, pub declarative_environments: Box<[bool]>, pub e_2_1: Box<[(bool, u8)]>, pub e_2_2: Box<[(bool, u8)]>, @@ -190,6 +194,8 @@ pub(crate) struct WorkQueues { pub data_views: Vec>, #[cfg(feature = "date")] pub dates: Vec>, + #[cfg(feature = "temporal")] + pub instants: Vec>, pub declarative_environments: Vec>, pub e_2_1: Vec<(ElementIndex<'static>, u32)>, pub e_2_2: Vec<(ElementIndex<'static>, u32)>, @@ -286,6 +292,8 @@ impl HeapBits { let data_views = vec![false; heap.data_views.len()]; #[cfg(feature = "date")] let dates = vec![false; heap.dates.len()]; + #[cfg(feature = "temporal")] + let instants = vec![false; heap.instants.len()]; let declarative_environments = vec![false; heap.environments.declarative.len()]; let e_2_1 = vec![(false, 0u8); heap.elements.e2pow1.values.len()]; let e_2_2 = vec![(false, 0u8); heap.elements.e2pow2.values.len()]; @@ -379,6 +387,8 @@ impl HeapBits { data_views: data_views.into_boxed_slice(), #[cfg(feature = "date")] dates: dates.into_boxed_slice(), + #[cfg(feature = "temporal")] + instants: instants.into_boxed_slice(), declarative_environments: declarative_environments.into_boxed_slice(), e_2_1: e_2_1.into_boxed_slice(), e_2_2: e_2_2.into_boxed_slice(), @@ -478,6 +488,8 @@ impl WorkQueues { 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), @@ -579,6 +591,8 @@ impl WorkQueues { data_views, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, declarative_environments, e_2_1, e_2_2, @@ -698,6 +712,7 @@ impl WorkQueues { && 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() @@ -1053,6 +1068,8 @@ pub(crate) struct CompactionLists { pub data_views: CompactionList, #[cfg(feature = "date")] pub dates: CompactionList, + #[cfg(feature = "temporal")] + pub instants: CompactionList, pub declarative_environments: CompactionList, pub e_2_1: CompactionList, pub e_2_2: CompactionList, @@ -1192,6 +1209,8 @@ impl CompactionLists { source_codes: CompactionList::from_mark_bits(&bits.source_codes), #[cfg(feature = "date")] dates: CompactionList::from_mark_bits(&bits.dates), + #[cfg(feature = "temporal")] + instants: CompactionList::from_mark_bits(&bits.instants), errors: CompactionList::from_mark_bits(&bits.errors), executables: CompactionList::from_mark_bits(&bits.executables), maps: CompactionList::from_mark_bits(&bits.maps), diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index cf8597ccb..6af23a070 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -22,6 +22,8 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::SharedArrayBuffer; #[cfg(feature = "array-buffer")] @@ -127,6 +129,8 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc caches, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, ecmascript_functions, elements, embedder_objects, @@ -580,6 +584,22 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc } }); } + #[cfg(feature = "date")] + { + let mut instant_marks: Box<[Instant]> = queues.instants.drain(..).collect(); + instant_marks.sort(); + instant_marks.iter().for_each(|&idx| { + let index = idx.get_index(); + if let Some(marked) = bits.instants.get_mut(index) { + if *marked { + // Already marked, ignore + return; + } + *marked = true; + instants.get(index).mark_values(&mut queues); + } + }); + } let mut embedder_object_marks: Box<[EmbedderObject]> = queues.embedder_objects.drain(..).collect(); embedder_object_marks.sort(); @@ -1477,6 +1497,8 @@ fn sweep( caches, #[cfg(feature = "date")] dates, + #[cfg(feature = "temporal")] + instants, ecmascript_functions, elements, embedder_objects, @@ -1879,6 +1901,12 @@ fn sweep( sweep_heap_vector_values(dates, &compactions, &bits.dates); }); } + #[cfg(feature = "temporal")] + if !instants.is_empty() { + s.spawn(|| { + sweep_heap_vector_values(instants, &compactions, &bits.instants); + }); + } if !declarative.is_empty() { s.spawn(|| { sweep_heap_vector_values(declarative, &compactions, &bits.declarative_environments); From 21a1b49a9faedfd12bd04219df65415ddea70abe Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Sun, 19 Oct 2025 10:02:45 +0200 Subject: [PATCH 04/25] added data.rs --- nova_vm/src/ecmascript/builtins/ordinary.rs | 13 +- .../ecmascript/builtins/temporal/instant.rs | 124 ++++++++++-------- .../builtins/temporal/instant/data.rs | 28 ++++ .../ecmascript/execution/realm/intrinsics.rs | 4 + nova_vm/src/heap.rs | 2 +- 5 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/instant/data.rs diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 887386123..efe4a498a 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -6,9 +6,7 @@ pub(crate) mod caches; pub mod shape; use std::{ - collections::{TryReserveError, hash_map::Entry}, - ops::ControlFlow, - vec, + collections::{hash_map::Entry, TryReserveError}, ops::ControlFlow, time::Instant, vec }; use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; @@ -30,9 +28,7 @@ use crate::{ }, }, engine::{ - Scoped, - context::{Bindable, GcScope, NoGcScope}, - rootable::Scopable, + context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, Scoped }, heap::element_array::{ElementStorageRef, PropertyStorageRef}, }; @@ -1680,6 +1676,11 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .heap .create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None)) .into_object(), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => agent + .heap + .create(InstantHeapData::default()) + .into_object(), ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 0419de163..19943d368 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -1,17 +1,19 @@ use core::ops::{Index, IndexMut}; +pub(crate) mod data; + use crate::{ ecmascript::{ builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, builtins::{ ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor }, - execution::{agent::Agent, JsResult, Realm}, + execution::{agent::Agent, JsResult, ProtoIntrinsics, Realm}, types::{ InternalMethods, InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY }, }, - engine::{context::{bindable_handle, GcScope, NoGcScope}, rootable::{HeapRootRef, Rootable}}, + engine::{context::{bindable_handle, Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}}, heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues}, }; /// Constructor function object for %Temporal.Instant%. @@ -58,36 +60,11 @@ impl InstantPrototype { .build(); } } -/// HEAP DATA -- Move to internal instant/data.rs -#[derive(Debug, Clone, Copy)] -pub(crate) struct InstantValue(/*TODO:BigInt*/); - -impl InstantValue { - // TODO -} -#[derive(Debug, Clone, Copy)] -pub struct InstantHeapData<'a> { - pub(crate) object_index: Option>, - pub(crate) date: InstantValue, -} - -impl InstantHeapData<'_> { - // TODO -} -bindable_handle!(InstantHeapData); -impl HeapMarkAndSweep for InstantHeapData<'static> { - fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { - todo!() - } - fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { - todo!() - } -} - -// HANDLES -- Keep public facing within instant.rs +use self::data::InstantHeapData; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl Instant<'_> { //TODO @@ -103,12 +80,12 @@ bindable_handle!(Instant); impl<'a> From> for Value<'a> { fn from(value: Instant<'a>) -> Self { - Value::Instant(value) // todo: add to value.rs + Value::Instant(value) } } impl<'a> From> for Object<'a> { fn from(value: Instant<'a>) -> Self { - Object::Instant(value) // todo: add to object.rs + Object::Instant(value) } } impl<'a> TryFrom> for Instant<'a> { @@ -116,7 +93,7 @@ impl<'a> TryFrom> for Instant<'a> { fn try_from(value: Value<'a>) -> Result { match value { - Value::Instant(idx) => Ok(idx), // todo: add to value.rs + Value::Instant(idx) => Ok(idx), _ => Err(()), } } @@ -125,66 +102,103 @@ impl<'a> TryFrom> for Instant<'a> { type Error = (); fn try_from(object: Object<'a>) -> Result { match object { - Object::Instant(idx) => Ok(idx), // todo: add to object.rs + Object::Instant(idx) => Ok(idx), _ => Err(()), } } } -// TODO impl trait bounds properly impl<'a> InternalSlots<'a> for Instant<'a> { - // TODO: Add TemporalInstant to ProtoIntrinsics - //const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; + const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; fn get_backing_object(self, agent: &Agent) -> Option> { - todo!() + agent[self].object_index // not implemented for `agent::Agent` } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { - todo!() + assert!(agent[self].object_index.replace(backing_object).is_none()); // not implemented for `agent::Agent` } } impl<'a> InternalMethods<'a> for Instant<'a> {} -impl HeapMarkAndSweep for Instant<'static> { - fn mark_values(&self, queues: &mut WorkQueues) { - todo!() +impl Index> for Agent { + type Output = InstantHeapData<'static>; + + fn index(&self, index: Instant<'_>) -> &Self::Output { + &self.heap.instants[index] } - fn sweep_values(&mut self, compactions: &CompactionLists) { - todo!() +} + +impl IndexMut> for Agent { + fn index_mut(&mut self, index: Instant) -> &mut Self::Output { + &mut self.heap.instants[index] } } -impl HeapSweepWeakReference for Instant<'static> { - fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { - compactions.dates.shift_weak_index(self.0).map(Self) +impl Index> for Vec>> { + type Output = InstantHeapData<'static>; + + fn index(&self, index: Instant<'_>) -> &Self::Output { + self.get(index.get_index()) + .expect("heap access out of bounds") + .as_ref() + .expect("") } } -impl<'a> CreateHeapData, Instant<'a>> for Heap { - fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { - todo!() +impl IndexMut> for Vec>> { + fn index_mut(&mut self, index: Instant<'_>) -> &mut Self::Output { + self.get_mut(index.get_index()) + .expect("dasdas") + .as_mut() + .expect("") } } -/* todo - impl keep public facing in temporal/instant.rs + impl Rootable for Instant<'_> { type RootRepr = HeapRootRef; fn to_root_repr(value: Self) -> Result { - todo!() + Err(HeapRootData::Instant(value.unbind())) } fn from_root_repr(value: &Self::RootRepr) -> Result { - todo!() + Err(*value) } fn from_heap_ref(heap_ref: crate::engine::rootable::HeapRootRef) -> Self::RootRepr { - todo!() + heap_ref } fn from_heap_data(heap_data: crate::engine::rootable::HeapRootData) -> Option { - todo!() + match heap_data { + HeapRootData::Instant(object) => Some(object), + _ => None, + } + } +} + + +impl HeapMarkAndSweep for Instant<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.instants.push(*self); + } + fn sweep_values(&mut self, compactions: &CompactionLists) { + compactions.instants.shift_index(&mut self.0); + } +} + +impl HeapSweepWeakReference for Instant<'static> { + fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { + compactions.dates.shift_weak_index(self.0).map(Self) + } +} + +impl<'a> CreateHeapData, Instant<'a>> for Heap { + fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { + self.instants.push(Some(data.unbind())); + self.alloc_counter += core::mem::size_of::>>(); + Instant(BaseIndex::last(&self.instants)) } } -*/ \ No newline at end of file diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs new file mode 100644 index 000000000..e4d397ba9 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -0,0 +1,28 @@ + +use crate::{ecmascript::types::{OrdinaryObject,bigint::BigInt}, engine::context::bindable_handle, heap::HeapMarkAndSweep}; + +#[derive(Debug, Clone, Copy)] +pub struct InstantHeapData<'a> { + pub(crate) object_index: Option>, + pub(crate) instant: BigInt<'a>, +} + +impl InstantHeapData<'_> { + pub fn default() -> Self { + Self { + object_index: None, + instant: BigInt::zero(), + } + } +} + +bindable_handle!(InstantHeapData); + +impl HeapMarkAndSweep for InstantHeapData<'static> { + fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { + todo!() + } + fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { + todo!() + } +} diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 40af86f3c..421fcbdca 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -218,6 +218,8 @@ pub enum ProtoIntrinsics { RegExpStringIterator, Symbol, SyntaxError, + #[cfg(feature = "temporal")] + TemporalInstant, TypeError, #[cfg(feature = "array-buffer")] Uint16Array, @@ -415,6 +417,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(), @@ -504,6 +507,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 33a201e27..0fa7092a9 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -32,7 +32,7 @@ use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::data::SharedArrayBufferRecord; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::InstantHeapData; +use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, From f82b591a8ded2e6576d45d2d8b0c425c9be8d5aa Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:24:03 +0300 Subject: [PATCH 05/25] fix errors after rebase --- nova_vm/src/ecmascript/builtins/ordinary.rs | 19 ++++-- .../ecmascript/builtins/temporal/instant.rs | 63 +++++++++++-------- nova_vm/src/ecmascript/execution/weak_key.rs | 2 + .../src/ecmascript/types/language/object.rs | 6 +- .../src/ecmascript/types/language/value.rs | 9 ++- nova_vm/src/engine/rootable.rs | 4 ++ 6 files changed, 67 insertions(+), 36 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index efe4a498a..01036432b 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -6,13 +6,17 @@ pub(crate) mod caches; pub mod shape; use std::{ - collections::{hash_map::Entry, TryReserveError}, ops::ControlFlow, time::Instant, vec + collections::{TryReserveError, hash_map::Entry}, + ops::ControlFlow, + vec, }; use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::{ @@ -28,7 +32,9 @@ use crate::{ }, }, engine::{ - context::{Bindable, GcScope, NoGcScope}, rootable::Scopable, Scoped + Scoped, + context::{Bindable, GcScope, NoGcScope}, + rootable::Scopable, }, heap::element_array::{ElementStorageRef, PropertyStorageRef}, }; @@ -1677,10 +1683,9 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .create(ErrorHeapData::new(ExceptionType::SyntaxError, None, None)) .into_object(), #[cfg(feature = "temporal")] - ProtoIntrinsics::TemporalInstant => agent - .heap - .create(InstantHeapData::default()) - .into_object(), + ProtoIntrinsics::TemporalInstant => { + agent.heap.create(InstantHeapData::default()).into_object() + } ProtoIntrinsics::TypeError => agent .heap .create(ErrorHeapData::new(ExceptionType::TypeError, None, None)) @@ -2067,6 +2072,8 @@ fn get_intrinsic_constructor<'a>( ProtoIntrinsics::WeakRef => Some(intrinsics.weak_ref().into_function()), #[cfg(feature = "weak-refs")] ProtoIntrinsics::WeakSet => Some(intrinsics.weak_set().into_function()), + #[cfg(feature = "temporal")] + ProtoIntrinsics::TemporalInstant => Some(intrinsics.temporal_instant().into_function()), } } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 19943d368..88f3794e2 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -4,17 +4,25 @@ pub(crate) mod data; use crate::{ ecmascript::{ - builders::{builtin_function_builder::BuiltinFunctionBuilder, ordinary_object_builder::OrdinaryObjectBuilder}, - builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor + builders::{ + builtin_function_builder::BuiltinFunctionBuilder, + ordinary_object_builder::OrdinaryObjectBuilder, }, - execution::{agent::Agent, JsResult, ProtoIntrinsics, Realm}, + builtins::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsicConstructor}, + execution::{JsResult, ProtoIntrinsics, Realm, agent::Agent}, types::{ - InternalMethods, InternalSlots, IntoObject, Object, OrdinaryObject, String, Value, BUILTIN_STRING_MEMORY + BUILTIN_STRING_MEMORY, InternalMethods, InternalSlots, IntoObject, Object, + OrdinaryObject, String, Value, }, }, - engine::{context::{bindable_handle, Bindable, GcScope, NoGcScope}, rootable::{HeapRootData, HeapRootRef, Rootable}}, - heap::{indexes::BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicConstructorIndexes, WorkQueues}, + engine::{ + context::{Bindable, GcScope, NoGcScope, bindable_handle}, + rootable::{HeapRootData, HeapRootRef, Rootable}, + }, + heap::{ + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, + IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, + }, }; /// Constructor function object for %Temporal.Instant%. pub(crate) struct InstantConstructor; @@ -28,18 +36,23 @@ impl BuiltinIntrinsicConstructor for InstantConstructor { } impl InstantConstructor { - fn construct<'gc>(agent: &mut Agent, this_value: Value, args: ArgumentsList, new_target: Option, gc: GcScope<'gc, '_>) -> JsResult<'gc, Value<'gc>> { + fn construct<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + new_target: Option, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { todo!(); } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { - let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); + let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); - + BuiltinFunctionBuilder::new_intrinsic_constructor::(agent, realm) - .with_property_capacity(1) - .with_prototype_property(instant_prototype.into_object()) - .build(); - + .with_property_capacity(1) + .with_prototype_property(instant_prototype.into_object()) + .build(); } } /// %Temporal.Instant.Prototype% @@ -53,7 +66,7 @@ impl InstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(1) // TODO add correct property capacity + .with_property_capacity(1) // TODO add correct property capacity .with_prototype(object_prototype) .with_constructor_property(instant_constructor) // TODO add all prototype methods @@ -61,9 +74,8 @@ impl InstantPrototype { } } - use self::data::InstantHeapData; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); impl Instant<'_> { @@ -79,9 +91,9 @@ impl Instant<'_> { bindable_handle!(Instant); impl<'a> From> for Value<'a> { - fn from(value: Instant<'a>) -> Self { + fn from(value: Instant<'a>) -> Self { Value::Instant(value) - } + } } impl<'a> From> for Object<'a> { fn from(value: Instant<'a>) -> Self { @@ -116,7 +128,6 @@ impl<'a> InternalSlots<'a> for Instant<'a> { fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { assert!(agent[self].object_index.replace(backing_object).is_none()); // not implemented for `agent::Agent` } - } impl<'a> InternalMethods<'a> for Instant<'a> {} @@ -140,9 +151,9 @@ impl Index> for Vec>> { fn index(&self, index: Instant<'_>) -> &Self::Output { self.get(index.get_index()) - .expect("heap access out of bounds") - .as_ref() - .expect("") + .expect("heap access out of bounds") + .as_ref() + .expect("") } } @@ -155,7 +166,6 @@ impl IndexMut> for Vec>> { } } - impl Rootable for Instant<'_> { type RootRepr = HeapRootRef; @@ -163,7 +173,9 @@ impl Rootable for Instant<'_> { Err(HeapRootData::Instant(value.unbind())) } - fn from_root_repr(value: &Self::RootRepr) -> Result { + fn from_root_repr( + value: &Self::RootRepr, + ) -> Result { Err(*value) } @@ -179,7 +191,6 @@ impl Rootable for Instant<'_> { } } - impl HeapMarkAndSweep for Instant<'static> { fn mark_values(&self, queues: &mut WorkQueues) { queues.instants.push(*self); diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index 1bc2922a7..97b376004 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -14,6 +14,8 @@ use crate::ecmascript::types::DATE_DISCRIMINANT; use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 4d05f0c2d..2e4e59371 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -124,10 +124,11 @@ use crate::{ promise::Promise, promise_objects::promise_abstract_operations::promise_finally_functions::BuiltinPromiseFinallyFunction, proxy::Proxy, + temporal::instant::Instant, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{Agent, JsResult, ProtoIntrinsics, agent::TryResult}, - types::PropertyDescriptor, + types::{INSTANT_DISCRIMINANT, PropertyDescriptor}, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -179,6 +180,7 @@ pub enum Object<'a> { Array(Array<'a>) = ARRAY_DISCRIMINANT, #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, + #[cfg(feature = "temporal")] Instant(Instant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, @@ -1436,6 +1438,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 48979cb61..15d26b6f1 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -11,6 +11,8 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::builtins::typed_array::Float16Array; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -1306,6 +1308,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), @@ -1326,7 +1330,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")] @@ -1355,7 +1358,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")] @@ -1384,7 +1386,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), @@ -1423,6 +1424,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 b7f06a909..4a2b9eb37 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -17,6 +17,8 @@ use crate::ecmascript::types::DATE_DISCRIMINANT; use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; +#[cfg(feature = "temporal")] +use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -136,6 +138,8 @@ pub mod private { #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; + #[cfg(feature = "temporal")] + use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, From 04e04afe2967c779ce7140dfc34db894742102ab Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:34:51 +0300 Subject: [PATCH 06/25] cleanup --- nova_vm/src/ecmascript/builtins/ordinary.rs | 4 +- .../ecmascript/builtins/temporal/instant.rs | 45 +++++++++---------- .../builtins/temporal/instant/data.rs | 37 ++++++++++----- nova_vm/src/heap.rs | 4 +- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index 01036432b..fea34c868 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -16,7 +16,7 @@ use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; +use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::{ @@ -1684,7 +1684,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .into_object(), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => { - agent.heap.create(InstantHeapData::default()).into_object() + agent.heap.create(InstantRecord::default()).into_object() } ProtoIntrinsics::TypeError => agent .heap diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 88f3794e2..f1045a2d6 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -74,10 +74,10 @@ impl InstantPrototype { } } -use self::data::InstantHeapData; +use self::data::InstantRecord; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct Instant<'a>(BaseIndex<'a, InstantHeapData<'static>>); +pub struct Instant<'a>(BaseIndex<'a, InstantRecord<'static>>); impl Instant<'_> { //TODO pub(crate) const fn _def() -> Self { @@ -123,17 +123,18 @@ impl<'a> TryFrom> for Instant<'a> { impl<'a> InternalSlots<'a> for Instant<'a> { const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant; fn get_backing_object(self, agent: &Agent) -> Option> { - agent[self].object_index // not implemented for `agent::Agent` + agent[self].object_index } fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) { - assert!(agent[self].object_index.replace(backing_object).is_none()); // not implemented for `agent::Agent` + assert!(agent[self].object_index.replace(backing_object).is_none()); } } impl<'a> InternalMethods<'a> for Instant<'a> {} +// TODO: get rid of Index impls, replace with get/get_mut/get_direct/get_direct_mut functions impl Index> for Agent { - type Output = InstantHeapData<'static>; + type Output = InstantRecord<'static>; fn index(&self, index: Instant<'_>) -> &Self::Output { &self.heap.instants[index] @@ -146,44 +147,38 @@ impl IndexMut> for Agent { } } -impl Index> for Vec>> { - type Output = InstantHeapData<'static>; +impl Index> for Vec> { + type Output = InstantRecord<'static>; fn index(&self, index: Instant<'_>) -> &Self::Output { self.get(index.get_index()) .expect("heap access out of bounds") - .as_ref() - .expect("") } } -impl IndexMut> for Vec>> { +impl IndexMut> for Vec> { fn index_mut(&mut self, index: Instant<'_>) -> &mut Self::Output { self.get_mut(index.get_index()) - .expect("dasdas") - .as_mut() - .expect("") + .expect("heap access out of bounds") } } impl Rootable for Instant<'_> { type RootRepr = HeapRootRef; - fn to_root_repr(value: Self) -> Result { + fn to_root_repr(value: Self) -> Result { Err(HeapRootData::Instant(value.unbind())) } - fn from_root_repr( - value: &Self::RootRepr, - ) -> Result { + fn from_root_repr(value: &Self::RootRepr) -> Result { Err(*value) } - fn from_heap_ref(heap_ref: crate::engine::rootable::HeapRootRef) -> Self::RootRepr { + fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr { heap_ref } - fn from_heap_data(heap_data: crate::engine::rootable::HeapRootData) -> Option { + fn from_heap_data(heap_data: HeapRootData) -> Option { match heap_data { HeapRootData::Instant(object) => Some(object), _ => None, @@ -202,14 +197,14 @@ impl HeapMarkAndSweep for Instant<'static> { impl HeapSweepWeakReference for Instant<'static> { fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option { - compactions.dates.shift_weak_index(self.0).map(Self) + compactions.instants.shift_weak_index(self.0).map(Self) } } -impl<'a> CreateHeapData, Instant<'a>> for Heap { - fn create(&mut self, data: InstantHeapData<'a>) -> Instant<'a> { - self.instants.push(Some(data.unbind())); - self.alloc_counter += core::mem::size_of::>>(); - Instant(BaseIndex::last(&self.instants)) +impl<'a> CreateHeapData, Instant<'a>> for Heap { + fn create(&mut self, data: InstantRecord<'a>) -> Instant<'a> { + self.instants.push(data.unbind()); + self.alloc_counter += core::mem::size_of::>(); + Instant(BaseIndex::last_t(&self.instants)) } } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index e4d397ba9..4664fa0c6 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -1,28 +1,41 @@ - -use crate::{ecmascript::types::{OrdinaryObject,bigint::BigInt}, engine::context::bindable_handle, heap::HeapMarkAndSweep}; +use crate::{ + ecmascript::types::OrdinaryObject, + engine::context::bindable_handle, + heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, +}; #[derive(Debug, Clone, Copy)] -pub struct InstantHeapData<'a> { +pub struct InstantRecord<'a> { pub(crate) object_index: Option>, - pub(crate) instant: BigInt<'a>, + pub(crate) instant: temporal_rs::Instant, } -impl InstantHeapData<'_> { +impl InstantRecord<'_> { pub fn default() -> Self { Self { object_index: None, - instant: BigInt::zero(), + instant: temporal_rs::Instant::try_new(0).unwrap(), } } } -bindable_handle!(InstantHeapData); +bindable_handle!(InstantRecord); + +impl HeapMarkAndSweep for InstantRecord<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + object_index, + instant: _, + } = self; -impl HeapMarkAndSweep for InstantHeapData<'static> { - fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { - todo!() + object_index.mark_values(queues); } - fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { - todo!() + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + object_index, + instant: _, + } = self; + + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 0fa7092a9..ebb7b2c0f 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -32,7 +32,7 @@ use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::data::SharedArrayBufferRecord; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; +use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, @@ -144,7 +144,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 b1f33f464ec089fb3f7dc46d3ce21c4838ab4446 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 11:39:07 +0300 Subject: [PATCH 07/25] 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 f77f2e6d7..d0b735c2c 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -677,6 +677,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 9b7186df73a6602b6fd36661fb123f6962fc07f9 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 12:15:46 +0300 Subject: [PATCH 08/25] 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 32de10a73..ef364c53f 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -216,6 +216,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 ea6ac12867668b5ee6dd03f95be160e0d14bc7f2 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sun, 19 Oct 2025 13:07:16 +0200 Subject: [PATCH 09/25] add prototype methods --- nova_vm/src/builtin_strings | 3 + .../ecmascript/builtins/temporal/instant.rs | 197 +++++++++++++++--- .../ecmascript/execution/realm/intrinsics.rs | 35 +++- nova_vm/src/ecmascript/execution/weak_key.rs | 6 +- .../src/ecmascript/types/language/object.rs | 4 +- .../src/ecmascript/types/language/value.rs | 7 +- nova_vm/src/engine/rootable.rs | 10 +- nova_vm/src/heap/heap_bits.rs | 6 +- nova_vm/src/heap/heap_gc.rs | 6 +- 9 files changed, 214 insertions(+), 60 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 96b6b8a11..ab2f76e04 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -86,6 +86,7 @@ codePointAt concat configurable construct +compare constructor copyWithin #[cfg(feature = "math")]cos @@ -146,6 +147,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 421fcbdca..68a3471ee 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,7 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - instant::InstantConstructor, instant::InstantPrototype, TemporalObject + TemporalObject, instant::InstantConstructor, instant::TemporalInstantPrototype, }; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ @@ -67,6 +67,7 @@ use crate::ecmascript::builtins::{ use crate::{ ecmascript::{ builtins::{ + Array, BuiltinFunction, control_abstraction_objects::{ async_function_objects::{ async_function_constructor::AsyncFunctionConstructor, @@ -89,22 +90,31 @@ use crate::{ promise_objects::{ promise_constructor::PromiseConstructor, promise_prototype::PromisePrototype, }, - }, global_object::GlobalObject, indexed_collections::array_objects::{ + }, + global_object::GlobalObject, + indexed_collections::array_objects::{ array_constructor::ArrayConstructor, array_iterator_objects::array_iterator_prototype::ArrayIteratorPrototype, array_prototype::ArrayPrototype, - }, iteration::iterator_constructor::IteratorConstructor, keyed_collections::map_objects::{ + }, + iteration::iterator_constructor::IteratorConstructor, + keyed_collections::map_objects::{ map_constructor::MapConstructor, map_iterator_objects::map_iterator_prototype::MapIteratorPrototype, map_prototype::MapPrototype, - }, managing_memory::finalization_registry_objects::{ + }, + managing_memory::finalization_registry_objects::{ finalization_registry_constructor::FinalizationRegistryConstructor, finalization_registry_prototype::FinalizationRegistryPrototype, - }, ordinary::shape::ObjectShape, primitive_objects::{PrimitiveObject, PrimitiveObjectHeapData}, reflection::{proxy_constructor::ProxyConstructor, reflect_object::ReflectObject}, text_processing::string_objects::{ + }, + ordinary::shape::ObjectShape, + primitive_objects::{PrimitiveObject, PrimitiveObjectHeapData}, + reflection::{proxy_constructor::ProxyConstructor, reflect_object::ReflectObject}, + text_processing::string_objects::{ string_constructor::StringConstructor, string_iterator_objects::StringIteratorPrototype, string_prototype::StringPrototype, - }, Array, BuiltinFunction + }, }, execution::Agent, fundamental_objects::{ @@ -140,7 +150,10 @@ use crate::{ }, engine::context::NoGcScope, heap::{ - indexes::BaseIndex, intrinsic_function_count, intrinsic_object_count, intrinsic_primitive_object_count, CompactionLists, HeapMarkAndSweep, IntrinsicConstructorIndexes, IntrinsicFunctionIndexes, IntrinsicObjectIndexes, IntrinsicObjectShapes, IntrinsicPrimitiveObjectIndexes, WorkQueues + CompactionLists, HeapMarkAndSweep, IntrinsicConstructorIndexes, IntrinsicFunctionIndexes, + IntrinsicObjectIndexes, IntrinsicObjectShapes, IntrinsicPrimitiveObjectIndexes, WorkQueues, + indexes::BaseIndex, intrinsic_function_count, intrinsic_object_count, + intrinsic_primitive_object_count, }, }; #[derive(Debug, Clone)] @@ -301,11 +314,12 @@ impl Intrinsics { #[cfg(feature = "math")] MathObject::create_intrinsic(agent, realm, gc); - #[cfg(feature = "temporal")] { + #[cfg(feature = "temporal")] + { 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")] @@ -1021,7 +1035,8 @@ impl Intrinsics { /// %Temporal.Instant% pub(crate) const fn temporal_instant(&self) -> BuiltinFunction<'static> { - IntrinsicConstructorIndexes::TemporalInstant.get_builtin_function(self.builtin_function_index_base) + IntrinsicConstructorIndexes::TemporalInstant + .get_builtin_function(self.builtin_function_index_base) } /// %Temporal.Instant.Prototype% pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { diff --git a/nova_vm/src/ecmascript/execution/weak_key.rs b/nova_vm/src/ecmascript/execution/weak_key.rs index 97b376004..ae595f747 100644 --- a/nova_vm/src/ecmascript/execution/weak_key.rs +++ b/nova_vm/src/ecmascript/execution/weak_key.rs @@ -15,7 +15,9 @@ use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; +use crate::ecmascript::{ + builtins::temporal::instant::TemporalInstant, types::INSTANT_DISCRIMINANT, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -144,7 +146,7 @@ pub(crate) enum WeakKey<'a> { #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] - Instant(Instant<'a>) = INSTANT_DISCRIMINANT, + Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 2e4e59371..8c06763a0 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -124,7 +124,7 @@ use crate::{ promise::Promise, promise_objects::promise_abstract_operations::promise_finally_functions::BuiltinPromiseFinallyFunction, proxy::Proxy, - temporal::instant::Instant, + temporal::instant::TemporalInstant, text_processing::string_objects::string_iterator_objects::StringIterator, }, execution::{Agent, JsResult, ProtoIntrinsics, agent::TryResult}, @@ -181,7 +181,7 @@ pub enum Object<'a> { #[cfg(feature = "date")] Date(Date<'a>) = DATE_DISCRIMINANT, #[cfg(feature = "temporal")] - Instant(Instant<'a>) = INSTANT_DISCRIMINANT, + Instant(TemporalInstant<'a>) = INSTANT_DISCRIMINANT, Error(Error<'a>) = ERROR_DISCRIMINANT, FinalizationRegistry(FinalizationRegistry<'a>) = FINALIZATION_REGISTRY_DISCRIMINANT, Map(Map<'a>) = MAP_DISCRIMINANT, diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 15d26b6f1..68671b82d 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -12,7 +12,7 @@ use super::{ #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::Instant; +use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::builtins::typed_array::Float16Array; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -184,7 +184,7 @@ pub enum Value<'a> { #[cfg(feature = "date")] Date(Date<'a>), #[cfg(feature = "temporal")] - Instant(Instant<'a>), + Instant(TemporalInstant<'a>), Error(Error<'a>), FinalizationRegistry(FinalizationRegistry<'a>), Map(Map<'a>), @@ -321,7 +321,8 @@ pub(crate) const ARRAY_DISCRIMINANT: u8 = value_discriminant(Value::Array(Array: #[cfg(feature = "date")] pub(crate) const DATE_DISCRIMINANT: u8 = value_discriminant(Value::Date(Date::_def())); #[cfg(feature = "temporal")] -pub(crate) const INSTANT_DISCRIMINANT: u8 = value_discriminant(Value::Instant(Instant::_def())); +pub(crate) const INSTANT_DISCRIMINANT: u8 = + value_discriminant(Value::Instant(TemporalInstant::_def())); pub(crate) const ERROR_DISCRIMINANT: u8 = value_discriminant(Value::Error(Error::_def())); pub(crate) const BUILTIN_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinFunction(BuiltinFunction::_def())); diff --git a/nova_vm/src/engine/rootable.rs b/nova_vm/src/engine/rootable.rs index 4a2b9eb37..281a27686 100644 --- a/nova_vm/src/engine/rootable.rs +++ b/nova_vm/src/engine/rootable.rs @@ -18,7 +18,9 @@ use crate::ecmascript::types::{ WEAK_MAP_DISCRIMINANT, WEAK_REF_DISCRIMINANT, WEAK_SET_DISCRIMINANT, }; #[cfg(feature = "temporal")] -use crate::ecmascript::{builtins::temporal::instant::Instant, types::INSTANT_DISCRIMINANT}; +use crate::ecmascript::{ + builtins::temporal::instant::TemporalInstant, types::INSTANT_DISCRIMINANT, +}; #[cfg(feature = "proposal-float16array")] use crate::ecmascript::{builtins::typed_array::Float16Array, types::FLOAT_16_ARRAY_DISCRIMINANT}; #[cfg(all(feature = "proposal-float16array", feature = "shared-array-buffer"))] @@ -139,7 +141,7 @@ pub mod private { #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; #[cfg(feature = "temporal")] - use crate::ecmascript::builtins::temporal::instant::Instant; + use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, @@ -234,7 +236,7 @@ pub mod private { #[cfg(feature = "date")] impl RootableSealed for Date<'_> {} #[cfg(feature = "temporal")] - impl RootableSealed for Instant<'_> {} + impl RootableSealed for TemporalInstant<'_> {} impl RootableSealed for ECMAScriptFunction<'_> {} impl RootableSealed for EmbedderObject<'_> {} impl RootableSealed for Error<'_> {} @@ -550,7 +552,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 1fe0c0a28..05dd322ef 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -16,10 +16,10 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; -#[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::SharedArrayBuffer; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView}; #[cfg(feature = "set")] @@ -195,7 +195,7 @@ pub(crate) struct WorkQueues { #[cfg(feature = "date")] pub dates: Vec>, #[cfg(feature = "temporal")] - pub instants: Vec>, + pub instants: Vec>, pub declarative_environments: Vec>, pub e_2_1: Vec<(ElementIndex<'static>, u32)>, pub e_2_2: Vec<(ElementIndex<'static>, u32)>, diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 6af23a070..2e3ef170c 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -22,10 +22,10 @@ use super::{ }; #[cfg(feature = "date")] use crate::ecmascript::builtins::date::Date; -#[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::Instant; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::SharedArrayBuffer; +#[cfg(feature = "temporal")] +use crate::ecmascript::builtins::temporal::instant::TemporalInstant; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ArrayBuffer, data_view::DataView}; #[cfg(feature = "set")] @@ -586,7 +586,7 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc } #[cfg(feature = "date")] { - let mut instant_marks: Box<[Instant]> = queues.instants.drain(..).collect(); + let mut instant_marks: Box<[TemporalInstant]> = queues.instants.drain(..).collect(); instant_marks.sort(); instant_marks.iter().for_each(|&idx| { let index = idx.get_index(); From 6f5102df976210f7541d61ce6ac75175ecf3cee9 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 14:26:20 +0300 Subject: [PATCH 10/25] get it working --- nova_vm/src/ecmascript/builtins/temporal.rs | 14 +++++- .../ecmascript/builtins/temporal/instant.rs | 47 ++++++++++--------- .../ecmascript/execution/realm/intrinsics.rs | 4 +- nova_vm/src/heap/heap_constants.rs | 2 - 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 6b30cd6fc..4df4d759c 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -7,8 +7,9 @@ pub mod instant; use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, + builtins::Builtin, execution::{Agent, Realm}, - types::BUILTIN_STRING_MEMORY, + types::{BUILTIN_STRING_MEMORY, IntoValue}, }, engine::context::NoGcScope, heap::WellKnownSymbolIndexes, @@ -22,6 +23,8 @@ impl TemporalObject { let object_prototype = intrinsics.object_prototype(); let this = intrinsics.temporal(); + let temporal_instant_constructor = intrinsics.temporal_instant(); + OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) .with_property_capacity(2) .with_prototype(object_prototype) @@ -33,7 +36,14 @@ impl TemporalObject { .with_configurable(true) .build() }) - .with_builtin_function_property::() + .with_property(|builder| { + builder + .with_key(BUILTIN_STRING_MEMORY.Instant.into()) + .with_value(temporal_instant_constructor.into_value()) + .with_enumerable(instant::TemporalInstantConstructor::ENUMERABLE) + .with_configurable(instant::TemporalInstantConstructor::CONFIGURABLE) + .build() + }) .build(); } } diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 655a99f39..d92b8ce32 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -36,17 +36,17 @@ use crate::{ }, }; /// Constructor function object for %Temporal.Instant%. -pub(crate) struct InstantConstructor; -impl Builtin for InstantConstructor { +pub(crate) struct TemporalInstantConstructor; +impl Builtin for TemporalInstantConstructor { const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Constructor(InstantConstructor::construct); + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::construct); } -impl BuiltinIntrinsicConstructor for InstantConstructor { +impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; } -impl InstantConstructor { +impl TemporalInstantConstructor { /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) fn construct<'gc>( agent: &mut Agent, @@ -93,12 +93,8 @@ impl InstantConstructor { )); }; // 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget). - create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc).map( - |instant| { - eprintln!("Temporal.Instant {:?}", &agent[instant].instant); - instant.into_value() - }, - ) + create_temporal_instant(agent, epoch_nanoseconds, Some(new_target.unbind()), gc) + .map(|instant| instant.into_value()) } /// ### [8.2.2 Temporal.Instant.from ( item )](https://tc39.es/proposal-temporal/#sec-temporal.instant.from) @@ -145,13 +141,20 @@ impl InstantConstructor { todo!() } - pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, gc: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let instant_prototype = intrinsics.temporal_instant_prototype(); - BuiltinFunctionBuilder::new_intrinsic_constructor::(agent, realm) - .with_property_capacity(1) + let result = + BuiltinFunctionBuilder::new_intrinsic_constructor::( + agent, realm, + ) + .with_property_capacity(5) .with_prototype_property(instant_prototype.into_object()) + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() .build(); } } @@ -248,7 +251,7 @@ impl Builtin for TemporalInstantFrom { const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from); + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); } struct TemporalInstantFromEpochMilliseconds; @@ -257,7 +260,8 @@ impl Builtin for TemporalInstantFromEpochMilliseconds { const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from_epoch_milliseconds); + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); } struct TemporalInstantFromEpochNanoseconds; @@ -266,7 +270,8 @@ impl Builtin for TemporalInstantFromEpochNanoseconds { const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::from_epoch_nanoseconds); + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); } struct TemporalInstantCompare; @@ -275,7 +280,7 @@ impl Builtin for TemporalInstantCompare { const LENGTH: u8 = 2; - const BEHAVIOUR: Behaviour = Behaviour::Regular(InstantConstructor::compare); + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); } impl TemporalInstantPrototype { @@ -286,13 +291,9 @@ impl TemporalInstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(5) + .with_property_capacity(1) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() - .with_builtin_function_property::() .build(); } } diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 68a3471ee..542428085 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,7 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - TemporalObject, instant::InstantConstructor, instant::TemporalInstantPrototype, + TemporalObject, instant::TemporalInstantConstructor, instant::TemporalInstantPrototype, }; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ @@ -318,7 +318,7 @@ impl Intrinsics { { TemporalObject::create_intrinsic(agent, realm, gc); // Instant - InstantConstructor::create_intrinsic(agent, realm, gc); + TemporalInstantConstructor::create_intrinsic(agent, realm, gc); TemporalInstantPrototype::create_intrinsic(agent, realm, gc); } diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index 7a44b1afb..2e7341991 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -37,8 +37,6 @@ pub(crate) enum IntrinsicObjectIndexes { #[cfg(feature = "temporal")] TemporalObject, #[cfg(feature = "temporal")] - TemporalInstant, - #[cfg(feature = "temporal")] TemporalInstantPrototype, // Text processing #[cfg(feature = "regexp")] From 5803efe4b3b4451fbff123a61084e71d7132f412 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Oct 2025 14:26:35 +0300 Subject: [PATCH 11/25] 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 03c62e57fbe5a0bbf76711933fa0994b8812575d Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 03:26:47 +0200 Subject: [PATCH 12/25] 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 a218f42857471640fbe4599f1db5e182843ad928 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 15:04:51 +0200 Subject: [PATCH 13/25] 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 78c8329183f97e91ae4e6bd00d32bd2eb7a387d7 Mon Sep 17 00:00:00 2001 From: SebastianJM Date: Thu, 23 Oct 2025 16:05:00 +0200 Subject: [PATCH 14/25] 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 887d8844a0ff95417422b8425abf2150b26ef059 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sat, 25 Oct 2025 14:50:36 +0200 Subject: [PATCH 15/25] implementation of Temporal.Instant.compare --- nova_vm/Cargo.toml | 4 +- nova_vm/src/ecmascript/builtins/temporal.rs | 6 +- .../ecmascript/builtins/temporal/instant.rs | 105 ++++++++++-------- .../builtins/temporal/instant/data.rs | 4 +- .../ecmascript/execution/realm/intrinsics.rs | 9 +- 5 files changed, 69 insertions(+), 59 deletions(-) diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 84f893b83..f3c118048 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -52,7 +52,7 @@ default = [ "regexp", "set", "annex-b", - "temporal", + "temporal", ] array-buffer = [] atomics = ["array-buffer", "shared-array-buffer"] @@ -64,7 +64,7 @@ shared-array-buffer = ["array-buffer"] weak-refs = [] set = [] typescript = [] -temporal = ["temporal_rs"] +temporal = ["temporal_rs"] # Enables features defined by [Annex B](https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers) annex-b = ["annex-b-string", "annex-b-global", "annex-b-date", "annex-b-regexp"] diff --git a/nova_vm/src/ecmascript/builtins/temporal.rs b/nova_vm/src/ecmascript/builtins/temporal.rs index 4df4d759c..b833296be 100644 --- a/nova_vm/src/ecmascript/builtins/temporal.rs +++ b/nova_vm/src/ecmascript/builtins/temporal.rs @@ -7,7 +7,7 @@ pub mod instant; use crate::{ ecmascript::{ builders::ordinary_object_builder::OrdinaryObjectBuilder, - builtins::Builtin, + builtins::{Builtin, temporal::instant::TemporalInstantConstructor}, execution::{Agent, Realm}, types::{BUILTIN_STRING_MEMORY, IntoValue}, }, @@ -40,8 +40,8 @@ impl TemporalObject { builder .with_key(BUILTIN_STRING_MEMORY.Instant.into()) .with_value(temporal_instant_constructor.into_value()) - .with_enumerable(instant::TemporalInstantConstructor::ENUMERABLE) - .with_configurable(instant::TemporalInstantConstructor::CONFIGURABLE) + .with_enumerable(TemporalInstantConstructor::ENUMERABLE) + .with_configurable(TemporalInstantConstructor::CONFIGURABLE) .build() }) .build(); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index dd66299ac..a4f712496 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -35,20 +35,61 @@ use crate::{ IntrinsicConstructorIndexes, WorkQueues, indexes::BaseIndex, }, }; + /// Constructor function object for %Temporal.Instant%. pub(crate) struct TemporalInstantConstructor; + impl Builtin for TemporalInstantConstructor { const NAME: String<'static> = BUILTIN_STRING_MEMORY.Instant; const LENGTH: u8 = 1; - const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::construct); + const BEHAVIOUR: Behaviour = Behaviour::Constructor(TemporalInstantConstructor::constructor); } + impl BuiltinIntrinsicConstructor for TemporalInstantConstructor { const INDEX: IntrinsicConstructorIndexes = IntrinsicConstructorIndexes::TemporalInstant; } +struct TemporalInstantFrom; +impl Builtin for TemporalInstantFrom { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); +} + +struct TemporalInstantFromEpochMilliseconds; +impl Builtin for TemporalInstantFromEpochMilliseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); +} + +struct TemporalInstantFromEpochNanoseconds; +impl Builtin for TemporalInstantFromEpochNanoseconds { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; + + const LENGTH: u8 = 1; + + const BEHAVIOUR: Behaviour = + Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); +} + +struct TemporalInstantCompare; +impl Builtin for TemporalInstantCompare { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; + + const LENGTH: u8 = 2; + + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); +} + impl TemporalInstantConstructor { /// ### [8.1.1 Temporal.Instant ( epochNanoseconds )](https://tc39.es/proposal-temporal/#sec-temporal.instant) - fn construct<'gc>( + fn constructor<'gc>( agent: &mut Agent, _: Value, args: ArgumentsList, @@ -101,10 +142,10 @@ impl TemporalInstantConstructor { fn from<'gc>( agent: &mut Agent, _this_value: Value, - arguments: ArgumentsList, + args: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - let item = arguments.get(0).bind(gc.nogc()); + let item = args.get(0).bind(gc.nogc()); // 1. Return ? ToTemporalInstant(item). let instant = to_temporal_instant(agent, item.unbind(), gc)?; let instant = agent.heap.create(InstantRecord { @@ -118,10 +159,10 @@ impl TemporalInstantConstructor { fn from_epoch_milliseconds<'gc>( agent: &mut Agent, _this_value: Value, - arguments: ArgumentsList, + args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - let epoch_ms = arguments.get(0).bind(gc.nogc()); + let epoch_ms = args.get(0).bind(gc.nogc()); // 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds). let epoch_ms_number = epoch_ms .unbind() @@ -190,19 +231,23 @@ impl TemporalInstantConstructor { Ok(value) } - /// [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) + /// ### [8.2.5 Temporal.Instant.compare ( one, two )](https://tc39.es/proposal-temporal/#sec-temporal.instant.compare) fn compare<'gc>( agent: &mut Agent, _this_value: Value, - arguments: ArgumentsList, + args: ArgumentsList, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { + let one = args.get(0).bind(gc.nogc()); + let two = args.get(0).bind(gc.nogc()); + let two = two.scope(agent, gc.nogc()); // 1. Set one to ? ToTemporalInstant(one). - let one = arguments.get(0).bind(gc.nogc()); + let one_instant = to_temporal_instant(agent, one.unbind(), gc.reborrow()).unbind()?; // 2. Set two to ? ToTemporalInstant(two). - let two = arguments.get(1).bind(gc.nogc()); + let two_value = two.get(agent).bind(gc.nogc()); + let two_instant = to_temporal_instant(agent, two_value.unbind(), gc.reborrow()).unbind()?; // 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])). - todo!() + Ok((one_instant.cmp(&two_instant) as i8).into()) } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { @@ -308,44 +353,6 @@ fn to_temporal_instant<'gc>( /// %Temporal.Instant.Prototype% pub(crate) struct TemporalInstantPrototype; -struct TemporalInstantFrom; -impl Builtin for TemporalInstantFrom { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.from; - - const LENGTH: u8 = 1; - - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::from); -} - -struct TemporalInstantFromEpochMilliseconds; -impl Builtin for TemporalInstantFromEpochMilliseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochMilliseconds; - - const LENGTH: u8 = 1; - - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantConstructor::from_epoch_milliseconds); -} - -struct TemporalInstantFromEpochNanoseconds; -impl Builtin for TemporalInstantFromEpochNanoseconds { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.fromEpochNanoseconds; - - const LENGTH: u8 = 1; - - const BEHAVIOUR: Behaviour = - Behaviour::Regular(TemporalInstantConstructor::from_epoch_nanoseconds); -} - -struct TemporalInstantCompare; -impl Builtin for TemporalInstantCompare { - const NAME: String<'static> = BUILTIN_STRING_MEMORY.compare; - - const LENGTH: u8 = 2; - - const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantConstructor::compare); -} - impl TemporalInstantPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index 91d365a58..1cf068d2e 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -2,9 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::engine::context::NoGcScope; use crate::{ ecmascript::types::OrdinaryObject, - engine::context::bindable_handle, + engine::context::{bindable_handle, trivially_bindable}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; @@ -23,6 +24,7 @@ impl InstantRecord<'_> { } } +trivially_bindable!(temporal_rs::Instant); bindable_handle!(InstantRecord); impl HeapMarkAndSweep for InstantRecord<'static> { diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 542428085..5e249f796 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -1033,15 +1033,16 @@ impl Intrinsics { IntrinsicObjectIndexes::TemporalObject.get_backing_object(self.object_index_base) } + /// %Temporal.Instant.Prototype% + pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { + IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) + } + /// %Temporal.Instant% pub(crate) const fn temporal_instant(&self) -> BuiltinFunction<'static> { IntrinsicConstructorIndexes::TemporalInstant .get_builtin_function(self.builtin_function_index_base) } - /// %Temporal.Instant.Prototype% - pub(crate) const fn temporal_instant_prototype(&self) -> OrdinaryObject<'static> { - IntrinsicObjectIndexes::TemporalInstantPrototype.get_backing_object(self.object_index_base) - } /// %Number.prototype% pub(crate) fn number_prototype(&self) -> PrimitiveObject<'static> { From c6e41636e7357c62c7f8b8339c1a00fea1ed1255 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Sat, 25 Oct 2025 19:25:35 +0200 Subject: [PATCH 16/25] 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 bb8f5d8da8ff9f8f546d50fcbb7fb8a30e4d5b26 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 11:26:47 +0100 Subject: [PATCH 17/25] get_epoch_milliseconds and get_epoch_nanoseconds --- nova_vm/src/builtin_strings | 2 + nova_vm/src/ecmascript/builtins/ordinary.rs | 4 +- .../ecmascript/builtins/temporal/instant.rs | 98 ++++++++++++++++--- .../builtins/temporal/instant/data.rs | 8 +- nova_vm/src/heap.rs | 4 +- 5 files changed, 97 insertions(+), 19 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index ab2f76e04..37da3f699 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -184,6 +184,8 @@ get size #[cfg(feature = "array-buffer")]getBigUint64 #[cfg(feature = "date")]getDate #[cfg(feature = "date")]getDay +#[cfg(feature = "temporal")]getEpochMilliseconds +#[cfg(feature = "temporal")]getEpochNanoSeconds #[cfg(feature = "proposal-float16array")]getFloat16 #[cfg(feature = "array-buffer")]getFloat32 #[cfg(feature = "array-buffer")]getFloat64 diff --git a/nova_vm/src/ecmascript/builtins/ordinary.rs b/nova_vm/src/ecmascript/builtins/ordinary.rs index fea34c868..01036432b 100644 --- a/nova_vm/src/ecmascript/builtins/ordinary.rs +++ b/nova_vm/src/ecmascript/builtins/ordinary.rs @@ -16,7 +16,7 @@ use caches::{CacheToPopulate, Caches, PropertyLookupCache, PropertyOffset}; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::data_view::data::SharedDataViewRecord; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; +use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; use crate::{ ecmascript::{ abstract_operations::operations_on_objects::{ @@ -1684,7 +1684,7 @@ pub(crate) fn ordinary_object_create_with_intrinsics<'a>( .into_object(), #[cfg(feature = "temporal")] ProtoIntrinsics::TemporalInstant => { - agent.heap.create(InstantRecord::default()).into_object() + agent.heap.create(InstantHeapData::default()).into_object() } ProtoIntrinsics::TypeError => agent .heap diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 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/data.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs index 1cf068d2e..54fa62454 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/data.rs @@ -10,12 +10,12 @@ use crate::{ }; #[derive(Debug, Clone, Copy)] -pub struct InstantRecord<'a> { +pub struct InstantHeapData<'a> { pub(crate) object_index: Option>, pub(crate) instant: temporal_rs::Instant, } -impl InstantRecord<'_> { +impl InstantHeapData<'_> { pub fn default() -> Self { Self { object_index: None, @@ -25,9 +25,9 @@ impl InstantRecord<'_> { } trivially_bindable!(temporal_rs::Instant); -bindable_handle!(InstantRecord); +bindable_handle!(InstantHeapData); -impl HeapMarkAndSweep for InstantRecord<'static> { +impl HeapMarkAndSweep for InstantHeapData<'static> { fn mark_values(&self, queues: &mut WorkQueues) { let Self { object_index, diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index ebb7b2c0f..60f3e686c 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -32,7 +32,7 @@ use crate::ecmascript::builtins::date::data::DateHeapData; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::builtins::shared_array_buffer::data::SharedArrayBufferRecord; #[cfg(feature = "temporal")] -use crate::ecmascript::builtins::temporal::instant::data::InstantRecord; +use crate::ecmascript::builtins::temporal::instant::data::InstantHeapData; #[cfg(feature = "array-buffer")] use crate::ecmascript::builtins::{ ArrayBuffer, ArrayBufferHeapData, @@ -144,7 +144,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 f9340a5c113ecd1776679d3f64b2252114a69096 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 12:43:13 +0100 Subject: [PATCH 18/25] add BigInt::from_i128, and add skeleton methods for Seb --- nova_vm/src/builtin_strings | 5 +- .../ecmascript/builtins/temporal/instant.rs | 72 ++++++++++++++++++- .../src/ecmascript/types/language/bigint.rs | 9 +++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 37da3f699..046575547 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 @@ -389,6 +389,7 @@ setPrototypeOf shift #[cfg(feature = "math")]sign #[cfg(feature = "math")]sin +#[cfg(feature = "temporal")]since #[cfg(feature = "math")]sinh size slice @@ -414,6 +415,7 @@ String Iterator #[cfg(feature = "array-buffer")]subarray #[cfg(feature = "annex-b-string")]substr substring +#[cfg(feature = "temporal")]subtract #[cfg(feature = "proposal-math-sum")]sumPrecise #[cfg(feature = "annex-b-string")]sup symbol @@ -485,6 +487,7 @@ undefined unregister unscopables unshift +#[cfg(feature = "temporal")]until URIError #[cfg(feature = "date")]UTC value diff --git a/nova_vm/src/ecmascript/builtins/temporal/instant.rs b/nova_vm/src/ecmascript/builtins/temporal/instant.rs index 52e97778b..bbc6606ac 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant.rs @@ -361,6 +361,34 @@ impl Builtin for TemporalInstantPrototypeGetEpochNanoSeconds { Behaviour::Regular(TemporalInstantPrototype::get_epoch_nanoseconds); } +struct TemporalInstantPrototypeAdd; +impl Builtin for TemporalInstantPrototypeAdd { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.add; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::add); +} + +struct TemporalInstantPrototypeSubtract; +impl Builtin for TemporalInstantPrototypeSubtract { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.subtract; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::subtract); +} + +struct TemporalInstantPrototypeUntil; +impl Builtin for TemporalInstantPrototypeUntil { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.until; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::until); +} + +struct TemporalInstantPrototypeSince; +impl Builtin for TemporalInstantPrototypeSince { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.since; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalInstantPrototype::since); +} + impl TemporalInstantPrototype { pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); @@ -369,11 +397,15 @@ impl TemporalInstantPrototype { let instant_constructor = intrinsics.temporal_instant(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(3) + .with_property_capacity(7) .with_prototype(object_prototype) .with_constructor_property(instant_constructor) .with_builtin_function_property::() .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() + .with_builtin_function_property::() .build(); } @@ -410,7 +442,43 @@ impl TemporalInstantPrototype { .bind(gc.nogc()); // 3. Return instant.[[EpochNanoseconds]]. let value = instant.inner_instant(agent).epoch_nanoseconds().as_i128(); - todo!() + Ok(BigInt::from_i128(agent, value).into()) + } + + fn add<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + fn subtract<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + fn until<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() + } + + fn since<'gc>( + _agent: &mut Agent, + _this_value: Value, + _args: ArgumentsList, + mut _gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + unimplemented!() } } diff --git a/nova_vm/src/ecmascript/types/language/bigint.rs b/nova_vm/src/ecmascript/types/language/bigint.rs index ef364c53f..bd3c01983 100644 --- a/nova_vm/src/ecmascript/types/language/bigint.rs +++ b/nova_vm/src/ecmascript/types/language/bigint.rs @@ -207,6 +207,15 @@ impl<'a> BigInt<'a> { } } + #[inline] + pub fn from_i128(agent: &mut Agent, value: i128) -> Self { + if let Ok(result) = SmallBigInt::try_from(value) { + Self::SmallBigInt(result) + } else { + agent.heap.create(BigIntHeapData { data: value.into() }) + } + } + #[inline] pub(crate) fn from_num_bigint(agent: &mut Agent, value: num_bigint::BigInt) -> Self { if let Ok(result) = SmallBigInt::try_from(&value) { From 625bfe0a6cedef75b4c1550c50a40f8b85cae58f Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 15:04:23 +0100 Subject: [PATCH 19/25] 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 | 244 ++++++++ .../temporal/instant/instant_prototype.rs | 156 +++++ .../ecmascript/execution/realm/intrinsics.rs | 17 +- 5 files changed, 504 insertions(+), 470 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs 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 new file mode 100644 index 000000000..74f6726e4 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -0,0 +1,244 @@ +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::InstantHeapData, 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::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(); + } +} 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 5e249f796..f4aa91a7e 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -41,7 +41,11 @@ use crate::ecmascript::builtins::structured_data::shared_array_buffer_objects::{ }; #[cfg(feature = "temporal")] use crate::ecmascript::builtins::temporal::{ - TemporalObject, instant::TemporalInstantConstructor, instant::TemporalInstantPrototype, + TemporalObject, + instant::{ + instant_constructor::TemporalInstantConstructor, + instant_prototype::TemporalInstantPrototype, + }, }; #[cfg(feature = "regexp")] use crate::ecmascript::builtins::text_processing::regexp_objects::{ @@ -315,12 +319,11 @@ impl Intrinsics { MathObject::create_intrinsic(agent, realm, gc); #[cfg(feature = "temporal")] - { - TemporalObject::create_intrinsic(agent, realm, gc); - // Instant - TemporalInstantConstructor::create_intrinsic(agent, realm, gc); - TemporalInstantPrototype::create_intrinsic(agent, realm, gc); - } + TemporalObject::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalInstantConstructor::create_intrinsic(agent, realm, gc); + #[cfg(feature = "temporal")] + TemporalInstantPrototype::create_intrinsic(agent, realm, gc); #[cfg(feature = "date")] DatePrototype::create_intrinsic(agent, realm); From 6cd7515797600d98e7b6bd0b986cb75efec07ff3 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:05:27 +0100 Subject: [PATCH 20/25] add skeleton for the rest of Temporal.Instant implementation --- nova_vm/src/builtin_strings | 5 +- .../temporal/instant/instant_constructor.rs | 2 +- .../temporal/instant/instant_prototype.rs | 175 ++++++++++++++++-- 3 files changed, 160 insertions(+), 22 deletions(-) diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 046575547..630387630 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -113,6 +113,7 @@ endsWith entries enumerable EPSILON +#[cfg(feature = "temporal")]equals Error errors #[cfg(any(feature = "annex-b-string", feature = "regexp"))]escape @@ -351,7 +352,7 @@ resolve return reverse revocable -#[cfg(feature = "math")]round +#[cfg(any(feature = "math", feature = "temporal"))]round seal #[cfg(feature = "regexp")]search set @@ -436,6 +437,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 @@ -463,6 +465,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_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs index 74f6726e4..64e71f886 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -173,7 +173,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 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 d0cf252ac2def4533454f8774c6eec357d4fb4a6 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:21:45 +0100 Subject: [PATCH 21/25] 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 f27f3f4903fa92285d0cc73ceb755d51f9514e07 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:23:23 +0100 Subject: [PATCH 22/25] cargo fmt and cargo clippy --- .../builtins/temporal/instant/instant_constructor.rs | 5 ++--- .../builtins/temporal/instant/instant_prototype.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) 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 64e71f886..a174bdacf 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/instant/instant_constructor.rs @@ -185,10 +185,9 @@ impl TemporalInstantConstructor { 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()) + to_big_int(agent, epoch_nanoseconds.unbind(), gc.reborrow()) .unbind()? - .bind(gc.nogc()); - epoch_nanoseconds + .bind(gc.nogc()) }; // 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. let Some(epoch_nanoseconds) = epoch_nanoseconds 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 2d81989b7a2f8d9c99e7586d60be7069146799f8 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:26:15 +0100 Subject: [PATCH 23/25] 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 dcf84a9fa58f1e36bf53497a899332a7715768c1 Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 16:59:09 +0100 Subject: [PATCH 24/25] 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 4744affba08c53e3463466853d9745fc082dcf8d Mon Sep 17 00:00:00 2001 From: Idris Elmi Date: Wed, 29 Oct 2025 17:42:59 +0100 Subject: [PATCH 25/25] 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()?;