Skip to content

Commit 7f5e8be

Browse files
komygomfj
authored andcommitted
feat(ecmascript): Promise.any (trynova#883)
1 parent 9222271 commit 7f5e8be

File tree

4 files changed

+129
-125
lines changed

4 files changed

+129
-125
lines changed

nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs

Lines changed: 114 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
use crate::{
22
ecmascript::{
3+
abstract_operations::operations_on_objects::define_property_or_throw,
34
builtins::{
45
Array,
6+
error::ErrorHeapData,
57
promise::Promise,
68
promise_objects::promise_abstract_operations::{
79
promise_capability_records::PromiseCapability,
810
promise_reaction_records::PromiseReactionType,
911
},
1012
},
11-
execution::Agent,
12-
types::{BUILTIN_STRING_MEMORY, IntoValue, OrdinaryObject, Value},
13+
execution::{Agent, agent::ExceptionType},
14+
types::{BUILTIN_STRING_MEMORY, IntoValue, OrdinaryObject, PropertyDescriptor, Value},
1315
},
1416
engine::{
1517
context::{Bindable, GcScope, NoGcScope, bindable_handle},
@@ -23,8 +25,9 @@ use crate::{
2325

2426
#[derive(Debug, Clone, Copy)]
2527
pub enum PromiseGroupType {
26-
PromiseAll,
27-
PromiseAllSettled,
28+
All,
29+
AllSettled,
30+
Any,
2831
}
2932

3033
#[derive(Debug, Clone, Copy)]
@@ -64,30 +67,32 @@ impl<'a> PromiseGroup<'a> {
6467
let record = self.get(agent);
6568

6669
match record.promise_group_type {
67-
PromiseGroupType::PromiseAll => match reaction_type {
70+
PromiseGroupType::All => match reaction_type {
6871
PromiseReactionType::Fulfill => {
6972
self.fulfill(agent, index, value.unbind(), gc.reborrow());
7073
}
7174
PromiseReactionType::Reject => {
72-
self.reject(agent, value.unbind(), gc.nogc());
75+
self.immediately_reject(agent, value.unbind(), gc.nogc());
7376
}
7477
},
75-
PromiseGroupType::PromiseAllSettled => {
78+
PromiseGroupType::AllSettled => {
7679
let obj = self
7780
.to_all_settled_obj(agent, reaction_type, value.unbind(), gc.nogc())
7881
.bind(gc.nogc());
7982
self.fulfill(agent, index, obj.unbind(), gc.reborrow());
8083
}
84+
PromiseGroupType::Any => match reaction_type {
85+
PromiseReactionType::Fulfill => {
86+
self.immediately_resolve(agent, value.unbind(), gc.reborrow());
87+
}
88+
PromiseReactionType::Reject => {
89+
self.reject(agent, index, value.unbind(), gc.reborrow());
90+
}
91+
},
8192
}
8293
}
8394

84-
pub(crate) fn fulfill(
85-
self,
86-
agent: &mut Agent,
87-
index: u32,
88-
value: Value<'a>,
89-
mut gc: GcScope<'a, '_>,
90-
) {
95+
fn fulfill(self, agent: &mut Agent, index: u32, value: Value<'a>, mut gc: GcScope<'a, '_>) {
9196
let promise_group = self.bind(gc.nogc());
9297
let value = value.bind(gc.nogc());
9398

@@ -98,17 +103,72 @@ impl<'a> PromiseGroup<'a> {
98103
elements[index as usize] = Some(value.unbind());
99104

100105
if let Some(promise_to_resolve) = promise_to_resolve {
106+
promise_group.pop_empty_records(agent);
107+
101108
let capability = PromiseCapability::from_promise(promise_to_resolve, true);
102109
capability.resolve(agent, result_array.into_value().unbind(), gc.reborrow());
103110
}
104111
}
105112

106-
pub(crate) fn reject(self, agent: &mut Agent, value: Value<'a>, gc: NoGcScope<'a, '_>) {
113+
fn reject(self, agent: &mut Agent, index: u32, error: Value<'a>, mut gc: GcScope<'a, '_>) {
114+
let promise_group = self.bind(gc.nogc());
115+
let error = error.bind(gc.nogc());
116+
117+
let promise_group_record = promise_group.get_mut(agent);
118+
let (result_array, promise_to_resolve) = promise_group_record.take_result_and_promise();
119+
120+
let elements = result_array.as_mut_slice(agent);
121+
elements[index as usize] = Some(error.unbind());
122+
123+
if let Some(promise_to_resolve) = promise_to_resolve {
124+
promise_group.pop_empty_records(agent);
125+
126+
let aggregate_error = agent.heap.create(ErrorHeapData::new(
127+
ExceptionType::AggregateError,
128+
None,
129+
None,
130+
));
131+
132+
let capability = PromiseCapability::from_promise(promise_to_resolve, true);
133+
define_property_or_throw(
134+
agent,
135+
aggregate_error,
136+
BUILTIN_STRING_MEMORY.errors.into(),
137+
PropertyDescriptor {
138+
value: Some(result_array.into_value().unbind()),
139+
writable: Some(true),
140+
get: None,
141+
set: None,
142+
enumerable: Some(true),
143+
configurable: Some(true),
144+
},
145+
gc.reborrow(),
146+
)
147+
.unwrap();
148+
149+
capability.reject(agent, aggregate_error.into_value(), gc.nogc());
150+
}
151+
}
152+
153+
fn immediately_resolve(self, agent: &mut Agent, value: Value<'a>, gc: GcScope<'a, '_>) {
154+
let value = value.bind(gc.nogc());
155+
let promise_group = self.bind(gc.nogc());
156+
let data = promise_group.get_mut(agent);
157+
158+
let capability = PromiseCapability::from_promise(data.promise, true);
159+
160+
promise_group.pop_empty_records(agent);
161+
capability.resolve(agent, value.unbind(), gc);
162+
}
163+
164+
fn immediately_reject(self, agent: &mut Agent, value: Value<'a>, gc: NoGcScope<'a, '_>) {
107165
let value = value.bind(gc);
108166
let promise_group = self.bind(gc);
109167
let data = promise_group.get_mut(agent);
110168

111169
let capability = PromiseCapability::from_promise(data.promise, true);
170+
171+
promise_group.pop_empty_records(agent);
112172
capability.reject(agent, value.unbind(), gc);
113173
}
114174

@@ -121,47 +181,35 @@ impl<'a> PromiseGroup<'a> {
121181
) -> Value<'a> {
122182
let value = value.bind(gc);
123183

124-
let obj = match reaction_type {
125-
PromiseReactionType::Fulfill => OrdinaryObject::create_object(
126-
agent,
127-
Some(
128-
agent
129-
.current_realm_record()
130-
.intrinsics()
131-
.object_prototype()
132-
.into(),
133-
),
134-
&[
135-
ObjectEntry::new_data_entry(
136-
BUILTIN_STRING_MEMORY.status.into(),
137-
BUILTIN_STRING_MEMORY.fulfilled.into(),
138-
),
139-
ObjectEntry::new_data_entry(BUILTIN_STRING_MEMORY.value.into(), value.unbind()),
140-
],
141-
)
142-
.bind(gc),
143-
PromiseReactionType::Reject => OrdinaryObject::create_object(
144-
agent,
145-
Some(
146-
agent
147-
.current_realm_record()
148-
.intrinsics()
149-
.object_prototype()
150-
.into(),
151-
),
152-
&[
153-
ObjectEntry::new_data_entry(
154-
BUILTIN_STRING_MEMORY.status.into(),
155-
BUILTIN_STRING_MEMORY.rejected.into(),
156-
),
157-
ObjectEntry::new_data_entry(
158-
BUILTIN_STRING_MEMORY.reason.into(),
159-
value.unbind(),
160-
),
161-
],
162-
)
163-
.bind(gc),
164-
};
184+
let entries = vec![
185+
ObjectEntry::new_data_entry(
186+
BUILTIN_STRING_MEMORY.status.into(),
187+
match reaction_type {
188+
PromiseReactionType::Fulfill => BUILTIN_STRING_MEMORY.fulfilled.into(),
189+
PromiseReactionType::Reject => BUILTIN_STRING_MEMORY.rejected.into(),
190+
},
191+
),
192+
ObjectEntry::new_data_entry(
193+
match reaction_type {
194+
PromiseReactionType::Fulfill => BUILTIN_STRING_MEMORY.value.into(),
195+
PromiseReactionType::Reject => BUILTIN_STRING_MEMORY.reason.into(),
196+
},
197+
value.unbind(),
198+
),
199+
];
200+
201+
let obj = OrdinaryObject::create_object(
202+
agent,
203+
Some(
204+
agent
205+
.current_realm_record()
206+
.intrinsics()
207+
.object_prototype()
208+
.into(),
209+
),
210+
&entries,
211+
)
212+
.bind(gc);
165213

166214
obj.into_value().unbind()
167215
}
@@ -186,6 +234,16 @@ impl<'a> PromiseGroup<'a> {
186234
.expect("PromiseGroupRecord not found")
187235
}
188236

237+
fn pop_empty_records(self, agent: &mut Agent) {
238+
while let Some(last) = agent.heap.promise_group_records.last() {
239+
if last.remaining_elements_count == 0 {
240+
agent.heap.promise_group_records.pop();
241+
} else {
242+
break;
243+
}
244+
}
245+
}
246+
189247
pub(crate) const _DEF: Self = { Self(BaseIndex::from_u32_index(0)) };
190248
}
191249

nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,7 @@ impl PromiseConstructor {
236236
arguments: ArgumentsList,
237237
gc: GcScope<'gc, '_>,
238238
) -> JsResult<'gc, Value<'gc>> {
239-
promise_group(
240-
agent,
241-
this_value,
242-
arguments,
243-
PromiseGroupType::PromiseAll,
244-
gc,
245-
)
239+
promise_group(agent, this_value, arguments, PromiseGroupType::All, gc)
246240
}
247241

248242
/// ### [27.2.4.2 Promise.allSettled ( iterable )](https://tc39.es/ecma262/#sec-promise.allsettled)
@@ -260,7 +254,7 @@ impl PromiseConstructor {
260254
agent,
261255
this_value,
262256
arguments,
263-
PromiseGroupType::PromiseAllSettled,
257+
PromiseGroupType::AllSettled,
264258
gc,
265259
)
266260
}
@@ -272,11 +266,11 @@ impl PromiseConstructor {
272266
/// > constructor.
273267
fn any<'gc>(
274268
agent: &mut Agent,
275-
_this_value: Value,
276-
_arguments: ArgumentsList,
269+
this_value: Value,
270+
arguments: ArgumentsList,
277271
gc: GcScope<'gc, '_>,
278272
) -> JsResult<'gc, Value<'gc>> {
279-
Err(agent.todo("Promise.any", gc.into_nogc()))
273+
promise_group(agent, this_value, arguments, PromiseGroupType::Any, gc)
280274
}
281275

282276
/// ### [27.2.4.5 Promise.race ( iterable )](https://tc39.es/ecma262/#sec-promise.race)
@@ -685,6 +679,7 @@ fn promise_group<'gc>(
685679

686680
/// ### [27.2.4.1.2 PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiseall)
687681
/// ### [27.2.4.2.1 PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiseallsettled)
682+
/// ### [27.2.4.3.1 PerformPromiseAny ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiseany)
688683
#[allow(clippy::too_many_arguments)]
689684
fn perform_promise_group<'gc>(
690685
agent: &mut Agent,

tests/expectations.json

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,82 +1249,33 @@
12491249
"built-ins/Promise/any/ctx-ctor.js": "FAIL",
12501250
"built-ins/Promise/any/ctx-non-ctor.js": "FAIL",
12511251
"built-ins/Promise/any/ctx-non-object.js": "FAIL",
1252-
"built-ins/Promise/any/invoke-resolve-error-close.js": "FAIL",
1253-
"built-ins/Promise/any/invoke-resolve-error-reject.js": "FAIL",
1254-
"built-ins/Promise/any/invoke-resolve-get-error-reject.js": "FAIL",
1255-
"built-ins/Promise/any/invoke-resolve-get-error.js": "FAIL",
1256-
"built-ins/Promise/any/invoke-resolve-get-once-multiple-calls.js": "FAIL",
12571252
"built-ins/Promise/any/invoke-resolve-get-once-no-calls.js": "FAIL",
12581253
"built-ins/Promise/any/invoke-resolve-on-promises-every-iteration-of-custom.js": "FAIL",
1259-
"built-ins/Promise/any/invoke-resolve-on-promises-every-iteration-of-promise.js": "FAIL",
12601254
"built-ins/Promise/any/invoke-resolve-on-values-every-iteration-of-custom.js": "FAIL",
1261-
"built-ins/Promise/any/invoke-resolve-on-values-every-iteration-of-promise.js": "FAIL",
12621255
"built-ins/Promise/any/invoke-resolve-return.js": "FAIL",
1263-
"built-ins/Promise/any/invoke-resolve.js": "FAIL",
1264-
"built-ins/Promise/any/invoke-then-error-close.js": "FAIL",
1256+
"built-ins/Promise/any/invoke-then-error-close.js": "TIMEOUT",
12651257
"built-ins/Promise/any/invoke-then-error-reject.js": "FAIL",
1266-
"built-ins/Promise/any/invoke-then-get-error-close.js": "FAIL",
1258+
"built-ins/Promise/any/invoke-then-get-error-close.js": "TIMEOUT",
12671259
"built-ins/Promise/any/invoke-then-get-error-reject.js": "FAIL",
12681260
"built-ins/Promise/any/invoke-then-on-promises-every-iteration.js": "FAIL",
1269-
"built-ins/Promise/any/invoke-then.js": "FAIL",
1261+
"built-ins/Promise/any/invoke-then.js": "UNRESOLVED",
12701262
"built-ins/Promise/any/iter-arg-is-empty-iterable-reject.js": "FAIL",
12711263
"built-ins/Promise/any/iter-arg-is-empty-string-reject.js": "FAIL",
1272-
"built-ins/Promise/any/iter-arg-is-error-object-reject.js": "FAIL",
1273-
"built-ins/Promise/any/iter-arg-is-false-reject.js": "FAIL",
1274-
"built-ins/Promise/any/iter-arg-is-null-reject.js": "FAIL",
1275-
"built-ins/Promise/any/iter-arg-is-number-reject.js": "FAIL",
1276-
"built-ins/Promise/any/iter-arg-is-poisoned.js": "FAIL",
1277-
"built-ins/Promise/any/iter-arg-is-string-resolve.js": "FAIL",
1278-
"built-ins/Promise/any/iter-arg-is-symbol-reject.js": "FAIL",
1279-
"built-ins/Promise/any/iter-arg-is-true-reject.js": "FAIL",
1280-
"built-ins/Promise/any/iter-arg-is-undefined-reject.js": "FAIL",
1281-
"built-ins/Promise/any/iter-assigned-false-reject.js": "FAIL",
1282-
"built-ins/Promise/any/iter-assigned-null-reject.js": "FAIL",
1283-
"built-ins/Promise/any/iter-assigned-number-reject.js": "FAIL",
1284-
"built-ins/Promise/any/iter-assigned-string-reject.js": "FAIL",
1285-
"built-ins/Promise/any/iter-assigned-symbol-reject.js": "FAIL",
1286-
"built-ins/Promise/any/iter-assigned-true-reject.js": "FAIL",
1287-
"built-ins/Promise/any/iter-assigned-undefined-reject.js": "FAIL",
12881264
"built-ins/Promise/any/iter-next-val-err-no-close.js": "FAIL",
1289-
"built-ins/Promise/any/iter-next-val-err-reject.js": "FAIL",
1290-
"built-ins/Promise/any/iter-returns-false-reject.js": "FAIL",
1291-
"built-ins/Promise/any/iter-returns-null-reject.js": "FAIL",
1292-
"built-ins/Promise/any/iter-returns-number-reject.js": "FAIL",
1293-
"built-ins/Promise/any/iter-returns-string-reject.js": "FAIL",
1294-
"built-ins/Promise/any/iter-returns-symbol-reject.js": "FAIL",
1295-
"built-ins/Promise/any/iter-returns-true-reject.js": "FAIL",
1296-
"built-ins/Promise/any/iter-returns-undefined-reject.js": "FAIL",
12971265
"built-ins/Promise/any/iter-step-err-no-close.js": "FAIL",
1298-
"built-ins/Promise/any/iter-step-err-reject.js": "FAIL",
12991266
"built-ins/Promise/any/new-reject-function.js": "FAIL",
1300-
"built-ins/Promise/any/reject-all-mixed.js": "FAIL",
1301-
"built-ins/Promise/any/reject-deferred.js": "FAIL",
13021267
"built-ins/Promise/any/reject-element-function-extensible.js": "FAIL",
13031268
"built-ins/Promise/any/reject-element-function-length.js": "FAIL",
13041269
"built-ins/Promise/any/reject-element-function-name.js": "FAIL",
13051270
"built-ins/Promise/any/reject-element-function-nonconstructor.js": "FAIL",
13061271
"built-ins/Promise/any/reject-element-function-property-order.js": "FAIL",
13071272
"built-ins/Promise/any/reject-element-function-prototype.js": "FAIL",
13081273
"built-ins/Promise/any/reject-from-same-thenable.js": "FAIL",
1309-
"built-ins/Promise/any/reject-ignored-immed.js": "FAIL",
1310-
"built-ins/Promise/any/reject-immed.js": "FAIL",
13111274
"built-ins/Promise/any/resolve-before-loop-exit-from-same.js": "FAIL",
13121275
"built-ins/Promise/any/resolve-before-loop-exit.js": "FAIL",
1313-
"built-ins/Promise/any/resolve-from-reject-catch.js": "FAIL",
1314-
"built-ins/Promise/any/resolve-from-resolve-reject-catch.js": "FAIL",
13151276
"built-ins/Promise/any/resolve-from-same-thenable.js": "FAIL",
1316-
"built-ins/Promise/any/resolve-ignores-late-rejection-deferred.js": "FAIL",
1317-
"built-ins/Promise/any/resolve-ignores-late-rejection.js": "FAIL",
1318-
"built-ins/Promise/any/resolve-non-callable.js": "FAIL",
1319-
"built-ins/Promise/any/resolve-non-thenable.js": "FAIL",
1320-
"built-ins/Promise/any/resolve-not-callable-reject-with-typeerror.js": "FAIL",
13211277
"built-ins/Promise/any/resolve-throws-iterator-return-is-not-callable.js": "FAIL",
13221278
"built-ins/Promise/any/resolve-throws-iterator-return-null-or-undefined.js": "FAIL",
1323-
"built-ins/Promise/any/resolved-sequence-extra-ticks.js": "FAIL",
1324-
"built-ins/Promise/any/resolved-sequence-mixed.js": "FAIL",
1325-
"built-ins/Promise/any/resolved-sequence-with-rejections.js": "FAIL",
1326-
"built-ins/Promise/any/resolved-sequence.js": "FAIL",
1327-
"built-ins/Promise/any/returns-promise.js": "FAIL",
13281279
"built-ins/Promise/any/species-get-error.js": "FAIL",
13291280
"built-ins/Promise/exception-after-resolve-in-thenable-job.js": "FAIL",
13301281
"built-ins/Promise/executor-function-extensible.js": "FAIL",
@@ -8111,4 +8062,4 @@
81118062
"staging/sm/syntax/yield-as-identifier.js": "FAIL",
81128063
"staging/source-phase-imports/import-source-source-text-module.js": "FAIL",
81138064
"staging/top-level-await/tla-hang-entry.js": "FAIL"
8114-
}
8065+
}

0 commit comments

Comments
 (0)