diff --git a/core/ast/src/operations/mod.rs b/core/ast/src/operations/mod.rs index b01bf35c67e..06210018948 100644 --- a/core/ast/src/operations/mod.rs +++ b/core/ast/src/operations/mod.rs @@ -2462,11 +2462,10 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> { ) -> ControlFlow { self.visit(node.body())?; - if let IterableLoopInitializer::Let(node) = node.initializer() { - let bound_names = bound_names(node); - self.0.retain(|name| !bound_names.contains(name)); - } - if let IterableLoopInitializer::Const(node) = node.initializer() { + if let IterableLoopInitializer::Let(node) + | IterableLoopInitializer::Const(node) + | IterableLoopInitializer::Using(node) = node.initializer() + { let bound_names = bound_names(node); self.0.retain(|name| !bound_names.contains(name)); } @@ -2480,11 +2479,10 @@ impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> { ) -> ControlFlow { self.visit(node.body())?; - if let IterableLoopInitializer::Let(node) = node.initializer() { - let bound_names = bound_names(node); - self.0.retain(|name| !bound_names.contains(name)); - } - if let IterableLoopInitializer::Const(node) = node.initializer() { + if let IterableLoopInitializer::Let(node) + | IterableLoopInitializer::Const(node) + | IterableLoopInitializer::Using(node) = node.initializer() + { let bound_names = bound_names(node); self.0.retain(|name| !bound_names.contains(name)); } diff --git a/core/ast/src/scope_analyzer.rs b/core/ast/src/scope_analyzer.rs index c586e78f341..a7976329f06 100644 --- a/core/ast/src/scope_analyzer.rs +++ b/core/ast/src/scope_analyzer.rs @@ -975,7 +975,12 @@ impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { Some(ForLoopInitializer::Lexical(decl)) => { let mut scope = Scope::new(self.scope.clone(), false); let names = bound_names(&decl.declaration); - if decl.declaration.is_const() { + if decl.declaration.is_const() + || matches!( + decl.declaration, + LexicalDeclaration::Using(_) | LexicalDeclaration::AwaitUsing(_) + ) + { for name in &names { let name = name.to_js_string(self.interner); scope.create_immutable_binding(name, true); @@ -1011,7 +1016,8 @@ impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { let initializer_bound_names = match node.initializer() { IterableLoopInitializer::Let(declaration) - | IterableLoopInitializer::Const(declaration) => bound_names(declaration), + | IterableLoopInitializer::Const(declaration) + | IterableLoopInitializer::Using(declaration) => bound_names(declaration), _ => Vec::new(), }; if initializer_bound_names.is_empty() { @@ -1078,7 +1084,8 @@ impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { let initializer_bound_names = match node.initializer() { IterableLoopInitializer::Let(declaration) - | IterableLoopInitializer::Const(declaration) => bound_names(declaration), + | IterableLoopInitializer::Const(declaration) + | IterableLoopInitializer::Using(declaration) => bound_names(declaration), _ => Vec::new(), }; if initializer_bound_names.is_empty() { @@ -1111,7 +1118,8 @@ impl<'ast> VisitorMut<'ast> for BindingCollectorVisitor<'_> { } Some(scope) } - IterableLoopInitializer::Const(declaration) => { + IterableLoopInitializer::Const(declaration) + | IterableLoopInitializer::Using(declaration) => { let scope = Scope::new(self.scope.clone(), false); match declaration { Binding::Identifier(ident) => { @@ -1794,7 +1802,11 @@ fn global_declaration_instantiation( drop(env.create_mutable_binding(name, false)); } } - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + Declaration::Lexical( + LexicalDeclaration::Const(declaration) + | LexicalDeclaration::Using(declaration) + | LexicalDeclaration::AwaitUsing(declaration), + ) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); env.create_immutable_binding(name, true); @@ -2174,7 +2186,11 @@ fn function_declaration_instantiation( drop(lex_env.create_mutable_binding(name, false)); } } - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + Declaration::Lexical( + LexicalDeclaration::Const(declaration) + | LexicalDeclaration::Using(declaration) + | LexicalDeclaration::AwaitUsing(declaration), + ) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); lex_env.create_immutable_binding(name, true); @@ -2492,7 +2508,11 @@ pub(crate) fn eval_declaration_instantiation_scope( drop(lex_env.create_mutable_binding(name, false)); } } - Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + Declaration::Lexical( + LexicalDeclaration::Const(declaration) + | LexicalDeclaration::Using(declaration) + | LexicalDeclaration::AwaitUsing(declaration), + ) => { for name in bound_names(declaration) { let name = name.to_js_string(interner); lex_env.create_immutable_binding(name, true); diff --git a/core/ast/src/statement/iteration/mod.rs b/core/ast/src/statement/iteration/mod.rs index 9bad14f4bb8..5dbcfdc8d01 100644 --- a/core/ast/src/statement/iteration/mod.rs +++ b/core/ast/src/statement/iteration/mod.rs @@ -48,6 +48,8 @@ pub enum IterableLoopInitializer { Let(Binding), /// A new const declaration. Const(Binding), + /// A new using declaration. + Using(Binding), /// A pattern with already declared variables. Pattern(Pattern), } @@ -61,6 +63,7 @@ impl ToInternedString for IterableLoopInitializer { Self::Var(binding) => (binding.to_interned_string(interner), "var"), Self::Let(binding) => (binding.to_interned_string(interner), "let"), Self::Const(binding) => (binding.to_interned_string(interner), "const"), + Self::Using(binding) => (binding.to_interned_string(interner), "using"), }; format!("{pre} {binding}") @@ -76,7 +79,7 @@ impl VisitWith for IterableLoopInitializer { Self::Identifier(id) => visitor.visit_identifier(id), Self::Access(pa) => visitor.visit_property_access(pa), Self::Var(b) => visitor.visit_variable(b), - Self::Let(b) | Self::Const(b) => visitor.visit_binding(b), + Self::Let(b) | Self::Const(b) | Self::Using(b) => visitor.visit_binding(b), Self::Pattern(p) => visitor.visit_pattern(p), } } @@ -89,7 +92,7 @@ impl VisitWith for IterableLoopInitializer { Self::Identifier(id) => visitor.visit_identifier_mut(id), Self::Access(pa) => visitor.visit_property_access_mut(pa), Self::Var(b) => visitor.visit_variable_mut(b), - Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b), + Self::Let(b) | Self::Const(b) | Self::Using(b) => visitor.visit_binding_mut(b), Self::Pattern(p) => visitor.visit_pattern_mut(p), } } diff --git a/core/engine/src/builtins/error/mod.rs b/core/engine/src/builtins/error/mod.rs index 65fd7415aa3..538cd561490 100644 --- a/core/engine/src/builtins/error/mod.rs +++ b/core/engine/src/builtins/error/mod.rs @@ -29,6 +29,7 @@ pub(crate) mod aggregate; pub(crate) mod eval; pub(crate) mod range; pub(crate) mod reference; +pub(crate) mod suppressed; pub(crate) mod syntax; pub(crate) mod r#type; pub(crate) mod uri; @@ -40,6 +41,7 @@ pub(crate) use self::aggregate::AggregateError; pub(crate) use self::eval::EvalError; pub(crate) use self::range::RangeError; pub(crate) use self::reference::ReferenceError; +pub(crate) use self::suppressed::SuppressedError; pub(crate) use self::syntax::SyntaxError; pub(crate) use self::r#type::TypeError; pub(crate) use self::uri::UriError; @@ -116,6 +118,14 @@ pub enum ErrorKind { /// /// [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-urierror Uri, + + /// The `SuppressedError` type. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-suppressederror + Suppressed, } /// A built-in `Error` object, per the [ECMAScript spec][spec]. diff --git a/core/engine/src/builtins/error/suppressed.rs b/core/engine/src/builtins/error/suppressed.rs new file mode 100644 index 00000000000..28938bf17bf --- /dev/null +++ b/core/engine/src/builtins/error/suppressed.rs @@ -0,0 +1,119 @@ +//! This module implements the global `SuppressedError` object. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-suppressederror-constructor +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SuppressedError + +use crate::{ + Context, JsArgs, JsResult, JsString, JsValue, + builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, + context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, + js_string, + object::{JsObject, internal_methods::get_prototype_from_constructor}, + property::Attribute, + realm::Realm, + string::StaticJsStrings, +}; + +use super::{Error, ErrorKind}; + +#[derive(Debug, Clone, Copy)] +pub(crate) struct SuppressedError; + +impl IntrinsicObject for SuppressedError { + fn init(realm: &Realm) { + let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE; + BuiltInBuilder::from_standard_constructor::(realm) + .prototype(realm.intrinsics().constructors().error().constructor()) + .inherits(Some(realm.intrinsics().constructors().error().prototype())) + .property(js_string!("name"), Self::NAME, attribute) + .property(js_string!("message"), js_string!(), attribute) + .build(); + } + + fn get(intrinsics: &Intrinsics) -> JsObject { + Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() + } +} + +impl BuiltInObject for SuppressedError { + const NAME: JsString = StaticJsStrings::SUPPRESSED_ERROR; +} + +impl BuiltInConstructor for SuppressedError { + const CONSTRUCTOR_ARGUMENTS: usize = 3; + const PROTOTYPE_STORAGE_SLOTS: usize = 2; + const CONSTRUCTOR_STORAGE_SLOTS: usize = 0; + + const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = + StandardConstructors::suppressed_error; + + /// `SuppressedError ( error, suppressed [ , message ] )` + /// + /// Creates a new `SuppressedError` object. + /// + /// [spec]: https://tc39.es/ecma262/#sec-suppressederror + fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. If NewTarget is undefined, let newTarget be the active function object; + // else let newTarget be NewTarget. + let new_target = &if new_target.is_undefined() { + context + .active_function_object() + .unwrap_or_else(|| { + context + .intrinsics() + .constructors() + .suppressed_error() + .constructor() + }) + .into() + } else { + new_target.clone() + }; + + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%SuppressedError.prototype%", + // « [[ErrorData]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardConstructors::suppressed_error, + context, + )?; + let o = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype, + Error::with_caller_position(ErrorKind::Suppressed, context), + ) + .upcast(); + + // 3. Perform ! CreateNonEnumerableDataPropertyOrThrow(O, "error", error). + let error = args.get_or_undefined(0); + o.create_non_enumerable_data_property_or_throw(js_string!("error"), error.clone(), context); + + // 4. Perform ! CreateNonEnumerableDataPropertyOrThrow(O, "suppressed", suppressed). + let suppressed = args.get_or_undefined(1); + o.create_non_enumerable_data_property_or_throw( + js_string!("suppressed"), + suppressed.clone(), + context, + ); + + // 5. If message is not undefined, then + let message = args.get_or_undefined(2); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw(js_string!("message"), msg, context); + } + + // 6. Return O. + Ok(o.into()) + } +} diff --git a/core/engine/src/builtins/iterable/mod.rs b/core/engine/src/builtins/iterable/mod.rs index bbeda81acba..e5312ed8e79 100644 --- a/core/engine/src/builtins/iterable/mod.rs +++ b/core/engine/src/builtins/iterable/mod.rs @@ -2,10 +2,14 @@ use crate::{ Context, JsResult, JsValue, - builtins::{BuiltInBuilder, IntrinsicObject}, + builtins::{ + BuiltInBuilder, IntrinsicObject, + promise::{Promise, PromiseCapability}, + }, context::intrinsics::Intrinsics, error::JsNativeError, js_string, + native_function::NativeFunction, object::JsObject, realm::Realm, symbol::JsSymbol, @@ -196,6 +200,7 @@ impl IntrinsicObject for Iterator { fn init(realm: &Realm) { BuiltInBuilder::with_intrinsic::(realm) .static_method(|v, _, _| Ok(v.clone()), JsSymbol::iterator(), 0) + .static_method(Self::dispose, JsSymbol::dispose(), 0) .build(); } @@ -204,6 +209,28 @@ impl IntrinsicObject for Iterator { } } +impl Iterator { + /// `%IteratorPrototype% [ @@dispose ] ()` + /// + /// [spec]: https://tc39.es/ecma262/#sec-%iteratorprototype%-@@dispose + fn dispose(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let O be the this value. + let o = this; + + // 2. Let return be ? GetMethod(O, "return"). + let ret = o.get_method(js_string!("return"), context)?; + + // 3. If return is not undefined, then + if let Some(ret) = ret { + // a. Perform ? Call(return, O, « »). + ret.call(o, &[], context)?; + } + + // 4. Return undefined. + Ok(JsValue::undefined()) + } +} + /// `%AsyncIteratorPrototype%` object /// /// More information: @@ -216,6 +243,7 @@ impl IntrinsicObject for AsyncIterator { fn init(realm: &Realm) { BuiltInBuilder::with_intrinsic::(realm) .static_method(|v, _, _| Ok(v.clone()), JsSymbol::async_iterator(), 0) + .static_method(Self::async_dispose, JsSymbol::async_dispose(), 0) .build(); } @@ -224,6 +252,95 @@ impl IntrinsicObject for AsyncIterator { } } +impl AsyncIterator { + /// `%AsyncIteratorPrototype% [ @@asyncDispose ] ()` + /// + /// [spec]: https://tc39.es/ecma262/#sec-%asynciteratorprototype%-@@asyncDispose + fn async_dispose(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + let o = this.clone(); + + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + let promise_capability = PromiseCapability::new( + &context.intrinsics().constructors().promise().constructor(), + context, + )?; + + // 3. Let return be GetMethod(O, "return"). + // 4. IfAbruptRejectPromise(return, promiseCapability). + let ret = match o.get_method(js_string!("return"), context) { + Ok(ret) => ret, + Err(err) => { + let e = err.into_opaque(context).unwrap_or(JsValue::undefined()); + promise_capability + .reject() + .call(&JsValue::undefined(), &[e], context)?; + return Ok(promise_capability.promise().clone().into()); + } + }; + + match ret { + // 5. If return is undefined, then + None => { + // a. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »). + promise_capability.resolve().call( + &JsValue::undefined(), + &[JsValue::undefined()], + context, + )?; + } + // 6. Else, + Some(ret) => { + // a. Let result be Call(return, O, « undefined »). + // b. IfAbruptRejectPromise(result, promiseCapability). + let result = match ret.call(&o, &[JsValue::undefined()], context) { + Ok(r) => r, + Err(err) => { + let e = err.into_opaque(context).unwrap_or(JsValue::undefined()); + promise_capability + .reject() + .call(&JsValue::undefined(), &[e], context)?; + return Ok(promise_capability.promise().clone().into()); + } + }; + + // c. Let resultWrapper be Completion(PromiseResolve(%Promise%, result)). + // d. IfAbruptRejectPromise(resultWrapper, promiseCapability). + let promise_ctor = context.intrinsics().constructors().promise().constructor(); + let result_wrapper = match Promise::promise_resolve(&promise_ctor, result, context) + { + Ok(r) => r + .downcast::() + .expect("%Promise% constructor must return a `Promise` object"), + Err(err) => { + let e = err.into_opaque(context).unwrap_or(JsValue::undefined()); + promise_capability + .reject() + .call(&JsValue::undefined(), &[e], context)?; + return Ok(promise_capability.promise().clone().into()); + } + }; + + // e. Let unwrap be a new Abstract Closure that returns undefined. + // f. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »). + let on_fulfilled = NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())) + .to_js_function(context.realm()); + + // g. Perform PerformPromiseThen(resultWrapper, onFulfilled, undefined, promiseCapability). + Promise::perform_promise_then( + &result_wrapper, + Some(on_fulfilled), + None, + Some(promise_capability.clone()), + context, + ); + } + } + + // 7. Return promiseCapability.[[Promise]]. + Ok(promise_capability.promise().clone().into()) + } +} + /// `CreateIterResultObject( value, done )` /// /// Generates an object supporting the `IteratorResult` interface. diff --git a/core/engine/src/builtins/mod.rs b/core/engine/src/builtins/mod.rs index 470e6ea399b..901c5db7dbc 100644 --- a/core/engine/src/builtins/mod.rs +++ b/core/engine/src/builtins/mod.rs @@ -63,7 +63,8 @@ pub(crate) use self::{ dataview::DataView, date::Date, error::{ - AggregateError, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, UriError, + AggregateError, EvalError, RangeError, ReferenceError, SuppressedError, SyntaxError, + TypeError, UriError, }, eval::Eval, function::BuiltInFunctionObject, @@ -301,6 +302,7 @@ impl Realm { EvalError::init(self); UriError::init(self); AggregateError::init(self); + SuppressedError::init(self); Reflect::init(self); Generator::init(self); GeneratorFunction::init(self); @@ -433,6 +435,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()> global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; + global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index 9316e28ae6b..1120c3aebbf 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -219,7 +219,25 @@ impl FunctionCompiler { { let mut compiler = compiler.position_guard(body); + + // Push a dispose capability marker for `using` declarations in function bodies. + compiler.bytecode.emit_create_dispose_capability(); + + // Install an exception handler so `DisposeResources` runs on abnormal completion. + let handler = compiler.push_handler(); + compiler.compile_statement_list(body.statement_list(), false, false); + + // Normal completion: dispose resources and jump past the handler. + compiler.bytecode.emit_dispose_resources(); + let skip = compiler.jump(); + + // Exception handler: dispose resources and re-throw. + compiler.patch_handler(handler); + compiler.bytecode.emit_dispose_resources(); + compiler.bytecode.emit_re_throw(); + + compiler.patch_jump(skip); } compiler.params = parameters.clone(); diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index db4607d1abd..980b5542d33 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -2243,10 +2243,10 @@ impl<'ctx> ByteCompiler<'ctx> { } } LexicalDeclaration::Using(decls) => { - // For each using declaration, we need to: + // For each using declaration: // 1. Evaluate the initializer - // 2. Add the resource to the disposal stack - // 3. Bind the variable + // 2. Bind the variable + // 3. Add the resource to the disposal stack for variable in decls.as_ref() { match variable.binding() { Binding::Identifier(ident) => { @@ -2259,31 +2259,18 @@ impl<'ctx> ByteCompiler<'ctx> { self.bytecode.emit_store_undefined(value.variable()); } - // TODO(@abhinavs1920): Add resource to disposal stack - // For now, we just bind the variable like a let declaration - // Full implementation will add: AddDisposableResource opcode - self.emit_binding(BindingOpcode::InitLexical, ident, &value); - self.register_allocator.dealloc(value); - } - Binding::Pattern(pattern) => { - let value = self.register_allocator.alloc(); - if let Some(init) = variable.init() { - self.compile_expr(init, &value); - } else { - self.bytecode.emit_store_undefined(value.variable()); - } - - // TODO: Same as above + // Emit AddDisposableResource to register for disposal. + self.bytecode.emit_add_disposable_resource(value.variable()); - self.compile_declaration_pattern( - pattern, - BindingOpcode::InitLexical, - &value, - ); self.register_allocator.dealloc(value); } + Binding::Pattern(_) => { + // The spec disallows destructuring in using declarations. + // The parser should have rejected this. + unreachable!("using declarations do not support destructuring"); + } } } } @@ -2300,30 +2287,17 @@ impl<'ctx> ByteCompiler<'ctx> { self.bytecode.emit_store_undefined(value.variable()); } - // TODO: Add resource to async disposal stack - // For now, we just bind the variable like a let declaration - // Full implementation will add: AddAsyncDisposableResource opcode - self.emit_binding(BindingOpcode::InitLexical, ident, &value); - self.register_allocator.dealloc(value); - } - Binding::Pattern(pattern) => { - let value = self.register_allocator.alloc(); - if let Some(init) = variable.init() { - self.compile_expr(init, &value); - } else { - self.bytecode.emit_store_undefined(value.variable()); - } + // TODO: Emit AddAsyncDisposableResource for await using. + // For now, treat same as sync using. + self.bytecode.emit_add_disposable_resource(value.variable()); - // TODO: SAME - self.compile_declaration_pattern( - pattern, - BindingOpcode::InitLexical, - &value, - ); self.register_allocator.dealloc(value); } + Binding::Pattern(_) => { + unreachable!("await using declarations do not support destructuring"); + } } } } diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index 99da2023627..c7257383653 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -5,8 +5,26 @@ impl ByteCompiler<'_> { /// Compile a [`Block`] `boa_ast` node pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { let scope = self.push_declarative_scope(block.scope()); + + // Push a dispose capability marker for `using` declarations. + self.bytecode.emit_create_dispose_capability(); + + // Install an exception handler so `DisposeResources` runs on abnormal completion. + let handler = self.push_handler(); + self.block_declaration_instantiation(block); self.compile_statement_list(block.statement_list(), use_expr, true); + + // Normal completion: dispose resources and jump past the handler. + self.bytecode.emit_dispose_resources(); + let skip = self.jump(); + + // Exception handler: dispose resources and re-throw. + self.patch_handler(handler); + self.bytecode.emit_dispose_resources(); + self.bytecode.emit_re_throw(); + + self.patch_jump(skip); self.pop_declarative_scope(scope); } } diff --git a/core/engine/src/bytecompiler/statement/loop.rs b/core/engine/src/bytecompiler/statement/loop.rs index 0c046ae3f37..5c2a3b01ed7 100644 --- a/core/engine/src/bytecompiler/statement/loop.rs +++ b/core/engine/src/bytecompiler/statement/loop.rs @@ -24,6 +24,7 @@ impl ByteCompiler<'_> { let mut let_binding_indices = None; let mut outer_scope_local = None; let mut outer_scope = None; + let mut has_using = false; if let Some(init) = for_loop.init() { match init { @@ -48,8 +49,13 @@ impl ByteCompiler<'_> { }; let names = bound_names(decl.declaration()); - if decl.declaration().is_const() { - } else { + if !decl.declaration().is_const() + && !matches!( + decl.declaration(), + boa_ast::declaration::LexicalDeclaration::Using(_) + | boa_ast::declaration::LexicalDeclaration::AwaitUsing(_) + ) + { let mut indices = Vec::with_capacity(names.len()); for name in &names { let name = name.to_js_string(self.interner()); @@ -62,6 +68,15 @@ impl ByteCompiler<'_> { } let_binding_indices = Some((indices, scope_index)); } + // Check if this is a `using` declaration. + has_using = matches!( + decl.declaration(), + boa_ast::declaration::LexicalDeclaration::Using(_) + | boa_ast::declaration::LexicalDeclaration::AwaitUsing(_) + ); + if has_using { + self.bytecode.emit_create_dispose_capability(); + } self.compile_lexical_decl(decl.declaration()); } } @@ -148,8 +163,14 @@ impl ByteCompiler<'_> { } if let Some(outer_scope_local) = outer_scope_local { + if has_using { + self.bytecode.emit_dispose_resources(); + } self.lexical_scope = outer_scope_local; } + if has_using && outer_scope.is_some() { + self.bytecode.emit_dispose_resources(); + } self.pop_declarative_scope(outer_scope); } @@ -197,7 +218,8 @@ impl ByteCompiler<'_> { // For let/const with a local identifier binding, emit iterator_value // directly into the binding's persistent register to avoid a Move. if let IterableLoopInitializer::Let(Binding::Identifier(ident)) - | IterableLoopInitializer::Const(Binding::Identifier(ident)) = for_in_loop.initializer() + | IterableLoopInitializer::Const(Binding::Identifier(ident)) + | IterableLoopInitializer::Using(Binding::Identifier(ident)) = for_in_loop.initializer() && let ident = ident.to_js_string(self.interner()) && let binding = self.lexical_scope.get_identifier_reference(ident) && binding.local() @@ -227,7 +249,8 @@ impl ByteCompiler<'_> { } }, IterableLoopInitializer::Let(declaration) - | IterableLoopInitializer::Const(declaration) => match declaration { + | IterableLoopInitializer::Const(declaration) + | IterableLoopInitializer::Using(declaration) => match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner()); self.emit_binding(BindingOpcode::InitLexical, ident, &value); @@ -313,7 +336,8 @@ impl ByteCompiler<'_> { // For let/const with a local identifier binding, emit iterator_value // directly into the binding's persistent register to avoid a Move. let handler_index = if let IterableLoopInitializer::Let(Binding::Identifier(ident)) - | IterableLoopInitializer::Const(Binding::Identifier(ident)) = + | IterableLoopInitializer::Const(Binding::Identifier(ident)) + | IterableLoopInitializer::Using(Binding::Identifier(ident)) = for_of_loop.initializer() && let ident = ident.to_js_string(self.interner()) && let ident = self.lexical_scope.get_identifier_reference(ident) @@ -368,7 +392,8 @@ impl ByteCompiler<'_> { } } IterableLoopInitializer::Let(declaration) - | IterableLoopInitializer::Const(declaration) => match declaration { + | IterableLoopInitializer::Const(declaration) + | IterableLoopInitializer::Using(declaration) => match declaration { Binding::Identifier(ident) => { let ident = ident.to_js_string(self.interner()); self.emit_binding(BindingOpcode::InitLexical, ident, &value); diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index b4dcc162e9d..3b00d84f7cf 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -146,6 +146,7 @@ pub struct StandardConstructors { eval_error: StandardConstructor, uri_error: StandardConstructor, aggregate_error: StandardConstructor, + suppressed_error: StandardConstructor, map: StandardConstructor, set: StandardConstructor, typed_array: StandardConstructor, @@ -241,6 +242,7 @@ impl Default for StandardConstructors { eval_error: StandardConstructor::default(), uri_error: StandardConstructor::default(), aggregate_error: StandardConstructor::default(), + suppressed_error: StandardConstructor::default(), map: StandardConstructor::default(), set: StandardConstructor::default(), typed_array: StandardConstructor::default(), @@ -567,6 +569,18 @@ impl StandardConstructors { &self.aggregate_error } + /// Returns the `SuppressedError` constructor. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-suppressed-error-constructor + #[inline] + #[must_use] + pub const fn suppressed_error(&self) -> &StandardConstructor { + &self.suppressed_error + } + /// Returns the `Map` constructor. /// /// More information: diff --git a/core/engine/src/error/mod.rs b/core/engine/src/error/mod.rs index 3b8cce86e4e..e3730f34f20 100644 --- a/core/engine/src/error/mod.rs +++ b/core/engine/src/error/mod.rs @@ -599,7 +599,7 @@ impl JsError { let position = error_data.position.clone(); let kind = match error_data.tag { - ErrorKind::Error => JsNativeErrorKind::Error, + ErrorKind::Error | ErrorKind::Suppressed => JsNativeErrorKind::Error, ErrorKind::Eval => JsNativeErrorKind::Eval, ErrorKind::Type => JsNativeErrorKind::Type, ErrorKind::Range => JsNativeErrorKind::Range, diff --git a/core/engine/src/vm/call_frame/mod.rs b/core/engine/src/vm/call_frame/mod.rs index cd560f18594..3f95bcd3326 100644 --- a/core/engine/src/vm/call_frame/mod.rs +++ b/core/engine/src/vm/call_frame/mod.rs @@ -9,7 +9,7 @@ use crate::{ bytecompiler::Register, environments::EnvironmentStack, realm::Realm, - vm::{CodeBlock, SourcePath}, + vm::{CodeBlock, SourcePath, opcode::DisposeEntry}, }; use boa_ast::Position; use boa_ast::scope::BindingLocator; @@ -77,6 +77,9 @@ pub struct CallFrame { /// \[\[Realm\]\] pub(crate) realm: Realm, + /// The stack of disposable resources for `using` declarations. + pub(crate) dispose_stack: ThinVec, + // SAFETY: Nothing in `CallFrameFlags` requires tracing, so this is safe. #[unsafe_ignore_trace] pub(crate) flags: CallFrameFlags, @@ -154,6 +157,7 @@ impl CallFrame { active_runnable, environments, realm, + dispose_stack: ThinVec::new(), flags: CallFrameFlags::empty(), } } diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index d9eec2fc949..19a30182975 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -760,7 +760,8 @@ impl CodeBlock { | Instruction::Neg { value } | Instruction::IsObject { value } | Instruction::BindThisValue { value } - | Instruction::BitNot { value } => { + | Instruction::BitNot { value } + | Instruction::AddDisposableResource { value } => { format!("value:{value}") } Instruction::ImportCall { @@ -874,7 +875,9 @@ impl CodeBlock { | Instruction::SuperCallSpread | Instruction::PopPrivateEnvironment | Instruction::Generator - | Instruction::AsyncGenerator => String::new(), + | Instruction::AsyncGenerator + | Instruction::CreateDisposeCapability + | Instruction::DisposeResources => String::new(), Instruction::Reserved1 | Instruction::Reserved2 | Instruction::Reserved3 @@ -901,9 +904,6 @@ impl CodeBlock { | Instruction::Reserved24 | Instruction::Reserved25 | Instruction::Reserved26 - | Instruction::Reserved27 - | Instruction::Reserved28 - | Instruction::Reserved29 | Instruction::Reserved30 | Instruction::Reserved31 | Instruction::Reserved32 diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index 2f96e6edcbd..cba1d4fd2ba 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -367,7 +367,10 @@ impl CodeBlock { | Instruction::CheckReturn | Instruction::BindThisValue { .. } | Instruction::CreateMappedArgumentsObject { .. } - | Instruction::CreateUnmappedArgumentsObject { .. } => { + | Instruction::CreateUnmappedArgumentsObject { .. } + | Instruction::CreateDisposeCapability + | Instruction::AddDisposableResource { .. } + | Instruction::DisposeResources => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } @@ -400,9 +403,6 @@ impl CodeBlock { | Instruction::Reserved24 | Instruction::Reserved25 | Instruction::Reserved26 - | Instruction::Reserved27 - | Instruction::Reserved28 - | Instruction::Reserved29 | Instruction::Reserved30 | Instruction::Reserved31 | Instruction::Reserved32 diff --git a/core/engine/src/vm/opcode/dispose/mod.rs b/core/engine/src/vm/opcode/dispose/mod.rs new file mode 100644 index 00000000000..01b12a17ef6 --- /dev/null +++ b/core/engine/src/vm/opcode/dispose/mod.rs @@ -0,0 +1,219 @@ +use super::RegisterOperand; +use crate::{ + Context, JsResult, JsValue, builtins::OrdinaryObject, error::JsNativeError, js_string, + symbol::JsSymbol, vm::opcode::Operation, +}; +use boa_gc::{Finalize, Trace}; + +/// A disposable resource entry in the disposal stack. +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) struct DisposableResource { + /// The resource value (or undefined for null/undefined resources). + pub(crate) value: JsValue, + /// The dispose method to call. + pub(crate) method: JsValue, +} + +/// Entry on the disposal stack — either a resource or a scope marker. +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) enum DisposeEntry { + /// A disposable resource. + Resource(DisposableResource), + /// A scope boundary marker. + Marker, +} + +/// `CreateDisposeCapability` +/// +/// Pushes a scope marker onto the disposal stack. +#[derive(Debug, Clone, Copy)] +pub(crate) struct CreateDisposeCapability; + +impl CreateDisposeCapability { + #[inline(always)] + pub(super) fn operation((): (), context: &mut Context) { + context + .vm + .frame_mut() + .dispose_stack + .push(DisposeEntry::Marker); + } +} + +impl Operation for CreateDisposeCapability { + const NAME: &'static str = "CreateDisposeCapability"; + const INSTRUCTION: &'static str = "INST - CreateDisposeCapability"; + const COST: u8 = 1; +} + +/// `AddDisposableResource` +/// +/// Takes a value from a register, gets its `Symbol.dispose` method, +/// and pushes the resource onto the disposal stack. +#[derive(Debug, Clone, Copy)] +pub(crate) struct AddDisposableResource; + +impl AddDisposableResource { + #[inline(always)] + pub(super) fn operation(value: RegisterOperand, context: &mut Context) -> JsResult<()> { + let value = context.vm.get_register(value.into()).clone(); + + // If value is null or undefined, add a no-op resource (spec step). + if value.is_null_or_undefined() { + context + .vm + .frame_mut() + .dispose_stack + .push(DisposeEntry::Resource(DisposableResource { + value: JsValue::undefined(), + method: JsValue::undefined(), + })); + return Ok(()); + } + + // Ensure value is an object. + if !value.is_object() { + return Err(JsNativeError::typ() + .with_message("using declaration requires an object or null/undefined") + .into()); + } + + // Get @@dispose method. + let method = value.get_method(JsSymbol::dispose(), context)?; + + match method { + Some(m) => { + let method_value: JsValue = m.into(); + context + .vm + .frame_mut() + .dispose_stack + .push(DisposeEntry::Resource(DisposableResource { + value, + method: method_value, + })); + Ok(()) + } + None => Err(JsNativeError::typ() + .with_message("value does not have a [Symbol.dispose] method") + .into()), + } + } +} + +impl Operation for AddDisposableResource { + const NAME: &'static str = "AddDisposableResource"; + const INSTRUCTION: &'static str = "INST - AddDisposableResource"; + const COST: u8 = 4; +} + +/// `DisposeResources` +/// +/// Pops and disposes resources back to the last scope marker. +/// Implements the `DisposeResources` abstract operation (sync). +#[derive(Debug, Clone, Copy)] +pub(crate) struct DisposeResources; + +impl DisposeResources { + #[inline(always)] + pub(super) fn operation((): (), context: &mut Context) -> JsResult<()> { + Self::dispose_sync(context) + } + + /// Core sync disposal logic. + pub(crate) fn dispose_sync(context: &mut Context) -> JsResult<()> { + // Collect resources back to the last marker. + let mut resources = Vec::new(); + loop { + let entry = context.vm.frame_mut().dispose_stack.pop(); + match entry.as_ref() { + Some(DisposeEntry::Resource(resource)) => { + resources.push(resource.clone()); + } + Some(DisposeEntry::Marker) | None => break, + } + } + + // Per spec: DisposeResources(disposeCapability, completion) + // The initial completion is the pending exception (if any) from the body. + let had_pending = context.vm.pending_exception.is_some(); + let mut completion: Result<(), crate::JsError> = + if let Some(pending) = context.vm.pending_exception.take() { + Err(pending) + } else { + Ok(()) + }; + + // Resources are already in reverse order (LIFO from the stack). + // Dispose each one, collecting errors. + for resource in &resources { + if resource.method.is_undefined() { + continue; + } + + let result = resource.method.as_callable().map_or_else( + || { + Err(JsNativeError::typ() + .with_message("dispose method is not callable") + .into()) + }, + |method| method.call(&resource.value, &[], context).map(|_| ()), + ); + + if let Err(err) = result { + completion = match completion { + Ok(()) => Err(err), + Err(prev_err) => { + // Create SuppressedError: new error suppresses previous one. + let suppressed_error = crate::object::JsObject::from_proto_and_data( + context + .intrinsics() + .constructors() + .suppressed_error() + .prototype(), + OrdinaryObject, + ); + let error_val = err.into_opaque(context).unwrap_or(JsValue::undefined()); + let suppressed_val = prev_err + .into_opaque(context) + .unwrap_or(JsValue::undefined()); + suppressed_error.create_non_enumerable_data_property_or_throw( + js_string!("error"), + error_val, + context, + ); + suppressed_error.create_non_enumerable_data_property_or_throw( + js_string!("suppressed"), + suppressed_val, + context, + ); + suppressed_error.create_non_enumerable_data_property_or_throw( + js_string!("message"), + js_string!(""), + context, + ); + Err(crate::JsError::from_opaque(suppressed_error.into())) + } + }; + } + } + + // If we were in the exception handler path (had pending exception), + // set the combined error back as pending exception for ReThrow. + // Otherwise (normal path), return errors via `?` propagation. + if had_pending { + if let Err(err) = completion { + context.vm.pending_exception = Some(err); + } + Ok(()) + } else { + completion + } + } +} + +impl Operation for DisposeResources { + const NAME: &'static str = "DisposeResources"; + const INSTRUCTION: &'static str = "INST - DisposeResources"; + const COST: u8 = 8; +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index e304514a8fc..a3304a055f0 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -20,6 +20,7 @@ mod control_flow; mod copy; mod define; mod delete; +mod dispose; mod environment; mod function; mod generator; @@ -59,6 +60,8 @@ pub(crate) use define::*; #[doc(inline)] pub(crate) use delete::*; #[doc(inline)] +pub(crate) use dispose::*; +#[doc(inline)] pub(crate) use environment::*; #[doc(inline)] pub(crate) use function::*; @@ -2191,12 +2194,22 @@ generate_opcodes! { Reserved25 => Reserved, /// Reserved [`Opcode`]. Reserved26 => Reserved, - /// Reserved [`Opcode`]. - Reserved27 => Reserved, - /// Reserved [`Opcode`]. - Reserved28 => Reserved, - /// Reserved [`Opcode`]. - Reserved29 => Reserved, + /// Create a dispose capability (scope marker) on the disposal stack. + CreateDisposeCapability, + + /// Add a disposable resource to the disposal stack. + /// + /// Gets `Symbol.dispose` from the value and pushes it. + /// + /// - Registers: + /// - Input: value + AddDisposableResource { value: RegisterOperand }, + + /// Dispose resources back to the last scope marker. + /// + /// Calls dispose methods in reverse order, wrapping errors in SuppressedError. + DisposeResources, + /// Reserved [`Opcode`]. Reserved30 => Reserved, /// Reserved [`Opcode`]. diff --git a/core/parser/src/parser/expression/primary/mod.rs b/core/parser/src/parser/expression/primary/mod.rs index 6b532da224c..63cd1fd1bee 100644 --- a/core/parser/src/parser/expression/primary/mod.rs +++ b/core/parser/src/parser/expression/primary/mod.rs @@ -190,7 +190,7 @@ where } TokenKind::IdentifierName(_) | TokenKind::Keyword(( - Keyword::Let | Keyword::Yield | Keyword::Await | Keyword::Of, + Keyword::Let | Keyword::Yield | Keyword::Await | Keyword::Of | Keyword::Using, _, )) => IdentifierReference::new(self.allow_yield, self.allow_await) .parse(cursor, interner) diff --git a/core/parser/src/parser/mod.rs b/core/parser/src/parser/mod.rs index d3e06d38804..884036b9f25 100644 --- a/core/parser/src/parser/mod.rs +++ b/core/parser/src/parser/mod.rs @@ -391,6 +391,25 @@ where ) .parse(cursor, interner)?; + // It is a Syntax Error if the goal symbol is Script and UsingDeclaration + // is not contained within a Block, ForStatement, FunctionBody, etc. + for statement in body.statements() { + if let boa_ast::StatementListItem::Declaration(decl) = statement + && matches!( + decl.as_ref(), + boa_ast::Declaration::Lexical( + boa_ast::declaration::LexicalDeclaration::Using(_) + | boa_ast::declaration::LexicalDeclaration::AwaitUsing(_), + ), + ) + { + return Err(Error::general( + "`using` declarations are not allowed at the top level of a script", + Position::new(1, 1), + )); + } + } + if !self.direct_eval { // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval // code that is being processed by a direct eval. diff --git a/core/parser/src/parser/statement/iteration/for_statement.rs b/core/parser/src/parser/statement/iteration/for_statement.rs index e64d48ab6b9..359037aa04e 100644 --- a/core/parser/src/parser/statement/iteration/for_statement.rs +++ b/core/parser/src/parser/statement/iteration/for_statement.rs @@ -131,6 +131,31 @@ where .parse(cursor, interner)? .into(), ), + TokenKind::Keyword((Keyword::Using, false)) + if matches!( + cursor.peek(1, interner)?.map(|t| t.kind().clone()), + Some( + TokenKind::IdentifierName(_) + | TokenKind::Keyword((Keyword::Yield | Keyword::Await, _)), + ) + ) || ( + // `for (using of = null;;)` — `of` is a valid binding name for `using`. + // But `for (using of of arr)` — `using` is an identifier, not a keyword. + matches!( + cursor.peek(1, interner)?.map(|t| t.kind().clone()), + Some(TokenKind::Keyword((Keyword::Of, false))) + ) && !matches!( + cursor.peek(2, interner)?.map(|t| t.kind().clone()), + Some(TokenKind::Keyword((Keyword::Of, _))) + ) + ) => + { + Some( + LexicalDeclaration::new(false, self.allow_yield, self.allow_await, true) + .parse(cursor, interner)? + .into(), + ) + } TokenKind::Keyword((Keyword::Async, false)) if !r#await => { if matches!( cursor.peek(1, interner).or_abrupt()?.kind(), @@ -168,6 +193,20 @@ where position, )); } + ( + Some(ForLoopInitializer::Lexical(ref init)), + TokenKind::Keyword((Keyword::In, false)), + ) if matches!( + init.declaration(), + ast::declaration::LexicalDeclaration::Using(_) + | ast::declaration::LexicalDeclaration::AwaitUsing(_) + ) => + { + return Err(Error::general( + "`using` declarations are not allowed in `for-in` loops", + position, + )); + } (Some(init), TokenKind::Keyword((kw @ (Keyword::In | Keyword::Of), false))) => { if kw == &Keyword::Of && let ForLoopInitializer::Expression(ast::Expression::Identifier(ident)) = init @@ -221,7 +260,9 @@ where // Checks are only applicable to lexical bindings. if matches!( &init, - IterableLoopInitializer::Const(_) | IterableLoopInitializer::Let(_) + IterableLoopInitializer::Const(_) + | IterableLoopInitializer::Let(_) + | IterableLoopInitializer::Using(_) ) { // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". // It is a Syntax Error if any element of the BoundNames of ForDeclaration also occurs in the VarDeclaredNames of Statement. @@ -390,9 +431,11 @@ fn initializer_to_iterable_loop_initializer( ast::declaration::LexicalDeclaration::Const(_) => { IterableLoopInitializer::Const(decl.binding().clone()) } - ast::declaration::LexicalDeclaration::Let(_) - | ast::declaration::LexicalDeclaration::Using(_) + ast::declaration::LexicalDeclaration::Using(_) | ast::declaration::LexicalDeclaration::AwaitUsing(_) => { + IterableLoopInitializer::Using(decl.binding().clone()) + } + ast::declaration::LexicalDeclaration::Let(_) => { IterableLoopInitializer::Let(decl.binding().clone()) } }) diff --git a/core/parser/src/parser/statement/mod.rs b/core/parser/src/parser/statement/mod.rs index c02d38cddfa..ca616b02303 100644 --- a/core/parser/src/parser/statement/mod.rs +++ b/core/parser/src/parser/statement/mod.rs @@ -424,12 +424,46 @@ where let tok = cursor.peek(0, interner).or_abrupt()?; match tok.kind().clone() { - TokenKind::Keyword(( - Keyword::Function | Keyword::Class | Keyword::Const | Keyword::Using, - _, - )) => Declaration::new(self.allow_yield, self.allow_await) - .parse(cursor, interner) - .map(ast::StatementListItem::from), + TokenKind::Keyword((Keyword::Function | Keyword::Class | Keyword::Const, _)) => { + Declaration::new(self.allow_yield, self.allow_await) + .parse(cursor, interner) + .map(ast::StatementListItem::from) + } + TokenKind::Keyword((Keyword::Using, false)) => { + // Per spec: `using [no LineTerminator here] BindingList ;` + // If there's a line terminator after `using`, then `using` is an identifier. + let skip_n = if cursor.peek_is_line_terminator(0, interner).or_abrupt()? { + 2 + } else { + 1 + }; + let is_line_terminator = cursor + .peek_is_line_terminator(skip_n, interner)? + .unwrap_or(true); + + if is_line_terminator { + return Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor, interner) + .map(ast::StatementListItem::from); + } + + // If the next (non-LT) token is `[` or `{`, `using` is an identifier + // (element access or object literal, not a declaration). + if let Some(next_tok) = cursor.peek(1, interner)? + && matches!( + next_tok.kind(), + TokenKind::Punctuator(Punctuator::OpenBracket | Punctuator::OpenBlock) + ) + { + return Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor, interner) + .map(ast::StatementListItem::from); + } + + Declaration::new(self.allow_yield, self.allow_await) + .parse(cursor, interner) + .map(ast::StatementListItem::from) + } TokenKind::Keyword((Keyword::Let, false)) if allowed_token_after_let(cursor.peek(1, interner)?) => { diff --git a/core/parser/src/parser/statement/switch/mod.rs b/core/parser/src/parser/statement/switch/mod.rs index 777f3d4c3ba..8d145eb6bac 100644 --- a/core/parser/src/parser/statement/switch/mod.rs +++ b/core/parser/src/parser/statement/switch/mod.rs @@ -175,6 +175,25 @@ where ) .parse(cursor, interner)?; + // It is a Syntax Error if UsingDeclaration is contained directly + // within the StatementList of a CaseClause. + for statement in statement_list.statements() { + if let ast::StatementListItem::Declaration(decl) = statement + && matches!( + decl.as_ref(), + ast::Declaration::Lexical( + ast::declaration::LexicalDeclaration::Using(_) + | ast::declaration::LexicalDeclaration::AwaitUsing(_), + ), + ) + { + return Err(Error::general( + "`using` declarations are not allowed directly in switch clauses", + token.span().start(), + )); + } + } + cases.push(statement::Case::new(cond, statement_list)); } TokenKind::Keyword((Keyword::Default, false)) => { @@ -199,6 +218,25 @@ where ) .parse(cursor, interner)?; + // It is a Syntax Error if UsingDeclaration is contained directly + // within the StatementList of a DefaultClause. + for statement in statement_list.statements() { + if let ast::StatementListItem::Declaration(decl) = statement + && matches!( + decl.as_ref(), + ast::Declaration::Lexical( + ast::declaration::LexicalDeclaration::Using(_) + | ast::declaration::LexicalDeclaration::AwaitUsing(_), + ), + ) + { + return Err(Error::general( + "`using` declarations are not allowed directly in switch clauses", + token.span().start(), + )); + } + } + cases.push(statement::Case::default(statement_list)); has_default_case = true; diff --git a/core/string/src/common.rs b/core/string/src/common.rs index 993fff83edc..103e11f7bd7 100644 --- a/core/string/src/common.rs +++ b/core/string/src/common.rs @@ -134,6 +134,7 @@ impl StaticJsStrings { (DATE, "Date"), (ERROR, "Error"), (AGGREGATE_ERROR, "AggregateError"), + (SUPPRESSED_ERROR, "SuppressedError"), (EVAL_ERROR, "EvalError"), (RANGE_ERROR, "RangeError"), (REFERENCE_ERROR, "ReferenceError"), @@ -281,6 +282,7 @@ const RAW_STATICS: &[StaticString] = &[ StaticString::new(JsStr::latin1("Date".as_bytes())), StaticString::new(JsStr::latin1("Error".as_bytes())), StaticString::new(JsStr::latin1("AggregateError".as_bytes())), + StaticString::new(JsStr::latin1("SuppressedError".as_bytes())), StaticString::new(JsStr::latin1("EvalError".as_bytes())), StaticString::new(JsStr::latin1("RangeError".as_bytes())), StaticString::new(JsStr::latin1("ReferenceError".as_bytes())), diff --git a/test262_config.toml b/test262_config.toml index fcc0f5927bb..0d40ffe0980 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -15,7 +15,6 @@ features = [ "Intl.DurationFormat", "regexp-duplicate-named-groups", "iterator-helpers", - "explicit-resource-management", "joint-iteration", ### Pending proposals