Skip to content

-mm=own phase 8: Shared<T> (spec 23) - #463

Merged
ASDAlexander77 merged 21 commits into
mainfrom
own-shared
Oct 3, 2026
Merged

ASDAlexander77 merged 21 commits into
mainfrom
own-shared

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Summary

Phase 8 of -mm=own (spec §23): a built-in, counted Shared<T> for graphs a tree of single owners cannot express (a node in two lists, a DAG, a parent link).

  • Shared<T> is !ts.shared<T>, a handle to a heap block holding the count and one T. Its ops are ts.SharedNew, ts.SharedValueRef (s.value is a ts.Load of it) and ts.SharedCount. Members: new Shared(x), .value read and write, ===/!==, Shared.count(s) (-1 under gc and none). A program's own Shared wins.
  • The same program under every model. gc and none never count. rc counts the handle as it counts every block. Own counts this type and no other: the inference pass steps aside for every retain and release of a handle, so a handle's count is exactly rc's.
  • A handle, widened during review, is a Shared<T>, an optional of one, or a union of Shared members with members that own no heap (Shared<T> | null | number). Under own, a Shared<T> in a union with an owning member (Shared<T> | string) is rejected with a named error.
  • §23.3, reading through a handle. Two handles may reach one block, so a borrow read through a handle ends at any of these:
    • the handle's own release or overwrite;
    • any write through any handle, whatever its element type;
    • a push or unshift through a handle, inline or in a callee;
    • a call that may drop.
  • A handle read out of a place is a borrow of the place unless every use of it is one rc counts. Every use is classified and a use not known to count is a borrow (Ruling 9): a counted use is a receiver with rc's retain for that very use (a store, a return, a push or unshift, new Shared, an array literal's element, an any box, or an owning local's slot); a cast or view that is still a handle, a merge or a local that owns nothing passes it on; anything else (a call argument, a tuple's element, an object literal's field, a capture, a yield, any other op) is a borrow. f(p, p.child), with f overwriting p.child, is rejected under own, and so are f(p, q, q.child = p.child), { s: (q.child = p.child) } and [(q.child = p.child), 1], where one use is counted and the other is not. cur = cur.value.next is counted, so walking a graph is not restricted, and Shared.count(q.child = p.child) is legal.
  • A handle global takes over its initializer's reference through any view of its type (Shared<T> | null, Shared<T> | undefined, a union), from new Shared(..) or from a call, so it is born holding one count under rc and own. A handle read out of a field or an element (holder.child, new P().child, mkArr()[0]) carries no reference, and the global takes a count of its own.
  • any: a handle boxes under its own descriptor (typeof "object"), and unboxes through a checked ___unbox.
  • Cycles leak, as under rc.

Spec §23.7 records the results. The plan is docs/superpowers/plans/2026-10-03-own-shared.md.

Measured (AOT, measure.ps1, MB)

program iterations gc rc none own
a leaf in two arrays 20000 5.9 4.4 10.6 4.4
a leaf in two arrays 200000 5.9 4.4 65.9 4.4
a two-node cycle 200000 5.9 35.2 35.2 35.2

