mod data;
mod duration_constructor;
mod duration_prototype;
pub(crate) use data::*;
pub(crate) use duration_constructor::*;
pub(crate) use duration_prototype::*;
use crate::{
ecmascript::{
Agent, BUILTIN_STRING_MEMORY, ExceptionType, Function, InternalMethods, InternalSlots,
JsResult, Object, OrdinaryObject, ProtoIntrinsics, String, Value, get, object_handle,
ordinary_populate_from_constructor, temporal_err_to_js_err, to_integer_if_integral,
},
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 TemporalDuration<'a>(BaseIndex<'a, DurationRecord<'static>>);
object_handle!(TemporalDuration, Duration);
arena_vec_access!(
TemporalDuration,
'a,
DurationRecord,
durations
);
impl TemporalDuration<'_> {
pub(crate) fn inner_duration(self, agent: &Agent) -> &temporal_rs::Duration {
&self.unbind().get(agent).duration
}
}
impl<'a> InternalSlots<'a> for TemporalDuration<'a> {
const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::TemporalDuration;
fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
self.unbind().get(agent).object_index
}
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 TemporalDuration<'a> {}
impl HeapMarkAndSweep for TemporalDuration<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.durations.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.durations.shift_index(&mut self.0);
}
}
impl HeapSweepWeakReference for TemporalDuration<'static> {
fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
compactions.durations.shift_weak_index(self.0).map(Self)
}
}
impl<'a> CreateHeapData<DurationRecord<'a>, TemporalDuration<'a>> for Heap {
fn create(&mut self, data: DurationRecord<'a>) -> TemporalDuration<'a> {
self.durations.push(data.unbind());
self.alloc_counter += core::mem::size_of::<DurationRecord<'static>>();
TemporalDuration(BaseIndex::last(&self.durations))
}
}
pub(crate) fn create_temporal_duration<'gc>(
agent: &mut Agent,
duration: temporal_rs::Duration,
new_target: Option<Function>,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, TemporalDuration<'gc>> {
let new_target = new_target.unwrap_or_else(|| {
agent
.current_realm_record()
.intrinsics()
.temporal_duration()
.into()
});
let object = agent.heap.create(DurationRecord {
object_index: None,
duration,
});
Ok(
TemporalDuration::try_from(ordinary_populate_from_constructor(
agent,
object.unbind().into(),
new_target,
ProtoIntrinsics::TemporalDuration,
gc,
)?)
.unwrap(),
)
}
pub(crate) fn to_temporal_duration<'gc>(
agent: &mut Agent,
item: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, temporal_rs::Duration> {
let item = item.bind(gc.nogc());
if let Value::Duration(item) = item {
return Ok(*item.inner_duration(agent));
}
let Ok(item) = Object::try_from(item) else {
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(),
));
};
return temporal_rs::Duration::from_utf8(item.as_bytes(agent))
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()));
};
let partial =
to_temporal_partial_duration_record(agent, item.unbind(), gc.reborrow()).unbind()?;
temporal_rs::Duration::from_partial_duration(partial)
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))
}
pub(crate) fn to_temporal_partial_duration_record<'gc>(
agent: &mut Agent,
temporal_duration_like: Object,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, temporal_rs::partial::PartialDuration> {
let temporal_duration_like = temporal_duration_like.scope(agent, gc.nogc());
let mut result = temporal_rs::partial::PartialDuration::empty();
let days = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.days.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !days.is_undefined() {
let days = to_integer_if_integral(agent, days.unbind(), gc.reborrow()).unbind()? as i64;
result.days = Some(days)
}
let hours = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.hours.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !hours.is_undefined() {
let hours = to_integer_if_integral(agent, hours.unbind(), gc.reborrow()).unbind()? as i64;
result.hours = Some(hours)
}
let microseconds = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.microseconds.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !microseconds.is_undefined() {
let microseconds =
to_integer_if_integral(agent, microseconds.unbind(), gc.reborrow()).unbind()?;
result.microseconds = Some(microseconds as i128);
}
let milliseconds = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.milliseconds.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !milliseconds.is_undefined() {
let milliseconds =
to_integer_if_integral(agent, milliseconds.unbind(), gc.reborrow()).unbind()? as i64;
result.milliseconds = Some(milliseconds)
}
let minutes = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.minutes.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !minutes.is_undefined() {
let minutes =
to_integer_if_integral(agent, minutes.unbind(), gc.reborrow()).unbind()? as i64;
result.minutes = Some(minutes)
}
let months = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.months.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !months.is_undefined() {
let months = to_integer_if_integral(agent, months.unbind(), gc.reborrow()).unbind()? as i64;
result.months = Some(months)
}
let nanoseconds = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.nanoseconds.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !nanoseconds.is_undefined() {
let nanoseconds =
to_integer_if_integral(agent, nanoseconds.unbind(), gc.reborrow()).unbind()?;
result.nanoseconds = Some(nanoseconds as i128);
}
let seconds = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.seconds.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !seconds.is_undefined() {
let seconds =
to_integer_if_integral(agent, seconds.unbind(), gc.reborrow()).unbind()? as i64;
result.seconds = Some(seconds)
}
let weeks = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.weeks.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !weeks.is_undefined() {
let weeks = to_integer_if_integral(agent, weeks.unbind(), gc.reborrow()).unbind()? as i64;
result.weeks = Some(weeks)
}
let years = get(
agent,
temporal_duration_like.get(agent),
BUILTIN_STRING_MEMORY.years.to_property_key(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if !years.is_undefined() {
let years = to_integer_if_integral(agent, years.unbind(), gc.reborrow()).unbind()? as i64;
result.years = Some(years)
}
if result.is_empty() {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Duration must have at least one unit",
gc.into_nogc(),
));
}
Ok(result)
}
#[inline(always)]
pub(crate) fn _require_internal_slot_temporal_duration<'a>(
agent: &mut Agent,
value: Value,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, TemporalDuration<'a>> {
match value {
Value::Duration(duration) => Ok(duration.bind(gc)),
_ => Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Object is not a Temporal Duration",
gc,
)),
}
}