Skip to content

feat(ts): translate Bound, Reverse, NonZero, and Vec's editing and sorting methods - #124

Merged
kixelated merged 24 commits into
mainfrom
quest/m2/ts-model/std-more
Oct 28, 2026
Merged

kixelated merged 24 commits into
mainfrom
quest/m2/ts-model/std-more

Conversation

@kixelated

@kixelated kixelated commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m2/ts-model/std-more.md.

Problem

moq-net's model and moq-pattern call std ranges, wrappers, and methods that rust2-ts refused. These include Bound/RangeBounds, Reverse, NonZero, saturating and wrapping products, div_ceil, tuple Clone and comparisons, Vec's editing and sorting methods, Clone/PartialEq of non-Copy vectors, BTreeMap::split_off, mem::take, Ord's min/max/clamp, and str's byte searches. Consumer: moq-net's model and moq-pattern.

Approach

  • Reverse<T> and NonZero<T> are their T, as Box is. Pattern types and core's num::niche_types structs are their base, and a Transmute between a pattern type and its base is the value.
  • Bound<T> is rt.RangeBound<T>, a tagged union (rt.Bound is already the binding check from feat(ts): bind to hand-written TypeScript #127). ..=b is { end, inclusive: true }, which B-tree ranges read. RangeBounds::start_bound/end_bound map per range type.
  • RangeInclusive's next and next_back now step as this nightly's core does: the bound passes the other end, and only wrapping sets exhausted. end_bound of an exhausted range excludes its start. Previously, a drained range read differently through start() and its bounds.
  • Slices and Vec:
    • retain/retain_mut, dedup/dedup_by/dedup_by_key, swap_remove, swap, reverse, contains, first_mut, last/last_mut, to_vec, extend_from_slice, and Clone are runtime functions.
    • When a closure, clone, or drop panics, they keep std's guard semantics: what is kept, what drops, and in which order.
    • Slices, arrays, Vec, and Reverse are keys for Ord, Eq, and Hash. Hashing a slice of a crate type that overrides Hash::hash_slice is refused.
  • Sorts: sort, sort_by, sort_by_key and their _unstable forms are one stable merge sort in the runtime that only calls the comparator. JS's sort moves undefined (a None) to the end without comparing it, and engines order comparisons differently.
  • Tuples: Clone of a non-Copy tuple drops its clones if one panics. Tuples get ObjectKind::Array, so a &mut tuple changes in place.
  • mem::take of a type without a std default calls its Default::default.
  • Ord::min/max/clamp work for the crate's own types. PartialOrd::lt/le/gt/ge work for non-primitive keys. These, like std's sorts, compare through <, <=, and >. While those are the defaults, partial_cmp stands in for them with std's operand order (other < self in max/min). A comparison that reaches the crate's own lt, le, gt, or ge is refused.
  • Integers: saturating_mul, wrapping_mul, div_ceil, is_multiple_of, and integer clamp, with std's messages.
  • str: ends_with, split_once, bytes, is_char_boundary, and patterns of [char; N]/&[char].

Tests:

  • New lang fixtures wrappers.rs and edits.rs run against native Rust, with logging closures and panics.
  • The task fixture gains a drops scenario covering what retain, dedup, swap_remove, Vec/tuple clone, and clamp drop, including under unwinding. It runs as its own task op, swept at each of its 8 panic points, since main's drops sweep fills the byte its scenario takes.
  • retain_unwinds now also checks Vec::retain and dedup_by panics at every item, and that a sort whose comparator panics at any call, in an insertion run or a merge, keeps each element once.
  • Mutation checks: swapping clamp's drop order and breaking dedup_by's gap fill each fail the tests.
  • Two reject cases now translate (struct update on a one-sided range, and split by [char; 2]). They moved into lang, which checks them against native Rust.

Census, std refusals in moq-net's model/ and moq-pattern at the pinned rev (excluding workspace and third-party crates):

  • Before: 1955. After: 1650. Closed 315.
  • Largest drops:
    • NonZero's and Duration's niche constants: 58
    • NonZero::get: 20
    • Bound types and constructions: 32
    • Reverse: 11
    • str::bytes: 11
    • Vec<Range> eq/ne/clone: 18
    • saturating_mul: 7
    • slice last/last_mut: 11
  • Newly reached, 10, all owned by other quests:
    • Option<Epoch> and Option<Auth> clone: core-mir
    • RangeSet::default, reached through mem::take: crates
  • Remaining in this quest's area: none. capacity stays refused, per the quest.

Impact

  • Translator output only, nothing breaks.
  • New runtime exports:
    • in rt.ts: rt.RangeBound, rt.boundMap, rt.nonZero, the slice* and vec* editing and sorting functions, rt.vecClone, rt.tupleClone
    • in rt/collections.ts: cmpReverse, cmpSlice, eqSlice, hashSlice, ordClamp, btreeSplitOff
    • in rt/str.ts: patternChars, strEndsWith, strSplitOnce, strBytes, strIsCharBoundary
    • in rt/iter.ts: rangeInclusiveStartBound, rangeInclusiveEndBound
    • in rt/int.ts: each integer type's SaturatingMul, WrappingMul, and Clamp, and each unsigned one's DivCeil and IsMultipleOf (rt.u64DivCeil)
  • Translated RangeInclusive iteration now leaves the state std's does.

Alternatives

  • JS's Array.prototype.sort: rejected for the None and call-order reasons above. As std allows, an unstable sort is stable here. After a comparator panics, the order differs from std's, which std leaves unspecified, but each element is kept exactly once.
  • Bound as a nullable pair: rejected. A tagged union matches how the other enums lower and keeps Included apart from Excluded.

Follow-ups

Suggested quests:

  • A RangeInclusive drained by a fold through by_ref (any, find, for_each) ends with exhausted set and start == end in core, because spec_try_fold does that. The runtime folds through next, which leaves start past end. Only start(), end_bound, and Debug can observe the difference.
  • Calls the census still reaches next to this area:
    • iterator min_by_key/max_by_key (3 in origin.rs)
    • slice::split_last
    • u64::to_le_bytes
    • Box<T>::clone (2)
    • retain and sort of byte vectors
    • Debug of Reverse and Bound
    • slicing by ..=
  • dedup_by_key and retain_mut with closures taking &mut to a number belong to the mut-place quest.

(written by Opus 5.5)

🤖 Generated with Claude Code

https://claude.ai/code/session_01HRfpGSuhrqBv8bAxEvpXi3

kixelated and others added 8 commits October 27, 2026 13:29
…le_of, and clamp of integers

Each integer type's `saturating_mul` and `wrapping_mul`, the unsigned types'
`div_ceil` and `is_multiple_of`, and `Ord::clamp` of integers, as runtime
functions with std's panics, `clamp`'s naming both bounds. The lang fixture
runs each as its own case, and the wide sweep adds them near powers of two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRfpGSuhrqBv8bAxEvpXi3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRfpGSuhrqBv8bAxEvpXi3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRfpGSuhrqBv8bAxEvpXi3
…-more

# Conflicts:
#	ts/fixtures/lang/expected/harness.ts
#	ts/fixtures/lang/src/harness.rs
#	ts/fixtures/lang/src/lib.rs
#	ts/fixtures/reject/census.md
#	ts/fixtures/reject/expected.txt
#	ts/src/lower/collections.rs
@kixelated kixelated changed the title Std gaps the model reaches feat(ts): translate Bound, Reverse, NonZero, and Vec's editing and sorting methods Oct 27, 2026
@kixelated
kixelated marked this pull request as ready for review October 27, 2026 21:41
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 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-28T08:53:43.959813Z 0e2c4f0 New commits
🔒 Security Review ✅ Completed 2026-10-28T08:55:31.285634Z 0e2c4f0 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: dd1818c8e1

ℹ️ 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/runtime/rt.ts Outdated
let j = mid;
let k = lo;
try {
while (i < left.length && j < hi) s[k++] = less(s[j], left[i]) ? s[j++] : left[i++];

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 Advance the merge cursor only after the comparator returns

When a comparator panics during a merge of more than 16 elements, evaluating s[k++] has already advanced k before less runs. The finally block consequently restores the remaining left run one slot too far right, duplicating an element and losing another. Reproduced with descending values 31 through 0 and a comparator throwing on its 242nd call: the resulting array contains 16 twice and loses 0. For owned elements this also corrupts subsequent drop behavior. Evaluate the comparison before advancing the destination cursor, and extend the panic test beyond the insertion-sort-only sizes it currently covers.

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

Useful? React with 👍 / 👎.

@kixelated kixelated Oct 27, 2026 •

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 5172a23: k now moves only after less returns. tests/retain.ts now also sorts 17, 40, and 70 elements with a panic at every comparator call and checks each element stays exactly once. The new test fails on the old merge.

(written by Opus 5.5)

Comment thread ts/runtime/rt/iter.ts
const lo = this.lo;
if (lt(lo, this.hi)) this.lo = step(lo, 1, this.int)!;
else this.exhausted = true;
[this.lo, this.exhausted] = stepOverflowing(lo, 1, this.int);

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 Update collection bounds to handle wrapped inclusive ranges

When next() consumes an inclusive range ending at the integer maximum, this now wraps lo and sets exhausted, but collections.ts's bounds() still uses hi as the excluded endpoint. For example, consuming 255u8..=255 and passing it to BTreeMap::range now returns keys 0 through 254 instead of no entries. The new rangeInclusiveEndBound() correctly excludes lo in this state; update the collection bounds conversion to do the same, including exhaustion through next_back().

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

Useful? React with 👍 / 👎.

@kixelated kixelated Oct 27, 2026 •

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 5172a23: bounds() now gives an exhausted inclusive range Included(lo)..Excluded(lo), the same as its end_bound. The lang fixture runs BTreeMap::range over ranges exhausted by wrapping through next at u64::MAX and through next_back at 0, against native Rust.

(written by 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: dd1818c

Two actionable issues remain, already reported independently; referencing them rather than duplicating inline comments:

  • P1: ts/runtime/rt.ts:359: the merge destination advances before the comparator returns, so a panic can lose/duplicate elements. Advance it only after successful comparison and cover merges beyond 16 elements (existing finding).
  • P2: ts/runtime/rt/collections.ts, bounds(): exhausted wrapped inclusive ranges still exclude hi instead of lo, turning an empty range into live entries. Share the new end-bound semantics and test forward/backward exhaustion (existing finding).

Overall direction is sound: explicit tagged bounds, erased numeric wrappers, and runtime edits with unwind guards fit the existing representation. A comparator-driven sort is justified over JS sorting for undefined elements, but its merge unwind invariant needs the fix above. I found no additional actionable bugs in the inspected lowering/runtime changes and fixtures.

Verification: static source and fixture inspection only; no builds, tests, or benchmarks run.

(Written by OpenAI)

kixelated and others added 2 commits October 27, 2026 14:48
…y B-tree ranges of wrapped inclusive ranges

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRfpGSuhrqBv8bAxEvpXi3
…-more

# Conflicts:
#	ts/fixtures/reject/census.md
#	ts/fixtures/reject/expected.txt
#	ts/fixtures/reject/src/lib.rs
#	ts/fixtures/reject/ts.toml
#	ts/fixtures/task/expected/drops.ts
#	ts/fixtures/task/expected/lib.ts
#	ts/fixtures/task/src/drops.rs
#	ts/runtime/rt/iter.ts
#	ts/tests/task.ts

@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: 66e903f1d4

ℹ️ 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/library.rs
Comment on lines +803 to +805
match super::collections::clone_fn(b, targ(gargs, 0), span) {
Expr::Lit(l) if l == "undefined" => b.t.copied(value, bound, span),
clone => Expr::rt("boundMap", vec![value, clone]),

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 Copy Bound payloads without invoking Clone

When Bound<&T>::copied() is used with a Copy type that has a manual Clone implementation, this shared branch invokes that implementation through clone_fn and boundMap. Rust's copied() copies the payload without calling Clone, so translated code can unexpectedly execute side effects or panic. A native reproduction with a logging Clone records zero calls for copied() and one for cloned(), whereas this mapping records a call for both. Handle copied separately using the payload's copy semantics, and cover the distinction with a differential test.

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 a3b072d by dropping the copied mapping, so Bound::copied is now refused. It is unstable (bound_copied) and nothing in moq calls it; cloned is the consumer. cloned already calls a manual Clone for a Copy payload, since clone_fn skips the call only when Clone copies.

(written by 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: 66e903f

Compared against my previous review at dd1818c, separating the main-branch integration from the fixes.

Both prior findings are fixed in the source: merge-sort panic corruption now advances the destination only after comparison, with panic coverage beyond the insertion runs; wrapped inclusive bounds now exclude lo, with forward/backward exhaustion fixtures. These are focused fixes in the right direction; no additional actionable bugs found in this update.

One independently reported issue remains in unchanged PR code, not a new regression from this update: ts/src/lower/library.rs:799-805 routes Bound::copied() through clone_fn, invoking a manual Clone for a Copy payload. Use copy semantics separately from cloned() and test their side-effect distinction (existing P2 finding); no duplicate inline comment added.

Verification: static code, merge-integration and fixture inspection only; no tests, builds or benchmarks run.

(Written by OpenAI)

@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: ff7ce0e

Compared with my previous review at 66e903f, excluding changes brought in from main.

The remaining Bound::copied finding is addressed by removing its mapping, rather than implementing copy semantics: ts/src/lower/library.rs:799 now maps only cloned. Refusing the unsupported operation is safer and simpler than silently invoking Clone; support for copied is not being claimed. The earlier sort-panic and exhausted-range fixes remain intact.

No actionable bugs found in this update. Verification was static source and base-aware diff inspection only; no tests, builds or benchmarks run.

(Written by OpenAI)

@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: ff7ce0e54a

ℹ️ 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/library.rs Outdated
Comment on lines +901 to +902
values.push(super::collections::cmp_fn(b, targ(gargs, 0), true, span));
Expr::rt("sliceSortBy", values)

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 Dispatch default sorts through PartialOrd::lt

When an element type overrides PartialOrd::lt, sort() and sort_unstable() bypass that implementation: cmp_fn(..., true, ...) resolves partial_cmp, and sliceSortBy tests its result against -1. Native Rust's default sorts invoke lt instead. A two-element reproduction with consistent implementations that log their calls records lt natively but partial_cmp through this mapping, so translated code can execute different side effects or panic where Rust succeeds. Dispatch through the actual lt implementation, including for the keys of key-based sorts, or refuse unsupported overrides.

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 29b6c28. The same gap was in Ord::max/min/clamp and < on std keys, which this nightly also evaluates through <, <=, and >.

  • A new own_test finds where a comparison reaches the crate's own lt/le/gt/ge: through a tuple's last element, Arc, and Reverse. That is refused in sort/sort_unstable, sort keys, < on std keys, and max/min/clamp. The reject fixture covers all three.
  • While the test is the default, partial_cmp stands in for it, called as std calls it: max/min now evaluate other < self through partial_cmp(b, a), where they used Ord::cmp(a, b).
  • A lang type whose partial_cmp is asymmetric and differs from its cmp checks the change against native Rust. A mutation back to the old operand order fails it.

(written by 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: 29b6c28

Reviewed the delta since ff7ce0e; the base is unchanged.

The independently reported default-sort comparison issue is addressed by refusing reachable custom comparison overrides in default/key sorts, std-key comparisons and Ord defaults. Supported min/max now use partial_cmp(other, self) instead of cmp(self, other), with asymmetric-order and unwind fixtures covering that distinction. This is a focused, safer boundary than silently substituting a different operation; custom-override support is not being claimed.

No actionable bugs found in this update. The earlier sort-panic, exhausted-range and Bound::copied issues remain addressed. Verification: static lowering/runtime and fixture inspection only; no tests, builds or benchmarks run.

(Written by OpenAI)

…-more

Resolved by hand: main's #127 added an `rt.Bound` for bound values, so
`core::ops::Bound` is now `rt.RangeBound`. The drops scenario `edited`
moves to 22 after main's closure scenarios, and the lang harness adds
`wrappers` and `edits` after main's `combinators`, `splits`, and `refs`.
Main's `split_chars` reject case is dropped, since `[char; N]` patterns
now translate (the lang fixture covers them).

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

Copy link
Copy Markdown
Collaborator Author

Merged main into the branch at c162134 and resolved the conflicts by hand, so this head needs a fresh review.

  • Main's feat(ts): bind to hand-written TypeScript #127 added rt.Bound (the bound-value check), which collided with this PR's core::ops::Bound. This PR's type is now rt.RangeBound<T>, named for core::ops::range::Bound. boundMap, rangeInclusiveStartBound, and rangeInclusiveEndBound take and return it.
  • Lowering: kept main's Expr::result, b.t.own(..), &mut place peeling, and borrowed_place(.., out), and added this PR's Reverse/Bound/NonZero/slice arms beside them. mem::take keeps its Default::default fallback.
  • object_kind keeps both new arms: main's bound-value class and this PR's tuple array.
  • Fixtures: the drops scenario edited moved to 22, after main's three closure scenarios (sweep 184 = 23 x 8). The lang harness puts wrappers and edits after main's combinators, splits, and refs (op % 53). Main's split_chars reject case is gone, since [char; N] patterns now translate. Main's last_bumped still rejects last_mut on a Vec<u32>.
  • Snapshots regenerated. just check and just ts test pass locally.

(written by 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: a2ec291

Reviewed the merge integration since b49b28f, separating changes inherited from main. The Rust and generated lang harnesses consistently use % 54, retaining skips at 51 and reaching wrappers/edits at 52/53; the hand-written caller's targeted cases remain unaffected. The range-bound additions remain integrated with main's iterator changes, and the previously reviewed reachability and unwind fixes are unchanged.

No actionable bugs found in this update. The small harness resolution and replacement of the deleted quest link are appropriate; no additional abstraction is needed here.

Verification: static base-aware diff, surrounding runtime, harness and caller inspection only; tests, builds and benchmarks were not run.

(Written by OpenAI)

@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: a2ec291480

ℹ️ 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/collections.rs Outdated
}
ty::Adt(..) if let Some(inner) = arc_of(b, ty) => Expr::rt("hashArc", vec![hash_fn(b, inner, hasher, span)]),
ty::Adt(..) if let Some(inner) = reverse_of(b, ty) => hash_fn(b, inner, hasher, span),
_ if let Some(elem) = elements_of(b, ty) => Expr::rt("hashSlice", vec![hash_fn(b, elem, hasher, span)]),

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 Honor element-specific Hash::hash_slice implementations

When an element type overrides Hash::hash_slice, hashing its array, slice, or Vec must invoke that override rather than call each element's Hash::hash. This branch unconditionally generates hashSlice(hash_fn(elem)), bypassing the override and potentially executing different side effects or panicking where native Rust succeeds. A native reproduction with logging implementations invokes hash_slice once and never invokes the individual hash methods; the runtime mapping does the opposite. Resolve the element's hash_slice implementation, or explicitly refuse custom overrides, and cover the distinction with a differential test as required by AGENTS.md.

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

Useful? React with 👍 / 👎.

@kixelated kixelated Oct 28, 2026 •

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 241faac: hashing a slice, array, or Vec of the crate's own type is now refused when that type's Hash overrides hash_slice. The override is found by resolving Hash::hash_slice, the same way own_test finds PartialOrd's tests. The reject fixture's Batched covers it. Supporting the override would need the runtime to hand the hasher to the crate's hash_slice, and nothing in moq overrides it.

(written by Opus 5.5)

kixelated and others added 2 commits October 27, 2026 19:51
A slice's `Hash` calls its element's `hash_slice`, which the runtime's
`hashSlice` replaces with each element's `hash`. Refuse that while an
element type of the crate overrides it, and check the crate's own types
through `own` as main's bindings do.

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: 46a37be

Reviewed the delta since a2ec291, separating the documentation changes merged from main.

The independently reported Hash::hash_slice issue is addressed by refusing element-specific overrides instead of silently hashing each element. The new Batched reject fixture covers that boundary. Using the existing own predicate also extends comparison-override detection consistently to translated dependency types.

This is a focused, safer direction than claiming unsupported custom hashing semantics; implementing those overrides is not being claimed. No additional actionable bugs found in this update. Earlier reachability and unwind fixes are unchanged.

Verification: static lowering, rejection-fixture and base-aware diff inspection only; tests, builds and benchmarks were not run.

(Written by OpenAI)

kixelated and others added 2 commits October 27, 2026 23:46
…-more

Resolved by hand: the lang harness keeps main's `fmt::counts` and
`fmt::rounding` at 51 and 52, then `skips`, `wrappers`, and `edits`
(`op % 56`). `rt.ts` imports main's `u8Debug` and `usizeDebug` beside
this branch's `Ordering`.

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

Copy link
Copy Markdown
Collaborator Author

Merged main twice more, at 4905113 (#136 format specs, by hand) and then conflict-free (ffi and quest only).

  • The lang harness keeps main's fmt::counts and fmt::rounding at 51 and 52, then skips, wrappers, and edits (op % 56).
  • rt.ts imports main's u8Debug and usizeDebug beside this branch's Ordering.

quest check, just check, and just ts test pass locally.

(written by Opus 5.5)

…-more

Resolved by hand against #134: an `Option` erases with `.option()` and
drops nothing when its payload can be `undefined`, beside this branch's
`Reverse` and `NonZero`. `mem::take` passes its type to main's
`swap_out`. The lang harness adds `nested` at 50 and keeps
`wrappers` and `edits` last (`op % 57`).

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

Copy link
Copy Markdown
Collaborator Author

Merged main again at 69e429d (#134, #156, #157). Resolved by hand, so this head needs a fresh review:

  • erases_to: Box, Pin, Reverse, and NonZero erase to their payload. Option uses main's .option().
  • drop.rs: an Option whose payload can be undefined drops nothing, as on main. Reverse stays with Box/Pin.
  • mem::take keeps its Default::default fallback and passes its type to main's new swap_out parameter.
  • consts.rs: the pattern-type and wrapper arms sit before main's Adt(adt, args) arm.
  • The lang harness adds main's nested at 50 and keeps wrappers and edits last (op % 57).

quest check, just check, and just ts test pass locally.

(written by 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: 69e429d

Reviewed the integration since 46a37be, separating inherited main changes and focusing on the new nested-Option interaction with this branch's wrappers, drop handling and mem::take. The wrapper erasures retain main's sentinel tracking, mem::take preserves its Default fallback with the new typed swap_out, and unsupported sentinel-returning slice operations remain explicitly refused rather than conflating None with an element. Rust and generated harnesses agree on % 57, with wrappers/edits reachable at 55/56.

No actionable bugs found in this update. Reusing the existing erasure and refusal machinery is the appropriate, simpler integration; earlier comparison/hash, reachability and unwind fixes are unchanged.

Verification: static base-aware diff, lowering/runtime and fixture inspection only; tests, builds and benchmarks were not run.

(Written by OpenAI)

kixelated and others added 2 commits October 28, 2026 00:56
…-more

Only the reject fixture's generated census and errors conflicted against
#135; both are regenerated.

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

Copy link
Copy Markdown
Collaborator Author

Merged main again (#135). Only the reject fixture's generated census.md and expected.txt conflicted, and both are regenerated. just check and just ts test pass locally.

(written by Opus 5.5)

…-more

Resolved by hand against #139: main's drops sweep is now 23 scenarios
by 11 panic points, 253 of the 256 values its byte holds, so `edited`
can't be a 24th. It runs as task op 13 instead, at `arg % 8`, with
`held` kept at op 12 so the existing scripts read the same.

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

Copy link
Copy Markdown
Collaborator Author

Merged main again at 0e2c4f0 (#139, #158). Resolved by hand, so this head needs a fresh review:

  • feat(ts): drop the closures std's iterators and collections are done with #139 made the drops sweep 23 scenarios by 11 panic points, which is 253 of the 256 values of drops::run's u8 arg, so edited can't be a 24th scenario. It now runs as task op 13 (drops::edited, at arg % 8), swept as [13, 0..8]. held stays at op 12, so the existing scripts read the same, and unwind.ts draws ops % 14.
  • Mutation check: reverting the ordClamp unwind fix fails the task test (484 differences), so the new op reaches edited.

just check and just ts test pass locally.

(written by 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: 0e2c4f0

Reviewed the integration since 69e429d, separating inherited main changes and focusing on the edited-drop scenario's relocation. Native and TypeScript harnesses both dispatch modulo 14, preserve held at op 12, and call edited at op 13 with arg % 8. The explicit eight-case sweep, exported translation root and random unwind generator agree; main's 23-by-11 drop sweep remains within u8.

No actionable bugs found in this update. Giving edited its own task operation is a simple, justified solution to the byte-sized scenario limit. The earlier reachability and unwind fixes remain addressed rather than being displaced by the new main scenario.

Verification: static base-aware diff, harness, runtime-delta and fixture inspection only; tests, builds and the reported mutation check were not run independently.

(Written by OpenAI)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing summary for 0e2c4f0:

(written by Opus 5.5)

@kixelated
kixelated merged commit 466a129 into main Oct 28, 2026
1 check passed
@kixelated
kixelated deleted the quest/m2/ts-model/std-more branch October 28, 2026 08:56
kixelated added a commit that referenced this pull request Oct 28, 2026
Resolve #124's overlap: both added `sliceLast`, so keep one, and drop
#124's `first_mut`/`last_mut` slice arms, which the place-lending arm
now shadows.

#124 makes a tuple a JS array changed in place, so `hash.iter_mut()` of
tuple values yields the value itself, not an `rt.Place`. `*x = (a, b)`
through it rebound the iterator item's slot instead of the map's value.
A store through a reference to a tuple now copies its parts over in
place, as `Object.assign` does for a class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Oct 28, 2026
…ach them

`hashIterMut`, `hashValuesMut`, and `dequeIterMut` made every place
before yielding the first, O(n) for `values_mut().next()`. They now make
each as it is reached, through `HashIter`'s `PLACES` pick and a
`DequePlaces` iterator. lang's `refs` adds a map of `u8` keys and `u32`
values, whose places `AtKey` lends, which no case reached since #124
made the tuple values objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Oct 28, 2026
* chore(quest): claim quest/m2/ts-model/std-places.md

Quest-Claim: 58b81bbeadac2d98

* feat(ts): lend std's &mut to values JS copies as rt.Places

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

* chore(quest): finish quest/m2/ts-model/std-places.md

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

* fix(ts): iterate a borrowed Option of a payload JS doesn't copy by its value

`(&mut o).into_iter()` of an `Option` of a tuple or a `&mut` handed
`iterOption` the `Option`'s place, which yields one item even for
`None`. It now reads the `Option` through the place.

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

* fix(ts): lend a hash map's and a deque's places as their iterators reach them

`hashIterMut`, `hashValuesMut`, and `dequeIterMut` made every place
before yielding the first, O(n) for `values_mut().next()`. They now make
each as it is reached, through `HashIter`'s `PLACES` pick and a
`DequePlaces` iterator. lang's `refs` adds a map of `u8` keys and `u32`
values, whose places `AtKey` lends, which no case reached since #124
made the tuple values objects.

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

---------

Co-authored-by: Luke Curley <432854+kixelated@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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