Repository navigation
rc: a const chosen by a conditional expression is released at the end of its block - #673
Merged
Merged
Conversation
… of its block `const h = new C(); const h2 = new C(); const each: C = b ? h : h2;` gave neither `new` a release under rc, and so under own, which reads rc's IR. A folded const is given back at the end of its block by releaseDiscardedTemporaries, but allUsesReleasableInOwnBlock refused any value with a terminator user, and the ternary's `ts.If` yields `h` through `ts.Result`. `let each = b ? h : h2` leaked both too, and `b ? h : new C()` leaked `h`. A `ts.Result` ending a branch of a one-result `ts.If` now hands the value on to the If's result, whose users are judged by the same rules in its place. What reads a result nobody owns retains it, and a branch that makes the result owned retained `h` for it, so the release at the end of the block stays balanced. storedIntoUnownedLocalOutside and escapesThroughStore follow the same step, so a choice given to a local that owns nothing, or yielded from a generator under own, still keeps its value. 00conditional_named_const_release runs under every model but own (which rejects these merges until c ? h : h2 borrows), and conditional-named-const-release.cmake reads the releases under rc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 23, 2026
§35's "Not here" said that under rc a folded const a ternary yields is never released, so the class cases leaked under rc and own. #673 releases it at the end of its block. 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
Under
-mm=rc, and so under-mm=own, which runs its inference on rc's IR, this gave neithernewats.Release:let each = b ? h : h2leaked both as well, andb ? h : new C()leakedh.const each: C = h;was fine. This is why the class cases ofown_ternary_outer_loop(branchown-ternary-outer-loop) never freedh/h2under either model. The interface cases did not leak: there thets.Newis used byts.NewInterface, not by the branch.releaseDiscardedTemporariesgives a folded const (__owned_result_named) its reference back at the end of its block. ButallUsesReleasableInOwnBlockrefused any value with a terminator user, and the ternary'sts.Ifyieldshthroughts.Result.Fix (
OwnedReturnConsumptionPass.cpp)allUsesReleasableInOwnBlocknow walks a worklist. When ats.Resultends a branch of ats.Ifwith one result (handedOnByConditional), the value is handed on to the If's result. That result's users are then judged by the same rules: no other terminator, in the block or in a structured statement in it, and before the generator release range ends. A reader of a result nobody owns retains it. Where a branch makes the result owned,consumeConditionalResultsretainedhfor it. Either way, the release at the end ofh's block stays balanced.storedIntoUnownedLocalOutsidetakes the same step. Solet n: C; if (b) { const h = new C(); n = b ? h : h2; }still keepsh(it leaks, as before) instead of freeing whatnreads after theif.escapesThroughStoretakes it too, so a choice yielded from a generator under own is not released before the yield. This is defensive only: a generator's locals live in its state object, so no folded const reaches it today.Tests
tests/00conditional_named_const_release.tscovers const, let, made-on-one-side, nested, in a loop, returned, and given to an unowned local outside. Each value is read afterchurn()reuses freed blocks. The file is inTSLANG_CORPUS(default/rc/none, compile + jit). It is not run under own: own still rejects these merges, partly untilown-ternary-outer-looplands, and returned/stored merges stay errors there.conditional-named-const-release.cmake(test-rc-conditional-named-const-release) reads the rc IR. Each namednewmust be released exactly once, the one inassignedOutsidenever, and the ownedts.IfinchosenOrNewonce. With the pass from main it fails:constChosen` releases %3 0 times, not 1.origin/own-ternary-outer-loop(not committed):own/own_ternary_outer_loop.tspasses under own/rc/gc/none (jit and compile), and--verify-ownershipis clean.constClass,letClassandbranchednow release bothnews under rc and own.🤖 Generated with Claude Code