Problem
/Unnest parses an inner LAMBDA(...) as an ordinary function call and descends into its body, hoisting nested sub-expressions up into the top-level LET. Any step that references a LAMBDA parameter then escapes the scope where that parameter is bound, producing a broken formula.
Example
=ROUND(MIN(BYROW(HSTACK(IFS(SEQUENCE(6), "Vienna"), PERMUTATIONS(XLOOKUP(G141:I141,t[Concat], t[City]),3)), LAMBDA(r, SUM(PAIROP(r, LAMBDA(a,b, SQRT(SUMSQ(PAIROP(XV(VSTACK(a,b), t[[City]:[Y-Coordinates]], {2,3}),,1)))*100),,1)))))+45,)
/Unnest pulls VSTACK(a, b) (and other inner nodes) out into its own top-level step, but a and b are only bound inside LAMBDA(a, b, …). Hoisted above the lambda they are unbound, so the synthesised LET does not evaluate.
The same bug class applies to a nested LET(...) used as a sub-expression (e.g. =SUM(LET(x, A1, x+1))) — LET and LAMBDA are the two name-binding constructs and both introduce scope the hoister must respect. (Top-level LET is already refused, so only nested LETs reach this path.)
Fix (Option 1 — opaque LAMBDA/LET)
Treat a LAMBDA(...) (and a nested LET(...)) as opaque in the decomposition engine: never descend into its body and never extract it as a step — it stays inline in its parent's RHS exactly as written. Everything outside lambdas decomposes as before.
For the example above, the outer nest (ROUND / MIN / BYROW / HSTACK / IFS / SEQUENCE / PERMUTATIONS / XLOOKUP, and +45) still decomposes; the two lambdas stay intact.
This is the minimal correctness fix — right now /Unnest silently emits broken output whenever a lambda is present. Step-level decomposition inside lambdas (via a nested LET in the lambda body) is tracked separately as a future enhancement.
Acceptance criteria
Engine-level change (originally #271 territory), independent of the dialog (#272).
Problem
/Unnestparses an innerLAMBDA(...)as an ordinary function call and descends into its body, hoisting nested sub-expressions up into the top-levelLET. Any step that references a LAMBDA parameter then escapes the scope where that parameter is bound, producing a broken formula.Example
/UnnestpullsVSTACK(a, b)(and other inner nodes) out into its own top-level step, butaandbare only bound insideLAMBDA(a, b, …). Hoisted above the lambda they are unbound, so the synthesised LET does not evaluate.The same bug class applies to a nested
LET(...)used as a sub-expression (e.g.=SUM(LET(x, A1, x+1))) —LETandLAMBDAare the two name-binding constructs and both introduce scope the hoister must respect. (Top-level LET is already refused, so only nested LETs reach this path.)Fix (Option 1 — opaque LAMBDA/LET)
Treat a
LAMBDA(...)(and a nestedLET(...)) as opaque in the decomposition engine: never descend into its body and never extract it as a step — it stays inline in its parent's RHS exactly as written. Everything outside lambdas decomposes as before.For the example above, the outer nest (
ROUND / MIN / BYROW / HSTACK / IFS / SEQUENCE / PERMUTATIONS / XLOOKUP, and+45) still decomposes; the two lambdas stay intact.This is the minimal correctness fix — right now
/Unnestsilently emits broken output whenever a lambda is present. Step-level decomposition inside lambdas (via a nested LET in the lambda body) is tracked separately as a future enhancement.Acceptance criteria
LAMBDA(...)body, and never emits aLAMBDA(...)node as a step — it stays inline in its parent.LET(...)sub-expression is treated the same way (opaque; not descended into).LetParser; no step references a lambda parameter.Engine-level change (originally #271 territory), independent of the dialog (#272).