Browse Source

Fix remaining static module bugs (#2955)

* Fix remaining module bugs

* npx prettier

* Fix regression
pull/2961/head
José Julián Espina 2 years ago committed by GitHub
parent
commit
9bbe0184a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      .vscode/launch.json
  2. 1
      Cargo.lock
  3. 1
      boa_ast/Cargo.toml
  4. 11
      boa_ast/src/declaration/export.rs
  5. 11
      boa_ast/src/module_item_list/mod.rs
  6. 10
      boa_engine/src/bytecompiler/declarations.rs
  7. 2
      boa_engine/src/bytecompiler/env.rs
  8. 12
      boa_engine/src/bytecompiler/expression/mod.rs
  9. 86
      boa_engine/src/bytecompiler/mod.rs
  10. 16
      boa_engine/src/bytecompiler/module.rs
  11. 2
      boa_engine/src/bytecompiler/statement/labelled.rs
  12. 130
      boa_engine/src/module/source.rs
  13. 27
      boa_engine/src/object/internal_methods/module_namespace.rs
  14. 4
      boa_engine/src/tests/mod.rs
  15. 15
      boa_parser/src/parser/expression/identifiers.rs
  16. 45
      boa_parser/src/parser/statement/declaration/export.rs
  17. 56
      boa_parser/src/parser/statement/declaration/import.rs
  18. 2
      test_ignore.toml

20
.vscode/launch.json vendored

@ -7,7 +7,7 @@
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",
"name": "Launch", "name": "Launch (Script)",
"windows": { "windows": {
"program": "${workspaceFolder}/target/debug/boa.exe" "program": "${workspaceFolder}/target/debug/boa.exe"
}, },
@ -15,6 +15,24 @@
"args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"], "args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"],
"sourceLanguages": ["rust"], "sourceLanguages": ["rust"],
"preLaunchTask": "Cargo Build" "preLaunchTask": "Cargo Build"
},
{
"type": "lldb",
"request": "launch",
"name": "Launch (Module)",
"windows": {
"program": "${workspaceFolder}/target/debug/boa.exe"
},
"program": "${workspaceFolder}/target/debug/boa",
"args": [
"${workspaceFolder}/tests/js/test.js",
"--debug-object",
"-m",
"-r",
"tests/js"
],
"sourceLanguages": ["rust"],
"preLaunchTask": "Cargo Build"
} }
] ]
} }

1
Cargo.lock generated

