Skip to content

feat(ts): hold a crate's own BuildHasher in a hash map or set - #509

Merged
kixelated merged 13 commits into
mainfrom
quest/m3/ts-map-own-hasher
Nov 3, 2026
Merged

kixelated merged 13 commits into
mainfrom
quest/m3/ts-map-own-hasher

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A std or hashbrown HashMap/HashSet built with a crate's own BuildHasher didn't translate. A key that is a JS object was refused with "hashing by the hasher of _". With a primitive key, with_hasher(s) translated but threw s away, so its Drop never ran and hasher() was refused.

Approach

Hash around it, as the maintainer decided. The map keeps finding keys with the runtime's own Mixer, through the key's Hash for std's DefaultHasher, and never calls the crate's hasher. What it does with S follows hashbrown:

  • rt.HashMap gains s, the map's own BuildHasher (anything but RandomState). with_hasher and with_capacity_and_hasher keep it, and hasher() lends it.
  • S::default() runs wherever std's makes one: Default, collect/from_iter, mem::take, and |, &, -, ^ of sets.
  • clone clones S before the entries. Drop glue drops S before the entries (hashbrown's field order). into_iter, into_keys, and into_values drop it immediately.
  • hashbrown's map from_iter takes the items and reads their size hint before making S (rt.hashFromIter), unlike std's map and set and hashbrown's set.

Differential tests against native wasm32: lang/hashes maps (std) and stock maps::own_hasher / maps::own_from_iter (hashbrown). They log each S's default, clone, and drop, interleaved with value clones/drops, iterator size_hint, and hasher().hash_one(..). Maps print sorted contents; only one-entry maps print drop and clone order, since std's iteration order is its own.

Decision

Decided by the maintainer: hash around the crate's BuildHasher. The runtime never calls its hasher, which extends the README's rule that how often the runtime hashes is its own. A hasher that logs its calls sees none from the map, and one that panics on misuse (nohash-hasher's debug check) does not panic as it does natively. Calling through it could not match native exactly anyway: native rehashes every key as it grows by hashbrown's bucket counts, which the runtime does not follow (decided 2026-11-01).

Impact

  • Translator output only: rt.HashMap has a new field s. rt.hashNew, hashWithCapacity, hashClone, hashDrop, hashIter, hashKeys, and hashValues take an optional trailing state argument, and rt.hashFromIter is new. Output for maps without their own BuildHasher is unchanged.

Alternatives

  • Hash through it: call build_hasher, Hash, and finish per operation for every key, primitives too. It matches native except on growth, but it gives up the JS Map fast path for primitive keys and needs a larger runtime change. The maintainer chose against it.
  • Storing RandomState too, so hasher() works on a default map: it's a separate gap, costs a RandomState per map, and has no consumer.

Follow-ups

Review

Codex gpt-6.1-sol, medium effort, quest review, head 00de759600eb2e9f7582b994ba0c62cbd18b0b37: round 3 reported no findings.

  • Round 1: hashbrown's map from_iter must call into_iter before S::default(). Fixed, with a differential case.
  • Round 2: it must also read size_hint before S::default(). Fixed with rt.hashFromIter, with a differential case using a logging size_hint.

Head 742f7a70044068fe6f0eb908701edaca35b98606 merges origin/main after that review. The PR's code diff is unchanged; the merges changed only the stock fixture's case list and the hashes and stock snapshots (main's new case, and #520's parentheses), so no new review was needed. just ci passed on 742f7a70044068fe6f0eb908701edaca35b98606.

(written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 7 commits November 3, 2026 01:32
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…BuildHasher

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er in from_iter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Hasher in from_iter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title A std map with a crate's own BuildHasher feat(ts): hold a crate's own BuildHasher in a hash map or set Nov 3, 2026
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 14:49
@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 37 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: 2164231e-2bb2-4972-886e-f4f3189d2764
Commits

Reviewing files that changed from the base of the PR and between 404992c and 742f7a7.

Files selected for processing: 26
  • quest/m3/README.md
  • quest/m3/ts-build-hasher-default.md
  • quest/m3/ts-hash-map-eq.md
  • quest/m3/ts-map-own-hasher.md
  • ts/README.md
  • ts/fixtures/lang/hashes/expected/harness.ts
  • ts/fixtures/lang/hashes/expected/hashes.Fnv.ts
  • ts/fixtures/lang/hashes/expected/hashes.Key.ts
  • ts/fixtures/lang/hashes/expected/hashes.Noisy.ts
  • ts/fixtures/lang/hashes/expected/hashes.Numbered.ts
  • ts/fixtures/lang/hashes/expected/hashes.ts
  • ts/fixtures/lang/hashes/expected/lib.ts
  • ts/fixtures/lang/hashes/src/hashes.rs
  • ts/fixtures/lang/hashes/src/lib.rs
  • ts/fixtures/stock/expected/core/option.ts
  • ts/fixtures/stock/expected/harness.ts
  • ts/fixtures/stock/expected/lib.ts
  • ts/fixtures/stock/expected/maps.Hinted.ts
  • ts/fixtures/stock/expected/maps.Made.ts
  • ts/fixtures/stock/expected/maps.Seeded.ts
  • ts/fixtures/stock/expected/maps.ts
  • ts/fixtures/stock/src/harness.rs
  • ts/fixtures/stock/src/maps.rs
  • ts/runtime/rt/collections.ts
  • ts/src/lower/collections.rs
  • ts/src/lower/drop.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 15:06
@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 93df389 Nov 3, 2026
5 checks passed
@kixelated
kixelated deleted the quest/m3/ts-map-own-hasher branch November 3, 2026 15:14
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