fix: JS backend scoping for let/match/if inside closures
Three related bugs fixed: - BUG-009: let bindings inside lambdas hoisted to top-level - BUG-011: match expressions inside lambdas hoisted to top-level - BUG-012: variable name deduplication leaked across function scopes Root cause: emit_expr() uses writeln() for statements, but lambdas captured only the return value, not the emitted statements. Also, var_substitutions from emit_function() leaked to subsequent code. Fix: Lambda handler now captures all output emitted during body evaluation and places it inside the function body. Both emit_function and Lambda save/restore var_substitutions to prevent cross-scope leaks. Lambda params are registered as identity substitutions to override any outer bindings with the same name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -888,7 +888,8 @@ impl JsBackend {
|
||||
let prev_has_handlers = self.has_handlers;
|
||||
self.has_handlers = is_effectful;
|
||||
|
||||
// Clear var substitutions for this function
|
||||
// Save and clear var substitutions for this function scope
|
||||
let saved_substitutions = self.var_substitutions.clone();
|
||||
self.var_substitutions.clear();
|
||||
|
||||
// Emit function body
|
||||
@@ -896,6 +897,7 @@ impl JsBackend {
|
||||
self.writeln(&format!("return {};", body_code));
|
||||
|
||||
self.has_handlers = prev_has_handlers;
|
||||
self.var_substitutions = saved_substitutions;
|
||||
|
||||
self.indent -= 1;
|
||||
self.writeln("}");
|
||||
@@ -1218,18 +1220,39 @@ impl JsBackend {
|
||||
param_names
|
||||
};
|
||||
|
||||
// Save handler state
|
||||
// Save state
|
||||
let prev_has_handlers = self.has_handlers;
|
||||
let saved_substitutions = self.var_substitutions.clone();
|
||||
self.has_handlers = !effects.is_empty();
|
||||
|
||||
// Register lambda params as themselves (override any outer substitutions)
|
||||
for p in &all_params {
|
||||
self.var_substitutions.insert(p.clone(), p.clone());
|
||||
}
|
||||
|
||||
// Capture any statements emitted during body evaluation
|
||||
let output_start = self.output.len();
|
||||
let prev_indent = self.indent;
|
||||
self.indent += 1;
|
||||
|
||||
let body_code = self.emit_expr(body)?;
|
||||
self.writeln(&format!("return {};", body_code));
|
||||
|
||||
// Extract body statements and restore output
|
||||
let body_statements = self.output[output_start..].to_string();
|
||||
self.output.truncate(output_start);
|
||||
self.indent = prev_indent;
|
||||
|
||||
// Restore state
|
||||
self.has_handlers = prev_has_handlers;
|
||||
self.var_substitutions = saved_substitutions;
|
||||
|
||||
let indent_str = " ".repeat(self.indent);
|
||||
Ok(format!(
|
||||
"(function({}) {{ return {}; }})",
|
||||
"(function({}) {{\n{}{}}})",
|
||||
all_params.join(", "),
|
||||
body_code
|
||||
body_statements,
|
||||
indent_str,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user