Skip to content
Closed
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
22 changes: 20 additions & 2 deletions core/ast/src/function/ordinary_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use crate::{
join_nodes,
operations::{ContainsSymbol, contains},
scope::{FunctionScopes, Scope},
scope_analyzer::{analyze_binding_escapes, collect_bindings},
scope_analyzer::{
analyze_binding_escapes, collect_bindings, optimize_scope_indices_with_root_function_scope,
},
visitor::{VisitWith, Visitor, VisitorMut},
};
use boa_interner::{Interner, ToIndentedString};
Expand Down Expand Up @@ -279,9 +281,25 @@ impl FunctionExpression {
strict: bool,
scope: &Scope,
interner: &Interner,
) -> Result<(), &'static str> {
self.analyze_scope_with_options(strict, scope, interner, false)
}

/// Analyze the scope of the function expression, with extra scope analysis options.
///
/// # Errors
/// Any scope or binding errors that happened during the analysis.
pub fn analyze_scope_with_options(
&mut self,
strict: bool,
scope: &Scope,
interner: &Interner,
force_function_scope: bool,
) -> Result<(), &'static str> {
collect_bindings(self, strict, false, scope, interner)?;
analyze_binding_escapes(self, false, scope.clone(), interner)
analyze_binding_escapes(self, false, scope.clone(), interner)?;
optimize_scope_indices_with_root_function_scope(self, scope, force_function_scope);
Ok(())
}
}

Expand Down
24 changes: 24 additions & 0 deletions core/ast/src/scope_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,15 +1223,31 @@ impl BindingCollectorVisitor<'_> {
pub(crate) fn optimize_scope_indices<'a, N>(node: &'a mut N, scope: &Scope)
where
&'a mut N: Into<NodeRefMut<'a>>,
{
optimize_scope_indices_with_root_function_scope(node, scope, false);
}

/// Optimize scope indices when scopes only contain local bindings, optionally forcing the
/// root function-like scope to be treated as requiring a function environment.
pub(crate) fn optimize_scope_indices_with_root_function_scope<'a, N>(
node: &'a mut N,
scope: &Scope,
force_root_function_scope: bool,
) where
&'a mut N: Into<NodeRefMut<'a>>,
{
let mut visitor = ScopeIndexVisitor {
index: scope.scope_index(),
force_root_function_scope,
root_function_scope_forced: false,
};
let _ = visitor.visit(node.into());
}

struct ScopeIndexVisitor {
index: u32,
force_root_function_scope: bool,
root_function_scope_forced: bool,
}

impl<'ast> VisitorMut<'ast> for ScopeIndexVisitor {
Expand Down Expand Up @@ -1658,6 +1674,14 @@ impl ScopeIndexVisitor {
scope.set_index(self.index);
}

let force_function_scope =
if !arrow && self.force_root_function_scope && !self.root_function_scope_forced {
self.root_function_scope_forced = true;
true
} else {
force_function_scope
};

if force_function_scope || !scopes.function_scope().all_bindings_local() {
scopes.requires_function_scope = true;
self.index += 1;
Expand Down
9 changes: 6 additions & 3 deletions core/engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,9 +647,12 @@ impl BuiltInFunctionObject {
false,
Span::new(function_span_start, function_span_end),
);
if let Err(reason) =
function.analyze_scope(strict, context.realm().scope(), context.interner())
{
if let Err(reason) = function.analyze_scope_with_options(
strict,
context.realm().scope(),
context.interner(),
true,
) {
return Err(js_error!(SyntaxError: "failed to analyze function scope: {}", reason));
}

Expand Down
36 changes: 36 additions & 0 deletions core/engine/src/builtins/function/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,39 @@ fn function_constructor_early_errors_super() {
),
]);
}

#[test]
fn function_constructor_nested_scope_indices_regression() {
run_test_actions([TestAction::assert_eq(
indoc! {r#"
(() => {
const compiledCode = `
const noop = () => {}
function Counter() {
const [v, setV] = [0, noop]

const onClick = () => {
setV(v => v + 1)
}

return null
}
return Counter()
`;

const React = {};
const f = new Function("React", compiledCode);
return f(React);
})()
"#},
JsValue::null(),
)]);
}

#[test]
fn function_constructor_nested_lexical_capture() {
run_test_actions([TestAction::assert_eq(
"Function(\"function f(){ const x = 1; return () => x; } return f()()\")()",
1,
)]);
}
Loading