Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 9 additions & 16 deletions core/engine/src/bytecompiler/module.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down
46 changes: 46 additions & 0 deletions core/engine/tests/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
12 changes: 11 additions & 1 deletion core/parser/src/parser/statement/declaration/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
use boa_ast::{
Keyword, Punctuator, Spanned,
declaration::{ExportDeclaration as AstExportDeclaration, ReExportKind},
expression::Identifier,
};
use boa_interner::{Interner, Sym};

Expand Down Expand Up @@ -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)
Expand Down
Loading