Skip to content

Fresh writes through a zip, a copy, and a repeat - #516

Merged
kixelated merged 7 commits into
mainfrom
quest/m3/ts-fresh-shapes
Nov 3, 2026
Merged

kixelated merged 7 commits into
mainfrom
quest/m3/ts-fresh-shapes

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Claims quest/m3/ts-fresh-shapes.md.

Problem

#394 made a whole write into fresh memory count as fresh in a function generic over a type. Three shapes stayed refused ("a raw pointer to ... which the crate may put another object in place of"), for concrete types and type parameters alike:

  • A value of a type parameter read out of what std's code returns, such as Zip::next's Some((slot, x)), counted as holding the pointer that went in, whatever its type.
  • iter().copied() over a Copy type, the crate's or a T: Copy, counted as running the crate's Clone, since Copy's supertrait selects a parameter candidate.
  • vec![x; n] does run the crate's Clone, n - 1 times, so it was refused whole.

A Copy type of the crate's also counted as dropping the crate's code, though it has no drop glue.

Consumer: the lengths fixture now, and any crate that fills slots from a zip, a copied, or a vec![x; n].

Approach

All in ts/src/lower/mir/holders.rs:

  • Holds: Trace::holders takes what its root holds in place of a bool. A value whose type shows no pointer holds one only if read as a part. Neither std's code nor a body generic over T makes a T out of a pointer, except by reading one through a pointer cast to point at it, which is a part read. keeps, which asks whether a crate callee generic over T keeps its parameter, uses Holds::Any, since that parameter may itself be the pointer. The reject fixture's new clones::stashed_slots covers this: it stays refused, and translates if keeps uses Holds::Pointers.
  • copies: inert passes a Copy clause on std's copied (of Iterator, Option, Result) and on the Copied adaptor, which copy and never call clone. It does not trust Copy in general. Std's Extend<&T> for LinkedList, BinaryHeap, BTreeSet and HashSet calls cloned() under a bare T: Copy bound.
  • repeats: vec::from_elem (vec![x; n]) counts as fresh when its element shows no pointer and drops none of the crate's code. It runs the crate's Clone only on &x, then moves x into the vector last, so that Clone never sees the returned memory, and what it returns holds no pointer std's code could keep.
  • drops_own_code: counts only the crate's types that have drop glue, so a crate's Copy type no longer counts.

Tests: lengths gains repeated::<T> (generic) and loud (concrete). Both rewrite through raw pointers into the parts of rows made by copied and vec![x; n], then read back the source rows, so a write that showed through a shared copy would change the output. Loud is Copy with a hand-written Clone that logs, returns a different value than the copy, and panics on 0xff. caller.ts pins inputs that put 0xff in the copied rows (no clone, no panic) and as each vec! value (panics after logged clones). Each fix is load-bearing: reverting any one makes the fixture refuse.

Impact

  • Public API: the translator's output only.
  • Refusals: the reject fixture gains clones::stashed_slots, a pointer kept by a generic callee as a T.

Alternatives

  • Copy calls nothing, everywhere: unsound, because of the Extend<&T> impls above.
  • Typing a Vec's buffer pointer by its element, not Unique<u8>: would fix one of the vector-access refusals, but it's a separate cause and is recorded in the new quest.

Follow-ups

  • quest/m3/ts-fresh-shapes.md, rewritten: a whole write into a slot zipped with an iterator whose pointers point at a type the slot holds in place (zip with a Vec<[T; N]>, or with copied of the slot's held type). Trace::fresh reads a live Zip's whole type. The quest weighs a parametric mask of std's signatures against a table of adaptors.
  • quest/m3/ts-fresh-vec-access.md, new: refusals the tests step around. They cover a shared borrow of the vector, first_mut/get_mut, a by-value array for, and a Vec's Unique<u8> buffer.

Choices made without asking

  • The zip-half cause is split out into the rewritten quest rather than left as unfinished work in this PR. It's a separate cause with a design choice of its own, and this PR stands on its own without it.

Review

quest review, Codex gpt-6.1-sol at medium effort, round 1 on head 2b3e086 against base dcfa0b2: 0 findings. The head since adds only conflict-free merges of origin/main (#517, #518, #524).

just ci passed on 2b3e086 and on the merge head b0f609e.

🤖 Generated with Claude Code

kixelated and others added 6 commits November 3, 2026 02:01
…] of a Copy type

A type parameter's value read out of what std's code returns holds no pointer but one
read through a cast pointer, or a function's parameter of that type. copied and Copied
copy rather than run a Copy bound's Clone. vec![x; n] runs the crate's Clone only on
its argument. A Copy type of the crate's drops none of its code. The quest keeps a zip
whose other half points at a part of the slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 14:02
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 34 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dd15aae6-611f-4353-bcf8-f8499145df11
Commits

Reviewing files that changed from the base of the PR and between 210e977 and b0f609e.

Files selected for processing: 15
  • quest/m3/README.md
  • quest/m3/ts-fresh-shapes.md
  • quest/m3/ts-fresh-vec-access.md
  • ts/fixtures/lang/lengths/caller.ts
  • ts/fixtures/lang/lengths/expected/lengths.Loud.ts
  • ts/fixtures/lang/lengths/expected/lengths.Row.ts
  • ts/fixtures/lang/lengths/expected/lengths.ts
  • ts/fixtures/lang/lengths/expected/lib.ts
  • ts/fixtures/lang/lengths/src/lengths.rs
  • ts/fixtures/reject/census.txt
  • ts/fixtures/reject/expected.txt
  • ts/fixtures/reject/src/clones.rs
  • ts/fixtures/reject/ts.toml
  • ts/src/lower/copy.rs
  • ts/src/lower/mir/holders.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@kixelated
kixelated enabled auto-merge November 3, 2026 14:05
@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 6e956d4 Nov 3, 2026
5 checks passed
@kixelated
kixelated deleted the quest/m3/ts-fresh-shapes branch November 3, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant