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 c30ce60fd..bbb68c7b2 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 @@ -2,16 +2,20 @@ // 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::options::{RoundingMode, ToStringRoundingOptions, Unit}; + use crate::{ ecmascript::{ - Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, JsResult, - PropertyKey, Realm, String, Value, + Agent, ArgumentsList, BUILTIN_STRING_MEMORY, Behaviour, Builtin, BuiltinGetter, + ExceptionType, JsResult, PropertyKey, Realm, String, Value, builders::OrdinaryObjectBuilder, builtins::temporal::plain_time::{ add_duration_to_time, require_internal_slot_temporal_plain_time, }, + get_options_object, get_rounding_mode_option, get_temporal_fractional_second_digits_option, + get_temporal_unit_valued_option, temporal_err_to_js_err, }, - engine::{Bindable, GcScope, NoGcScope}, + engine::{Bindable, GcScope, NoGcScope, Scopable}, heap::WellKnownSymbols, }; @@ -90,6 +94,13 @@ impl Builtin for TemporalPlainTimePrototypeSubtract { const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::subtract); } +struct TemporalPlainTimePrototypeToString; +impl Builtin for TemporalPlainTimePrototypeToString { + const NAME: String<'static> = BUILTIN_STRING_MEMORY.toString; + const LENGTH: u8 = 1; + const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::to_string); +} + 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>( @@ -228,6 +239,91 @@ impl TemporalPlainTimePrototype { .map(Value::from) } + /// ### [4.3.16 Temporal.PlainTime.prototype.toString ( [ options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tostring) + fn to_string<'gc>( + agent: &mut Agent, + this_value: Value, + args: ArgumentsList, + mut gc: GcScope<'gc, '_>, + ) -> JsResult<'gc, Value<'gc>> { + let options = 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()? + .scope(agent, gc.nogc()); + // 3. Let resolvedOptions be ? GetOptionsObject(options). + let resolved_options = get_options_object(agent, options, gc.nogc()) + .unbind()? + .map(|r| r.scope(agent, gc.nogc())); + + let (digits, rounding_mode, smallest_unit) = + if let Some(resolved_options) = resolved_options { + // 4. NOTE: The following steps read options and perform independent validation + // in alphabetical order (GetTemporalFractionalSecondDigitsOption reads + // "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode"). + // 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions). + let digits = get_temporal_fractional_second_digits_option( + agent, + resolved_options.get(agent), + gc.reborrow(), + ) + .unbind()?; + // 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc). + let rounding_mode = get_rounding_mode_option( + agent, + resolved_options.get(agent), + RoundingMode::Trunc, + gc.reborrow(), + ) + .unbind()?; + // 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", unset). + let smallest_unit = get_temporal_unit_valued_option( + agent, + resolved_options.get(agent), + BUILTIN_STRING_MEMORY.smallestUnit.to_property_key(), + gc.reborrow(), + ) + .unbind()?; + (digits, rounding_mode, smallest_unit) + } else { + Default::default() + }; + + // 8. Perform ? ValidateTemporalUnitValue(smallestUnit, time). + if !smallest_unit.is_none_or(|su| su.is_time_unit()) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "smallestUnit is not a valid time unit", + gc.into_nogc(), + )); + } + // 9. If smallestUnit is hour, throw a RangeError exception. + if smallest_unit == Some(Unit::Hour) { + return Err(agent.throw_exception_with_static_message( + ExceptionType::RangeError, + "smallestUnit is hour", + gc.into_nogc(), + )); + } + + // 10. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits). + // 11. Let roundResult be RoundTime(plainTime.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode). + let options = ToStringRoundingOptions { + precision: digits, + smallest_unit, + rounding_mode: Some(rounding_mode), + }; + // 12. Return TimeRecordToString(roundResult, precision.[[Precision]]). + let plain_time = unsafe { plain_time.take(agent) }; + match plain_time.inner_plain_time(agent).to_ixdtf_string(options) { + Ok(string) => Ok(Value::from_string(agent, string, gc.into_nogc())), + Err(err) => Err(temporal_err_to_js_err(agent, err, gc.into_nogc())), + } + } + 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(); @@ -235,7 +331,7 @@ impl TemporalPlainTimePrototype { let plain_time_constructor = intrinsics.temporal_plain_time(); OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this) - .with_property_capacity(10) + .with_property_capacity(11) .with_prototype(object_prototype) .with_constructor_property(plain_time_constructor) .with_builtin_function_getter_property::() @@ -246,6 +342,7 @@ impl TemporalPlainTimePrototype { .with_builtin_function_getter_property::() .with_builtin_function_property::() .with_builtin_function_property::() + .with_builtin_function_property::() .with_property(|builder| { builder .with_key(WellKnownSymbols::ToStringTag.into())