fix: unwrap non-recursive let bindings

This commit is contained in:
2026-01-14 21:33:18 +08:00
parent b6a6630a93
commit e676d2f9f4

View File

@@ -186,37 +186,45 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Call {
} }
} }
/// Determines if a Thunk should be kept (not unwrapped) for non-recursive let bindings.
/// Returns true for complex expressions that should remain lazy to preserve Nix semantics.
fn should_keep_thunk(ir: &Ir) -> bool {
match ir {
// Simple literals can be evaluated eagerly
Ir::Int(_) | Ir::Float(_) | Ir::Bool(_) | Ir::Null(_) | Ir::Str(_) => false,
// Builtin references are safe to evaluate eagerly
Ir::Builtin(_) | Ir::Builtins(_) => false,
Ir::ExprRef(_) => false,
_ => true,
}
}
fn unwrap_thunk(ir: &Ir, ctx: &impl CodegenContext) -> String {
if let Ir::Thunk(inner) = ir {
let inner_ir = ctx.get_ir(*inner);
if should_keep_thunk(inner_ir) {
ir.compile(ctx)
} else {
inner_ir.compile(ctx)
}
} else {
ir.compile(ctx)
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Let { impl<Ctx: CodegenContext> Compile<Ctx> for Let {
fn compile(&self, ctx: &Ctx) -> String { fn compile(&self, ctx: &Ctx) -> String {
let info = &self.binding_sccs; let info = &self.binding_sccs;
let mut js_statements = Vec::new(); let mut js_statements = Vec::new();
for (scc_exprs, is_recursive) in info.sccs.iter() { for (scc_exprs, is_recursive) in info.sccs.iter() {
if *is_recursive { for &expr in scc_exprs {
for &expr in scc_exprs { let value = if *is_recursive {
js_statements.push(format!("let expr{}", expr.0)); ctx.get_ir(expr).compile(ctx)
} } else {
for &expr in scc_exprs { unwrap_thunk(ctx.get_ir(expr), ctx)
let value = ctx.get_ir(expr).compile(ctx); };
js_statements.push(format!("expr{}={}", expr.0, value)); js_statements.push(format!("const expr{}={}", expr.0, value));
}
} else {
for &expr in scc_exprs {
let ir = ctx.get_ir(expr);
let value = if let Ir::Thunk(inner) = ir {
let inner_ir = ctx.get_ir(*inner);
// Don't unwrap Thunk if inner is a Let expression
// to avoid generating IIFE that executes immediately
if matches!(inner_ir, Ir::Let(_)) {
ir.compile(ctx)
} else {
inner_ir.compile(ctx)
}
} else {
ir.compile(ctx)
};
js_statements.push(format!("const expr{}={}", expr.0, value));
}
} }
} }