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. 138
      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. 58
      boa_parser/src/parser/statement/declaration/import.rs
  18. 2
      test_ignore.toml

20
.vscode/launch.json vendored

@ -7,7 +7,7 @@
{
"type": "lldb",
"request": "launch",
"name": "Launch",
"name": "Launch (Script)",
"windows": {
"program": "${workspaceFolder}/target/debug/boa.exe"
},
@ -15,6 +15,24 @@
"args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"],
"sourceLanguages": ["rust"],
"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",
"boa_interner",
"boa_macros",
"indexmap",
"num-bigint",
"rustc-hash",
"serde",

1
boa_ast/Cargo.toml

@ -22,3 +22,4 @@ bitflags = "2.3.1"
num-bigint = "0.4.3"
serde = { version = "1.0.163", 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 {
alias: Sym,
private_name: Sym,
string_literal: bool,
}
impl ExportSpecifier {
/// Creates a new [`ExportSpecifier`].
#[inline]
#[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 {
alias,
private_name,
string_literal,
}
}
@ -200,6 +202,13 @@ impl ExportSpecifier {
pub const fn private_name(self) -> Sym {
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 {

11
boa_ast/src/module_item_list/mod.rs

@ -5,10 +5,11 @@
//!
//! [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 rustc_hash::FxHashSet;
use indexmap::IndexSet;
use rustc_hash::{FxHashSet, FxHasher};
use crate::{
declaration::{
@ -205,9 +206,9 @@ impl ModuleItemList {
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-modulerequests
#[inline]
#[must_use]
pub fn requests(&self) -> FxHashSet<Sym> {
pub fn requests(&self) -> IndexSet<Sym, BuildHasherDefault<FxHasher>> {
#[derive(Debug)]
struct RequestsVisitor<'vec>(&'vec mut FxHashSet<Sym>);
struct RequestsVisitor<'vec>(&'vec mut IndexSet<Sym, BuildHasherDefault<FxHasher>>);
impl<'ast> Visitor<'ast> for RequestsVisitor<'_> {
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);

10
boa_engine/src/bytecompiler/declarations.rs

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

2
boa_engine/src/bytecompiler/env.rs

@ -88,7 +88,7 @@ impl ByteCompiler<'_, '_> {
.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(
&self,
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::Function(function) => {
self.function(function.into(), NodeKind::Expression, use_expr);
self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
}
Expression::ArrowFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr);
self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
}
Expression::AsyncArrowFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr);
self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
}
Expression::Generator(function) => {
self.function(function.into(), NodeKind::Expression, use_expr);
self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
}
Expression::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr);
self.function_with_binding(function.into(), NodeKind::Expression, use_expr);
}
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::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.
#[derive(Debug, Clone, Copy, PartialEq)]
enum FunctionKind {
pub(crate) enum FunctionKind {
Ordinary,
Arrow,
AsyncArrow,
@ -57,37 +57,31 @@ enum FunctionKind {
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.
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(single_use_lifetimes)]
pub(crate) struct FunctionSpec<'a> {
kind: FunctionKind,
pub(crate) kind: FunctionKind,
pub(crate) name: Option<Identifier>,
parameters: &'a FormalParameterList,
body: &'a FunctionBody,
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> {
fn from(function: &'a Function) -> Self {
FunctionSpec {
@ -1070,17 +1064,13 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
}
}
/// Compile a function AST Node into bytecode.
pub(crate) fn function(
&mut self,
function: FunctionSpec<'_>,
node_kind: NodeKind,
use_expr: bool,
) {
/// Compiles a function AST Node into bytecode, and returns its index into
/// the `functions` array.
pub(crate) fn function(&mut self, function: FunctionSpec<'_>) -> u32 {
let (generator, r#async, arrow) = (
function.is_generator(),
function.is_async(),
function.is_arrow(),
function.kind.is_generator(),
function.kind.is_async(),
function.kind.is_arrow(),
);
let FunctionSpec {
name,
@ -1117,6 +1107,26 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
let index = self.functions.len() as u32;
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 {
self.emit(Opcode::GetGeneratorAsync, &[index]);
} else if generator {
@ -1152,9 +1162,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile an object method AST Node into bytecode.
pub(crate) fn object_method(&mut self, function: FunctionSpec<'_>) {
let (generator, r#async, arrow) = (
function.is_generator(),
function.is_async(),
function.is_arrow(),
function.kind.is_generator(),
function.kind.is_async(),
function.kind.is_arrow(),
);
let FunctionSpec {
name,
@ -1212,9 +1222,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile a class method AST Node into bytecode.
fn method(&mut self, function: FunctionSpec<'_>, class_name: Sym) {
let (generator, r#async, arrow) = (
function.is_generator(),
function.is_async(),
function.is_arrow(),
function.kind.is_generator(),
function.kind.is_async(),
function.kind.is_arrow(),
);
let FunctionSpec {
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_interner::Sym;
@ -55,6 +55,18 @@ impl ByteCompiler<'_, '_> {
let name = Identifier::from(Sym::DEFAULT_EXPORT);
self.create_mutable_binding(name, false);
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);
}
}

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

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

138
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::{
declaration::{
@ -12,16 +17,20 @@ use boa_ast::{
};
use boa_gc::{custom_trace, empty_trace, Finalize, Gc, GcRefCell, Trace};
use boa_interner::Sym;
use rustc_hash::{FxHashMap, FxHashSet};
use indexmap::IndexSet;
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use crate::{
builtins::{promise::PromiseCapability, Promise},
bytecompiler::{ByteCompiler, NodeKind},
bytecompiler::{ByteCompiler, FunctionSpec},
environments::{BindingLocator, CompileTimeEnvironment, EnvironmentStack},
module::ModuleKind,
object::{FunctionObjectBuilder, JsPromise, RecursionLimiter},
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,
};
@ -273,7 +282,7 @@ struct Inner {
#[derive(Debug)]
struct ModuleCode {
has_tla: bool,
requested_modules: FxHashSet<Sym>,
requested_modules: IndexSet<Sym, BuildHasherDefault<FxHasher>>,
source: boa_ast::Module,
import_entries: Vec<ImportEntry>,
local_export_entries: Vec<LocalExportEntry>,
@ -1403,7 +1412,7 @@ impl SourceTextModule {
ByteCompiler::new(Sym::MAIN, true, false, module_compile_env.clone(), context);
let mut imports = Vec::new();
let codeblock = {
let (codeblock, functions) = {
// 7. For each ImportEntry Record in of module.[[ImportEntries]], do
for entry in &self.inner.code.import_entries {
// a. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]).
@ -1448,7 +1457,7 @@ impl SourceTextModule {
// deferred to initialization below
imports.push(ImportBinding::Namespace {
locator,
module: imported_module,
module: resolution.module,
});
}
} else {
@ -1494,47 +1503,87 @@ impl SourceTextModule {
// 22. Let lexDeclarations be the LexicallyScopedDeclarations of code.
// 23. Let privateEnv be null.
let lex_declarations = lexically_scoped_declarations(&self.inner.code.source);
let mut functions = Vec::new();
// 24. For each element d of lexDeclarations, do
for declaration in &lex_declarations {
// i. If IsConstantDeclaration of d is true, then
if let LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(
decl,
)) = declaration
{
// a. For each element dn of the BoundNames of d, do
for name in bound_names(decl) {
// 1. Perform ! env.CreateImmutableBinding(dn, true).
compiler.create_immutable_binding(name, true);
}
} else {
// ii. Else,
// a. For each element dn of the BoundNames of d, do
for name in declaration.bound_names() {
// 1. Perform ! env.CreateMutableBinding(dn, false).
compiler.create_mutable_binding(name, false);
}
}
// 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).
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,
//
// 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
LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Const(
c,
)) => {
// a. For each element dn of the BoundNames of d, do
for name in bound_names(c) {
// 1. Perform ! env.CreateImmutableBinding(dn, true).
compiler.create_immutable_binding(name, true);
}
continue;
}
LexicallyScopedDeclaration::LexicalDeclaration(LexicalDeclaration::Let(l)) => {
for name in bound_names(l) {
compiler.create_mutable_binding(name, false);
}
continue;
}
LexicallyScopedDeclaration::AssignmentExpression(expr) => {
for name in bound_names(expr) {
compiler.create_mutable_binding(name, false);
}
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());
Gc::new(compiler.finish())
(Gc::new(compiler.finish()), functions)
};
// 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.
std::mem::swap(&mut context.vm.environments, &mut envs);
context.vm.stack = stack;

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

@ -1,6 +1,7 @@
use std::collections::HashSet;
use crate::{
js_string,
module::BindingName,
object::{JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey},
@ -88,8 +89,8 @@ fn module_namespace_exotic_get_own_property(
// 1. If P is a Symbol, return OrdinaryGetOwnProperty(O, P).
let key = match key {
PropertyKey::Symbol(_) => return ordinary_get_own_property(obj, key, context),
PropertyKey::Index(_) => return Ok(None),
PropertyKey::String(s) => s,
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
{
@ -101,13 +102,13 @@ fn module_namespace_exotic_get_own_property(
let exports = obj.exports();
// 3. If exports does not contain P, return undefined.
if !exports.contains_key(key) {
if !exports.contains_key(&key) {
return Ok(None);
}
}
// 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 }.
Ok(Some(
@ -168,8 +169,8 @@ fn module_namespace_exotic_has_property(
// 1. If P is a Symbol, return ! OrdinaryHasProperty(O, P).
let key = match key {
PropertyKey::Symbol(_) => return ordinary_has_property(obj, key, context),
PropertyKey::Index(_) => return Ok(false),
PropertyKey::String(s) => s,
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
let obj = obj.borrow();
@ -182,7 +183,7 @@ fn module_namespace_exotic_has_property(
// 3. If exports contains P, return true.
// 4. Return false.
Ok(exports.contains_key(key))
Ok(exports.contains_key(&key))
}
/// [`[[Get]] ( P, Receiver )`][spec]
@ -198,8 +199,8 @@ fn module_namespace_exotic_get(
// a. Return ! OrdinaryGet(O, P, Receiver).
let key = match key {
PropertyKey::Symbol(_) => return ordinary_get(obj, key, receiver, context),
PropertyKey::Index(_) => return Ok(JsValue::undefined()),
PropertyKey::String(s) => s,
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
let obj = obj.borrow();
@ -210,7 +211,7 @@ fn module_namespace_exotic_get(
// 2. Let exports be O.[[Exports]].
let exports = obj.exports();
// 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());
};
@ -285,8 +286,8 @@ fn module_namespace_exotic_delete(
// a. Return ! OrdinaryDelete(O, P).
let key = match key {
PropertyKey::Symbol(_) => return ordinary_delete(obj, key, context),
PropertyKey::Index(_) => return Ok(true),
PropertyKey::String(s) => s,
PropertyKey::Index(idx) => js_string!(format!("{idx}")),
PropertyKey::String(s) => s.clone(),
};
let obj = obj.borrow();
@ -299,7 +300,7 @@ fn module_namespace_exotic_delete(
// 3. If exports contains P, return false.
// 4. Return true.
Ok(!exports.contains_key(key))
Ok(!exports.contains_key(&key))
}
/// [`[[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 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 eval = 10;", "unexpected token 'eval', 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 eval = 10;", "binding identifier `eval` 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 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())
.utf8()
.expect("keyword must be utf-8");
Err(Error::unexpected(
name,
span,
Err(Error::general(
format!("binding identifier `{name}` not allowed in strict mode"),
span.start(),
))
}
Sym::YIELD if self.allow_yield.0 => Err(Error::unexpected(
"yield",
span,
Sym::YIELD if self.allow_yield.0 => Err(Error::general(
"keyword `yield` not allowed in this context",
span.start(),
)),
Sym::AWAIT if self.allow_await.0 => Err(Error::unexpected(
"await",
span,
Sym::AWAIT if self.allow_await.0 => Err(Error::general(
"keyword `await` not allowed in this context",
span.start(),
)),
_ => Ok(ident),
}

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

@ -52,6 +52,7 @@ where
cursor.expect((Keyword::Export, false), "export declaration", interner)?;
let tok = cursor.peek(0, interner).or_abrupt()?;
let span = tok.span();
let export_clause: Self::Output = match tok.kind() {
TokenKind::Punctuator(Punctuator::Mul) => {
@ -130,6 +131,33 @@ where
} else {
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)
}
}
@ -272,7 +300,7 @@ impl<R> TokenParser<R> for ModuleExportName
where
R: Read,
{
type Output = Sym;
type Output = (Sym, bool);
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let tok = cursor.next(interner).or_abrupt()?;
@ -285,10 +313,10 @@ where
tok.span().end(),
));
}
Ok(*ident)
Ok((*ident, true))
}
TokenKind::IdentifierName((ident, _)) => Ok(*ident),
TokenKind::Keyword((kw, _)) => Ok(kw.to_sym()),
TokenKind::IdentifierName((ident, _)) => Ok((*ident, false)),
TokenKind::Keyword((kw, _)) => Ok((kw.to_sym(), false)),
_ => Err(Error::expected(
["identifier".to_owned(), "string literal".to_owned()],
tok.to_string(interner),
@ -315,20 +343,23 @@ where
type Output = boa_ast::declaration::ExportSpecifier;
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
.next_if(TokenKind::identifier(Sym::AS), interner)?
.is_some()
{
let export_name = ModuleExportName.parse(cursor, interner)?;
let (export_name, _) = ModuleExportName.parse(cursor, interner)?;
Ok(boa_ast::declaration::ExportSpecifier::new(
export_name,
inner_name,
string_literal,
))
} else {
Ok(boa_ast::declaration::ExportSpecifier::new(
inner_name, inner_name,
inner_name,
inner_name,
string_literal,
))
}
}

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

@ -58,9 +58,7 @@ impl ImportDeclaration {
TokenKind::StringLiteral(_)
| TokenKind::Punctuator(Punctuator::OpenBlock | Punctuator::Mul)
| TokenKind::IdentifierName(_)
| TokenKind::Keyword((Keyword::Await | Keyword::Yield, _)) => {
return Ok(true)
}
| TokenKind::Keyword(_) => return Ok(true),
_ => {}
}
}
@ -225,7 +223,9 @@ where
}
cursor.advance(interner);
}
TokenKind::StringLiteral(_) | TokenKind::IdentifierName(_) => {
TokenKind::StringLiteral(_)
| TokenKind::IdentifierName(_)
| TokenKind::Keyword(_) => {
list.push(ImportSpecifier.parse(cursor, interner)?);
}
_ => {
@ -293,16 +293,20 @@ where
type Output = AstImportSpecifier;
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() {
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(
"import specifiers don't allow unpaired surrogates",
tok.span().end(),
));
}
cursor.advance(interner);
cursor.expect(
TokenKind::identifier(Sym::AS),
"import declaration",
@ -311,18 +315,42 @@ where
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, _)) => {
if cursor
.next_if(TokenKind::identifier(Sym::AS), interner)?
.is_some()
{
let binding = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(binding, *name))
} else {
Ok(AstImportSpecifier::new(Identifier::new(*name), *name))
let name = *name;
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)?;
return Ok(AstImportSpecifier::new(binding, name));
}
}
let name = ImportedBinding.parse(cursor, interner)?;
Ok(AstImportSpecifier::new(name, name.sym()))
}
_ => Err(Error::expected(
["string literal".to_owned(), "identifier".to_owned()],

2
test_ignore.toml

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

Loading…
Cancel
Save