Skip to content

Resolve mutual recursion between sibling let-bound closures - #55

Merged
revarbat merged 1 commit into
mainfrom
letrec-mutual-recursion
Jul 30, 2026
Merged

Resolve mutual recursion between sibling let-bound closures#55
revarbat merged 1 commit into
mainfrom
letrec-mutual-recursion

Conversation

@revarbat

Copy link
Copy Markdown
Member

Summary

  • Extends the letrec pre-declare from Let a self-referential let-bound closure resolve itself as an upvalue #54 (self-reference) to cover two-or-more sibling let-bound closures that reference each other by name — fnliterals.scad-style isEven()/isOdd(), each calling the other. All function-literal-RHS assignments in a LetOp now get their own slot pre-declared up front (not just each one's own, right before compiling it), so a forward reference to a not-yet-compiled sibling also resolves as a genuine upvalue.
  • Unlike self-reference, a forward reference has nothing to read or self-patch into at Op::MakeClosure time — the sibling it names doesn't exist at all yet. Such captures are left out of the closure's own capture list entirely at compile time and deferred to a new Op::PatchClosureCapture instruction, emitted right after the referenced sibling's own StoreLocal actually runs — mutating the earlier closure's already-real capturedTrail in place.
  • Verified 2-way and 3-way (f1->f2->f3->f1) cycles both resolve correctly, including a case mixing a genuine outer capture with a forward-sibling reference in the same closure. Both scale linearly (100,000-deep isEven/isOdd chain in ~0.07s) — no repeat of the capturedLet-chaining regression the self-reference fix needed a guard against, since a resolved forward reference is a real capture snapshotted once, not re-derived every call.

Test plan

  • Full test suite (673 tests, up from 671) passes locally.
  • New MutualRecursionBetweenSiblingLetBoundClosuresResolvesCorrectly and ThreeWaySiblingMutualRecursionResolvesCorrectly.
  • Manually verified via the CLI: 2-way/3-way cycles, scale (10K/100K), a mixed genuine-capture + forward-reference case, and all previously-fixed patterns (count_to, f_1arg, reduce() at scale, let(x=x+1) shadowing, ternary-wrapped self-reference, accumulate()/while()) unaffected.

🤖 Generated with Claude Code

Extends the letrec pre-declare (v0.8.1's self-reference fix) to cover
two-or-more sibling `let`-bound closures that reference each other by
name -- fnliterals.scad-style isEven()/isOdd(), each calling the
other. All function-literal-RHS assignments in a LetOp now get their
own slot pre-declared up front (not just each one's own, right before
compiling it), so a forward reference to a not-yet-compiled sibling
also resolves as a genuine upvalue.

Unlike self-reference, a forward reference has nothing to read OR
self-patch into at Op::MakeClosure time -- the sibling it names
doesn't exist AT ALL yet, not just "not yet stored." Such captures are
left out of the closure's own capture list entirely at compile time
and deferred to a new Op::PatchClosureCapture instruction, emitted
right after the referenced sibling's own StoreLocal actually runs --
mutating the earlier closure's already-real capturedTrail in place,
the same shared TrailView its own later invocations already read
through.

Verified: 2-way (isEven/isOdd) and 3-way (f1->f2->f3->f1) cycles both
resolve correctly, including a case mixing a genuine outer capture
with a forward-sibling reference in the same closure. Both scale
linearly (100,000-deep isEven/isOdd chain in ~0.07s) -- no repeat of
the capturedLet-chaining regression the self-reference fix required a
guard against, since a resolved forward reference is a real capture
snapshotted once, not a value re-derived on every call.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@revarbat
revarbat merged commit 6850974 into main Jul 30, 2026
3 checks passed
@revarbat
revarbat deleted the letrec-mutual-recursion branch July 30, 2026 23:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant