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 0de90b7a9..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 @@ -1,15 +1,17 @@ use crate::{ ecmascript::{ + abstract_operations::operations_on_objects::define_property_or_throw, builtins::{ Array, + error::ErrorHeapData, promise::Promise, promise_objects::promise_abstract_operations::{ promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReactionType, }, }, - execution::Agent, - types::{BUILTIN_STRING_MEMORY, IntoValue, OrdinaryObject, Value}, + execution::{Agent, agent::ExceptionType}, + types::{BUILTIN_STRING_MEMORY, IntoValue, OrdinaryObject, PropertyDescriptor, Value}, }, engine::{ context::{Bindable, GcScope, NoGcScope, bindable_handle}, @@ -23,8 +25,9 @@ use crate::{ #[derive(Debug, Clone, Copy)] pub enum PromiseGroupType { - PromiseAll, - PromiseAllSettled, + All, + AllSettled, + Any, } #[derive(Debug, Clone, Copy)] @@ -64,30 +67,32 @@ impl<'a> PromiseGroup<'a> { let record = self.get(agent); match record.promise_group_type { - PromiseGroupType::PromiseAll => match reaction_type { + PromiseGroupType::All => match reaction_type { PromiseReactionType::Fulfill => { self.fulfill(agent, index, value.unbind(), gc.reborrow()); } PromiseReactionType::Reject => { - self.reject(agent, value.unbind(), gc.nogc()); + self.immediately_reject(agent, value.unbind(), gc.nogc()); } }, - PromiseGroupType::PromiseAllSettled => { + PromiseGroupType::AllSettled => { let obj = self .to_all_settled_obj(agent, reaction_type, value.unbind(), gc.nogc()) .bind(gc.nogc()); self.fulfill(agent, index, obj.unbind(), gc.reborrow()); } + PromiseGroupType::Any => match reaction_type { + PromiseReactionType::Fulfill => { + self.immediately_resolve(agent, value.unbind(), gc.reborrow()); + } + PromiseReactionType::Reject => { + self.reject(agent, index, value.unbind(), gc.reborrow()); + } + }, } } - pub(crate) fn fulfill( - self, - agent: &mut Agent, - index: u32, - value: Value<'a>, - mut gc: GcScope<'a, '_>, - ) { + fn fulfill(self, agent: &mut Agent, index: u32, value: Value<'a>, mut gc: GcScope<'a, '_>) { let promise_group = self.bind(gc.nogc()); let value = value.bind(gc.nogc()); @@ -98,17 +103,72 @@ impl<'a> PromiseGroup<'a> { elements[index as usize] = Some(value.unbind()); if let Some(promise_to_resolve) = promise_to_resolve { + promise_group.pop_empty_records(agent); + let capability = PromiseCapability::from_promise(promise_to_resolve, true); capability.resolve(agent, result_array.into_value().unbind(), gc.reborrow()); } } - pub(crate) fn reject(self, agent: &mut Agent, value: Value<'a>, gc: NoGcScope<'a, '_>) { + fn reject(self, agent: &mut Agent, index: u32, error: Value<'a>, mut gc: GcScope<'a, '_>) { + let promise_group = self.bind(gc.nogc()); + let error = error.bind(gc.nogc()); + + let promise_group_record = promise_group.get_mut(agent); + let (result_array, promise_to_resolve) = promise_group_record.take_result_and_promise(); + + let elements = result_array.as_mut_slice(agent); + elements[index as usize] = Some(error.unbind()); + + if let Some(promise_to_resolve) = promise_to_resolve { + promise_group.pop_empty_records(agent); + + let aggregate_error = agent.heap.create(ErrorHeapData::new( + ExceptionType::AggregateError, + None, + None, + )); + + let capability = PromiseCapability::from_promise(promise_to_resolve, true); + define_property_or_throw( + agent, + aggregate_error, + BUILTIN_STRING_MEMORY.errors.into(), + PropertyDescriptor { + value: Some(result_array.into_value().unbind()), + writable: Some(true), + get: None, + set: None, + enumerable: Some(true), + configurable: Some(true), + }, + gc.reborrow(), + ) + .unwrap(); + + capability.reject(agent, aggregate_error.into_value(), gc.nogc()); + } + } + + fn immediately_resolve(self, agent: &mut Agent, value: Value<'a>, gc: GcScope<'a, '_>) { + let value = value.bind(gc.nogc()); + let promise_group = self.bind(gc.nogc()); + let data = promise_group.get_mut(agent); + + let capability = PromiseCapability::from_promise(data.promise, true); + + promise_group.pop_empty_records(agent); + capability.resolve(agent, value.unbind(), gc); + } + + fn immediately_reject(self, agent: &mut Agent, value: Value<'a>, gc: NoGcScope<'a, '_>) { let value = value.bind(gc); let promise_group = self.bind(gc); let data = promise_group.get_mut(agent); let capability = PromiseCapability::from_promise(data.promise, true); + + promise_group.pop_empty_records(agent); capability.reject(agent, value.unbind(), gc); } @@ -121,47 +181,35 @@ impl<'a> PromiseGroup<'a> { ) -> Value<'a> { let value = value.bind(gc); - let obj = match reaction_type { - PromiseReactionType::Fulfill => OrdinaryObject::create_object( - agent, - Some( - agent - .current_realm_record() - .intrinsics() - .object_prototype() - .into(), - ), - &[ - ObjectEntry::new_data_entry( - BUILTIN_STRING_MEMORY.status.into(), - BUILTIN_STRING_MEMORY.fulfilled.into(), - ), - ObjectEntry::new_data_entry(BUILTIN_STRING_MEMORY.value.into(), value.unbind()), - ], - ) - .bind(gc), - PromiseReactionType::Reject => OrdinaryObject::create_object( - agent, - Some( - agent - .current_realm_record() - .intrinsics() - .object_prototype() - .into(), - ), - &[ - ObjectEntry::new_data_entry( - BUILTIN_STRING_MEMORY.status.into(), - BUILTIN_STRING_MEMORY.rejected.into(), - ), - ObjectEntry::new_data_entry( - BUILTIN_STRING_MEMORY.reason.into(), - value.unbind(), - ), - ], - ) - .bind(gc), - }; + let entries = vec![ + ObjectEntry::new_data_entry( + BUILTIN_STRING_MEMORY.status.into(), + match reaction_type { + PromiseReactionType::Fulfill => BUILTIN_STRING_MEMORY.fulfilled.into(), + PromiseReactionType::Reject => BUILTIN_STRING_MEMORY.rejected.into(), + }, + ), + ObjectEntry::new_data_entry( + match reaction_type { + PromiseReactionType::Fulfill => BUILTIN_STRING_MEMORY.value.into(), + PromiseReactionType::Reject => BUILTIN_STRING_MEMORY.reason.into(), + }, + value.unbind(), + ), + ]; + + let obj = OrdinaryObject::create_object( + agent, + Some( + agent + .current_realm_record() + .intrinsics() + .object_prototype() + .into(), + ), + &entries, + ) + .bind(gc); obj.into_value().unbind() } @@ -186,6 +234,16 @@ impl<'a> PromiseGroup<'a> { .expect("PromiseGroupRecord not found") } + fn pop_empty_records(self, agent: &mut Agent) { + while let Some(last) = agent.heap.promise_group_records.last() { + if last.remaining_elements_count == 0 { + agent.heap.promise_group_records.pop(); + } else { + break; + } + } + } + pub(crate) const _DEF: Self = { Self(BaseIndex::from_u32_index(0)) }; } 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 9c645757c..c4cbee21d 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 @@ -236,13 +236,7 @@ impl PromiseConstructor { arguments: ArgumentsList, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - promise_group( - agent, - this_value, - arguments, - PromiseGroupType::PromiseAll, - gc, - ) + promise_group(agent, this_value, arguments, PromiseGroupType::All, gc) } /// ### [27.2.4.2 Promise.allSettled ( iterable )](https://tc39.es/ecma262/#sec-promise.allsettled) @@ -260,7 +254,7 @@ impl PromiseConstructor { agent, this_value, arguments, - PromiseGroupType::PromiseAllSettled, + PromiseGroupType::AllSettled, gc, ) } @@ -272,11 +266,11 @@ impl PromiseConstructor { /// > constructor. fn any<'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.any", gc.into_nogc())) + promise_group(agent, this_value, arguments, PromiseGroupType::Any, gc) } /// ### [27.2.4.5 Promise.race ( iterable )](https://tc39.es/ecma262/#sec-promise.race) @@ -685,6 +679,7 @@ fn promise_group<'gc>( /// ### [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) #[allow(clippy::too_many_arguments)] fn perform_promise_group<'gc>( agent: &mut Agent, diff --git a/tests/expectations.json b/tests/expectations.json index 04bcf56c8..fd2b19261 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -1249,56 +1249,21 @@ "built-ins/Promise/any/ctx-ctor.js": "FAIL", "built-ins/Promise/any/ctx-non-ctor.js": "FAIL", "built-ins/Promise/any/ctx-non-object.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-error-close.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-error-reject.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-get-error-reject.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-get-error.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-get-once-multiple-calls.js": "FAIL", "built-ins/Promise/any/invoke-resolve-get-once-no-calls.js": "FAIL", "built-ins/Promise/any/invoke-resolve-on-promises-every-iteration-of-custom.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-on-promises-every-iteration-of-promise.js": "FAIL", "built-ins/Promise/any/invoke-resolve-on-values-every-iteration-of-custom.js": "FAIL", - "built-ins/Promise/any/invoke-resolve-on-values-every-iteration-of-promise.js": "FAIL", "built-ins/Promise/any/invoke-resolve-return.js": "FAIL", - "built-ins/Promise/any/invoke-resolve.js": "FAIL", - "built-ins/Promise/any/invoke-then-error-close.js": "FAIL", + "built-ins/Promise/any/invoke-then-error-close.js": "TIMEOUT", "built-ins/Promise/any/invoke-then-error-reject.js": "FAIL", - "built-ins/Promise/any/invoke-then-get-error-close.js": "FAIL", + "built-ins/Promise/any/invoke-then-get-error-close.js": "TIMEOUT", "built-ins/Promise/any/invoke-then-get-error-reject.js": "FAIL", "built-ins/Promise/any/invoke-then-on-promises-every-iteration.js": "FAIL", - "built-ins/Promise/any/invoke-then.js": "FAIL", + "built-ins/Promise/any/invoke-then.js": "UNRESOLVED", "built-ins/Promise/any/iter-arg-is-empty-iterable-reject.js": "FAIL", "built-ins/Promise/any/iter-arg-is-empty-string-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-error-object-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-false-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-null-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-number-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-poisoned.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-string-resolve.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-symbol-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-true-reject.js": "FAIL", - "built-ins/Promise/any/iter-arg-is-undefined-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-false-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-null-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-number-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-string-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-symbol-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-true-reject.js": "FAIL", - "built-ins/Promise/any/iter-assigned-undefined-reject.js": "FAIL", "built-ins/Promise/any/iter-next-val-err-no-close.js": "FAIL", - "built-ins/Promise/any/iter-next-val-err-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-false-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-null-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-number-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-string-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-symbol-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-true-reject.js": "FAIL", - "built-ins/Promise/any/iter-returns-undefined-reject.js": "FAIL", "built-ins/Promise/any/iter-step-err-no-close.js": "FAIL", - "built-ins/Promise/any/iter-step-err-reject.js": "FAIL", "built-ins/Promise/any/new-reject-function.js": "FAIL", - "built-ins/Promise/any/reject-all-mixed.js": "FAIL", - "built-ins/Promise/any/reject-deferred.js": "FAIL", "built-ins/Promise/any/reject-element-function-extensible.js": "FAIL", "built-ins/Promise/any/reject-element-function-length.js": "FAIL", "built-ins/Promise/any/reject-element-function-name.js": "FAIL", @@ -1306,25 +1271,11 @@ "built-ins/Promise/any/reject-element-function-property-order.js": "FAIL", "built-ins/Promise/any/reject-element-function-prototype.js": "FAIL", "built-ins/Promise/any/reject-from-same-thenable.js": "FAIL", - "built-ins/Promise/any/reject-ignored-immed.js": "FAIL", - "built-ins/Promise/any/reject-immed.js": "FAIL", "built-ins/Promise/any/resolve-before-loop-exit-from-same.js": "FAIL", "built-ins/Promise/any/resolve-before-loop-exit.js": "FAIL", - "built-ins/Promise/any/resolve-from-reject-catch.js": "FAIL", - "built-ins/Promise/any/resolve-from-resolve-reject-catch.js": "FAIL", "built-ins/Promise/any/resolve-from-same-thenable.js": "FAIL", - "built-ins/Promise/any/resolve-ignores-late-rejection-deferred.js": "FAIL", - "built-ins/Promise/any/resolve-ignores-late-rejection.js": "FAIL", - "built-ins/Promise/any/resolve-non-callable.js": "FAIL", - "built-ins/Promise/any/resolve-non-thenable.js": "FAIL", - "built-ins/Promise/any/resolve-not-callable-reject-with-typeerror.js": "FAIL", "built-ins/Promise/any/resolve-throws-iterator-return-is-not-callable.js": "FAIL", "built-ins/Promise/any/resolve-throws-iterator-return-null-or-undefined.js": "FAIL", - "built-ins/Promise/any/resolved-sequence-extra-ticks.js": "FAIL", - "built-ins/Promise/any/resolved-sequence-mixed.js": "FAIL", - "built-ins/Promise/any/resolved-sequence-with-rejections.js": "FAIL", - "built-ins/Promise/any/resolved-sequence.js": "FAIL", - "built-ins/Promise/any/returns-promise.js": "FAIL", "built-ins/Promise/any/species-get-error.js": "FAIL", "built-ins/Promise/exception-after-resolve-in-thenable-job.js": "FAIL", "built-ins/Promise/executor-function-extensible.js": "FAIL", @@ -8111,4 +8062,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 1e0b8bbe7..880202e22 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,11 +1,11 @@ { "results": { - "crash": 107, - "fail": 7958, - "pass": 39296, + "crash": 105, + "fail": 7907, + "pass": 39350, "skip": 3325, - "timeout": 13, - "unresolved": 34 + "timeout": 11, + "unresolved": 35 }, "total": 50733 } \ No newline at end of file