Corpus and IR

  • --emit=llvm -mm=own --no-default-lib over test/tester/tests gives 453 / 593 plain and 451 / 593 with --opt. That is the same set of files, with the same first errors, as main.
  • The LLVM IR of the 453 files that compile is identical to main's for 451 of them, after §22.7's normalisations. The other two (00union_null_undefined_nonstrict and 00union_bin_ops2) flip a union's member order between runs of main itself. Each binary gives the same two outputs.
  • Rerun after the final review's fix round: the same 453 / 451 files with the same first errors, and all 453 IR files identical to main's (both flipping files came out in main's order in that run).
  • Under -mm=rc, 50 files were compared: 49 are identical, and 00union_bin_ops2 flips the same way.
  • Rerun after the second fix round: the same 453 / 451 files with the same first errors. Under own, 451 of 453 IR files are identical to main's; 00union_null_undefined_nonstrict and 00funcs (a third flipper of the same kind, ___cast<!ts.union<si32, !ts.boolean>,…>, which flips in main too: 11 and 1 over 12 runs) came out in main's other order. Under rc, 49 of 50, 00union_bin_ops2 again.
  • Spot check after the third fix round: 40 corpus files under own and rc against main's binary. All 70 that compile are identical; the other ten fail under own on both binaries with the same first error.
  • Spot check after the fourth fix round: another 40 files, the same way. 66 of the 67 that compile are identical; the other is 00union_bin_ops2, main's other output (under rc in one run, under own in the rerun; main gives it 1 run in 10). The 13 that fail fail on both binaries with the same first error.

Tests

  • Positives run under gc, none, rc and own, AOT and JIT: own_shared_basic, own_shared_user_named, own_shared_graph, own_shared_containers, own_shared_cycle, own_shared_union, own_shared_global, own_shared_unlink, own_shared_global_init, own_shared_place_two_uses and own_shared_global_field. The last five drop the only other owner, reuse its memory, and then read through the handle that is left. own_shared_global_init covers a Shared<T> | undefined global from new Shared, let and const Shared<T> | null globals from a call, an explicit cast of a call's result, and Shared<T> | undefined and Shared<T> | number from a call, each asserting its count of 1. own_shared_global_field covers globals from holder.child (count 2), new P().child, mkP(..).child and mkArr(..)[0] (count 1).
  • own_shared_count runs under rc and own. It asserts every count, including a generator over a handle parameter, handles copied inside a generator, and a generator that reads its handle after the caller has replaced its own and the memory is reused.
  • --verify-ownership is quiet on the eleven counted files under own and rc.
  • Negatives:
test message
own_err_shared_borrow_write 'n' borrows 'a.value' but is used here after it may be released or overwritten
own_err_shared_borrow_call the same, after a call that may drop
own_err_shared_borrow_push 'first' borrows 'a.value.items[]' but is used here after …
own_err_shared_borrow_push_call … borrows 'a.value' but is used here after … (push in a callee)
own_err_shared_borrow_other_type the same, for a write through a Shared<Base> alias
own_err_shared_borrow_reassigned the same, after the handle itself is reassigned
own_err_shared_moved_in is used here after its value was moved
own_err_shared_union_owned a Shared<Node> in a union with 'string', which -mm=own does not count, is not supported
own_err_shared_place_arg 'this value' borrows 'p.child' but is used here after … (f(p, p.child), f overwriting p.child)
own_err_shared_place_two_uses 'this value' borrows a field but is used here after … (f(p, q, q.child = p.child), f overwriting both fields)
own_err_shared_place_literal 'this value' borrows a field and cannot be stored, returned or captured ({ s: (q.child = p.child) }, then both fields overwritten)
own_err_shared_place_tuple 'this value' borrows a field and cannot be stored, returned or captured ([(q.child = p.child), 1] in a const tuple, then both fields overwritten)
  • Full Windows Release suite: 3630/3631 at d636eb82; the one failure is test-compile-gc-defaultlib-collector, the known local LNK2005 against the stale installed default library (fails on main the same way).
  • Windows Release: the Shared set passes 126/126. ctest -R own passes 616/616, and ctest -R "own|rc" passes 1570/1570.
  • Linux (GCC, WSL): with the fourth round applied to ceedafa5 as a patch, the build has no errors, and ctest -R "own_shared|own-err|own-no-counting|own-verify|verify-ownership-own_shared" passes 267/267, the same count as on Windows. (Before the fix rounds: 213/213 at bbac9ba8; after the first: 234/234 at fad98c53; after the second: 255/255, its changes applied to 476c4d8f; after the third: 266/266 at 3a86e20d.)
  • Teeth: each §23.3 rule has a negative that compiles once that rule is switched off. Without the step-aside, the own positives fail to compile. Without the payload release, own_shared_count fails. Each fix of the final review's four rounds has its own tooth. The full table is in §23.7.

The final review's fix round

The final review found three critical defects and one important one, all fixed here:

  • A global handle was born at count 0 under own (moveIntoGlobals erased its retain), and a Shared<T> | null global from new Shared gave back its initializer's reference under rc and own. Test: own_shared_global. This round fixed only a cast of new Shared; the second round fixed the rest (below).
  • A const of a union or optional handle was folded, holding no count and getting no check: unlinking a list node read freed memory. Every handle const now has storage. Test: own_shared_unlink.
  • A handle read out of a place and given to a call was neither counted nor checked. It is now a borrow unless rc counts it. Test: own_err_shared_place_arg.
  • A generator over a handle parameter was falsely rejected under own. It now compiles, and its box owns the handle's count. Test: own_shared_count.

The second fix round

The re-review found the global fix partial and one new important defect, both fixed here:

  • A handle global initialised through a view other than a cast of new Shared - Shared<T> | undefined = new Shared(..), Shared<T> | null = mk() (let or const), <Shared<T> | null>mk() - was still born at count 0: own read garbage and crashed, rc printed count 0. OwnedReturnConsumptionPass now claims the reference through any view, once a call's result is known to carry one (MLIRGen cannot see that). Keyed on the global's type. Test: own_shared_global_init.
  • One counted use exempted the whole read, so f(p, q, q.child = p.child), with f overwriting both fields, compiled under own and read freed memory. The exemption is now per use (Ruling 7): the counted uses are a copy, the other use is tenure-checked, and the counted store's own release of the old value is not a drop of the read. Tests: own_err_shared_place_two_uses, own_shared_place_two_uses.
  • A generator that outlives its caller's handle is now asserted in own_shared_count.

The third fix round

The second re-review found one more important defect and an understated limit, both fixed here:

  • Any store of a handle read counted as a counted owner, so in const o = { s: (q.child = p.child) } the object literal's field, which rc makes no retain for, kept the read with no count, and own crashed once both fields were replaced. A store now counts only when rc retained for it (ts.Retain, then the store, with or without the old value's ts.ReleaseSlot between), and one it did not makes the read a borrow. Test: own_err_shared_place_literal.
  • A handle global initialised from a field (holder.child, new P().child, mkP().child) was born at count 0 and crashed under rc and own; A global initialized from another global reads garbage (heap-typed globals; crash with a class) #446 hides only the global-from-global case. The global claim now gives a handle read out of a field or an element a retain of its own, right after the read. Keyed on the handle type. Test: own_shared_global_field. The class equivalents read freed memory under rc on main as well and are not changed.

