From e17a3439440eeb0f7e2189ad398bc4ba002d22ec Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:26:45 -0800 Subject: [PATCH 1/4] chore(quest): claim quest/m3/ts-build-hasher-default.md Quest-Claim: 82c10d747866d9d8 From 371643ad39446fe9f6256eeda298f7e9ac223485 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:35:35 -0800 Subject: [PATCH 2/4] feat(ts): translate BuildHasherDefault as a value that holds nothing Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-build-hasher-default.md | 21 ---- ts/README.md | 2 +- .../lang/hashes/expected/hashes.Fnv.ts | 4 + .../lang/hashes/expected/hashes.Key.ts | 6 + .../lang/hashes/expected/hashes.Tallied.ts | 39 ++++++ .../lang/hashes/expected/hashes.Tally.ts | 46 +++++++ ts/fixtures/lang/hashes/expected/hashes.ts | 93 ++++++++++++++ ts/fixtures/lang/hashes/expected/lib.ts | 14 ++- ts/fixtures/lang/hashes/src/hashes.rs | 113 +++++++++++++++++- ts/fixtures/lang/hashes/src/lib.rs | 1 + ts/fixtures/stock/expected/harness.ts | 4 +- ts/fixtures/stock/expected/maps.ts | 40 +++++++ ts/fixtures/stock/src/harness.rs | 1 + ts/fixtures/stock/src/maps.rs | 23 +++- ts/src/lower/collections.rs | 45 ++++++- ts/src/lower/consts.rs | 2 +- ts/src/lower/fmt.rs | 5 +- ts/src/lower/library.rs | 10 +- ts/src/lower/mir/rvalue.rs | 18 ++- ts/src/lower/mod.rs | 2 +- 21 files changed, 444 insertions(+), 46 deletions(-) delete mode 100644 quest/m3/ts-build-hasher-default.md create mode 100644 ts/fixtures/lang/hashes/expected/hashes.Tallied.ts create mode 100644 ts/fixtures/lang/hashes/expected/hashes.Tally.ts diff --git a/quest/m3/README.md b/quest/m3/README.md index ed3e956a9..6f00d761b 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -34,7 +34,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 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 - [A panic's Location](/quest/m3/ts-panic-location.md) - `Location::caller` and `#[track_caller]` translate with native Rust's locations diff --git a/quest/m3/ts-build-hasher-default.md b/quest/m3/ts-build-hasher-default.md deleted file mode 100644 index 59fe588b5..000000000 --- a/quest/m3/ts-build-hasher-default.md +++ /dev/null @@ -1,21 +0,0 @@ -# [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/ts/README.md b/ts/README.md index 6fbe5213f..43e8b2090 100644 --- a/ts/README.md +++ b/ts/README.md @@ -117,7 +117,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | a range a drain or a B-tree's `range` takes | itself, or of a type other than std's ranges and a pair of `Bound`s, `rt.boundsOf` of it and its `start_bound` and `end_bound`, which the runtime calls where std's does; one that drops, the parameter of the call `rt.dropAfter` makes, which drops it once the call returns, as std drops it before it does anything JS can see | | `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet`, `VecDeque`, `LinkedList`, `BinaryHeap` | `rt.HashMap` and so on, each a plain object whose functions are the runtime's: `rt.hashGet(map, key)` | | `PeekMut` | `rt.PeekMut`, the `rt.Place` of a heap's greatest element, which sifts it down as it drops once `DerefMut` lent it | -| `Ordering`, `DefaultHasher`, `RandomState` | `rt.Ordering`, which is `-1`, `0`, or `1`; `rt.Hasher`; `rt.RandomState`, the keys of the `DefaultHasher` it builds, never changed once made | +| `Ordering`, `DefaultHasher`, `RandomState`, `BuildHasherDefault` | `rt.Ordering`, which is `-1`, `0`, or `1`; `rt.Hasher`; `rt.RandomState`, the keys of the `DefaultHasher` it builds, never changed once made; `undefined`, as `PhantomData` is, whose `build_hasher` is `H::default()` | | `Duration` | `rt.Duration`: `{ secs, nanos }`, std's whole seconds as an `rt.u64` and the nanoseconds past them, never changed once made | | `Layout`, `alloc::alloc` | `rt.Layout`: `{ size, align }`, never changed once made, checked as std's constructors check it; `alloc` gives an `rt.Block` of its bytes, which a cast of its pointer to `*mut T` the body shows came from `alloc` or `realloc` makes a JS array of `T`s, values JS copies, as an `rt.AtIndex` of its first; a cast back to `*mut u8` only as what `dealloc` or `realloc` takes, which free the array or copy it into a new one; its bytes, read, written, or cast to any other type, are refused, and fail at run time through a pointer from elsewhere | | closure | an arrow function, with `[Symbol.dispose]` if it owns a value that drops, and what makes its clone if a clone can't be itself | diff --git a/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts b/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts index 8815c1d5d..222fd05f8 100644 --- a/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts +++ b/ts/fixtures/lang/hashes/expected/hashes.Fnv.ts @@ -10,6 +10,10 @@ export interface Fnv { _0: rt.u64; } +export function default$Default(): Fnv { + return { _0: { hi: 0xcbf29ce4, lo: 0x84222325 } as rt.u64 }; +} + export function finish$hash_Hasher(self: Fnv): rt.u64 { return self._0; } diff --git a/ts/fixtures/lang/hashes/expected/hashes.Key.ts b/ts/fixtures/lang/hashes/expected/hashes.Key.ts index 01bd99c7f..9d25cccd4 100644 --- a/ts/fixtures/lang/hashes/expected/hashes.Key.ts +++ b/ts/fixtures/lang/hashes/expected/hashes.Key.ts @@ -2,6 +2,7 @@ import * as rt from "./rt.ts"; import * as Fnv from "./hashes.Fnv.ts"; +import * as Tally from "./hashes.Tally.ts"; export function eq$cmp_PartialEq(self: Key, other: Key): boolean { if (!(self._0 === other._0)) { @@ -20,6 +21,11 @@ export function hash$Hash_hashes_Fnv(self: Key, state: Fnv.Fnv): void { rt.hashStr(self._1, new rt.OwnHasher(state, Fnv.hasher$hash_Hasher)); } +export function hash$Hash_hashes_Tally(self: Key, state: Tally.Tally): void { + rt.hash16(self._0, new rt.OwnHasher(state, Tally.hasher$hash_Hasher)); + rt.hashStr(self._1, new rt.OwnHasher(state, Tally.hasher$hash_Hasher)); +} + export function partialCmp$cmp_PartialOrd(self: Key, other: Key): rt.Ordering | undefined { return cmp$cmp_Ord(self, other); } diff --git a/ts/fixtures/lang/hashes/expected/hashes.Tallied.ts b/ts/fixtures/lang/hashes/expected/hashes.Tallied.ts new file mode 100644 index 000000000..42dac65f4 --- /dev/null +++ b/ts/fixtures/lang/hashes/expected/hashes.Tallied.ts @@ -0,0 +1,39 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export function default$Default(): Tallied { + const _1: rt.u8 = 0; + undefined; + return { tag: _1, state: undefined }; +} + +export function clone$Clone(self: Tallied): Tallied { + const _2: rt.u8 = self.tag; + undefined; + return { tag: _2, state: undefined }; +} + +export function fmt$fmt_Debug(self: Tallied, f: rt.Formatter): rt.Result { + const _3: string = "Tallied"; + const _4: string = "tag"; + const _5: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self.tag, f); + const _7: string = "state"; + const _8: rt.Fmt = (f: rt.Formatter) => rt.fmtWriteStr(f, "BuildHasherDefault"); + return rt.debugStructFinish(rt.debugStructField(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5), _7, _8)); +} + +export function eq$cmp_PartialEq(self: Tallied, other: Tallied): boolean { + if (!(self.tag === other.tag)) { + return false; + } + return true; +} + +/** `BuildHasherDefault` held in a field, its impls derived. */ +export interface Tallied { + tag: rt.u8; + state: undefined; +} + +//# sourceMappingURL=hashes.Tallied.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.Tally.ts b/ts/fixtures/lang/hashes/expected/hashes.Tally.ts new file mode 100644 index 000000000..f1e18f887 --- /dev/null +++ b/ts/fixtures/lang/hashes/expected/hashes.Tally.ts @@ -0,0 +1,46 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { event } from "./hashes.ts"; + +/** + * A `Hasher` of the crate's own that `BuildHasherDefault` builds, logging each one + * `Default` makes, each byte written, each finish, and each drop. + */ +export class Tally { + _0: rt.u64; + + constructor(_0: rt.u64) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function default$Default(): Tally { + event(110, 0); + return new Tally(0); +} + +export function finish$hash_Hasher(self: Tally): rt.u64 { + event(102, rt.u64Low(self._0) & 0xff); + return self._0; +} + +/** The methods of `hashes::Tally`'s impl, which the runtime's hashing calls. */ +export const hasher$hash_Hasher: rt.HasherImpl = { write: write$hash_Hasher as (a0: Tally, a1: rt.ByteSlice) => undefined, finish: finish$hash_Hasher }; + +export function write$hash_Hasher(self: Tally, bytes: rt.ByteSlice): void { + for (const b of rt.byteSliceView(bytes)) { + event(119, b); + self._0 = rt.u64WrappingAdd(rt.u64WrappingMul(self._0, 31), b as rt.u64); + } +} + +export function drop$ops_Drop(self: Tally): void { + event(100, rt.u64Low(self._0) & 0xff); +} + +//# sourceMappingURL=hashes.Tally.ts.map diff --git a/ts/fixtures/lang/hashes/expected/hashes.ts b/ts/fixtures/lang/hashes/expected/hashes.ts index d5bce2449..b8e2184a1 100644 --- a/ts/fixtures/lang/hashes/expected/hashes.ts +++ b/ts/fixtures/lang/hashes/expected/hashes.ts @@ -25,6 +25,8 @@ import * as Seeded from "./hashes.Seeded.ts"; import * as Shape from "./hashes.Shape.ts"; import * as Small from "./hashes.Small.ts"; import * as Switch from "./hashes.Switch.ts"; +import * as Tallied from "./hashes.Tallied.ts"; +import * as Tally from "./hashes.Tally.ts"; /** `DefaultHasher::new`'s hash of `value`, little-endian. */ export function digest$Option_Option_u8(value: rt.u8 | undefined | rt.None, out: rt.ByteVec): void { @@ -1114,4 +1116,95 @@ export function maps(data: rt.ByteSlice, out: rt.ByteVec): void { rt.hashDrop(seen, undefined, undefined, rt.drop); } +/** + * `BuildHasherDefault`, made by `new`, `Default`, and `Clone`, which all compare equal and + * print alike whatever their `Hasher`, and each builds its hasher by the hasher's own + * `Default`, once per hash. Then hash maps and sets that hold one, which find their keys + * whatever it hashes them to, and say only sorted what they hold, as std's order is its own. + */ +export function defaults(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); + Tallied.default$Default(); + const held: Tallied.Tallied = { tag: first, state: undefined }; + const cloned: Tallied.Tallied = Tallied.clone$Clone(held); + rt.byteVecPush(out, true ? 1 : 0); + rt.byteVecPush(out, false ? 1 : 0); + rt.byteVecPush(out, Tallied.eq$cmp_PartialEq(held, cloned) ? 1 : 0); + const _36: Tallied.Tallied = Tallied.default$Default(); + rt.byteVecPush(out, Tallied.eq$cmp_PartialEq(held, _36) ? 1 : 0); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "BuildHasherDefault"), (f: rt.Formatter) => rt.fmtOption(undefined, f, (_value: undefined, f: rt.Formatter) => rt.fmtWriteStr(f, "BuildHasherDefault"), rt.None), (f: rt.Formatter) => Tallied.fmt$fmt_Debug(cloned, f), (f: rt.Formatter) => Tallied.fmt$fmt_Debug(held, f)]; + put(out, rt.fmtFormat([args2[0], " ", args2[1], " ", args2[2], " ", rt.fmtSpec({ alternate: true }, args2[3])])); + const word: string = result_unwrapOr$01jdcm7(rt.strFromUtf8(data), "✓"); + const n: rt.u16 = rt.u128Low(wide(data, 0, 2)) & 0xffff; + const built: Tally.Tally = Tally.default$Default(); + rt.hash16(n, new rt.OwnHasher(built, Tally.hasher$hash_Hasher)); + const _66: rt.u64 = Tally.finish$hash_Hasher(built); + const _68: rt.u64 = rt.hashOne(new rt.OwnHasher(Tally.default$Default(), Tally.hasher$hash_Hasher), n, rt.hash16, undefined, rt.drop); + const _70: rt.u64 = rt.hashOne(new rt.OwnHasher(Tally.default$Default(), Tally.hasher$hash_Hasher), word, rt.hashStr, undefined, rt.drop); + const _74: [rt.u8, string] = [n & 0xff, word]; + const _72: rt.u64 = rt.hashOne(new rt.OwnHasher(Tally.default$Default(), Tally.hasher$hash_Hasher), _74, rt.hashTuple([rt.hash8, rt.hashStr]), undefined, rt.drop); + const _79: Key.Key = { _0: n, _1: word }; + const hashes: rt.u64[] = [_66, _68, _70, _72, rt.hashOne(new rt.OwnHasher(Tally.default$Default(), Tally.hasher$hash_Hasher), _79, rt.hashOwn(Key.hash$Hash_hashes_Tally as (a0: Key.Key, a1: Tally.Tally) => undefined), undefined, rt.drop)]; + built[Symbol.dispose](); + const iter: rt.Iter = rt.iterSlice(hashes); + while (true) { + const _85: rt.u64 | rt.Done = iter.next(); + if (_85 === rt.DONE) { + break; + } + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(_85).reverse())); + } + const keyed: rt.HashMap = rt.hashNew(rt.hashing(Key.hash$Hash_hash_DefaultHasher as (a0: Key.Key, a1: rt.Hasher) => undefined, Key.eq$cmp_PartialEq), undefined) as rt.HashMap; + const seen: rt.HashSet = rt.hashWithCapacity(data.length, 469762048, undefined, undefined) as rt.HashSet; + const iter2: rt.Iter<[rt.usize, rt.u8]> = rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(data))); + while (true) { + const _101: [rt.usize, rt.u8] | rt.Done = iter2.next(); + if (_101 === rt.DONE) { + break; + } + const i: rt.usize = _101[0]; + const b: rt.u8 = _101[1]; + const _110: rt.u16 = rt.u8Rem(b, 8) as rt.u16; + const bounds = rt.sliceIndexBounds(new rt.RangeInclusive(i, i, rt.usizeStep), data.length); + const _109: Key.Key = { _0: _110, _1: text(rt.byteSliceRange(data, bounds[0], bounds[1])) }; + const _106: rt.Place = rt.entryOrInsertWithMut(rt.entry(keyed, _109), (): rt.u8 => { + return 0; + }); + _106.v = rt.u8Add(_106.v, 1); + rt.byteVecPush(out, rt.hashAdd(seen, rt.u8Rem(b, 16)) ? 1 : 0); + const _128: 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, _128) ? 1 : 0); + } + const entries: [Key.Key, rt.u8][] = rt.iterToArray(rt.hashIter(keyed), "next", 8); + rt.sliceSort(entries, rt.cmpLt(rt.cmpTuple<[Key.Key, rt.u8], rt.Ordering | undefined>([Key.partialCmp$cmp_PartialOrd, rt.cmp])), 8, true); + for (const _147 of entries) { + rt.byteVecPush(out, _147[0]._0 & 0xff); + put(out, _147[0]._1); + rt.byteVecPush(out, _147[1]); + } + const probe: Key.Key = { _0: first as rt.u16, _1: text(rt.byteSliceFrom(Uint8Array.of(first))) }; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(rt.hashOne(new rt.OwnHasher(Fnv.default$Default(), Fnv.hasher$hash_Hasher), probe, rt.hashOwn(Key.hash$Hash_hashes_Fnv as (a0: Key.Key, a1: Fnv.Fnv) => undefined))).reverse())); + rt.hashClone(seen, undefined, undefined, (_a0: undefined): undefined => { + return undefined; + }).s as undefined; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(rt.hashOne(new rt.OwnHasher(Fnv.default$Default(), Fnv.hasher$hash_Hasher), first, rt.hash8)).reverse())); + rt.byteVecPush(out, true ? 1 : 0); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtWriteStr(f, "BuildHasherDefault")]; + put(out, rt.fmtFormat([args4[0]])); + const _205: 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, undefined) as rt.HashSet, _205, undefined, undefined, 469762048); + rt.byteVecPush(out, rt.hashLen(rt.hashExtendKeys(rt.hashNew(undefined, undefined) as rt.HashSet, rt.hashUnion(small, seen), undefined, undefined, 469762048)) & 0xff); + rt.byteVecPush(out, rt.hashIsSubset(small, seen) ? 1 : 0); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(((events2: rt.RefCell): rt.ByteVec => { + return rt.refCellReplace(events2, new rt.ByteVec()); + })(EVENTS.get()))); +} + //# sourceMappingURL=hashes.ts.map diff --git a/ts/fixtures/lang/hashes/expected/lib.ts b/ts/fixtures/lang/hashes/expected/lib.ts index 8de3149d7..c174fe06d 100644 --- a/ts/fixtures/lang/hashes/expected/lib.ts +++ b/ts/fixtures/lang/hashes/expected/lib.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { values, random, bytes, own, copied as hashes_copied, maps } from "./hashes.ts"; +import { values, random, bytes, own, copied as hashes_copied, maps, defaults } 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"; @@ -30,16 +30,18 @@ export function drop$tuple2_u8_hashes_Noisy(_1: [rt.u8, Noisy.Noisy]): void { * their count, so a case added at the end moves no other. */ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { - return [["hashes::values", values as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::random", (_22: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { + return [["hashes::values", values as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::random", (_24: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { random(data, out); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::bytes", (_23: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::bytes", (_25: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): undefined => { bytes(data2, out2); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::own", (_24: rt.u8, data3: rt.ByteSlice, out3: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::own", (_26: rt.u8, data3: rt.ByteSlice, out3: rt.ByteVec): undefined => { 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 => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::copied", (_27: 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 => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::maps", (_28: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): undefined => { maps(data5, out5); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["hashes::defaults", (_29: rt.u8, data6: rt.ByteSlice, out6: rt.ByteVec): undefined => { + defaults(data6, out6); }] 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 86acbe978..fa05b5525 100644 --- a/ts/fixtures/lang/hashes/src/hashes.rs +++ b/ts/fixtures/lang/hashes/src/hashes.rs @@ -3,12 +3,13 @@ //! `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. Then hash maps and sets -//! that hold a `BuildHasher` of the crate's own. +//! that hold a `BuildHasher` of the crate's own, and `BuildHasherDefault` of a `Hasher` 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::hash::{BuildHasher, BuildHasherDefault, DefaultHasher, Hash, Hasher, RandomState}; use std::num::NonZero; use std::sync::Arc; use std::time::Duration; @@ -630,6 +631,12 @@ fn event(what: u8, n: u8) { /// as it likes. struct Fnv(u64); +impl Default for Fnv { + fn default() -> Self { + Fnv(0xcbf2_9ce4_8422_2325) + } +} + impl Hasher for Fnv { fn finish(&self) -> u64 { self.0 @@ -751,3 +758,105 @@ pub fn maps(data: &[u8], out: &mut Vec) { drop(union); out.extend_from_slice(&EVENTS.with(|events| events.take())); } + +/// A `Hasher` of the crate's own that `BuildHasherDefault` builds, logging each one +/// `Default` makes, each byte written, each finish, and each drop. +struct Tally(u64); + +impl Default for Tally { + fn default() -> Self { + event(b'n', 0); + Tally(0) + } +} + +impl Hasher for Tally { + fn finish(&self) -> u64 { + event(b'f', self.0 as u8); + self.0 + } + + fn write(&mut self, bytes: &[u8]) { + for &b in bytes { + event(b'w', b); + self.0 = self.0.wrapping_mul(31).wrapping_add(u64::from(b)); + } + } +} + +impl Drop for Tally { + fn drop(&mut self) { + event(b'd', self.0 as u8); + } +} + +/// `BuildHasherDefault` held in a field, its impls derived. +#[derive(Default, Clone, Debug, PartialEq)] +struct Tallied { + tag: u8, + state: BuildHasherDefault, +} + +/// One made in a `const`. +const MADE: BuildHasherDefault = BuildHasherDefault::new(); + +/// `BuildHasherDefault`, made by `new`, `Default`, and `Clone`, which all compare equal and +/// print alike whatever their `Hasher`, and each builds its hasher by the hasher's own +/// `Default`, once per hash. Then hash maps and sets that hold one, which find their keys +/// whatever it hashes them to, and say only sorted what they hold, as std's order is its own. +pub fn defaults(data: &[u8], out: &mut Vec) { + EVENTS.with(|events| events.borrow_mut().clear()); + let first = data.first().copied().unwrap_or(7); + let made = BuildHasherDefault::::new(); + let copy = made.clone(); + let held = Tallied { + tag: first, + ..Tallied::default() + }; + let cloned = held.clone(); + out.push((made == copy) as u8); + out.push((MADE != BuildHasherDefault::default()) as u8); + out.push((held == cloned) as u8); + out.push((held == Tallied::default()) as u8); + put(out, &format!("{made:?} {:?} {cloned:?} {held:#?}", Some(&MADE))); + let word = std::str::from_utf8(data).unwrap_or("✓"); + let n = wide(data, 0, 2) as u16; + let mut built = made.build_hasher(); + n.hash(&mut built); + let hashes = [ + built.finish(), + made.hash_one(n), + copy.hash_one(word), + MADE.hash_one((n as u8, word)), + cloned.state.hash_one(&Key(n, word.to_string())), + ]; + drop(built); + for h in hashes { + out.extend_from_slice(&h.to_le_bytes()); + } + let mut keyed: HashMap> = HashMap::default(); + let mut seen: HashSet> = + HashSet::with_capacity_and_hasher(data.len(), BuildHasherDefault::new()); + 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 entries: Vec<(&Key, &u8)> = keyed.iter().collect(); + entries.sort(); + for (Key(n, text), count) in entries { + out.push(*n as u8); + put(out, text); + out.push(*count); + } + let probe = Key(u16::from(first), text(&[first])); + out.extend_from_slice(&keyed.hasher().hash_one(&probe).to_le_bytes()); + out.extend_from_slice(&seen.clone().hasher().hash_one(first).to_le_bytes()); + out.push((*keyed.hasher() == BuildHasherDefault::default()) as u8); + put(out, &format!("{:?}", seen.hasher())); + 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); + 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 60ab0ca46..b64fc310b 100644 --- a/ts/fixtures/lang/hashes/src/lib.rs +++ b/ts/fixtures/lang/hashes/src/lib.rs @@ -24,5 +24,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("hashes::own", |_, data, out| hashes::own(data, out)), ("hashes::copied", |_, data, out| hashes::copied(data, out)), ("hashes::maps", |_, data, out| hashes::maps(data, out)), + ("hashes::defaults", |_, data, out| hashes::defaults(data, out)), ] } diff --git a/ts/fixtures/stock/expected/harness.ts b/ts/fixtures/stock/expected/harness.ts index 0ca27b03f..8f623998b 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, ownHasher, ownFromIter } from "./maps.ts"; +import { map as maps_map, table, capacity, linked, linkedIters, nested, ownHasher, ownFromIter, defaultHasher } 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], ["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]]; + }] 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], ["maps::default_hasher", defaultHasher 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/maps.ts b/ts/fixtures/stock/expected/maps.ts index c5ccf6f21..98a92e98f 100644 --- a/ts/fixtures/stock/expected/maps.ts +++ b/ts/fixtures/stock/expected/maps.ts @@ -1251,4 +1251,44 @@ export function ownFromIter(data: rt.ByteSlice, out: rt.ByteVec): void { })(MADE.get()))); } +/** + * `hashbrown::HashMap` and `HashSet` with `BuildHasherDefault` of std's `DefaultHasher`, + * made by `default` and `from_iter`, whose `hasher` hashes as `DefaultHasher::new` does. + */ +export function defaultHasher(data: rt.ByteSlice, out: rt.ByteVec): void { + let _25!: rt.u8; + let _32!: rt.u8; + 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])), undefined) as rt.HashMap<[rt.u8, rt.u8], rt.u32>; + const _5: rt.Iter = rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (b3: rt.u8): rt.u8 => { + return rt.u8Rem(b3, 8); + }); + const set: rt.HashSet = rt.hashExtendKeys(rt.hashNew(undefined, undefined) as rt.HashSet, _5, undefined, undefined, 469762048); + for (const b of rt.byteSliceView(data)) { + const _16: [rt.u8, rt.u8] = [rt.u8Rem(b, 4), rt.u8Rem(b, 3)]; + const _13: rt.Place = rt.entryOrInsertMut(rt.entry(pairs, _16), 0); + _13.v = rt.u32Add(_13.v, 1); + } + let iterStart: rt.u8 = 0; + const iterEnd: rt.u8 = 4; + l3: while ((_25 = iterStart++) < iterEnd) { + const a: rt.u8 = _25; + let iterStart2: rt.u8 = 0; + const iterEnd2: rt.u8 = 3; + while (true) { + if (!((_32 = iterStart2++) < iterEnd2)) { + continue l3; + } + put(out, option_unwrapOr$11c0we2(rt.hashGet(pairs, [a, _32] as [rt.u8, rt.u8]), 0) as rt.usize); + } + } + put(out, rt.hashLen(set)); + const first: rt.u8 = option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data), 0); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(rt.hashOne(rt.hasher(), [first, 0] as [rt.u8, rt.u8], rt.hashTuple([rt.hash8, rt.hash8]))).reverse())); + rt.hashClone(set, undefined, undefined, (_a0: undefined): undefined => { + return undefined; + }).s as undefined; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.u64ToBeBytes(rt.hashOne(rt.hasher(), first, rt.hash8)).reverse())); + rt.byteVecPush(out, true ? 1 : 0); +} + //# sourceMappingURL=maps.ts.map diff --git a/ts/fixtures/stock/src/harness.rs b/ts/fixtures/stock/src/harness.rs index cdc5591a6..c18f167c7 100644 --- a/ts/fixtures/stock/src/harness.rs +++ b/ts/fixtures/stock/src/harness.rs @@ -31,6 +31,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("buf::late", crate::buf::late), ("maps::own_hasher", crate::maps::own_hasher), ("maps::own_from_iter", crate::maps::own_from_iter), + ("maps::default_hasher", crate::maps::default_hasher), ] } diff --git a/ts/fixtures/stock/src/maps.rs b/ts/fixtures/stock/src/maps.rs index a741927b4..0674eac54 100644 --- a/ts/fixtures/stock/src/maps.rs +++ b/ts/fixtures/stock/src/maps.rs @@ -5,7 +5,7 @@ use std::cell::{Cell, RefCell}; use std::collections::hash_map::RandomState; -use std::hash::BuildHasher; +use std::hash::{BuildHasher, BuildHasherDefault, DefaultHasher}; use std::sync::Arc; use hashbrown::hash_table::{Entry, HashTable}; @@ -734,3 +734,24 @@ pub fn own_from_iter(data: &[u8], out: &mut Vec) { drop((hinted, keys)); out.extend_from_slice(&MADE.with(|made| made.take())); } + +/// `hashbrown::HashMap` and `HashSet` with `BuildHasherDefault` of std's `DefaultHasher`, +/// made by `default` and `from_iter`, whose `hasher` hashes as `DefaultHasher::new` does. +pub fn default_hasher(data: &[u8], out: &mut Vec) { + type Build = BuildHasherDefault; + let mut pairs: HashMap<(u8, u8), u32, Build> = HashMap::default(); + let set: HashSet = data.iter().map(|b| b % 8).collect(); + for &b in data { + *pairs.entry((b % 4, b % 3)).or_insert(0) += 1; + } + for a in 0..4 { + for b in 0..3 { + put(out, pairs.get(&(a, b)).copied().unwrap_or(0) as usize); + } + } + put(out, set.len()); + let first = data.first().copied().unwrap_or(0); + out.extend_from_slice(&pairs.hasher().hash_one((first, 0u8)).to_le_bytes()); + out.extend_from_slice(&set.clone().hasher().hash_one(first).to_le_bytes()); + out.push(u8::from(*set.hasher() == Build::default())); +} diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index 9a113a915..057621952 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -170,7 +170,9 @@ pub fn call<'tcx>( values.insert(0, own_state(b, h, state, span)); return Ok(Some(hasher_call(b, name, values, span, out))); } - ("BuildHasher", _, Some(Std::RandomState), Some(t)) => (Std::RandomState, t.peel_refs()), + ("BuildHasher", _, Some(std @ (Std::RandomState | Std::BuildHasherDefault)), Some(t)) => { + (std, t.peel_refs()) + } ("BuildHasher", "hash_one", None, Some(s)) if let Some(h) = built_hasher(b, s) && own_hasher(b.t, h) => @@ -240,6 +242,7 @@ pub fn call<'tcx>( Std::Ordering => ordering(name, args), Std::Hasher if matches!(name, "new" | "default") => Some(Expr::rt("hasher", vec![])), Std::RandomState => Some(random_state(b, call, coll, name, args)), + Std::BuildHasherDefault => Some(build_hasher_default(b, call, coll, name, args, out)), _ => return Err(args), }; let Some(e) = e else { @@ -2396,6 +2399,46 @@ fn random_state<'tcx>( } } +/// A function of `BuildHasherDefault`, or of a trait it implements, which holds nothing: +/// making one, which `Default` and `Clone` do too, building its hasher by the hasher's own +/// `Default`, or hashing one value with it as `BuildHasher::hash_one` does, dropping the +/// value. +fn build_hasher_default<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + call: &Call<'tcx>, + ty: Ty<'tcx>, + name: &str, + args: Vec, + out: &mut Vec, +) -> Expr { + let (tcx, span) = (b.t.tcx, call.span); + let ty::Adt(_, gargs) = ty.kind() else { + unreachable!("a BuildHasherDefault") + }; + let hasher = gargs.type_at(0); + match name { + "new" | "default" | "clone" => Expr::undefined(), + "build_hasher" => { + let default = trait_fn(tcx, "Default", "default"); + b.call_def(default, tcx.mk_args(&[hasher.into()]), Vec::new(), span, out) + } + "hash_one" if own_hasher(b.t, hasher) => own_hash_one(b, call, ty, hasher, args, out), + "hash_one" if b.t.std_of(hasher) == Some(Std::Hasher) => { + let Ok([_, value]) = <[Expr; 2]>::try_from(args) else { + unreachable!("hash_one takes a state and a value") + }; + let value_ty = library::targ(call.args, 1); + let mut args = vec![Expr::rt("hasher", vec![]), value, hash_fn(b, value_ty, hasher, span)]; + args.extend(b.t.drop_fn(value_ty, span)); + Expr::rt("hashOne", args) + } + _ => b.err( + span, + format!("`BuildHasherDefault<{hasher}>::{name}`, which is not supported yet"), + ), + } +} + /// `Hasher`'s methods: writing an integer or a string, and `finish`. fn hasher_call(b: &mut Body<'_, '_, '_>, name: &str, args: Vec, span: Span, out: &mut Vec) -> Expr { let mut values = args; diff --git a/ts/src/lower/consts.rs b/ts/src/lower/consts.rs index 89eb8b7a3..30c65108d 100644 --- a/ts/src/lower/consts.rs +++ b/ts/src/lower/consts.rs @@ -173,7 +173,7 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { values.extend(super::library::range_step(tcx, args.type_at(0), false)); Expr::New(Box::new(Expr::Rt("Range".into())), values) } - (Std::RangeFull, _) => Expr::undefined(), + (Std::RangeFull | Std::BuildHasherDefault, _) => Expr::undefined(), (Std::ParseIntError, _) => { Expr::Object(vec![(Key::Name("kind".into()), values.pop().unwrap())]) } diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index 79076d8e3..f0a60fa3e 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -289,7 +289,10 @@ pub fn std_impl<'tcx>( } // Each its name alone, unpadded. ty::Adt(adt, _) - if tr == Trait::Debug && (library::pinned(tcx, adt.did()) || library::empty_iter(tcx, adt.did())) => + if tr == Trait::Debug + && (library::pinned(tcx, adt.did()) + || library::empty_iter(tcx, adt.did()) + || Std::of(tcx, adt.did()) == Some(Std::BuildHasherDefault)) => { rt("writeStr", vec![f, Expr::Str(tcx.item_name(adt.did()).to_string())]) } diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 8a70fb8d7..3e71b5f68 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -181,6 +181,8 @@ pub enum Std { Hasher, /// `rt.RandomState`: the keys of the `DefaultHasher` it builds, never changed once made. RandomState, + /// `BuildHasherDefault`, which holds nothing and builds its `Hasher` by `Default`. + BuildHasherDefault, /// `rt.Duration`: its seconds and nanoseconds, never changed once made. Duration, /// `rt.Layout`: its size and alignment, never changed once made. @@ -370,6 +372,7 @@ impl Std { ["num", "saturating", "Saturating"] => Some(Self::Saturating), ["hash", "random", "DefaultHasher"] => Some(Self::Hasher), ["hash", "random", "RandomState"] => Some(Self::RandomState), + ["hash", "BuildHasherDefault"] => Some(Self::BuildHasherDefault), ["collections", "hash", "map", "Entry" | "OccupiedEntry" | "VacantEntry"] | [ "collections", @@ -422,12 +425,15 @@ impl Flow { } /// Whether `ty` holds nothing, so its value is `undefined`: `()`, `PhantomData`, -/// `PhantomPinned`, or `..`, whose impls each compare equal and hash nothing. +/// `PhantomPinned`, `..`, or `BuildHasherDefault`, whose impls each compare equal and hash +/// nothing. pub fn nothing<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind() { ty::Tuple(tys) => tys.is_empty(), ty::Adt(adt, _) => { - adt.is_phantom_data() || pinned(tcx, adt.did()) || Std::of(tcx, adt.did()) == Some(Std::RangeFull) + adt.is_phantom_data() + || pinned(tcx, adt.did()) + || matches!(Std::of(tcx, adt.did()), Some(Std::RangeFull | Std::BuildHasherDefault)) } _ => false, } diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 4c43b200e..0a5670efd 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -619,11 +619,17 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let mut rereadable = Vec::with_capacity(ops.len()); for op in ops.iter() { let v = self.operand(op, span, out); - let is_ref = op.ty(&self.mir.local_decls, self.tcx).is_ref(); - let reread = is_ref && v.is_path() && op.place().is_none_or(|place| self.rereads(place)); - let v = match parts_read && is_ref && !reread { - true => self.path(v, "part", out), - false => v, + let op_ty = op.ty(&self.mir.local_decls, self.tcx); + let is_ref = op_ty.is_ref(); + // A shared reference to what holds nothing is `undefined` wherever it points. + let empty = matches!(op_ty.kind(), ty::Ref(_, inner, rustc_ast::Mutability::Not) + if crate::lower::library::nothing(self.tcx, inner.peel_refs())) + && !self.part_reads(&v).0; + let reread = empty || is_ref && v.is_path() && op.place().is_none_or(|place| self.rereads(place)); + let v = match (empty, parts_read && is_ref && !reread) { + (true, _) => Expr::undefined(), + (false, true) => self.path(v, "part", out), + (false, false) => v, }; values.push(v); rereadable.push(reread); @@ -784,7 +790,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { values.extend(crate::lower::library::range_step(tcx, args.type_at(0), true)); Expr::New(Box::new(Expr::Rt("RangeFrom".into())), values) } - (Std::RangeFull, _) => Expr::undefined(), + (Std::RangeFull | Std::BuildHasherDefault, _) => Expr::undefined(), (Std::LayoutError, _) => Expr::Rt("LAYOUT_ERROR".into()), (Std::ParseIntError, _) => Expr::Object(vec![(Key::Name("kind".into()), values.pop().unwrap())]), (Std::ErrorKind, _) => Expr::As(Box::new(Expr::Str(def.name.to_string())), self.b.t.ty(ty, span)), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 11774d539..50c8b30c8 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1733,7 +1733,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ("inclusive".into(), Type::Builtin("true")), ]), library::Std::RangeFrom => Type::Rt("RangeFrom", vec![self.ty(args.type_at(0), span)]), - library::Std::RangeFull => Type::Builtin("undefined"), + library::Std::RangeFull | library::Std::BuildHasherDefault => Type::Builtin("undefined"), library::Std::Argument => Type::Rt("Fmt", vec![]), library::Std::NonNull => self.pointer_ty(args.type_at(0), span), library::Std::MaybeUninit | library::Std::ManuallyDrop => self.ty(args.type_at(0), span), From 1a699ddd7908c549ba80bf57ed8d833bfa0f261e Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:36:28 -0800 Subject: [PATCH 3/4] fix(ts): read through shared references only to a value that holds nothing Co-Authored-By: Claude Opus 5.5 --- ts/src/lower/mir/rvalue.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 0a5670efd..1197c32e0 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -621,10 +621,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let v = self.operand(op, span, out); let op_ty = op.ty(&self.mir.local_decls, self.tcx); let is_ref = op_ty.is_ref(); - // A shared reference to what holds nothing is `undefined` wherever it points. - let empty = matches!(op_ty.kind(), ty::Ref(_, inner, rustc_ast::Mutability::Not) - if crate::lower::library::nothing(self.tcx, inner.peel_refs())) - && !self.part_reads(&v).0; + // Shared references to what holds nothing are `undefined` wherever they point, unlike + // a `&mut`, which may be an `rt.Place`. + let mut pointee = op_ty; + while let ty::Ref(_, inner, rustc_ast::Mutability::Not) = pointee.kind() { + pointee = *inner; + } + let empty = is_ref && crate::lower::library::nothing(self.tcx, pointee) && !self.part_reads(&v).0; let reread = empty || is_ref && v.is_path() && op.place().is_none_or(|place| self.rereads(place)); let v = match (empty, parts_read && is_ref && !reread) { (true, _) => Expr::undefined(), From c1611cb5378ff7ae754997a5763e17c180ff8e4e Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:41:22 -0800 Subject: [PATCH 4/4] chore(ts): update snapshots that read a shared reference to () in place Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/cells/expected/cells.ts | 6 +++--- ts/fixtures/lang/cells/expected/core/result.ts | 4 ++-- ts/fixtures/lang/results/expected/core/option.ts | 4 ++-- ts/fixtures/lang/results/expected/results.ts | 4 ++-- ts/fixtures/lang/shapes/expected/fmt.ts | 3 +-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ts/fixtures/lang/cells/expected/cells.ts b/ts/fixtures/lang/cells/expected/cells.ts index a49931942..a5861d2b2 100644 --- a/ts/fixtures/lang/cells/expected/cells.ts +++ b/ts/fixtures/lang/cells/expected/cells.ts @@ -4,7 +4,7 @@ import * as rt from "./rt.ts"; import { out$Option_u8, out$u64, out$u8 } from "./lib.ts"; import { thenSome$0d5am30 } from "./core/bool.ts"; import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr } from "./core/option.ts"; -import { err$1nbmqmx, map$1ma2nl9 as result_map$1ma2nl9, map$0l5uclx as result_map$0l5uclx, map$14n87rz as result_map$14n87rz, map$0h1vl84 as result_map$0h1vl84, map$03hz3l7 as result_map$03hz3l7, map$0yqi183 as result_map$0yqi183 } from "./core/result.ts"; +import { err$1nbmqmx, map$1ma2nl9 as result_map$1ma2nl9, map$0l5uclx as result_map$0l5uclx, map$14n87rz as result_map$14n87rz, map$0h1vl84 as result_map$0h1vl84, map$03hz3l7 as result_map$03hz3l7, map$1916nm0 as result_map$1916nm0 } from "./core/result.ts"; /** Steps of an operation and an operand on one cell, each putting what it read. */ export function cellOption(dataData: Uint8Array, dataStart: number, dataLen: number, out: rt.ByteVec): void { @@ -907,7 +907,7 @@ export function cellUnit(dataData: Uint8Array, dataStart: number, dataLen: numbe } else { const _179: rt.OnceCell = cell; result = rt.onceGetOrTryInit(cell, (): rt.Result => { - return made$unit(make, option_unwrapOr$0r2bmpr(err$1nbmqmx(result_map$0yqi183(rt.onceGetOrTryInit(_179, (): rt.Result => { + return made$unit(make, option_unwrapOr$0r2bmpr(err$1nbmqmx(result_map$1916nm0(rt.onceGetOrTryInit(_179, (): rt.Result => { return made$unit(make, v2 ^ 1); }, undefined, rt.None), (_23: undefined): undefined => {})), v2)); }, undefined, rt.None); @@ -2047,7 +2047,7 @@ export function lockUnit(dataData: Uint8Array, dataStart: number, dataLen: numbe } else { const _179: rt.OnceLock = cell; result = rt.onceGetOrTryInit(cell, (): rt.Result => { - return made$unit(make, option_unwrapOr$0r2bmpr(err$1nbmqmx(result_map$0yqi183(rt.onceGetOrTryInit(_179, (): rt.Result => { + return made$unit(make, option_unwrapOr$0r2bmpr(err$1nbmqmx(result_map$1916nm0(rt.onceGetOrTryInit(_179, (): rt.Result => { return made$unit(make, v2 ^ 1); }, undefined, rt.None), (_23: undefined): undefined => {})), v2)); }, undefined, rt.None); diff --git a/ts/fixtures/lang/cells/expected/core/result.ts b/ts/fixtures/lang/cells/expected/core/result.ts index eca3e6b7d..e51dd643c 100644 --- a/ts/fixtures/lang/cells/expected/core/result.ts +++ b/ts/fixtures/lang/cells/expected/core/result.ts @@ -49,9 +49,9 @@ export function map$03hz3l7(self: rt.Result, op: (a0: rt.u8) => un return self as rt.Result; } -export function map$0yqi183(self: rt.Result, op: (a0: undefined) => undefined): rt.Result { +export function map$1916nm0(self: rt.Result, op: (a0: undefined) => undefined): rt.Result { if (self.ok) { - op(self.value); + op(undefined); return rt.OK; } return self; diff --git a/ts/fixtures/lang/results/expected/core/option.ts b/ts/fixtures/lang/results/expected/core/option.ts index abd6afb03..bbab83530 100644 --- a/ts/fixtures/lang/results/expected/core/option.ts +++ b/ts/fixtures/lang/results/expected/core/option.ts @@ -16,11 +16,11 @@ export function unwrapOr$0r2bmpr(self: rt.u8 | undefined, default_: rt.u8): rt.u return self; } -export function mapOr$1w5qtj7(self: undefined | rt.None, default_: rt.u8, f: (a0: undefined) => rt.u8): rt.u8 { +export function mapOr$18ph0j9(self: undefined | rt.None, default_: rt.u8, f: (a0: undefined) => rt.u8): rt.u8 { if (self === rt.None) { return default_; } - return f(self); + return f(undefined); } export function mapOr$02k4ito(self: rt.usize | undefined, default_: rt.u8, f: (a0: rt.usize) => rt.u8): rt.u8 { diff --git a/ts/fixtures/lang/results/expected/results.ts b/ts/fixtures/lang/results/expected/results.ts index 27741a18e..dedcf7309 100644 --- a/ts/fixtures/lang/results/expected/results.ts +++ b/ts/fixtures/lang/results/expected/results.ts @@ -2,7 +2,7 @@ import * as rt from "./rt.ts"; import { copy$Result_array0_u8_u8, copy$Result_array1_u8_u8, copy$Result_array5_u8_u8, drop$Result_results_Logged_results_Logged, out$u8 } from "./lib.ts"; -import { unwrapOr$17ntxfo as option_unwrapOr$17ntxfo, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$1w5qtj7 as option_mapOr$1w5qtj7, mapOr$02k4ito as option_mapOr$02k4ito } from "./core/option.ts"; +import { unwrapOr$17ntxfo as option_unwrapOr$17ntxfo, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$18ph0j9 as option_mapOr$18ph0j9, mapOr$02k4ito as option_mapOr$02k4ito } from "./core/option.ts"; import { map$03hz3l7 as result_map$03hz3l7 } from "./core/result.ts"; import * as Logged from "./results.Logged.ts"; @@ -250,7 +250,7 @@ export function units(data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, rt.iterToOption(it.next()) !== undefined ? 1 : 0); } for (const _36 of results) { - rt.byteVecPush(out, option_mapOr$1w5qtj7(rt.iterToOption(rt.iterResult(_36).next(), rt.None), 160, (_26: undefined): rt.u8 => { + rt.byteVecPush(out, option_mapOr$18ph0j9(rt.iterToOption(rt.iterResult(_36).next(), rt.None), 160, (_26: undefined): rt.u8 => { return 161; })); rt.byteVecPush(out, rt.iterToOption(rt.iterResult(_36).nextBack(), rt.None) !== rt.None ? 1 : 0); diff --git a/ts/fixtures/lang/shapes/expected/fmt.ts b/ts/fixtures/lang/shapes/expected/fmt.ts index 705095988..5b08babd0 100644 --- a/ts/fixtures/lang/shapes/expected/fmt.ts +++ b/ts/fixtures/lang/shapes/expected/fmt.ts @@ -633,9 +633,8 @@ export function wrapped(data: rt.ByteSlice, out: rt.ByteVec): void { const _68: { end: rt.i32; inclusive: true } = { end: b, inclusive: true }; const _70: rt.Range = new rt.Range(a, b); const _74: { end: rt.i32; inclusive: true } = { end: b, inclusive: true }; - const _75: undefined = undefined; const _77: rt.Range = new rt.Range(a, b); - const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtRange(_60, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeInclusive(_62, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeFrom(_64, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeTo(_66, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeToInclusive(_68, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRange(_70, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeInclusive(_72, f, rt.i32Debug), rt.fmtCount(w), (f: rt.Formatter) => rt.fmtRangeToInclusive(_74, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeFull(_75, f), (f: rt.Formatter) => rt.fmtRange(_77, f, rt.i32Debug)]; + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtRange(_60, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeInclusive(_62, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeFrom(_64, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeTo(_66, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeToInclusive(_68, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRange(_70, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeInclusive(_72, f, rt.i32Debug), rt.fmtCount(w), (f: rt.Formatter) => rt.fmtRangeToInclusive(_74, f, rt.i32Debug), (f: rt.Formatter) => rt.fmtRangeFull(undefined, f), (f: rt.Formatter) => rt.fmtRange(_77, f, rt.i32Debug)]; put(out, rt.fmtFormat([rt.fmtSpec({ width: 5 }, args4[0]), "|", rt.fmtSpec({ align: "<", width: 4 }, args4[1]), "|", rt.fmtSpec({ plus: true }, args4[2]), "|", rt.fmtSpec({ zero: true, width: 5 }, args4[3]), "|", rt.fmtSpec({ debugHex: "x" }, args4[4]), "|", rt.fmtSpec({ alternate: true, debugHex: "X" }, args4[5]), "|", rt.fmtSpec({ width: rt.fmtCountOf(args4[7]) }, args4[6]), "|", rt.fmtSpec({ align: "^", width: rt.fmtCountOf(args4[7]) }, args4[8]), "|", rt.fmtSpec({ align: ">", width: 3 }, args4[9]), "|", rt.fmtSpec({ alternate: true }, args4[10])])); const _96: rt.usize = rt.usizeRem(at(3) as rt.usize, ["", "a", "héllo", "日本語", "tab\tnew\nline\r", "quote\"back\\slash'", "\u0000\u0001\u001f", "€Ÿ ­", "é", "‍ᅟ\u2028 ", "͸􏿽𽀀𿱀", "😀 🇦", "゙゚ン", "{}"].length); if (!(_96 < 14)) {