Skip to content

-mm=own phase 6: named messages for a second reference; nullable views, spreads, declaration retains - #449

Merged
ASDAlexander77 merged 1 commit into
mainfrom
own-phase-6-messages
Oct 2, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
own-phase-6-messages

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Phase 6, second PR (plan docs/superpowers/plans/2026-10-02-own-phase-6.md, task 5; spec §21).

What

The catch-all 'x' takes a second reference; -mm=own cannot prove a move or a borrow here yet named no rule. reportSecondReference now classifies what it is given, from the retained value's root, in one place, so all four places that report it get the same names:

  • a function returning a borrow of its argument on some paths and another value on others (the ___unbox<string> and ___cast<A, string> helpers, and user code such as return name / return "Sorry, " + name), with a note at the other return;
  • a parameter returned or kept where its callers cannot learn the fact (with §15's facts-lost note);
  • a parameter assigned;
  • a local that owns nothing (declared without a value, or in a catch/finally clause) given a value;
  • a value merged from branches or loop iterations;
  • a global's value stored/returned, or read into a local across a call;
  • a captured variable's value;
  • an object literal holding a value it owns, given anywhere but an owning local or the result.

Accepted on the way (each with a positive test, and a tooth: turning the rule off makes it fail plain, with --opt and with --di):

  • a string or an array widened to a union of it and null/undefined, or narrowed back, is a view (the lowering passes the pointer through);
  • a view of a value whose type owns no block holds none (a union's number payload made into another union), and a retain of it is erased. The signature pass reads this too: mkN(v: number): number | P { return v } no longer has a result that "borrows" its argument;
  • a local's declaration retain of constant data is erased wherever the slot still holds its initializer, whatever is stored later (MLIRGen emits ts.RetainSlot only at a declaration). Replaces §20's "nothing is ever stored into the slot";
  • a field of constant data is constant data (a named tuple of literals);
  • an array a spread builds in a local of its own moves into the owning local it is read into (const b = [...a, ...a]);
  • an interface's this bound for a call of a function-typed field is a borrow (-mm=own: an importer can build objects of an imported class #410's shape with ts.ExtractInterfaceThis).

Two rules were tried and dropped because nothing needed them: ts.SafeCast as a view, and a number cast to a union as holding no block.

Corpus (592 files, --no-default-lib)

first error before (cd7c284) after
compiles 402 (402 with --opt) 416 (415 with --opt)
takes a second reference (catch-all) 100 25
used after its value was moved 24 23
borrows a field and cannot be stored, returned or captured 21 20

None lost, plain or --opt. The 13 files gained under --opt each pass test-runner -mm=own, AOT and JIT, with the default flags and with -noopt. 00interface_object3 compiles plain only: under --opt, CSE merges the interface this reads into a taker (§21.6), so it is not counted.

Tests

  • Positive: own_nullable_view, own_declared_constant, own_spread_build, own_interface_field_call (also pass under rc, none and gc, AOT and JIT).
  • Negative: 11 new own_error_cases, one per message.
  • own ctest set: 430/430 on Windows; GCC build in WSL and its 132 compile-only own tests pass.
  • Only own's two passes change (both run only under -mm=own), so rc, gc and none emit what they did.

Found, not fixed

  • instanceof is false for a class value held in a union or nullable local under every model, even narrowed: let a: C | null = new C(1); a instanceof C.
  • Model questions (§21.5): a read after a move while the destination still holds the value (~14 of the 24 use-after-move files), and strings as values, where a second reference copies (most of the 21 borrows-a-field files).

🤖 Generated with Claude Code

…s, spreads, declaration retains

The catch-all ("takes a second reference; -mm=own cannot prove a move or a borrow here yet") was
the first error of 100 corpus files. reportSecondReference now names the shape it is given, from
the retained value's root, for all four places that report it: a function returning a borrow of
its argument on some paths and another value on others (___unbox<string>, ___cast<A, string>,
user code), a parameter returned or kept where its callers cannot learn the fact, a parameter
assigned, a local that owns nothing given a value, a value merged from branches, a global's value,
a captured variable's value, and an object literal holding a value it owns.

Accepted on the way:
- a string or an array widened to a union of it and null or undefined, or narrowed back, is a view;
- a view of a value whose type owns no block holds none (a union's number payload made into another
  union), and a retain of it is erased;
- a local's declaration retain of constant data is erased wherever the slot still holds its
  initializer, whatever is stored later (MLIRGen emits it only at the declaration);
- a field of constant data is constant data;
- an array a spread builds in a local of its own moves into the owning local it is read into;
- an interface's `this` bound for a call of a function-typed field is a borrow (#410's shape).

Corpus: 402 -> 416 of 592 (415 with --opt), none lost; the catch-all 100 -> 25. Spec section 21.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 6ebcc98 into main Oct 2, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the own-phase-6-messages branch October 2, 2026 20:29
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