Skip to content

feat(ts): map bytes, rand, and tracing without config - #147

Merged
kixelated merged 17 commits into
mainfrom
quest/m2/ts-model/stock
Oct 28, 2026
Merged

kixelated merged 17 commits into
mainfrom
quest/m2/ts-model/stock

Conversation

@kixelated

@kixelated kixelated commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest/m2/ts-model/stock.md: rust2-ts translates calls into bytes, rand, and tracing with no binding or config from the consumer, as it does std's.

Summary

  • tracing: the translator leaves out tracing's events itself: trace!, debug!, info!, warn!, error!, and event!. Translated code installs no subscriber. Without one, an event evaluates none of its fields and does nothing, so leaving it out is exact. Installing a subscriber (set_global_default and the like) is an unmapped call, so it is refused. A parameter that only a left-out event reads now counts as unused, so it gets a _ name and passes noUnusedParameters. The ignore-macros key is gone.
  • bytes::Bytes: a Bytes is an rt.ByteSlice, the same value a &[u8] is. Each of these maps to rt/bytes.ts, with bytes' own panic messages: new, from_static, copy_from_slice, len, is_empty, slice over every std range, from_owner, Clone, Deref, AsRef, Borrow, PartialEq against Bytes or [u8], Default, Debug (written a piece at a time, as bytes writes it), and From between Bytes and Vec<u8>, String, &'static [u8], and &'static str. slice reads the range's bounds where the range is made, so no range object is created. from_owner calls the owner's AsRef once, as bytes does. If the owner drops, from_owner is refused at its span.
  • rand: rand::rng() returns an rt.ThreadRng, which holds no state. On it, random of an integer or bool, and random_range over a..b, a..=b, or ..b of any integer width, or ..=b of an unsigned one, map to rt/rand.ts. Values come from crypto.getRandomValues and are uniform, as rand's are. An empty range panics with rand's message. Like rand, random_range reads only an inclusive range's bounds, even once the range is exhausted. A seeded generator such as StdRng is refused.
  • Bindings: bytes::Bytes and ThreadRng now have their own representation, so binding either one is refused, just as binding std::vec::Vec is.
  • Dependencies: bytes and rand don't translate from source with the other dependencies (#141), so their mappings win, as std's do. From source, bytes refuses its raw pointers.
  • moq: ts/moq/model.toml drops its Bytes and rand bindings and ignore-macros, and keeps BytesMut's. ts.toml and net.toml drop ignore-macros.

Census at the pinned moq revision

Before After
model.toml refusals 1,164 1,165
model.toml function instances 3,527 3,462
net.toml refusals 3,804 3,398
kio.toml refusals 397 397
bytes/rand bindings, ignore-macros entries in model.toml 4 + 5 1 + 0

The model's one new refusal is at rs/moq-net/src/model/frame.rs:707: Bytes::from_owner of model::frame::Filled, whose Arc<Segment> would drop when the last clone does. The model now reaches BytesMut (frame.rs:183), which this PR doesn't map, so model.toml keeps its bytes::BytesMut binding.

Consumer

moq: ts/moq/model.toml, the census that measures the model epic. Also moq/net.toml, moq-net as a whole, which reaches rand::rng() in Origin::random and the clock's jitter.

Public API

  • Breaking: ts.toml no longer takes ignore-macros. bytes::Bytes and rand::rngs::ThreadRng can't be bound. Both are listed in ts/MIGRATE.md.
  • The output maps Bytes to rt.ByteSlice and ThreadRng to rt.ThreadRng. The runtime gains rt/bytes.ts (bytesNew, bytesCopyFromSlice, bytesSlice, bytesSliceInclusive, bytesSliceToInclusive, bytesDebug), rt/rand.ts (ThreadRng, randRng, randInt, randBool, randRange, randRangeInclusive, randRangeToInclusive), and intFromBig/intToBig in rt/int.ts. Every one of them shakes out of a bundle that doesn't call it, which the codec's bundle test checks.

Tests

  • New stock fixture: snapshotted, type-checked, and differentially tested against native wasm32 Rust over 20k cases:
    • Bytes sliced by every kind of range, including out of order, out of bounds, and a..=usize::MAX and ..=usize::MAX, each panicking as bytes does.
    • Bytes made each way and printed with Debug, with every escape class.
    • A copy that a later write to its source must not change.
    • Six events whose fields would push to the output if they were evaluated.
    • random_range across u8, i32, u64 past 2^53, i64 at both ends, i128, inclusive, ..b, and ..=b up to the type's maximum, with empty ranges panicking. A 4-value range past 2^53 must produce every value within 256 draws.
  • Native randomness on wasm32: the native side's rand::rng() reads the fixture's own getrandom backend. Only getrandom's profile rustflags select it, so nothing else rebuilds.
  • Reject fixture: refuses Bytes::from_owner of an Arc<[u8]>.
  • Bind fixture: binds blob::Blob, a small path-dependency type with unsafe inside, in place of bytes::Bytes. It keeps covering a dependency's bound type, including its generic slice and its trait impls.

