You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
flattenDeepBinds breaks up deeply-nested do/>>= chains that would otherwise exceed Lua 5.1's parser nesting cap (chunk has too many syntax levels). It recognises bind/discard steps and lambda-lifts the continuation chain into flat $kont helpers. #107 widened recognition to cover discard statement lines and monad-transformer binds, with goldens for Maybe, Either, State, Reader, Writer, Except, and a StateT/Except stack.
Recognition is name-anchored: it matches Control.Bind.bind and Control.Bind.discard by name, and a Bind dictionary by its bind and Apply0 fields. That is precise but incomplete. Two properties make a different design possible.
First, the transform does not depend on recognition for correctness. Lambda-lifting only relocates continuations into let-bound helpers and forwards live variables by name. The head and action are kept verbatim, and no bind/k call is reordered, dropped, or duplicated. Flattening an expression that is not a bind is therefore still semantics-preserving. Recognition decides which expressions get restructured, never whether the result is correct.
Second, NestingCheck is a backstop. Anything that is not flattened and still nests too deeply is rejected at compile time with a clear error, never emitted as unloadable Lua.
The limitation
Because recognition is name-anchored, a chain shape it does not explicitly know about silently fails to flatten and falls through to a NestingTooDeep compile error instead of working:
Applicative chains: ado/apply, and bindFlipped/=<<.
Any future dictionary encoding that does not match the bind + Apply0 literal-record shape.
A new monad whose bind head reduces through some form the normaliser does not handle. This is how the State transformer case was originally missed (see Flatten deeply-nested non-Effect/ST do-blocks #107).
The State case showed the failure is graceful (a compile error, not a miscompile) but real: a category of valid do blocks fails to compile where it could work.
Proposal
Replace name-anchored recognition with a structure-anchored breaker: flatten any sufficiently deep right-nested continuation chain f a (\x -> ...) (last argument a lambda), whether or not f is a known bind. This relies on the two properties above. False positives are semantically safe, and NestingCheck catches whatever still overflows.
It subsumes the name-gated cases and removes the fragile surface: hardcoded names, dictionary-field assumptions, and reduction-shape assumptions. This is the general Lua-AST-level nesting breaker the #107 scope referred to as separate work.
Tradeoffs and open questions
Golden churn. Any existing chain deeper than the threshold that is a right-nested lambda nest but not a bind would start flattening, changing its golden. The threshold (currently 50) bounds this, and in practice nests deeper than 50 are almost always binds. Needs a golden reset and a review of what actually moves.
Codegen quality. For genuinely non-bind deep nests, helper extraction may be less tidy than the original. Acceptable when the alternative is a compile error, but worth eyeballing the output.
Placement. This may fit better as a Lua-AST-level pass (truly monad-agnostic, and able to catch non-bind constructs like deep case trees or long <> chains) than as an IR pass. The IR-vs-Lua-AST choice is open.
Upvalue budget. The maxLiveSet bail and the per-segment upvalue cost still apply. A structure-anchored pass fires on more shapes, so the bail behaviour should be re-checked.
Acceptance
A deep right-nested non-bind continuation chain, plus the currently-unsupported ado/bindFlipped cases, compiles to Lua that loads and runs under stock Lua 5.1.
No regression in the existing monad goldens.
NestingCheck retained as the backstop.
Non-goals
Reworking NestingCheck itself.
Improving codegen quality of the flattened output beyond "loads and runs".
Follow-up to #104 (PR #107).
Background
flattenDeepBindsbreaks up deeply-nesteddo/>>=chains that would otherwise exceed Lua 5.1's parser nesting cap (chunk has too many syntax levels). It recognises bind/discard steps and lambda-lifts the continuation chain into flat$konthelpers. #107 widened recognition to coverdiscardstatement lines and monad-transformer binds, with goldens for Maybe, Either, State, Reader, Writer, Except, and aStateT/Exceptstack.Recognition is name-anchored: it matches
Control.Bind.bindandControl.Bind.discardby name, and aBinddictionary by itsbindandApply0fields. That is precise but incomplete. Two properties make a different design possible.First, the transform does not depend on recognition for correctness. Lambda-lifting only relocates continuations into
let-bound helpers and forwards live variables by name. The head and action are kept verbatim, and nobind/kcall is reordered, dropped, or duplicated. Flattening an expression that is not a bind is therefore still semantics-preserving. Recognition decides which expressions get restructured, never whether the result is correct.Second,
NestingCheckis a backstop. Anything that is not flattened and still nests too deeply is rejected at compile time with a clear error, never emitted as unloadable Lua.The limitation
Because recognition is name-anchored, a chain shape it does not explicitly know about silently fails to flatten and falls through to a
NestingTooDeepcompile error instead of working:ado/apply, andbindFlipped/=<<.bind+Apply0literal-record shape.The State case showed the failure is graceful (a compile error, not a miscompile) but real: a category of valid
doblocks fails to compile where it could work.Proposal
Replace name-anchored recognition with a structure-anchored breaker: flatten any sufficiently deep right-nested continuation chain
f a (\x -> ...)(last argument a lambda), whether or notfis a known bind. This relies on the two properties above. False positives are semantically safe, andNestingCheckcatches whatever still overflows.It subsumes the name-gated cases and removes the fragile surface: hardcoded names, dictionary-field assumptions, and reduction-shape assumptions. This is the general Lua-AST-level nesting breaker the #107 scope referred to as separate work.
Tradeoffs and open questions
Golden churn. Any existing chain deeper than the threshold that is a right-nested lambda nest but not a bind would start flattening, changing its golden. The threshold (currently 50) bounds this, and in practice nests deeper than 50 are almost always binds. Needs a golden reset and a review of what actually moves.
Codegen quality. For genuinely non-bind deep nests, helper extraction may be less tidy than the original. Acceptable when the alternative is a compile error, but worth eyeballing the output.
Placement. This may fit better as a Lua-AST-level pass (truly monad-agnostic, and able to catch non-
bindconstructs like deepcasetrees or long<>chains) than as an IR pass. The IR-vs-Lua-AST choice is open.Upvalue budget. The
maxLiveSetbail and the per-segment upvalue cost still apply. A structure-anchored pass fires on more shapes, so the bail behaviour should be re-checked.Acceptance
bindcontinuation chain, plus the currently-unsupportedado/bindFlippedcases, compiles to Lua that loads and runs under stock Lua 5.1.NestingCheckretained as the backstop.Non-goals
NestingCheckitself.