@ -365,6 +365,7 @@ dependencies = [
"bitflags 2.3.1", "bitflags 2.3.1",
"boa_interner", "boa_interner",
"boa_macros", "boa_macros",
"indexmap",
"num-bigint", "num-bigint",
"rustc-hash", "rustc-hash",
"serde", "serde",

1
boa_ast/Cargo.toml

@ -22,3 +22,4 @@ bitflags = "2.3.1"
num-bigint = "0.4.3" num-bigint = "0.4.3"
serde = { version = "1.0.163", features = ["derive"], optional = true } serde = { version = "1.0.163", features = ["derive"], optional = true }
arbitrary = { version = "1", features = ["derive"], optional = true } arbitrary = { version = "1", features = ["derive"], optional = true }
indexmap = "1.9.3"

11
boa_ast/src/declaration/export.rs

@ -174,16 +174,18 @@ impl VisitWith for ExportDeclaration {
pub struct ExportSpecifier { pub struct ExportSpecifier {
alias: Sym, alias: Sym,
private_name: Sym, private_name: Sym,
string_literal: bool,
} }
impl ExportSpecifier { impl ExportSpecifier {
/// Creates a new [`ExportSpecifier`]. /// Creates a new [`ExportSpecifier`].
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn new(alias: Sym, private_name: Sym) -> Self { pub const fn new(alias: Sym, private_name: Sym, string_literal: bool) -> Self {
Self { Self {
alias, alias,
private_name, private_name,
string_literal,
} }
} }
@ -200,6 +202,13 @@ impl ExportSpecifier {
pub const fn private_name(self) -> Sym { pub const fn private_name(self) -> Sym {
self.private_name self.private_name
} }
/// Returns `true` if the private name of the specifier was a `StringLiteral`.
#[inline]
#[must_use]
pub const fn string_literal(&self) -> bool {
self.string_literal
}
} }
impl VisitWith for ExportSpecifier { impl VisitWith for ExportSpecifier {

11
boa_ast/src/module_item_list/mod.rs

@ -5,10 +5,11 @@
//! //!
//! [spec]: https://tc39.es/ecma262/#sec-modules //! [spec]: https://tc39.es/ecma262/#sec-modules
use std::{convert::Infallible, ops::ControlFlow}; use std::{convert::Infallible, hash::BuildHasherDefault, ops::ControlFlow};
use boa_interner::Sym; use boa_interner::Sym;
use rustc_hash::FxHashSet; use indexmap::IndexSet;
use rustc_hash::{FxHashSet, FxHasher};
use crate::{ use crate::{
declaration::{ declaration::{
@ -205,9 +206,9 @@ impl ModuleItemList {
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-modulerequests /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-modulerequests
#[inline] #[inline]
#[must_use] #[must_use]
pub fn requests(&self) -> FxHashSet<Sym> { pub fn requests(&self) -> IndexSet<Sym, BuildHasherDefault<FxHasher>> {
#[derive(Debug)] #[derive(Debug)]
struct RequestsVisitor<'vec>(&'vec mut FxHashSet<Sym>); struct RequestsVisitor<'vec>(&'vec mut IndexSet<Sym, BuildHasherDefault<FxHasher>>);
impl<'ast> Visitor<'ast> for RequestsVisitor<'_> { impl<'ast> Visitor<'ast> for RequestsVisitor<'_> {
type BreakTy = Infallible; type BreakTy = Infallible;
@ -227,7 +228,7 @@ impl ModuleItemList {
} }
} }
let mut requests = FxHashSet::default(); let mut requests = IndexSet::default();
RequestsVisitor(&mut requests).visit_module_item_list(self); RequestsVisitor(&mut requests).visit_module_item_list(self);

10
boa_engine/src/bytecompiler/declarations.rs

@ -351,16 +351,16 @@ impl ByteCompiler<'_, '_> {
for d in &declarations { for d in &declarations {
match d { match d {
LexicallyScopedDeclaration::Function(function) => { LexicallyScopedDeclaration::Function(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function_with_binding(function.into(), NodeKind::Declaration, false);
} }
LexicallyScopedDeclaration::Generator(function) => { LexicallyScopedDeclaration::Generator(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function_with_binding(function.into(), NodeKind::Declaration, false);
} }
LexicallyScopedDeclaration::AsyncFunction(function) => { LexicallyScopedDeclaration::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function_with_binding(function.into(), NodeKind::Declaration, false);
} }
LexicallyScopedDeclaration::AsyncGenerator(function) => { LexicallyScopedDeclaration::AsyncGenerator(function) => {
self.function(function.into(), NodeKind::Declaration, false); self.function_with_binding(function.into(), NodeKind::Declaration, false);
} }
_ => {} _ => {}
} }
@ -1147,7 +1147,7 @@ impl ByteCompiler<'_, '_> {
// a. Let fn be the sole element of the BoundNames of f. // a. Let fn be the sole element of the BoundNames of f.
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
// c. Perform ! varEnv.SetMutableBinding(fn, fo, false). // c. Perform ! varEnv.SetMutableBinding(fn, fo, false).
self.function(function, NodeKind::Declaration, false); self.function_with_binding(function, NodeKind::Declaration, false);
} }
// 37. Return unused. // 37. Return unused.

2
boa_engine/src/bytecompiler/env.rs

@ -88,7 +88,7 @@ impl ByteCompiler<'_, '_> {
.create_mutable_binding(name, function_scope)); .create_mutable_binding(name, function_scope));
} }
/// Initialize a mutable binding at bytecode compile time and return it's binding locator. /// Initialize a mutable binding at bytecode compile time and return its binding locator.
pub(crate) fn initialize_mutable_binding( pub(crate) fn initialize_mutable_binding(
&self, &self,
name: Identifier, name: Identifier,

12
boa_engine/src/bytecompiler/expression/mod.rs

@ -124,22 +124,22 @@ impl ByteCompiler<'_, '_> {
} }
Expression::Spread(spread) => self.compile_expr(spread.target(), true), Expression::Spread(spread) => self.compile_expr(spread.target(), true),
Expression::Function(function) => { Expression::Function(function) => {
self.function(function.into(), NodeKind::Expression, use_expr); self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
} }
Expression::ArrowFunction(function) => { Expression::ArrowFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr); self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
} }
Expression::AsyncArrowFunction(function) => { Expression::AsyncArrowFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr); self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
} }
Expression::Generator(function) => { Expression::Generator(function) => {
self.function(function.into(), NodeKind::Expression, use_expr); self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
} }
Expression::AsyncFunction(function) => { Expression::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr); self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
} }
Expression::AsyncGenerator(function) => { Expression::AsyncGenerator(function) => {
self.function(function.into(), NodeKind::Expression, use_expr); self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
} }
Expression::Call(call) => self.call(Callable::Call(call), use_expr), Expression::Call(call) => self.call(Callable::Call(call), use_expr),
Expression::New(new) => self.call(Callable::New(new), use_expr), Expression::New(new) => self.call(Callable::New(new), use_expr),

86
boa_engine/src/bytecompiler/mod.rs

@ -48,7 +48,7 @@ pub(crate) enum NodeKind {
/// Describes the type of a function. /// Describes the type of a function.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
enum FunctionKind { pub(crate) enum FunctionKind {
Ordinary, Ordinary,
Arrow, Arrow,
AsyncArrow, AsyncArrow,
@ -57,37 +57,31 @@ enum FunctionKind {
AsyncGenerator, AsyncGenerator,
} }
impl FunctionKind {
pub(crate) const fn is_arrow(self) -> bool {
matches!(self, Self::Arrow | Self::AsyncArrow)
}
pub(crate) const fn is_async(self) -> bool {
matches!(self, Self::Async | Self::AsyncGenerator | Self::AsyncArrow)
}
pub(crate) const fn is_generator(self) -> bool {
matches!(self, Self::Generator | Self::AsyncGenerator)
}
}
/// Describes the complete specification of a function node. /// Describes the complete specification of a function node.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
#[allow(single_use_lifetimes)] #[allow(single_use_lifetimes)]
pub(crate) struct FunctionSpec<'a> { pub(crate) struct FunctionSpec<'a> {
kind: FunctionKind, pub(crate) kind: FunctionKind,
pub(crate) name: Option<Identifier>, pub(crate) name: Option<Identifier>,
parameters: &'a FormalParameterList, parameters: &'a FormalParameterList,
body: &'a FunctionBody, body: &'a FunctionBody,
has_binding_identifier: bool, has_binding_identifier: bool,
} }
impl FunctionSpec<'_> {
const fn is_arrow(&self) -> bool {
matches!(self.kind, FunctionKind::Arrow | FunctionKind::AsyncArrow)
}
const fn is_async(&self) -> bool {
matches!(
self.kind,
FunctionKind::Async | FunctionKind::AsyncGenerator | FunctionKind::AsyncArrow
)
}
const fn is_generator(&self) -> bool {
matches!(
self.kind,
FunctionKind::Generator | FunctionKind::AsyncGenerator
)
}
}
impl<'a> From<&'a Function> for FunctionSpec<'a> { impl<'a> From<&'a Function> for FunctionSpec<'a> {
fn from(function: &'a Function) -> Self { fn from(function: &'a Function) -> Self {
FunctionSpec { FunctionSpec {
@ -1070,17 +1064,13 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
} }
} }
/// Compile a function AST Node into bytecode. /// Compiles a function AST Node into bytecode, and returns its index into
pub(crate) fn function( /// the `functions` array.
&mut self, pub(crate) fn function(&mut self, function: FunctionSpec<'_>) -> u32 {
function: FunctionSpec<'_>,
node_kind: NodeKind,
use_expr: bool,
) {
let (generator, r#async, arrow) = ( let (generator, r#async, arrow) = (
function.is_generator(), function.kind.is_generator(),
function.is_async(), function.kind.is_async(),
function.is_arrow(), function.kind.is_arrow(),
); );
let FunctionSpec { let FunctionSpec {
name, name,
@ -1117,6 +1107,26 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
let index = self.functions.len() as u32; let index = self.functions.len() as u32;
self.functions.push(code); self.functions.push(code);
index
}
/// Compiles a function AST Node into bytecode, setting its corresponding binding or
/// pushing it to the stack if necessary.
pub(crate) fn function_with_binding(
&mut self,
function: FunctionSpec<'_>,
node_kind: NodeKind,
use_expr: bool,
) {
let name = function.name;
let (generator, r#async, arrow) = (
function.kind.is_generator(),
function.kind.is_async(),
function.kind.is_arrow(),
);
let index = self.function(function);
if r#async && generator { if r#async && generator {
self.emit(Opcode::GetGeneratorAsync, &[index]); self.emit(Opcode::GetGeneratorAsync, &[index]);
} else if generator { } else if generator {
@ -1152,9 +1162,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile an object method AST Node into bytecode. /// Compile an object method AST Node into bytecode.
pub(crate) fn object_method(&mut self, function: FunctionSpec<'_>) { pub(crate) fn object_method(&mut self, function: FunctionSpec<'_>) {
let (generator, r#async, arrow) = ( let (generator, r#async, arrow) = (
function.is_generator(), function.kind.is_generator(),
function.is_async(), function.kind.is_async(),
function.is_arrow(), function.kind.is_arrow(),
); );
let FunctionSpec { let FunctionSpec {
name, name,
@ -1212,9 +1222,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile a class method AST Node into bytecode. /// Compile a class method AST Node into bytecode.
fn method(&mut self, function: FunctionSpec<'_>, class_name: Sym) { fn method(&mut self, function: FunctionSpec<'_>, class_name: Sym) {
let (generator, r#async, arrow) = ( let (generator, r#async, arrow) = (
function.is_generator(), function.kind.is_generator(),
function.is_async(), function.kind.is_async(),
function.is_arrow(), function.kind.is_arrow(),
); );
let FunctionSpec { let FunctionSpec {
name, name,

16
boa_engine/src/bytecompiler/module.rs

@ -1,6 +1,6 @@
use crate::vm::BindingOpcode; use crate::vm::{BindingOpcode, Opcode};
use super::ByteCompiler; use super::{ByteCompiler, Literal};
use boa_ast::{declaration::ExportDeclaration, expression::Identifier, ModuleItem, ModuleItemList}; use boa_ast::{declaration::ExportDeclaration, expression::Identifier, ModuleItem, ModuleItemList};
use boa_interner::Sym; use boa_interner::Sym;
@ -55,6 +55,18 @@ impl ByteCompiler<'_, '_> {
let name = Identifier::from(Sym::DEFAULT_EXPORT); let name = Identifier::from(Sym::DEFAULT_EXPORT);
self.create_mutable_binding(name, false); self.create_mutable_binding(name, false);
self.compile_expr(expr, true); self.compile_expr(expr, true);
if expr.is_anonymous_function_definition() {
let default = self
.interner()
.resolve_expect(Sym::DEFAULT)
.into_common(false);
self.emit_push_literal(Literal::String(default));
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(0);
}
self.emit_binding(BindingOpcode::InitLet, name); self.emit_binding(BindingOpcode::InitLet, name);
} }
} }

2
boa_engine/src/bytecompiler/statement/labelled.rs

@ -34,7 +34,7 @@ impl ByteCompiler<'_, '_> {
stmt => self.compile_stmt(stmt, use_expr), stmt => self.compile_stmt(stmt, use_expr),
}, },
LabelledItem::Function(f) => { LabelledItem::Function(f) => {
self.function(f.into(), NodeKind::Declaration, false); self.function_with_binding(f.into(), NodeKind::Declaration, false);
} }
} }

130
boa_engine/src/module/source.rs

@ -1,4 +1,9 @@
use std::{cell::Cell, collections::HashSet, hash::Hash, rc::Rc}; use std::{
cell::Cell,
collections::HashSet,
hash::{BuildHasherDefault, Hash},
rc::Rc,
};
use boa_ast::{ use boa_ast::{
declaration::{ declaration::{
@ -12,16 +17,20 @@ use boa_ast::{
}; };
use boa_gc::{custom_trace, empty_trace, Finalize, Gc, GcRefCell, Trace}; use boa_gc::{custom_trace, empty_trace, Finalize, Gc, GcRefCell, Trace};
use boa_interner::Sym; use boa_interner::Sym;
use rustc_hash::{FxHashMap, FxHashSet}; use indexmap::IndexSet;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use crate::{ use crate::{
builtins::{promise::PromiseCapability, Promise}, builtins::{promise::PromiseCapability, Promise},
bytecompiler::{ByteCompiler, NodeKind}, bytecompiler::{ByteCompiler, FunctionSpec},
environments::{BindingLocator, CompileTimeEnvironment, EnvironmentStack}, environments::{BindingLocator, CompileTimeEnvironment, EnvironmentStack},
module::ModuleKind, module::ModuleKind,
object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, object::{FunctionObjectBuilder, JsPromise, RecursionLimiter},
realm::Realm, realm::Realm,
vm::{ActiveRunnable, CallFrame, CodeBlock, CompletionRecord, Opcode}, vm::{
create_function_object_fast, create_generator_function_object, ActiveRunnable, CallFrame,
CodeBlock, CompletionRecord, Opcode,
},
Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction,
}; };
@ -273,7 +282,7 @@ struct Inner {
#[derive(Debug)] #[derive(Debug)]
struct ModuleCode { struct ModuleCode {
has_tla: bool, has_tla: bool,
requested_modules: FxHashSet<Sym>, requested_modules: IndexSet<Sym, BuildHasherDefault<FxHasher>>,
source: boa_ast::Module, source: boa_ast::Module,
import_entries: Vec<ImportEntry>, import_entries: Vec<ImportEntry>,
local_export_entries: Vec<LocalExportEntry>, local_export_entries: Vec<LocalExportEntry>,
@ -1403,7 +1412,7 @@ impl SourceTextModule {
ByteCompiler::new(Sym::MAIN, true, false, module_compile_env.clone(), context); ByteCompiler::new(Sym::MAIN, true, false, module_compile_env.clone(), context);
let mut imports = Vec::new(); let mut imports = Vec::new();
let codeblock = { let (codeblock, functions) = {
// 7. For each ImportEntry Record in of module.[[ImportEntries]], do // 7. For each ImportEntry Record in of module.[[ImportEntries]], do
for entry in &self.inner.code.import_entries { for entry in &self.inner.code.import_entries {
// a. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]). // a. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]).
@ -1448,7 +1457,7 @@ impl SourceTextModule {
// deferred to initialization below // deferred to initialization below
imports.push(ImportBinding::Namespace { imports.push(ImportBinding::Namespace {
locator, locator,
module: imported_module, module: resolution.module,
}); });
} }
} else { } else {
@ -1494,47 +1503,87 @@ impl SourceTextModule {
// 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code.
// 23. Let privateEnv be null. // 23. Let privateEnv be null.
let lex_declarations = lexically_scoped_declarations(&self.inner.code.source); let lex_declarations = lexically_scoped_declarations(&self.inner.code.source);
let mut functions = Vec::new();
// 24. For each element d of lexDeclarations, do // 24. For each element d of lexDeclarations, do
for declaration in &lex_declarations { for declaration in &lex_declarations {
// ii. Else,
// a. For each element dn of the BoundNames of d, do
// 1. Perform ! env.CreateMutableBinding(dn, false).
//
// iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an
// AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
// 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
// 2. Perform ! env.InitializeBinding(dn, fo).
//
// deferred to below.
let (spec, locator): (FunctionSpec<'_>, _) = match declaration {
LexicallyScopedDeclaration::Function(f) => {
let name = bound_names(f)[0];
compiler.create_mutable_binding(name, false);
let locator = compiler.initialize_mutable_binding(name, false);
(f.into(), locator)
}
LexicallyScopedDeclaration::Generator(g) => {
let name = bound_names(g)[0];
compiler.create_mutable_binding(name, false);
let locator = compiler.initialize_mutable_binding(name, false);
(g.into(), locator)
}
LexicallyScopedDeclaration::AsyncFunction(af) => {
let name = bound_names(af)[0];
compiler.create_mutable_binding(name, false);
let locator = compiler.initialize_mutable_binding(name, false);
(af.into(), locator)
}
LexicallyScopedDeclaration::AsyncGenerator(ag) => {
let name = bound_names(ag)[0];
compiler.create_mutable_binding(name, false);
let locator = compiler.initialize_mutable_binding(name, false);
(ag.into(), locator)
}
LexicallyScopedDeclaration::Class(class) => {
for name in bound_names(class) {
compiler.create_mutable_binding(name, false);
}
continue;
}
// i. If IsConstantDeclaration of d is true, then // i. If IsConstantDeclaration of d is true, then
if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const( LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(
decl, c,
)) = declaration )) => {
{
// a. For each element dn of the BoundNames of d, do // a. For each element dn of the BoundNames of d, do
for name in bound_names(decl) { for name in bound_names(c) {
// 1. Perform ! env.CreateImmutableBinding(dn, true). // 1. Perform ! env.CreateImmutableBinding(dn, true).
compiler.create_immutable_binding(name, true); compiler.create_immutable_binding(name, true);
} }
} else { continue;
// ii. Else, }
// a. For each element dn of the BoundNames of d, do LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Let(l)) => {
for name in declaration.bound_names() { for name in bound_names(l) {
// 1. Perform ! env.CreateMutableBinding(dn, false).
compiler.create_mutable_binding(name, false); compiler.create_mutable_binding(name, false);
} }
continue;
}
LexicallyScopedDeclaration::AssignmentExpression(expr) => {
for name in bound_names(expr) {
compiler.create_mutable_binding(name, false);
}
continue;
} }
// iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an
// AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
// 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
// 2. Perform ! env.InitializeBinding(dn, fo).
let spec = match declaration {
LexicallyScopedDeclaration::Function(f) => f.into(),
LexicallyScopedDeclaration::Generator(g) => g.into(),
LexicallyScopedDeclaration::AsyncFunction(af) => af.into(),
LexicallyScopedDeclaration::AsyncGenerator(ag) => ag.into(),
LexicallyScopedDeclaration::Class(_)
| LexicallyScopedDeclaration::LexicalDeclaration(_)
| LexicallyScopedDeclaration::AssignmentExpression(_) => continue,
}; };
compiler.function(spec, NodeKind::Declaration, false); let kind = spec.kind;
functions.push((compiler.function(spec), locator, kind));
} }
compiler.compile_module_item_list(self.inner.code.source.items()); compiler.compile_module_item_list(self.inner.code.source.items());
Gc::new(compiler.finish()) (Gc::new(compiler.finish()), functions)
}; };
// 8. Let moduleContext be a new ECMAScript code execution context. // 8. Let moduleContext be a new ECMAScript code execution context.
@ -1598,6 +1647,23 @@ impl SourceTextModule {
} }
} }
// deferred initialization of function exports
for (index, locator, kind) in functions {
let code = codeblock.functions[index as usize].clone();
let function = if kind.is_generator() {
create_generator_function_object(code, kind.is_async(), None, context)
} else {
create_function_object_fast(code, kind.is_async(), false, false, context)
};
context.vm.environments.put_lexical_value(
locator.environment_index(),
locator.binding_index(),
function.into(),
);
}
// 25. Remove moduleContext from the execution context stack. // 25. Remove moduleContext from the execution context stack.
std::mem::swap(&mut context.vm.environments, &mut envs); std::mem::swap(&mut context.vm.environments, &mut envs);
context.vm.stack = stack; context.vm.stack = stack;

27
boa_engine/src/object/internal_methods/module_namespace.rs

@ -1,6 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use crate::{ use crate::{
js_string,
module::BindingName, module::BindingName,
object::{JsObject, JsPrototype}, object::{JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
@ -88,8 +89,8 @@ fn module_namespace_exotic_get_own_property(
// 1. If P is a Symbol, return OrdinaryGetOwnProperty(O, P). // 1. If P is a Symbol, return OrdinaryGetOwnProperty(O, P).
let key = match key { let key = match key {
PropertyKey::Symbol(_) => return ordinary_get_own_property(obj, key, context), PropertyKey::Symbol(_) => return ordinary_get_own_property(obj, key, context),
PropertyKey::Index(_) => return Ok(None), PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s, PropertyKey::String(s) => s.clone(),
}; };
{ {
@ -101,13 +102,13 @@ fn module_namespace_exotic_get_own_property(
let exports = obj.exports(); let exports = obj.exports();
// 3. If exports does not contain P, return undefined. // 3. If exports does not contain P, return undefined.
if !exports.contains_key(key) { if !exports.contains_key(&key) {
return Ok(None); return Ok(None);
} }
} }
// 4. Let value be ? O.[[Get]](P, O). // 4. Let value be ? O.[[Get]](P, O).
let value = obj.get(key.clone(), context)?; let value = obj.get(key, context)?;
// 5. Return PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }. // 5. Return PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }.
Ok(Some( Ok(Some(
@ -168,8 +169,8 @@ fn module_namespace_exotic_has_property(
// 1. If P is a Symbol, return ! OrdinaryHasProperty(O, P). // 1. If P is a Symbol, return ! OrdinaryHasProperty(O, P).
let key = match key { let key = match key {
PropertyKey::Symbol(_) => return ordinary_has_property(obj, key, context), PropertyKey::Symbol(_) => return ordinary_has_property(obj, key, context),
PropertyKey::Index(_) => return Ok(false), PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s, PropertyKey::String(s) => s.clone(),
}; };
let obj = obj.borrow(); let obj = obj.borrow();
@ -182,7 +183,7 @@ fn module_namespace_exotic_has_property(
// 3. If exports contains P, return true. // 3. If exports contains P, return true.
// 4. Return false. // 4. Return false.
Ok(exports.contains_key(key)) Ok(exports.contains_key(&key))
} }
/// [`[[Get]] ( P, Receiver )`][spec] /// [`[[Get]] ( P, Receiver )`][spec]
@ -198,8 +199,8 @@ fn module_namespace_exotic_get(
// a. Return ! OrdinaryGet(O, P, Receiver). // a. Return ! OrdinaryGet(O, P, Receiver).
let key = match key { let key = match key {
PropertyKey::Symbol(_) => return ordinary_get(obj, key, receiver, context), PropertyKey::Symbol(_) => return ordinary_get(obj, key, receiver, context),
PropertyKey::Index(_) => return Ok(JsValue::undefined()), PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s, PropertyKey::String(s) => s.clone(),
}; };
let obj = obj.borrow(); let obj = obj.borrow();
@ -210,7 +211,7 @@ fn module_namespace_exotic_get(
// 2. Let exports be O.[[Exports]]. // 2. Let exports be O.[[Exports]].
let exports = obj.exports(); let exports = obj.exports();
// 3. If exports does not contain P, return undefined. // 3. If exports does not contain P, return undefined.
let Some(export_name) = exports.get(key).copied() else { let Some(export_name) = exports.get(&key).copied() else {
return Ok(JsValue::undefined()); return Ok(JsValue::undefined());
}; };
@ -285,8 +286,8 @@ fn module_namespace_exotic_delete(
// a. Return ! OrdinaryDelete(O, P). // a. Return ! OrdinaryDelete(O, P).
let key = match key { let key = match key {
PropertyKey::Symbol(_) => return ordinary_delete(obj, key, context), PropertyKey::Symbol(_) => return ordinary_delete(obj, key, context),
PropertyKey::Index(_) => return Ok(true), PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s, PropertyKey::String(s) => s.clone(),
}; };
let obj = obj.borrow(); let obj = obj.borrow();
@ -299,7 +300,7 @@ fn module_namespace_exotic_delete(
// 3. If exports contains P, return false. // 3. If exports contains P, return false.
// 4. Return true. // 4. Return true.
Ok(!exports.contains_key(key)) Ok(!exports.contains_key(&key))
} }
/// [`[[OwnPropertyKeys]] ( )`][spec]. /// [`[[OwnPropertyKeys]] ( )`][spec].

4
boa_engine/src/tests/mod.rs

@ -422,8 +422,8 @@ fn strict_mode_reserved_name() {
("var protected = 10;", "unexpected token 'protected', strict reserved word cannot be an identifier at line 1, col 19"), ("var protected = 10;", "unexpected token 'protected', strict reserved word cannot be an identifier at line 1, col 19"),
("var public = 10;", "unexpected token 'public', strict reserved word cannot be an identifier at line 1, col 19"), ("var public = 10;", "unexpected token 'public', strict reserved word cannot be an identifier at line 1, col 19"),
("var static = 10;", "unexpected token 'static', strict reserved word cannot be an identifier at line 1, col 19"), ("var static = 10;", "unexpected token 'static', strict reserved word cannot be an identifier at line 1, col 19"),
("var eval = 10;", "unexpected token 'eval', binding identifier `eval` not allowed in strict mode at line 1, col 19"), ("var eval = 10;", "binding identifier `eval` not allowed in strict mode at line 1, col 19"),
("var arguments = 10;", "unexpected token 'arguments', binding identifier `arguments` not allowed in strict mode at line 1, col 19"), ("var arguments = 10;", "binding identifier `arguments` not allowed in strict mode at line 1, col 19"),
("var let = 10;", "unexpected token 'let', strict reserved word cannot be an identifier at line 1, col 19"), ("var let = 10;", "unexpected token 'let', strict reserved word cannot be an identifier at line 1, col 19"),
("var yield = 10;", "unexpected token 'yield', strict reserved word cannot be an identifier at line 1, col 19"), ("var yield = 10;", "unexpected token 'yield', strict reserved word cannot be an identifier at line 1, col 19"),
]; ];

15
boa_parser/src/parser/expression/identifiers.rs

@ -114,21 +114,18 @@ where
.resolve_expect(ident.sym()) .resolve_expect(ident.sym())
.utf8() .utf8()
.expect("keyword must be utf-8"); .expect("keyword must be utf-8");
Err(Error::unexpected( Err(Error::general(
name,
span,
format!("binding identifier `{name}` not allowed in strict mode"), format!("binding identifier `{name}` not allowed in strict mode"),
span.start(),
)) ))
} }
Sym::YIELD if self.allow_yield.0 => Err(Error::unexpected( Sym::YIELD if self.allow_yield.0 => Err(Error::general(
"yield",
span,
"keyword `yield` not allowed in this context", "keyword `yield` not allowed in this context",
span.start(),
)), )),
Sym::AWAIT if self.allow_await.0 => Err(Error::unexpected( Sym::AWAIT if self.allow_await.0 => Err(Error::general(
"await",
span,
"keyword `await` not allowed in this context", "keyword `await` not allowed in this context",
span.start(),
)), )),
_ => Ok(ident), _ => Ok(ident),
} }

45
boa_parser/src/parser/statement/declaration/export.rs

@ -52,6 +52,7 @@ where
cursor.expect((Keyword::Export, false), "export declaration", interner)?; cursor.expect((Keyword::Export, false), "export declaration", interner)?;
let tok = cursor.peek(0, interner).or_abrupt()?; let tok = cursor.peek(0, interner).or_abrupt()?;
let span = tok.span();
let export_clause: Self::Output = match tok.kind() { let export_clause: Self::Output = match tok.kind() {
TokenKind::Punctuator(Punctuator::Mul) => { TokenKind::Punctuator(Punctuator::Mul) => {
@ -130,6 +131,33 @@ where
} else { } else {
cursor.expect_semicolon("named exports", interner)?; cursor.expect_semicolon("named exports", interner)?;
for specifier in &*names {
let name = specifier.private_name();
if specifier.string_literal() {
let name = interner.resolve_expect(name);
return Err(Error::general(
format!(
"local referenced binding `{name}` cannot be a string literal",
),
span.start(),
));
}
if name == Sym::AWAIT
|| name.is_reserved_identifier()
|| name.is_strict_reserved_identifier()
{
let name = interner.resolve_expect(name);
return Err(Error::general(
format!(
"local referenced binding `{name}` cannot be a reserved word",
),
span.start(),
));
}
}
AstExportDeclaration::List(names) AstExportDeclaration::List(names)
} }
} }
@ -272,7 +300,7 @@ impl<R> TokenParser<R> for ModuleExportName
where where
R: Read, R: Read,
{ {
type Output = Sym; type Output = (Sym, bool);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let tok = cursor.next(interner).or_abrupt()?; let tok = cursor.next(interner).or_abrupt()?;
@ -285,10 +313,10 @@ where
tok.span().end(), tok.span().end(),
)); ));
} }
Ok(*ident) Ok((*ident, true))
} }
TokenKind::IdentifierName((ident, _)) => Ok(*ident), TokenKind::IdentifierName((ident, _)) => Ok((*ident, false)),
TokenKind::Keyword((kw, _)) => Ok(kw.to_sym()), TokenKind::Keyword((kw, _)) => Ok((kw.to_sym(), false)),
_ => Err(Error::expected( _ => Err(Error::expected(
["identifier".to_owned(), "string literal".to_owned()], ["identifier".to_owned(), "string literal".to_owned()],
tok.to_string(interner), tok.to_string(interner),
@ -315,20 +343,23 @@ where
type Output = boa_ast::declaration::ExportSpecifier; type Output = boa_ast::declaration::ExportSpecifier;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let inner_name = ModuleExportName.parse(cursor, interner)?; let (inner_name, string_literal) = ModuleExportName.parse(cursor, interner)?;
if cursor if cursor
.next_if(TokenKind::identifier(Sym::AS), interner)? .next_if(TokenKind::identifier(Sym::AS), interner)?
.is_some() .is_some()
{ {
let export_name = ModuleExportName.parse(cursor, interner)?; let (export_name, _) = ModuleExportName.parse(cursor, interner)?;
Ok(boa_ast::declaration::ExportSpecifier::new( Ok(boa_ast::declaration::ExportSpecifier::new(
export_name, export_name,
inner_name, inner_name,
string_literal,
)) ))
} else { } else {
Ok(boa_ast::declaration::ExportSpecifier::new( Ok(boa_ast::declaration::ExportSpecifier::new(
inner_name, inner_name, inner_name,
inner_name,
string_literal,
)) ))
} }
} }

56
boa_parser/src/parser/statement/declaration/import.rs

@ -58,9 +58,7 @@ impl ImportDeclaration {
TokenKind::StringLiteral(_) TokenKind::StringLiteral(_)
| TokenKind::Punctuator(Punctuator::OpenBlock | Punctuator::Mul) | TokenKind::Punctuator(Punctuator::OpenBlock | Punctuator::Mul)
| TokenKind::IdentifierName(_) | TokenKind::IdentifierName(_)
| TokenKind::Keyword((Keyword::Await | Keyword::Yield, _)) => { | TokenKind::Keyword(_) => return Ok(true),
return Ok(true)
}
_ => {} _ => {}
} }
} }
@ -225,7 +223,9 @@ where
} }
cursor.advance(interner); cursor.advance(interner);
} }
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => { TokenKind::StringLiteral(_)
| TokenKind::IdentifierName(_)
| TokenKind::Keyword(_) => {
list.push(ImportSpecifier.parse(cursor, interner)?); list.push(ImportSpecifier.parse(cursor, interner)?);
} }
_ => { _ => {
@ -293,16 +293,20 @@ where
type Output = AstImportSpecifier; type Output = AstImportSpecifier;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> { fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let tok = cursor.next(interner).or_abrupt()?; let tok = cursor.peek(0, interner).or_abrupt()?;
match tok.kind() { match tok.kind() {
TokenKind::StringLiteral((name, _)) => { TokenKind::StringLiteral((name, _)) => {
if interner.resolve_expect(*name).utf8().is_none() { let name = *name;
if interner.resolve_expect(name).utf8().is_none() {
return Err(Error::general( return Err(Error::general(
"import specifiers don't allow unpaired surrogates", "import specifiers don't allow unpaired surrogates",
tok.span().end(), tok.span().end(),
)); ));
} }
cursor.advance(interner);
cursor.expect( cursor.expect(
TokenKind::identifier(Sym::AS), TokenKind::identifier(Sym::AS),
"import declaration", "import declaration",
@ -311,19 +315,43 @@ where
let binding = ImportedBinding.parse(cursor, interner)?; let binding = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(binding, *name)) Ok(AstImportSpecifier::new(binding, name))
}
TokenKind::Keyword((kw, _)) => {
let export_name = kw.to_sym();
cursor.advance(interner);
cursor.expect(
TokenKind::identifier(Sym::AS),
"import declaration",
interner,
)?;
let binding = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(binding, export_name))
} }
TokenKind::IdentifierName((name, _)) => { TokenKind::IdentifierName((name, _)) => {
if cursor let name = *name;
.next_if(TokenKind::identifier(Sym::AS), interner)?
.is_some() if let Some(token) = cursor.peek(1, interner)? {
{ if token.kind() == &TokenKind::identifier(Sym::AS) {
// export name
cursor.advance(interner);
// `as`
cursor.advance(interner);
let binding = ImportedBinding.parse(cursor, interner)?; let binding = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(binding, *name)) return Ok(AstImportSpecifier::new(binding, name));
} else {
Ok(AstImportSpecifier::new(Identifier::new(*name), *name))
} }
} }
let name = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(name, name.sym()))
}
_ => Err(Error::expected( _ => Err(Error::expected(
["string literal".to_owned(), "identifier".to_owned()], ["string literal".to_owned(), "identifier".to_owned()],
tok.to_string(interner), tok.to_string(interner),

2
test_ignore.toml

@ -30,6 +30,8 @@ features = [
"Intl-enumeration", "Intl-enumeration",
# https://github.com/tc39/proposal-array-from-async # https://github.com/tc39/proposal-array-from-async
"Array.fromAsync", "Array.fromAsync",
# https://github.com/tc39/proposal-import-attributes
"import-assertions",
# Non-standard # Non-standard
"caller", "caller",

Loading…
Cancel
Save