fix: preserve TCO through error values#818
Open
He-Pin wants to merge 1 commit intodatabricks:masterfrom
Open
Conversation
ba255b1 to
115eba9
Compare
Motivation: `error value` evaluates `value` immediately before throwing, so a tail-position self call there should not force nested trampoline resolution inside the current function body. The previous auto-TCO marker treated that value as tail position, but the evaluator still used normal `visitExpr`, leaving deep `error f(n - 1)` chains stack-sensitive. At the same time, `error value` must not be treated as an unconditional non-recursive exit: `local f() = error f()` has no base case and should keep the existing max-stack diagnostic instead of becoming an infinite trampoline loop. Modification: Add delayed TailCall result state for `error value` and fold it together with the existing delayed boolean validation. The result state is fixed-slot (`&&`/`||` validation plus optional error throw) rather than linked continuation nodes, so the trampoline preserves ordering without accumulating per-wrapper objects on deep recursive paths. Update auto-TCO exit analysis so `Expr.Error` recursively inspects its value expression instead of always counting as a non-recursive exit. Result: Auto-TCO and explicit `tailstrict` now remain stack-safe through `error value`, lazy arguments remain lazy, and nested boolean/error continuation ordering matches expression evaluation order. Directional golden tests cover the new error path, continuation ordering, a deep non-tail negative case, an `error value` expression with an internal base case, and a no-exit `error f()` case that must not be auto-TCOd. Risk: The main risk is continuation ordering and exit analysis: a boolean check inside an error message must run before the outer error throw, an inner error must preempt an outer boolean check, and no-exit recursive error expressions must not be converted into unbounded trampoline loops. The new tests lock down these orders and the no-exit guard. This is still scoped to the current auto-TCO protocol; broader proper-tail-call lowering remains a separate follow-up.
115eba9 to
6f1f2ee
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebased onto current
masterat7eb796f(chore(deps): bump actions/cache from 4.3.0 to 5.0.5 (#819)).Motivation
error valueevaluatesvalueimmediately before throwing. That value is a tail position for auto-TCO and explicittailstrict, but resolving aTailCallinside theerrorexpression would re-enter the trampoline from the current function body and can bring back recursive stack growth for deeperror f(n - 1)chains.error valuealso must not be treated as an unconditional non-recursive exit. A function likelocal f() = error f()has no base case; auto-TCOing it would turn the existing max-stack diagnostic into an infinite trampoline loop.Implementation
error valuewith tail-call support and attach a delayed error-value continuation to the returnedTailCall.&&/||validation plus optionalerror valuethrow.error valuepreempts any outer boolean check or outer error throwExpr.Errorrecursively inspects its value expression instead of always counting as a non-recursive exit.Tests
New golden tests cover:
tailstrictthrougherror valueerror f(n - 1, unused)chainerror valueexpression whose non-recursive exit is inside the error valueerror f()case that must keep the max-stack diagnosticVerification
./mill --no-server 'sjsonnet.jvm[3.3.7].compile'./mill --no-server 'sjsonnet.jvm[3.3.7].test.testOnly' sjsonnet.FileTests./mill --no-server 'sjsonnet.jvm[3.3.7].test.testOnly' sjsonnet.TailCallOptimizationTests./mill --no-server __.checkFormatgit diff --check