-
Notifications
You must be signed in to change notification settings - Fork 62
Implement Promise.all #797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2519499
3dba3e7
3699e5d
e90ffb0
8f93139
5488192
4d8bdc2
7d5530c
24ab2a7
e2e2656
106d503
be86077
cab7195
960b9ed
ddb27f4
80a5ba2
33924a6
1259899
50a3b84
4adfb07
8d7c640
d8eccea
fe562a1
0dfa893
79a2fa0
fc72d59
49f9aed
b5c1189
3282bd7
1a2fc7a
785c68c
2c4c9e5
0976b82
c26cefe
977ee2c
302c459
3fe4d5c
aad261a
fcb8e47
18a0f51
f7e966e
7a77664
6eb1b15
95e3dad
c064ec1
7864cce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
use crate::{ | ||
ecmascript::{ | ||
builtins::{ | ||
Array, promise::Promise, | ||
promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, | ||
}, | ||
execution::Agent, | ||
types::{IntoValue, Value}, | ||
}, | ||
engine::context::{Bindable, GcScope, NoGcScope, bindable_handle}, | ||
heap::{ | ||
CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, indexes::BaseIndex, | ||
}, | ||
}; | ||
|
||
#[derive(Debug, Clone, Copy)] | ||
pub struct PromiseAllRecord<'a> { | ||
pub(crate) remaining_unresolved_promise_count: u32, | ||
pub(crate) result_array: Array<'a>, | ||
pub(crate) promise: Promise<'a>, | ||
} | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] | ||
#[repr(transparent)] | ||
pub struct PromiseAll<'a>(BaseIndex<'a, PromiseAllRecord<'static>>); | ||
|
||
impl<'a> PromiseAll<'a> { | ||
pub(crate) fn on_promise_fulfilled( | ||
self, | ||
agent: &mut Agent, | ||
index: u32, | ||
value: Value<'a>, | ||
gc: GcScope<'a, '_>, | ||
) { | ||
let promise_all = self.bind(gc.nogc()); | ||
let value = value.bind(gc.nogc()); | ||
|
||
let result_array = self.get_result_array(agent, gc.nogc()); | ||
|
||
let elements = result_array.as_mut_slice(agent); | ||
elements[index as usize] = Some(value.unbind()); | ||
komyg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let data = promise_all.get_mut(agent); | ||
data.remaining_unresolved_promise_count = | ||
data.remaining_unresolved_promise_count.saturating_sub(1); | ||
if data.remaining_unresolved_promise_count == 0 { | ||
let capability = PromiseCapability::from_promise(data.promise, true); | ||
capability.resolve(agent, result_array.into_value().unbind(), gc); | ||
} | ||
} | ||
|
||
pub(crate) fn on_promise_rejected( | ||
self, | ||
agent: &mut Agent, | ||
value: Value<'a>, | ||
gc: NoGcScope<'a, '_>, | ||
) { | ||
let value = value.bind(gc); | ||
let promise_all = self.bind(gc); | ||
let data = promise_all.get_mut(agent); | ||
|
||
let capability = PromiseCapability::from_promise(data.promise, true); | ||
capability.reject(agent, value.unbind(), gc); | ||
} | ||
|
||
pub(crate) fn get_result_array(self, agent: &Agent, gc: NoGcScope<'a, '_>) -> Array<'a> { | ||
let data = self.get(agent); | ||
data.result_array.bind(gc).unbind() | ||
} | ||
|
||
pub(crate) const fn get_index(self) -> usize { | ||
self.0.into_index() | ||
} | ||
|
||
fn get(self, agent: &Agent) -> &PromiseAllRecord<'a> { | ||
agent | ||
.heap | ||
.promise_all_records | ||
.get(self.get_index()) | ||
.expect("PromiseAllRecord not found") | ||
} | ||
|
||
fn get_mut(self, agent: &mut Agent) -> &mut PromiseAllRecord<'static> { | ||
agent | ||
.heap | ||
.promise_all_records | ||
.get_mut(self.get_index()) | ||
.expect("PromiseAllRecord not found") | ||
} | ||
|
||
pub(crate) const fn _def() -> Self { | ||
Self(BaseIndex::from_u32_index(0)) | ||
} | ||
} | ||
|
||
impl AsRef<[PromiseAllRecord<'static>]> for Agent { | ||
fn as_ref(&self) -> &[PromiseAllRecord<'static>] { | ||
&self.heap.promise_all_records | ||
} | ||
} | ||
|
||
impl AsMut<[PromiseAllRecord<'static>]> for Agent { | ||
fn as_mut(&mut self) -> &mut [PromiseAllRecord<'static>] { | ||
&mut self.heap.promise_all_records | ||
} | ||
} | ||
|
||
impl HeapMarkAndSweep for PromiseAllRecord<'static> { | ||
komyg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn mark_values(&self, queues: &mut WorkQueues) { | ||
let Self { | ||
remaining_unresolved_promise_count: _, | ||
result_array, | ||
promise, | ||
} = self; | ||
result_array.mark_values(queues); | ||
promise.mark_values(queues); | ||
} | ||
|
||
fn sweep_values(&mut self, compactions: &CompactionLists) { | ||
let Self { | ||
remaining_unresolved_promise_count: _, | ||
result_array, | ||
promise, | ||
} = self; | ||
result_array.sweep_values(compactions); | ||
promise.sweep_values(compactions); | ||
} | ||
} | ||
|
||
impl HeapMarkAndSweep for PromiseAll<'static> { | ||
fn mark_values(&self, queues: &mut WorkQueues) { | ||
queues.promise_all_records.push(*self); | ||
komyg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
fn sweep_values(&mut self, compactions: &CompactionLists) { | ||
compactions.promise_all_records.shift_index(&mut self.0); | ||
} | ||
} | ||
|
||
bindable_handle!(PromiseAllRecord); | ||
bindable_handle!(PromiseAll); | ||
|
||
impl<'a> CreateHeapData<PromiseAllRecord<'a>, PromiseAll<'a>> for Heap { | ||
fn create(&mut self, data: PromiseAllRecord<'a>) -> PromiseAll<'a> { | ||
self.promise_all_records.push(data.unbind()); | ||
self.alloc_counter += core::mem::size_of::<PromiseAllRecord<'static>>(); | ||
PromiseAll(BaseIndex::last_t(&self.promise_all_records)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,12 +10,20 @@ use crate::{ | |
}, | ||
builders::builtin_function_builder::BuiltinFunctionBuilder, | ||
builtins::{ | ||
ArgumentsList, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, | ||
ArgumentsList, Array, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, | ||
array_create, | ||
ordinary::ordinary_create_from_constructor, | ||
promise::{ | ||
Promise, | ||
data::{PromiseHeapData, PromiseState}, | ||
}, | ||
promise_objects::{ | ||
promise_abstract_operations::{ | ||
promise_all_record::PromiseAllRecord, | ||
promise_reaction_records::PromiseReactionHandler, | ||
}, | ||
promise_prototype::inner_promise_then, | ||
}, | ||
}, | ||
execution::{ | ||
Agent, JsResult, ProtoIntrinsics, Realm, | ||
|
@@ -210,13 +218,90 @@ impl PromiseConstructor { | |
Ok(scoped_promise.get(agent).into_value()) | ||
} | ||
|
||
/// ### [27.2.4.1 Promise.all ( iterable )](https://tc39.es/ecma262/#sec-promise.all) | ||
fn all<'gc>( | ||
komyg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
agent: &mut Agent, | ||
_this_value: Value, | ||
_arguments: ArgumentsList, | ||
gc: GcScope<'gc, '_>, | ||
this_value: Value, | ||
arguments: ArgumentsList, | ||
mut gc: GcScope<'gc, '_>, | ||
) -> JsResult<'gc, Value<'gc>> { | ||
Err(agent.todo("Promise.all", gc.into_nogc())) | ||
// 1. Let C be the this value. | ||
if this_value | ||
komyg marked this conversation as resolved.
Show resolved
Hide resolved
komyg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
!= agent | ||
.current_realm_record() | ||
.intrinsics() | ||
.promise() | ||
.into_value() | ||
{ | ||
return Err(throw_promise_subclassing_not_supported( | ||
agent, | ||
gc.into_nogc(), | ||
)); | ||
} | ||
let arguments = arguments.bind(gc.nogc()); | ||
|
||
let first_arg = arguments.get(0); | ||
let Ok(promise_array) = Array::try_from(first_arg) else { | ||
return Err(agent.throw_exception_with_static_message( | ||
ExceptionType::TypeError, | ||
"Expected an array as first argument", | ||
gc.into_nogc(), | ||
)); | ||
}; | ||
let scoped_promise_array = promise_array.scope(agent, gc.nogc()); | ||
|
||
let array_len = promise_array.len(agent); | ||
let result_capability = PromiseCapability::new(agent, gc.nogc()); | ||
let result_promise = result_capability.promise(); | ||
let scoped_result_promise = result_promise.scope(agent, gc.nogc()); | ||
|
||
let result_array = array_create( | ||
agent, | ||
array_len as usize, | ||
array_len as usize, | ||
None, | ||
gc.nogc(), | ||
) | ||
.unbind()? | ||
.bind(gc.nogc()); | ||
|
||
let promise_all_record = agent.heap.create(PromiseAllRecord { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: This A further issue is that this method doesn't quite do what the spec says in general. The I'm wondering, would you want to pair-program this over the finish line? There's some busy-work to be done (around making the |
||
remaining_unresolved_promise_count: array_len, | ||
result_array: result_array.unbind(), | ||
promise: scoped_result_promise.get(agent), | ||
}); | ||
|
||
for index in 0..array_len { | ||
let element = scoped_promise_array.get(agent).as_slice(agent)[index as usize]; | ||
|
||
let promise = match element { | ||
Some(Value::Promise(promise)) => promise, | ||
Some(value) => Promise::resolve(agent, value.unbind(), gc.reborrow()) | ||
.unbind() | ||
.bind(gc.nogc()), | ||
None => { | ||
unreachable!() | ||
} | ||
}; | ||
|
||
let capability = PromiseCapability::new(agent, gc.nogc()); | ||
inner_promise_then( | ||
agent, | ||
promise.unbind(), | ||
PromiseReactionHandler::PromiseAll { | ||
index, | ||
promise_all: promise_all_record, | ||
}, | ||
PromiseReactionHandler::PromiseAll { | ||
index, | ||
promise_all: promise_all_record, | ||
}, | ||
Some(capability.unbind()), | ||
gc.nogc(), | ||
); | ||
} | ||
|
||
Ok(scoped_result_promise.get(agent).into_value()) | ||
} | ||
|
||
fn all_settled<'gc>( | ||
|
Uh oh!
There was an error while loading. Please reload this page.