The fourth fix round

The third re-review found one more important defect of the same kind, fixed here:

  • A use outside both lists fell through. handleReadUses let a use that was neither a counted receiver nor in isBorrow's list count for nothing either way, so in const t: [Shared<T>, number] = [(q.child = p.child), 1] (and with as const, or a Shared<T> | null element) the counted store exempted the read, the tuple kept it with no count, and own crashed once both fields were replaced. The default is inverted (Ruling 9): every use is classified, a receiver counts only with rc's retain for that very use (push, unshift, new Shared and a return now need theirs too, and an array literal's element is now a receiver with its retain), and anything else is a borrow. A return (a store into the result slot after the scope exit's releases) and an any box are classified before they would be passed on. Test: own_err_shared_place_tuple. Three probes that compiled before are now rejected as conservative (below).

Known limits

Every limit in §23.7's "Outside the rule":

  • Covariance. Shared<Derived> can be assigned to Shared<Base>, which is TypeScript's own unsoundness and is not fixed. Under own, any write through any handle ends every borrow.
  • Conservative rejections (false errors, never miscompiles):
    • a function that gives back a handle loses __own_no_drops, so a call to it ends its caller's borrows;
    • a getter returning s.value;
    • a write through a handle of an unrelated type ends borrows through every handle;
    • a push or unshift through a handle ends element borrows, inline and in a callee;
    • a handle read out of a place and given straight to a call that may drop, even when the callee retains it first: with p a parameter, keep(k, p.child). Copying it into a local first is accepted;
    • one read with a counted use and a use given to a call that may drop, even when the counted owner keeps the block: f(p, k = p.child), with k an owning local. k = p.child; f(p, k) is accepted;
    • using r = …; return p.child: a scope exit split across blocks hides the return's retain from the check, so it is rejected (it compiled before the fourth round);
    • one read with a counted use whose value also goes into a tuple, whether or not anything drops it: [(q.child = p.child), 1] with no drop after it, and const [a, n] = [(q.child = p.child), 1] (both compiled before the fourth round). const x = p.child; const t = [x, 1] is accepted;
    • a generator over a handle parameter and another parameter that holds a block, that reads the handle;
    • a generator function expression capturing a local handle, as for any local.
  • The any unbox checks only the typeof name, so a Shared<B> passes as a Shared<A>. A wrong-type unbox cannot be asserted in a test (A let in try whose initializer throws is not caught (stack overflow / uncaught exception) #456).
  • Through null. Shared.count(u) and u.value on an un-narrowed Shared<T> | null read through null.
  • Misused names (Shared.foo(s), a bare Shared) report only "failed statement"; another member gets the generic "Can't resolve property"; a program's own Shared leaves one dead op behind.
  • A rejected mixed union also reports own's ordinary error after the named one.
  • The union check builds its MLIRTypeHelper with a default CompileOptions.
  • rc is no oracle for borrows: rc runs the use-after-free shapes own rejects.
  • No count is elided under own yet.
  • A bare { } block does not end a folded const boxed: any = h.
  • A handle returned from an imported function is counted one extra under rc and own (no __owned_result on the importer's call); classes leak the same way. No test.
  • A handle global initialised from another global takes no count of its own. Today such an initializer does not read the other global's value in any model (A global initialized from another global reads garbage (heap-typed globals; crash with a class) #446), which hides it. A global object literal holding a handle from a call (let o = { s: mkS(4) }) gives the call's reference back and crashes under rc and own; its class equivalent reads freed memory under rc on main.
  • An owning local initialised through a view of its type (let b: Shared<T> | undefined = new Shared(..)) holds two counts until the end of its block, where the initializer's reference is given back: Shared.count reads one high there. Balanced, not a leak.
  • Not tested: a generic function f<T>(s: Shared<T>).

Issues filed (pre-existing, every model)

🤖 Generated with Claude Code

ASDAlexander77 and others added 21 commits October 3, 2026 09:48
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…andle (spec 23.2, 23.3)

Inference never erases, decides or reports a retain or release of a handle
(touchesHandle), and lowering counts what is left under own. The signature
pass gives handles no owned or borrowed facts; a handle's release, and a store
into `s.value`, make a function one that may drop.

A borrow read through a handle has a root nobody owns, so any call that may
drop ends it, as do `x.value = ...`, a store into a place under any handle,
and push/unshift on an array reached through one. The handle's own release or
overwrite (a local's ReleaseSlot/Store, a temporary's Release) ends it too; a
copy of the handle does not.

Also: own_shared_count asserts that a block held by a dying block releases its
payload and that `s.value = x` hands the counts over; comment reflow in
MLIRGenImpl.h and the GC-root condition wrapped in LowerToLLVM.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (spec 23.3)

dropsInBody marked every ReleaseSlot of a slot holding a handle as a drop, ahead
of the place rule, so a constructor initialising a Shared<T> | null field lost
__own_no_drops and `new C()` ended unrelated borrows in its caller. A handle
held by a field or an element is now judged as any place overwrite is; the
handle rule applies to ts.Release and to non-place slots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cells counted (spec 23.3)

- dropsChain: a store into `x.value` ends every borrow read through a handle,
  whatever the handles' static types (a Shared<Derived> converts to a
  Shared<Base>, so matching by type compiled a use-after-free).
- chainOf: the root of a `s.value` is a handle because of the op, not its type
  (a view may hide it); merges into it are each handles; rootEnds keys on that.
- decideCells: a cell holding a handle is not a borrowing cell; its release
  gives back the count lowering takes for it (a captured handle parameter
  leaked one count per call). decideEscaping lets an escaping closure take
  such a cell.
- dropsInBody: push/unshift on an array reached through a handle is a drop
  (reachesSharedValue, now in OwnershipFacts.h).
- Tests: own_err_shared_borrow_other_type, own_err_shared_borrow_push_call,
  own_err_shared_borrow_reassigned; own_shared_count asserts the counts of a
  captured and an escaping-captured handle parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pec 23.1, 23.4)

