From 99c5258fea44bfd501dd6ca21b27c875d7ea55c6 Mon Sep 17 00:00:00 2001 From: Deepak-negi11 Date: Sat, 21 Feb 2026 11:24:23 +0530 Subject: [PATCH 1/2] Fix panic in Function constructor with nested lexical bindings (#4531) Added optimize_scope_indices_function_constructor() to account for force_function_scope=true in the Function constructor's scope analysis. Closes #4531 --- core/ast/src/function/ordinary_function.rs | 6 ++++-- core/ast/src/scope_analyzer.rs | 24 ++++++++++++++++++++++ core/engine/src/tests/function.rs | 20 ++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/core/ast/src/function/ordinary_function.rs b/core/ast/src/function/ordinary_function.rs index 76701afabe8..4c376e8ef91 100644 --- a/core/ast/src/function/ordinary_function.rs +++ b/core/ast/src/function/ordinary_function.rs @@ -5,7 +5,7 @@ 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_function_constructor}, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString}; @@ -281,7 +281,9 @@ impl FunctionExpression { interner: &Interner, ) -> 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_function_constructor(self, scope); + Ok(()) } } diff --git a/core/ast/src/scope_analyzer.rs b/core/ast/src/scope_analyzer.rs index 56875874ae9..dbed6dbf3a3 100644 --- a/core/ast/src/scope_analyzer.rs +++ b/core/ast/src/scope_analyzer.rs @@ -1230,6 +1230,30 @@ where let _ = visitor.visit(node.into()); } +/// Like [`optimize_scope_indices`] but for a [`FunctionExpression`] compiled +/// by the `Function` constructor with `force_function_scope = true`. +/// +/// The `Function` constructor always pushes a function scope at runtime, so +/// the optimizer must account for that even when the function scope would +/// otherwise be elided. +pub(crate) fn optimize_scope_indices_function_constructor( + node: &mut FunctionExpression, + scope: &Scope, +) { + let mut visitor = ScopeIndexVisitor { + index: scope.scope_index(), + }; + let _ = visitor.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut node.name_scope, + false, + // Always force the function scope for the Function constructor. + true, + ); +} + struct ScopeIndexVisitor { index: u32, } diff --git a/core/engine/src/tests/function.rs b/core/engine/src/tests/function.rs index a076d952b6c..dedfdbd050b 100644 --- a/core/engine/src/tests/function.rs +++ b/core/engine/src/tests/function.rs @@ -178,3 +178,23 @@ fn eval_out_of_scope() { TestAction::assert_eq("f()", JsValue::null()), ]); } + +/// Regression test for issue #4531. +/// `Function` constructor with nested function containing lexical bindings +/// captured by a closure should not panic with "must be declarative environment". +#[test] +fn function_constructor_nested_lexical_binding() { + run_test_actions([TestAction::assert_eq( + indoc! {r#" + var code = "\ + function f() {\ + const a = 42;\ + return () => { return a; };\ + }\ + return f()();\ + "; + Function(code)(); + "#}, + 42, + )]); +} From 241b9aaee739b075a66849b43ad5ac1b44b2c5b8 Mon Sep 17 00:00:00 2001 From: Deepak-negi-11 Date: Wed, 25 Feb 2026 02:22:04 +0530 Subject: [PATCH 2/2] style: cargo fmt --- core/ast/src/function/ordinary_function.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/ast/src/function/ordinary_function.rs b/core/ast/src/function/ordinary_function.rs index 4c376e8ef91..09f07e1358c 100644 --- a/core/ast/src/function/ordinary_function.rs +++ b/core/ast/src/function/ordinary_function.rs @@ -5,7 +5,9 @@ use crate::{ join_nodes, operations::{ContainsSymbol, contains}, scope::{FunctionScopes, Scope}, - scope_analyzer::{analyze_binding_escapes, collect_bindings, optimize_scope_indices_function_constructor}, + scope_analyzer::{ + analyze_binding_escapes, collect_bindings, optimize_scope_indices_function_constructor, + }, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString};