mod data;
mod instant_constructor;
mod instant_prototype;
pub(crate) use data::*;
pub(crate) use instant_constructor::*;
pub(crate) use instant_prototype::*;
use temporal_rs::options::{Unit, UnitGroup};
use crate::{
ecmascript::{
Agent, DurationRecord, ExceptionType, Function, InternalMethods, InternalSlots, JsResult,
Object, OrdinaryObject, PreferredType, Primitive, ProtoIntrinsics, String,
TemporalDuration, Value, get_difference_settings, get_options_object, object_handle,
ordinary_populate_from_constructor, temporal_err_to_js_err, to_primitive_object,
to_temporal_duration,
},
engine::{Bindable, GcScope, NoGcScope, Scopable},
heap::{
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct TemporalInstant<'a>(BaseIndex<'a, InstantRecord<'static>>);
object_handle!(TemporalInstant, Instant);
arena_vec_access!(
TemporalInstant,
'a,
InstantRecord,
instants
);
impl TemporalInstant<'_> {
pub(crate) fn inner_instant(self, agent: &Agent) -> &temporal_rs::Instant {
&self.unbind().get(agent).instant
}
}
impl<'a> InternalSlots<'a> for TemporalInstant<'a> {
const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalInstant;
fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
self.get(agent).object_index.unbind()
}
fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
assert!(
self.get_mut(agent)
.object_index
.replace(backing_object)
.is_none()
);
}
}
impl<'a> InternalMethods<'a> for TemporalInstant<'a> {}
impl HeapMarkAndSweep for TemporalInstant<'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 TemporalInstant<'static> {
fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
compactions.instants.shift_weak_index(self.0).map(Self)
}
}
impl<'a> CreateHeapData<InstantRecord<'a>, 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::<InstantRecord<'static>>();
TemporalInstant(BaseIndex::last(&self.instants))
}
}
pub(crate) fn create_temporal_instant<'gc>(
agent: &mut Agent,
epoch_nanoseconds: temporal_rs::Instant,
new_target: Option<Function>,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, TemporalInstant<'gc>> {
let new_target = new_target.unwrap_or_else(|| {
agent
.current_realm_record()
.intrinsics()
.temporal_instant()
.into()
});
let object = agent.heap.create(InstantRecord {
object_index: None,
instant: epoch_nanoseconds,
});
Ok(
TemporalInstant::try_from(ordinary_populate_from_constructor(
agent,
object.unbind().into(),
new_target,
ProtoIntrinsics::TemporalInstant,
gc,
)?)
.unwrap(),
)
}
pub(crate) fn to_temporal_instant<'gc>(
agent: &mut Agent,
item: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, temporal_rs::Instant> {
let item = item.bind(gc.nogc());
let item = if let Ok(item) = Object::try_from(item) {
if let Ok(item) = TemporalInstant::try_from(item) {
return Ok(*item.inner_instant(agent));
}
to_primitive_object(
agent,
item.unbind(),
Some(PreferredType::String),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
} else {
Primitive::try_from(item).unwrap()
};
let Ok(item) = String::try_from(item) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Item is not a String",
gc.into_nogc(),
));
};
temporal_rs::Instant::from_utf8(item.as_bytes(agent))
.map_err(|e| temporal_err_to_js_err(agent, e, gc.into_nogc()))
}
fn add_duration_to_instant<'gc, const IS_ADD: bool>(
agent: &mut Agent,
instant: TemporalInstant,
duration: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, TemporalInstant<'gc>> {
let duration = duration.bind(gc.nogc());
let mut instant = instant.bind(gc.nogc());
let duration = if let Value::Duration(duration) = duration {
duration.get(agent).duration
} else {
let scoped_instant = instant.scope(agent, gc.nogc());
let res = to_temporal_duration(agent, duration.unbind(), gc.reborrow()).unbind()?;
unsafe {
instant = scoped_instant.take(agent);
}
res
};
let ns_result = if IS_ADD {
temporal_rs::Instant::add(instant.inner_instant(agent), &duration)
.map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc()))
.unbind()?
} else {
temporal_rs::Instant::subtract(instant.inner_instant(agent), &duration)
.map_err(|err| temporal_err_to_js_err(agent, err, gc.nogc()))
.unbind()?
};
Ok(create_temporal_instant(agent, ns_result, None, gc).unwrap())
}
fn difference_temporal_instant<'gc, const IS_UNTIL: bool>(
agent: &mut Agent,
instant: TemporalInstant,
other: Value,
options: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, TemporalDuration<'gc>> {
let instant = instant.scope(agent, gc.nogc());
let other = other.bind(gc.nogc());
let options = options.scope(agent, gc.nogc());
let other = to_temporal_instant(agent, other.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let resolved_options = get_options_object(agent, options.get(agent), gc.nogc())
.unbind()?
.bind(gc.nogc());
let duration = if IS_UNTIL {
const UNTIL: bool = true;
let settings = get_difference_settings::<UNTIL>(
agent,
resolved_options.unbind(),
UnitGroup::Time,
&[],
Unit::Nanosecond,
Unit::Second,
gc.reborrow(),
)
.unbind()?;
temporal_rs::Instant::until(instant.get(agent).inner_instant(agent), &other, settings)
} else {
const SINCE: bool = false;
let settings = get_difference_settings::<SINCE>(
agent,
resolved_options.unbind(),
UnitGroup::Time,
&[],
Unit::Nanosecond,
Unit::Second,
gc.reborrow(),
)
.unbind()?;
temporal_rs::Instant::since(instant.get(agent).inner_instant(agent), &other, settings)
};
let gc = gc.into_nogc();
let duration = duration.map_err(|err| temporal_err_to_js_err(agent, err, gc))?;
Ok(agent.heap.create(DurationRecord {
object_index: None,
duration,
}))
}
#[inline(always)]
fn require_internal_slot_temporal_instant<'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,
)),
}
}