From 8e661dc15c4cb372206d072963b446023ef526be Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Fri, 27 Mar 2026 23:52:33 -0600 Subject: [PATCH 1/2] Update modules code to the latest spec --- core/engine/src/bytecompiler/module.rs | 22 +- core/engine/src/module/source.rs | 465 ++++++++++++++----------- test262_config.toml | 2 +- 3 files changed, 278 insertions(+), 211 deletions(-) diff --git a/core/engine/src/bytecompiler/module.rs b/core/engine/src/bytecompiler/module.rs index a358e728aa3..a14f3f5dfc2 100644 --- a/core/engine/src/bytecompiler/module.rs +++ b/core/engine/src/bytecompiler/module.rs @@ -42,7 +42,27 @@ impl ByteCompiler<'_> { ExportDeclaration::VarStatement(var) => self.compile_var_decl(var), ExportDeclaration::Declaration(decl) => self.compile_decl(decl, false), ExportDeclaration::DefaultClassDeclaration(cl) => { - self.compile_class(cl.as_ref().into(), None); + // 2. Let className be the sole element of BoundNames of ClassDeclaration. + // 3. If className is "*default*", then + if cl.name().sym() == Sym::DEFAULT_EXPORT { + let class = self.register_allocator.alloc(); + + // 1. Let value be ? BindingClassDeclarationEvaluation of ClassDeclaration. + self.compile_class(cl.as_ref().into(), Some(&class)); + + // a. Let env be the running execution context's LexicalEnvironment. + // b. Perform ? InitializeBoundName("*default*", value, env). + let default_export = self + .interner() + .resolve_expect(Sym::DEFAULT_EXPORT) + .into_common(false); + self.emit_binding(BindingOpcode::InitLexical, default_export, &class); + + self.register_allocator.dealloc(class); + } else { + // 1. Let value be ? BindingClassDeclarationEvaluation of ClassDeclaration. + self.compile_class(cl.as_ref().into(), None); + } } ExportDeclaration::DefaultAssignmentExpression(expr) => { let function = self.register_allocator.alloc(); diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index c8827ee284b..f19bdf175ad 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -47,15 +47,6 @@ use super::{ SourceText, }; -/// Information for the [**Depth-first search**] algorithm used in the -/// [`Module::link`] and [`Module::evaluate`] methods. -#[derive(Clone, Copy, Debug, Trace, Finalize)] -#[boa_gc(empty_trace)] -pub(super) struct DfsInfo { - dfs_index: usize, - dfs_ancestor_index: usize, -} - /// Current status of a [`SourceTextModule`]. /// /// Roughly corresponds to the `[[Status]]` field of [**Cyclic Module Records**][cyclic], @@ -74,32 +65,32 @@ enum ModuleStatus { #[unsafe_ignore_trace] source: boa_ast::Module, source_text: SourceText, - info: DfsInfo, + ancestor_index: usize, }, PreLinked { environment: Gc, context: SourceTextContext, - info: DfsInfo, + ancestor_index: usize, }, Linked { environment: Gc, context: SourceTextContext, - info: DfsInfo, + ancestor_index: usize, }, Evaluating { environment: Gc, context: SourceTextContext, top_level_capability: Option, cycle_root: Module, - info: DfsInfo, - async_eval_index: Option, + ancestor_index: usize, + async_evaluation_order: Option, }, EvaluatingAsync { environment: Gc, context: SourceTextContext, top_level_capability: Option, cycle_root: Module, - async_eval_index: usize, + async_evaluation_order: usize, pending_async_dependencies: usize, }, Evaluated { @@ -126,27 +117,40 @@ impl ModuleStatus { )); } - /// Gets the current index info of the module within the dependency graph, or `None` if the + /// Gets the ancestor index of the current module within the dependency graph, or `None` if the /// module is not in a state executing the dfs algorithm. - const fn dfs_info(&self) -> Option<&DfsInfo> { - match self { + const fn ancestor_index(&self) -> Option { + match *self { Self::Unlinked { .. } | Self::EvaluatingAsync { .. } | Self::Evaluated { .. } => None, - Self::Linking { info, .. } - | Self::PreLinked { info, .. } - | Self::Linked { info, .. } - | Self::Evaluating { info, .. } => Some(info), + Self::Linking { ancestor_index, .. } + | Self::PreLinked { ancestor_index, .. } + | Self::Linked { ancestor_index, .. } + | Self::Evaluating { ancestor_index, .. } => Some(ancestor_index), } } - /// Gets a mutable reference to the current index info of the module within the dependency graph, - /// or `None` if the module is not in a state executing the dfs algorithm. - fn dfs_info_mut(&mut self) -> Option<&mut DfsInfo> { + /// Gets a mutable reference to the current ancestor index of the module + /// within the dependency graph, or `None` if the module is not in a state + /// executing the dfs algorithm. + fn ancestor_index_mut(&mut self) -> Option<&mut usize> { match self { Self::Unlinked { .. } | Self::EvaluatingAsync { .. } | Self::Evaluated { .. } => None, - Self::Linking { info, .. } - | Self::PreLinked { info, .. } - | Self::Linked { info, .. } - | Self::Evaluating { info, .. } => Some(info), + Self::Linking { + ancestor_index: info, + .. + } + | Self::PreLinked { + ancestor_index: info, + .. + } + | Self::Linked { + ancestor_index: info, + .. + } + | Self::Evaluating { + ancestor_index: info, + .. + } => Some(info), } } @@ -364,37 +368,66 @@ impl SourceTextModule { // 10. For each ExportEntry Record ee of exportEntries, do for ee in source.items().export_entries() { + // a. If ee.[[ModuleRequest]] is null, then match ee { - // a. If ee.[[ModuleRequest]] is null, then + // ii. Else, ExportEntry::Ordinary(entry) => { - // ii. Else, - // 1. Let ie be the element of importEntries whose [[LocalName]] is ee.[[LocalName]]. - if let Some((module, import, attrs)) = - import_entries.iter().find_map(|ie| match ie.import_name() { - ImportName::Name(name) if ie.local_name() == entry.local_name() => { - Some((ie.module_request(), name, ie.attributes())) - } - _ => None, - }) + if let Some(ie) = import_entries + .iter() + .find(|ie| ie.local_name() == entry.local_name()) { - // 3. Else, - // a. NOTE: This is a re-export of a single name. - // b. Append the ExportEntry Record { [[ModuleRequest]]: ie.[[ModuleRequest]], - // [[ImportName]]: ie.[[ImportName]], [[LocalName]]: null, - // [[ExportName]]: ee.[[ExportName]] } to indirectExportEntries. - indirect_export_entries.push(IndirectExportEntry::new( - module, - ReExportImportName::Name(import), - entry.export_name(), - attrs.to_vec().into_boxed_slice(), - )); + // 1. NOTE: When exporting a binding or namespace object + // which was originally imported from another module, + // the ExportEntry Record is rewritten to match the form + // it would have if the binding or namespace object had + // been re-exported directly from the original module + // rather than imported then exported. This allows + // conflicts which arise from exporting the same binding + // or namespace twice under the same name through export + // * from to be ignored rather than being treated as + // ambiguous in step 9.e.iii of the ResolveExport + // concrete method of Source Text Module Records. + // + // 2. Let ie be the element of importEntries whose + // [[LocalName]] is ee.[[LocalName]]. + // + match ie.import_name() { + // 3. If ie.[[ImportName]] is namespace-object, then + ImportName::Namespace => { + // a. NOTE: This is a re-export of an imported module namespace object. + // b. Append the ExportEntry Record { + // [[ModuleRequest]]: ie.[[ModuleRequest]], + // [[ImportName]]: all, + // [[LocalName]]: null, + // [[ExportName]]: ee.[[ExportName]] + // } to indirectExportEntries. + indirect_export_entries.push(IndirectExportEntry::new( + ie.module_request(), + ReExportImportName::Star, + entry.export_name(), + ie.attributes().to_vec().into_boxed_slice(), + )); + } + // 4. Else, + ImportName::Name(import) => { + // a. NOTE: This is a re-export of a single name. + // b. Append the ExportEntry Record { + // [[ModuleRequest]]: ie.[[ModuleRequest]], + // [[ImportName]]: ie.[[ImportName]], + // [[LocalName]]: null, + // [[ExportName]]: ee.[[ExportName]] + // } to indirectExportEntries. + indirect_export_entries.push(IndirectExportEntry::new( + ie.module_request(), + ReExportImportName::Name(import), + entry.export_name(), + ie.attributes().to_vec().into_boxed_slice(), + )); + } + } } else { // i. If importedBoundNames does not contain ee.[[LocalName]], then // 1. Append ee to localExportEntries. - - // 2. If ie.[[ImportName]] is namespace-object, then - // a. NOTE: This is a re-export of an imported module namespace object. - // b. Append ee to localExportEntries. local_export_entries.push(entry); } } @@ -867,7 +900,7 @@ impl SourceTextModule { } // 4. Set module.[[Status]] to linking. - // 5. Set module.[[DFSIndex]] to index. + // 5. Let moduleIndex be index. // 6. Set module.[[DFSAncestorIndex]] to index. self.status.borrow_mut().transition(|status| match status { ModuleStatus::Unlinked { @@ -876,22 +909,20 @@ impl SourceTextModule { } => ModuleStatus::Linking { source, source_text, - info: DfsInfo { - dfs_index: index, - dfs_ancestor_index: index, - }, + ancestor_index: index, }, _ => unreachable!("3. Assert: module.[[Status]] is unlinked."), }); + let module_index = index; + // 7. Set index to index + 1. index += 1; // 8. Append module to stack. stack.push(module_self.clone()); - // 9. For each String required of module.[[RequestedModules]], do - + // 9. For each ModuleRequest Record request of module.[[RequestedModules]], do for required in &self.code.requested_modules { // a. Let requiredModule be GetImportedModule(module, required). let required_module = self.loaded_modules.borrow()[required].clone(); @@ -912,31 +943,23 @@ impl SourceTextModule { }); // iii. If requiredModule.[[Status]] is linking, then - let required_index = if let ModuleStatus::Linking { - info: - DfsInfo { - dfs_ancestor_index, .. - }, - .. - } = &*required_module_src.status.borrow() + let required_index = if let ModuleStatus::Linking { ancestor_index, .. } = + &*required_module_src.status.borrow() { - // 1. Set module.[[DFSAncestorIndex]] to - // min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). - - Some(*dfs_ancestor_index) + Some(*ancestor_index) } else { None }; if let Some(required_index) = required_index { + // 1. Set module.[[DFSAncestorIndex]] to + // min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). let mut status = self.status.borrow_mut(); - let DfsInfo { - dfs_ancestor_index, .. - } = status - .dfs_info_mut() + let ancestor_index = status + .ancestor_index_mut() .js_expect("should be on the linking state")?; - *dfs_ancestor_index = usize::min(*dfs_ancestor_index, required_index); + *ancestor_index = usize::min(*ancestor_index, required_index); } } } @@ -949,27 +972,21 @@ impl SourceTextModule { stack.iter().filter(|module| *module == module_self).count(), 1 ); - // 12. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. + // 12. Assert: module.[[DFSAncestorIndex]] ≤ moduleIndex. debug_assert!({ - let DfsInfo { - dfs_ancestor_index, - dfs_index, - } = self - .status + self.status .borrow() - .dfs_info() - .copied() - .expect("should be linking"); - dfs_ancestor_index <= dfs_index + .ancestor_index() + .expect("should be linking") + <= module_index }); - let info = self.status.borrow().dfs_info().copied(); - match info { - // 13. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then - + let ancestor_index = self.status.borrow().ancestor_index(); + match ancestor_index { + // 13. If module.[[DFSAncestorIndex]] = moduleIndex, then // a. Let done be false. // b. Repeat, while done is false, - Some(info) if info.dfs_ancestor_index == info.dfs_index => loop { + Some(ancestor_index) if ancestor_index == module_index => loop { // i. Let requiredModule be the last element of stack. // ii. Remove the last element of stack. let last = stack.pop().js_expect("should have at least one element")?; @@ -983,11 +1000,11 @@ impl SourceTextModule { .borrow_mut() .transition(|current| match current { ModuleStatus::PreLinked { - info, + ancestor_index: info, context, environment, } => ModuleStatus::Linked { - info, + ancestor_index: info, context, environment, }, @@ -1165,14 +1182,20 @@ impl SourceTextModule { capability: Option, context: &mut Context, ) -> JsResult { - /// Gets the next evaluation index of an async module. + // TODO: this count should be part of the `context` instead, and we should + // reset it after the last module has executed. + /// [`IncrementModuleAsyncEvaluationCount ( )`][spec] /// - /// Returns an error if there's no more available indices. - fn get_async_eval_index() -> JsResult { + /// [spec]: https://tc39.es/ecma262/#sec-IncrementModuleAsyncEvaluationCount + fn increment_module_async_evaluation_count() -> JsResult { thread_local! { static ASYNC_EVAL_QUEUE_INDEX: Cell = const { Cell::new(0) }; } + // 1. Let AR be the Agent Record of the surrounding agent. + // 2. Let count be AR.[[ModuleAsyncEvaluationCount]]. + // 3. Set AR.[[ModuleAsyncEvaluationCount]] to count + 1. + // 4. Return count. ASYNC_EVAL_QUEUE_INDEX .with(|idx| { let next = idx.get().checked_add(1)?; @@ -1204,7 +1227,7 @@ impl SourceTextModule { } // 5. Set module.[[Status]] to evaluating. - // 6. Set module.[[DFSIndex]] to index. + // 6. Set moduleIndex to index. // 7. Set module.[[DFSAncestorIndex]] to index. // 8. Set module.[[PendingAsyncDependencies]] to 0. self.status.borrow_mut().transition(|status| match status { @@ -1217,15 +1240,14 @@ impl SourceTextModule { context, top_level_capability: capability, cycle_root: module_self.clone(), - info: DfsInfo { - dfs_index: index, - dfs_ancestor_index: index, - }, - async_eval_index: None, + ancestor_index: index, + async_evaluation_order: None, }, _ => unreachable!("already asserted that this state is `Linked`. "), }); + let module_index = index; + // 9. Set index to index + 1. index += 1; @@ -1233,7 +1255,7 @@ impl SourceTextModule { // 10. Append module to stack. stack.push(module_self.clone()); - // 11. For each String required of module.[[RequestedModules]], do + // 11. 11. For each ModuleRequest Record request of module.[[RequestedModules]], do for required in &self.code.requested_modules { // a. Let requiredModule be GetImportedModule(module, required). let required_module = self.loaded_modules.borrow()[required].clone(); @@ -1250,21 +1272,21 @@ impl SourceTextModule { _ => false, }); - let (required_module, async_eval, req_info) = match &*required_module_src + let (required_module, async_eval, req_ancestor) = match &*required_module_src .status .borrow() { // iii. If requiredModule.[[Status]] is evaluating, then ModuleStatus::Evaluating { - info, - async_eval_index, + ancestor_index, + async_evaluation_order, .. } => { - // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). + // Continues after the borrow to avoid a borrow panic ( required_module.clone(), - async_eval_index.is_some(), - Some(*info), + async_evaluation_order.is_some(), + Some(*ancestor_index), ) } // iv. Else, @@ -1299,16 +1321,17 @@ impl SourceTextModule { unreachable!("required_module must be a source text module"); }; - if let Some(req_info) = req_info { + if let Some(req_ancestor) = req_ancestor { let mut status = self.status.borrow_mut(); - let info = status - .dfs_info_mut() + let ancestor_index = status + .ancestor_index_mut() .js_expect("self should still be in the evaluating state")?; - info.dfs_ancestor_index = - usize::min(info.dfs_ancestor_index, req_info.dfs_ancestor_index); + + // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). + *ancestor_index = usize::min(*ancestor_index, req_ancestor); } - // v. If requiredModule.[[AsyncEvaluation]] is true, then + // v. If requiredModule.[[AsyncEvaluationOrder]] is an integer, then if async_eval { // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. pending_async_dependencies += 1; @@ -1323,18 +1346,20 @@ impl SourceTextModule { // 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then if pending_async_dependencies > 0 || self.code.has_tla { - // a. Assert: module.[[AsyncEvaluation]] is false and was never previously set to true. { let ModuleStatus::Evaluating { - async_eval_index, .. + async_evaluation_order, + .. } = &mut *self.status.borrow_mut() else { unreachable!("self should still be in the evaluating state") }; - // b. Set module.[[AsyncEvaluation]] to true. - // c. NOTE: The order in which module records have their [[AsyncEvaluation]] fields transition to true is significant. (See 16.2.1.5.3.4.) - *async_eval_index = Some(get_async_eval_index()?); + // a. Assert: module.[[AsyncEvaluationOrder]] is unset. + debug_assert!(async_evaluation_order.is_none()); + + // b. Set module.[[AsyncEvaluationOrder]] to IncrementModuleAsyncEvaluationCount(). + *async_evaluation_order = Some(increment_module_async_evaluation_count()?); } // d. If module.[[PendingAsyncDependencies]] = 0, perform ExecuteAsyncModule(module). @@ -1347,17 +1372,17 @@ impl SourceTextModule { self.execute(module_self, None, context)?; } - let dfs_info = self.status.borrow().dfs_info().copied().js_expect( - "haven't transitioned from the `Evaluating` state, so it should have its dfs info", + let ancestor_index = self.status.borrow().ancestor_index().js_expect( + "haven't transitioned from the `Evaluating` state, so it should have its ancestor index", )?; // 14. Assert: module occurs exactly once in stack. debug_assert_eq!(stack.iter().filter(|m| *m == module_self).count(), 1); - // 15. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. - assert!(dfs_info.dfs_ancestor_index <= dfs_info.dfs_index); + // 15. Assert: module.[[DFSAncestorIndex]] ≤ moduleIndex. + assert!(ancestor_index <= module_index); - // 16. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then - if dfs_info.dfs_ancestor_index == dfs_info.dfs_index { + // 16. If module.[[DFSAncestorIndex]] = moduleIndex, then + if ancestor_index == module_index { // a. Let done be false. // b. Repeat, while done is false, loop { @@ -1376,26 +1401,26 @@ impl SourceTextModule { environment, top_level_capability, cycle_root, - async_eval_index, + async_evaluation_order, context, .. - } => if let Some(async_eval_index) = async_eval_index { - // v. Otherwise, set requiredModule.[[Status]] to evaluating-async. + } => if let Some(async_evaluation_order) = async_evaluation_order { + // vi. Else, set requiredModule.[[Status]] to evaluating-async. ModuleStatus::EvaluatingAsync { environment, top_level_capability, - // vii. Set requiredModule.[[CycleRoot]] to module. + // viii. Set requiredModule.[[CycleRoot]] to module. cycle_root: if is_self { cycle_root } else { module_self.clone() }, - async_eval_index, + async_evaluation_order, pending_async_dependencies, context } } else { - // iv. If requiredModule.[[AsyncEvaluation]] is false, set requiredModule.[[Status]] to evaluated. + // v. If requiredModule.[[AsyncEvaluationOrder]] is unset, set requiredModule.[[Status]] to evaluated. ModuleStatus::Evaluated { environment, top_level_capability, @@ -1413,7 +1438,7 @@ impl SourceTextModule { } ); - // vi. If requiredModule and module are the same Module Record, set done to true. + // vii. If requiredModule and module are the same Module Record, set done to true. if is_self { break; } @@ -1472,7 +1497,7 @@ impl SourceTextModule { |_, args, module, context| { let error = JsError::from_opaque(args.get_or_undefined(0).clone()); // a. Perform AsyncModuleExecutionRejected(module, error). - async_module_execution_rejected(module, error, context)?; + async_module_execution_rejected(module, &error, context)?; // b. Return undefined. Ok(JsValue::undefined()) }, @@ -1908,9 +1933,9 @@ impl SourceTextModule { // 16. Set module.[[Context]] to moduleContext. self.status.borrow_mut().transition(|state| match state { - ModuleStatus::Linking { info, .. } => ModuleStatus::PreLinked { + ModuleStatus::Linking { ancestor_index, .. } => ModuleStatus::PreLinked { environment: env, - info, + ancestor_index, context: SourceTextContext { codeblock, environments: frame.environments.clone(), @@ -2023,15 +2048,15 @@ fn async_module_execution_fulfilled(module: &Module, context: &mut Context) -> J // 1. If module.[[Status]] is evaluated, then if let ModuleStatus::Evaluated { error, .. } = &*module_src.status.borrow() { // a. Assert: module.[[EvaluationError]] is not empty. - assert!(error.is_some()); + debug_assert!(error.is_some()); // b. Return unused. return Ok(()); } // 2. Assert: module.[[Status]] is evaluating-async. - // 3. Assert: module.[[AsyncEvaluation]] is true. + // 3. Assert: module.[[AsyncEvaluationOrder]] is an integer. // 4. Assert: module.[[EvaluationError]] is empty. - // 5. Set module.[[AsyncEvaluation]] to false. + // 5. Set module.[[AsyncEvaluationOrder]] to done. // 6. Set module.[[Status]] to evaluated. module_src .status @@ -2053,7 +2078,7 @@ fn async_module_execution_fulfilled(module: &Module, context: &mut Context) -> J // 7. If module.[[TopLevelCapability]] is not empty, then if let Some(cap) = module_src.status.borrow().top_level_capability() { - // a. Assert: module.[[CycleRoot]] is module. + // a. Assert: module.[[CycleRoot]] and module are the same Module Record. debug_assert_eq!(module_src.status.borrow().cycle_root(), Some(module)); // b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). @@ -2068,27 +2093,33 @@ fn async_module_execution_fulfilled(module: &Module, context: &mut Context) -> J // 9. Perform GatherAvailableAncestors(module, execList). module_src.gather_available_ancestors(&mut ancestors); - // 11. Assert: All elements of sortedExecList have their [[AsyncEvaluation]] field set to true, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to empty. - let mut ancestors = ancestors.into_iter().collect::>(); - - // 10. Let sortedExecList be a List whose elements are the elements of execList, in the order in which they had their [[AsyncEvaluation]] fields set to true in InnerModuleEvaluation. - ancestors.sort_by_cached_key(|m| { + // 10. 10. Assert: All elements of execList have their + // [[AsyncEvaluationOrder]] field set to an integer, + // [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] + // field set to empty. + // + // 11. Let sortedExecList be a List whose elements are the elements of + // execList, sorted by their [[AsyncEvaluationOrder]] field in ascending + // order. + let mut sorted_exec_list = ancestors.into_iter().collect::>(); + sorted_exec_list.sort_by_cached_key(|m| { let ModuleKind::SourceText(m_src) = m.kind() else { unreachable!("ancestors must only be source text modules"); }; let ModuleStatus::EvaluatingAsync { - async_eval_index, .. + async_evaluation_order, + .. } = &*m_src.status.borrow() else { unreachable!("GatherAvailableAncestors: i. Assert: m.[[Status]] is evaluating-async."); }; - *async_eval_index + *async_evaluation_order }); // 12. For each Cyclic Module Record m of sortedExecList, do - for m in ancestors { + for m in sorted_exec_list { let ModuleKind::SourceText(m_src) = m.kind() else { continue; }; @@ -2096,7 +2127,7 @@ fn async_module_execution_fulfilled(module: &Module, context: &mut Context) -> J // a. If m.[[Status]] is evaluated, then if let ModuleStatus::Evaluated { error, .. } = &*m_src.status.borrow() { // i. Assert: m.[[EvaluationError]] is not empty. - assert!(error.is_some()); + debug_assert!(error.is_some()); continue; } @@ -2105,45 +2136,52 @@ fn async_module_execution_fulfilled(module: &Module, context: &mut Context) -> J if has_tla { // i. Perform ExecuteAsyncModule(m). m_src.execute_async(&m, context); - } else { - // c. Else, - // i. Let result be m.ExecuteModule(). - let result = m_src.execute(&m, None, context); - - // ii. If result is an abrupt completion, then - if let Err(e) = result { - // 1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]). - async_module_execution_rejected(module, e, context)?; - } else { - // iii. Else, - // 1. Set m.[[Status]] to evaluated. - m_src.status.borrow_mut().transition(|status| match status { - ModuleStatus::EvaluatingAsync { - environment, - top_level_capability, - cycle_root, - .. - } => ModuleStatus::Evaluated { - environment, - top_level_capability, - cycle_root, - error: None, - }, - _ => unreachable!(), - }); + continue; + } + // c. Else, + // c.i. Let result be m.ExecuteModule(). + let result = m_src.execute(&m, None, context); + + // c.ii. If result is an abrupt completion, then + if let Err(e) = result { + // eagerly convert to an opaque error so that we can exit immediately + // if there was an EngineError. + let e = &JsError::from_opaque(e.into_opaque(context)?); + + // 1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]). + async_module_execution_rejected(module, e, context)?; + continue; + } - let status = m_src.status.borrow(); - // 2. If m.[[TopLevelCapability]] is not empty, then - if let Some(cap) = status.top_level_capability() { - // a. Assert: m.[[CycleRoot]] is m. - assert_eq!(status.cycle_root(), Some(&m)); + // iii. Else, + // iii.1. Set m.[[AsyncEvaluationOrder]] to done. + // iii.2. Set m.[[Status]] to evaluated. + m_src.status.borrow_mut().transition(|status| match status { + ModuleStatus::EvaluatingAsync { + environment, + top_level_capability, + cycle_root, + .. + } => ModuleStatus::Evaluated { + environment, + top_level_capability, + cycle_root, + error: None, + }, + _ => unreachable!(), + }); - // b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). - cap.resolve() - .call(&JsValue::undefined(), &[], context) - .js_expect("default `resolve` function cannot fail")?; - } - } + let status = m_src.status.borrow(); + + // 3. If m.[[TopLevelCapability]] is not empty, then + if let Some(cap) = status.top_level_capability() { + // a. Assert: m.[[CycleRoot]] and m are the same Module Record.. + debug_assert_eq!(status.cycle_root(), Some(&m)); + + // b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). + cap.resolve() + .call(&JsValue::undefined(), &[JsValue::undefined()], context) + .js_expect("default `resolve` function cannot fail")?; } } // 13. Return unused. @@ -2155,7 +2193,7 @@ fn async_module_execution_fulfilled(module: &Module, context: &mut Context) -> J /// [spec]: https://tc39.es/ecma262/#sec-async-module-execution-rejected fn async_module_execution_rejected( module: &Module, - error: JsError, + error: &JsError, context: &mut Context, ) -> JsResult<()> { let ModuleKind::SourceText(module_src) = module.kind() else { @@ -2164,16 +2202,17 @@ fn async_module_execution_rejected( // 1. If module.[[Status]] is evaluated, then if let ModuleStatus::Evaluated { error, .. } = &*module_src.status.borrow() { // a. Assert: module.[[EvaluationError]] is not empty. - assert!(error.is_some()); + debug_assert!(error.is_some()); // b. Return unused. return Ok(()); } // 2. Assert: module.[[Status]] is evaluating-async. - // 3. Assert: module.[[AsyncEvaluation]] is true. + // 3. Assert: module.[[AsyncEvaluationOrder]] is an integer.. // 4. Assert: module.[[EvaluationError]] is empty. // 5. Set module.[[EvaluationError]] to ThrowCompletion(error). // 6. Set module.[[Status]] to evaluated. + // 7. Set module.[[AsyncEvaluationOrder]] to done. module_src .status .borrow_mut() @@ -2192,27 +2231,35 @@ fn async_module_execution_rejected( _ => unreachable!(), }); - // 7. For each Cyclic Module Record m of module.[[AsyncParentModules]], do - for m in std::mem::take(&mut *module_src.async_parent_modules.borrow_mut()) { - // a. Perform AsyncModuleExecutionRejected(m, error). - async_module_execution_rejected(&m, error.clone(), context)?; + // 8. NOTE: module.[[AsyncEvaluationOrder]] is set to done for symmetry with + // AsyncModuleExecutionFulfilled. In InnerModuleEvaluation, the value of a + // module's [[AsyncEvaluationOrder]] internal slot is unused when its + // [[EvaluationError]] internal slot is not empty. + + { + let status = module_src.status.borrow(); + + // 9. If module.[[TopLevelCapability]] is not empty, then + if let Some(cap) = status.top_level_capability() { + // a. Assert: module.[[CycleRoot]] is module. + debug_assert_eq!(status.cycle_root(), Some(module)); + + // b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »). + cap.reject() + .call( + &JsValue::undefined(), + &[error.clone().into_opaque(context)?], + context, + ) + .js_expect("default `reject` function cannot fail")?; + } } - let status = module_src.status.borrow(); - // 8. If module.[[TopLevelCapability]] is not empty, then - if let Some(cap) = status.top_level_capability() { - // a. Assert: module.[[CycleRoot]] is module. - assert_eq!(status.cycle_root(), Some(module)); - - // b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »). - cap.reject() - .call( - &JsValue::undefined(), - &[error.into_opaque(context)?], - context, - ) - .js_expect("default `reject` function cannot fail")?; + // 10. For each Cyclic Module Record m of module.[[AsyncParentModules]], do + for m in std::mem::take(&mut *module_src.async_parent_modules.borrow_mut()) { + // a. Perform AsyncModuleExecutionRejected(m, error). + async_module_execution_rejected(&m, error, context)?; } - // 9. Return unused. + // 11. Return unused. Ok(()) } diff --git a/test262_config.toml b/test262_config.toml index 1bb2f29b959..01a3fecca09 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -1,4 +1,4 @@ -commit = "72ec758c9b6351b5baa299e0a162c2b7fe497cc6" +commit = "5c8206929d81b2d3d727ca6aac56c18358c8d790" [ignored] # Not implemented yet: From 03e9baac9d3ce473944241fc135943b4feba48f3 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sat, 28 Mar 2026 00:42:29 -0600 Subject: [PATCH 2/2] Cleanup features --- tests/tester/src/edition.rs | 88 +++++++++++++------------------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/tests/tester/src/edition.rs b/tests/tester/src/edition.rs index 06906dfd77e..957eae5a6fb 100644 --- a/tests/tester/src/edition.rs +++ b/tests/tester/src/edition.rs @@ -11,7 +11,7 @@ use crate::read::{MetaData, TestFlag}; /// Minimum edition required by a specific feature in the `test262` repository. static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { - // Proposed language features + // =============================== Proposals =============================== // Import bytes proposal // https://github.com/tc39/proposal-import-bytes @@ -25,10 +25,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://github.com/tc39/proposal-intl-locale-info "Intl.Locale-info" => SpecEdition::ESNext, - // FinalizationRegistry#cleanupSome - // https://github.com/tc39/proposal-cleanup-some - "FinalizationRegistry.prototype.cleanupSome" => SpecEdition::ESNext, - // Legacy RegExp features // https://github.com/tc39/proposal-regexp-legacy-features "legacy-regexp" => SpecEdition::ESNext, @@ -37,18 +33,14 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://tc39.es/proposal-defer-import-eval "import-defer" => SpecEdition::ESNext, - // Iterator sequencing - // https://github.com/tc39/proposal-iterator-sequencing - "iterator-sequencing" => SpecEdition::ESNext, + // Import Text + // https://github.com/tc39/proposal-import-text + "import-text" => SpecEdition::ESNext, // Time Zone Canonicalization // https://github.com/tc39/proposal-canonical-tz "canonical-tz" => SpecEdition::ESNext, - // Upsert - // https://github.com/tc39/proposal-upsert - "upsert" => SpecEdition::ESNext, - // Temporal // https://github.com/tc39/proposal-temporal "Temporal" => SpecEdition::ESNext, @@ -65,14 +57,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://github.com/tc39/proposal-array-from-async "Array.fromAsync" => SpecEdition::ESNext, - // JSON.parse with source - // https://github.com/tc39/proposal-json-parse-with-source - "json-parse-with-source" => SpecEdition::ESNext, - - // Regular expression modifiers - // https://github.com/tc39/proposal-regexp-modifiers - "regexp-modifiers" => SpecEdition::ESNext, - // Explicit Resource Management // https://github.com/tc39/proposal-explicit-resource-management "explicit-resource-management" => SpecEdition::ESNext, @@ -87,10 +71,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // test262 special specifier "source-phase-imports-module-source" => SpecEdition::ESNext, - // Uint8Array Base64 - // https://github.com/tc39/proposal-arraybuffer-base64 - "uint8array-base64" => SpecEdition::ESNext, - // Atomics.pause // https://github.com/tc39/proposal-atomics-microwait "Atomics.pause" => SpecEdition::ESNext, @@ -103,58 +83,42 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // https://github.com/tc39/proposal-nonextensible-applies-to-private "nonextensible-applies-to-private" => SpecEdition::ESNext, - // ===== Next ES version ===== - - // Error.isError - // https://github.com/tc39/proposal-is-error - "Error.isError" => SpecEdition::ESNext, - - // Import Attributes - // https://github.com/tc39/proposal-import-attributes/ - "import-attributes" => SpecEdition::ESNext, - // Joint iteration // https://github.com/tc39/proposal-joint-iteration "joint-iteration" => SpecEdition::ESNext, - // JSON modules - // https://github.com/tc39/proposal-json-modules - "json-modules" => SpecEdition::ESNext, + // ============================ Next ES version ============================ - // Intl.DurationFormat - // https://github.com/tc39/proposal-intl-duration-format - "Intl.DurationFormat" => SpecEdition::ESNext, + // Error.isError + // https://github.com/tc39/proposal-is-error + "Error.isError" => SpecEdition::ESNext, // Intl.Era-monthcode // https://github.com/tc39/proposal-intl-era-monthcode "Intl.Era-monthcode" => SpecEdition::ESNext, - // Duplicate named capturing groups - // https://github.com/tc39/proposal-duplicate-named-capturing-groups - "regexp-duplicate-named-groups" => SpecEdition::ESNext, + // Iterator sequencing + // https://github.com/tc39/proposal-iterator-sequencing + "iterator-sequencing" => SpecEdition::ESNext, - // RegExp.escape - // https://github.com/tc39/proposal-regex-escaping - "RegExp.escape" => SpecEdition::ESNext, + // Upsert + // https://github.com/tc39/proposal-upsert + "upsert" => SpecEdition::ESNext, - // Iterator Helpers - // https://github.com/tc39/proposal-iterator-helpers - "iterator-helpers" => SpecEdition::ESNext, + // JSON.parse with source + // https://github.com/tc39/proposal-json-parse-with-source + "json-parse-with-source" => SpecEdition::ESNext, - // Promise.try - // https://github.com/tc39/proposal-promise-try - "promise-try" => SpecEdition::ESNext, + // Uint8Array Base64 + // https://github.com/tc39/proposal-arraybuffer-base64 + "uint8array-base64" => SpecEdition::ESNext, - // Float16Array + Math.f16round - // https://github.com/tc39/proposal-float16array - "Float16Array" => SpecEdition::ESNext, + // ====================== Standard Language Features ======================= - // Standard language features "AggregateError" => SpecEdition::ES12, "Atomics.waitAsync" => SpecEdition::ES15, "align-detached-buffer-semantics-with-web-reality" => SpecEdition::ES12, "arbitrary-module-namespace-names" => SpecEdition::ES13, - // https://github.com/tc39/proposal-arraybuffer-transfer "arraybuffer-transfer" => SpecEdition::ES15, "array-grouping" => SpecEdition::ES15, "ArrayBuffer" => SpecEdition::ES6, @@ -204,12 +168,15 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "FinalizationRegistry" => SpecEdition::ES12, "for-in-order" => SpecEdition::ES11, "for-of" => SpecEdition::ES6, + "Float16Array" => SpecEdition::ES16, "Float32Array" => SpecEdition::ES6, "Float64Array" => SpecEdition::ES6, "generators" => SpecEdition::ES6, "globalThis" => SpecEdition::ES11, "hashbang" => SpecEdition::ES14, "import.meta" => SpecEdition::ES11, + "import-attributes" => SpecEdition::ES16, + "iterator-helpers" => SpecEdition::ES16, "Int8Array" => SpecEdition::ES6, "Int16Array" => SpecEdition::ES6, "Int32Array" => SpecEdition::ES6, @@ -222,12 +189,14 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "Intl.DateTimeFormat-fractionalSecondDigits" => SpecEdition::ES12, "Intl.DisplayNames" => SpecEdition::ES12, "Intl.DisplayNames-v2" => SpecEdition::ES13, + "Intl.DurationFormat" => SpecEdition::ES16, "Intl.ListFormat" => SpecEdition::ES12, "Intl.Locale" => SpecEdition::ES12, "Intl.NumberFormat-unified" => SpecEdition::ES11, "Intl.NumberFormat-v3" => SpecEdition::ES14, "Intl.RelativeTimeFormat" => SpecEdition::ES11, "Intl.Segmenter" => SpecEdition::ES13, + "json-modules" => SpecEdition::ES16, "json-superset" => SpecEdition::ES10, "let" => SpecEdition::ES6, "logical-assignment-operators" => SpecEdition::ES12, @@ -245,6 +214,7 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "Promise.allSettled" => SpecEdition::ES11, "Promise.any" => SpecEdition::ES12, "Promise.prototype.finally" => SpecEdition::ES9, + "promise-try" => SpecEdition::ES16, "Proxy" => SpecEdition::ES6, "promise-with-resolvers" => SpecEdition::ES15, "proxy-missing-checks" => SpecEdition::ES6, @@ -252,9 +222,12 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "Reflect.construct" => SpecEdition::ES6, "Reflect.set" => SpecEdition::ES6, "Reflect.setPrototypeOf" => SpecEdition::ES6, + "RegExp.escape" => SpecEdition::ES16, "regexp-dotall" => SpecEdition::ES9, + "regexp-duplicate-named-groups" => SpecEdition::ES16, "regexp-lookbehind" => SpecEdition::ES9, "regexp-match-indices" => SpecEdition::ES13, + "regexp-modifiers" => SpecEdition::ES16, "regexp-named-groups" => SpecEdition::ES9, "regexp-unicode-property-escapes" => SpecEdition::ES9, "regexp-v-flag" => SpecEdition::ES15, @@ -275,7 +248,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "String.prototype.toWellFormed" => SpecEdition::ES15, "String.prototype.trimEnd" => SpecEdition::ES10, "String.prototype.trimStart" => SpecEdition::ES10, - // https://github.com/tc39/proposal-set-methods "set-methods" => SpecEdition::ES16, "super" => SpecEdition::ES6, "Symbol" => SpecEdition::ES6,