///|
// A closed fallback result keeps an already-created intrinsic iterator apart
// from a callable iterator method. The latter must cross the managed call
// activation seam before it can execute guest code.
priv enum ExecutorIterableSpreadIteratorFallback {
ExecutorIterableSpreadReadyIterator(Value)
ExecutorIterableSpreadIteratorMethod(Value)
}
///|
// Canonical built-in iterator fallback for spread. Built-in variants may lack
// a materialized @@iterator in a bare RealmState, so the fallback is selected
// only after the observable lookup returned undefined and callback-free shape
// facts prove that no override can be hidden. An explicit own/prototype
// override suppresses the fallback and keeps ordinary TypeError semantics.
fn Interpreter::default_iterable_iterator_if_unoverridden(
self : Interpreter,
iterable : Value,
iterator_method : Value,
) -> ExecutorIterableSpreadIteratorFallback? {
guard iterator_method is Undefined else { return None }
let iterator_sym = self.realm_state.well_known_symbols.iterator
match iterable {
Array(data) => {
guard !data.bag.symbol_descriptors.contains(iterator_sym.id) &&
!data.bag.symbol_properties.contains(iterator_sym.id) else {
return None
}
// An initialized realm materializes Array.prototype[@@iterator], so an
// undefined lookup there is authoritative. Only a bare RealmState lacks
// that observable prototype representation and may use this legacy
// intrinsic fallback. Per-array prototype overrides are representation
// facts and must suppress fallback without probing a Proxy with `has`.
guard get_array_prototype_override(data) is None else { return None }
guard self.realm_state.get_array_proto() is Null else { return None }
Some(
ExecutorIterableSpreadReadyIterator(
self.realm_state.make_array_iterator_value(data),
),
)
}
String_(value) => {
// As with Array, only an uninitialized realm may use the intrinsic
// fallback. Initialized String.prototype lookup owns even an undefined
// result, including observable inherited overrides.
guard self.realm_state.get_string_proto() is Null else { return None }
Some(
ExecutorIterableSpreadReadyIterator(
self.realm_state.make_string_iterator_value(value),
),
)
}
Map(data) => {
// Bare Map lookup has no materialized prototype. Return the supplied
// intrinsic method only after callback-free representation facts prove
// that own and inherited overrides cannot be present. The caller owns
// the eventual invocation because this Value may enter guest code.
guard data.prototype is None &&
self.realm_state.get_map_proto() is Null &&
!data.bag.symbol_descriptors.contains(iterator_sym.id) &&
!data.bag.symbol_properties.contains(iterator_sym.id) else {
return None
}
let intrinsic_method = (self.stdlib_hooks.get_map_method)(
data,
"entries",
self.realm_state,
)
guard is_callable(intrinsic_method) else { return None }
Some(ExecutorIterableSpreadIteratorMethod(intrinsic_method))
}
Set(data) => {
// Set follows the same bare-runtime contract as Map, using its
// intrinsic values method and preserving the original Set receiver.
// Invocation is deferred to the caller for the same guest-entry reason.
guard data.prototype is None &&
self.realm_state.get_set_proto() is Null &&
!data.bag.symbol_descriptors.contains(iterator_sym.id) &&
!data.bag.symbol_properties.contains(iterator_sym.id) else {
return None
}
let intrinsic_method = (self.stdlib_hooks.get_set_method)(
data,
"values",
self.realm_state,
)
guard is_callable(intrinsic_method) else { return None }
Some(ExecutorIterableSpreadIteratorMethod(intrinsic_method))
}
_ => None
}
}
///|
// The batch is an internal Array Value so the neutral activation seam carries
// one owned result without exposing a sink callback to the runtime. Consumers
// copy the elements before appending them to their concrete destination.
pub fn executor_iterable_spread_batch_values(
batch : Value,
) -> Array[Value] raise Error {
match batch {
Array(data) => data.elements.copy()
_ =>
raise @errors.InternalError(
message="iterable spread completion was not an Array batch",
)
}
}
///|
priv enum ExecutorIterableSpreadPhase {
ExecutorIterableSpreadNeedIteratorMethod(Value)
ExecutorIterableSpreadAwaitIteratorMethod(Value)
ExecutorIterableSpreadHaveIteratorMethod(Value, Value)
ExecutorIterableSpreadAwaitIteratorCall
ExecutorIterableSpreadHaveIterator(Value)
ExecutorIterableSpreadNeedNextMethod(Value)
ExecutorIterableSpreadAwaitNextMethod(Value)
ExecutorIterableSpreadHaveNextMethod(Value, Value)
ExecutorIterableSpreadAwaitNextCall(Value, Value)
ExecutorIterableSpreadHaveNextResult(Value, Value, Value)
ExecutorIterableSpreadNeedDone(Value, Value, Value)
ExecutorIterableSpreadAwaitDone(Value, Value, Value)
ExecutorIterableSpreadHaveDone(Value, Value, Value, Value)
ExecutorIterableSpreadNeedValue(Value, Value, Value)
ExecutorIterableSpreadAwaitValue(Value, Value, Value)
ExecutorIterableSpreadComplete
}
///|
priv struct ExecutorIterableSpreadFrame {
request : ExecutorIterableSpreadRequest
values : Array[Value]
mut phase : ExecutorIterableSpreadPhase
}
///|
fn ExecutorIterableSpreadFrame::ExecutorIterableSpreadFrame(
request : ExecutorIterableSpreadRequest,
) -> ExecutorIterableSpreadFrame {
{
request,
values: [],
phase: ExecutorIterableSpreadNeedIteratorMethod(request.iterable),
}
}
///|
fn ExecutorIterableSpreadFrame::step_frame(
self : ExecutorIterableSpreadFrame,
interp : Interpreter,
) -> ExecutorActivationStep raise Error {
let loc = self.request.loc
match self.phase {
ExecutorIterableSpreadNeedIteratorMethod(iterable) => {
self.phase = ExecutorIterableSpreadAwaitIteratorMethod(iterable)
executor_activation_symbol_property_get(
iterable,
interp.realm_state.well_known_symbols.iterator,
loc,
)
}
ExecutorIterableSpreadHaveIteratorMethod(iterable, iterator_method) =>
if is_callable(iterator_method) {
self.phase = ExecutorIterableSpreadAwaitIteratorCall
ExecutorActivationCall(
ExecutorCallRequest(
callee=iterator_method,
this_value=iterable,
args=[],
loc~,
),
)
} else {
match
interp.default_iterable_iterator_if_unoverridden(
iterable, iterator_method,
) {
Some(ExecutorIterableSpreadReadyIterator(iterator)) => {
self.phase = ExecutorIterableSpreadHaveIterator(iterator)
self.step_frame(interp)
}
Some(ExecutorIterableSpreadIteratorMethod(iterator_method)) => {
self.phase = ExecutorIterableSpreadAwaitIteratorCall
ExecutorActivationCall(
ExecutorCallRequest(
callee=iterator_method,
this_value=iterable,
args=[],
loc~,
),
)
}
None => {
let message = match iterator_method {
Undefined => type_of(iterable) + " is not iterable"
_ =>
type_of(iterable) +
" is not iterable (Symbol.iterator is not a function)"
}
raise @errors.TypeError(message~)
}
}
}
ExecutorIterableSpreadHaveIterator(iterator) => {
guard is_object_value(iterator) else {
raise @errors.TypeError(message="Iterator is not an object")
}
self.phase = ExecutorIterableSpreadNeedNextMethod(iterator)
self.step_frame(interp)
}
ExecutorIterableSpreadNeedNextMethod(iterator) => {
self.phase = ExecutorIterableSpreadAwaitNextMethod(iterator)
executor_activation_managed_property_get(iterator, "next", loc)
}
ExecutorIterableSpreadHaveNextMethod(iterator, next_method) => {
self.phase = ExecutorIterableSpreadAwaitNextCall(iterator, next_method)
ExecutorActivationCall(
ExecutorCallRequest(
callee=next_method,
this_value=iterator,
args=[],
loc~,
),
)
}
ExecutorIterableSpreadHaveNextResult(iterator, next_method, result) => {
guard is_object_value(result) else {
raise @errors.TypeError(message="Iterator result is not an object")
}
self.phase = ExecutorIterableSpreadNeedDone(iterator, next_method, result)
self.step_frame(interp)
}
ExecutorIterableSpreadNeedDone(iterator, next_method, result) => {
self.phase = ExecutorIterableSpreadAwaitDone(
iterator, next_method, result,
)
executor_activation_managed_property_get(result, "done", loc)
}
ExecutorIterableSpreadHaveDone(iterator, next_method, result, done) =>
if is_truthy(done) {
self.phase = ExecutorIterableSpreadComplete
// The Array Value is an internal, owned batch. The pending VM
// destination unwraps it and appends the elements exactly once.
ExecutorActivationNormal(make_array(self.values.copy()))
} else {
self.phase = ExecutorIterableSpreadNeedValue(
iterator, next_method, result,
)
self.step_frame(interp)
}
ExecutorIterableSpreadNeedValue(iterator, next_method, result) => {
self.phase = ExecutorIterableSpreadAwaitValue(
iterator, next_method, result,
)
executor_activation_managed_property_get(result, "value", loc)
}
ExecutorIterableSpreadAwaitIteratorMethod(_)
| ExecutorIterableSpreadAwaitIteratorCall
| ExecutorIterableSpreadAwaitNextMethod(_)
| ExecutorIterableSpreadAwaitNextCall(_, _)
| ExecutorIterableSpreadAwaitDone(_, _, _)
| ExecutorIterableSpreadAwaitValue(_, _, _) =>
raise @errors.InternalError(
message="iterable spread frame stepped while awaiting a child completion",
)
ExecutorIterableSpreadComplete =>
raise @errors.InternalError(
message="iterable spread frame stepped after completion",
)
}
}
///|
impl ExecutorActivationFrame for ExecutorIterableSpreadFrame with fn step(
self,
interp,
) {
self.step_frame(interp)
}
///|
impl ExecutorActivationFrame for ExecutorIterableSpreadFrame with fn deliver_activation_completion(
self,
completion,
) {
match completion {
ExecutorActivationCompletionAbrupt(error) => raise error
ExecutorActivationCompletionReference(_) =>
raise @errors.InternalError(
message="iterable spread received an unexpected binding reference completion",
)
ExecutorActivationCompletionNormal(value) =>
match self.phase {
ExecutorIterableSpreadAwaitIteratorMethod(iterable) =>
self.phase = ExecutorIterableSpreadHaveIteratorMethod(iterable, value)
ExecutorIterableSpreadAwaitIteratorCall =>
self.phase = ExecutorIterableSpreadHaveIterator(value)
ExecutorIterableSpreadAwaitNextMethod(iterator) =>
self.phase = ExecutorIterableSpreadHaveNextMethod(iterator, value)
ExecutorIterableSpreadAwaitNextCall(iterator, next_method) =>
self.phase = ExecutorIterableSpreadHaveNextResult(
iterator, next_method, value,
)
ExecutorIterableSpreadAwaitDone(iterator, next_method, result) =>
self.phase = ExecutorIterableSpreadHaveDone(
iterator, next_method, result, value,
)
ExecutorIterableSpreadAwaitValue(iterator, next_method, result) => {
self.values.push(value)
self.phase = ExecutorIterableSpreadHaveNextMethod(
iterator, next_method,
)
ignore(result)
}
ExecutorIterableSpreadNeedIteratorMethod(_)
| ExecutorIterableSpreadHaveIteratorMethod(_, _)
| ExecutorIterableSpreadHaveIterator(_)
| ExecutorIterableSpreadNeedNextMethod(_)
| ExecutorIterableSpreadHaveNextMethod(_, _)
| ExecutorIterableSpreadHaveNextResult(_, _, _)
| ExecutorIterableSpreadNeedDone(_, _, _)
| ExecutorIterableSpreadHaveDone(_, _, _, _)
| ExecutorIterableSpreadNeedValue(_, _, _)
| ExecutorIterableSpreadComplete =>
raise @errors.InternalError(
message="iterable spread frame received an unexpected child completion",
)
}
}
}