diff --git a/quest/m3/README.md b/quest/m3/README.md index 8d105a58a..6e3bf6a0a 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -37,5 +37,6 @@ generic std gap is planned here even when moq never calls it. - [Fresh writes beside a zipped iterator that points at a part](/quest/m3/ts-fresh-shapes.md) - zipping slots with a `Vec<[T; N]>`, `vec![row; n]`, or `copied` of what the slot holds translates - [Python's wake on the loop's own thread](/quest/m3/ffi-python-wake.md) - a wake on the event loop's thread resumes the future with no socket pair - [Python's call cost](/quest/m3/ffi-python-calls.md) - a call on an object costs what BoltFFI's does, or the gap is explained -- [A std map with a crate's own BuildHasher](/quest/m3/ts-map-own-hasher.md) - std's and hashbrown's maps built with a crate's own hasher translate for keys that are JS objects +- [A map built with BuildHasherDefault](/quest/m3/ts-build-hasher-default.md) - `FxHashMap`-style maps, of `BuildHasherDefault` over a crate's own `Hasher`, translate +- [Equality of hash maps and sets](/quest/m3/ts-hash-map-eq.md) - `==` of two hash maps or sets translates as std's - [A name or an operator argument at its own Rust span](/quest/m3/ts-argument-spans.md) - a `tsc` error on a plain-name or operator argument names that argument's Rust, not the method it is passed to diff --git a/quest/m3/ts-build-hasher-default.md b/quest/m3/ts-build-hasher-default.md new file mode 100644 index 000000000..59fe588b5 --- /dev/null +++ b/quest/m3/ts-build-hasher-default.md @@ -0,0 +1,21 @@ +# [S] A map built with BuildHasherDefault + +## Goal + +[rust2-ts](/ts/README.md) translates `BuildHasherDefault`, std's `BuildHasher` of a +`Hasher` that is `Default`, as a map's hasher, as `rustc-hash`'s `FxHashMap` and +`nohash-hasher`'s `IntMap` build theirs: making one, `Default`, `Clone`, `build_hasher`, +and `hash_one` through a crate's own `H`, proven by differential tests against native Rust. + +## Plan + +Found while landing maps that hold a crate's own `BuildHasher`, which refuse +`BuildHasherDefault` as "unsupported type `std::hash::BuildHasherDefault` from another +crate". + +- It holds nothing but a `PhantomData`, so it may be what such a type is, as `PhantomData` + is, and its `build_hasher` is `H::default()`. +- A map already holds any `BuildHasher` but `RandomState` and hashes keys around it. +- No consumer yet; planned here because rust2 aims to support all of std. + +Public API: the translator's output. diff --git a/quest/m3/ts-hash-map-eq.md b/quest/m3/ts-hash-map-eq.md new file mode 100644 index 000000000..d82823dfd --- /dev/null +++ b/quest/m3/ts-hash-map-eq.md @@ -0,0 +1,19 @@ +# [S] Equality of hash maps and sets + +## Goal + +[rust2-ts](/ts/README.md) translates `==` and `!=` of two `HashMap`s or two `HashSet`s, +std's and hashbrown's, whatever their `BuildHasher`, as std's: equal lengths, then each +entry of one found in the other with an equal value, proven by differential tests against +native Rust. + +## Plan + +Found while landing maps that hold a crate's own `BuildHasher`: `PartialEq::eq` of two +maps is refused as an unsupported call, for a `RandomState` map too. + +- std's walks one map and looks each key up in the other, calling the value's + `PartialEq`; which map it walks decides which value's `eq` runs as `self`. +- No consumer yet; planned here because rust2 aims to support all of std. + +Public API: the translator's output. diff --git a/quest/m3/ts-map-own-hasher.md b/quest/m3/ts-map-own-hasher.md deleted file mode 100644 index 8e61d5709..000000000 --- a/quest/m3/ts-map-own-hasher.md +++ /dev/null @@ -1,25 +0,0 @@ -# [M] A std map with a crate's own BuildHasher - -## Goal - -[rust2-ts](/ts/README.md) translates std's `HashMap` and `HashSet`, and hashbrown's, built -with a crate's own `BuildHasher`, for keys that are JS objects too, proven by differential -tests against native Rust with a hasher that logs what it is given. - -## Plan - -Found by [#424](https://github.com/moq-dev/rust2/pull/424), which translates hashing -through a crate's own `Hasher` but leaves this refused with "hashing by the hasher of -`_`". A key that is a JS primitive already translates, and never reaches the hasher. - -- The runtime finds a JS object key by its `Hash` into the collection's own `Mixer`. A - crate's own key `Hash` is generic over the hasher, so the map's instance takes the - crate's hasher type, not the runtime's. -- Open question: whether the runtime must call the crate's hasher at all. std leaves - how often a map hashes unspecified, and the README already treats it as the runtime's, - but native calls it at least once per insert or lookup, which a logging hasher sees. - Decide that before choosing between hashing through an `rt.OwnHasher` and hashing - around it. -- No consumer yet; planned here because rust2 aims to support all of std. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index eb0df64e4..0f02c5f81 100644 --- a/ts/README.md +++ b/ts/README.md @@ -464,7 +464,11 @@ such as a number, from `get_mut` or the entry API, is an `rt.Place` whose `v` re writes the map's slot. A hash map iterates in an order of the runtime's, mostly insertion order, which std leaves unspecified, and hashes a key with a hasher of its own, as std's keys its at random. So is how often the runtime -calls a key's `Hash`, `Eq`, or `Ord`. +calls a key's `Hash`, `Eq`, or `Ord`. A map or set built with any `BuildHasher` but +`RandomState` holds it, which `hasher` lends, `Default` and `collect` make, and `clone` +clones before what the map holds; it drops before the map's entries, and at once from +`into_iter`, `into_keys`, and `into_values`, as hashbrown's does. No key goes through it: +the runtime's own hasher finds them, so it builds no hasher for the map. A `DefaultHasher` is std's SipHash-1-3, and `Hash` writes into it what std's impls write, so `DefaultHasher::new`'s hash of a value is native's. `RandomState::new` keys it as std diff --git a/ts/fixtures/lang/hashes/expected/harness.ts b/ts/fixtures/lang/hashes/expected/harness.ts index 58ec1cf62..7c19e53ff 100644 --- a/ts/fixtures/lang/hashes/expected/harness.ts +++ b/ts/fixtures/lang/hashes/expected/harness.ts @@ -37,4 +37,28 @@ export function op(name: string): rt.u16 { } } +/** Write `s` and the byte no UTF-8 holds, which ends it. */ +export function put(out: rt.ByteVec, s: string): void { + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(s)); + rt.byteVecPush(out, 255); +} + +/** A char of the alphabet for each byte, by its low bits. */ +export function text(data: rt.ByteSlice): string { + let s: string = ""; + const iterS: Uint8Array = rt.byteSliceView(data); + let iterI: number = 0; + while (true) { + const _5: rt.u8 | rt.Done = iterI < iterS.length ? iterS[iterI++] : rt.DONE; + if (_5 === rt.DONE) { + return s; + } + const _13: rt.usize = rt.u8Rem(_5, 16) as rt.usize; + if (!(_13 < 16)) { + rt.panic("index out of bounds: the len is " + 16 + " but the index is " + _13); + } + s = s + String.fromCodePoint([47, 92, 97, 48, 57, 127, 128, 233, 2047, 2048, 55295, 57344, 65535, 65536, 128512, 1114111][_13]); + } +} + //# sourceMappingURL=harness.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts b/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts new file mode 100644 index 000000000..8815c1d5d --- /dev/null +++ b/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts @@ -0,0 +1,26 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** + * FNV-1a, a `Hasher` of the crate's own that logs nothing, so a map may call it as often + * as it likes. + */ +export interface Fnv { + _0: rt.u64; +} + +export function finish$hash_Hasher(self: Fnv): rt.u64 { + return self._0; +} + +/** The methods of `hashes::Fnv`'s impl, which the runtime's hashing calls. */ +export const hasher$hash_Hasher: rt.HasherImpl = { write: write$hash_Hasher as (a0: Fnv, a1: rt.ByteSlice) => undefined, finish: finish$hash_Hasher }; + +export function write$hash_Hasher(self: Fnv, bytes: rt.ByteSlice): void { + for (const b of rt.byteSliceView(bytes)) { + self._0 = rt.u64WrappingMul(rt.u64Xor(self._0, b as rt.u64), 1099511628211); + } +} + +//# sourceMappingURL=hashes.Fnv.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.Key.ts b/ts/fixtures/lang/hashes/expected/hashes.Key.ts new file mode 100644 index 000000000..01bd99c7f --- /dev/null +++ b/ts/fixtures/lang/hashes/expected/hashes.Key.ts @@ -0,0 +1,41 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import * as Fnv from "./hashes.Fnv.ts"; + +export function eq$cmp_PartialEq(self: Key, other: Key): boolean { + if (!(self._0 === other._0)) { + return false; + } + return self._1 === other._1; +} + +export function hash$Hash_hash_DefaultHasher(self: Key, state: rt.Hasher): void { + rt.hash16(self._0, state); + rt.hashStr(self._1, state); +} + +export function hash$Hash_hashes_Fnv(self: Key, state: Fnv.Fnv): void { + rt.hash16(self._0, new rt.OwnHasher(state, Fnv.hasher$hash_Hasher)); + rt.hashStr(self._1, new rt.OwnHasher(state, Fnv.hasher$hash_Hasher)); +} + +export function partialCmp$cmp_PartialOrd(self: Key, other: Key): rt.Ordering | undefined { + return cmp$cmp_Ord(self, other); +} + +export function cmp$cmp_Ord(self: Key, other: Key): rt.Ordering { + const cmp: rt.Ordering = rt.cmp(self._0, other._0); + if (cmp === 0) { + return rt.cmpStr(self._1, other._1); + } + return cmp; +} + +/** A key that is a JS object. */ +export interface Key { + _0: rt.u16; + _1: string; +} + +//# sourceMappingURL=hashes.Key.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.Noisy.ts b/ts/fixtures/lang/hashes/expected/hashes.Noisy.ts new file mode 100644 index 000000000..2a9365ed5 --- /dev/null +++ b/ts/fixtures/lang/hashes/expected/hashes.Noisy.ts @@ -0,0 +1,28 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { event } from "./hashes.ts"; + +/** A value that logs its clone and its drop. */ +export class Noisy { + _0: rt.u8; + + constructor(_0: rt.u8) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function clone$Clone(self: Noisy): Noisy { + event(67, self._0); + return new Noisy(self._0); +} + +export function drop$ops_Drop(self: Noisy): void { + event(68, self._0); +} + +//# sourceMappingURL=hashes.Noisy.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.Numbered.ts b/ts/fixtures/lang/hashes/expected/hashes.Numbered.ts new file mode 100644 index 000000000..b3cb268a8 --- /dev/null +++ b/ts/fixtures/lang/hashes/expected/hashes.Numbered.ts @@ -0,0 +1,41 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { event } from "./hashes.ts"; +import type * as Fnv from "./hashes.Fnv.ts"; + +/** + * A `BuildHasher` of the crate's own, numbered, which logs each one `Default` makes, each + * clone, and each drop, and seeds its [`Fnv`] by its number. + */ +export class Numbered { + _0: rt.u8; + + constructor(_0: rt.u8) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function buildHasher$hash_BuildHasher(self: Numbered): Fnv.Fnv { + return { _0: rt.u64Xor({ hi: 0xcbf29ce4, lo: 0x84222325 } as rt.u64, self._0 as rt.u64) }; +} + +export function default$Default(): Numbered { + event(110, 0); + return new Numbered(0); +} + +export function clone$Clone(self: Numbered): Numbered { + event(99, self._0); + return new Numbered(rt.u8WrappingAdd(self._0, 1)); +} + +export function drop$ops_Drop(self: Numbered): void { + event(120, self._0); +} + +//# sourceMappingURL=hashes.Numbered.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.ts b/ts/fixtures/lang/hashes/expected/hashes.ts index a46a64b1b..d5bce2449 100644 --- a/ts/fixtures/lang/hashes/expected/hashes.ts +++ b/ts/fixtures/lang/hashes/expected/hashes.ts @@ -1,18 +1,23 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { drop$hashes_Switch, drop$tuple2_hashes_Seeded_hashes_Seeded } from "./lib.ts"; +import { drop$Option_tuple2_u8_hashes_Noisy, drop$hashes_Switch, drop$tuple2_hashes_Seeded_hashes_Seeded } from "./lib.ts"; import { thenSome$0n2gkz4 } from "./core/bool.ts"; import { unwrapOr$0k15iai as option_unwrapOr$0k15iai, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr } from "./core/option.ts"; import { unwrapOr$01jdcm7 as result_unwrapOr$01jdcm7 } from "./core/result.ts"; +import { put, text } from "./harness.ts"; import * as Bare from "./hashes.Bare.ts"; import * as Build from "./hashes.Build.ts"; import * as Counted from "./hashes.Counted.ts"; import * as Dir from "./hashes.Dir.ts"; +import * as Fnv from "./hashes.Fnv.ts"; import * as Full from "./hashes.Full.ts"; import * as Holder from "./hashes.Holder.ts"; +import * as Key from "./hashes.Key.ts"; import * as Logged from "./hashes.Logged.ts"; import * as Nibble from "./hashes.Nibble.ts"; +import * as Noisy from "./hashes.Noisy.ts"; +import * as Numbered from "./hashes.Numbered.ts"; import * as Packet from "./hashes.Packet.ts"; import * as Partial from "./hashes.Partial.ts"; import * as Priorities$random from "./hashes.Priorities$random.ts"; @@ -999,4 +1004,114 @@ export function copied(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecExtendFromSlice(out, rt.byteVecSlice(log.value)); } +export const EVENTS: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(EVENTS$init, false); + +export function EVENTS$init(): rt.RefCell { + return new rt.RefCell(new rt.ByteVec()); +} + +/** Log `what` happened to the value numbered `n`. */ +export function event(what: rt.u8, n: rt.u8): void { + ((events: rt.RefCell): undefined => { + const _52: rt.RefCell = rt.refCellBorrowMut(events); + rt.iterExtendBytes(_52.value, rt.iterSlice(Uint8Array.of(what, n)), undefined, true); + rt.refMutRelease(_52); + })(EVENTS.get()); +} + +/** + * Hash maps and sets with a `BuildHasher` of the crate's own, keyed by JS objects and by + * numbers, which find their keys whatever it hashes them to. Each holds its own, which + * `hasher` lends, made by `Default` where std's makes one, cloned before what the map + * holds, and dropped before it, and at once by a consuming iterator. Only maps of one + * entry say what they drop or clone, and only sorted what they hold, as std's order is + * its own. + */ +export function maps(data: rt.ByteSlice, out: rt.ByteVec): void { + ((events: rt.RefCell): undefined => { + const _5: rt.RefCell = rt.refCellBorrowMut(events); + rt.byteVecClear(_5.value); + rt.refMutRelease(_5); + })(EVENTS.get()); + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 7); + const keyed: rt.HashMap = rt.hashWithCapacity(data.length, 58720256, rt.hashing(Key.hash$Hash_hash_DefaultHasher as (a0: Key.Key, a1: rt.Hasher) => undefined, Key.eq$cmp_PartialEq), new Numbered.Numbered(first)) as rt.HashMap; + const seen: rt.HashSet = rt.hashNew(undefined, new Numbered.Numbered(1)) as rt.HashSet; + const iter: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(data))); + while (true) { + const _17: [rt.usize, rt.u8] | rt.Done = iter.next(); + if (_17 === rt.DONE) { + break; + } + const i: rt.usize = _17[0]; + const b: rt.u8 = _17[1]; + const _26: rt.u16 = rt.u8Rem(b, 8) as rt.u16; + const bounds = rt.sliceIndexBounds(new rt.RangeInclusive(i, i, rt.usizeStep), data.length); + const _25: Key.Key = { _0: _26, _1: text(rt.byteSliceRange(data, bounds[0], bounds[1])) }; + const _22: rt.Place = rt.entryOrInsertWithMut(rt.entry(keyed, _25), (): rt.u8 => { + return 0; + }); + _22.v = rt.u8Add(_22.v, 1); + rt.byteVecPush(out, rt.hashAdd(seen, rt.u8Rem(b, 16)) ? 1 : 0); + const _44: Key.Key = { _0: (rt.u8Rem(b, 8) ^ 1) as rt.u16, _1: text(rt.byteSliceFrom(Uint8Array.of(b ^ 1))) }; + rt.byteVecPush(out, rt.hashContains(keyed, _44) ? 1 : 0); + } + const held: [Key.Key, rt.u8][] = rt.iterToArray(rt.hashIter(keyed), "next", 8); + rt.sliceSort(held, rt.cmpLt(rt.cmpTuple<[Key.Key, rt.u8], rt.Ordering | undefined>([Key.partialCmp$cmp_PartialOrd, rt.cmp])), 8, true); + for (const _63 of held) { + rt.byteVecPush(out, _63[0]._0 & 0xff); + put(out, _63[0]._1); + rt.byteVecPush(out, _63[1]); + } + const probe: Key.Key = { _0: first as rt.u16, _1: text(rt.byteSliceFrom(Uint8Array.of(first))) }; + rt.byteVecPush(out, (keyed.s as Numbered.Numbered)._0); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(rt.hashOne(new rt.OwnHasher(Numbered.buildHasher$hash_BuildHasher(keyed.s as Numbered.Numbered), Fnv.hasher$hash_Hasher), probe, rt.hashOwn(Key.hash$Hash_hashes_Fnv as (a0: Key.Key, a1: Fnv.Fnv) => undefined))).reverse())); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(rt.hashOne(new rt.OwnHasher(Numbered.buildHasher$hash_BuildHasher(seen.s as Numbered.Numbered), Fnv.hasher$hash_Hasher), first, rt.hash8)).reverse())); + const noisy: rt.HashMap = rt.hashNew(undefined, new Numbered.Numbered(5)) as rt.HashMap; + rt.drop(rt.hashInsert(noisy, first, new Noisy.Noisy(first))); + const copy: rt.HashMap = rt.hashClone(noisy, undefined, Noisy.clone$Clone, Numbered.clone$Clone); + rt.byteVecPush(out, (copy.s as Numbered.Numbered)._0); + rt.hashDrop(noisy, undefined, rt.drop, rt.drop); + const entries: rt.Iter<[rt.u8, Noisy.Noisy]> = rt.hashIter(copy, undefined, rt.drop, rt.drop); + event(105, 0); + drop$Option_tuple2_u8_hashes_Noisy(rt.iterToOption(entries.next())); + rt.drop(entries); + let made: rt.HashMap = rt.hashNew(undefined, Numbered.default$Default()) as rt.HashMap; + rt.drop(rt.hashInsert(made, 2, new Noisy.Noisy(2))); + const default_ = rt.hashNew(undefined, Numbered.default$Default()) as rt.HashMap; + const taken2 = made; + made = default_; + const taken: rt.HashMap = taken2; + rt.byteVecPush(out, rt.hashLen(taken) & 0xff); + rt.byteVecPush(out, rt.hashLen(made) & 0xff); + const _136: rt.Iter = rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (b2: rt.u8): rt.u8 => { + return rt.u8Rem(b2, 4); + }); + const small: rt.HashSet = rt.hashExtendKeys(rt.hashNew(undefined, Numbered.default$Default()) as rt.HashSet, _136, undefined, undefined, 469762048); + const union: rt.HashSet = rt.hashExtendKeys(rt.hashNew(undefined, Numbered.default$Default()) as rt.HashSet, rt.hashUnion(small, seen), undefined, undefined, 469762048); + rt.byteVecPush(out, rt.hashLen(union) & 0xff); + rt.byteVecPush(out, rt.hashIsSubset(small, seen) ? 1 : 0); + rt.hashExtendKeys(seen, rt.hashKeys(small, undefined, undefined, rt.drop), undefined, undefined, 469762048); + rt.byteVecPush(out, rt.hashLen(seen) & 0xff); + const keys: Key.Key[] = rt.iterToArray(rt.hashKeys(keyed, undefined, undefined, rt.drop), "next", 16); + rt.sliceSort(keys, rt.cmpLt(Key.partialCmp$cmp_PartialOrd), 16, true); + rt.byteVecPush(out, keys.length & 0xff); + const iter3: rt.Iter = rt.hashValues(taken, rt.drop, undefined, rt.drop); + while (true) { + const _169: Noisy.Noisy | rt.Done = iter3.next(); + if (_169 === rt.DONE) { + break; + } + const value: Noisy.Noisy = _169; + event(118, value._0); + value[Symbol.dispose](); + } + rt.drop(iter3); + rt.hashDrop(union, undefined, undefined, rt.drop); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(((events2: rt.RefCell): rt.ByteVec => { + return rt.refCellReplace(events2, new rt.ByteVec()); + })(EVENTS.get()))); + rt.hashDrop(made, undefined, rt.drop, rt.drop); + rt.hashDrop(seen, undefined, undefined, rt.drop); +} + //# sourceMappingURL=hashes.ts.map diff --git a/ts/fixtures/lang/hashes/expected/lib.ts b/ts/fixtures/lang/hashes/expected/lib.ts index 0ee202926..8de3149d7 100644 --- a/ts/fixtures/lang/hashes/expected/lib.ts +++ b/ts/fixtures/lang/hashes/expected/lib.ts @@ -1,10 +1,17 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { values, random, bytes, own, copied as hashes_copied } from "./hashes.ts"; +import { values, random, bytes, own, copied as hashes_copied, maps } from "./hashes.ts"; +import type * as Noisy from "./hashes.Noisy.ts"; import type * as Seeded from "./hashes.Seeded.ts"; import * as Switch from "./hashes.Switch.ts"; +export function drop$Option_tuple2_u8_hashes_Noisy(_1: [rt.u8, Noisy.Noisy] | undefined): void { + if (_1 !== undefined) { + drop$tuple2_u8_hashes_Noisy(_1); + } +} + export function drop$hashes_Switch(_1: Switch.Switch): void { Switch.drop$ops_Drop(rt.place(() => _1, (value) => _1 = value)); } @@ -14,6 +21,10 @@ export function drop$tuple2_hashes_Seeded_hashes_Seeded(_1: [Seeded.Seeded, Seed _1[1][Symbol.dispose](); } +export function drop$tuple2_u8_hashes_Noisy(_1: [rt.u8, Noisy.Noisy]): void { + _1[1][Symbol.dispose](); +} + /** * Each case's name and what it runs. The first two bytes, big-endian, pick one modulo * their count, so a case added at the end moves no other. @@ -27,6 +38,8 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) own(data3, out3); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::copied", (_25: rt.u8, data4: rt.ByteSlice, out4: rt.ByteVec): undefined => { hashes_copied(data4, out4); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::maps", (_26: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): undefined => { + maps(data5, out5); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/hashes/src/hashes.rs b/ts/fixtures/lang/hashes/src/hashes.rs index 8a9796087..86acbe978 100644 --- a/ts/fixtures/lang/hashes/src/hashes.rs +++ b/ts/fixtures/lang/hashes/src/hashes.rs @@ -2,15 +2,19 @@ //! `DefaultHasher::new` keys it, whose values must be native's byte for byte, and //! `RandomState`, whose keys are random on both sides: its case prints only what holds //! whatever they are, such as one state hashing equal values alike, and never a hash. A -//! `Hasher` of the crate's own logs each call `Hash` makes of it. +//! `Hasher` of the crate's own logs each call `Hash` makes of it. Then hash maps and sets +//! that hold a `BuildHasher` of the crate's own. use std::cell::{Cell, RefCell}; use std::cmp::Reverse; +use std::collections::{HashMap, HashSet}; use std::hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}; use std::num::NonZero; use std::sync::Arc; use std::time::Duration; +use crate::harness::{put, text}; + #[derive(Hash)] struct Packet { id: u64, @@ -611,3 +615,139 @@ pub fn copied(data: &[u8], out: &mut Vec) { } out.extend_from_slice(&log.into_inner()); } + +thread_local! { + /// What each [`Numbered`] and [`Noisy`] did, in order. + static EVENTS: RefCell> = const { RefCell::new(Vec::new()) }; +} + +/// Log `what` happened to the value numbered `n`. +fn event(what: u8, n: u8) { + EVENTS.with(|events| events.borrow_mut().extend([what, n])); +} + +/// FNV-1a, a `Hasher` of the crate's own that logs nothing, so a map may call it as often +/// as it likes. +struct Fnv(u64); + +impl Hasher for Fnv { + fn finish(&self) -> u64 { + self.0 + } + + fn write(&mut self, bytes: &[u8]) { + for &b in bytes { + self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3); + } + } +} + +/// A `BuildHasher` of the crate's own, numbered, which logs each one `Default` makes, each +/// clone, and each drop, and seeds its [`Fnv`] by its number. +struct Numbered(u8); + +impl BuildHasher for Numbered { + type Hasher = Fnv; + + fn build_hasher(&self) -> Fnv { + Fnv(0xcbf2_9ce4_8422_2325 ^ u64::from(self.0)) + } +} + +impl Default for Numbered { + fn default() -> Self { + event(b'n', 0); + Numbered(0) + } +} + +impl Clone for Numbered { + fn clone(&self) -> Self { + event(b'c', self.0); + Numbered(self.0.wrapping_add(1)) + } +} + +impl Drop for Numbered { + fn drop(&mut self) { + event(b'x', self.0); + } +} + +/// A value that logs its clone and its drop. +struct Noisy(u8); + +impl Clone for Noisy { + fn clone(&self) -> Self { + event(b'C', self.0); + Noisy(self.0) + } +} + +impl Drop for Noisy { + fn drop(&mut self) { + event(b'D', self.0); + } +} + +/// A key that is a JS object. +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +struct Key(u16, String); + +/// Hash maps and sets with a `BuildHasher` of the crate's own, keyed by JS objects and by +/// numbers, which find their keys whatever it hashes them to. Each holds its own, which +/// `hasher` lends, made by `Default` where std's makes one, cloned before what the map +/// holds, and dropped before it, and at once by a consuming iterator. Only maps of one +/// entry say what they drop or clone, and only sorted what they hold, as std's order is +/// its own. +pub fn maps(data: &[u8], out: &mut Vec) { + EVENTS.with(|events| events.borrow_mut().clear()); + let first = data.first().copied().unwrap_or(7); + let mut keyed: HashMap = HashMap::with_capacity_and_hasher(data.len(), Numbered(first)); + let mut seen: HashSet = HashSet::with_hasher(Numbered(1)); + for (i, &b) in data.iter().enumerate() { + *keyed.entry(Key(u16::from(b % 8), text(&data[i..=i]))).or_default() += 1; + out.push(seen.insert(b % 16) as u8); + out.push(keyed.contains_key(&Key(u16::from(b % 8 ^ 1), text(&[b ^ 1]))) as u8); + } + let mut held: Vec<(&Key, &u8)> = keyed.iter().collect(); + held.sort(); + for (Key(n, text), count) in held { + out.push(*n as u8); + put(out, text); + out.push(*count); + } + // What `hasher` lends is the map's own. + let probe = Key(u16::from(first), text(&[first])); + out.push(keyed.hasher().0); + out.extend_from_slice(&keyed.hasher().hash_one(&probe).to_le_bytes()); + out.extend_from_slice(&seen.hasher().hash_one(first).to_le_bytes()); + let mut noisy: HashMap = HashMap::with_hasher(Numbered(5)); + noisy.insert(first, Noisy(first)); + let copy = noisy.clone(); + out.push(copy.hasher().0); + drop(noisy); + let mut entries = copy.into_iter(); + event(b'i', 0); + drop(entries.next()); + drop(entries); + let mut made: HashMap = HashMap::default(); + made.insert(2, Noisy(2)); + let taken = std::mem::take(&mut made); + out.push(taken.len() as u8); + out.push(made.len() as u8); + let small: HashSet = data.iter().map(|b| b % 4).collect(); + let union = &small | &seen; + out.push(union.len() as u8); + out.push(small.is_subset(&seen) as u8); + seen.extend(small); + out.push(seen.len() as u8); + let mut keys: Vec = keyed.into_keys().collect(); + keys.sort(); + out.push(keys.len() as u8); + for value in taken.into_values() { + event(b'v', value.0); + } + drop(union); + out.extend_from_slice(&EVENTS.with(|events| events.take())); +} diff --git a/ts/fixtures/lang/hashes/src/lib.rs b/ts/fixtures/lang/hashes/src/lib.rs index 1d7a23753..60ab0ca46 100644 --- a/ts/fixtures/lang/hashes/src/lib.rs +++ b/ts/fixtures/lang/hashes/src/lib.rs @@ -23,5 +23,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("hashes::bytes", |_, data, out| hashes::bytes(data, out)), ("hashes::own", |_, data, out| hashes::own(data, out)), ("hashes::copied", |_, data, out| hashes::copied(data, out)), + ("hashes::maps", |_, data, out| hashes::maps(data, out)), ] } diff --git a/ts/fixtures/stock/expected/core/option.ts b/ts/fixtures/stock/expected/core/option.ts index 308651a8c..8be4bd5d6 100644 --- a/ts/fixtures/stock/expected/core/option.ts +++ b/ts/fixtures/stock/expected/core/option.ts @@ -3,6 +3,13 @@ import * as rt from "../rt.ts"; import type * as Logged from "../maps.Logged.ts"; +export function unwrapOr$11c0we2(self: rt.u32 | undefined, default_: rt.u32): rt.u32 { + if (self === undefined) { + return default_; + } + return self; +} + export function unwrapOr$0r2bmpr(self: rt.u8 | undefined, default_: rt.u8): rt.u8 { if (self === undefined) { return default_; diff --git a/ts/fixtures/stock/expected/harness.ts b/ts/fixtures/stock/expected/harness.ts index 38a2ae381..0ca27b03f 100644 --- a/ts/fixtures/stock/expected/harness.ts +++ b/ts/fixtures/stock/expected/harness.ts @@ -3,7 +3,7 @@ import * as rt from "./rt.ts"; import { bytesMut, reserves, reads, dyns, late, writes, edits, chains } from "./buf.ts"; import * as Owner from "./harness.Owner.ts"; -import { map as maps_map, table, capacity, linked, linkedIters, nested } from "./maps.ts"; +import { map as maps_map, table, capacity, linked, linkedIters, nested, ownHasher, ownFromIter } from "./maps.ts"; /** * Each case's name and what it runs. The first byte picks one modulo their count, so a @@ -36,7 +36,7 @@ export function cases(): [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefine chains(a012.data, a012.start, a012.length, a112); }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::nested", (a013: rt.ByteSlice, a113: rt.ByteVec): undefined => { nested(a013.data, a013.start, a013.length, a113); - }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["buf::late", late as (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["buf::late", late as (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::own_hasher", ownHasher as (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined], ["maps::own_from_iter", ownFromIter as (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined] as [string, (a0: rt.ByteSlice, a1: rt.ByteVec) => undefined]]; } /** Run one case. */ diff --git a/ts/fixtures/stock/expected/lib.ts b/ts/fixtures/stock/expected/lib.ts index 0e86707dc..f4bb15509 100644 --- a/ts/fixtures/stock/expected/lib.ts +++ b/ts/fixtures/stock/expected/lib.ts @@ -63,6 +63,12 @@ export function drop$Option_tuple2_maps_Logged_maps_Logged(_1: [Logged.Logged, L } } +export function drop$Option_tuple2_tuple2_u8_u8_maps_Logged(_1: [[rt.u8, rt.u8], Logged.Logged] | undefined): void { + if (_1 !== undefined) { + drop$tuple2_tuple2_u8_u8_maps_Logged(_1); + } +} + export function drop$Option_tuple2_u16_maps_Logged(_1: [rt.u16, Logged.Logged] | undefined): void { if (_1 !== undefined) { drop$tuple2_u16_maps_Logged(_1); @@ -142,6 +148,11 @@ export function drop$maps_Held_maps_Logged(_1: Held.Held): void { _1.last[Symbol.dispose](); } +export function drop$tuple2_hashbrown_HashMap_u8_u8_maps_Made_hashbrown_HashSet_u8_maps_Made(_1: [rt.HashMap, rt.HashSet]): void { + rt.hashDrop(_1[0], undefined, undefined, rt.drop); + rt.hashDrop(_1[1], undefined, undefined, rt.drop); +} + export function drop$tuple2_hashlink_LinkedHashSet_maps_Logged_maps_Logged(_1: [LinkedHashSet.LinkedHashSet, Logged.Logged]): void { drop$hashlink_LinkedHashSet_maps_Logged(_1[0]); _1[1][Symbol.dispose](); @@ -160,10 +171,20 @@ export function drop$tuple2_sync_Arc_str_u32(_1: [rt.Arc, rt.u32]): void _1[0][Symbol.dispose](); } +export function drop$tuple2_tuple2_u8_u8_maps_Logged(_1: [[rt.u8, rt.u8], Logged.Logged]): void { + _1[1][Symbol.dispose](); +} + export function drop$tuple2_u16_maps_Logged(_1: [rt.u16, Logged.Logged]): void { _1[1][Symbol.dispose](); } +export function drop$tuple3_hashbrown_HashMap_tuple2_u8_u8_u32_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded(_1: [rt.HashMap<[rt.u8, rt.u8], rt.u32>, rt.HashSet, rt.HashSet]): void { + rt.hashDrop(_1[0], undefined, undefined, rt.drop); + rt.hashDrop(_1[1], undefined, undefined, rt.drop); + rt.hashDrop(_1[2], undefined, undefined, rt.drop); +} + export class of$buf_Counted { v: buf_Counted.Counted; diff --git a/ts/fixtures/stock/expected/maps.Hinted.ts b/ts/fixtures/stock/expected/maps.Hinted.ts new file mode 100644 index 000000000..80ff5f09e --- /dev/null +++ b/ts/fixtures/stock/expected/maps.Hinted.ts @@ -0,0 +1,41 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { MADE } from "./maps.ts"; + +/** An iterator of the crate's own that logs each `size_hint`. */ +export class Hinted { + _0: I; + + constructor(_0: I) { + this._0 = _0; + } +} + +export function next$iter_Iterator_hashbrown_hash_map_IntoIter_u8_u8(self: Hinted>): [rt.u8, rt.u8] | rt.Done { + return self._0.next(); +} + +export function next$iter_Iterator_hashbrown_hash_set_IntoIter_u8(self: Hinted>): rt.u8 | rt.Done { + return self._0.next(); +} + +export function sizeHint$iter_Iterator_hashbrown_hash_map_IntoIter_u8_u8(self: Hinted>): [rt.usize, rt.usize | undefined] { + ((made: rt.RefCell): undefined => { + const _5: rt.RefCell = rt.refCellBorrowMut(made); + rt.byteVecPush(_5.value, 104); + rt.refMutRelease(_5); + })(MADE.get()); + return self._0.sizeHint(); +} + +export function sizeHint$iter_Iterator_hashbrown_hash_set_IntoIter_u8(self: Hinted>): [rt.usize, rt.usize | undefined] { + ((made: rt.RefCell): undefined => { + const _5: rt.RefCell = rt.refCellBorrowMut(made); + rt.byteVecPush(_5.value, 104); + rt.refMutRelease(_5); + })(MADE.get()); + return self._0.sizeHint(); +} + +//# sourceMappingURL=maps.Hinted.ts.map diff --git a/ts/fixtures/stock/expected/maps.Made.ts b/ts/fixtures/stock/expected/maps.Made.ts new file mode 100644 index 000000000..d379fcca5 --- /dev/null +++ b/ts/fixtures/stock/expected/maps.Made.ts @@ -0,0 +1,39 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { MADE } from "./maps.ts"; + +/** + * A `BuildHasher` of the crate's own, numbered, which logs each one `Default` makes and + * each drop. + */ +export class Made { + _0: rt.u8; + + constructor(_0: rt.u8) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function default$Default(): Made { + ((made: rt.RefCell): undefined => { + const _5: rt.RefCell = rt.refCellBorrowMut(made); + rt.byteVecPush(_5.value, 110); + rt.refMutRelease(_5); + })(MADE.get()); + return new Made(0); +} + +export function drop$ops_Drop(self: Made): void { + ((made: rt.RefCell): undefined => { + const _5: rt.RefCell = rt.refCellBorrowMut(made); + rt.iterExtendBytes(_5.value, rt.iterSlice(Uint8Array.of(120, self._0)), undefined, true); + rt.refMutRelease(_5); + })(MADE.get()); +} + +//# sourceMappingURL=maps.Made.ts.map diff --git a/ts/fixtures/stock/expected/maps.Seeded.ts b/ts/fixtures/stock/expected/maps.Seeded.ts new file mode 100644 index 000000000..755176295 --- /dev/null +++ b/ts/fixtures/stock/expected/maps.Seeded.ts @@ -0,0 +1,36 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +/** + * A `BuildHasher` of the crate's own, numbered, which logs its clone and its drop, and + * builds std's `DefaultHasher`. + */ +export class Seeded { + _0: rt.u8; + _1: rt.RefCell; + + constructor(_0: rt.u8, _1: rt.RefCell) { + this._0 = _0; + this._1 = _1; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function clone$Clone(self: Seeded): Seeded { + const _5: rt.RefCell = rt.refCellBorrowMut(self._1); + rt.byteVecPush(_5.value, 99); + rt.refMutRelease(_5); + return new Seeded(rt.u8Add(self._0, 1), self._1); +} + +export function drop$ops_Drop(self: Seeded): void { + const _5: rt.RefCell = rt.refCellBorrowMut(self._1); + rt.iterExtendBytes(_5.value, rt.iterSlice(Uint8Array.of(120, self._0)), undefined, true); + rt.refMutRelease(_5); +} + +//# sourceMappingURL=maps.Seeded.ts.map diff --git a/ts/fixtures/stock/expected/maps.ts b/ts/fixtures/stock/expected/maps.ts index f2f27481b..c5ccf6f21 100644 --- a/ts/fixtures/stock/expected/maps.ts +++ b/ts/fixtures/stock/expected/maps.ts @@ -1,8 +1,8 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { copy$tuple2_u16_u32, drop$Option_tuple2_hashlink_LinkedHashSet_maps_Logged_maps_Logged as lib_drop$Option_tuple2_hashlink_LinkedHashSet_maps_Logged_maps_Logged, drop$hashlink_LinkedHashMap_sync_Arc_str_u32 as lib_drop$hashlink_LinkedHashMap_sync_Arc_str_u32, drop$hashlink_LinkedHashMap_u16_u32 as lib_drop$hashlink_LinkedHashMap_u16_u32, drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState as lib_drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState, drop$hashlink_LinkedHashSet_maps_Logged as lib_drop$hashlink_LinkedHashSet_maps_Logged, drop$hashlink_LruCache_maps_Logged_maps_Logged as lib_drop$hashlink_LruCache_maps_Logged_maps_Logged, drop$hashlink_LruCache_u16_maps_Logged as lib_drop$hashlink_LruCache_u16_maps_Logged, drop$hashlink_linked_hash_map_Drain_u16_u32 as lib_drop$hashlink_linked_hash_map_Drain_u16_u32, drop$hashlink_linked_hash_map_IntoIter_u16_u32 as lib_drop$hashlink_linked_hash_map_IntoIter_u16_u32, drop$maps_Held_maps_Logged as lib_drop$maps_Held_maps_Logged } from "./lib.ts"; -import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, unwrapOr$0l7j3f6 as option_unwrapOr$0l7j3f6, mapOr$085zvfb as option_mapOr$085zvfb, mapOr$1787w44 as option_mapOr$1787w44, mapOr$0iaogpf as option_mapOr$0iaogpf, mapOr$0yixjb2 as option_mapOr$0yixjb2, mapOr$1vcgixw as option_mapOr$1vcgixw, mapOr$1noewr3 as option_mapOr$1noewr3 } from "./core/option.ts"; +import { copy$tuple2_u16_u32, drop$Option_tuple2_hashlink_LinkedHashSet_maps_Logged_maps_Logged as lib_drop$Option_tuple2_hashlink_LinkedHashSet_maps_Logged_maps_Logged, drop$Option_tuple2_tuple2_u8_u8_maps_Logged as lib_drop$Option_tuple2_tuple2_u8_u8_maps_Logged, drop$hashlink_LinkedHashMap_sync_Arc_str_u32 as lib_drop$hashlink_LinkedHashMap_sync_Arc_str_u32, drop$hashlink_LinkedHashMap_u16_u32 as lib_drop$hashlink_LinkedHashMap_u16_u32, drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState as lib_drop$hashlink_LinkedHashMap_u16_u32_hash_RandomState, drop$hashlink_LinkedHashSet_maps_Logged as lib_drop$hashlink_LinkedHashSet_maps_Logged, drop$hashlink_LruCache_maps_Logged_maps_Logged as lib_drop$hashlink_LruCache_maps_Logged_maps_Logged, drop$hashlink_LruCache_u16_maps_Logged as lib_drop$hashlink_LruCache_u16_maps_Logged, drop$hashlink_linked_hash_map_Drain_u16_u32 as lib_drop$hashlink_linked_hash_map_Drain_u16_u32, drop$hashlink_linked_hash_map_IntoIter_u16_u32 as lib_drop$hashlink_linked_hash_map_IntoIter_u16_u32, drop$maps_Held_maps_Logged as lib_drop$maps_Held_maps_Logged, drop$tuple2_hashbrown_HashMap_u8_u8_maps_Made_hashbrown_HashSet_u8_maps_Made as lib_drop$tuple2_hashbrown_HashMap_u8_u8_maps_Made_hashbrown_HashSet_u8_maps_Made, drop$tuple3_hashbrown_HashMap_tuple2_u8_u8_u32_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded as lib_drop$tuple3_hashbrown_HashMap_tuple2_u8_u8_u32_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded } from "./lib.ts"; +import { unwrapOr$11c0we2 as option_unwrapOr$11c0we2, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, unwrapOr$0l7j3f6 as option_unwrapOr$0l7j3f6, mapOr$085zvfb as option_mapOr$085zvfb, mapOr$1787w44 as option_mapOr$1787w44, mapOr$0iaogpf as option_mapOr$0iaogpf, mapOr$0yixjb2 as option_mapOr$0yixjb2, mapOr$1vcgixw as option_mapOr$1vcgixw, mapOr$1noewr3 as option_mapOr$1noewr3 } from "./core/option.ts"; import * as linked_hash_map_Drain from "./hashlink/linked_hash_map.Drain.ts"; import * as linked_hash_map_IntoIter from "./hashlink/linked_hash_map.IntoIter.ts"; import * as linked_hash_map_Iter from "./hashlink/linked_hash_map.Iter.ts"; @@ -16,7 +16,10 @@ import * as LruCache from "./hashlink/lru_cache.LruCache.ts"; import type * as DefaultHashBuilder from "./hashlink.DefaultHashBuilder.ts"; import * as maps_Counted from "./maps.Counted.ts"; import * as Held from "./maps.Held.ts"; +import * as Hinted from "./maps.Hinted.ts"; import * as Logged from "./maps.Logged.ts"; +import * as Made from "./maps.Made.ts"; +import * as Seeded from "./maps.Seeded.ts"; /** A length, capacity, or value, as four bytes. */ export function put(out: rt.ByteVec, n: rt.usize): void { @@ -1159,4 +1162,93 @@ export function nested(dataData: Uint8Array, dataStart: number, dataLen: number, } } +/** + * `hashbrown::HashMap` and `HashSet` with a `BuildHasher` of the crate's own, of keys that + * are JS objects and keys that are not: each holds its own, which `hasher` lends, clones + * it before what it holds, and drops it before them, and at once from `into_iter`. Only + * maps of one entry say what they drop, as hashbrown's order is its own. + */ +export function ownHasher(data: rt.ByteSlice, out: rt.ByteVec): void { + let _36!: rt.u8; + let _43!: rt.u8; + const log: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + const _6: Seeded.Seeded = new Seeded.Seeded(1, log); + const pairs: rt.HashMap<[rt.u8, rt.u8], rt.u32> = rt.hashNew(rt.hashing(rt.hashTuple([rt.hash8, rt.hash8]), rt.eqTuple([rt.same, rt.same])), _6) as rt.HashMap<[rt.u8, rt.u8], rt.u32>; + const set: rt.HashSet = rt.hashWithCapacity(data.length, 469762048, undefined, new Seeded.Seeded(10, log)) as rt.HashSet; + for (const b of rt.byteSliceView(data)) { + const _21: [rt.u8, rt.u8] = [rt.u8Rem(b, 4), rt.u8Rem(b, 3)]; + const _18: rt.Place = rt.entryOrInsertMut(rt.entry(pairs, _21), 0); + _18.v = rt.u32Add(_18.v, 1); + rt.byteVecPush(out, rt.hashAdd(set, rt.u8Rem(b, 8)) ? 1 : 0); + } + let iterStart: rt.u8 = 0; + const iterEnd: rt.u8 = 4; + l3: while ((_36 = iterStart++) < iterEnd) { + const a: rt.u8 = _36; + let iterStart2: rt.u8 = 0; + const iterEnd2: rt.u8 = 3; + while (true) { + if (!((_43 = iterStart2++) < iterEnd2)) { + continue l3; + } + put(out, option_unwrapOr$11c0we2(rt.hashGet(pairs, [a, _43] as [rt.u8, rt.u8]), 0) as rt.usize); + } + } + rt.byteVecPush(out, (pairs.s as Seeded.Seeded)._0); + rt.byteVecPush(out, (set.s as Seeded.Seeded)._0); + const _64: Seeded.Seeded = new Seeded.Seeded(20, log); + const one: rt.HashMap<[rt.u8, rt.u8], Logged.Logged> = rt.hashNew(rt.hashing(rt.hashTuple([rt.hash8, rt.hash8]), rt.eqTuple([rt.same, rt.same])), _64) as rt.HashMap<[rt.u8, rt.u8], Logged.Logged>; + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + rt.drop(rt.hashInsert(one, [first, 0] as [rt.u8, rt.u8], new Logged.Logged(first, log))); + const copy: rt.HashSet = rt.hashClone(set, undefined, undefined, Seeded.clone$Clone); + rt.byteVecPush(out, (copy.s as Seeded.Seeded)._0); + const entries: rt.Iter<[[rt.u8, rt.u8], Logged.Logged]> = rt.hashIter(one, undefined, rt.drop, rt.drop); + const _85: rt.RefCell = rt.refCellBorrowMut(log); + rt.byteVecPush(_85.value, 105); + rt.refMutRelease(_85); + lib_drop$Option_tuple2_tuple2_u8_u8_maps_Logged(rt.iterToOption(entries.next())); + rt.drop(entries); + lib_drop$tuple3_hashbrown_HashMap_tuple2_u8_u8_u32_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded_hashbrown_HashSet_u8_maps_Seeded([pairs, set, copy] as [rt.HashMap<[rt.u8, rt.u8], rt.u32>, rt.HashSet, rt.HashSet]); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(rt.refCellReplace(log, new rt.ByteVec()))); +} + +export const MADE: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(MADE$init, false); + +export function MADE$init(): rt.RefCell { + return new rt.RefCell(new rt.ByteVec()); +} + +/** + * `hashbrown`'s `from_iter` of a map or set that holds a `BuildHasher` of the crate's own: + * a map's takes the source's items, which drops its hasher, and reads their size hint + * before it makes its own, and a set's after. + */ +export function ownFromIter(data: rt.ByteSlice, out: rt.ByteVec): void { + ((made: rt.RefCell): undefined => { + const _5: rt.RefCell = rt.refCellBorrowMut(made); + rt.byteVecClear(_5.value); + rt.refMutRelease(_5); + })(MADE.get()); + const map: rt.HashMap = rt.hashNew(undefined, new Made.Made(1)) as rt.HashMap; + const set: rt.HashSet = rt.hashNew(undefined, new Made.Made(2)) as rt.HashSet; + for (const b of rt.byteSliceView(data)) { + rt.hashInsert(map, rt.u8Rem(b, 8), b); + rt.hashAdd(set, rt.u8Rem(b, 8)); + } + const map2: rt.HashMap = rt.hashFromIter(rt.hashIter(map, undefined, undefined, rt.drop), () => rt.hashNew(undefined, Made.default$Default()) as rt.HashMap, undefined, undefined, undefined, 469762048); + const _26: rt.HashSet = set; + const set2: rt.HashSet = rt.hashExtendKeys(rt.hashNew(undefined, Made.default$Default()) as rt.HashSet, rt.hashKeys(_26, undefined, undefined, rt.drop), undefined, undefined, 469762048); + put(out, rt.hashLen(map2)); + put(out, rt.hashLen(set2)); + const hinted: rt.HashMap = rt.hashFromIter(rt.iterFrom(new Hinted.Hinted(rt.hashIter(map2, undefined, undefined, rt.drop)), Hinted.next$iter_Iterator_hashbrown_hash_map_IntoIter_u8_u8, undefined, { hint: Hinted.sizeHint$iter_Iterator_hashbrown_hash_map_IntoIter_u8_u8 }), () => rt.hashNew(undefined, Made.default$Default()) as rt.HashMap, undefined, undefined, undefined, 469762048); + const _37: Hinted.Hinted> = new Hinted.Hinted(rt.hashKeys(set2, undefined, undefined, rt.drop)); + const keys: rt.HashSet = rt.hashExtendKeys(rt.hashNew(undefined, Made.default$Default()) as rt.HashSet, rt.iterFrom(_37, Hinted.next$iter_Iterator_hashbrown_hash_set_IntoIter_u8, undefined, { hint: Hinted.sizeHint$iter_Iterator_hashbrown_hash_set_IntoIter_u8 }), undefined, undefined, 469762048); + put(out, rt.hashLen(hinted)); + put(out, rt.hashLen(keys)); + lib_drop$tuple2_hashbrown_HashMap_u8_u8_maps_Made_hashbrown_HashSet_u8_maps_Made([hinted, keys] as [rt.HashMap, rt.HashSet]); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(((made2: rt.RefCell): rt.ByteVec => { + return rt.refCellReplace(made2, new rt.ByteVec()); + })(MADE.get()))); +} + //# sourceMappingURL=maps.ts.map diff --git a/ts/fixtures/stock/src/harness.rs b/ts/fixtures/stock/src/harness.rs index bc5012183..cdc5591a6 100644 --- a/ts/fixtures/stock/src/harness.rs +++ b/ts/fixtures/stock/src/harness.rs @@ -29,6 +29,8 @@ fn cases() -> Vec<(&'static str, Case)> { ("buf::chains", crate::buf::chains), ("maps::nested", crate::maps::nested), ("buf::late", crate::buf::late), + ("maps::own_hasher", crate::maps::own_hasher), + ("maps::own_from_iter", crate::maps::own_from_iter), ] } diff --git a/ts/fixtures/stock/src/maps.rs b/ts/fixtures/stock/src/maps.rs index 2a2d8d722..a741927b4 100644 --- a/ts/fixtures/stock/src/maps.rs +++ b/ts/fixtures/stock/src/maps.rs @@ -608,3 +608,129 @@ pub fn nested(data: &[u8], out: &mut Vec) { } out.extend(log.borrow().iter()); } + +/// A `BuildHasher` of the crate's own, numbered, which logs its clone and its drop, and +/// builds std's `DefaultHasher`. +struct Seeded<'a>(u8, &'a RefCell>); + +impl BuildHasher for Seeded<'_> { + type Hasher = std::hash::DefaultHasher; + + fn build_hasher(&self) -> Self::Hasher { + std::hash::DefaultHasher::new() + } +} + +impl Clone for Seeded<'_> { + fn clone(&self) -> Self { + self.1.borrow_mut().push(b'c'); + Seeded(self.0 + 1, self.1) + } +} + +impl Drop for Seeded<'_> { + fn drop(&mut self) { + self.1.borrow_mut().extend([b'x', self.0]); + } +} + +/// `hashbrown::HashMap` and `HashSet` with a `BuildHasher` of the crate's own, of keys that +/// are JS objects and keys that are not: each holds its own, which `hasher` lends, clones +/// it before what it holds, and drops it before them, and at once from `into_iter`. Only +/// maps of one entry say what they drop, as hashbrown's order is its own. +pub fn own_hasher(data: &[u8], out: &mut Vec) { + let log = RefCell::new(Vec::new()); + let mut pairs: HashMap<(u8, u8), u32, Seeded> = HashMap::with_hasher(Seeded(1, &log)); + let mut set: HashSet = HashSet::with_capacity_and_hasher(data.len(), Seeded(10, &log)); + for &b in data { + *pairs.entry((b % 4, b % 3)).or_insert(0) += 1; + out.push(u8::from(set.insert(b % 8))); + } + for a in 0..4 { + for b in 0..3 { + put(out, pairs.get(&(a, b)).copied().unwrap_or(0) as usize); + } + } + out.push(pairs.hasher().0); + out.push(set.hasher().0); + let mut one: HashMap<(u8, u8), Logged, Seeded> = HashMap::with_hasher(Seeded(20, &log)); + let first = data.first().copied().unwrap_or(0); + one.insert((first, 0), Logged(first, &log)); + let copy = HashSet::::clone(&set); + out.push(copy.hasher().0); + let mut entries = one.into_iter(); + log.borrow_mut().push(b'i'); + drop(entries.next()); + drop(entries); + drop((pairs, set, copy)); + out.extend_from_slice(&log.take()); +} + +thread_local! { + /// What each [`Made`] did, in order. + static MADE: RefCell> = const { RefCell::new(Vec::new()) }; +} + +/// A `BuildHasher` of the crate's own, numbered, which logs each one `Default` makes and +/// each drop. +struct Made(u8); + +impl BuildHasher for Made { + type Hasher = std::hash::DefaultHasher; + + fn build_hasher(&self) -> Self::Hasher { + std::hash::DefaultHasher::new() + } +} + +impl Default for Made { + fn default() -> Self { + MADE.with(|made| made.borrow_mut().push(b'n')); + Made(0) + } +} + +impl Drop for Made { + fn drop(&mut self) { + MADE.with(|made| made.borrow_mut().extend([b'x', self.0])); + } +} + +/// An iterator of the crate's own that logs each `size_hint`. +struct Hinted(I); + +impl Iterator for Hinted { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + MADE.with(|made| made.borrow_mut().push(b'h')); + self.0.size_hint() + } +} + +/// `hashbrown`'s `from_iter` of a map or set that holds a `BuildHasher` of the crate's own: +/// a map's takes the source's items, which drops its hasher, and reads their size hint +/// before it makes its own, and a set's after. +pub fn own_from_iter(data: &[u8], out: &mut Vec) { + MADE.with(|made| made.borrow_mut().clear()); + let mut map: HashMap = HashMap::with_hasher(Made(1)); + let mut set: HashSet = HashSet::with_hasher(Made(2)); + for &b in data { + map.insert(b % 8, b); + set.insert(b % 8); + } + let map: HashMap = HashMap::from_iter(map); + let set: HashSet = HashSet::from_iter(set); + put(out, map.len()); + put(out, set.len()); + let hinted: HashMap = HashMap::from_iter(Hinted(map.into_iter())); + let keys: HashSet = HashSet::from_iter(Hinted(set.into_iter())); + put(out, hinted.len()); + put(out, keys.len()); + drop((hinted, keys)); + out.extend_from_slice(&MADE.with(|made| made.take())); +} diff --git a/ts/runtime/rt/collections.ts b/ts/runtime/rt/collections.ts index b32c594c9..3d9d09b7f 100644 --- a/ts/runtime/rt/collections.ts +++ b/ts/runtime/rt/collections.ts @@ -442,6 +442,11 @@ export interface HashMap { n: number; /** How to find a key that is an object, or `undefined` if no key is one. */ readonly h: Hashing | undefined; + /** + * Its own `BuildHasher`, which `hasher` lends, or `undefined` for a `RandomState`. No + * key goes through it: `h` finds them by the runtime's own hasher. + */ + readonly s: unknown; /** The capacity an operation last set it to, if any, which `hashCapacity` reads. */ cap?: number; } @@ -450,11 +455,12 @@ export interface HashMap { export type HashSet = HashMap; /** - * `HashMap::new` and `HashSet::new`, with how to find a key that is an object. The type - * `K` is asserted, not inferred from `h`: a slice's hashing takes any `ArrayLike`. + * `HashMap::new` and `HashSet::new`, with how to find a key that is an object, and + * `with_hasher` of its own `BuildHasher` `s`. The type `K` is asserted, not inferred from + * `h`: a slice's hashing takes any `ArrayLike`. */ -export function hashNew(h?: Hashing>): HashMap { - return { p: new Map(), o: undefined, n: 0, h }; +export function hashNew(h?: Hashing>, s?: unknown): HashMap { + return { p: new Map(), o: undefined, n: 0, h, s }; } /** Whether `key` goes by its hash rather than as its own JS map key. */ @@ -496,9 +502,9 @@ function hold(m: HashMap): void { } /** `with_capacity`, of at most `max` entries, past which it panics, as hashbrown's does. */ -export function hashWithCapacity(n: number, max: number, h?: Hashing>): HashMap { +export function hashWithCapacity(n: number, max: number, h?: Hashing>, s?: unknown): HashMap { if (n > max) panic("Hash table capacity overflow"); - const m = hashNew(h); + const m = hashNew(h, s); if (n > 0) m.cap = n; return m; } @@ -726,8 +732,12 @@ export function hashKey(m: HashSet, key: K, take = false, none?: None): K return key; } -/** Drop `m`: each key by `k` and each value by `v`, of those that drop, entry by entry. */ -export function hashDrop(m: HashMap, k: ((key: K) => void) | undefined, v?: (value: V) => void): void { +/** + * Drop `m`: its own `BuildHasher` by `s`, as hashbrown's field before its table, then each + * key by `k` and each value by `v`, of those that drop, entry by entry. + */ +export function hashDrop(m: HashMap, k: ((key: K) => void) | undefined, v?: (value: V) => void, s?: (state: never) => void): void { + s?.(m.s as never); for (const [key, value] of m.p) dropPair(key, value, k, v); for (const first of m.o?.values() ?? []) { for (let s: Slot | undefined = first; s !== undefined; s = s.next) dropPair(s.k, s.v, k, v); @@ -928,19 +938,23 @@ export class HashIter extends Iter implements Disposable { /** * `iter` and `iter_mut` of a map, and with what drops each key and value of those that - * drop, `into_iter`: its entries, as `[key, value]`. + * drop, `into_iter`: its entries, as `[key, value]`. Each consuming one drops the map's own + * `BuildHasher` by `s` at once, as hashbrown's does. */ -export function hashIter(m: HashMap, k?: (key: K) => void, v?: (value: V) => void): Iter<[K, V]> { +export function hashIter(m: HashMap, k?: (key: K) => void, v?: (value: V) => void, s?: (state: never) => void): Iter<[K, V]> { + s?.(m.s as never); return new HashIter(m, ENTRIES, k, v); } /** `keys` of a map and `iter` of a set, and with what drops each key and value, `into_keys` and `into_iter`. */ -export function hashKeys(m: HashMap, k?: (key: K) => void, v?: (value: V) => void): Iter { +export function hashKeys(m: HashMap, k?: (key: K) => void, v?: (value: V) => void, s?: (state: never) => void): Iter { + s?.(m.s as never); return new HashIter(m, KEYS, k, v); } /** `values` and `values_mut`, and with what drops each value and key, `into_values`. */ -export function hashValues(m: HashMap, v?: (value: V) => void, k?: (key: K) => void): Iter { +export function hashValues(m: HashMap, v?: (value: V) => void, k?: (key: K) => void, s?: (state: never) => void): Iter { + s?.(m.s as never); return new HashIter(m, VALUES, k, v); } @@ -967,7 +981,7 @@ export function hashDrainKeys(m: HashMap, k?: (key: K) => void): Ite /** What `m` held, which it no longer does, with the room it had. */ function taken(m: HashMap): HashMap { hold(m); - const out: HashMap = { p: m.p, o: m.o, n: m.n, h: m.h }; + const out: HashMap = { p: m.p, o: m.o, n: m.n, h: m.h, s: undefined }; m.p = new Map(); hashClear(m); return out; @@ -1025,6 +1039,29 @@ function extendReserve(m: HashMap, additional: number, max: number | */ export function hashExtend(m: HashMap, items: Iter<[K, V]>, k?: (key: K) => void, v?: (value: V) => void, take?: Take, max?: number): HashMap { extendReserve(m, items.sizeHint()[0], max); + return hashFill(m, items, k, v, take); +} + +/** + * hashbrown's `from_iter` of a map, as `hashExtend` into what `make` makes, but which reads + * `items`' size hint first, as hashbrown's does before it makes its `BuildHasher`. + */ +export function hashFromIter( + items: Iter<[K, V]>, + make: () => HashMap, + k?: (key: K) => void, + v?: (value: V) => void, + take?: Take, + max?: number, +): HashMap { + const n = items.sizeHint()[0]; + const m = make(); + extendReserve(m, n, max); + return hashFill(m, items, k, v, take); +} + +/** Insert each of `items` into `m`, as `hashExtend` does once it has reserved. */ +function hashFill(m: HashMap, items: Iter<[K, V]>, k?: (key: K) => void, v?: (value: V) => void, take?: Take): HashMap { const add = (item: [K, V]) => { const old = hashInsert(m, item[0], item[1], k); if (old !== undefined) v?.(old); @@ -1038,9 +1075,17 @@ export function hashExtendKeys(m: HashSet, keys: Iter, k?: (key: K) => return extending(m, keys, (key) => hashAdd(m, key, k), take); } -/** `clone`, with each key and value through its `Clone`, or as itself without one. */ -export function hashClone(m: HashMap, key: (k: K) => K = itself, value: (v: V) => V = itself): HashMap { - const out = hashNew(m.h); +/** + * `clone`, with its own `BuildHasher` through `state`, first, as hashbrown's, then each key + * and value through its `Clone`, or each as itself without one. + */ +export function hashClone( + m: HashMap, + key: (k: K) => K = itself, + value: (v: V) => V = itself, + state?: (s: never) => unknown, +): HashMap { + const out = hashNew(m.h, state === undefined ? m.s : state(m.s as never)); for (const [k, v] of m.p) out.p.set(key(k), value(v)); // Each clone keeps its original's hash, as hashbrown's copies its table without // rehashing, and a table has no hasher to rehash by. diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index bbda85743..65ca57671 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -571,17 +571,33 @@ fn hash<'tcx>( let none = library::sentinel_option(b.t, call).is_some(); let mut values = args; let e = match (owner, name) { - (_, "new" | "with_hasher") | ("Default", "default") => { + (_, "new") | ("Default", "default") => { + discard(values, out); + let state = new_state(b, coll, span); + hash_new(b, coll, state, span)? + } + // Its own `BuildHasher`, which it keeps, or a `RandomState`, which nothing reads. + (_, "with_hasher") => { + let state = held_state(b, coll).and_then(|_| values.pop()); discard(values, out); - hash_new(b, coll, span)? + hash_new(b, coll, state, span)? } (_, "with_capacity" | "with_capacity_and_hasher") => { let n = values.remove(0); + let state = held_state(b, coll).and_then(|_| values.pop()); discard(values, out); let mut values = vec![n, hash_max(b, coll, span)]; values.extend(hashing(b, coll, key, span)?); + if let Some(state) = state { + values.resize(3, Expr::undefined()); + values.push(state); + } empty(b, Expr::rt("hashWithCapacity", values), coll, span) } + (_, "hasher") if let Some(state) = held_state(b, coll) => { + let ty = b.t.ty(state, span); + Expr::As(Box::new(values.pop()?.member("s")), ty) + } (_, "len") => Expr::rt("hashLen", values), (_, "is_empty") => Expr::rt("hashLen", values).binary("===", Expr::lit("0")), (_, "clear") => Expr::rt("hashClear", values), @@ -608,9 +624,13 @@ fn hash<'tcx>( // does: before `next` returns, and after `fold` has the item. (_, "into_keys" | "into_values") => { let (kept, other) = if name == "into_keys" { (0, 1) } else { (1, 0) }; - let Some(droppers) = b.t.droppers([arg(coll, kept), arg(coll, other)], span) else { + let Some(mut droppers) = b.t.droppers([arg(coll, kept), arg(coll, other)], span) else { return Some(Expr::undefined()); }; + if let Some(state) = state_dropper(b, coll, span) { + droppers.resize(2, Expr::undefined()); + droppers.push(state); + } let f = if name == "into_keys" { "hashKeys" } else { "hashValues" }; Expr::rt(f, super::iter::tail(values, droppers)) } @@ -625,7 +645,8 @@ fn hash<'tcx>( (_, name) if let Some(f) = set_op("hash", name) => Expr::rt(f, values), ("BitOr" | "BitAnd" | "Sub" | "BitXor", _) => { let keys = set_op_keys(b, coll, "hash", owner, values, span)?; - let target = hash_new(b, coll, span)?; + let state = new_state(b, coll, span); + let target = hash_new(b, coll, state, span)?; let max = hash_max(b, coll, span); Expr::rt( "hashExtendKeys", @@ -643,12 +664,16 @@ fn hash<'tcx>( None => Expr::undefined(), }; values.extend([k, v]); - Expr::rt("hashClone", values) + let s = held_state(b, coll).map(|s| clone_fn(b, s, span)); + Expr::rt("hashClone", super::iter::tail(values, s.into_iter().collect())) } ("Extend", "extend") | ("From", "from") => { let (source, iter) = extend_source(b, coll, call, owner, values.pop()?)?; let target = match owner { - "From" => hash_new(b, coll, span)?, + "From" => { + let state = new_state(b, coll, span); + hash_new(b, coll, state, span)? + } _ => values.pop()?, }; hash_extend(b, coll, target, source, iter, span) @@ -846,15 +871,22 @@ fn hash_extend<'tcx>( iter: Ty<'tcx>, span: Span, ) -> Expr { - let mut droppers = entry_droppers(b, coll, span); - // By `for_each`. - droppers.push(super::iter::iter_take(b, iter, true, span)); - droppers.push(hash_max(b, coll, span)); let f = match b.t.std_of(coll) { Some(Std::HashSet) => "hashExtendKeys", _ => "hashExtend", }; - Expr::rt(f, super::iter::tail(vec![target, source], droppers)) + let tail = hash_extend_tail(b, coll, iter, span); + Expr::rt(f, super::iter::tail(vec![target, source], tail)) +} + +/// What `hashExtend` and its kin take after the collection and the items, of an iterator of +/// type `iter`: what drops each entry, how it reads them, and the most entries it holds. +fn hash_extend_tail<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, iter: Ty<'tcx>, span: Span) -> Vec { + let mut tail = entry_droppers(b, coll, span); + // By `for_each`. + tail.push(super::iter::iter_take(b, iter, true, span)); + tail.push(hash_max(b, coll, span)); + tail } /// The most entries a hash collection of type `coll` holds on wasm32, past which hashbrown's @@ -902,13 +934,58 @@ fn hash_max<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, span: Span) -> Exp Expr::lit(lo.to_string()) } -/// An empty hash collection of type `coll`, which finds its keys as their type needs. -fn hash_new<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, span: Span) -> Option { +/// An empty hash collection of type `coll`, which finds its keys as their type needs and +/// holds `state`, its own `BuildHasher`, if given. +fn hash_new<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, state: Option, span: Span) -> Option { let key = arg(coll, 0); - let values = hashing(b, coll, key, span)?.into_iter().collect(); + let mut values: Vec = hashing(b, coll, key, span)?.into_iter().collect(); + if let Some(state) = state { + values.resize(1, Expr::undefined()); + values.push(state); + } Some(empty(b, Expr::rt("hashNew", values), coll, span)) } +/// The `BuildHasher` of a `HashMap` or `HashSet` of type `coll`. +fn state_of<'tcx>(b: &Body<'_, '_, 'tcx>, coll: Ty<'tcx>) -> Option> { + let n = match b.t.std_of(coll)? { + Std::HashMap => 2, + Std::HashSet => 1, + _ => return None, + }; + let ty::Adt(_, args) = coll.kind() else { + return None; + }; + args.types().nth(n) +} + +/// The `BuildHasher` a hash collection of type `coll` holds, which `hasher` lends: any but +/// `RandomState`, whose keys nothing reads. +fn held_state<'tcx>(b: &Body<'_, '_, 'tcx>, coll: Ty<'tcx>) -> Option> { + state_of(b, coll).filter(|&state| b.t.std_of(state) != Some(Std::RandomState)) +} + +/// `Default::default` of the `BuildHasher` a new hash collection of type `coll` holds, if +/// it holds one. +fn new_state<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, span: Span) -> Option { + let state = held_state(b, coll)?; + let tcx = b.t.tcx; + let default = trait_fn(tcx, "Default", "default"); + let mut body = Vec::new(); + let value = b.call_def(default, tcx.mk_args(&[state.into()]), Vec::new(), span, &mut body); + if body.is_empty() { + return Some(value); + } + body.push(StmtKind::Return(Some(value.typed())).at(None)); + Some(Expr::Arrow(Vec::new(), b.t.ty(state, span), body).call(Vec::new())) +} + +/// What drops the `BuildHasher` a hash collection of type `coll` holds, if it drops. +fn state_dropper<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, span: Span) -> Option { + let state = held_state(b, coll)?; + b.t.drops(state).then(|| b.t.dropper(state, span)) +} + /// `new`, as an empty collection of type `coll`: TS infers a generic key's type, as of /// `rt.cmp`, from no argument, only from the type it is asserted to be. fn empty<'tcx>(b: &mut Body<'_, '_, 'tcx>, new: Expr, coll: Ty<'tcx>, span: Span) -> Expr { @@ -1051,16 +1128,24 @@ fn derived_eq<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> bool { .is_some_and(|imp| tcx.is_automatically_derived(imp)) } -/// The `Hasher` a hash collection of type `coll` hashes with: its `BuildHasher`'s. +/// The `Hasher` a hash collection of type `coll` hashes a key with, as the runtime's own +/// hasher stands in for it: its `RandomState`'s, or std's `DefaultHasher` in place of any +/// other `BuildHasher`'s, which no key goes through. fn hasher_ty<'tcx>(b: &Body<'_, '_, 'tcx>, coll: Ty<'tcx>) -> Option> { - let ty::Adt(_, args) = coll.kind() else { - return None; - }; - // `HashMap` and `HashSet`. - let state = args - .types() - .nth(if b.t.std_of(coll) == Some(Std::HashSet) { 1 } else { 2 })?; - built_hasher(b, state).filter(|&hasher| b.t.std_of(hasher) == Some(Std::Hasher)) + let tcx = b.t.tcx; + if held_state(b, coll).is_some() { + let std = tcx + .crates(()) + .iter() + .find(|&&c| tcx.crate_name(c) == rustc_span::sym::std)?; + let child = |module: DefId, name: &str| { + let child = tcx.module_children(module).iter().find(|c| c.ident.as_str() == name)?; + child.res.opt_def_id() + }; + let hasher = child(child(std.as_def_id(), "hash")?, "DefaultHasher")?; + return Some(tcx.type_of(hasher).instantiate_identity().skip_norm_wip()); + } + built_hasher(b, state_of(b, coll)?).filter(|&hasher| b.t.std_of(hasher) == Some(Std::Hasher)) } /// The `Hasher` a `BuildHasher` of type `state` builds. @@ -2173,7 +2258,10 @@ fn default<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, span: Span) -> Opti Std::VecDeque => deque_new(b, coll, span), Std::LinkedList => list_new(b, coll, span), Std::BinaryHeap => empty(b, Expr::rt("heapNew", Vec::new()), coll, span), - Std::HashMap | Std::HashSet => hash_new(b, coll, span)?, + Std::HashMap | Std::HashSet => { + let state = new_state(b, coll, span); + hash_new(b, coll, state, span)? + } Std::BTreeMap | Std::BTreeSet => btree_new(b, coll, span), _ => return None, }) @@ -3071,7 +3159,12 @@ pub fn into_iter<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, sp if !ty.is_ref() { // A deque's is the deque itself, which it pops from. let name = if name == "dequeIter" { "dequeIntoIter" } else { name }; - let droppers = entry_droppers(b, coll, span); + let mut droppers = entry_droppers(b, coll, span); + // A map's own `BuildHasher` drops at once, after what drops each entry. + if let Some(state) = state_dropper(b, coll, span) { + droppers.resize(2, Expr::undefined()); + droppers.push(state); + } return Some(Expr::rt(name, super::iter::tail(vec![value], droppers))); } // What it holds, borrowed. @@ -3095,7 +3188,19 @@ pub fn collect<'tcx>( let std = b.t.std_of(ty)?; Some(match std { Std::HashMap | Std::HashSet => { - let target = hash_new(b, ty, span)?; + let state = new_state(b, ty, span); + let held = state.is_some(); + let target = hash_new(b, ty, state, span)?; + // hashbrown's map takes its items and reads their size hint before it makes its + // hasher, where std's and hashbrown's set make it first. + if std == Std::HashMap + && held + && matches!(ty.kind(), ty::Adt(adt, _) if super::stock::as_std(b.t.tcx, adt.did())) + { + let make = Expr::Lambda(Vec::new(), Box::new(target)); + let tail = hash_extend_tail(b, ty, from, span); + return Some(Expr::rt("hashFromIter", super::iter::tail(vec![iter, make], tail))); + } hash_extend(b, ty, target, iter, from, span) } Std::BTreeMap | Std::BTreeSet => btree_collect(b, ty, iter, from, trusted, span), diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index b38cea08e..7516451cb 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -297,7 +297,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Entry) if self.tcx.item_name(adt.did()).as_str() == "OccupiedEntry" => true, Some(Std::OnceCell | Std::OnceLock) => self.uniform(Ty::new_option(self.tcx, args.type_at(0))), Some(Std::Cow) => library::cow_slice(self.tcx, ty).is_none_or(|elem| self.uniform(elem)), - Some(Std::LazyCell | Std::LazyLock | Std::HashMap | Std::BTreeMap) => held(self, 2), + // A hash map's and set's own `BuildHasher` too. + Some(Std::HashMap) => held(self, 3), + Some(Std::HashSet) => held(self, 2), + Some(Std::LazyCell | Std::LazyLock | Std::BTreeMap) => held(self, 2), Some( Std::Arc | Std::Mutex @@ -309,7 +312,6 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | Std::Wrapping | Std::Saturating | Std::PoisonError - | Std::HashSet | Std::HashTable | Std::BTreeSet | Std::VecDeque @@ -415,9 +417,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } // Its value, or the function that never made one. Std::LazyCell | Std::LazyLock => ("lazyDrop", vec![value, each(self, 0)?, each(self, 1)?]), - Std::HashMap => ("hashDrop", vec![value, each(self, 0)?, each(self, 1)?]), + // Its own `BuildHasher` last, which the runtime drops first, as hashbrown's field. + Std::HashMap => ("hashDrop", vec![value, each(self, 0)?, each(self, 1)?, each(self, 2)?]), Std::BTreeMap => ("btreeDrop", vec![value, each(self, 0)?, each(self, 1)?]), - Std::HashSet => ("hashDrop", vec![value, each(self, 0)?]), + Std::HashSet => ( + "hashDrop", + vec![value, each(self, 0)?, Expr::undefined(), each(self, 1)?], + ), // Its items are the values of a map whose keys are nothing. Std::HashTable => ("hashDrop", vec![value, Expr::undefined(), each(self, 0)?]), Std::BTreeSet => ("btreeDrop", vec![value, each(self, 0)?]),