From 430bbda60617897d3baa22b82a50e7c8500e2e55 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Fri, 14 Nov 2025 09:56:06 -0300 Subject: [PATCH 01/10] Perform Promise Race Without Promise Group --- .../promise_group_record.rs | 4 + .../promise_objects/promise_constructor.rs | 151 +++++++++++++++--- 2 files changed, 137 insertions(+), 18 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs index 801f27503..74ecfda6f 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs @@ -28,6 +28,7 @@ pub enum PromiseGroupType { All, AllSettled, Any, + Race, } #[derive(Debug, Clone, Copy)] @@ -89,6 +90,9 @@ impl<'a> PromiseGroup<'a> { self.reject(agent, index, value.unbind(), gc.reborrow()); } }, + PromiseGroupType::Race => { + unreachable!() + } } } diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index c4cbee21d..0853f3654 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -14,8 +14,8 @@ use crate::{ }, builders::builtin_function_builder::BuiltinFunctionBuilder, builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, - array_create, + ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, BuiltinGetter, + BuiltinIntrinsicConstructor, array_create, create_builtin_function, ordinary::ordinary_create_from_constructor, promise::{ Promise, @@ -285,11 +285,11 @@ impl PromiseConstructor { /// > method. fn race<'gc>( agent: &mut Agent, - _this_value: Value, - _arguments: ArgumentsList, + this_value: Value, + arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Promise.race", gc.into_nogc())) + promise_group(agent, this_value, arguments, PromiseGroupType::Race, gc) } /// ### [27.2.4.6 Promise.reject ( r )](https://tc39.es/ecma262/#sec-promise.reject) @@ -632,19 +632,48 @@ fn promise_group<'gc>( // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). let mut iterator_done = false; - let result = perform_promise_group( - agent, - iterator.clone(), - next_method.unbind(), - constructor, - promise_capability.unbind(), - promise_resolve, - &mut iterator_done, - promise_group_type, - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()); + let result = match promise_group_type { + PromiseGroupType::All | PromiseGroupType::AllSettled | PromiseGroupType::Any => { + perform_promise_group( + agent, + iterator.clone(), + next_method.unbind(), + constructor, + promise_capability.unbind(), + promise_resolve, + &mut iterator_done, + promise_group_type, + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()) + } + PromiseGroupType::Race => perform_promise_race( + agent, + iterator.clone(), + next_method.unbind(), + constructor, + promise_capability.unbind(), + promise_resolve, + &mut iterator_done, + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()), + }; + // let result = perform_promise_group( + // agent, + // iterator.clone(), + // next_method.unbind(), + // constructor, + // promise_capability.unbind(), + // promise_resolve, + // &mut iterator_done, + // promise_group_type, + // gc.reborrow(), + // ) + // .unbind() + // .bind(gc.nogc()); // 8. If result is an abrupt completion, then let result = match result { @@ -828,6 +857,92 @@ fn perform_promise_group<'gc>( } } +/// ### [27.2.4.5.1 PerformPromiseRace ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiserace) +#[allow(clippy::too_many_arguments)] +fn perform_promise_race<'gc>( + agent: &mut Agent, + iterator: Scoped, + next_method: Option, + constructor: Scoped, + result_capability: PromiseCapability, + promise_resolve: Scoped, + iterator_done: &mut bool, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Promise<'gc>> { + let result_capability = result_capability.bind(gc.nogc()); + + let Some(next_method) = next_method else { + return Err(throw_not_callable(agent, gc.into_nogc())); + }; + + let next_method = next_method.scope(agent, gc.nogc()); + let promise = result_capability.promise.scope(agent, gc.nogc()); + + loop { + let iterator_record = IteratorRecord { + iterator: iterator.get(agent), + next_method: next_method.get(agent), + } + .bind(gc.nogc()); + + let next = iterator_step_value(agent, iterator_record.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + + let Some(next) = next else { + *iterator_done = true; + return Ok(promise.get(agent)); + }; + + let call_result = call_function( + agent, + promise_resolve.get(agent), + constructor.get(agent).into_value(), + Some(ArgumentsList::from_mut_value(&mut next.unbind())), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + let next_promise = match call_result { + Value::Promise(next_promise) => next_promise, + _ => Promise::new_resolved(agent, call_result), + }; + + let callback = create_builtin_function( + agent, + Behaviour::Regular(on_promise_race_settled), + BuiltinFunctionArgs::new(0, "on_promise_race_settled"), + gc.nogc(), + ); + + let promise_capability = PromiseCapability { + promise: promise.get(agent).bind(gc.nogc()), + must_be_unresolved: true, + }; + + inner_promise_then( + agent, + next_promise.unbind(), + PromiseReactionHandler::JobCallback(callback.into()), + PromiseReactionHandler::JobCallback(callback.into()), + Some(promise_capability), + gc.nogc(), + ); + } +} + +fn on_promise_race_settled<'a>( + _agent: &mut Agent, + _value: Value, + arguments: ArgumentsList, + gc: GcScope<'a, '_>, +) -> JsResult<'a, Value<'a>> { + let arguments = arguments.bind(gc.nogc()); + let result_value = arguments.get(0); + Ok(result_value.unbind()) +} + fn throw_promise_subclassing_not_supported<'a>( agent: &mut Agent, gc: NoGcScope<'a, '_>, From 13509cb55cc929232803f25bddb8a77bfeff8e78 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Sat, 15 Nov 2025 10:02:19 -0300 Subject: [PATCH 02/10] Use `PromiseJob::Empty` --- .../promise_objects/promise_constructor.rs | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index 0853f3654..df4195bf8 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -14,8 +14,8 @@ use crate::{ }, builders::builtin_function_builder::BuiltinFunctionBuilder, builtins::{ - ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, BuiltinGetter, - BuiltinIntrinsicConstructor, array_create, create_builtin_function, + ArgumentsList, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, + array_create, ordinary::ordinary_create_from_constructor, promise::{ Promise, @@ -909,13 +909,6 @@ fn perform_promise_race<'gc>( _ => Promise::new_resolved(agent, call_result), }; - let callback = create_builtin_function( - agent, - Behaviour::Regular(on_promise_race_settled), - BuiltinFunctionArgs::new(0, "on_promise_race_settled"), - gc.nogc(), - ); - let promise_capability = PromiseCapability { promise: promise.get(agent).bind(gc.nogc()), must_be_unresolved: true, @@ -924,25 +917,14 @@ fn perform_promise_race<'gc>( inner_promise_then( agent, next_promise.unbind(), - PromiseReactionHandler::JobCallback(callback.into()), - PromiseReactionHandler::JobCallback(callback.into()), + PromiseReactionHandler::Empty, + PromiseReactionHandler::Empty, Some(promise_capability), gc.nogc(), ); } } -fn on_promise_race_settled<'a>( - _agent: &mut Agent, - _value: Value, - arguments: ArgumentsList, - gc: GcScope<'a, '_>, -) -> JsResult<'a, Value<'a>> { - let arguments = arguments.bind(gc.nogc()); - let result_value = arguments.get(0); - Ok(result_value.unbind()) -} - fn throw_promise_subclassing_not_supported<'a>( agent: &mut Agent, gc: NoGcScope<'a, '_>, From 29f954444ffba42ff4a322f491e505660b43358c Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Sat, 15 Nov 2025 10:24:32 -0300 Subject: [PATCH 03/10] self-contained promise race --- .../promise_group_record.rs | 4 - .../promise_objects/promise_constructor.rs | 163 +++++++++++++----- 2 files changed, 119 insertions(+), 48 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs index 74ecfda6f..801f27503 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_group_record.rs @@ -28,7 +28,6 @@ pub enum PromiseGroupType { All, AllSettled, Any, - Race, } #[derive(Debug, Clone, Copy)] @@ -90,9 +89,6 @@ impl<'a> PromiseGroup<'a> { self.reject(agent, index, value.unbind(), gc.reborrow()); } }, - PromiseGroupType::Race => { - unreachable!() - } } } diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index df4195bf8..c9acf541a 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -287,9 +287,113 @@ impl PromiseConstructor { agent: &mut Agent, this_value: Value, arguments: ArgumentsList, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - promise_group(agent, this_value, arguments, PromiseGroupType::Race, gc) + let this_value = this_value.bind(gc.nogc()); + let arguments = arguments.bind(gc.nogc()); + let iterable = arguments.get(0).scope(agent, gc.nogc()); + + // 1. Let C be the this value. + if this_value + != agent + .current_realm_record() + .intrinsics() + .promise() + .into_value() + { + return Err(throw_promise_subclassing_not_supported( + agent, + gc.into_nogc(), + )); + } + + // 2. Let promiseCapability be ? NewPromiseCapability(C). + let Some(constructor) = is_constructor(agent, this_value) else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Expected the this value to be a constructor.", + gc.into_nogc(), + )); + }; + let constructor = constructor.scope(agent, gc.nogc()); + let promise_capability = PromiseCapability::new(agent, gc.nogc()); + let promise = promise_capability.promise().scope(agent, gc.nogc()); + + // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). + let promise_resolve = get_promise_resolve(agent, constructor.get(agent), gc.reborrow()) + .unbind() + .bind(gc.nogc()); + + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + let promise_capability = PromiseCapability { + promise: promise.get(agent).bind(gc.nogc()), + must_be_unresolved: true, + }; + let promise_resolve = + if_abrupt_reject_promise_m!(agent, promise_resolve, promise_capability, gc); + let promise_resolve = promise_resolve.scope(agent, gc.nogc()); + + // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). + let iterator_record = get_iterator(agent, iterable.get(agent), false, gc.reborrow()) + .unbind() + .bind(gc.nogc()); + + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + let promise_capability = PromiseCapability { + promise: promise.get(agent).bind(gc.nogc()), + must_be_unresolved: true, + }; + let MaybeInvalidIteratorRecord { + iterator, + next_method, + } = if_abrupt_reject_promise_m!(agent, iterator_record, promise_capability, gc); + + let iterator = iterator.scope(agent, gc.nogc()); + + // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). + let mut iterator_done = false; + let result = perform_promise_race( + agent, + iterator.clone(), + next_method.unbind(), + constructor, + promise_capability.unbind(), + promise_resolve, + &mut iterator_done, + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()); + + // 8. If result is an abrupt completion, then + let result = match result { + Err(mut result) => { + // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + if !iterator_done { + result = iterator_close_with_error( + agent, + iterator.get(agent), + result.unbind(), + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()); + } + + // b. IfAbruptRejectPromise(result, promiseCapability). + let promise_capability = PromiseCapability { + promise: promise.get(agent).bind(gc.nogc()), + must_be_unresolved: true, + }; + // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). + promise_capability.reject(agent, result.value().unbind(), gc.nogc()); + // b. Return capability.[[Promise]]. + promise_capability.promise() + } + Ok(result) => result, + }; + // 9. Return ! result. + Ok(result.into_value().unbind()) } /// ### [27.2.4.6 Promise.reject ( r )](https://tc39.es/ecma262/#sec-promise.reject) @@ -632,48 +736,19 @@ fn promise_group<'gc>( // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). let mut iterator_done = false; - let result = match promise_group_type { - PromiseGroupType::All | PromiseGroupType::AllSettled | PromiseGroupType::Any => { - perform_promise_group( - agent, - iterator.clone(), - next_method.unbind(), - constructor, - promise_capability.unbind(), - promise_resolve, - &mut iterator_done, - promise_group_type, - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()) - } - PromiseGroupType::Race => perform_promise_race( - agent, - iterator.clone(), - next_method.unbind(), - constructor, - promise_capability.unbind(), - promise_resolve, - &mut iterator_done, - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()), - }; - // let result = perform_promise_group( - // agent, - // iterator.clone(), - // next_method.unbind(), - // constructor, - // promise_capability.unbind(), - // promise_resolve, - // &mut iterator_done, - // promise_group_type, - // gc.reborrow(), - // ) - // .unbind() - // .bind(gc.nogc()); + let result = perform_promise_group( + agent, + iterator.clone(), + next_method.unbind(), + constructor, + promise_capability.unbind(), + promise_resolve, + &mut iterator_done, + promise_group_type, + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()); // 8. If result is an abrupt completion, then let result = match result { From 341dd52c231131c65e9ed4926b49a0e1ab7d28b1 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Sat, 15 Nov 2025 10:32:19 -0300 Subject: [PATCH 04/10] Updated expectations --- tests/expectations.json | 71 +++-------------------------------------- tests/metrics.json | 2 +- 2 files changed, 5 insertions(+), 68 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index fdfd77a86..b34701e27 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -1290,92 +1290,30 @@ "built-ins/Promise/prototype/then/ctor-poisoned.js": "FAIL", "built-ins/Promise/prototype/then/ctor-throws.js": "FAIL", "built-ins/Promise/prototype/then/deferred-is-resolved-value.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A2.1_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A2.2_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A2.2_T2.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A2.2_T3.js": "FAIL", "built-ins/Promise/race/S25.4.4.3_A3.1_T1.js": "FAIL", "built-ins/Promise/race/S25.4.4.3_A3.1_T2.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A4.1_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A4.1_T2.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A5.1_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A6.1_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A6.2_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A7.1_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A7.1_T2.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A7.1_T3.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A7.2_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A7.3_T1.js": "FAIL", - "built-ins/Promise/race/S25.4.4.3_A7.3_T2.js": "FAIL", "built-ins/Promise/race/capability-executor-called-twice.js": "FAIL", "built-ins/Promise/race/capability-executor-not-callable.js": "FAIL", "built-ins/Promise/race/ctx-ctor-throws.js": "FAIL", "built-ins/Promise/race/ctx-ctor.js": "FAIL", "built-ins/Promise/race/ctx-non-ctor.js": "FAIL", "built-ins/Promise/race/ctx-non-object.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-error-close.js": "FAIL", "built-ins/Promise/race/invoke-resolve-error-reject.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-get-error-reject.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-get-error.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-get-once-multiple-calls.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-get-once-no-calls.js": "FAIL", "built-ins/Promise/race/invoke-resolve-on-promises-every-iteration-of-custom.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-on-promises-every-iteration-of-promise.js": "FAIL", - "built-ins/Promise/race/invoke-resolve-on-values-every-iteration-of-promise.js": "FAIL", "built-ins/Promise/race/invoke-resolve-return.js": "FAIL", - "built-ins/Promise/race/invoke-resolve.js": "FAIL", - "built-ins/Promise/race/invoke-then-error-close.js": "FAIL", - "built-ins/Promise/race/invoke-then-error-reject.js": "FAIL", - "built-ins/Promise/race/invoke-then-get-error-close.js": "FAIL", - "built-ins/Promise/race/invoke-then-get-error-reject.js": "FAIL", + "built-ins/Promise/race/invoke-then-error-close.js": "TIMEOUT", + "built-ins/Promise/race/invoke-then-error-reject.js": "UNRESOLVED", + "built-ins/Promise/race/invoke-then-get-error-close.js": "TIMEOUT", + "built-ins/Promise/race/invoke-then-get-error-reject.js": "UNRESOLVED", "built-ins/Promise/race/invoke-then.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-false-reject.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-null-reject.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-number-reject.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-string-resolve.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-symbol-reject.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-true-reject.js": "FAIL", - "built-ins/Promise/race/iter-arg-is-undefined-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-false-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-null-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-number-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-string-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-symbol-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-true-reject.js": "FAIL", - "built-ins/Promise/race/iter-assigned-undefined-reject.js": "FAIL", "built-ins/Promise/race/iter-next-val-err-no-close.js": "FAIL", - "built-ins/Promise/race/iter-next-val-err-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-false-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-null-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-number-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-string-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-symbol-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-true-reject.js": "FAIL", - "built-ins/Promise/race/iter-returns-undefined-reject.js": "FAIL", "built-ins/Promise/race/iter-step-err-no-close.js": "FAIL", - "built-ins/Promise/race/iter-step-err-reject.js": "FAIL", - "built-ins/Promise/race/reject-deferred.js": "FAIL", "built-ins/Promise/race/reject-from-same-thenable.js": "FAIL", - "built-ins/Promise/race/reject-ignored-deferred.js": "FAIL", - "built-ins/Promise/race/reject-ignored-immed.js": "FAIL", - "built-ins/Promise/race/reject-immed.js": "FAIL", "built-ins/Promise/race/resolve-from-same-thenable.js": "FAIL", - "built-ins/Promise/race/resolve-ignores-late-rejection-deferred.js": "FAIL", - "built-ins/Promise/race/resolve-ignores-late-rejection.js": "FAIL", - "built-ins/Promise/race/resolve-non-callable.js": "FAIL", - "built-ins/Promise/race/resolve-non-obj.js": "FAIL", - "built-ins/Promise/race/resolve-non-thenable.js": "FAIL", - "built-ins/Promise/race/resolve-poisoned-then.js": "FAIL", "built-ins/Promise/race/resolve-prms-cstm-then.js": "FAIL", "built-ins/Promise/race/resolve-self.js": "FAIL", - "built-ins/Promise/race/resolve-thenable.js": "FAIL", "built-ins/Promise/race/resolve-throws-iterator-return-is-not-callable.js": "FAIL", "built-ins/Promise/race/resolve-throws-iterator-return-null-or-undefined.js": "FAIL", - "built-ins/Promise/race/resolved-sequence-extra-ticks.js": "FAIL", - "built-ins/Promise/race/resolved-sequence-mixed.js": "FAIL", - "built-ins/Promise/race/resolved-sequence-with-rejections.js": "FAIL", - "built-ins/Promise/race/resolved-sequence.js": "FAIL", - "built-ins/Promise/race/resolved-then-catch-finally.js": "FAIL", "built-ins/Promise/race/same-reject-function.js": "FAIL", "built-ins/Promise/race/same-resolve-function.js": "FAIL", "built-ins/Promise/race/species-get-error.js": "FAIL", @@ -7752,7 +7690,6 @@ "staging/sm/PrivateName/proxy-init-set.js": "FAIL", "staging/sm/PrivateName/read-private-eval.js": "FAIL", "staging/sm/Promise/bug-1288382.js": "FAIL", - "staging/sm/Promise/for-of-iterator-uses-getv.js": "FAIL", "staging/sm/Proxy/getPrototypeOf.js": "FAIL", "staging/sm/Proxy/hasInstance.js": "FAIL", "staging/sm/Proxy/json-stringify-replacer-array-revocable-proxy.js": "FAIL", diff --git a/tests/metrics.json b/tests/metrics.json index d2b0219e9..328736e1a 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -8,4 +8,4 @@ "unresolved": 35 }, "total": 50733 -} \ No newline at end of file +} From 321ef5c1735dad60bd0c27a050cf15656b80b257 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Mon, 17 Nov 2025 09:09:50 -0300 Subject: [PATCH 05/10] Added helper function --- .../promise_objects/promise_constructor.rs | 309 ++++++++++++------ 1 file changed, 211 insertions(+), 98 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index c9acf541a..dbca562e0 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -41,10 +41,13 @@ use crate::{ }, engine::{ Scoped, - context::{Bindable, GcScope, NoGcScope}, + context::{Bindable, GcScope, NoGcScope, bindable_handle}, rootable::Scopable, }, - heap::{CreateHeapData, IntrinsicConstructorIndexes, ObjectEntry, WellKnownSymbolIndexes}, + heap::{ + CompactionLists, CreateHeapData, HeapMarkAndSweep, IntrinsicConstructorIndexes, + ObjectEntry, WellKnownSymbolIndexes, WorkQueues, + }, }; use super::promise_abstract_operations::{ @@ -121,6 +124,30 @@ impl Builtin for PromiseGetSpecies { } impl BuiltinGetter for PromiseGetSpecies {} +struct PromiseGroupSetup<'a> { + iterator_record: IteratorRecord<'a>, + constructor: Function<'a>, + promise_capability: PromiseCapability<'a>, + promise_resolve: Function<'a>, +} +bindable_handle!(PromiseGroupSetup); + +impl HeapMarkAndSweep for PromiseGroupSetup<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + self.iterator_record.mark_values(queues); + self.constructor.mark_values(queues); + self.promise_capability.mark_values(queues); + self.promise_resolve.mark_values(queues); + } + + fn sweep_values(&mut self, compactions: &CompactionLists) { + self.iterator_record.sweep_values(compactions); + self.constructor.sweep_values(compactions); + self.promise_capability.sweep_values(compactions); + self.promise_resolve.sweep_values(compactions); + } +} + impl PromiseConstructor { /// ### [27.2.3.1 Promise ( executor )](https://tc39.es/ecma262/#sec-promise-executor) fn constructor<'gc>( @@ -291,109 +318,51 @@ impl PromiseConstructor { ) -> JsResult<'gc, Value<'gc>> { let this_value = this_value.bind(gc.nogc()); let arguments = arguments.bind(gc.nogc()); - let iterable = arguments.get(0).scope(agent, gc.nogc()); - // 1. Let C be the this value. - if this_value - != agent - .current_realm_record() - .intrinsics() - .promise() - .into_value() - { - return Err(throw_promise_subclassing_not_supported( - agent, - gc.into_nogc(), - )); - } - - // 2. Let promiseCapability be ? NewPromiseCapability(C). - let Some(constructor) = is_constructor(agent, this_value) else { - return Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, - "Expected the this value to be a constructor.", - gc.into_nogc(), - )); - }; - let constructor = constructor.scope(agent, gc.nogc()); - let promise_capability = PromiseCapability::new(agent, gc.nogc()); - let promise = promise_capability.promise().scope(agent, gc.nogc()); - - // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). - let promise_resolve = get_promise_resolve(agent, constructor.get(agent), gc.reborrow()) - .unbind() - .bind(gc.nogc()); - - // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). - let promise_capability = PromiseCapability { - promise: promise.get(agent).bind(gc.nogc()), - must_be_unresolved: true, - }; - let promise_resolve = - if_abrupt_reject_promise_m!(agent, promise_resolve, promise_capability, gc); - let promise_resolve = promise_resolve.scope(agent, gc.nogc()); - - // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). - let iterator_record = get_iterator(agent, iterable.get(agent), false, gc.reborrow()) - .unbind() - .bind(gc.nogc()); - - // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). - let promise_capability = PromiseCapability { - promise: promise.get(agent).bind(gc.nogc()), - must_be_unresolved: true, - }; - let MaybeInvalidIteratorRecord { - iterator, - next_method, - } = if_abrupt_reject_promise_m!(agent, iterator_record, promise_capability, gc); + let PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + } = promise_group_setup( + agent, + this_value.unbind(), + arguments.unbind(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); - let iterator = iterator.scope(agent, gc.nogc()); + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); + let promise = promise_capability.promise.scope(agent, gc.nogc()); // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). let mut iterator_done = false; let result = perform_promise_race( agent, iterator.clone(), - next_method.unbind(), - constructor, + iterator_record.next_method.unbind(), + constructor.unbind(), promise_capability.unbind(), - promise_resolve, + promise_resolve.unbind(), &mut iterator_done, gc.reborrow(), ) .unbind() .bind(gc.nogc()); - // 8. If result is an abrupt completion, then - let result = match result { - Err(mut result) => { - // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). - if !iterator_done { - result = iterator_close_with_error( - agent, - iterator.get(agent), - result.unbind(), - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()); - } - - // b. IfAbruptRejectPromise(result, promiseCapability). - let promise_capability = PromiseCapability { - promise: promise.get(agent).bind(gc.nogc()), - must_be_unresolved: true, - }; - // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). - promise_capability.reject(agent, result.value().unbind(), gc.nogc()); - // b. Return capability.[[Promise]]. - promise_capability.promise() - } - Ok(result) => result, - }; - // 9. Return ! result. - Ok(result.into_value().unbind()) + let result_value = handle_promise_group_result( + agent, + result.unbind(), + iterator_done, + iterator.get(agent), + promise.get(agent), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + Ok(result_value.unbind()) } /// ### [27.2.4.6 Promise.reject ( r )](https://tc39.es/ecma262/#sec-promise.reject) @@ -781,6 +750,153 @@ fn promise_group<'gc>( Ok(result.into_value().unbind()) } +fn promise_group_setup<'gc>( + agent: &mut Agent, + this_value: Value, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, PromiseGroupSetup<'gc>> { + let this_value = this_value.bind(gc.nogc()); + let arguments = arguments.bind(gc.nogc()); + let iterable = arguments.get(0).scope(agent, gc.nogc()); + + // 1. Let C be the this value. + if this_value + != agent + .current_realm_record() + .intrinsics() + .promise() + .into_value() + { + return Err(throw_promise_subclassing_not_supported( + agent, + gc.into_nogc(), + )); + } + + // 2. Let promiseCapability be ? NewPromiseCapability(C). + let Some(constructor) = is_constructor(agent, this_value) else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Expected the this value to be a constructor.", + gc.into_nogc(), + )); + }; + let constructor = constructor.scope(agent, gc.nogc()); + let promise_capability = PromiseCapability::new(agent, gc.nogc()); + let promise = promise_capability.promise().scope(agent, gc.nogc()); + + // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). + let promise_resolve = get_promise_resolve(agent, constructor.get(agent), gc.reborrow()) + .unbind() + .bind(gc.nogc()); + + // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). + let promise_capability = PromiseCapability { + promise: promise.get(agent).bind(gc.nogc()), + must_be_unresolved: true, + }; + + // let promise_resolve = + // if_abrupt_reject_promise_m!(agent, promise_resolve, promise_capability, gc); + let promise_resolve = match promise_resolve.unbind().bind(gc.nogc()) { + Err(err) => { + promise_capability.reject(agent, err.value().unbind(), gc.nogc()); + let promise = promise_capability + .promise + .unbind() + .bind(gc.into_nogc()) + .into_value(); + return Err(JsError::new(promise)); + } + Ok(value) => value.unbind().bind(gc.nogc()), + }; + let promise_resolve = promise_resolve.scope(agent, gc.nogc()); + + // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). + let iterator_record = get_iterator(agent, iterable.get(agent), false, gc.reborrow()) + .unbind() + .bind(gc.nogc()); + + // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). + let promise_capability = PromiseCapability { + promise: promise.get(agent).bind(gc.nogc()), + must_be_unresolved: true, + }; + + let iterator_record = match iterator_record.unbind().bind(gc.nogc()) { + Err(err) => { + promise_capability.reject(agent, err.value().unbind(), gc.nogc()); + let promise = promise_capability + .promise + .unbind() + .bind(gc.into_nogc()) + .into_value(); + return Err(JsError::new(promise)); + } + Ok(value) => value.unbind().bind(gc.nogc()), + }; + let iterator_record = iterator_record.bind(gc.nogc()); + + let Some(iterator_record) = iterator_record.into_iterator_record() else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Iterator is not iterable", + gc.into_nogc(), + )); + }; + + Ok(PromiseGroupSetup { + iterator_record: iterator_record.unbind(), + constructor: constructor.get(agent), + promise_capability: promise_capability.unbind(), + promise_resolve: promise_resolve.get(agent), + }) +} + +fn handle_promise_group_result<'gc>( + agent: &mut Agent, + result: JsResult<'gc, Promise<'gc>>, + iterator_done: bool, + iterator: Object, + promise: Promise<'gc>, + mut gc: GcScope<'gc, '_>, +) -> JsResult<'gc, Value<'gc>> { + let result = result.bind(gc.nogc()); + let iterator = iterator.bind(gc.nogc()); + let promise = promise.bind(gc.nogc()).scope(agent, gc.nogc()); + + // 8. If result is an abrupt completion, then + let result = match result { + Err(mut result) => { + // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). + if !iterator_done { + result = iterator_close_with_error( + agent, + iterator.unbind(), + result.unbind(), + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()); + } + + // b. IfAbruptRejectPromise(result, promiseCapability). + let promise_capability = PromiseCapability { + promise: promise.get(agent), + must_be_unresolved: true, + }; + // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). + promise_capability.reject(agent, result.value().unbind(), gc.nogc()); + // b. Return capability.[[Promise]]. + promise_capability.promise() + } + Ok(result) => result, + }; + // 9. Return ! result. + Ok(result.into_value().unbind()) +} + /// ### [27.2.4.1.2 PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiseall) /// ### [27.2.4.2.1 PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiseallsettled) /// ### [27.2.4.3.1 PerformPromiseAny ( iteratorRecord, constructor, resultCapability, promiseResolve )](https://tc39.es/ecma262/#sec-performpromiseany) @@ -937,19 +1053,16 @@ fn perform_promise_group<'gc>( fn perform_promise_race<'gc>( agent: &mut Agent, iterator: Scoped, - next_method: Option, - constructor: Scoped, + next_method: Function, + constructor: Function, result_capability: PromiseCapability, - promise_resolve: Scoped, + promise_resolve: Function, iterator_done: &mut bool, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Promise<'gc>> { let result_capability = result_capability.bind(gc.nogc()); - - let Some(next_method) = next_method else { - return Err(throw_not_callable(agent, gc.into_nogc())); - }; - + let constructor = constructor.scope(agent, gc.nogc()); + let promise_resolve = promise_resolve.scope(agent, gc.nogc()); let next_method = next_method.scope(agent, gc.nogc()); let promise = result_capability.promise.scope(agent, gc.nogc()); From 9d070b996a84c86ec21d52ead0ec39ece910821d Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Mon, 17 Nov 2025 09:16:50 -0300 Subject: [PATCH 06/10] Use helpers in promise group --- .../promise_objects/promise_constructor.rs | 286 +++++++++--------- 1 file changed, 151 insertions(+), 135 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index dbca562e0..4252ecba6 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -6,10 +6,9 @@ use crate::{ ecmascript::{ abstract_operations::{ operations_on_iterator_objects::{ - IteratorRecord, MaybeInvalidIteratorRecord, get_iterator, - iterator_close_with_error, iterator_step_value, + IteratorRecord, get_iterator, iterator_close_with_error, iterator_step_value, }, - operations_on_objects::{call, call_function, get, throw_not_callable}, + operations_on_objects::{call, call_function, get}, testing_and_comparison::{is_callable, is_constructor}, }, builders::builtin_function_builder::BuiltinFunctionBuilder, @@ -23,7 +22,6 @@ use crate::{ }, promise_objects::{ promise_abstract_operations::{ - promise_capability_records::if_abrupt_reject_promise_m, promise_group_record::{PromiseGroupRecord, PromiseGroupType}, promise_reaction_records::PromiseReactionHandler, }, @@ -261,9 +259,56 @@ impl PromiseConstructor { agent: &mut Agent, this_value: Value, arguments: ArgumentsList, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - promise_group(agent, this_value, arguments, PromiseGroupType::All, gc) + let this_value = this_value.bind(gc.nogc()); + let arguments = arguments.bind(gc.nogc()); + + let PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + } = promise_group_setup( + agent, + this_value.unbind(), + arguments.unbind(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); + let promise = promise_capability.promise.scope(agent, gc.nogc()); + + // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). + let mut iterator_done = false; + let result = perform_promise_group( + agent, + iterator.clone(), + iterator_record.next_method.unbind(), + constructor.unbind(), + promise_capability.unbind(), + promise_resolve.unbind(), + &mut iterator_done, + PromiseGroupType::All, + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()); + + let result_value = handle_promise_group_result( + agent, + result.unbind(), + iterator_done, + iterator.get(agent), + promise.get(agent), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + Ok(result_value.unbind()) } /// ### [27.2.4.2 Promise.allSettled ( iterable )](https://tc39.es/ecma262/#sec-promise.allsettled) @@ -275,15 +320,56 @@ impl PromiseConstructor { agent: &mut Agent, this_value: Value, arguments: ArgumentsList, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - promise_group( + let this_value = this_value.bind(gc.nogc()); + let arguments = arguments.bind(gc.nogc()); + + let PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + } = promise_group_setup( + agent, + this_value.unbind(), + arguments.unbind(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); + let promise = promise_capability.promise.scope(agent, gc.nogc()); + + // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). + let mut iterator_done = false; + let result = perform_promise_group( agent, - this_value, - arguments, + iterator.clone(), + iterator_record.next_method.unbind(), + constructor.unbind(), + promise_capability.unbind(), + promise_resolve.unbind(), + &mut iterator_done, PromiseGroupType::AllSettled, - gc, + gc.reborrow(), ) + .unbind() + .bind(gc.nogc()); + + let result_value = handle_promise_group_result( + agent, + result.unbind(), + iterator_done, + iterator.get(agent), + promise.get(agent), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + Ok(result_value.unbind()) } /// ### [27.2.4.3 Promise.any ( iterable )](https://tc39.es/ecma262/#sec-promise.any) @@ -295,9 +381,56 @@ impl PromiseConstructor { agent: &mut Agent, this_value: Value, arguments: ArgumentsList, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - promise_group(agent, this_value, arguments, PromiseGroupType::Any, gc) + let this_value = this_value.bind(gc.nogc()); + let arguments = arguments.bind(gc.nogc()); + + let PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + } = promise_group_setup( + agent, + this_value.unbind(), + arguments.unbind(), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); + let promise = promise_capability.promise.scope(agent, gc.nogc()); + + // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). + let mut iterator_done = false; + let result = perform_promise_group( + agent, + iterator.clone(), + iterator_record.next_method.unbind(), + constructor.unbind(), + promise_capability.unbind(), + promise_resolve.unbind(), + &mut iterator_done, + PromiseGroupType::Any, + gc.reborrow(), + ) + .unbind() + .bind(gc.nogc()); + + let result_value = handle_promise_group_result( + agent, + result.unbind(), + iterator_done, + iterator.get(agent), + promise.get(agent), + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + + Ok(result_value.unbind()) } /// ### [27.2.4.5 Promise.race ( iterable )](https://tc39.es/ecma262/#sec-promise.race) @@ -635,121 +768,6 @@ fn get_promise_resolve<'gc>( Ok(promise_resolve.unbind()) } -fn promise_group<'gc>( - agent: &mut Agent, - this_value: Value, - arguments: ArgumentsList, - promise_group_type: PromiseGroupType, - mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Value<'gc>> { - let this_value = this_value.bind(gc.nogc()); - let arguments = arguments.bind(gc.nogc()); - let iterable = arguments.get(0).scope(agent, gc.nogc()); - - // 1. Let C be the this value. - if this_value - != agent - .current_realm_record() - .intrinsics() - .promise() - .into_value() - { - return Err(throw_promise_subclassing_not_supported( - agent, - gc.into_nogc(), - )); - } - - // 2. Let promiseCapability be ? NewPromiseCapability(C). - let Some(constructor) = is_constructor(agent, this_value) else { - return Err(agent.throw_exception_with_static_message( - ExceptionType::TypeError, - "Expected the this value to be a constructor.", - gc.into_nogc(), - )); - }; - let constructor = constructor.scope(agent, gc.nogc()); - let promise_capability = PromiseCapability::new(agent, gc.nogc()); - let promise = promise_capability.promise().scope(agent, gc.nogc()); - - // 3. Let promiseResolve be Completion(GetPromiseResolve(C)). - let promise_resolve = get_promise_resolve(agent, constructor.get(agent), gc.reborrow()) - .unbind() - .bind(gc.nogc()); - - // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). - let promise_capability = PromiseCapability { - promise: promise.get(agent).bind(gc.nogc()), - must_be_unresolved: true, - }; - let promise_resolve = - if_abrupt_reject_promise_m!(agent, promise_resolve, promise_capability, gc); - let promise_resolve = promise_resolve.scope(agent, gc.nogc()); - - // 5. Let iteratorRecord be Completion(GetIterator(iterable, sync)). - let iterator_record = get_iterator(agent, iterable.get(agent), false, gc.reborrow()) - .unbind() - .bind(gc.nogc()); - - // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). - let promise_capability = PromiseCapability { - promise: promise.get(agent).bind(gc.nogc()), - must_be_unresolved: true, - }; - let MaybeInvalidIteratorRecord { - iterator, - next_method, - } = if_abrupt_reject_promise_m!(agent, iterator_record, promise_capability, gc); - - let iterator = iterator.scope(agent, gc.nogc()); - - // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). - let mut iterator_done = false; - let result = perform_promise_group( - agent, - iterator.clone(), - next_method.unbind(), - constructor, - promise_capability.unbind(), - promise_resolve, - &mut iterator_done, - promise_group_type, - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()); - - // 8. If result is an abrupt completion, then - let result = match result { - Err(mut result) => { - // a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)). - if !iterator_done { - result = iterator_close_with_error( - agent, - iterator.get(agent), - result.unbind(), - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()); - } - - // b. IfAbruptRejectPromise(result, promiseCapability). - let promise_capability = PromiseCapability { - promise: promise.get(agent).bind(gc.nogc()), - must_be_unresolved: true, - }; - // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). - promise_capability.reject(agent, result.value().unbind(), gc.nogc()); - // b. Return capability.[[Promise]]. - promise_capability.promise() - } - Ok(result) => result, - }; - // 9. Return ! result. - Ok(result.into_value().unbind()) -} - fn promise_group_setup<'gc>( agent: &mut Agent, this_value: Value, @@ -904,19 +922,17 @@ fn handle_promise_group_result<'gc>( fn perform_promise_group<'gc>( agent: &mut Agent, iterator: Scoped, - next_method: Option, - constructor: Scoped, + next_method: Function, + constructor: Function, result_capability: PromiseCapability, - promise_resolve: Scoped, + promise_resolve: Function, iterator_done: &mut bool, promise_group_type: PromiseGroupType, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Promise<'gc>> { let result_capability = result_capability.bind(gc.nogc()); - - let Some(next_method) = next_method else { - return Err(throw_not_callable(agent, gc.into_nogc())); - }; + let constructor = constructor.scope(agent, gc.nogc()); + let promise_resolve = promise_resolve.scope(agent, gc.nogc()); let next_method = next_method.scope(agent, gc.nogc()); From 26e6b8b45b4bd8165a70012f141ebfdbb905e8c4 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Mon, 17 Nov 2025 09:26:32 -0300 Subject: [PATCH 07/10] Remove unnecessary mark and sweep --- .../promise_objects/promise_constructor.rs | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index 4252ecba6..3594e080f 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -42,10 +42,7 @@ use crate::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, rootable::Scopable, }, - heap::{ - CompactionLists, CreateHeapData, HeapMarkAndSweep, IntrinsicConstructorIndexes, - ObjectEntry, WellKnownSymbolIndexes, WorkQueues, - }, + heap::{CreateHeapData, IntrinsicConstructorIndexes, ObjectEntry, WellKnownSymbolIndexes}, }; use super::promise_abstract_operations::{ @@ -130,22 +127,6 @@ struct PromiseGroupSetup<'a> { } bindable_handle!(PromiseGroupSetup); -impl HeapMarkAndSweep for PromiseGroupSetup<'static> { - fn mark_values(&self, queues: &mut WorkQueues) { - self.iterator_record.mark_values(queues); - self.constructor.mark_values(queues); - self.promise_capability.mark_values(queues); - self.promise_resolve.mark_values(queues); - } - - fn sweep_values(&mut self, compactions: &CompactionLists) { - self.iterator_record.sweep_values(compactions); - self.constructor.sweep_values(compactions); - self.promise_capability.sweep_values(compactions); - self.promise_resolve.sweep_values(compactions); - } -} - impl PromiseConstructor { /// ### [27.2.3.1 Promise ( executor )](https://tc39.es/ecma262/#sec-promise-executor) fn constructor<'gc>( From 9e63a7f657894635ecd5569a06cca3e490e84740 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Tue, 18 Nov 2025 22:16:15 -0300 Subject: [PATCH 08/10] Handle abrupt reject correctly --- .../promise_objects/promise_constructor.rs | 114 +++++++++++------- 1 file changed, 73 insertions(+), 41 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index 3594e080f..dbe1c65db 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -127,6 +127,12 @@ struct PromiseGroupSetup<'a> { } bindable_handle!(PromiseGroupSetup); +enum PromiseGroupSetupResult<'a> { + Success(PromiseGroupSetup<'a>), + AbruptReject(Promise<'a>), +} +bindable_handle!(PromiseGroupSetupResult); + impl PromiseConstructor { /// ### [27.2.3.1 Promise ( executor )](https://tc39.es/ecma262/#sec-promise-executor) fn constructor<'gc>( @@ -245,12 +251,7 @@ impl PromiseConstructor { let this_value = this_value.bind(gc.nogc()); let arguments = arguments.bind(gc.nogc()); - let PromiseGroupSetup { - iterator_record, - constructor, - promise_capability, - promise_resolve, - } = promise_group_setup( + let setup_result = promise_group_setup( agent, this_value.unbind(), arguments.unbind(), @@ -259,6 +260,20 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); + if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { + return Ok(promise.unbind().into_value()); + } + + let PromiseGroupSetupResult::Success(PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + }) = setup_result + else { + unreachable!() + }; + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); let promise = promise_capability.promise.scope(agent, gc.nogc()); @@ -306,12 +321,7 @@ impl PromiseConstructor { let this_value = this_value.bind(gc.nogc()); let arguments = arguments.bind(gc.nogc()); - let PromiseGroupSetup { - iterator_record, - constructor, - promise_capability, - promise_resolve, - } = promise_group_setup( + let setup_result = promise_group_setup( agent, this_value.unbind(), arguments.unbind(), @@ -320,6 +330,20 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); + if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { + return Ok(promise.unbind().into_value()); + } + + let PromiseGroupSetupResult::Success(PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + }) = setup_result + else { + unreachable!() + }; + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); let promise = promise_capability.promise.scope(agent, gc.nogc()); @@ -367,12 +391,7 @@ impl PromiseConstructor { let this_value = this_value.bind(gc.nogc()); let arguments = arguments.bind(gc.nogc()); - let PromiseGroupSetup { - iterator_record, - constructor, - promise_capability, - promise_resolve, - } = promise_group_setup( + let setup_result = promise_group_setup( agent, this_value.unbind(), arguments.unbind(), @@ -381,6 +400,20 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); + if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { + return Ok(promise.unbind().into_value()); + } + + let PromiseGroupSetupResult::Success(PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + }) = setup_result + else { + unreachable!() + }; + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); let promise = promise_capability.promise.scope(agent, gc.nogc()); @@ -433,12 +466,7 @@ impl PromiseConstructor { let this_value = this_value.bind(gc.nogc()); let arguments = arguments.bind(gc.nogc()); - let PromiseGroupSetup { - iterator_record, - constructor, - promise_capability, - promise_resolve, - } = promise_group_setup( + let setup_result = promise_group_setup( agent, this_value.unbind(), arguments.unbind(), @@ -447,6 +475,20 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); + if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { + return Ok(promise.unbind().into_value()); + } + + let PromiseGroupSetupResult::Success(PromiseGroupSetup { + iterator_record, + constructor, + promise_capability, + promise_resolve, + }) = setup_result + else { + unreachable!() + }; + let iterator = iterator_record.iterator.scope(agent, gc.nogc()); let promise = promise_capability.promise.scope(agent, gc.nogc()); @@ -754,7 +796,7 @@ fn promise_group_setup<'gc>( this_value: Value, arguments: ArgumentsList, mut gc: GcScope<'gc, '_>, -) -> JsResult<'gc, PromiseGroupSetup<'gc>> { +) -> JsResult<'gc, PromiseGroupSetupResult<'gc>> { let this_value = this_value.bind(gc.nogc()); let arguments = arguments.bind(gc.nogc()); let iterable = arguments.get(0).scope(agent, gc.nogc()); @@ -796,17 +838,11 @@ fn promise_group_setup<'gc>( must_be_unresolved: true, }; - // let promise_resolve = - // if_abrupt_reject_promise_m!(agent, promise_resolve, promise_capability, gc); let promise_resolve = match promise_resolve.unbind().bind(gc.nogc()) { Err(err) => { promise_capability.reject(agent, err.value().unbind(), gc.nogc()); - let promise = promise_capability - .promise - .unbind() - .bind(gc.into_nogc()) - .into_value(); - return Err(JsError::new(promise)); + let promise = promise_capability.promise.unbind().bind(gc.into_nogc()); + return Ok(PromiseGroupSetupResult::AbruptReject(promise)); } Ok(value) => value.unbind().bind(gc.nogc()), }; @@ -826,12 +862,8 @@ fn promise_group_setup<'gc>( let iterator_record = match iterator_record.unbind().bind(gc.nogc()) { Err(err) => { promise_capability.reject(agent, err.value().unbind(), gc.nogc()); - let promise = promise_capability - .promise - .unbind() - .bind(gc.into_nogc()) - .into_value(); - return Err(JsError::new(promise)); + let promise = promise_capability.promise.unbind().bind(gc.into_nogc()); + return Ok(PromiseGroupSetupResult::AbruptReject(promise)); } Ok(value) => value.unbind().bind(gc.nogc()), }; @@ -845,12 +877,12 @@ fn promise_group_setup<'gc>( )); }; - Ok(PromiseGroupSetup { + Ok(PromiseGroupSetupResult::Success(PromiseGroupSetup { iterator_record: iterator_record.unbind(), constructor: constructor.get(agent), promise_capability: promise_capability.unbind(), promise_resolve: promise_resolve.get(agent), - }) + })) } fn handle_promise_group_result<'gc>( From c46c8615fbec927099fc0c88f2678673d98e032b Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Tue, 18 Nov 2025 22:21:29 -0300 Subject: [PATCH 09/10] Updated expectations --- tests/expectations.json | 2 +- tests/metrics.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index b34701e27..a8a4bcfb6 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -7982,4 +7982,4 @@ "staging/sm/syntax/yield-as-identifier.js": "FAIL", "staging/source-phase-imports/import-source-source-text-module.js": "FAIL", "staging/top-level-await/tla-hang-entry.js": "FAIL" -} \ No newline at end of file +} diff --git a/tests/metrics.json b/tests/metrics.json index 328736e1a..14b4271e7 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,11 +1,11 @@ { "results": { "crash": 107, - "fail": 7889, - "pass": 39362, + "fail": 7821, + "pass": 39425, "skip": 3325, - "timeout": 15, - "unresolved": 35 + "timeout": 18, + "unresolved": 37 }, "total": 50733 -} +} \ No newline at end of file From bdd6a50f32cdcf701796d184282fabba8de52a10 Mon Sep 17 00:00:00 2001 From: Felipe Armoni Date: Wed, 19 Nov 2025 07:25:50 -0300 Subject: [PATCH 10/10] Replace if / else with match --- .../promise_objects/promise_constructor.rs | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index dbe1c65db..a9a6847fb 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -260,18 +260,16 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); - if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { - return Ok(promise.unbind().into_value()); - } - - let PromiseGroupSetupResult::Success(PromiseGroupSetup { + let PromiseGroupSetup { iterator_record, constructor, promise_capability, promise_resolve, - }) = setup_result - else { - unreachable!() + } = match setup_result { + PromiseGroupSetupResult::Success(res) => res, + PromiseGroupSetupResult::AbruptReject(promise) => { + return Ok(promise.unbind().into_value()); + } }; let iterator = iterator_record.iterator.scope(agent, gc.nogc()); @@ -330,18 +328,16 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); - if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { - return Ok(promise.unbind().into_value()); - } - - let PromiseGroupSetupResult::Success(PromiseGroupSetup { + let PromiseGroupSetup { iterator_record, constructor, promise_capability, promise_resolve, - }) = setup_result - else { - unreachable!() + } = match setup_result { + PromiseGroupSetupResult::Success(res) => res, + PromiseGroupSetupResult::AbruptReject(promise) => { + return Ok(promise.unbind().into_value()); + } }; let iterator = iterator_record.iterator.scope(agent, gc.nogc()); @@ -400,18 +396,16 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); - if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { - return Ok(promise.unbind().into_value()); - } - - let PromiseGroupSetupResult::Success(PromiseGroupSetup { + let PromiseGroupSetup { iterator_record, constructor, promise_capability, promise_resolve, - }) = setup_result - else { - unreachable!() + } = match setup_result { + PromiseGroupSetupResult::Success(res) => res, + PromiseGroupSetupResult::AbruptReject(promise) => { + return Ok(promise.unbind().into_value()); + } }; let iterator = iterator_record.iterator.scope(agent, gc.nogc()); @@ -475,18 +469,16 @@ impl PromiseConstructor { .unbind()? .bind(gc.nogc()); - if let PromiseGroupSetupResult::AbruptReject(promise) = setup_result { - return Ok(promise.unbind().into_value()); - } - - let PromiseGroupSetupResult::Success(PromiseGroupSetup { + let PromiseGroupSetup { iterator_record, constructor, promise_capability, promise_resolve, - }) = setup_result - else { - unreachable!() + } = match setup_result { + PromiseGroupSetupResult::Success(res) => res, + PromiseGroupSetupResult::AbruptReject(promise) => { + return Ok(promise.unbind().into_value()); + } }; let iterator = iterator_record.iterator.scope(agent, gc.nogc());