After merging main (the stock harness now picks its cases from one list, as #164's do; Codex's slice and random_range(..=b) findings are fixed, the slice ones with differential cases that failed before): just check (whole repo), just ts test, and quest check --base origin/main pass. just ts census ran on main and on this branch, with the numbers above.

Choices made without asking

  • ignore-macros is deleted, not kept. Its only consumers were tracing's events. AGENTS.md treats a config key as a gap to close. log's macros aren't left out, so they stay refused.
  • Bytes is rt.ByteSlice itself, not a type of its own. Deref, AsRef, and from_static cost nothing, and a Bytes passes wherever a &[u8] does.
  • ThreadRng is stateless, so a mapped call doesn't read the generator argument. The JS values aren't rand's stream, which no seed reproduces natively either.
  • Mappings are keyed by crate name (bytes, rand) for any version, but never for the crate being translated. rand's methods are matched on either Rng or RngExt, so 0.9 and 0.10 both map. 0.8's gen_range is refused.

Decisions

  • Bytes::from_owner with an owner that drops stays refused (maintainer, 2026-10-28). Matching Rust would make every Bytes a counted value with [Symbol.dispose], at a cost to every Bytes, even the many that never have such an owner.

Follow-ups

  • Bytes that own what drops: worth doing only if moq's safe cfg twin of FrameBuf still needs from_owner.
  • BytesMut, Buf, and BufMut: the model reaches BytesMut (still bound), and moq-net's codec is generic over Buf and BufMut.
  • range.by_ref() on a RangeInclusive mistypes: for _ in r.by_ref() {} then using r as a range fails tsc (Iter<usize> is not a RangeInclusive<usize>). The fixture drains with while r.next().is_some() instead.
  • A closure that captures a u8 by &mut and is called once loses its write: let mut n = 0; let mut f = || { n += 1; n }; f(); n reads 0 in the translation. It's unrelated to this PR, and I found it while writing the fixture.

(written by Claude Opus 5.5)

🤖 Generated with Claude Code

@kixelated kixelated changed the title Well-known crates without config feat(ts): map bytes, rand, and tracing without config Oct 28, 2026
kixelated and others added 5 commits October 28, 2026 08:25
# Conflicts:
#	ts/MIGRATE.md
#	ts/README.md
#	ts/fixtures/bind/wrong.txt
#	ts/fixtures/reject/census.md
#	ts/fixtures/reject/expected.txt
#	ts/fixtures/reject/src/lib.rs
#	ts/fixtures/reject/ts.toml
#	ts/runtime/rt.ts
#	ts/tests/fixtures.rs
…es in a list

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>
@kixelated
kixelated marked this pull request as ready for review October 28, 2026 16:50
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-28T17:20:18.531789Z dd7601f New commits
🔒 Security Review ✅ Completed 2026-10-28T17:22:44.056388Z dd7601f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8c225bbde

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ts/src/lower/stock.rs
Comment on lines +110 to +112
"slice" if b.t.std_of(targ(call.args, 0)) == Some(Std::RangeInclusive) => {
Expr::rt("bytesSliceInclusive", vec![args.next()?, args.next()?])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle RangeToInclusive in Bytes::slice

For valid calls such as bytes.slice(..=end), the MIR interceptor's range_bounds rejects RangeToInclusive, and this fallback only accepts RangeInclusive, so translation reports an unsupported call. This leaves a standard range form unmapped, and the new prohibition on binding Bytes prevents consumers from supplying the missing behavior themselves. Handle RangeToInclusive, including overflow at usize::MAX, and add differential coverage alongside the other slicing forms.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in daf7a06: ..=end reads its bound where the range is made, like the other ranges, and panics with bytes' "out of range" at usize::MAX. The stock fixture slices by ..=b and ..=usize::MAX.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: d8c225b

P2 — ts/src/lower/stock.rs:110–112: I independently confirmed the missing Bytes::slice(..=end) mapping already reported in this finding; no duplicate inline comment. The MIR path rejects RangeToInclusive, and this fallback accepts only RangeInclusive, so valid inclusive-prefix slices cannot translate now that binding Bytes is prohibited. Add the mapping and differential cases for ordinary bounds, out-of-bounds bounds, and usize::MAX overflow.

Direction: reusing ByteSlice and narrowly mapping the unseeded RNG avoids unnecessary wrappers and consumer configuration; refusing dropping owners is a reasonable explicit boundary. No additional actionable bugs found in the inspected changes.

Verification: static review of mappings, runtime helpers, surrounding lowering, and fixture snapshots only; I did not run tests. The PR's separately documented pre-existing exhausted-range discrepancy remains outside this finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: daf7a06f49

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ts/src/lower/mir/call.rs Outdated
Comment on lines +580 to +581
Expr::rt("bytesSlice", std::iter::once(b).chain([start]).chain(end).collect()),
false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the panic effects of intercepted Bytes slices

When a slice's result is unused, marking this call as effect-free makes Fx::set_local discard it, including its bounds checks. For example, let bytes = Bytes::new(); let _ = bytes.slice(1..); must panic in Rust but translates without that panic. The RangeToInclusive interceptor also passes false and has the same problem. Mark both intercepted calls as effectful so unused slices still validate their bounds and panics retain their source location.

AGENTS.md reference: AGENTS.md:L15-L15

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e6fbdf6: both intercepted slices are effectful now. The stock fixture adds an unused bytes.slice(b..) that panics past the end; with the old flag it differed from native in 42 of 20k cases.

(Written by Claude Opus 5.5)

Comment thread ts/runtime/rt/bytes.ts Outdated

/** `Bytes::slice` of `a..=b`, which an exhausted range ends before. */
export function bytesSliceInclusive(b: Bytes, range: RangeInclusive<usize>): Bytes {
if (range.exhausted) return bytesSlice(b, range.lo, range.hi);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge End exhausted inclusive slices at the range's start

When inclusive iteration exhausts by overflowing, lo and hi differ: consuming (usize::MAX..=usize::MAX).next() leaves lo = 0, hi = usize::MAX, and exhausted = true. Its RangeBounds end is then Excluded(lo), as the existing rangeInclusiveEndBound helper also implements, so slicing should produce the empty 0..0 slice. This branch instead slices 0..usize::MAX and panics even for an empty buffer. Use range.lo for both bounds in the exhausted branch and cover an overflow-exhausted range differentially.

AGENTS.md reference: AGENTS.md:L15-L15

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e6fbdf6: an exhausted range slices lo..lo, matching rangeInclusiveEndBound. The stock fixture slices a drained range, a..=b and usize::MAX..=usize::MAX; the old branch differed in 37 cases.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: daf7a06

The prior P2 inclusive-prefix slicing finding is fixed: the MIR interceptor now handles RangeToInclusive, and bytesSliceToInclusive checks usize::MAX before adding one and delegates bounds validation to bytesSlice. The fixture and generated snapshot now cover ..=b and ..=usize::MAX.

No new actionable bugs found in the update from d8c225b. Direction: the small helper and shared range-field extraction address the gap without unnecessary machinery.

Verification: static review of the four changed files and surrounding lowering/runtime behavior; tests were not run.

kixelated and others added 3 commits October 28, 2026 10:05
Keeps bytes and rand out of the dependencies #141 translates from source, so their
mappings win as std's do; translating bytes from source refuses its raw pointers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flow-exhausted range's slice

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	ts/fixtures/reject/census.md

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: d1291d1

Reviewed the substantive update since daf7a06, accounting for the merged base changes. No new actionable bugs found.

Prior findings: the inclusive-prefix mapping remains fixed. The independently reported unused-slice panic and overflow-exhausted slice issues are also fixed: both MIR interceptors are effectful, and exhausted ranges use lo..lo, matching the existing range-bound helper. The fixture and snapshot exercise unused slices and drained ranges.

Direction: preserving stock mappings when dependency translation is enabled is the right integration boundary; these targeted fixes reuse the existing runtime rather than adding parallel representations.

Verification: static inspection of the changed mapping, lowering, runtime and fixtures, including current range semantics; I did not run tests.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1291d129a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ts/src/lower/stock.rs
Comment on lines +179 to +181
Std::RangeTo => Expr::rt("randRange", vec![int.lit(0), range.member("end"), bits, signed]),
Std::RangeInclusive => Expr::rt("randRangeInclusive", vec![range, bits, signed]),
_ => return None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Map unsigned RangeToInclusive in random_range

A valid call such as rand::rng().random_range(..=10u32) falls through to None here and produces an unsupported-call error. Rand implements SampleRange for unsigned RangeToInclusive, sampling from zero through the supplied end (upstream implementation). Since this change excludes rand from normal translation and prohibits binding ThreadRng, consumers must rewrite this standard range form to translate it. Add the missing mapping and differential coverage, including an end of the integer type's maximum value.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dd7601f: ..=b maps to rt.randRangeToInclusive, never empty. The stock fixture draws ..=b of u8 (including 255) and of u64 up to u64::MAX.

(Written by Claude Opus 5.5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed commit: dd7601f

The independently reported unsigned inclusive-prefix random-range gap is fixed. RangeToInclusive now maps to randRangeToInclusive; computing hi + 1 as bigint preserves the full-width maximum without overflow. The fixture and snapshot cover u8 and u64 inclusive prefixes, including their maximum endpoints. Earlier slice fixes are unchanged.

No new actionable bugs found in the four-file update from d1291d1. Direction: the small helper reuses the existing unbiased sampler and avoids unnecessary range objects in the runtime API.

Verification: static review of lowering, helper, surrounding sampling logic and fixture output; tests were not run.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing. Changes since the draft:

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit efdbe97 into main Oct 28, 2026
1 check passed
@kixelated
kixelated deleted the quest/m2/ts-model/stock branch October 28, 2026 17:44
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