Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 169 additions & 3 deletions nova_vm/src/ecmascript/builtins/temporal/plain_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ mod plain_time_prototype;
pub(crate) use data::*;
pub(crate) use plain_time_constructor::*;
pub(crate) use plain_time_prototype::*;
use sonic_rs::{Object, value::object::IterMut};
use temporal_rs::{
duration,
options::{Unit, UnitGroup},
};

use crate::{
ecmascript::{
Agent, ExceptionType, Function, InternalMethods, InternalSlots, JsResult, OrdinaryObject,
ProtoIntrinsics, Value, object_handle, ordinary_populate_from_constructor,
Agent, DurationRecord, ExceptionType, Function, InternalMethods, InternalSlots, JsResult,
Object, OrdinaryObject, ProtoIntrinsics, String, TemporalDuration, Value,
get_difference_settings, get_options_object, object_handle,
ordinary_populate_from_constructor, temporal_err_to_js_err,
},
engine::{Bindable, GcScope, NoGcScope},
engine::{Bindable, GcScope, NoGcScope, Scopable},
heap::{
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
Expand Down Expand Up @@ -133,3 +140,162 @@ pub(crate) fn create_temporal_plain_time<'gc>(
.unwrap(),
)
}

/// ### [4.5.6 ToTemporalTime ( item [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime)
///
/// The abstract operation ToTemporalTime takes argument item (an ECMAScript language value) and optional argument
/// options (an ECMAScript language value) and returns either a normal completion containing a Temporal.PlainTime
/// or a throw Completion. Converts item to a new Temporal.PlainTime instance if possible, and throws otherwise.
pub(crate) fn to_temporal_time<'gc>(
agent: &mut Agent,
item: Value,
options: Option<Value>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, temporal_rs::PlainTime> {
let item = item.bind(gc.nogc());

// 1. If options is not present, set options to undefined.
let options = options.unwrap_or(Value::Undefined);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Missing bind call here.


// 2. If item is an Object, then
if let Ok(item) = Object::try_from(item) {
// a. If item has an [[InitializedTemporalTime]] internal slot, then
if let Ok(time) = TemporalPlainTime::try_from(item) {
// i. Let resolvedOptions be ? GetOptionsObject(options).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: time is invalidated by get_temporal_overflow_option call and would need to be scoped to avoid that problem: better choice is to just get the inner_plain_time value out and store it on the stack here at the entry point of this branch, and return it after we've called get_temporal_overflow_option. A temporal_rs::PlainTime on the stack does not care one whit about / cannot be invalidated by GC.

let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Missing .bind() call after .unbind()?.

// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;
// iii. Return ! CreateTemporalTime(item.[[Time]]).
return Ok(*time.inner_plain_time(agent));
}

// b. If item has an [[InitializedTemporalDateTime]] internal slot, then
// i. Let resolvedOptions be ? GetOptionsObject(options).
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
// iii. Return ! CreateTemporalTime(item.[[ISODateTime]].[[Time]]).

// c. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]).
// ii. Let resolvedOptions be ? GetOptionsObject(options).
// iii. Perform ? GetTemporalOverflowOption(resolvedOptions).
// iv. Return ! CreateTemporalTime(isoDateTime.[[Time]]).

// d. Let result be ? ToTemporalTimeRecord(item).
let result = to_temporal_time_record(agent, item.unbind(), gc.reborrow()).unbind()?;
// e. Let resolvedOptions be ? GetOptionsObject(options).
let resolved_options = get_options_object(agent, options, gc.nogc()).unbind()?;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Missing .bind() call after .unbind()?.

// f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
let overflow =
get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;
// g. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], overflow).
return result
.regulate(overflow)
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()));
}

// 3. Else,
// a. If item is not a String, throw a TypeError exception.
let Ok(item) = String::try_from(item) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Item is not a String",
Comment thread
aapoalas marked this conversation as resolved.
gc.into_nogc(),
));
};

// b. Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »).
// c. Assert: parseResult.[[Time]] is not start-of-day.
// d. Set result to parseResult.[[Time]].
// e. NOTE: A successful parse using TemporalTimeString guarantees absence of ambiguity with respect to any ISO 8601 date-only, year-month, or month-day representation.
let result = temporal_rs::PlainTime::from_utf8(item.as_bytes(agent))
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))?;
// f. Let resolvedOptions be ? GetOptionsObject(options).
let resolved_options = get_options_object(agent, options, gc.reborrow()).unbind()?;
// g. Perform ? GetTemporalOverflowOption(resolvedOptions).
get_temporal_overflow_option(agent, resolved_options, gc.reborrow()).unbind()?;

// 4. Return ! CreateTemporalTime(result).
Ok(result)
}

