Carved out of #14 (subtleties 1 & 3) so #14 can close on its answered question (root cause is not checker allocation complexity — confirmed Linux + Windows CI).
The subtlety
The #14 investigation proved check_expr/is_assignable_from are linear, so MAX_EXPR_DEPTH = 256 is not a memory safeguard — it bounds stack recursion. Two real, still-open consequences:
- Recursive
Drop of a deep AST overflows the stack. The auto-derived Drop for the Box<Expr> chain recurses once per nesting level. This is independent of the checker (it happens even if checking is skipped) and is currently only worked around by a shape-specific test helper (drop_program_iteratively in crates/my-lang/src/checker.rs, which only handles 2-arg Call chains). A real input shaped differently would still overflow on drop.
MAX_EXPR_DEPTH = 256 is now unjustified at that specific value. It was chosen as an OOM defence that doesn't exist. It rejects deep-but-legal programs; the value should be re-derived from the actual stack budget of the deepest recursive walk (checker recursion and Drop), not from the disproven memory argument.
Why this is not a quick PR
Per #14's own "no speculative fix without measurement" principle: a general iterative Drop for the whole AST is a non-trivial, risky core change. It should be measured first (actual stack frame size per check_expr / per Drop level on the target platforms) and then either: a generalized iterative drop, a Box-arena / flattened AST, or an explicit recursion limit tied to a measured budget.
Definition of done
Carved out of #14 (subtleties 1 & 3) so #14 can close on its answered question (root cause is not checker allocation complexity — confirmed Linux + Windows CI).
The subtlety
The #14 investigation proved
check_expr/is_assignable_fromare linear, soMAX_EXPR_DEPTH = 256is not a memory safeguard — it bounds stack recursion. Two real, still-open consequences:Dropof a deep AST overflows the stack. The auto-derivedDropfor theBox<Expr>chain recurses once per nesting level. This is independent of the checker (it happens even if checking is skipped) and is currently only worked around by a shape-specific test helper (drop_program_iterativelyincrates/my-lang/src/checker.rs, which only handles 2-argCallchains). A real input shaped differently would still overflow on drop.MAX_EXPR_DEPTH = 256is now unjustified at that specific value. It was chosen as an OOM defence that doesn't exist. It rejects deep-but-legal programs; the value should be re-derived from the actual stack budget of the deepest recursive walk (checker recursion and Drop), not from the disproven memory argument.Why this is not a quick PR
Per #14's own "no speculative fix without measurement" principle: a general iterative
Dropfor the whole AST is a non-trivial, risky core change. It should be measured first (actual stack frame size percheck_expr/ perDroplevel on the target platforms) and then either: a generalized iterative drop, aBox-arena / flattened AST, or an explicit recursion limit tied to a measured budget.Definition of done
check_exprand ASTDrop(Linux + the Windows CI toolchain).MAX_EXPR_DEPTHfrom the measured budget (or remove it if the structural fix makes it unnecessary), updating the doc-comment reframed in docs(checker): reframe MAX_EXPR_DEPTH as a stack-recursion bound (closes #14) #36.Call-shaped AST.