use core::ops::ControlFlow;
use std::{marker::PhantomData, ptr::NonNull};
use crate::{
ecmascript::{
Agent, ArgumentsList, Array, AsyncFromSyncIteratorPrototype, BUILTIN_STRING_MEMORY,
ExceptionType, InternalMethods, IteratorRecord, JsError, JsResult, Object, OrdinaryObject,
Promise, PropertyKey, PropertyKeySet, ScopedArgumentsList, TryError, TryGetResult,
TryResult, Value, call_function, create_async_from_sync_iterator,
create_iter_result_object, get, get_iterator_from_method, get_method, get_object_method,
throw_not_callable, to_boolean, try_get,
},
engine::{Bindable, GcScope, NoGcScope, Scopable, ScopeToken, Scoped, bindable_handle},
heap::{CompactionLists, HeapMarkAndSweep, WellKnownSymbols, WorkQueues},
};
pub(crate) struct ActiveIterator<'a> {
scope: PhantomData<&'a ScopeToken>,
}
fn convert_to_iter_result_object<'a>(
agent: &mut Agent,
result: Option<Value<'a>>,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, OrdinaryObject<'a>> {
if let Some(result) = result {
create_iter_result_object(agent, result, false, gc)
} else {
create_iter_result_object(agent, Value::Undefined, true, gc)
}
}
impl<'a> ActiveIterator<'a> {
pub(super) fn new(agent: &Agent, _: NoGcScope<'_, 'a>) -> Self {
let iterator = Self { scope: PhantomData };
let _ = iterator.get(agent);
iterator
}
fn reborrow(&'a self) -> ActiveIterator<'a> {
Self { scope: PhantomData }
}
pub(super) fn call_next<'gc>(
&mut self,
agent: &mut Agent,
value: Option<Value>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
match self.get(agent) {
VmIteratorRecord::InvalidIterator { .. } => {
Err(throw_not_callable(agent, gc.into_nogc()).unbind())
}
VmIteratorRecord::ObjectProperties(_) => {
unreachable!()
}
VmIteratorRecord::ArrayValues(_) => ArrayValuesIterator::new(self)
.next(agent, gc.reborrow())
.unbind()
.and_then(|r| convert_to_iter_result_object(agent, r.unbind(), gc.into_nogc()))
.map(|o| o.into()),
VmIteratorRecord::AsyncFromSyncGenericIterator(_) => {
Ok(AsyncFromSyncGenericIterator::new(self)
.call_next(agent, value, gc)
.into())
}
VmIteratorRecord::GenericIterator(_) => {
GenericIterator::new(self).call_next(agent, value, gc)
}
VmIteratorRecord::SliceIterator(slice_ref) => {
let gc = gc.into_nogc();
convert_to_iter_result_object(agent, slice_ref.unshift(agent, gc), gc)
.map(|o| o.into())
}
VmIteratorRecord::EmptySliceIterator => {
create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc())
.map(|o| o.into())
}
}
}
pub(super) fn throw<'gc>(
&mut self,
agent: &mut Agent,
received_value: Value,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
match self.get(agent) {
VmIteratorRecord::ArrayValues(_) => {
ArrayValuesIterator::new(self).throw(agent, received_value, gc)
}
VmIteratorRecord::AsyncFromSyncGenericIterator(_) => Ok(Some(
AsyncFromSyncGenericIterator::new(self)
.throw(agent, received_value, gc)
.into(),
)),
VmIteratorRecord::GenericIterator(_) => {
GenericIterator::new(self).throw(agent, received_value, gc)
}
_ => unreachable!(),
}
}
pub(super) fn r#return<'gc>(
&mut self,
agent: &mut Agent,
received_value: Option<Value>,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
match self.get(agent) {
VmIteratorRecord::ArrayValues(_) => {
ArrayValuesIterator::new(self).r#return(agent, received_value, gc)
}
VmIteratorRecord::AsyncFromSyncGenericIterator(_) => Ok(Some(
AsyncFromSyncGenericIterator::new(self)
.r#return(agent, received_value, gc)
.into(),
)),
VmIteratorRecord::GenericIterator(IteratorRecord { iterator, .. })
| VmIteratorRecord::InvalidIterator { iterator } => {
GenericIterator::r#return(agent, *iterator, received_value, gc)
}
_ => unreachable!(),
}
}
pub(super) fn step_value<'gc>(
&mut self,
agent: &mut Agent,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
match self.get(agent) {
VmIteratorRecord::InvalidIterator { .. } => {
Err(throw_not_callable(agent, gc.into_nogc()).unbind())
}
VmIteratorRecord::ObjectProperties(_) => {
ObjectPropertiesIterator::new(self).step_value(agent, gc)
}
VmIteratorRecord::ArrayValues(_) => ArrayValuesIterator::new(self).next(agent, gc),
VmIteratorRecord::AsyncFromSyncGenericIterator(_) => {
unreachable!()
}
VmIteratorRecord::GenericIterator(_) => {
GenericIterator::new(self).step_value(agent, gc)
}
VmIteratorRecord::SliceIterator(slice_ref) => {
Ok(slice_ref.unshift(agent, gc.into_nogc()))
}
VmIteratorRecord::EmptySliceIterator => Ok(None),
}
}
pub(super) fn remaining_length_estimate(&mut self, agent: &mut Agent) -> Option<usize> {
self.get(agent).remaining_length_estimate(agent)
}
pub(super) fn get<'agent>(&self, agent: &'agent Agent) -> &'agent VmIteratorRecord<'a> {
unsafe { agent.vm_stack.last().expect("No VM found").as_ref() }.get_active_iterator()
}
fn get_mut<'agent>(
&mut self,
agent: &'agent mut Agent,
) -> &'agent mut VmIteratorRecord<'static> {
unsafe { agent.vm_stack.last_mut().expect("No VM found").as_mut() }
.get_active_iterator_mut()
}
}
#[derive(Debug)]
pub(crate) enum VmIteratorRecord<'a> {
InvalidIterator {
iterator: Object<'a>,
},
ObjectProperties(Box<ObjectPropertiesIteratorRecord<'a>>),
ArrayValues(ArrayValuesIteratorRecord<'a>),
AsyncFromSyncGenericIterator(IteratorRecord<'a>),
GenericIterator(IteratorRecord<'a>),
SliceIterator(ScopedArgumentsList<'a>),
EmptySliceIterator,
}
impl<'a> VmIteratorRecord<'a> {
pub(super) fn remaining_length_estimate(&self, agent: &Agent) -> Option<usize> {
match self {
VmIteratorRecord::InvalidIterator { .. } => Some(0),
VmIteratorRecord::ObjectProperties(iter) => {
Some(iter.remaining_keys.as_ref().map_or(0, |k| k.len()))
}
VmIteratorRecord::ArrayValues(iter) => {
Some(iter.array.len(agent).saturating_sub(iter.index) as usize)
}
VmIteratorRecord::AsyncFromSyncGenericIterator(_) => todo!(),
VmIteratorRecord::GenericIterator(_) => None,
VmIteratorRecord::SliceIterator(slice) => Some(slice.len(agent)),
VmIteratorRecord::EmptySliceIterator => Some(0),
}
}
pub(super) fn requires_return_call(&self, agent: &mut Agent, gc: NoGcScope) -> bool {
match self {
VmIteratorRecord::ObjectProperties(_)
| VmIteratorRecord::SliceIterator(_)
| VmIteratorRecord::EmptySliceIterator => false,
VmIteratorRecord::ArrayValues(_) => {
array_iterator_record_requires_return_call(agent, gc)
}
VmIteratorRecord::InvalidIterator { iterator }
| VmIteratorRecord::GenericIterator(IteratorRecord { iterator, .. })
| VmIteratorRecord::AsyncFromSyncGenericIterator(IteratorRecord { iterator, .. }) => {
generic_iterator_record_requires_return_call(agent, *iterator, gc)
}
}
}
pub(super) fn from_value(
agent: &mut Agent,
obj: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Self> {
let value = obj.bind(gc.nogc());
let scoped_value = value.scope(agent, gc.nogc());
let method = get_method(
agent,
value.unbind(),
PropertyKey::Symbol(WellKnownSymbols::Iterator.into()),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Some(method) = method else {
return Err(throw_iterator_method_undefined(agent, gc.into_nogc()));
};
let value = unsafe { scoped_value.take(agent).bind(gc.nogc()) };
match value {
Value::Array(array)
if method
== agent
.current_realm_record()
.intrinsics()
.array_prototype_values()
.into() =>
{
Ok(VmIteratorRecord::ArrayValues(
ArrayValuesIteratorRecord::new(array.unbind()),
))
}
_ => Ok(
get_iterator_from_method(agent, value.unbind(), method.unbind(), gc)?
.into_vm_iterator_record(),
),
}
}
pub(super) fn async_from_value(
agent: &mut Agent,
obj: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Self> {
let obj = obj.bind(gc.nogc());
let scoped_obj = obj.scope(agent, gc.nogc());
let method = get_method(
agent,
obj.unbind(),
PropertyKey::Symbol(WellKnownSymbols::AsyncIterator.into()),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Some(method) = method else {
return Self::async_from_sync_value(agent, scoped_obj, gc);
};
let obj = unsafe { scoped_obj.take(agent).bind(gc.nogc()) };
if let Some(array) = Array::is_iterable_array(agent, obj, method) {
Ok(VmIteratorRecord::ArrayValues(
ArrayValuesIteratorRecord::new(array.unbind()),
))
} else {
Ok(
get_iterator_from_method(agent, obj.unbind(), method.unbind(), gc)?
.into_vm_iterator_record(),
)
}
}
fn async_from_sync_value(
agent: &mut Agent,
obj: Scoped<Value>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Self> {
let sync_method = get_method(
agent,
obj.get(agent),
WellKnownSymbols::Iterator.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Some(sync_method) = sync_method else {
return Err(throw_iterator_method_undefined(agent, gc.into_nogc()));
};
let obj = unsafe { obj.take(agent) }.bind(gc.nogc());
let sync_iterator_record =
get_iterator_from_method(agent, obj.unbind(), sync_method.unbind(), gc)?;
Ok(create_async_from_sync_iterator(sync_iterator_record))
}
pub(super) fn try_step_value<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, Option<Value<'gc>>> {
match self {
VmIteratorRecord::InvalidIterator { .. } => throw_not_callable(agent, gc).into(),
VmIteratorRecord::ObjectProperties(iter) => iter.try_step_value(agent, gc),
VmIteratorRecord::ArrayValues(iter) => iter.try_next(agent, gc),
VmIteratorRecord::AsyncFromSyncGenericIterator(_) => {
unreachable!()
}
VmIteratorRecord::GenericIterator(_) => TryError::GcError.into(),
VmIteratorRecord::SliceIterator(slice_ref) => {
TryResult::Continue(slice_ref.unshift(agent, gc))
}
VmIteratorRecord::EmptySliceIterator => TryResult::Continue(None),
}
}
}
bindable_handle!(VmIteratorRecord);
struct ObjectPropertiesIterator<'a> {
iter: ActiveIterator<'a>,
}
impl<'a> ObjectPropertiesIterator<'a> {
fn new(iter: &'a ActiveIterator) -> Self {
Self {
iter: iter.reborrow(),
}
}
fn step_value<'gc>(
&mut self,
agent: &mut Agent,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
loop {
if !self.object_is_visited(agent) {
let keys = self
.object(agent, gc.nogc())
.unbind()
.internal_own_property_keys(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let mut remaining_keys = Vec::with_capacity(keys.len());
for key in keys {
if let PropertyKey::Symbol(_) = key {
continue;
} else {
remaining_keys.push(key);
}
}
self.set_remaining_keys(agent, remaining_keys);
}
while let Some((object, next_key)) = self.next_remaining_key(agent, gc.nogc()) {
let scoped_next_key = next_key.scope(agent, gc.nogc());
let desc = object
.unbind()
.internal_get_own_property(agent, next_key.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
let next_key = unsafe { scoped_next_key.take(agent) }.bind(gc.nogc());
if let Some(desc) = desc {
self.mark_key_visited(agent, next_key);
if desc.enumerable == Some(true) {
return Ok(Some(Self::convert_result(
agent,
next_key.unbind(),
gc.into_nogc(),
)));
}
}
}
let prototype = self
.object(agent, gc.nogc())
.unbind()
.internal_get_prototype_of(agent, gc.reborrow())
.unbind()?
.bind(gc.nogc());
if let Some(prototype) = prototype {
self.set_object(agent, prototype);
} else {
return Ok(None);
}
}
}
fn convert_result<'gc>(
agent: &mut Agent,
result: PropertyKey,
gc: NoGcScope<'gc, '_>,
) -> Value<'gc> {
match result {
PropertyKey::Integer(int) => Value::from_string(agent, int.into_i64().to_string(), gc),
PropertyKey::SmallString(data) => Value::SmallString(data),
PropertyKey::String(data) => Value::String(data.bind(gc)),
_ => unreachable!(),
}
}
fn object<'gc>(&self, agent: &Agent, gc: NoGcScope<'gc, '_>) -> Object<'gc> {
self.get(agent).object.bind(gc)
}
fn object_is_visited(&self, agent: &Agent) -> bool {
self.get(agent).remaining_keys.is_some()
}
fn set_remaining_keys(&mut self, agent: &mut Agent, mut remaining_keys: Vec<PropertyKey>) {
remaining_keys.reverse();
self.get_mut(agent).remaining_keys = Some(remaining_keys.unbind());
}
fn set_object(&mut self, agent: &mut Agent, object: Object) {
let iter = self.get_mut(agent);
iter.object = object.unbind();
iter.remaining_keys = None;
}
fn next_remaining_key<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> Option<(Object<'gc>, PropertyKey<'gc>)> {
let mut iter = NonNull::from(self.get_mut(agent));
loop {
let iter_mut = unsafe { iter.as_mut() };
let next_key = iter_mut.remaining_keys.as_mut().unwrap().pop()?;
let iter_ref = unsafe { iter.as_ref() };
if iter_ref.visited_keys.contains(agent, next_key) {
continue;
}
return Some((iter_ref.object.bind(gc), next_key.bind(gc)));
}
}
fn mark_key_visited(&mut self, agent: &mut Agent, key: PropertyKey) {
let mut iter = NonNull::from(self.get_mut(agent));
let iter = unsafe { iter.as_mut() };
iter.visited_keys.insert(agent, key);
}
fn get<'agent>(&self, agent: &'agent Agent) -> &'agent ObjectPropertiesIteratorRecord<'a> {
let VmIteratorRecord::ObjectProperties(iter) = self.iter.get(agent) else {
unreachable!()
};
iter
}
fn get_mut<'agent>(
&mut self,
agent: &'agent mut Agent,
) -> &'agent mut ObjectPropertiesIteratorRecord<'static> {
let VmIteratorRecord::ObjectProperties(iter) = self.iter.get_mut(agent) else {
unreachable!()
};
iter
}
}
#[derive(Debug)]
pub(crate) struct ObjectPropertiesIteratorRecord<'a> {
object: Object<'a>,
visited_keys: PropertyKeySet<'a>,
remaining_keys: Option<Vec<PropertyKey<'a>>>,
}
impl<'a> ObjectPropertiesIteratorRecord<'a> {
pub(super) fn new(object: Object<'a>) -> Self {
Self {
object,
visited_keys: Default::default(),
remaining_keys: Default::default(),
}
}
fn object_is_visited(&self) -> bool {
self.remaining_keys.is_some()
}
fn set_remaining_keys(&mut self, mut remaining_keys: Vec<PropertyKey>) {
remaining_keys.reverse();
self.remaining_keys = Some(remaining_keys.unbind());
}
fn set_object(&mut self, object: Object) {
let iter = self;
iter.object = object.unbind();
iter.remaining_keys = None;
}
fn next_remaining_key<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> Option<(Object<'gc>, PropertyKey<'gc>)> {
loop {
let next_key = self.remaining_keys.as_mut().unwrap().pop()?;
if self.visited_keys.contains(agent, next_key) {
continue;
}
return Some((self.object.bind(gc), next_key.bind(gc)));
}
}
fn mark_key_visited(&mut self, agent: &mut Agent, key: PropertyKey) {
self.visited_keys.insert(agent, key);
}
fn try_step_value<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, Option<Value<'gc>>> {
loop {
let object = self.object.bind(gc);
if !self.object_is_visited() {
let keys = object.try_own_property_keys(agent, gc)?;
let mut remaining_keys = Vec::with_capacity(keys.len());
for key in keys {
if let PropertyKey::Symbol(_) = key {
continue;
} else {
remaining_keys.push(key);
}
}
self.set_remaining_keys(remaining_keys);
}
while let Some((object, next_key)) = self.next_remaining_key(agent, gc) {
let desc = object.try_get_own_property(agent, next_key, None, gc)?;
if let Some(desc) = desc {
self.mark_key_visited(agent, next_key);
if desc.enumerable == Some(true) {
return TryResult::Continue(Some(
ObjectPropertiesIterator::convert_result(agent, next_key, gc),
));
}
}
}
let prototype = object.try_get_prototype_of(agent, gc)?;
if let Some(prototype) = prototype {
self.set_object(prototype);
} else {
return TryResult::Continue(None);
}
}
}
}
struct ArrayValuesIterator<'a> {
iter: ActiveIterator<'a>,
}
impl<'a> ArrayValuesIterator<'a> {
fn new(iter: &'a ActiveIterator) -> Self {
Self {
iter: iter.reborrow(),
}
}
pub(super) fn next<'gc>(
&mut self,
agent: &mut Agent,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
let Some((array, index)) = self.prep_step(agent, gc.nogc()) else {
return Ok(None);
};
if let Some(element_value) = array.as_slice(agent)[index as usize] {
return Ok(Some(element_value.unbind()));
}
let element_value = get(agent, array.unbind(), index.into(), gc)?;
Ok(Some(element_value))
}
fn throw<'gc>(
&mut self,
agent: &mut Agent,
_received_value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
let throw = get_object_method(
agent,
agent
.current_realm_record()
.intrinsics()
.array_prototype()
.into(),
BUILTIN_STRING_MEMORY.throw.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if let Some(throw) = throw {
todo!("Handle ArrayValuesIterator with throw method {throw:?}");
}
Ok(None)
}
fn r#return<'gc>(
&mut self,
agent: &mut Agent,
_received_value: Option<Value>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
let r#return = get_object_method(
agent,
agent
.current_realm_record()
.intrinsics()
.array_prototype()
.into(),
BUILTIN_STRING_MEMORY.r#return.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if let Some(r#return) = r#return {
todo!("Handle ArrayValuesIterator with return method {return:?}");
}
Ok(None)
}
fn prep_step<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> Option<(Array<'gc>, u32)> {
let VmIteratorRecord::ArrayValues(iter) = self.iter.get_mut(agent) else {
unreachable!()
};
let array = iter.array.bind(gc);
let index = iter.index;
let mut iter = NonNull::from(iter);
let len = array.len(agent);
if index >= len {
return None;
}
unsafe { iter.as_mut().index += 1 };
Some((array, index))
}
}
#[derive(Debug)]
pub(crate) struct ArrayValuesIteratorRecord<'a> {
array: Array<'a>,
index: u32,
}
impl<'a> ArrayValuesIteratorRecord<'a> {
pub(super) fn new(array: Array<'a>) -> Self {
Self {
array,
index: 0,
}
}
fn prep_step<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> Option<(Array<'gc>, u32)> {
let array = self.array.bind(gc);
let index = self.index;
let len = array.len(agent);
if index >= len {
return None;
}
self.index += 1;
Some((array, index))
}
fn try_next<'gc>(
&mut self,
agent: &mut Agent,
gc: NoGcScope<'gc, '_>,
) -> TryResult<'gc, Option<Value<'gc>>> {
let Some((array, index)) = self.prep_step(agent, gc) else {
return TryResult::Continue(None);
};
if let Some(element_value) = array.as_slice(agent)[index as usize] {
return TryResult::Continue(Some(element_value.unbind()));
}
let element_value = match try_get(agent, array.unbind(), index.into(), None, gc)? {
TryGetResult::Unset => Value::Undefined,
TryGetResult::Value(value) => value,
TryGetResult::Get(_) | TryGetResult::Proxy(_) => {
return TryError::GcError.into();
}
};
TryResult::Continue(Some(element_value))
}
}
struct GenericIterator<'a> {
iter: ActiveIterator<'a>,
}
impl<'a> GenericIterator<'a> {
fn new(iter: &'a ActiveIterator) -> Self {
Self {
iter: iter.reborrow(),
}
}
fn call_next<'gc>(
&mut self,
agent: &mut Agent,
value: Option<Value>,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let iter = self.get(agent);
let next_method = iter.next_method.bind(gc.nogc());
let iterator = iter.iterator.bind(gc.nogc());
let value = value.bind(gc.nogc());
if let Some(value) = value {
call_function(
agent,
next_method.unbind(),
iterator.unbind().into(),
Some(ArgumentsList::from_mut_value(&mut value.unbind())),
gc,
)
} else {
call_function(
agent,
next_method.unbind(),
iterator.unbind().into(),
None,
gc,
)
}
}
fn step_value<'gc>(
&mut self,
agent: &mut Agent,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
let iter = self.get(agent);
let next_method = iter.next_method.bind(gc.nogc());
let iterator = iter.iterator.bind(gc.nogc());
let result = call_function(
agent,
next_method.unbind(),
iterator.unbind().into(),
None,
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let Ok(result) = Object::try_from(result) else {
return Err(throw_iterator_returned_non_object(agent, gc.into_nogc()));
};
let result = result.unbind().bind(gc.nogc());
let scoped_result = result.scope(agent, gc.nogc());
let done = get(
agent,
result.unbind(),
BUILTIN_STRING_MEMORY.done.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
let done = to_boolean(agent, done);
if done {
Ok(None)
} else {
let value = get(
agent,
scoped_result.get(agent),
BUILTIN_STRING_MEMORY.value.into(),
gc,
)?;
Ok(Some(value))
}
}
fn throw<'gc>(
&mut self,
agent: &mut Agent,
received_value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
let received_value = received_value.scope(agent, gc.nogc());
let iterator = self.get(agent).iterator.bind(gc.nogc());
let throw = get_object_method(
agent,
iterator.unbind(),
BUILTIN_STRING_MEMORY.throw.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if let Some(throw) = throw {
let iterator = self.get(agent).iterator.bind(gc.nogc());
Ok(Some(call_function(
agent,
throw.unbind(),
iterator.unbind().into(),
Some(ArgumentsList::from_mut_value(&mut unsafe {
received_value.take(agent)
})),
gc,
)?))
} else {
Ok(None)
}
}
fn r#return<'gc>(
agent: &mut Agent,
iterator: Object,
value: Option<Value>,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<Value<'gc>>> {
let value = value.map(|v| v.scope(agent, gc.nogc()));
let iterator = iterator.bind(gc.nogc());
let scoped_iterator = iterator.scope(agent, gc.nogc());
let r#return = get_object_method(
agent,
iterator.unbind(),
BUILTIN_STRING_MEMORY.r#return.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if let Some(r#return) = r#return {
let iterator = unsafe { scoped_iterator.take(agent) }.bind(gc.nogc());
let mut value = value.map(|v| unsafe { v.take(agent) });
Ok(Some(call_function(
agent,
r#return.unbind(),
iterator.unbind().into(),
value.as_mut().map(ArgumentsList::from_mut_value),
gc,
)?))
} else {
Ok(None)
}
}
fn get<'agent>(&self, agent: &'agent Agent) -> &'agent IteratorRecord<'a> {
match self.iter.get(agent) {
VmIteratorRecord::GenericIterator(iter) => iter,
_ => unreachable!(),
}
}
}
struct AsyncFromSyncGenericIterator<'a> {
iter: ActiveIterator<'a>,
}
impl<'a> AsyncFromSyncGenericIterator<'a> {
fn new(iter: &'a ActiveIterator) -> Self {
Self {
iter: iter.reborrow(),
}
}
fn call_next<'gc>(
&mut self,
agent: &mut Agent,
value: Option<Value>,
gc: GcScope<'gc, '_>,
) -> Promise<'gc> {
let iter = self.get(agent);
let sync_iterator_record = iter.bind(gc.nogc());
AsyncFromSyncIteratorPrototype::next(agent, sync_iterator_record.unbind(), value, gc)
}
fn r#return<'gc>(
&mut self,
agent: &mut Agent,
value: Option<Value>,
gc: GcScope<'gc, '_>,
) -> Promise<'gc> {
let iter = self.get(agent);
let sync_iterator = iter.iterator.bind(gc.nogc());
AsyncFromSyncIteratorPrototype::r#return(agent, sync_iterator.unbind(), value, gc)
}
fn throw<'gc>(
&mut self,
agent: &mut Agent,
value: Value,
gc: GcScope<'gc, '_>,
) -> Promise<'gc> {
let iter = self.get(agent);
let sync_iterator = iter.iterator.bind(gc.nogc());
AsyncFromSyncIteratorPrototype::throw(agent, sync_iterator.unbind(), value, gc)
}
fn get<'agent>(&self, agent: &'agent Agent) -> &'agent IteratorRecord<'a> {
match self.iter.get(agent) {
VmIteratorRecord::AsyncFromSyncGenericIterator(iter) => iter,
_ => unreachable!(),
}
}
}
fn array_iterator_record_requires_return_call(agent: &mut Agent, gc: NoGcScope) -> bool {
let array_iterator_prototype = agent
.current_realm_record()
.intrinsics()
.array_iterator_prototype();
match array_iterator_prototype.try_get(
agent,
BUILTIN_STRING_MEMORY.r#return.into(),
array_iterator_prototype.into(),
None,
gc,
) {
ControlFlow::Continue(TryGetResult::Unset) => false,
ControlFlow::Continue(TryGetResult::Value(v)) => !v.is_undefined() && !v.is_null(),
ControlFlow::Break(TryError::Err(_)) => todo!(),
_ => true,
}
}
fn generic_iterator_record_requires_return_call(
agent: &mut Agent,
iterator: Object,
gc: NoGcScope,
) -> bool {
match iterator.try_get(
agent,
BUILTIN_STRING_MEMORY.r#return.into(),
iterator.into(),
None,
gc,
) {
ControlFlow::Continue(TryGetResult::Unset) => false,
ControlFlow::Continue(TryGetResult::Value(v)) => !v.is_undefined() && !v.is_null(),
ControlFlow::Break(TryError::Err(_)) => unreachable!(),
_ => true,
}
}
impl HeapMarkAndSweep for ObjectPropertiesIteratorRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
object,
visited_keys,
remaining_keys,
} = self;
object.mark_values(queues);
visited_keys.mark_values(queues);
if let Some(remaining_keys) = remaining_keys {
for key in remaining_keys.iter() {
key.mark_values(queues);
}
}
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
object,
visited_keys,
remaining_keys,
} = self;
object.sweep_values(compactions);
visited_keys.sweep_values(compactions);
if let Some(remaining_keys) = remaining_keys {
for key in remaining_keys.iter_mut() {
key.sweep_values(compactions);
}
}
}
}
impl HeapMarkAndSweep for ArrayValuesIteratorRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
self.array.mark_values(queues)
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
self.array.sweep_values(compactions);
}
}
impl HeapMarkAndSweep for VmIteratorRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
match self {
VmIteratorRecord::InvalidIterator { iterator } => iterator.mark_values(queues),
VmIteratorRecord::ObjectProperties(iter) => iter.mark_values(queues),
VmIteratorRecord::ArrayValues(iter) => iter.mark_values(queues),
VmIteratorRecord::AsyncFromSyncGenericIterator(iter) => iter.mark_values(queues),
VmIteratorRecord::GenericIterator(iter) => iter.mark_values(queues),
VmIteratorRecord::SliceIterator(_) => {}
VmIteratorRecord::EmptySliceIterator => {}
}
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
match self {
VmIteratorRecord::InvalidIterator { iterator } => iterator.sweep_values(compactions),
VmIteratorRecord::ObjectProperties(iter) => iter.sweep_values(compactions),
VmIteratorRecord::ArrayValues(iter) => iter.sweep_values(compactions),
VmIteratorRecord::AsyncFromSyncGenericIterator(iter) => iter.sweep_values(compactions),
VmIteratorRecord::GenericIterator(iter) => iter.sweep_values(compactions),
VmIteratorRecord::SliceIterator(_) => {}
VmIteratorRecord::EmptySliceIterator => {}
}
}
}
pub(crate) fn throw_iterator_returned_non_object<'a>(
agent: &mut Agent,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Iterator returned a non-object result",
gc,
)
}
fn throw_iterator_method_undefined<'a>(agent: &mut Agent, gc: NoGcScope<'a, '_>) -> JsError<'a> {
agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Iterator method cannot be undefined",
gc,
)
}