- A handle's typeof name is "object", so it has a type descriptor; under own
  the descriptor keeps its retain routine (a handle is counted).
- `Shared<T> | null` boxes into `any` under the handle's descriptor (it had no
  tag at all, and crashed the compiler).
- `<Shared<T>>any` goes through ___unbox like a class, checked by the typeof
  name only.
- Shared.count accepts a `Shared<T> | null` or optional narrowed by `if (s)`,
  which keeps the union's type.
- Tests: own_shared_containers and own_shared_cycle under gc, none, rc and own,
  AOT and JIT, plus --verify-ownership; own_shared_count asserts the narrowed
  union and the any round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s a handle (spec 23.2)

`Shared<T> | number` compiles since the handle has a type descriptor, but own
took it for a single-owner value: it erased the union's count and kept the
releases, so counts differed from rc's and a loop corrupted the heap.

- isSharedHandleType: a union of at least one Shared<T> whose other members own
  no heap memory (null, undefined, numbers, literals, other Shared<U>) is a
  handle, so own leaves its retains and releases as rc's. Its routines
  dispatch on the tag; a member with no retain routine is skipped.
- A handle put into a union with an owning member (`Shared<T> | string`,
  `Shared<T> | Node`) is rejected under own by the inference pass: "a
  Shared<T> in a union with '<type>', which -mm=own does not count, is not
  supported". gc, none and rc accept it.
