diff --git a/nova_vm/src/builtin_strings b/nova_vm/src/builtin_strings index 14fbf413c..5d57b4335 100644 --- a/nova_vm/src/builtin_strings +++ b/nova_vm/src/builtin_strings @@ -181,6 +181,7 @@ get description #[cfg(feature = "regexp")]get ignoreCase #[cfg(feature = "array-buffer")]get length #[cfg(feature = "array-buffer")]get maxByteLength +#[cfg(feature = "temporal")]get minute #[cfg(feature = "regexp")]get multiline #[cfg(feature = "array-buffer")]get resizable get size @@ -297,6 +298,7 @@ MAX_VALUE message #[cfg(feature = "temporal")]microseconds #[cfg(feature = "temporal")]milliseconds +#[cfg(feature = "temporal")]minute #[cfg(feature = "temporal")]minutes #[cfg(feature = "temporal")]months #[cfg(feature = "math")]min diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index be1ce4ae3..784ad266f 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -12,9 +12,10 @@ pub(crate) use plain_time_prototype::*; use crate::{ ecmascript::{ - Agent, InternalMethods, InternalSlots, OrdinaryObject, ProtoIntrinsics, object_handle, + Agent, ExceptionType, InternalMethods, InternalSlots, JsResult, OrdinaryObject, + ProtoIntrinsics, Value, object_handle, }, - engine::Bindable, + engine::{Bindable, NoGcScope}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -33,8 +34,8 @@ arena_vec_access!( ); impl TemporalPlainTime<'_> { - pub(crate) fn _inner_plain_time(self, agent: &Agent) -> &temporal_rs::PlainTime { - &self.unbind().get(agent)._plain_time + pub(crate) fn inner_plain_time(self, agent: &Agent) -> &temporal_rs::PlainTime { + &self.unbind().get(agent).plain_time } } @@ -77,3 +78,19 @@ impl<'a> CreateHeapData, TemporalPlainTime<'a>> for Heap { TemporalPlainTime(BaseIndex::last(&self.plain_times)) } } + +#[inline(always)] +fn require_internal_slot_temporal_plain_time<'a>( + agent: &mut Agent, + value: Value, + gc: NoGcScope<'a, '_>, +) -> JsResult<'a, TemporalPlainTime<'a>> { + match value { + Value::PlainTime(plain_time) => Ok(plain_time.bind(gc)), + _ => Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Object is not a Temporal PlainTime", + gc, + )), + } +} diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs index 396fb9d0c..d7e3751e9 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/data.rs @@ -11,14 +11,14 @@ use crate::{ #[derive(Debug, Clone, Copy)] pub struct PlainTimeRecord<'a> { pub(crate) object_index: Option>, - pub(crate) _plain_time: temporal_rs::PlainTime, + pub(crate) plain_time: temporal_rs::PlainTime, } impl PlainTimeRecord<'_> { pub fn default() -> Self { Self { object_index: None, - _plain_time: temporal_rs::PlainTime::try_new(0, 0, 0, 0, 0, 0).unwrap(), + plain_time: temporal_rs::PlainTime::try_new(0, 0, 0, 0, 0, 0).unwrap(), } } } @@ -30,7 +30,7 @@ impl HeapMarkAndSweep for PlainTimeRecord<'static> { fn mark_values(&self, queues: &mut WorkQueues) { let Self { object_index, - _plain_time: _, + plain_time: _, } = self; object_index.mark_values(queues); @@ -38,7 +38,7 @@ impl HeapMarkAndSweep for PlainTimeRecord<'static> { fn sweep_values(&mut self, compactions: &CompactionLists) { let Self { object_index, - _plain_time: _, + plain_time: _, } = self; object_index.sweep_values(compactions); diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs index dfdaa712f..71c69df95 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time/plain_time_constructor.rs @@ -5,10 +5,11 @@ use crate::{ ecmascript::{ Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, - BuiltinIntrinsicConstructor, JsResult, Object, Realm, String, Value, - builders::BuiltinFunctionBuilder, + BuiltinIntrinsicConstructor, ExceptionType, Function, JsResult, Object, Realm, String, + Value, builders::BuiltinFunctionBuilder, temporal_err_to_js_err, + to_integer_with_truncation, }, - engine::{GcScope, NoGcScope}, + engine::{Bindable as _, GcScope, NoGcScope, Scopable}, heap::IntrinsicConstructorIndexes, }; @@ -28,11 +29,120 @@ impl TemporalPlainTimeConstructor { fn constructor<'gc>( agent: &mut Agent, _: Value, - _args: ArgumentsList, - _new_target: Option, + args: ArgumentsList, + new_target: Option, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Temporal.PlainTime", gc.into_nogc())) + let years = args.get(1).scope(agent, gc.nogc()); + let months = args.get(2).scope(agent, gc.nogc()); + let weeks = args.get(3).scope(agent, gc.nogc()); + let days = args.get(4).scope(agent, gc.nogc()); + let hours = args.get(5).scope(agent, gc.nogc()); + let minutes = args.get(6).scope(agent, gc.nogc()); + let seconds = args.get(7).scope(agent, gc.nogc()); + let milliseconds = args.get(8).scope(agent, gc.nogc()); + let microseconds = args.get(9).scope(agent, gc.nogc()); + let nanoseconds = args.get(10).scope(agent, gc.nogc()); + let new_target = new_target.bind(gc.nogc()); + + // 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.PlainTime constructor without new is forbidden", + gc.into_nogc(), + )); + }; + + let Ok(new_target) = Function::try_from(new_target) else { + unreachable!() + }; + let new_target = new_target.scope(agent, gc.nogc()); + + // 2. If hour is undefined, set hour to 0; else set hour to ? ToIntegerWithTruncation(hour). + let h = if hours.get(agent).is_undefined() { + Ok(0) + } else { + u8::try_from( + to_integer_with_truncation(agent, hours.get(agent), gc.reborrow()).unbind()?, + ) + }; + + // 3. If minute is undefined, set minute to 0; else set minute to ? ToIntegerWithTruncation(minute). + let m = if minutes.get(agent).is_undefined() { + Ok(0) + } else { + u8::try_from( + to_integer_with_truncation(agent, minutes.get(agent), gc.reborrow()).unbind()?, + ) + }; + + // 4. If second is undefined, set second to 0; else set second to ? ToIntegerWithTruncation(second). + let s = if seconds.get(agent).is_undefined() { + Ok(0) + } else { + u8::try_from( + to_integer_with_truncation(agent, seconds.get(agent), gc.reborrow()).unbind()?, + ) + }; + + // 5. If millisecond is undefined, set millisecond to 0; else set millisecond to ? ToIntegerWithTruncation(millisecond). + let ms = if milliseconds.get(agent).is_undefined() { + Ok(0) + } else { + u16::try_from( + to_integer_with_truncation(agent, milliseconds.get(agent), gc.reborrow()) + .unbind()?, + ) + }; + + // 6. If microsecond is undefined, set microsecond to 0; else set microsecond to ? ToIntegerWithTruncation(microsecond). + let mis = if microseconds.get(agent).is_undefined() { + Ok(0) + } else { + u16::try_from( + to_integer_with_truncation(agent, microseconds.get(agent), gc.reborrow()) + .unbind()?, + ) + }; + + // 7. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to ? ToIntegerWithTruncation(nanosecond). + let ns = if nanoseconds.get(agent).is_undefined() { + Ok(0) + } else { + u16::try_from( + to_integer_with_truncation(agent, nanoseconds.get(agent), gc.reborrow()) + .unbind()?, + ) + }; + + // 8. If IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond) is false, throw a RangeError exception. + + // 9. Let time be CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond). + let time = if let ( + Ok(hour), + Ok(minute), + Ok(second), + Ok(millisecond), + Ok(microsecond), + Ok(nanosecond), + ) = (h, m, s, ms, mis, ns) + { + temporal_rs::PlainTime::try_new( + hour, + minute, + second, + millisecond, + microsecond, + nanosecond, + ) + .map_err(|err| temporal_err_to_js_err(agent, err, gc)) + .unbind()? + } else { + todo!() // TODO: create range error + }; + + // 10. Return ? CreateTemporalTime(time, NewTarget). } pub(crate) fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _gc: NoGcScope) { 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 a8ef313cb..3fccbb185 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 @@ -3,23 +3,54 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::{ - ecmascript::{Agent, BUILTIN_STRING_MEMORY, Realm, builders::OrdinaryObjectBuilder}, - engine::NoGcScope, + 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, + }, + engine::{GcScope, NoGcScope}, heap::WellKnownSymbols, }; pub(crate) struct TemporalPlainTimePrototype; + +struct TemporalPlainTimePrototypeGetMinute; +impl Builtin for TemporalPlainTimePrototypeGetMinute { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.get_minute; + const KEY: Option> = Some(BUILTIN_STRING_MEMORY.minute.to_property_key()); + const LENGTH: u8 = 0; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::get_minute); +} +impl BuiltinGetter for TemporalPlainTimePrototypeGetMinute {} + impl TemporalPlainTimePrototype { - pub fn create_intrinsic(agent: &mut Agent, realm: Realm<'static>, _: NoGcScope) { + /// ### [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>( + agent: &mut Agent, + this_value: Value, + _: ArgumentsList, + gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let gc = gc.into_nogc(); + // 1. Let plainTime be the this value. + // 2. Perform ? RequireInternalSlot(plainTime, [[InitializedTemporalTime]]). + let plain_time = require_internal_slot_temporal_plain_time(agent, this_value, gc)?; + // 3. Return 𝔽(plainTime.[[Time]].[[Minute]]). + let value = plain_time.inner_plain_time(agent).minute(); + Ok(value.into()) + } + + 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(); let object_prototype = intrinsics.object_prototype(); let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(2) + .with_property_capacity(3) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) + .with_builtin_function_getter_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into()) diff --git a/tests/expectations.json b/tests/expectations.json index c518a7e76..865339d93 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -3980,8 +3980,6 @@ "built-ins/Temporal/PlainTime/prototype/microsecond/prop-desc.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/millisecond/branding.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/millisecond/prop-desc.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/minute/branding.js": "FAIL", - "built-ins/Temporal/PlainTime/prototype/minute/prop-desc.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/nanosecond/branding.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/nanosecond/prop-desc.js": "FAIL", "built-ins/Temporal/PlainTime/prototype/round/branding.js": "FAIL", diff --git a/tests/metrics.json b/tests/metrics.json index e52e0bc4f..8fb7a2d4a 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,8 +1,8 @@ { "results": { "crash": 52, - "fail": 6971, - "pass": 40329, + "fail": 6969, + "pass": 40331, "skip": 3326, "timeout": 18, "unresolved": 37