Skip to content

feat(ts): key collections by a bound type through its binding's Hash, Eq, and Ord - #299

Merged
kixelated merged 3 commits into
mainfrom
quest/m3/ts-bind-gaps
Oct 30, 2026
Merged

kixelated merged 3 commits into
mainfrom
quest/m3/ts-bind-gaps

Conversation

@kixelated

@kixelated kixelated commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A hash or B-tree collection keyed by a type bound in ts.toml was refused ("hashing/comparing/ordering key::Nibble ..., which is not supported yet" at the collection's new), so a consumer that binds a type such as bytes::Bytes could not key a map by it. Completes quest/m3/ts-bind-gaps.md.

Approach

  • collections.rs key dispatch (eq_fn, hash_fn, cmp_fn, std_key, own_ne, own_test, own_hash_slice, B-tree range) treats a bound type as it does the crate's own: through its Rust impl's instance, which fn_value already resolves to the binding's export. One helper, local, replaces the repeated own checks. No parallel dispatch.
  • rt.hashing gets all = true for a bound key (or an Option/Reverse of one), since a Copy binding may be a JS primitive whose === disagrees with its Eq, as feat(ts): translate accessors of elements whose None is rt.None #292 did for fieldless enums.
  • own_ord counts a bound Ord as hand-written, so a B-tree collect of entries that drop is refused as for a hand-written Ord.
  • A binding missing an export a key needs is refused by the existing tsc check at that impl's span (see wrong.txt).
  • Docs: ts/README.md Bindings states the contract: hash$Hash(self, state: rt.Hasher): undefined and eq$PartialEq for hash collections, cmp$Ord for B-trees, partialCmp$PartialOrd for a B-tree's range, each agreeing with the Rust impl.

Consumer: none in moq today (ts/moq/ts.toml binds nothing); this closes the m3 bindings-contract gap #127 recorded, for any consumer keying a map by a bound type. Exercised by the bind fixture.

Tests (bind fixture, differential against native wasm32):

  • Blob ([u8]-like, Hash/Ord as bytes::Bytes) keys HashSet, HashMap (entry), BTreeSet, BTreeMap; a fresh copy finds slices of a different buffer (Hash and Eq must agree), plus contains, get, remove, range, and B-tree order.
  • key::Nibble, a Copy binding that is a JS number equal by its low 4 bits: which key each collection keeps, insert results, lookups, and order. Dropping all = true makes the differential fail (14601 differences).
  • wrong.toml: a Blob binding lacking hash$Hash, cmp$Ord, and partialCmp$PartialOrd fails at each impl.

Impact

  • ts.toml bindings (additive): a bound type may key HashMap, HashSet, BTreeMap, BTreeSet, alone or in a tuple, Option, or Arc, given hash$Hash, eq$PartialEq, cmp$Ord, and partialCmp$PartialOrd (for range) exports. Nothing that translated before changes requirements, so no MIGRATE.md entry.

Alternatives

  • A separate binding hook (e.g. a ts.toml key naming hash/eq functions): rejected, since the Rust impls already name the exports and tsc already checks them.
  • Hashing a bound key by JS identity: wrong for any binding whose Eq is not ===.

Choices made without asking

  • The quest said the refusal was at the Rust item's span; it was actually at the collection's span. Fixed either way.
  • Added key::Nibble to the bind fixture as a primitive-valued binding, since Blob alone can't show the all flag.

Follow-ups

None.

Review

  • Round 1: quest review --deep, Codex gpt-6-astra at medium effort, head d591e9eae8d2910b82a4d6520aa14bef90621686 against origin/main f7f0e722: no findings.
  • Local checks: just ci passed on d591e9ea.

Clean merge of main at 955ac19f (#296, #297, #300) into d591e9ea, as 83c8978c. Recorded skip, too trivial to review; just ci passed on 83c8978c.

🤖 Generated with Claude Code

(written by Claude Opus 5.5)

kixelated and others added 2 commits October 30, 2026 11:31
Quest-Claim: 0e312ce6be571f3a
… Eq, and Ord

A hash or B-tree key holding a bound type, alone or in a tuple, Option, or
Arc, hashes, compares, and orders through the exports of its Rust impls,
as one of the crate's own types does through its translated impls. A
hash collection finds even a bound value that is a JS primitive by them.
A binding missing an export a key needs fails at that impl's span.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Binding gaps feat(ts): key collections by a bound type through its binding's Hash, Eq, and Ord Oct 30, 2026
@kixelated
kixelated marked this pull request as ready for review October 30, 2026 18:59
@coderabbitai

coderabbitai Bot commented Oct 30, 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 13 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: 584200b5-1208-4d37-ae20-870bbd2b396a
Commits

Reviewing files that changed from the base of the PR and between 955ac19 and 83c8978.

Files selected for processing: 18
  • quest/m3/README.md
  • quest/m3/ts-bind-gaps.md
  • ts/README.md
  • ts/fixtures/bind/bind/blob.ts
  • ts/fixtures/bind/bind/nibble.ts
  • ts/fixtures/bind/blob/src/lib.rs
  • ts/fixtures/bind/expected/bind/blob.ts
  • ts/fixtures/bind/expected/bind/nibble.ts
  • ts/fixtures/bind/expected/core/option.ts
  • ts/fixtures/bind/expected/harness.ts
  • ts/fixtures/bind/refused.txt
  • ts/fixtures/bind/src/harness.rs
  • ts/fixtures/bind/src/key.rs
  • ts/fixtures/bind/src/lib.rs
  • ts/fixtures/bind/ts.toml
  • ts/fixtures/bind/wrong.txt
  • ts/fixtures/bind/wrong/blob.ts
  • ts/src/lower/collections.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

Comment @coderabbitai help to get the list of available commands.

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

No actionable bugs found. Reusing resolved Rust trait instances for bound keys is a sound direction and avoids a parallel binding-dispatch mechanism. Forcing custom hashing/equality for primitive-valued bindings addresses the important representation mismatch, while retaining the existing refusal paths for unsupported comparison/hash overrides and drop-sensitive B-tree collection.

The Blob and Nibble fixtures exercise both object-valued and primitive-valued bindings. Consider adding nested Option/Reverse/tuple/Arc bound-key cases to directly cover the documented wrapper support and the all-hashing flag propagation.

Verification: static review of the lowering changes, surrounding key dispatch, documentation and fixtures only. Tests, builds and the reported differential/mutation checks were not run or independently verified.

@kixelated
kixelated merged commit 1d3a8e0 into main Oct 30, 2026
1 check passed
@kixelated
kixelated deleted the quest/m3/ts-bind-gaps branch October 30, 2026 19:21
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