From 9b3663bcb6d9c0cea984750287abf2e03fd36cf1 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Fri, 10 Apr 2026 11:40:06 +0200 Subject: [PATCH 1/3] PlainTime.prototype.add and add_duration_to_time --- .../builtins/temporal/plain_time.rs | 51 ++++++++++++++++++- .../plain_time/plain_time_prototype.rs | 40 +++++++++++++-- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index 29ad116e7..f7359f306 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -14,8 +14,9 @@ use crate::{ ecmascript::{ Agent, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, OrdinaryObject, ProtoIntrinsics, Value, object_handle, ordinary_populate_from_constructor, + temporal_err_to_js_err, to_temporal_duration, }, - engine::{Bindable, GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -133,3 +134,51 @@ pub(crate) fn create_temporal_plain_time<'gc>( .unwrap(), ) } + +/// [4.5.18 AddDurationToTime ( operation, temporalTime, temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtotime) +/// +/// The abstract operation AddDurationToTime takes arguments operation +/// (either add or subtract), temporalTime (a Temporal.PlainTime), and +/// temporalDurationLike (an ECMAScript language value) and returns either +/// a normal completion containing a Temporal.PlainTime or a throw completion. +/// It adds/subtracts temporalDurationLike to/from temporalTime, returning a +/// point in time that is in the future/past relative to temporalTime. +/// It performs the following steps when called: +fn add_duration_to_time<'gc, const IS_ADD: bool>( + agent: &mut Agent, + plan_time: TemporalPlainTime, + duration: Value, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, TemporalPlainTime<'gc>> { + let duration = duration.bind(gc.nogc()); + let mut plain_time = plan_time.bind(gc.nogc()); + + // 1. Let duration be ? ToTemporalDuration(temporalDurationLike). + let duration = if let Value::Duration(duration) = duration { + duration.get(agent).duration + } else { + let scoped_instant = plain_time.scope(agent, gc.nogc()); + let res = to_temporal_duration(agent, duration.unbind(), gc.reborrow()).unbind()?; + // SAFETY: not shared + unsafe { + plain_time = scoped_instant.take(agent); + } + res + }; + + // If operation is subtract, set duration to CreateNegatedTemporalDuration(duration). + // 3. Let internalDuration be ToInternalDurationRecord(duration). + // 4. Let result be AddTime(temporalTime.[[Time]], internalDuration.[[Time]]). + let ns_result = if IS_ADD { + temporal_rs::PlainTime::add(plain_time.inner_plain_time(agent), &duration) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc())) + .unbind()? + } else { + temporal_rs::PlainTime::subtract(plain_time.inner_plain_time(agent), &duration) + .map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc())) + .unbind()? + }; + + // 5. Return ! CreateTemporalTime(result). + Ok(create_temporal_plain_time(agent, ns_result, None, gc).unwrap()) +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs index 5b2aeb211..f3114480a 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs @@ -5,10 +5,13 @@ use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, JsResult, - PropertyKey, Realm, String, Value, builders::OrdinaryObjectBuilder, - builtins::temporal::plain_time::require_internal_slot_temporal_plain_time, + PropertyKey, Realm, String, Value, + builders::OrdinaryObjectBuilder, + builtins::temporal::plain_time::{ + add_duration_to_time, require_internal_slot_temporal_plain_time, + }, }, - engine::{GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope}, heap::WellKnownSymbols, }; @@ -73,6 +76,13 @@ impl Builtin for TemporalPlainTimePrototypeGetNanosecond { } impl BuiltinGetter for TemporalPlainTimePrototypeGetNanosecond {} +struct TemporalPlainTimePrototypeAdd; +impl Builtin for TemporalPlainTimePrototypeAdd { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.add; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::add); +} + impl TemporalPlainTimePrototype { /// ### [4.3.4 get Temporal.PlainTime.prototype.minute](https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute) pub(crate) fn get_minute<'gc>( @@ -169,6 +179,27 @@ impl TemporalPlainTimePrototype { Ok(value.into()) } + /// ### [4.3.9 Temporal.PlainTime.prototype.add ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.add) + fn add<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let duration = args.get(0).bind(gc.nogc()); + // 1. Let plainTime be the this value. + let plain_time = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]). + let plain_time = + require_internal_slot_temporal_plain_time(agent, plain_time.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? AddDurationToTime(add, plainTime, temporalDurationLike). + const ADD: bool = true; + add_duration_to_time::(agent, plain_time.unbind(), duration.unbind(), gc) + .map(Value::from) + } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let this = intrinsics.temporal_plain_time_prototype(); @@ -176,7 +207,7 @@ impl TemporalPlainTimePrototype { let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(8) + .with_property_capacity(9) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) .with_builtin_function_getter_property::() @@ -185,6 +216,7 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .with_builtin_function_getter_property::() .with_builtin_function_getter_property::() + .with_builtin_function_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into()) From e2abb16a2f926304ff27cabfbb18f72aa46e62a2 Mon Sep 17 00:00:00 2001 From: jesperkha Date: Fri, 10 Apr 2026 11:46:25 +0200 Subject: [PATCH 2/3] PlainTime.prototype.subtract --- .../plain_time/plain_time_prototype.rs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs index f3114480a..e2f366e30 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_prototype.rs @@ -83,6 +83,13 @@ impl Builtin for TemporalPlainTimePrototypeAdd { const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::add); } +struct TemporalPlainTimePrototypeSubtract; +impl Builtin for TemporalPlainTimePrototypeSubtract { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.subtract; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::subtract); +} + impl TemporalPlainTimePrototype { /// ### [4.3.4 get Temporal.PlainTime.prototype.minute](https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute) pub(crate) fn get_minute<'gc>( @@ -200,6 +207,27 @@ impl TemporalPlainTimePrototype { .map(Value::from) } + /// ### [4.3.10 Temporal.PlainTime.prototype.subtract ( temporalDurationLike )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract) + fn subtract<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let duration = args.get(0).bind(gc.nogc()); + // 1. Let plainTime be the this value. + let plain_time = this_value.bind(gc.nogc()); + // 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]). + let plain_time = + require_internal_slot_temporal_plain_time(agent, plain_time.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + // 3. Return ? AddDurationToTime(subtract, plainTime, temporalDurationLike). + const ADD: bool = false; + add_duration_to_time::(agent, plain_time.unbind(), duration.unbind(), gc) + .map(Value::from) + } + pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { let intrinsics = agent.get_realm_record_by_id(realm).intrinsics(); let this = intrinsics.temporal_plain_time_prototype(); @@ -207,7 +235,7 @@ impl TemporalPlainTimePrototype { let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(9) + .with_property_capacity(10) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) .with_builtin_function_getter_property::() @@ -217,6 +245,7 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .with_builtin_function_getter_property::() .with_builtin_function_property::() + .with_builtin_function_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into()) From d7a1b6c32d4879a37c252e8653d25bb4057baba1 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 12 Apr 2026 19:19:53 +0300 Subject: [PATCH 3/3] chore(test262): Update expectations --- tests/expectations.json | 26 +------------------------- tests/metrics.json | 6 +++--- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index 298bfcb8c..63d771e26 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -3899,9 +3899,6 @@ "built-ins/Temporal/PlainTime/minute-undefined.js": "FAIL", "built-ins/Temporal/PlainTime/nanosecond-undefined.js": "FAIL", "built-ins/Temporal/PlainTime/negative-infinity-throws-rangeerror.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/argument-duration-max.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/argument-duration-out-of-range.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/argument-duration-precision-exact-numerical-values.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/argument-duration.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/argument-higher-units.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/argument-invalid-property.js": "FAIL", @@ -3909,25 +3906,16 @@ "built-ins/Temporal/PlainTime/prototype/add/argument-not-object.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/argument-object.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/argument-singular-properties.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/argument-string-duration-too-large.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/argument-string-fractional-units-rounding-mode.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/argument-string-negative-fractional-units.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/argument-string.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/balance-negative-time-units.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/blank-duration.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/branding.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/builtin.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/infinity-throws-rangeerror.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/length.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/name.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/negative-infinity-throws-rangeerror.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/non-integer-throws-rangeerror.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/not-a-constructor.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/options-ignored.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/order-of-operations.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/precision-exact-mathematical-values-1.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/precision-exact-mathematical-values-2.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/add/prop-desc.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/add/subclassing-ignored.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/equals/argument-cast.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/equals/argument-number.js": "FAIL", @@ -4070,9 +4058,6 @@ "built-ins/Temporal/PlainTime/prototype/since/smallestunit-undefined.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/since/smallestunit-wrong-type.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/since/year-zero.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/argument-duration-max.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/argument-duration-out-of-range.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/argument-duration-precision-exact-numerical-values.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/argument-duration.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/argument-higher-units.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/argument-invalid-property.js": "FAIL", @@ -4080,25 +4065,16 @@ "built-ins/Temporal/PlainTime/prototype/subtract/argument-not-object.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/argument-object.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/argument-singular-properties.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/argument-string-duration-too-large.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/argument-string-fractional-units-rounding-mode.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/argument-string-negative-fractional-units.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/argument-string.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/balance-negative-time-units.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/blank-duration.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/branding.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/builtin.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/infinity-throws-rangeerror.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/length.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/name.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/negative-infinity-throws-rangeerror.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/non-integer-throws-rangeerror.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/not-a-constructor.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/options-ignored.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/order-of-operations.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/precision-exact-mathematical-values-1.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/precision-exact-mathematical-values-2.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/subtract/prop-desc.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/subtract/subclassing-ignored.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/toJSON/basic.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/toJSON/branding.js": "FAIL", @@ -7026,4 +7002,4 @@ "staging/sm/syntax/yield-as-identifier.js": "FAIL", "staging/source-phase-imports/import-source-source-text-module.js": "FAIL", "staging/top-level-await/tla-hang-entry.js": "FAIL" -} +} \ No newline at end of file diff --git a/tests/metrics.json b/tests/metrics.json index 7588cceb5..1224f17e2 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,11 +1,11 @@ { "results": { "crash": 52, - "fail": 6920, - "pass": 40380, + "fail": 6896, + "pass": 40404, "skip": 3326, "timeout": 18, "unresolved": 37 }, "total": 50733 -} +} \ No newline at end of file