Bug
jac format on a lambda with multiple body statements concatenates the statements with no whitespace, producing broken output like { return undefined; }interval = setInterval(...);return lambda { clearInterval(interval);}; }.
This is a pre-existing bug in exit_lambda_expr — it predates the JSX/lambda formatter fixes in #5519. Reproducible on main as well as on the branch.
Repro
def main -> None {
useEffect(
lambda -> any {
if not autoRefresh { return undefined; }
interval = setInterval(lambda { loadMetrics(); }, 3000);
return lambda { clearInterval(interval); };
},
[autoRefresh]
);
}
Expected (something like)
def main -> None {
useEffect(
lambda -> any {
if not autoRefresh {
return undefined;
}
interval = setInterval(lambda { loadMetrics(); }, 3000);
return lambda { clearInterval(interval); };
},
[autoRefresh]
);
}
Actual (what jac format produces today)
def main -> None {
useEffect(
lambda -> any { if not autoRefresh { return undefined; }interval = setInterval(
lambda { loadMetrics();}, 3000
);return lambda { clearInterval(interval);}; },
[autoRefresh]
);
}
Notice }interval and );return and ;}; all collapsed together with no separator.
Real-world hit
Surfaced while formatting jac-scale/jac_scale/admin/ui/pages/admin/monitoring/MetricsPage.cl.jac for the downstream reformat pass in #5519. Line 42 reads );return lambda { clearInterval(interval);}; }, after format, which is obviously wrong.
Root cause
exit_lambda_expr in jac/jaclang/compiler/passes/tool/impl/doc_ir_gen_pass.impl.jac iterates nd.kid and handles tokens, single-expression bodies, and the function signature explicitly, but when the body is a Sequence[CodeBlockStmt] (multi-statement lambda body), each statement kid falls through to:
} else {
parts.append(i.gen.doc_ir);
}
No hard_line() separators are inserted between body statements and no indent wraps the body, so the statements run together. Every other statement-body node in the formatter (exit_if_stmt, exit_ability, function bodies, etc.) uses the pattern:
body_parts: list[doc.DocType] = [self.hard_line()];
prev_body_item: (uni.UniNode | None) = None;
for i in nd.kid {
if (isinstance(nd.body, Sequence) and self.is_within(i, nd.body)) {
if (i == nd.body[0]) {
parts.append(self.indent(self.concat(body_parts), ast_node=nd));
parts.append(self.hard_line());
}
self.add_body_stmt_with_spacing(body_parts, i, prev_body_item);
prev_body_item = i;
} elif ... {
exit_lambda_expr should grow the same branch gated on isinstance(nd.body, Sequence) and not isinstance(nd.body, uni.Expr), and drop the trailing hard-line on body_parts at the end so the closing } lands at the outer indent level.
What I tried
A draft fix that added the statement-body branch above to exit_lambda_expr produced correctly-separated output for the repro case and didn't regress the single-expression-body path (lambda x: int : x * 2) or the sole-statement path (lambda e: T { if cond { stmt; } } as in main.jac). Deliberately left out of #5519 to keep that PR scoped to JSX; the fix belongs in its own PR with dedicated regression tests covering:
- Multi-statement lambda body with
if/assignment/return
- Single-statement lambda body (must still work flat inside a JSX attribute — the main.jac case)
- Single-expression lambda body (
lambda x: int : x * 2 must stay untouched)
- Nested lambdas inside multi-statement lambda bodies
Related
Bug
jac formaton a lambda with multiple body statements concatenates the statements with no whitespace, producing broken output like{ return undefined; }interval = setInterval(...);return lambda { clearInterval(interval);}; }.This is a pre-existing bug in
exit_lambda_expr— it predates the JSX/lambda formatter fixes in #5519. Reproducible onmainas well as on the branch.Repro
Expected (something like)
Actual (what
jac formatproduces today)Notice
}intervaland);returnand;};all collapsed together with no separator.Real-world hit
Surfaced while formatting
jac-scale/jac_scale/admin/ui/pages/admin/monitoring/MetricsPage.cl.jacfor the downstream reformat pass in #5519. Line 42 reads);return lambda { clearInterval(interval);}; },after format, which is obviously wrong.Root cause
exit_lambda_exprinjac/jaclang/compiler/passes/tool/impl/doc_ir_gen_pass.impl.jaciteratesnd.kidand handles tokens, single-expression bodies, and the function signature explicitly, but when the body is aSequence[CodeBlockStmt](multi-statement lambda body), each statement kid falls through to:} else { parts.append(i.gen.doc_ir); }No
hard_line()separators are inserted between body statements and no indent wraps the body, so the statements run together. Every other statement-body node in the formatter (exit_if_stmt,exit_ability, function bodies, etc.) uses the pattern:exit_lambda_exprshould grow the same branch gated onisinstance(nd.body, Sequence) and not isinstance(nd.body, uni.Expr), and drop the trailing hard-line onbody_partsat the end so the closing}lands at the outer indent level.What I tried
A draft fix that added the statement-body branch above to
exit_lambda_exprproduced correctly-separated output for the repro case and didn't regress the single-expression-body path (lambda x: int : x * 2) or the sole-statement path (lambda e: T { if cond { stmt; } }as inmain.jac). Deliberately left out of #5519 to keep that PR scoped to JSX; the fix belongs in its own PR with dedicated regression tests covering:if/assignment/returnlambda x: int : x * 2must stay untouched)Related