From d8cf91d688042fdd35bdba2dd490a903c3fd93c4 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 13:28:52 -0800 Subject: [PATCH 1/3] chore(quest): claim quest/m3/ts-btree-drop-order.md Quest-Claim: cab9acc7dbc71cf4 From 6637a35b4dc8c729322ca0de0531801dd1e86ad4 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 14:26:31 -0800 Subject: [PATCH 2/3] feat(ts): drop a whole B-tree node by node as std's drop_tree does The runtime's B-tree now holds std's node layout, so a whole map or set, dropped or cleared, drops each node's entries in order after its children's, as std's drop_tree walks it in postorder, rather than in key order. IntoIter still drops in key order, as std's dying_next does. Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-btree-drop-order.md | 16 -- ts/README.md | 9 +- .../expected/collections.Tagged.ts | 37 ++++ .../lang/collections/expected/collections.ts | 207 ++++++++++++++++-- ts/fixtures/lang/collections/expected/lib.ts | 19 +- .../lang/collections/src/collections.rs | 182 ++++++++++++++- ts/fixtures/lang/collections/src/lib.rs | 1 + ts/fixtures/task/expected/drops/orders.ts | 48 ++-- ts/fixtures/task/src/drops/orders.rs | 6 +- ts/runtime/rs/collections.ts | 32 +-- ts/tests/btree.ts | 31 ++- ts/tests/runtime.rs | 69 ++++-- 13 files changed, 540 insertions(+), 118 deletions(-) delete mode 100644 quest/m3/ts-btree-drop-order.md create mode 100644 ts/fixtures/lang/collections/expected/collections.Tagged.ts diff --git a/quest/m3/README.md b/quest/m3/README.md index d4be3829d..39027f9db 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -26,4 +26,3 @@ generic std gap is planned here even when moq never calls it. - [rust2-ts with no ts.toml](/quest/m3/ts-configless.md) - a crate's whole `pub` API translates with no config and no bound items - [Buf on a deque of bytes](/quest/m3/ts-vecdeque-buf.md) - `bytes`' `Buf` for a deque of bytes translates - [std's panic messages hold std's capacity](/quest/m3/ts-panic-payload-capacity.md) - a hook reads native's `String` capacity from std's own formatted panics -- [A B-tree drops as std's does](/quest/m3/ts-btree-drop-order.md) - keys and values drop node by node, in std's order diff --git a/quest/m3/ts-btree-drop-order.md b/quest/m3/ts-btree-drop-order.md deleted file mode 100644 index 4d102937d..000000000 --- a/quest/m3/ts-btree-drop-order.md +++ /dev/null @@ -1,16 +0,0 @@ -# [S] A B-tree drops as std's does - -## Goal - -[rust2-ts](/ts/README.md)'s `BTreeMap` and `BTreeSet` drop their keys and values in -std's order, node by node, proven by differential tests against native Rust that log -each drop. - -## Plan - -The runtime drops in key order, decided with the maintainer on 2026-10-30 for speed -while its tree was not std's. With std's node layout, ported in -[#577](https://github.com/moq-dev/rust2/pull/577), std's order costs little; measure it on -the TS bench's `btree/` workloads. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index 1ad22f88b..539a0c3be 100644 --- a/ts/README.md +++ b/ts/README.md @@ -1023,10 +1023,11 @@ they need. where JS fails to allocate a byte `Vec`'s or `VecDeque`'s `Uint8Array` (decided with the maintainer on 2026-11-03). A `LinkedList` is nodes linked both ways, as std's are, so `append` is O(1) and `split_off` walks from the nearer end; each node is the place - `iter_mut` and `front_mut` lend of an item JS copies. Dropping a whole map or set drops its entries in the - runtime's order, keys' for a B-tree, not std's node by node: std leaves that order - undocumented ([#272](https://github.com/moq-dev/rust2/pull/272)). Maintainer's decision - on 2026-10-30; a map's `IntoIter` still drops in std's order. + `iter_mut` and `front_mut` lend of an item JS copies. Dropping a whole hash map or set drops its entries in the + runtime's order, not hashbrown's: std leaves that order undocumented + ([#272](https://github.com/moq-dev/rust2/pull/272)). Maintainer's decision on 2026-10-30. + A whole B-tree drops as std's `drop_tree` does, node by node in postorder, and its + `IntoIter` in key order, as std's does. - A slice's `sort` and `sort_unstable` are std's driftsort and ipnsort, ported to the runtime with the choices std makes by the element's size on wasm32 and whether it is `Freeze` and `Copy`, so each calls the comparator in std's order and leaves equal diff --git a/ts/fixtures/lang/collections/expected/collections.Tagged.ts b/ts/fixtures/lang/collections/expected/collections.Tagged.ts new file mode 100644 index 000000000..83fbb10cc --- /dev/null +++ b/ts/fixtures/lang/collections/expected/collections.Tagged.ts @@ -0,0 +1,37 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * A B-tree value, or a set's key, that logs its tag as it drops, ordered by it. A clone's + * tag is 1000 past the original's. + */ +export class Tagged { + _0: rs.u16; + _1: rs.RefCell>; + + constructor(_0: rs.u16, _1: rs.RefCell>) { + this._0 = _0; + this._1 = _1; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function drop$ops_Drop(self: Tagged): void { + const _5: rs.RefCell> = rs.refCellBorrowMut(self._1); + _5.value.items.push(self._0); + rs.refMutRelease(_5); +} + +export function clone$Clone(self: Tagged): Tagged { + return new Tagged(rs.u16Add(self._0, 1000), self._1); +} + +export function cmp$cmp_Ord(self: Tagged, other: Tagged): rs.Ordering { + return rs.cmp(self._0, other._0); +} + +//# sourceMappingURL=collections.Tagged.ts.map diff --git a/ts/fixtures/lang/collections/expected/collections.ts b/ts/fixtures/lang/collections/expected/collections.ts index a7e9efab2..866a3c55a 100644 --- a/ts/fixtures/lang/collections/expected/collections.ts +++ b/ts/fixtures/lang/collections/expected/collections.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$Option_collections_Tally, copy$array_collections_Quirky, copy$array_tuple2_u8_u16, copy$array_tuple2_u8_u8, copy$array_u8, copy$collections_Bumped, copy$collections_Quirky, copy$collections_Tally, copy$collections_Wrap_u8, copy$tuple2_u8_collections_Tally, copy$tuple2_u8_u16, copy$tuple2_u8_u8, drop$Option_tuple2_collections_Logged_collections_Logged, drop$ops_Range_collections_Logged, drop$tuple2_collections_Logged_collections_Logged, drop$tuple2_ops_Bound_collections_Logged_ops_Bound_collections_Logged, drop$tuple2_sync_Arc_collections_Picky_u8 } from "./lib.ts"; +import { copy$Option_collections_Tally, copy$array_collections_Quirky, copy$array_tuple2_u8_u16, copy$array_tuple2_u8_u8, copy$array_u8, copy$collections_Bumped, copy$collections_Quirky, copy$collections_Tally, copy$collections_Wrap_u8, copy$tuple2_u8_collections_Tally, copy$tuple2_u8_u16, copy$tuple2_u8_u8, drop$Option_tuple2_collections_Logged_collections_Logged, drop$Option_tuple2_u16_collections_Tagged, drop$collections_BTreeMap_u16_collections_Tagged, drop$ops_Range_collections_Logged, drop$tuple2_collections_Logged_collections_Logged, drop$tuple2_ops_Bound_collections_Logged_ops_Bound_collections_Logged, drop$tuple2_sync_Arc_collections_Picky_u8 } from "./lib.ts"; import type * as Boxed from "./collections.Boxed.ts"; import * as Bumped from "./collections.Bumped.ts"; import * as ByRef from "./collections.ByRef.ts"; @@ -22,6 +22,7 @@ import * as Probe from "./collections.Probe.ts"; import * as Quirky from "./collections.Quirky.ts"; import * as Rank from "./collections.Rank.ts"; import * as Right from "./collections.Right.ts"; +import * as Tagged from "./collections.Tagged.ts"; import * as Tally from "./collections.Tally.ts"; import * as Window from "./collections.Window.ts"; import * as Wrap from "./collections.Wrap.ts"; @@ -5925,36 +5926,34 @@ export function btreeExtracts(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): rs.byteVecPush(out, _302 === undefined ? 1 : 0); drop$Option_tuple2_collections_Logged_collections_Logged(_302); rs.drop(it4); - rs.iterForEach(rs.btreeIter(map2, rs.drop, rs.drop), (a0: [Logged.Logged, Logged.Logged]): undefined => { - drop$tuple2_collections_Logged_collections_Logged(a0); - }, true); + rs.btreeDrop(map2, rs.drop, rs.drop); const bytes: rs.BTreeMap = rs.btreeNew(rs.cmp) as rs.BTreeMap; const window: (a0: rs.u16, a1: rs.u16) => Window.Window = (lo2: rs.u16, hi2: rs.u16): Window.Window => { return { lo: lo2, hi: hi2, log }; }; - const _315: Window.Window = window(0, 9); - rs.iterForEach(rs.btreeExtractIfMut(bytes, rs.boundsOf(_315, Window.startBound$ops_RangeBounds_u16, Window.endBound$ops_RangeBounds_u16), (_26: rs.u16, _310: rs.Place): boolean => { + const _314: Window.Window = window(0, 9); + rs.iterForEach(rs.btreeExtractIfMut(bytes, rs.boundsOf(_314, Window.startBound$ops_RangeBounds_u16, Window.endBound$ops_RangeBounds_u16), (_26: rs.u16, _310: rs.Place): boolean => { return true; - }), (_a02: [rs.u16, rs.u8]): undefined => { + }), (_a0: [rs.u16, rs.u8]): undefined => { undefined; }); rs.btreeInsert(bytes, 1, 1); rs.btreeRemove(bytes, 1); - const _326: Window.Window = window(0, 9); - rs.iterForEach(rs.btreeExtractIfMut(bytes, rs.boundsOf(_326, Window.startBound$ops_RangeBounds_u16, Window.endBound$ops_RangeBounds_u16), (_27: rs.u16, _311: rs.Place): boolean => { + const _325: Window.Window = window(0, 9); + rs.iterForEach(rs.btreeExtractIfMut(bytes, rs.boundsOf(_325, Window.startBound$ops_RangeBounds_u16, Window.endBound$ops_RangeBounds_u16), (_27: rs.u16, _311: rs.Place): boolean => { return true; - }), (_a03: [rs.u16, rs.u8]): undefined => { + }), (_a02: [rs.u16, rs.u8]): undefined => { undefined; }); - const _331: rs.Iter<[rs.u16, rs.u8]> = rs.iterMap(new rs.Range(0, n), (k3: rs.u16): [rs.u16, rs.u8] => { + const _330: rs.Iter<[rs.u16, rs.u8]> = rs.iterMap(new rs.Range(0, n), (k3: rs.u16): [rs.u16, rs.u8] => { return [k3, k3 & 0xff] as [rs.u16, rs.u8]; }); - rs.btreeExtend(bytes, _331); - const _336: Window.Window = window(rs.u16Div(lo, 2), rs.u16Div(hi, 2)); - const _343: (a0: rs.u16, a1: rs.Place) => boolean = (k4: rs.u16, _314: rs.Place): boolean => { + rs.btreeExtend(bytes, _330); + const _335: Window.Window = window(rs.u16Div(lo, 2), rs.u16Div(hi, 2)); + const _342: (a0: rs.u16, a1: rs.Place) => boolean = (k4: rs.u16, _315: rs.Place): boolean => { return rs.u16Rem(k4, every) === 0; }; - rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(rs.iterToVec(rs.btreeExtractIfMut(bytes, rs.boundsOf(_336, Window.startBound$ops_RangeBounds_u16, Window.endBound$ops_RangeBounds_u16), _343), "next", 4).items), (_210: [rs.u16, rs.u8]): rs.u8 => { + rs.iterExtendBytes(out, rs.iterMap(rs.iterSlice(rs.iterToVec(rs.btreeExtractIfMut(bytes, rs.boundsOf(_335, Window.startBound$ops_RangeBounds_u16, Window.endBound$ops_RangeBounds_u16), _342), "next", 4).items), (_210: [rs.u16, rs.u8]): rs.u8 => { return _210[1]; }), undefined, true); rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(rs.refCellReplace(log, new rs.Vec()).items), (e: rs.u16): Uint8Array => { @@ -6261,4 +6260,182 @@ export function btreeSearches(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): } } +/** + * Whole B-trees whose entries log as they drop, which std drops node by node, each node's + * entries in order after its children's: maps built by random, ascending, and descending + * inserts and by `collect`, emptied in part by removals that merge nodes, cleared, assigned + * over, retained, split, cloned, and nested, sets, and `IntoIter`, which drops what it still + * holds in key order. The first byte sets how many entries, up to 297 so trees reach height + * 2, and the rest seed the keys. Each drop is logged, with a mark between the steps. + */ +export function btreeDrops(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): void { + let _201!: rs.u16; + const log: rs.RefCell> = new rs.RefCell(new rs.Vec()); + const tag: (a0: rs.u16) => Tagged.Tagged = (k4: rs.u16): Tagged.Tagged => { + return new Tagged.Tagged(k4, log); + }; + const mark: (a0: rs.u16) => undefined = (m: rs.u16): undefined => { + const _55: rs.RefCell> = rs.refCellBorrowMut(log); + _55.value.items.push(61440 | m); + rs.refMutRelease(_55); + }; + const n: rs.u16 = rs.u16Div(rs.u16Mul(option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16, 7), 6); + let state: rs.u32 = (rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), 2654435769, (s: rs.u32, _32: rs.u8): rs.u32 => { + return (rs.intRotateLeft(s, 5, 32, false) ^ _32 as rs.u32) >>> 0; + }) | 1) >>> 0; + const key: () => rs.u16 = (): rs.u16 => { + state = (state ^ ((state << 13) >>> 0)) >>> 0; + state = (state ^ (state >>> 17)) >>> 0; + state = (state ^ ((state << 5) >>> 0)) >>> 0; + return rs.u32Rem(state, 400) & 0xffff; + }; + const random: (a0: rs.u16) => rs.BTreeMap = (count: rs.u16): rs.BTreeMap => { + const map8: rs.BTreeMap = rs.btreeNew(rs.cmp) as rs.BTreeMap; + let iterStart9: rs.u16 = 0; + const iterEnd9: rs.u16 = count; + while (true) { + if (!(iterStart9++ < iterEnd9)) { + return map8; + } + const k5: rs.u16 = key(); + const _133: Tagged.Tagged = tag(k5); + rs.drop(rs.btreeInsert(map8, k5, _133)); + } + }; + const _26: rs.u8 = rs.u8Rem(kind, 10); + if (_26 === 0) { + const map: rs.BTreeMap = random(n); + mark(0); + rs.btreeDrop(map, undefined, rs.drop); + } else if (_26 === 1) { + rs.btreeDrop(rs.iterFold(new rs.Range(0, n), rs.btreeNew(rs.cmp) as rs.BTreeMap, (map9: rs.BTreeMap, k8: rs.u16): rs.BTreeMap => { + const _65: Tagged.Tagged = tag(k8); + rs.drop(rs.btreeInsert(map9, k8, _65)); + return map9; + }), undefined, rs.drop); + mark(0); + rs.btreeDrop(rs.iterFold(rs.iterRev(new rs.Range(0, n)), rs.btreeNew(rs.cmp) as rs.BTreeMap, (map10: rs.BTreeMap, k9: rs.u16): rs.BTreeMap => { + const _66: Tagged.Tagged = tag(k9); + rs.drop(rs.btreeInsert(map10, k9, _66)); + return map10; + }), undefined, rs.drop); + } else if (_26 === 2) { + const _57: rs.Iter<[rs.u16, Tagged.Tagged]> = rs.iterMap(rs.vecIntoIter(rs.iterToVec(rs.iterMap(new rs.Range(0, n), (_22: rs.u16): rs.u16 => { + return key(); + }), undefined, 2, true)), (k7: rs.u16): [rs.u16, Tagged.Tagged] => { + return [k7, tag(k7)] as [rs.u16, Tagged.Tagged]; + }); + const map2: rs.BTreeMap = rs.btreeCollect(rs.btreeNew(rs.cmp) as rs.BTreeMap, rs.iterToVec(_57, undefined, 12, true).items, 12, true, undefined, undefined, rs.drop); + mark(0); + rs.btreeDrop(map2, undefined, rs.drop); + } else if (_26 === 3) { + const map3: rs.BTreeMap = random(n); + let iterStart: rs.u16 = 0; + const iterEnd: rs.u16 = n; + while (iterStart++ < iterEnd) { + const k: rs.u16 = key(); + if (rs.u16Rem(k, 3) !== 0) { + rs.drop(rs.btreeRemove(map3, k)); + } + } + const _85: rs.u16 = rs.u16Div(n, 8); + let iterStart2: rs.u16 = 0; + const iterEnd2: rs.u16 = _85; + while (iterStart2++ < iterEnd2) { + drop$Option_tuple2_u16_collections_Tagged(rs.btreePop(map3)); + drop$Option_tuple2_u16_collections_Tagged(rs.btreePop(map3, true)); + } + mark(0); + rs.btreeDrop(map3, undefined, rs.drop); + } else if (_26 === 4) { + const set: rs.BTreeSet = rs.btreeNew(Tagged.cmp$cmp_Ord) as rs.BTreeSet; + let iterStart3: rs.u16 = 0; + const iterEnd3: rs.u16 = n; + while (iterStart3++ < iterEnd3) { + const part2 = key(); + const _111: Tagged.Tagged = tag(part2); + rs.btreeAdd(set, _111, rs.drop); + } + const _118: rs.u16 = rs.u16Div(n, 2); + let iterStart4: rs.u16 = 0; + const iterEnd4: rs.u16 = _118; + while (iterStart4++ < iterEnd4) { + const part3 = key(); + const _127: Tagged.Tagged = tag(part3); + rs.btreeDelete(set, _127, rs.drop); + _127[Symbol.dispose](); + } + mark(0); + rs.btreeDrop(set, rs.drop); + } else if (_26 === 5) { + const map4: rs.BTreeMap> = rs.btreeNew(rs.cmp) as rs.BTreeMap>; + let iterStart5: rs.u16 = 0; + const iterEnd5: rs.u16 = n; + while (iterStart5++ < iterEnd5) { + const _146: [rs.u16, rs.u16] = [rs.u16Rem(key(), 24), key()]; + const k2: rs.u16 = _146[0]; + const j: rs.u16 = _146[1]; + rs.drop(rs.btreeInsert(rs.entryOrInsertWith(rs.entry(map4, k2), (): rs.BTreeMap => { + return rs.btreeNew(rs.cmp) as rs.BTreeMap; + }), j, tag(j))); + } + mark(0); + rs.btreeDrop(map4, undefined, drop$collections_BTreeMap_u16_collections_Tagged); + } else if (_26 === 6) { + const iter6: rs.Iter<[rs.u16, Tagged.Tagged]> = rs.btreeIter(random(n), undefined, rs.drop); + let iterStart6: rs.usize = 0; + const iterEnd6: rs.usize = data.length; + while (iterStart6++ < iterEnd6) { + drop$Option_tuple2_u16_collections_Tagged(rs.iterToOption(iter6.next())); + drop$Option_tuple2_u16_collections_Tagged(rs.iterToOption(iter6.nextBack())); + } + mark(0); + rs.drop(iter6); + } else if (_26 === 7) { + let map5: rs.BTreeMap = random(n); + mark(0); + rs.btreeClear(map5, undefined, rs.drop); + mark(1); + let iterStart7: rs.u16 = 0; + const iterEnd7: rs.u16 = n; + while ((_201 = iterStart7++) < iterEnd7) { + const k3: rs.u16 = _201; + rs.drop(rs.btreeInsert(map5, k3, tag(k3))); + } + const _210: rs.BTreeMap = rs.btreeNew(rs.cmp) as rs.BTreeMap; + rs.btreeDrop(map5, undefined, rs.drop); + map5 = _210; + const part = rs.btreeLen(map5) & 0xffff; + mark(part); + rs.btreeDrop(map5, undefined, rs.drop); + } else if (_26 === 8) { + const map6: rs.BTreeMap = random(n); + rs.btreeRetain(map6, (k6: rs.u16, _35: Tagged.Tagged): boolean => { + return rs.u16Rem(k6, 5) !== 0; + }, undefined, rs.drop); + mark(0); + const high: rs.BTreeMap = rs.btreeSplitOff(map6, 200); + rs.btreeDrop(map6, undefined, rs.drop); + mark(1); + rs.btreeDrop(high, undefined, rs.drop); + } else { + const map7: rs.BTreeMap = random(n); + const _239: rs.u16 = rs.u16Div(n, 2); + let iterStart8: rs.u16 = 0; + const iterEnd8: rs.u16 = _239; + while (iterStart8++ < iterEnd8) { + const _249: rs.u16 = key(); + rs.drop(rs.btreeRemove(map7, _249)); + } + mark(0); + const copy: rs.BTreeMap = rs.btreeClone(map7, undefined, Tagged.clone$Clone); + rs.btreeDrop(map7, undefined, rs.drop); + mark(1); + rs.btreeDrop(copy, undefined, rs.drop); + } + rs.iterExtendBytes(out, rs.iterFlatMap(rs.iterSlice(rs.refCellReplace(log, new rs.Vec()).items), (e: rs.u16): Uint8Array => { + return rs.u16ToBeBytes(e); + }, (value: Uint8Array) => rs.iterSlice(value), true, 2), undefined, true); +} + //# sourceMappingURL=collections.ts.map diff --git a/ts/fixtures/lang/collections/expected/lib.ts b/ts/fixtures/lang/collections/expected/lib.ts index 135eb357a..a81acfca8 100644 --- a/ts/fixtures/lang/collections/expected/lib.ts +++ b/ts/fixtures/lang/collections/expected/lib.ts @@ -1,11 +1,12 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { hashMaps, sets, btreeMaps, deques, dequeRanges, vecs, stores, optionals, setOps, unlikeEqs, ownNes, cellEqs, unlikeCompares, refOrders, extracts, btreeExtracts, btreeSearches } from "./collections.ts"; +import { hashMaps, sets, btreeMaps, deques, dequeRanges, vecs, stores, optionals, setOps, unlikeEqs, ownNes, cellEqs, unlikeCompares, refOrders, extracts, btreeExtracts, btreeSearches, btreeDrops } from "./collections.ts"; import type * as Bumped from "./collections.Bumped.ts"; import type * as Logged from "./collections.Logged.ts"; import type * as Picky from "./collections.Picky.ts"; import type * as Quirky from "./collections.Quirky.ts"; +import type * as Tagged from "./collections.Tagged.ts"; import type * as Tally from "./collections.Tally.ts"; import * as Wrap from "./collections.Wrap.ts"; @@ -63,6 +64,16 @@ export function drop$Option_tuple2_collections_Logged_collections_Logged(_1: [Lo } } +export function drop$Option_tuple2_u16_collections_Tagged(_1: [rs.u16, Tagged.Tagged] | undefined): void { + if (_1 !== undefined) { + drop$tuple2_u16_collections_Tagged(_1); + } +} + +export function drop$collections_BTreeMap_u16_collections_Tagged(value: rs.BTreeMap): void { + rs.btreeDrop(value, undefined, rs.drop); +} + export function drop$ops_Bound_collections_Logged(_1: rs.RangeBound): void { if (_1.tag === "Included") { (_1 as { tag: "Included"; _0: Logged.Logged })._0[Symbol.dispose](); @@ -89,6 +100,10 @@ export function drop$tuple2_sync_Arc_collections_Picky_u8(_1: [rs.Arc undefined], ["collections::stores", (_212: rs.u8, data3: rs.ByteSlice, out3: rs.ByteVec): undefined => { stores(data3, out3); - }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::optionals", optionals as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::set_ops", setOps as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::unlike_eqs", unlikeEqs as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::own_nes", ownNes as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::cell_eqs", cellEqs as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::unlike_compares", unlikeCompares as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::ref_orders", refOrders as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::deque_ranges", dequeRanges as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::extracts", extracts as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::btree_extracts", btreeExtracts as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::btree_searches", btreeSearches as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::optionals", optionals as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::set_ops", setOps as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::unlike_eqs", unlikeEqs as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::own_nes", ownNes as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::cell_eqs", cellEqs as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::unlike_compares", unlikeCompares as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::ref_orders", refOrders as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::deque_ranges", dequeRanges as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::extracts", extracts as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::btree_extracts", btreeExtracts as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::btree_searches", btreeSearches as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["collections::btree_drops", btreeDrops as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } //# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/collections/src/collections.rs b/ts/fixtures/lang/collections/src/collections.rs index 113a87279..ed2bc690c 100644 --- a/ts/fixtures/lang/collections/src/collections.rs +++ b/ts/fixtures/lang/collections/src/collections.rs @@ -2342,8 +2342,7 @@ pub fn btree_extracts(kind: u8, data: &[u8], out: &mut Vec) { let mut it = map.extract_if(Logged(0xa00 | lo / 4, &log)..Logged(0xa00 | hi / 4, &log), |_, _| false); out.push(it.next().is_none() as u8); drop(it); - // In key order, which std's drop of a whole tree does not keep, node by node. - map.into_iter().for_each(drop); + drop(map); // A range of its own reads its start once, of a map with a root, and its end at each key. let mut bytes: BTreeMap = BTreeMap::new(); let window = |lo, hi| Window { lo, hi, log: &log }; @@ -2552,3 +2551,182 @@ pub fn btree_searches(kind: u8, data: &[u8], out: &mut Vec) { } } } + +/// A B-tree value, or a set's key, that logs its tag as it drops, ordered by it. A clone's +/// tag is 1000 past the original's. +struct Tagged<'a>(u16, &'a std::cell::RefCell>); + +impl Drop for Tagged<'_> { + fn drop(&mut self) { + self.1.borrow_mut().push(self.0); + } +} + +impl Clone for Tagged<'_> { + fn clone(&self) -> Self { + Tagged(self.0 + 1000, self.1) + } +} + +impl PartialEq for Tagged<'_> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for Tagged<'_> {} + +impl PartialOrd for Tagged<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Tagged<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +/// Whole B-trees whose entries log as they drop, which std drops node by node, each node's +/// entries in order after its children's: maps built by random, ascending, and descending +/// inserts and by `collect`, emptied in part by removals that merge nodes, cleared, assigned +/// over, retained, split, cloned, and nested, sets, and `IntoIter`, which drops what it still +/// holds in key order. The first byte sets how many entries, up to 297 so trees reach height +/// 2, and the rest seed the keys. Each drop is logged, with a mark between the steps. +pub fn btree_drops(kind: u8, data: &[u8], out: &mut Vec) { + let log = std::cell::RefCell::new(Vec::new()); + let tag = |k: u16| Tagged(k, &log); + let mark = |m: u16| log.borrow_mut().push(0xf000 | m); + let n = u16::from(data.first().copied().unwrap_or(0)) * 7 / 6; + // xorshift32, seeded by the input, so a short input scripts a tree of hundreds of entries. + let mut state = data + .iter() + .fold(0x9e37_79b9u32, |s, &b| s.rotate_left(5) ^ u32::from(b)) + | 1; + // Keys apart enough that a random script repeats some. + let mut key = || { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + (state % 400) as u16 + }; + let mut random = |count: u16| { + let mut map = BTreeMap::new(); + for _ in 0..count { + let k = key(); + map.insert(k, tag(k)); + } + map + }; + match kind % 10 { + // By random inserts, whose replaced values drop as they go. + 0 => { + let map = random(n); + mark(0); + drop(map); + } + // In ascending order, then descending, which leave nodes of other sizes. + 1 => { + drop((0..n).fold(BTreeMap::new(), |mut map, k| { + map.insert(k, tag(k)); + map + })); + mark(0); + drop((0..n).rev().fold(BTreeMap::new(), |mut map, k| { + map.insert(k, tag(k)); + map + })); + } + // By `collect`, std's bulk build from sorted entries, whose repeats drop as it builds. + 2 => { + let keys: Vec = (0..n).map(|_| key()).collect(); + let map: BTreeMap = keys.into_iter().map(|k| (k, tag(k))).collect(); + mark(0); + drop(map); + } + // Removals after random inserts, which steal from and merge nodes, then pops. + 3 => { + let mut map = random(n); + for _ in 0..n { + let k = key(); + if k % 3 != 0 { + drop(map.remove(&k)); + } + } + for _ in 0..n / 8 { + drop(map.pop_first()); + drop(map.pop_last()); + } + mark(0); + drop(map); + } + // A set, by inserts and removals. + 4 => { + let mut set = BTreeSet::new(); + for _ in 0..n { + set.insert(tag(key())); + } + for _ in 0..n / 2 { + set.remove(&tag(key())); + } + mark(0); + drop(set); + } + // Maps of maps, each of which drops its own nodes as its outer entry drops. + 5 => { + let mut map: BTreeMap> = BTreeMap::new(); + for _ in 0..n { + let (k, j) = (key() % 24, key()); + map.entry(k).or_default().insert(j, tag(j)); + } + mark(0); + drop(map); + } + // `IntoIter`, read from both ends, drops what it still holds in key order. + 6 => { + let mut iter = random(n).into_iter(); + for _ in 0..data.len() { + drop(iter.next()); + drop(iter.next_back()); + } + mark(0); + drop(iter); + } + // `clear`, and a map assigned over. + 7 => { + let mut map = random(n); + mark(0); + map.clear(); + mark(1); + for k in 0..n { + map.insert(k, tag(k)); + } + map = BTreeMap::new(); + mark(map.len() as u16); + } + // `retain`, and a map split in two, each of which drops as its nodes are. + 8 => { + let mut map = random(n); + map.retain(|k, _| k % 5 != 0); + mark(0); + let high = map.split_off(&200); + drop(map); + mark(1); + drop(high); + } + // A clone, which std builds node for node as the original. + _ => { + let mut map = random(n); + for _ in 0..n / 2 { + drop(map.remove(&key())); + } + mark(0); + let copy = map.clone(); + drop(map); + mark(1); + drop(copy); + } + } + out.extend(log.take().iter().flat_map(|e| e.to_be_bytes())); +} diff --git a/ts/fixtures/lang/collections/src/lib.rs b/ts/fixtures/lang/collections/src/lib.rs index 9b0d32573..f1efe365f 100644 --- a/ts/fixtures/lang/collections/src/lib.rs +++ b/ts/fixtures/lang/collections/src/lib.rs @@ -33,5 +33,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("collections::extracts", collections::extracts), ("collections::btree_extracts", collections::btree_extracts), ("collections::btree_searches", collections::btree_searches), + ("collections::btree_drops", collections::btree_drops), ] } diff --git a/ts/fixtures/task/expected/drops/orders.ts b/ts/fixtures/task/expected/drops/orders.ts index 8541f6208..126e26a21 100644 --- a/ts/fixtures/task/expected/drops/orders.ts +++ b/ts/fixtures/task/expected/drops/orders.ts @@ -86,27 +86,23 @@ export function orders(log: Log.Log, id: rs.u32, arg: rs.u8): void { const set3: rs.BTreeSet = rs.btreeCollectKeys(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeSet, rs.iterToVec(_161, "next", 12, true, 12).items, 12, true, rs.cmpLt(Ranked.partialCmp$cmp_PartialOrd), Ranked.eq$cmp_PartialEq, rs.drop); const part18 = setIds(set3); Log.note(log, rs.fmtFormat(["set ", (f: rs.Formatter) => rs.fmtPad(f, part18)])); - rs.iterForEach(rs.btreeKeys(set3, rs.drop), (a0: Ranked.Ranked): undefined => { - a0[Symbol.dispose](); - }, true); + rs.btreeDrop(set3, rs.drop); const part19 = rs.byteVecSlice(long); - const _184: rs.Iter<[Ranked.Ranked, Guard.Guard]> = rs.vecIntoIter(entries(part19), lib_drop$tuple2_drops_orders_Ranked_model_Guard); - const map3: rs.BTreeMap = rs.btreeCollect(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeMap, rs.iterToVec(_184, "next", 20, true, 20).items, 20, true, Ranked.eq$cmp_PartialEq, rs.drop, rs.drop); + const _183: rs.Iter<[Ranked.Ranked, Guard.Guard]> = rs.vecIntoIter(entries(part19), lib_drop$tuple2_drops_orders_Ranked_model_Guard); + const map3: rs.BTreeMap = rs.btreeCollect(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeMap, rs.iterToVec(_183, "next", 20, true, 20).items, 20, true, Ranked.eq$cmp_PartialEq, rs.drop, rs.drop); const part21 = mapIds(map3); Log.note(log, rs.fmtFormat(["map ", (f: rs.Formatter) => rs.fmtPad(f, part21)])); - rs.iterForEach(rs.btreeIter(map3, rs.drop, rs.drop), (a02: [Ranked.Ranked, Guard.Guard]): undefined => { - lib_drop$tuple2_drops_orders_Ranked_model_Guard(a02); - }, true); + rs.btreeDrop(map3, rs.drop, rs.drop); } else if (_21 === 4) { - const _207: rs.Iter<[rs.Cell, Guard.Guard]> = rs.iterMap(rs.iterZip(rs.iterSlice(rs.byteSliceView(rs.byteSliceFrom(Uint8Array.of(5, 1, 9, 2, 6, 3)))), new rs.RangeFrom(1, rs.u32Step), "len"), (_217: [rs.u8, rs.u32]): [rs.Cell, Guard.Guard] => { - const r: rs.u8 = _217[0]; - const n9: rs.u32 = _217[1]; + const _205: rs.Iter<[rs.Cell, Guard.Guard]> = rs.iterMap(rs.iterZip(rs.iterSlice(rs.byteSliceView(rs.byteSliceFrom(Uint8Array.of(5, 1, 9, 2, 6, 3)))), new rs.RangeFrom(1, rs.u32Step), "len"), (_215: [rs.u8, rs.u32]): [rs.Cell, Guard.Guard] => { + const r: rs.u8 = _215[0]; + const n9: rs.u32 = _215[1]; return [new rs.Cell({ _0: r }), Guard.new(rs.u32Add(id, n9), Log.clone$Clone(log))] as [rs.Cell, Guard.Guard]; }); - const map4: rs.BTreeMap, Guard.Guard> = rs.btreeCollect(rs.btreeNew(rs.cellBy(Rank.cmp$cmp_Ord)) as rs.BTreeMap, Guard.Guard>, rs.iterToVec(_207, undefined, 12, true).items, 12, false, rs.cellBy(Rank.eq$cmp_PartialEq), undefined, rs.drop); - const part15 = rs.strsJoin(rs.iterToVec(rs.iterMap(rs.btreeIter(map4), (_219: [rs.Cell, Guard.Guard]): string => { - const _913: Rank.Rank = _219[0].v; - const _1111: rs.u32 = rs.u32Sub(Guard.id(_219[1]), id); + const map4: rs.BTreeMap, Guard.Guard> = rs.btreeCollect(rs.btreeNew(rs.cellBy(Rank.cmp$cmp_Ord)) as rs.BTreeMap, Guard.Guard>, rs.iterToVec(_205, undefined, 12, true).items, 12, false, rs.cellBy(Rank.eq$cmp_PartialEq), undefined, rs.drop); + const part15 = rs.strsJoin(rs.iterToVec(rs.iterMap(rs.btreeIter(map4), (_217: [rs.Cell, Guard.Guard]): string => { + const _913: Rank.Rank = _217[0].v; + const _1111: rs.u32 = rs.u32Sub(Guard.id(_217[1]), id); const arg2 = _913._0; const args46 = [(f: rs.Formatter) => rs.u8Display(arg2, f), (f: rs.Formatter) => rs.u32Display(_1111, f)]; return rs.fmtFormat([args46[0], ":", args46[1]]); @@ -138,42 +134,42 @@ export function orders(log: Log.Log, id: rs.u32, arg: rs.u8): void { Log.note(log, rs.fmtFormat(["min ", (f: rs.Formatter) => rs.fmtOption(part14, f, rs.u32Debug)])); const one2: Guard.Guard | undefined = rs.iterMaxByKey(rs.vecIntoIter(guards(11, 12), rs.drop), by, rs.cmpLt(Ranked.partialCmp$cmp_PartialOrd), true, rs.drop, undefined, rs.drop); const none2: Guard.Guard | undefined = rs.iterMinByKey(rs.vecIntoIter(guards(12, 12), rs.drop), by, rs.cmpLt(Ranked.partialCmp$cmp_PartialOrd), true, rs.drop, undefined, rs.drop); - const _292: rs.u32 | undefined = option_map$06w9waf(one2, (g4: Guard.Guard): rs.u32 => { + const _290: rs.u32 | undefined = option_map$06w9waf(one2, (g4: Guard.Guard): rs.u32 => { const _020: rs.u32 = rs.u32Sub(Guard.id(g4), id); g4[Symbol.dispose](); return _020; }); - const _296: boolean = none2 === undefined; - const args43 = [(f: rs.Formatter) => rs.fmtOption(_292, f, rs.u32Debug), (f: rs.Formatter) => rs.fmtBool(_296, f)]; + const _294: boolean = none2 === undefined; + const args43 = [(f: rs.Formatter) => rs.fmtOption(_290, f, rs.u32Debug), (f: rs.Formatter) => rs.fmtBool(_294, f)]; Log.note(log, rs.fmtFormat(["one ", args43[0], " ", args43[1]])); rs.drop(none2); } else if (_21 === 6) { const part5 = rs.byteSliceFrom(short); - const _304: rs.Iter<[Ranked.Ranked, Guard.Guard]> = rs.vecIntoIter(entries(part5), lib_drop$tuple2_drops_orders_Ranked_model_Guard); - const part6 = option_map$16oph0b(rs.ownedEnd(rs.btreeKeys(rs.btreeCollect(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeMap, rs.iterToVec(_304, "next", 20, true, 20).items, 20, true, Ranked.eq$cmp_PartialEq, rs.drop, rs.drop), rs.drop, rs.drop, true), true), (k2: Ranked.Ranked): rs.u32 => { + const _302: rs.Iter<[Ranked.Ranked, Guard.Guard]> = rs.vecIntoIter(entries(part5), lib_drop$tuple2_drops_orders_Ranked_model_Guard); + const part6 = option_map$16oph0b(rs.ownedEnd(rs.btreeKeys(rs.btreeCollect(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeMap, rs.iterToVec(_302, "next", 20, true, 20).items, 20, true, Ranked.eq$cmp_PartialEq, rs.drop, rs.drop), rs.drop, rs.drop, true), true), (k2: Ranked.Ranked): rs.u32 => { const _012: rs.u32 = rs.u32Sub(k2.id, id); k2[Symbol.dispose](); return _012; }); Log.note(log, rs.fmtFormat(["max ", (f: rs.Formatter) => rs.fmtOption(part6, f, rs.u32Debug)])); const part7 = rs.byteSliceFrom(short); - const _326: rs.Iter<[Ranked.Ranked, Guard.Guard]> = rs.vecIntoIter(entries(part7), lib_drop$tuple2_drops_orders_Ranked_model_Guard); - const part8 = option_map$16oph0b(rs.ownedEnd(rs.btreeKeys(rs.btreeCollect(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeMap, rs.iterToVec(_326, "next", 20, true, 20).items, 20, true, Ranked.eq$cmp_PartialEq, rs.drop, rs.drop), rs.drop, rs.drop, true), false), (k3: Ranked.Ranked): rs.u32 => { + const _324: rs.Iter<[Ranked.Ranked, Guard.Guard]> = rs.vecIntoIter(entries(part7), lib_drop$tuple2_drops_orders_Ranked_model_Guard); + const part8 = option_map$16oph0b(rs.ownedEnd(rs.btreeKeys(rs.btreeCollect(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeMap, rs.iterToVec(_324, "next", 20, true, 20).items, 20, true, Ranked.eq$cmp_PartialEq, rs.drop, rs.drop), rs.drop, rs.drop, true), false), (k3: Ranked.Ranked): rs.u32 => { const _013: rs.u32 = rs.u32Sub(k3.id, id); k3[Symbol.dispose](); return _013; }); Log.note(log, rs.fmtFormat(["min ", (f: rs.Formatter) => rs.fmtOption(part8, f, rs.u32Debug)])); - const _348: rs.Iter<[rs.u32, Guard.Guard]> = rs.iterMap(new rs.Range(1, 5), (n6: rs.u32): [rs.u32, Guard.Guard] => { + const _346: rs.Iter<[rs.u32, Guard.Guard]> = rs.iterMap(new rs.Range(1, 5), (n6: rs.u32): [rs.u32, Guard.Guard] => { return [n6, Guard.new(rs.u32Add(rs.u32Add(id, n6), 200), Log.clone$Clone(log))] as [rs.u32, Guard.Guard]; }); - const part9 = rs.ownedEnd(rs.btreeKeys(rs.btreeCollect(rs.btreeNew(rs.cmp) as rs.BTreeMap, rs.iterToVec(_348, undefined, 12, true).items, 12, true, undefined, undefined, rs.drop), undefined, rs.drop, true), true); + const part9 = rs.ownedEnd(rs.btreeKeys(rs.btreeCollect(rs.btreeNew(rs.cmp) as rs.BTreeMap, rs.iterToVec(_346, undefined, 12, true).items, 12, true, undefined, undefined, rs.drop), undefined, rs.drop, true), true); Log.note(log, rs.fmtFormat(["plain ", (f: rs.Formatter) => rs.fmtOption(part9, f, rs.u32Debug)])); const part10 = rs.ownedEnd(rs.btreeKeys(rs.btreeNew(rs.cmp) as rs.BTreeMap, undefined, rs.drop, true), false); Log.note(log, rs.fmtFormat(["empty ", (f: rs.Formatter) => rs.fmtOption(part10, f, rs.u32Debug)])); } else { - const _378: rs.Iter = rs.vecIntoIter(keys(rs.byteSliceFrom(Uint8Array.of(2, 1, 99, 3))), rs.drop); - rs.btreeDrop(rs.btreeCollectKeys(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeSet, rs.iterToVec(_378, "next", 12, true, 12).items, 12, true, rs.cmpLt(Ranked.partialCmp$cmp_PartialOrd), Ranked.eq$cmp_PartialEq, rs.drop), rs.drop); + const _376: rs.Iter = rs.vecIntoIter(keys(rs.byteSliceFrom(Uint8Array.of(2, 1, 99, 3))), rs.drop); + rs.btreeDrop(rs.btreeCollectKeys(rs.btreeNew(Ranked.cmp$cmp_Ord) as rs.BTreeSet, rs.iterToVec(_376, "next", 12, true, 12).items, 12, true, rs.cmpLt(Ranked.partialCmp$cmp_PartialOrd), Ranked.eq$cmp_PartialEq, rs.drop), rs.drop); } } diff --git a/ts/fixtures/task/src/drops/orders.rs b/ts/fixtures/task/src/drops/orders.rs index 28b1704c8..dc5706dcd 100644 --- a/ts/fixtures/task/src/drops/orders.rs +++ b/ts/fixtures/task/src/drops/orders.rs @@ -122,14 +122,12 @@ pub(super) fn orders(log: &Log, id: u32, arg: u8) { log.note(format!("map {}", map_ids(&map))); } 3 => { - // Each emptied by `into_iter`, in std's order: dropped whole, a tree of more than one - // node drops in the runtime's order, not std's node by node. let set: BTreeSet = keys(&long).into_iter().collect(); log.note(format!("set {}", set_ids(&set))); - set.into_iter().for_each(drop); + drop(set); let map: BTreeMap = entries(&long).into_iter().collect(); log.note(format!("map {}", map_ids(&map))); - map.into_iter().for_each(drop); + drop(map); } 4 => { // Keys that drop nothing, by a `Cell` of what has its own `Ord`, of values that do. diff --git a/ts/runtime/rs/collections.ts b/ts/runtime/rs/collections.ts index 93b77cf1d..6c6c3bcb8 100644 --- a/ts/runtime/rs/collections.ts +++ b/ts/runtime/rs/collections.ts @@ -1989,22 +1989,6 @@ function bulk(m: BTreeMap, keys: K[], values: V[]): void { } } -/** Each key in order, and each one's value. */ -function flatten(m: BTreeMap): [K[], V[]] { - const keys: K[] = []; - const values: V[] = []; - const visit = (n: Node) => { - for (let i = 0; i < n.k.length; i++) { - if (n.e !== undefined) visit(n.e[i]); - keys.push(n.k[i]); - values.push(n.v[i]); - } - if (n.e !== undefined) visit(n.e[n.k.length]); - }; - visit(m.t); - return [keys, values]; -} - /** `first_key_value`, and with `last`, `last_key_value`. */ export function btreeFirst(m: BTreeMap, last = false): [K, V] | undefined { if (m.n === 0) return undefined; @@ -2040,13 +2024,21 @@ export function btreePopKey(m: BTreeMap, last = false, none?: None): return entry === undefined ? none : entry[0]; } -/** Drop `m`: each key by `k` and each value by `v`, of those that drop, in key order. */ +/** + * Drop `m`: each key by `k` and each value by `v`, of those that drop, as std's `drop_tree` + * does, node by node in postorder, so each node's entries in order after its children's. + */ export function btreeDrop(m: BTreeMap, k: ((key: K) => void) | undefined, v?: (value: V) => void): void { - const [keys, values] = flatten(m); - dropPairs(keys, values, k, v); + dropNode(m.t, k, v); +} + +/** Drops the entries of `n` and the nodes below it, as `btreeDrop` does. */ +function dropNode(n: Node, k: ((key: K) => void) | undefined, v?: (value: V) => void): void { + if (n.e !== undefined) for (const child of n.e) dropNode(child, k, v); + dropPairs(n.k, n.v, k, v); } -/** `clear`, which leaves the map empty and then drops each entry in key order. */ +/** `clear`, which leaves the map empty and then drops it, as `btreeDrop` does. */ export function btreeClear(m: BTreeMap, k?: (key: K) => void, v?: (value: V) => void): void { if (k !== undefined || v !== undefined) { const old: BTreeMap = { t: m.t, h: m.h, n: m.n, c: m.c, r: m.r }; diff --git a/ts/tests/btree.ts b/ts/tests/btree.ts index 6ad651261..16869b2e5 100644 --- a/ts/tests/btree.ts +++ b/ts/tests/btree.ts @@ -1,8 +1,9 @@ // The runtime's B-tree through a script of random operations, large enough to split, merge, // and rebalance its nodes at every height, and to empty it again: inserts, removals, pops, // lookups, ranges and iterators walked from both ends, `retain`, `extract_if` read part way, -// `clone`, `split_off`, and `collect`. Prints what each returns and which keys each compared, -// which `runtime.rs` compares with std's `BTreeMap` running the same script. +// `clone`, `split_off`, and `collect`, and drops whole trees. Prints what each returns, which +// keys each compared, and which values each whole drop dropped in what order, which +// `runtime.rs` compares with std's `BTreeMap` running the same script. // // usage: node btree.ts @@ -34,6 +35,16 @@ const say = (line: string) => { calls = 0; hash = 0; }; +/** How `f` drops values through the dropper it is given: how many, and their hash, as `runtime.rs` logs them. */ +const logged = (f: (v: (value: number) => void) => void) => { + let n = 0; + let h = 0; + f((value) => { + n++; + h = (Math.imul(h, 31) + value) >>> 0; + }); + return `drop ${n} ${h}`; +}; const show = (v: number | undefined) => (v === undefined ? "-" : String(v)); const walk = (it: rs.Iter<[number, number]>) => { const seen: number[] = []; @@ -111,17 +122,21 @@ for (let step = 0; step < steps; step++) { const copy = rs.btreeClone(m); rs.btreeRetain(m, (key) => key % 7 !== k % 7); say(`retain ${rs.btreeLen(m)} ${rs.btreeFirst(m)?.join(" ") ?? "-"} ${rs.btreeFirst(m, true)?.join(" ") ?? "-"}`); - m = rand() & 1 ? copy : m; - say(`kept ${rs.btreeLen(m)}`); + const [kept, unused] = rand() & 1 ? [copy, m] : [m, copy]; + m = kept; + say(`kept ${rs.btreeLen(m)} ${logged((v) => rs.btreeDrop(unused, undefined, v))}`); } else if (step % 1301 === 0) { const right = rs.btreeSplitOff(m, k); say(`split ${k} ${rs.btreeLen(m)} ${rs.btreeLen(right)}`); check(right); - m = rand() & 1 ? right : m; + const [kept, unused] = rand() & 1 ? [right, m] : [m, right]; + m = kept; + say(logged((v) => rs.btreeClear(unused, undefined, v))); } else if (step % 2003 === 0) { const entries = rs.iterToVec(rs.btreeIter(m)).items; + const old = m; m = rs.btreeCollect(rs.btreeNew(cmp), entries, 8, true, (a, b) => a === b); - say(`collect ${rs.btreeLen(m)}`); + say(`collect ${rs.btreeLen(m)} ${logged((v) => rs.btreeDrop(old, undefined, v))}`); } else if (step % 211 === 0) { const j = rand() % keys; const [lo, hi] = k < j ? [k, j] : [j, k]; @@ -136,7 +151,9 @@ for (let step = 0; step < steps; step++) { } say(`extract ${lo} ${hi} ${rs.btreeLen(m)} ${taken.join(",")}`); check(m); - m = rand() & 1 ? copy : m; + const [kept, unused] = rand() & 1 ? [copy, m] : [m, copy]; + m = kept; + say(logged((v) => rs.btreeDrop(unused, undefined, v))); } if (step % 499 === 0) check(m); } diff --git a/ts/tests/runtime.rs b/ts/tests/runtime.rs index e95802265..537a4d5f7 100644 --- a/ts/tests/runtime.rs +++ b/ts/tests/runtime.rs @@ -79,7 +79,8 @@ fn local_lazy() { } /// The runtime's B-tree runs `btree.ts`'s script of random operations as std's does, calling -/// `Ord::cmp` on the same keys in the same order. +/// `Ord::cmp` on the same keys in the same order and dropping a whole tree's values in the +/// same order. #[test] fn btree_script() { use std::cell::Cell; @@ -89,6 +90,8 @@ fn btree_script() { thread_local! { /// The calls of `Key::cmp` since the last line, and a hash of their keys. static CALLS: Cell<(u32, u32)> = const { Cell::new((0, 0)) }; + /// While a whole tree drops, how many of its values have, and a hash of them in order. + static DROPS: Cell> = const { Cell::new(None) }; } /// A key whose `cmp` counts and hashes each call, as `btree.ts`'s does. #[derive(Clone, Copy, PartialEq, Eq)] @@ -111,6 +114,21 @@ fn btree_script() { Some(self.cmp(other)) } } + /// A value that counts and hashes its drop while `DROPS` logs, as `btree.ts`'s dropper does. + #[derive(Clone)] + struct Val(u32); + impl Drop for Val { + fn drop(&mut self) { + DROPS.with(|d| d.set(d.get().map(|(n, h)| (n + 1, h.wrapping_mul(31).wrapping_add(self.0))))); + } + } + /// How `f` drops values: how many, and their hash. + fn logged(f: impl FnOnce()) -> String { + DROPS.with(|d| d.set(Some((0, 0)))); + f(); + let (n, h) = DROPS.with(|d| d.take()).unwrap(); + format!("drop {n} {h}") + } let say = |want: &mut Vec, line: String| { let (n, h) = CALLS.with(|c| c.replace((0, 0))); want.push(format!("{line} #{n} {h}")); @@ -134,7 +152,7 @@ fn btree_script() { }; fn walk<'a>( rand: &mut impl FnMut() -> u32, - it: &mut impl DoubleEndedIterator, + it: &mut impl DoubleEndedIterator, ) -> String { let mut seen = Vec::new(); for _ in 0..40 { @@ -144,11 +162,11 @@ fn btree_script() { } seen.join(",") } - let show = |v: Option| v.map_or("-".to_string(), |v| v.to_string()); - let entry = |e: Option<(&Key, &u32)>| e.map_or("-".to_string(), |(k, v)| format!("{} {v}", k.0)); + let show = |v: Option| v.map_or("-".to_string(), |v| v.0.to_string()); + let entry = |e: Option<(&Key, &Val)>| e.map_or("-".to_string(), |(k, v)| format!("{} {}", k.0, v.0)); let mut want = Vec::new(); - let mut m: BTreeMap = BTreeMap::new(); + let mut m: BTreeMap = BTreeMap::new(); let steps = 120_000; for step in 0..steps { let keys = 65_536; @@ -157,12 +175,12 @@ fn btree_script() { let k = rand() % keys; if op < 5 { if phase == 0 || (phase == 1 && k % 2 == 0) { - say(&mut want, format!("insert {k} {}", show(m.insert(Key(k), step)))); + say(&mut want, format!("insert {k} {}", show(m.insert(Key(k), Val(step))))); } else { say(&mut want, format!("remove {k} {}", show(m.remove(&Key(k))))); } } else if op == 5 { - say(&mut want, format!("get {k} {}", show(m.get(&Key(k)).copied()))); + say(&mut want, format!("get {k} {}", show(m.get(&Key(k)).cloned()))); } else if op == 6 { let j = rand() % keys; let (lo, hi) = if k < j { (k, j) } else { (j, k) }; @@ -179,7 +197,7 @@ fn btree_script() { &mut want, format!( "pop {last} {}", - popped.map_or("-".to_string(), |(k, v)| format!("{} {v}", k.0)) + popped.map_or("-".to_string(), |(k, v)| format!("{} {}", k.0, v.0)) ), ); } else if op == 8 { @@ -200,32 +218,41 @@ fn btree_script() { entry(m.last_key_value()) ), ); - if rand() & 1 == 1 { - m = copy; - } - say(&mut want, format!("kept {}", m.len())); + let unused = if rand() & 1 == 1 { + std::mem::replace(&mut m, copy) + } else { + copy + }; + say(&mut want, format!("kept {} {}", m.len(), logged(|| drop(unused)))); } else if step % 1301 == 0 { let right = m.split_off(&Key(k)); say(&mut want, format!("split {k} {} {}", m.len(), right.len())); - if rand() & 1 == 1 { - m = right; - } + let mut unused = if rand() & 1 == 1 { + std::mem::replace(&mut m, right) + } else { + right + }; + say(&mut want, logged(|| unused.clear())); } else if step % 2003 == 0 { - m = m.iter().map(|(k, v)| (*k, *v)).collect(); - say(&mut want, format!("collect {}", m.len())); + let collected = m.iter().map(|(k, v)| (*k, v.clone())).collect(); + let old = std::mem::replace(&mut m, collected); + say(&mut want, format!("collect {} {}", m.len(), logged(|| drop(old)))); } else if step % 211 == 0 { let j = rand() % keys; let (lo, hi) = if k < j { (k, j) } else { (j, k) }; let limit = rand() % 400; let copy = m.clone(); - let taken: Vec = (m.extract_if(Key(lo)..Key(hi), |key, value| (key.0 ^ *value) % 3 != 0)) + let taken: Vec = (m.extract_if(Key(lo)..Key(hi), |key, value| (key.0 ^ value.0) % 3 != 0)) .take(limit as usize) .map(|(k, _)| k.0.to_string()) .collect(); say(&mut want, format!("extract {lo} {hi} {} {}", m.len(), taken.join(","))); - if rand() & 1 == 1 { - m = copy; - } + let unused = if rand() & 1 == 1 { + std::mem::replace(&mut m, copy) + } else { + copy + }; + say(&mut want, logged(|| drop(unused))); } } let keys: Vec = m.keys().map(|k| k.0.to_string()).collect(); From c5bfba9db702e29940d413fb4a51b5ce3e46b8c9 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 14:50:29 -0800 Subject: [PATCH 3/3] refactor(ts): name the B-tree's drop walk after std's drop_tree Co-Authored-By: Claude Opus 5.5 --- ts/runtime/rs/collections.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/runtime/rs/collections.ts b/ts/runtime/rs/collections.ts index 505793320..72dc7a5ab 100644 --- a/ts/runtime/rs/collections.ts +++ b/ts/runtime/rs/collections.ts @@ -2061,12 +2061,12 @@ export function btreePopKey(m: BTreeMap, last = false, none?: None): * does, node by node in postorder, so each node's entries in order after its children's. */ export function btreeDrop(m: BTreeMap, k: ((key: K) => void) | undefined, v?: (value: V) => void): void { - dropNode(m.t, k, v); + dropTree(m.t, k, v); } /** Drops the entries of `n` and the nodes below it, as `btreeDrop` does. */ -function dropNode(n: Node, k: ((key: K) => void) | undefined, v?: (value: V) => void): void { - if (n.e !== undefined) for (const child of n.e) dropNode(child, k, v); +function dropTree(n: Node, k: ((key: K) => void) | undefined, v?: (value: V) => void): void { + if (n.e !== undefined) for (const child of n.e) dropTree(child, k, v); dropPairs(n.k, n.v, k, v); }