Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement annexB Block-Level Function Declarations #2910

Merged
merged 2 commits into from
May 7, 2023
Merged
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
212 changes: 211 additions & 1 deletion boa_ast/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use boa_interner::{Interner, Sym};
use rustc_hash::{FxHashMap, FxHashSet};

use crate::{
declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration, Variable},
declaration::{Binding, ExportDeclaration, ImportDeclaration, VarDeclaration, Variable},
expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield},
function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
Expand Down Expand Up @@ -1663,3 +1663,213 @@ impl<'ast> Visitor<'ast> for TopLevelVarScopedDeclarationsVisitor<'_> {
}
}
}

/// Returns a list function declaration names that are directly contained in a statement lists
/// `Block`, `CaseClause` or `DefaultClause`.
/// If the function declaration would cause an early error it is not included in the list.
///
/// This behavior is used in the following annexB sections:
/// * [B.3.2.1 Changes to FunctionDeclarationInstantiation][spec0]
/// * [B.3.2.2 Changes to GlobalDeclarationInstantiation][spec1]
/// * [B.3.2.3 Changes to EvalDeclarationInstantiation][spec2]
///
/// [spec0]: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation
/// [spec1]: https://tc39.es/ecma262/#sec-web-compat-globaldeclarationinstantiation
/// [spec2]: https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation
#[must_use]
pub fn annex_b_function_declarations_names<'a, N>(node: &'a N) -> Vec<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
let mut declarations = Vec::new();
AnnexBFunctionDeclarationNamesVisitor(&mut declarations).visit(node.into());
declarations
}

/// The [`Visitor`] used for [`annex_b_function_declarations_names`].
#[derive(Debug)]
struct AnnexBFunctionDeclarationNamesVisitor<'a>(&'a mut Vec<Identifier>);

impl<'ast> Visitor<'ast> for AnnexBFunctionDeclarationNamesVisitor<'_> {
type BreakTy = Infallible;

fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
StatementListItem::Statement(node) => self.visit(node),
StatementListItem::Declaration(_) => ControlFlow::Continue(()),
}
}

fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node {
Statement::Block(node) => self.visit(node),
Statement::If(node) => self.visit(node),
Statement::DoWhileLoop(node) => self.visit(node),
Statement::WhileLoop(node) => self.visit(node),
Statement::ForLoop(node) => self.visit(node),
Statement::ForInLoop(node) => self.visit(node),
Statement::ForOfLoop(node) => self.visit(node),
Statement::Switch(node) => self.visit(node),
Statement::Labelled(node) => self.visit(node),
Statement::Try(node) => self.visit(node),
Statement::With(node) => self.visit(node),
_ => ControlFlow::Continue(()),
}
}

fn visit_block(&mut self, node: &'ast crate::statement::Block) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement_list());
for statement in node.statement_list().statements() {
if let StatementListItem::Declaration(Declaration::Function(function)) = statement {
let name = function
.name()
.expect("function declaration must have name");
self.0.push(name);
}
}

let lexically_declared_names = lexically_declared_names_legacy(node.statement_list());

self.0
.retain(|name| !lexically_declared_names.contains(&(*name, false)));

ControlFlow::Continue(())
}

fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow<Self::BreakTy> {
for case in node.cases() {
self.visit(case);
for statement in case.body().statements() {
if let StatementListItem::Declaration(Declaration::Function(function)) = statement {
let name = function
.name()
.expect("function declaration must have name");
self.0.push(name);
}
}
}
if let Some(default) = node.default() {
self.visit(default);
for statement in default.statements() {
if let StatementListItem::Declaration(Declaration::Function(function)) = statement {
let name = function
.name()
.expect("function declaration must have name");
self.0.push(name);
}
}
}

let lexically_declared_names = lexically_declared_names_legacy(node);

self.0
.retain(|name| !lexically_declared_names.contains(&(*name, false)));

ControlFlow::Continue(())
}

fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow<Self::BreakTy> {
self.visit(node.block());
if let Some(catch) = node.catch() {
self.visit(catch.block());

if let Some(Binding::Pattern(pattern)) = catch.parameter() {
let bound_names = bound_names(pattern);

self.0.retain(|name| !bound_names.contains(name));
}
}
if let Some(finally) = node.finally() {
self.visit(finally.block());
}
ControlFlow::Continue(())
}

fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow<Self::BreakTy> {
if let Some(node) = node.else_node() {
self.visit(node);
}
self.visit(node.body())
}

fn visit_do_while_loop(
&mut self,
node: &'ast crate::statement::DoWhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body())
}

fn visit_while_loop(
&mut self,
node: &'ast crate::statement::WhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body())
}

fn visit_for_loop(
&mut self,
node: &'ast crate::statement::ForLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());

if let Some(ForLoopInitializer::Lexical(node)) = node.init() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}

ControlFlow::Continue(())
}

fn visit_for_in_loop(
&mut self,
node: &'ast crate::statement::ForInLoop,
) -> ControlFlow<Self::BreakTy> {
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() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}

ControlFlow::Continue(())
}

fn visit_for_of_loop(
&mut self,
node: &'ast crate::statement::ForOfLoop,
) -> ControlFlow<Self::BreakTy> {
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() {
let bound_names = bound_names(node);
self.0.retain(|name| !bound_names.contains(name));
}

ControlFlow::Continue(())
}

fn visit_labelled(
&mut self,
node: &'ast crate::statement::Labelled,
) -> ControlFlow<Self::BreakTy> {
if let LabelledItem::Statement(node) = node.item() {
self.visit(node);
}
ControlFlow::Continue(())
}

fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement())
}
}
2 changes: 1 addition & 1 deletion boa_engine/src/builtins/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ impl Eval {
let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);

compiler.eval_declaration_instantiation(&body, strict)?;
compiler.compile_statement_list(&body, true);
compiler.compile_statement_list(&body, true, false);

let env_info = compiler.pop_compile_environment();
compiler.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);
Expand Down
2 changes: 1 addition & 1 deletion boa_engine/src/builtins/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl Json {
context.realm().environment().compile_env(),
context,
);
compiler.compile_statement_list(&statement_list, true);
compiler.compile_statement_list(&statement_list, true, false);
Gc::new(compiler.finish())
};
let unfiltered = context.execute(code_block)?;
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/bytecompiler/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl ByteCompiler<'_, '_> {
false,
);

compiler.compile_statement_list(expr.body(), false);
compiler.compile_statement_list(expr.body(), false, false);

let env_info = compiler.pop_compile_environment();

Expand Down Expand Up @@ -374,7 +374,7 @@ impl ByteCompiler<'_, '_> {
false,
);

compiler.compile_statement_list(statement_list, false);
compiler.compile_statement_list(statement_list, false, false);
let env_info = compiler.pop_compile_environment();
compiler.pop_compile_environment();
compiler.num_bindings = env_info.num_bindings;
Expand Down
Loading