/// ### [4.5.17 DifferenceTemporalPlainTime ( operation, temporalTime, other, options )](https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaintime)
/// The abstract operation DifferenceTemporalPlainTime takes arguments
/// operation (either since or until), temporalTime (a Temporal.PlainTime),
/// other (an ECMAScript language value), and options
/// (an ECMAScript language value) and returns either
/// a normal completion containing a Temporal.Duration or a
/// throw completion. It computes the difference between the
/// two times represented by temporalTime and other, optionally
/// rounds it, and returns it as a Temporal.Duration object.
fn difference_temporal_plain_time<'gc, const IS_UNTIL: bool>(
agent: &mut Agent,
plain_time: TemporalPlainTime,
other: Value,
options: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, TemporalDuration<'gc>> {
let plain_time = plain_time.scope(agent, gc.nogc());
let other = other.bind(gc.nogc());
let options = options.scope(agent, gc.nogc());
// 1. Set other to ? ToTemporalTime(other).
let other = to_temporal_time(agent, other.unbind(), options, gc.reborrow());
// 2. Let resolvedOptions be ? GetOptionsObject(options).
let resolved_option = get_options_object(agent, options.get(agent), gc.nogc())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: You can take options here.

.unbind()?
.bind(gc.nogc());
// 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions,
// time, « », nanosecond, hour).
// 4. Let timeDuration be
// DifferenceTime(temporalTime.[[Time]], other.[[Time]]).
// 5. Set timeDuration to ! RoundTimeDuration(timeDuration,
// settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
// 6. Let duration be
// CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
// 7. Let result be !
// TemporalDurationFromInternal(duration, settings.[[LargestUnit]]).
// 8. If operation is since, set result to
// CreateNegatedTemporalDuration(result).
let duration = if IS_UNTIL {
const UNTIL: bool = true;
let settings = get_difference_settings::<UNTIL>(
agent,
resolved_option.unbind(),
UnitGroup::Time,
&[],
Unit::Nanosecond,
Unit::Hour,
gc.reborrow(),
)
.unbind()?;
temporal_rs::PlainTime::until(
plain_time.get(agent).inner_plain_time(agent),
&other.unbind()?,
settings,
)
} else {
const SINCE: bool = false;
let settings = get_difference_settings::<SINCE>(
agent,
resolved_option.unbind(),
UnitGroup::Time,
&[],
Unit::Nanosecond,
Unit::Hour,
gc.reborrow(),
)
.unbind()?;
temporal_rs::PlainTime::since(
plain_time.get(agent).inner_plain_time(agent),
&other.unbind()?,
settings,
)
};
let gc = gc.into_nogc();
let duration = duration.map_err(|err| temporal_err_to_js_err(agent, err, gc))?;

// 9. Return result.
Ok(agent.heap.create(DurationRecord {
object_index: None,
duration,
}))
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ 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,
builtins::temporal::plain_time::{self, require_internal_slot_temporal_plain_time},
Comment thread
0Zeno marked this conversation as resolved.
},
engine::{GcScope, NoGcScope},
engine::{Bindable, GcScope, NoGcScope},
heap::WellKnownSymbols,
};

Expand Down Expand Up @@ -73,6 +73,20 @@ impl Builtin for TemporalPlainTimePrototypeGetNanosecond {
}
impl BuiltinGetter for TemporalPlainTimePrototypeGetNanosecond {}

struct TemporalPlainTimePrototypeUntil;
impl Builtin for TemporalPlainTimePrototypeUntil {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.until;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::until);
}

struct TemporalPlainTimePrototypeSince;
impl Builtin for TemporalPlainTimePrototypeSince {
const NAME: String<'static> = BUILTIN_STRING_MEMORY.since;
const LENGTH: u8 = 1;
const BEHAVIOUR: Behaviour = Behaviour::Regular(TemporalPlainTimePrototype::since);
}

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>(
Expand Down Expand Up @@ -169,14 +183,68 @@ impl TemporalPlainTimePrototype {
Ok(value.into())
}

/// ### [4.3.12 Temporal.PlainTime.prototype.until ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until)
fn until<'gc>(
agent: &mut Agent,
this_value: Value,
args: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>>{
let other = args.get(0).bind(gc.nogc());
let options = args.get(1).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 ? DifferenceTemporalPlainTime(until, plainTime, other, options).
const UNTIL: bool = true;
difference_temporal_plain_time::<UNTIL>(
agent,
plain_time.unbind(),
other.unbind(),
options.unbind(),
gc,
)
.map(Value::from)
}

/// ### [4.3.13 Temporal.PlainTime.prototype.since ( other [ , options ] )](https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since)
fn since<'gc>(
agent: &mut Agent,
this_value: Value,
args: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let other = args.get(0).bind(gc.nogc());
let options = args.get(1).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 ? DifferenceTemporalPlainTime(since, instant, other, options).
const SINCE: bool = false;
difference_temporal_plain_time::<SINCE>(
agent,
plain_time.unbind(),
other.unbind(),
options.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();
let object_prototype = intrinsics.object_prototype();
let plain_time_constructor = intrinsics.temporal_plain_time();

OrdinaryObjectBuilder::new_intrinsic_object(agent, realm, this)
.with_property_capacity(8)
.with_property_capacity(10)
.with_prototype(object_prototype)
.with_constructor_property(plain_time_constructor)
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetHour>()
Expand All @@ -185,6 +253,8 @@ impl TemporalPlainTimePrototype {
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetMicrosecond>()
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetNanosecond>()
.with_builtin_function_getter_property::<TemporalPlainTimePrototypeGetMillisecond>()
.with_builtin_function_property::<TemporalPlainTimePrototypeSince>()
.with_builtin_function_property::<TemporalPlainTimePrototypeUntil>()
.with_property(|builder| {
builder
.with_key(WellKnownSymbols::ToStringTag.into())
Expand Down