diff --git a/core/engine/src/bytecompiler/module.rs b/core/engine/src/bytecompiler/module.rs index a14f3f5dfc2..76b58f9bec8 100644 --- a/core/engine/src/bytecompiler/module.rs +++ b/core/engine/src/bytecompiler/module.rs @@ -1,4 +1,4 @@ -use super::{ByteCompiler, Literal, ToJsString}; +use super::{ByteCompiler, ToJsString}; use crate::vm::opcode::BindingOpcode; use boa_ast::{ModuleItem, ModuleItemList, declaration::ExportDeclaration}; use boa_interner::Sym; @@ -65,24 +65,17 @@ impl ByteCompiler<'_> { } } ExportDeclaration::DefaultAssignmentExpression(expr) => { + // 1. If IsAnonymousFunctionDefinition(AssignmentExpression) is true, then + // a. Let value be ? NamedEvaluation of AssignmentExpression with argument "default". + // The parser has already named it "default". + // 2. Else, + // a. Let rhs be ? Evaluation of AssignmentExpression. + // b. Let value be ? GetValue(rhs). let function = self.register_allocator.alloc(); self.compile_expr(expr, &function); - if expr.is_anonymous_function_definition() { - let default = self - .interner() - .resolve_expect(Sym::DEFAULT) - .into_common(false); - let key = self.register_allocator.alloc(); - self.emit_store_literal(Literal::String(default), &key); - self.bytecode.emit_set_function_name( - function.variable(), - key.variable(), - 0u32.into(), - ); - self.register_allocator.dealloc(key); - } - + // 3. Let env be the running execution context's LexicalEnvironment. + // 4. Perform ? InitializeBoundName("*default*", value, env). let name = Sym::DEFAULT_EXPORT.to_js_string(self.interner()); self.emit_binding(BindingOpcode::InitLexical, name, &function); self.register_allocator.dealloc(function); diff --git a/core/engine/tests/module.rs b/core/engine/tests/module.rs index 9c49ce569e7..2353fdb8193 100644 --- a/core/engine/tests/module.rs +++ b/core/engine/tests/module.rs @@ -452,3 +452,49 @@ fn test_dynamic_import_symbol_key() { PromiseState::Pending => panic!("Dynamic import is still pending"), } } + +#[test] +fn test_default_export_named_evaluation() { + fn default_export(src: &[u8], context: &mut Context) -> boa_engine::JsValue { + let module = Module::parse(Source::from_bytes(src), None, context).unwrap(); + let promise = module.load_link_evaluate(context); + context.run_jobs().unwrap(); + if let PromiseState::Rejected(e) = promise.state() { + panic!("Unexpected error: {:?}", e.to_string(context).unwrap()); + } + module + .namespace(context) + .get(js_string!("default"), context) + .unwrap() + } + + fn name_of(value: &boa_engine::JsValue, context: &mut Context) -> boa_engine::JsValue { + value + .as_object() + .unwrap() + .get(js_string!("name"), context) + .unwrap() + } + + let mut context = Context::default(); + let sources: [&[u8]; 3] = [ + b"export default (function () {});", + b"export default (class {});", + b"export default (() => {});", + ]; + for src in sources { + let value = default_export(src, &mut context); + let name = name_of(&value, &mut context) + .to_string(&mut context) + .unwrap(); + assert_eq!(name, js_string!("default")); + } + + // The class is named before its static elements are defined, so a static + // `name` method replaces the name. + let value = default_export( + b"export default (class { static name() { return 'method'; } });", + &mut context, + ); + assert!(name_of(&value, &mut context).is_callable()); +} diff --git a/core/parser/src/parser/statement/declaration/export.rs b/core/parser/src/parser/statement/declaration/export.rs index 78b8d8f26e4..6db62f78445 100644 --- a/core/parser/src/parser/statement/declaration/export.rs +++ b/core/parser/src/parser/statement/declaration/export.rs @@ -22,6 +22,7 @@ use crate::{ use boa_ast::{ Keyword, Punctuator, Spanned, declaration::{ExportDeclaration as AstExportDeclaration, ReExportKind}, + expression::Identifier, }; use boa_interner::{Interner, Sym}; @@ -205,9 +206,18 @@ where ) } _ => { - let expr = + let mut expr = AssignmentExpression::new(true, false, true).parse(cursor, interner)?; + // An anonymous function definition is named "default" by its + // NamedEvaluation, so a class gets the name before its static + // elements are defined, and one of them can replace it. + let span = expr.span(); + expr.set_anonymous_function_definition_name(&Identifier::new( + Sym::DEFAULT, + span, + )); + cursor.expect_semicolon("default expression export", interner)?; AstExportDeclaration::DefaultAssignmentExpression(expr)