- Tests: own_shared_union (all four models, AOT and JIT, verify-ownership)
  and the negative own_err_shared_union_owned. Spec 23.2 and 23.6 amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- measure.ps1: a leaf handle in two arrays, flat and equal under own and
  rc (4.4 MB at 20000 and 200000 iterations), growing under none; a
  two-node cycle leaks under rc and own exactly as under none
- corpus: 453 / 593 plain and 451 / 593 with --opt, the same files and
  first errors as main
- LLVM IR identical to main's for the 451 of 453 files that compile; the
  other two flip a union's member order between runs of main itself
- spec 23.7: the ops as built, the widened handle, tests, negatives,
  teeth, limits and the pre-existing bugs filed (#453-#456)
- the plan's boxes ticked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GCC in WSL at bbac9ba: builds with no errors, and
ctest -R "own_shared|own-err|own-no-counting|own-verify|verify-ownership-own_shared"
passes 213 of 213.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a) runs (spec 23.7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…andles, handle arguments, generators (spec 23)

- A global handle is born with its count: own's moveIntoGlobals leaves a handle's
  retain in place, and a `Shared<T> | null` global takes over its initializer's
  reference through the cast to its type (it was released as a discarded temporary,
  under rc as well).
- A const of a union or optional handle (`const c = p.child`, `const c: Shared<T> | null = a`)
  gets identity storage, as a bare one does: it is a handle of its own, counted.
- A handle read out of a place is a borrow of the place, checked as a field read is,
  unless rc gives it an owner of its own (a retain, or an owning local's slot):
  `f(p, p.child)` with `f` overwriting `p.child` is rejected under own.
- `s.value` and `Shared.count(s)` are borrows (isBorrow); a generator over a handle
  parameter owns its box, whose cell holds the handle's count, so it is no longer
  rejected and gives the count back.
- Tests: own_shared_global, own_shared_unlink (all four models, AOT and JIT, verifier),
  own_err_shared_place_arg; a generator in own_shared_count; graph and union counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ount (spec 23.2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(spec 23.7)

The const-handle note corrected, the fix round's tests, teeth, rejections and
limits, the corpus and Linux rerun, and what was found and not fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tializer, a handle read with two uses (spec 23)

- A handle global takes over its initializer's reference through any view of its type
  (ts.Cast, ts.OptionalValue, ts.CreateUnionInstance), from `new Shared(..)` or from a call:
  OwnedReturnConsumptionPass claims it (claimHandleGlobalInitializers) once a call's result is
  known to carry one, which MLIRGen cannot see. `Shared<T> | undefined = new Shared(..)` and
  `Shared<T> | null = mk()` were born at count 0 under rc and own. The cast-only claim in
  MLIRGen goes.
- A handle read out of a place is a counted copy only when every use of it is one rc counts
  (Ruling 7): one use that reads through the place makes it a borrow, tenure-checked, while
  its retain and the store it feeds stay a counted copy, and that store's release of the old
  value is not a drop of the read. `f(p, q, q.child = p.child)`, where `f` overwrites both
  fields, is rejected under own; `Shared.count(q.child = p.child)` stays legal.
- Tests: own_shared_global_init and own_shared_place_two_uses (all four models, AOT and JIT,
  verifier), own_err_shared_place_two_uses, and a generator that outlives the caller's handle
  in own_shared_count.

Docs (spec 23.7): the second fix round in the results - the narrowed global claim (a handle
global initialised from another global or a field takes no count; #446 hides it), the per-use
exemption, tests and suites (114 / 604 / 1558), teeth, corpus rerun, Linux 255/255, the
generator rejection only when it reads the handle, #458, #459 and the not-filed findings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…retains for it, handle globals from a field (spec 23)

A handle read out of a place is exempt from the borrow check only when every
use gives it a counted owner (Ruling 7). Any ts.Store of the read counted as
one, so `{ s: (q.child = p.child) }` - one store rc retains for (q.child),
one it does not (the literal's field) - compiled under own and crashed.

- OwnershipInferencePass: countedStoreRetain finds rc's retain for a store
  (Retain(v), [ReleaseSlot(slot)], Store(v, slot)). isCountedHandleReceiver
  takes a store only with it, so walkBorrowed skips only such stores, and
  collectCountedStoreReleases shares it. handleReadUses marks a store with no
  retain as an uncounted use (isUncountedStoreOf), so the read is checked and
  the store is its keep.
- OwnedReturnConsumptionPass: a handle global initialised from a field read
  (`holder.child`, `new P().child`, `mkP().child`) gets a ts.Retain of its own
  right after the read; it was born at count 0 under rc and own. Erased
  under gc and none; keyed on the handle type.
- Tests: own_err_shared_place_literal (negative), own_shared_global_field
  (positive, gc/none/rc/own, JIT and AOT, verifier under own and rc).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… count too (spec 23)

`let g: Shared<T> | null = mkArr()[0]` read an element of a temporary array,
which gave its count back at the end of the initializer: the global was
born at count 0 under rc and own, as from a field. claimHandleGlobalInitializers
retains a handle read out of an element as it does one read out of a field.
own_shared_global_field asserts it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store receiver (a store counts only when rc retains for it), the
global initialised from a field or an element, and the place-read bullet's
misplaced gap; tests, suites (125 / 615 / 1569), four teeth rows, the
corpus spot check, the third round's paragraph, Linux 266, and three
not-filed findings (a global object literal, a discarded pop, arronly).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nied by default (spec 23, Ruling 9)

handleReadUses classified a use as counted, borrowed, or neither; a use that was
neither (a tuple's element: `[(q.child = p.child), 1]`) let a read with a counted
store stay exempt, and the tuple kept the block with no count of its own (c7, d4,
d10 compiled and crashed under own).

Every use of a handle read is now classified, and nothing falls through:
- counted: a receiver with rc's retain for that very use - a store
  (countedStoreRetain, now also a return's store into the result slot after the
  scope exit's releases), push and unshift, `new Shared`, an array literal's
  element, an `any` box, `ts.ReturnVal` (retainedRightBefore: the row of retains
  MLIRGen puts right before the op), and an owning local's initializer;
- passed on: a cast or view that is still a handle, a merge, a local that owns
  nothing;
- borrowed: everything else - isBorrow's list, a tuple element, an object
  literal's field, a capture, a yield, a receiver without its retain, a cast to
  what is not a handle.
rc's retains count for nothing alone. A return (result-slot store) and an `any`
box are classified before passesOn would hand them on, in handleReadUses and in
walkBorrowed's counted-copy skip alike. ReturnInternal is no receiver any more;
isUncountedStoreOf is folded into the default.

New negative own_err_shared_place_tuple.ts (c7's shape).

Spec 23.7: Ruling 9 in the place-read bullet and a fourth-round bullet (with
the retain-pairing argument), the new negative, suites 126 / 616 / 1570, two
teeth rows, the corpus spot check, Linux 267, and the conservative rejections
(a counted read's value in a tuple: c7c, c7d, d3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vative rejection (spec 23.7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit ae395dc into main Oct 3, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the own-shared branch October 3, 2026 17:26
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