Repository navigation
fix(types): check no longer stack-overflows on functions returning T[k] - #74
Open
JonathanTurnock wants to merge 2 commits into
Open
JonathanTurnock wants to merge 2 commits into
JonathanTurnock wants to merge 2 commits into
Conversation
…ial returns A function stored in a table that returns a dynamic index into that table (`function S.f(k) return S[k] end`) has itself among its returns: `S[k]` widens to every value of `S`, `f` included. `reify_func` had no cycle guard (unlike `reify_shape`), so reifying `f` recursed until the stack overflowed and aborted the whole `luabox check` run. Seen on real code: Penlight `pl/data.lua`, lazy.nvim `core/plugin.lua` and `manage/semver.lua`. A function reached again through its own returns now reifies with no returns, the way a shape cycle cuts off at the catch-all table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cycle guard alone left the acyclic case exponential: n sibling functions that each return `S[k]` reach one another in every order, so `check` took 5.2 s at 8 siblings and grew factorially. A depth counter replaces the stack and bounds both cases: a function's returns are spelled out only at the outermost level (n=30: 1.2 s -> 0.12 s; n=100: 2.7 s). Trade-off: a returned closure's own returns reify as unknown, so a factory hovers as `fun(): fun()` rather than `fun(): fun(): integer`. Call checking walks the inference graph, not reified types, and is unaffected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
luabox checkaborted the whole run with a stack overflow on ordinary Lua. Seen on Penlight (pl/data.lua) and lazy.nvim (lua/lazy/core/plugin.lua,lua/lazy/manage/semver.lua). Minimal repro:S[key]widens to every value ofS, andfis one of them, sof's inferred returns containfitself.reify_funchad no cycle guard (unlikereify_shape), so turningfinto aTyrecursed forever.Fix
reify_func.S[k],checktook 5.2 s at n=8. Replaced the stack with a depth counter (MAX_NESTED_FUNC_RETURNS = 1): a function's returns are spelled out only at the outermost level.Trade-off
A returned closure's own returns reify as unknown. A factory now hovers as
fun(): fun()instead offun(): fun(): integer. Same-file call checking walks the inference graph and is unaffected. A cross-file consumer callingM.make()()now seesunknownfor the inner result. The fd0703e commit message says call checking is unaffected; that holds only for same-file calls. The cap is one constant if we'd rather pay the cost.Verification
luabox-types: the self-reference, 12 siblings (would never finish under factorial cost), and the pinned depth-cap display.cargo test --workspace: 3078 passed, 0 failed.clippy -D warningsandfmt --checkare clean.examples/still check clean.🤖 Generated with Claude Code