From a5f5099d299ad98ba9f7ce7dca031bc9e659e369 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:16:07 -0800 Subject: [PATCH 1/3] chore(quest): claim quest/m3/ts-guard-leftovers.md Quest-Claim: bf331826411f364e From 7535e93129ff28a90a1623ceb3a789dc226fc448 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:28:41 -0800 Subject: [PATCH 2/3] feat(ts): translate a guard's downgrade, leak, and whole byte-array map Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-guard-leftovers.md | 24 --- ts/README.md | 2 +- ts/fixtures/lang/caller.ts | 15 +- ts/fixtures/lang/expected/borrows.ts | 172 ++++++++++++++++++++- ts/fixtures/lang/expected/core/bool.ts | 7 + ts/fixtures/lang/expected/core/result.ts | 16 +- ts/fixtures/lang/expected/harness.ts | 10 +- ts/fixtures/lang/expected/lib.ts | 82 ++++++++++ ts/fixtures/lang/expected/locks.ts | 187 ++++++++++++++++++++++- ts/fixtures/lang/src/borrows.rs | 100 +++++++++++- ts/fixtures/lang/src/harness.rs | 2 + ts/fixtures/lang/src/lib.rs | 1 + ts/fixtures/lang/src/locks.rs | 100 +++++++++++- ts/runtime/rt/task.ts | 12 ++ ts/src/lower/lend.rs | 4 +- ts/src/lower/library.rs | 26 +++- ts/src/lower/mir/call.rs | 4 +- ts/src/lower/task.rs | 11 +- 19 files changed, 729 insertions(+), 47 deletions(-) delete mode 100644 quest/m3/ts-guard-leftovers.md diff --git a/quest/m3/README.md b/quest/m3/README.md index d0e05438c..8e10c96e4 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -36,7 +36,6 @@ generic std gap is planned here even when moq never calls it. - [Aborts in the differential](/quest/m3/ts-abort-differential.md) - the lang differential compares wasm32's aborts, so an `RwLock` locked again while held is tested - [A reversed zip's per-pair cost](/quest/m3/ts-zip-rev-overhead.md) - a reversed by-index zip costs per pair what a forward one does, or the gap is explained - [Whole writes into a lazy cell or thread-local key](/quest/m3/ts-lazy-writes.md) - a whole write through a `&mut` to a `LazyCell`, `LazyLock`, or `LocalKey` keeps its state -- [A guard's downgrade, leak, and whole byte-array map](/quest/m3/ts-guard-leftovers.md) - `downgrade`, `Ref::leak`, `RefMut::leak`, and a guard's map over a whole `[u8; N]` translate - [Type errors at the Rust that made them](/quest/m3/ts-span-precision.md) - a `tsc` error on output names the tail expression or call argument that made it - [An empty iterator of mutable references](/quest/m3/ts-empty-places.md) - `iter::empty::<&mut T>()` given to `zip` or `chain` translates - [An Option of numbers in the result slot](/quest/m3/ts-option-slot.md) - `varint_opt`'s `Option` comes back through the slot with no object per call diff --git a/quest/m3/ts-guard-leftovers.md b/quest/m3/ts-guard-leftovers.md deleted file mode 100644 index f86842bb1..000000000 --- a/quest/m3/ts-guard-leftovers.md +++ /dev/null @@ -1,24 +0,0 @@ -# [S] A guard's downgrade, leak, and whole byte-array map - -## Goal - -[rust2-ts](/ts/README.md) translates `RwLockWriteGuard::downgrade`, `Ref::leak` and -`RefMut::leak`, and a guard's `map(|p| p)` that lends a whole `[u8; N]`, each holding or -releasing its lock or borrow as std's does, proven by differential tests against native Rust. - -## Plan - -Found by [#392](https://github.com/moq-dev/rust2/pull/392), which translated the rest of a -guard's maps and `Debug`: - -- `RwLockWriteGuard::map(guard, |p| p)` over a whole `[u8; N]` is refused with "a reference - to an array of bytes it is generic over". The refusal predates #392; check whether - `RefMut::map` over a whole byte array fails the same way, and fix both. -- `downgrade` (stable since 1.92) turns the write lock into a read lock without releasing - it, so a `try_write` between fails and a `try_read` succeeds. -- `leak` keeps the cell borrowed for the rest of the program, so a later `borrow_mut` - panics with wasm32's message. - -Reproduce each refusal first; any that already translate drop from this quest. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index 66692bd28..83c90514e 100644 --- a/ts/README.md +++ b/ts/README.md @@ -123,7 +123,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | a type's function | a function of the type's module, taking `self` first: `Decoder.slice(dec, 4)` after `import * as Decoder` | | `Arc`, `Rc`, `Weak`, `Mutex` | `rt.Arc` for either, with its strong and weak counts, and `new_cyclic`'s `Weak` upgrading only once its value is made, `get_mut` lending its value only while no other `Arc` or `Weak` shares it, `make_mut` cloning it into a new `rt.Arc` while another `Arc` shares it, or moving it there while only `Weak`s do, which then dangle, and `into_inner` giving it only from the last strong `Arc`, after which its `Weak`s no longer upgrade; `rt.Weak`, counted in its `rt.Arc` until it drops, and of no `Arc` once made by `Weak::new`; `rt.Mutex`, its own guard, which a lock gives as `Ok` and the guard's drop unlocks: locking it again while held panics and `try_lock` gives `WouldBlock`, as wasm32's do, and a guard's `map` and `filter_map` make `rt.MutexMap`s, each where the part it lends is, which hold the mutex locked until they drop, and a panic aborts, so nothing poisons it and `thread::panicking` is `false`; a `Mutex`'s, a `RefCell`'s, and an `RwLock`'s `into_inner` gives its value and `get_mut` lends it, or an `rt.Place` of it where JS copies it | | `PoisonError`, `TryLockError` | the `T` it holds; `rt.TryLockError`, tagged as `Bound` is | -| `Cell`, `UnsafeCell`, `RefCell`, `RwLock` | `rt.Cell`, the place holding its value in `v`, for either of the first two: `UnsafeCell::get`'s pointer is the cell where it is an `rt.Place`, and otherwise the object in `v`, refused where the crate may put another cell or object in its place; `rt.RefCell` for either of the others, its own guard, counting its borrows: borrowing it while that conflicts panics as `RefCell`'s does, and locking it so aborts as wasm32's `RwLock` does; `try_borrow` and `try_borrow_mut` fail with `rt.BorrowError` and `rt.BorrowMutError` instead, a clone borrows the cell, `Ref::clone` counts another borrow of it, and a guard's `map`, `filter_map`, `try_map`, and `map_split`, an `RwLock` guard's included, make `rt.RefMap`s, each where a part it lends is, which a write through a mapped `RefMut` reaches in place, sharing the cell's borrow until the last drops; refused for a part of a sized type JS can't write in place, such as a binding's | +| `Cell`, `UnsafeCell`, `RefCell`, `RwLock` | `rt.Cell`, the place holding its value in `v`, for either of the first two: `UnsafeCell::get`'s pointer is the cell where it is an `rt.Place`, and otherwise the object in `v`, refused where the crate may put another cell or object in its place; `rt.RefCell` for either of the others, its own guard, counting its borrows: borrowing it while that conflicts panics as `RefCell`'s does, and locking it so aborts as wasm32's `RwLock` does; `try_borrow` and `try_borrow_mut` fail with `rt.BorrowError` and `rt.BorrowMutError` instead, a clone borrows the cell, `Ref::clone` counts another borrow of it, and a guard's `map`, `filter_map`, `try_map`, and `map_split`, an `RwLock` guard's included, make `rt.RefMap`s, each where a part it lends is, which a write through a mapped `RefMut` reaches in place, sharing the cell's borrow until the last drops; `RwLockWriteGuard::downgrade` keeps the lock as one read, and `Ref::leak` and `RefMut::leak` keep the borrow until `RefCell::undo_leak` ends it; refused for a part of a sized type JS can't write in place, such as a binding's | | `thread_local!`, `LocalKey` | an `rt.LocalKey`, a module's value, which makes its value on first use from the `const` or the expression and never drops it, as wasm32's do; `with` calls its function on that value, `try_with` never fails, the `Cell` and `RefCell` helpers lower from std, and a `RefCell` key's `set` and `with_borrow_mut` borrow it as `borrow_mut` does | | `OnceCell`, `OnceLock` | `rt.OnceCell` and `rt.OnceLock`, holding the `Option` in `v`, which is the place `get_mut` lends where JS copies the value; a `OnceLock` initialized from within its own initialization panics, as wasm32's does, and `get_or_try_init`'s error leaves either empty | | `Once`, `OnceState` | `rt.Once`, an `rt.OnceLock` that holds `true` once `call_once` or `call_once_force` ran its function, and panics as wasm32's does if run again from within it; `rt.OnceState`, that `Once`, which a panic never poisons, as it aborts | diff --git a/ts/fixtures/lang/caller.ts b/ts/fixtures/lang/caller.ts index 2e52741fe..c0e355038 100644 --- a/ts/fixtures/lang/caller.ts +++ b/ts/fixtures/lang/caller.ts @@ -441,6 +441,19 @@ const mutexMaps = [[0x04, 0x01, 0x02, 0x86, 0xf5, 0x0a, 0x03, 0x02, 0xe1, 0x80, /** `locks::printed` scripts: each lock's guards printed, empty and not. */ const printed = [[], [0, 1, 25, 200]].map((bytes) => Uint8Array.of(op("locks::printed"), 0, ...bytes)); +/** `locks::downgraded` scripts: write guards downgraded, each lock's released in either order, and whole byte arrays mapped. */ +const downgrades = [[0x00, 0x01, 0x11, 0x02, 0xf2, 0xe2, 0x03, 0x04, 0x13, 0x05]].map((bytes) => + Uint8Array.of(op("locks::downgraded"), 0, ...bytes), +); + +/** + * `borrows::leaks` scripts: guards leaked and undone, whole byte arrays mapped, and each + * borrow again while a leak holds the cell, which panics. + */ +const leaks = [[0x00, 0x01, 0x02, 0xf1, 0xf2, 0x04, 0x05, 0x03, 0x06], [0xe3], [0x00, 0xf3]].map((bytes) => + Uint8Array.of(op("borrows::leaks"), 0, ...bytes), +); + /** `locks::rwlocks` scripts: `get_mut` and `into_inner` among writes and reads. */ const rwlocks = [[6, 7, 0, 6, 2, 14, 15, 1]].map((bytes) => Uint8Array.of(op("locks::rwlocks"), 0, ...bytes)); @@ -827,4 +840,4 @@ const capacityCases = [ ...Array.from({ length: 8 * 6 }, (_, i) => Uint8Array.of(op("capacity::collects"), i % 8, [0, 1, 2, 5, 9, 23][Math.floor(i / 8)], 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), ]; -export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...singlesCases, ...singlesFormats, ...singlesParsed, ...dyns, ...cells, ...shared, ...locks, ...guards, ...maps, ...mappedLocks, ...tries, ...mutexMaps, ...printed, ...rwlocks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...hinted, ...peekedOthers, ...hints, ...reserves, ...setOps, ...zeros, ...writes, ...floatsMulAdds, ...singlesMulAdds, ...unlikeEqs, ...ownNes, ...rangeFroms, ...compares, ...unlikeCompares, ...rangeMethods, ...wraps(), ...refOrders, ...saturates(), ...methods(), ...separated, ...asciiCases, ...gets, ...capacityCases]; +export const cases: Uint8Array[] = [...loops, ...durations, ...ints(), ...utf8, ...floats, ...formats, ...parsed, ...singlesCases, ...singlesFormats, ...singlesParsed, ...dyns, ...cells, ...shared, ...locks, ...guards, ...maps, ...mappedLocks, ...tries, ...mutexMaps, ...printed, ...downgrades, ...leaks, ...rwlocks, ...onces, ...bounds, ...unsafeCells, ...edits, ...subslices, ...consumers, ...closures, ...elements, ...ranges, ...reducers, ...sliceEdits, ...drains, ...iters, ...hinted, ...peekedOthers, ...hints, ...reserves, ...setOps, ...zeros, ...writes, ...floatsMulAdds, ...singlesMulAdds, ...unlikeEqs, ...ownNes, ...rangeFroms, ...compares, ...unlikeCompares, ...rangeMethods, ...wraps(), ...refOrders, ...saturates(), ...methods(), ...separated, ...asciiCases, ...gets, ...capacityCases]; diff --git a/ts/fixtures/lang/expected/borrows.ts b/ts/fixtures/lang/expected/borrows.ts index 21afb269f..160376c6a 100644 --- a/ts/fixtures/lang/expected/borrows.ts +++ b/ts/fixtures/lang/expected/borrows.ts @@ -1,12 +1,12 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { copy$Option_tuple2_u64_u64, copy$tuple2_u64_u64, drop$Result_cell_RefMut_Vec_u8_cell_BorrowMutError, drop$Result_cell_RefMut_array4_u8_cell_BorrowMutError, drop$Result_cell_RefMut_borrows_Parts_cell_BorrowMutError, drop$Result_cell_RefMut_borrows_Tagged_cell_BorrowMutError, drop$Result_cell_RefMut_u32_cell_BorrowMutError, drop$Result_cell_RefMut_u32_tuple2_cell_RefMut_borrows_Parts_unit, drop$Result_cell_Ref_Vec_u8_cell_BorrowError, drop$Result_cell_Ref_array4_u8_cell_BorrowError, drop$Result_cell_Ref_borrows_Parts_cell_BorrowError, drop$Result_cell_Ref_ref_str_tuple2_cell_Ref_tuple2_u8_ref_str_u8, drop$Result_cell_Ref_u32_cell_BorrowError, drop$Result_cell_Ref_u32_cell_Ref_borrows_Parts, drop$Result_cell_Ref_u32_tuple2_cell_Ref_borrows_Parts_unit, drop$tuple2_Option_cell_Ref_borrows_Tagged_Option_cell_RefMut_borrows_Tagged, drop$tuple2_Result_cell_Ref_Vec_u8_cell_BorrowError_cell_Ref_Vec_u8, drop$tuple2_cell_RefMut_slice_u8_cell_RefMut_slice_u8, drop$tuple2_cell_Ref_Vec_u8_cell_Ref_u8, drop$tuple2_cell_Ref_str_cell_RefMut_u32, drop$tuple3_cell_Ref_String_cell_Ref_array4_u8_cell_Ref_u32 } from "./lib.ts"; +import { copy$Option_tuple2_u64_u64, copy$tuple2_u64_u64, drop$Result_cell_RefMut_Vec_u8_cell_BorrowMutError, drop$Result_cell_RefMut_array4_u8_cell_BorrowMutError, drop$Result_cell_RefMut_borrows_Parts_cell_BorrowMutError, drop$Result_cell_RefMut_borrows_Tagged_cell_BorrowMutError, drop$Result_cell_RefMut_tuple2_u16_Vec_u8_cell_BorrowMutError, drop$Result_cell_RefMut_u32_cell_BorrowMutError, drop$Result_cell_RefMut_u32_tuple2_cell_RefMut_borrows_Parts_unit, drop$Result_cell_Ref_Vec_u8_cell_BorrowError, drop$Result_cell_Ref_array4_u8_cell_BorrowError, drop$Result_cell_Ref_borrows_Parts_cell_BorrowError, drop$Result_cell_Ref_ref_str_tuple2_cell_Ref_tuple2_u8_ref_str_u8, drop$Result_cell_Ref_tuple2_u16_Vec_u8_cell_BorrowError, drop$Result_cell_Ref_u32_cell_BorrowError, drop$Result_cell_Ref_u32_cell_Ref_borrows_Parts, drop$Result_cell_Ref_u32_tuple2_cell_Ref_borrows_Parts_unit, drop$tuple2_Option_cell_Ref_borrows_Tagged_Option_cell_RefMut_borrows_Tagged, drop$tuple2_Result_cell_Ref_Vec_u8_cell_BorrowError_cell_Ref_Vec_u8, drop$tuple2_cell_RefMut_slice_u8_cell_RefMut_slice_u8, drop$tuple2_cell_Ref_Vec_u8_cell_Ref_u8, drop$tuple2_cell_Ref_str_cell_RefMut_u32, drop$tuple3_cell_Ref_String_cell_Ref_array4_u8_cell_Ref_u32 } from "./lib.ts"; import * as borrows_Noisy from "./borrows.Noisy.ts"; import * as borrows_Parts from "./borrows.Parts.ts"; import * as borrows_Polling from "./borrows.Polling.ts"; import * as borrows_Tagged from "./borrows.Tagged.ts"; -import { then$1jgls7y } from "./core/bool.ts"; +import { thenSome$0zrlsto, then$1jgls7y } from "./core/bool.ts"; import { unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, mapOr$0qqbaqw as option_mapOr$0qqbaqw, mapOr$1dvn0au as option_mapOr$1dvn0au, mapOr$099335t as option_mapOr$099335t, mapOr$0ucbp7g as option_mapOr$0ucbp7g, okOr$1oufq9m as option_okOr$1oufq9m } from "./core/option.ts"; import { map$0n1xnuw as result_map$0n1xnuw, unwrapOr$0k9nfhn as result_unwrapOr$0k9nfhn } from "./core/result.ts"; @@ -819,6 +819,153 @@ export function tries(data: rt.ByteSlice, out: rt.ByteVec): void { log[Symbol.dispose](); } +/** + * Each byte an operation on `RefCell`s through guards leaked, which keep the cell borrowed + * until `undo_leak`, and guards mapped, filtered, and tried to the whole of the bytes a + * cell holds, its value the operand; each puts what it read through them and whether the + * cell is still borrowed, and borrows again while a leak holds it, which panics. + */ +export function leaks(data: rt.ByteSlice, out: rt.ByteVec): void { + const pair: rt.RefCell<[rt.u16, rt.ByteVec]> = new rt.RefCell([0, new rt.ByteVec()] as [rt.u16, rt.ByteVec]); + const number: rt.RefCell = new rt.RefCell(0); + const quad: rt.RefCell = new rt.RefCell(Uint8Array.of(1, 2, 3, 4)); + for (const op of rt.byteSliceView(data)) { + const _16: rt.u8 = rt.u8Rem(op, 4); + if (_16 === 0) { + const whole: [rt.u16, rt.ByteVec] = rt.refCellBorrow(pair).value; + const other: rt.RefCell<[rt.u16, rt.ByteVec]> = rt.refCellBorrow(pair); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of((whole[0] & 0xff) ^ (other.value[1].length & 0xff), borrowed$tuple2_u16_Vec_u8(pair)))); + rt.refRelease(other); + const _40: rt.RefCell<[rt.u16, rt.ByteVec]> = rt.refCellBorrow(pair); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(rt.refMap(_40, ((p2: [rt.u16, rt.ByteVec]): rt.ByteVec => { + return p2[1]; + })(_40.value)).value)); + rt.byteVecPush(out, borrowed$tuple2_u16_Vec_u8(pair)); + rt.refCellUndoLeak(pair).value; + rt.byteVecPush(out, borrowed$tuple2_u16_Vec_u8(pair)); + } else if (_16 === 1) { + const n: rt.Place = rt.valuePlace(rt.refCellBorrowMut(number)); + n.v = rt.u32Add(n.v, op as rt.u32); + rt.byteVecPush(out, borrowed$u32(number)); + const _60: rt.Place = rt.valuePlace(rt.refCellUndoLeak(number)); + _60.v = (_60.v ^ 1) >>> 0; + const _66: rt.u8 = borrowed$u32(number); + const _72: rt.RefCell = rt.refCellBorrow(number); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of(_66, _72.value & 0xff))); + rt.refRelease(_72); + const whole2: [rt.u16, rt.ByteVec] = rt.refCellBorrowMut(pair).value; + whole2[0] = rt.u16Add(whole2[0], op as rt.u16); + rt.byteVecPush(whole2[1], op); + if ((op >> 4) === 15) { + Object.assign(whole2, [op as rt.u16, rt.byteVecFrom(Uint8Array.of(op))] as [rt.u16, rt.ByteVec]); + } + rt.byteVecPush(out, borrowed$tuple2_u16_Vec_u8(pair)); + rt.byteVecPush(rt.refCellUndoLeak(pair).value[1], 252); + rt.byteVecPush(out, borrowed$tuple2_u16_Vec_u8(pair)); + const _100: rt.RefCell<[rt.u16, rt.ByteVec]> = rt.refCellBorrowMut(pair); + rt.byteVecPush(rt.refMutMap(_100, rt.partPlace(((p: [rt.u16, rt.ByteVec]): rt.ByteVec => { + return p[1]; + })(_100.value))).value, op); + rt.byteVecPush(out, borrowed$tuple2_u16_Vec_u8(pair)); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(pair.value[1])); + } else if (_16 === 2) { + const _112: rt.RefCell = rt.refCellBorrowMut(quad); + const f = (q3: rt.ByteSlice): rt.ByteSlice => { + return q3; + }; + const bytes3: rt.RefCell = rt.refMutMap(_112, rt.partPlace(rt.byteSliceView(((held) => f(rt.byteSliceFrom(held)))(_112.value)))); + const _114: rt.ByteSlice = rt.byteSliceFrom(bytes3.value); + const _116: rt.usize = (op & 3) as rt.usize; + if (!(_116 < 4)) { + rt.panic("index out of bounds: the len is " + 4 + " but the index is " + _116); + } + _114.data[_114.start + _116] = _114.data[_114.start + _116] ^ op; + const _120: rt.RefCell = bytes3; + const f2 = (q4: rt.ByteSlice): rt.ByteSlice => { + return q4; + }; + const again: rt.RefCell = rt.refMutMap(_120, rt.partPlace(rt.byteSliceView(((held2) => f2(rt.byteSliceFrom(held2)))(_120.value)))); + if ((op >> 4) === 15) { + const _125: rt.ByteSlice = rt.byteSliceFrom(again.value); + _125.data.set(rt.bytes(4).fill(op), _125.start); + } + rt.byteVecPush(out, borrowed$array4_u8(quad)); + rt.refMutRelease(again); + const _133: rt.RefCell = rt.refCellBorrow(quad); + const f3 = (q5: rt.ByteSlice): rt.ByteSlice => { + return q5; + }; + const read: rt.RefCell = rt.refMap(_133, rt.byteSliceView(((held3) => f3(rt.byteSliceFrom(held3)))(_133.value))); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(read.value)); + rt.byteVecPush(out, borrowed$array4_u8(quad)); + rt.refRelease(read); + const _145: rt.RefCell = rt.refCellBorrowMut(quad); + const f4 = (q6: rt.ByteSlice): rt.ByteSlice | undefined => { + return thenSome$0zrlsto((q6.data[q6.start] & 1) === 0, q6); + }; + const part = ((held4) => f4(rt.byteSliceFrom(held4)))(_145.value); + const _144: rt.Result, rt.RefCell> = rt.refMutFilterMap(_145, part === undefined ? undefined : rt.partPlace(rt.byteSliceView(part))); + if (_144.ok) { + const q: rt.RefCell = _144.value; + const _151: rt.ByteSlice = rt.byteSliceFrom(q.value); + const _149: rt.u8 = rt.u8WrappingAdd(_151.data[_151.start + 1], op); + const _155: rt.ByteSlice = rt.byteSliceFrom(q.value); + _155.data[_155.start + 1] = _149; + rt.byteVecPush(out, borrowed$array4_u8(quad)); + rt.refMutRelease(q); + } else { + const whole3: rt.RefCell = _144.error; + const _165: rt.ByteSlice = rt.byteSliceFrom(whole3.value); + rt.byteVecPush(out, _165.data[_165.start] ^ borrowed$array4_u8(quad)); + rt.refMutRelease(whole3); + } + const _173: rt.RefCell = rt.refCellBorrow(quad); + const f5 = (q7: rt.ByteSlice): rt.Result => { + if (!((q7.data[q7.start + 2] & 1) === 0)) { + return rt.Err(q7.data[q7.start + 2]); + } + return rt.Ok(q7); + }; + const part2 = ((held5) => f5(rt.byteSliceFrom(held5)))(_173.value); + const _172: rt.Result, [rt.RefCell, rt.u8]> = rt.refTryMap(_173, part2.ok ? rt.Ok(rt.byteSliceView(part2.value)) : rt.Err(part2.error)); + if (_172.ok) { + const q2: rt.RefCell = _172.value; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(q2.value)); + rt.refRelease(q2); + } else { + const whole4: rt.RefCell = _172.error[0]; + const e: rt.u8 = _172.error[1]; + const _184: rt.ByteSlice = rt.byteSliceFrom(whole4.value); + rt.byteVecPush(out, _184.data[_184.start + 3] ^ e); + rt.refRelease(whole4); + } + const _190: rt.RefCell = rt.refCellBorrowMut(quad); + const f6 = (q8: rt.ByteSlice): rt.ByteSlice => { + return q8; + }; + rt.byteSliceRotateLeft(rt.byteSliceFrom(rt.refMutMap(_190, rt.partPlace(rt.byteSliceView(((held6) => f6(rt.byteSliceFrom(held6)))(_190.value)))).value), 1); + rt.byteVecPush(out, borrowed$array4_u8(quad)); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(rt.refCellUndoLeak(quad).value)); + } else { + const _202: rt.u8 = op >> 4; + if (_202 === 14) { + rt.refCellBorrow(pair).value; + rt.refMutRelease(rt.refCellBorrowMut(pair)); + } else if (_202 === 15) { + rt.valuePlace(rt.refCellBorrowMut(number)); + rt.refRelease(rt.refCellBorrow(number)); + } else { + rt.byteVecPush(out, borrowed$tuple2_u16_Vec_u8(pair)); + } + } + } + const last: [rt.u16, rt.ByteVec] = pair.value; + rt.byteVecPush(out, last[0] & 0xff); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(last[1])); + rt.byteVecPush(out, number.value & 0xff); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(quad.value)); +} + /** Whether `cell` is borrowed exclusively, as 2, shared, as 1, or not at all. */ export function borrowed$array4_u8(cell: rt.RefCell): rt.u8 { let _0!: rt.u8; @@ -861,6 +1008,27 @@ export function borrowed$borrows_Parts(cell: rt.RefCell): r return _0; } +/** Whether `cell` is borrowed exclusively, as 2, shared, as 1, or not at all. */ +export function borrowed$tuple2_u16_Vec_u8(cell: rt.RefCell<[rt.u16, rt.ByteVec]>): rt.u8 { + let _0!: rt.u8; + const _5: rt.Result, rt.BorrowError> = rt.refCellTryBorrow(cell); + const _3: boolean = !_5.ok; + const _8: rt.Result, rt.BorrowMutError> = rt.refCellTryBorrowMut(cell); + const _2: [boolean, boolean] = [_3, !_8.ok]; + if (!_3) { + if (!_2[1]) { + _0 = 0; + } else { + _0 = 1; + } + } else { + _0 = 2; + } + drop$Result_cell_RefMut_tuple2_u16_Vec_u8_cell_BorrowMutError(_8); + drop$Result_cell_Ref_tuple2_u16_Vec_u8_cell_BorrowError(_5); + return _0; +} + /** Whether `cell` is borrowed exclusively, as 2, shared, as 1, or not at all. */ export function borrowed$u32(cell: rt.RefCell): rt.u8 { let _0!: rt.u8; diff --git a/ts/fixtures/lang/expected/core/bool.ts b/ts/fixtures/lang/expected/core/bool.ts index daa6d4fd5..91fbaa4c3 100644 --- a/ts/fixtures/lang/expected/core/bool.ts +++ b/ts/fixtures/lang/expected/core/bool.ts @@ -63,6 +63,13 @@ export function thenSome$1d6toqn(self: boolean, t: undefined): undefined | rt.No return t; } +export function thenSome$0zrlsto(self: boolean, t: rt.ByteSlice): rt.ByteSlice | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$0xksuo2(self: boolean, t: rt.Place): rt.Place | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/expected/core/result.ts b/ts/fixtures/lang/expected/core/result.ts index 459479065..5cab658e1 100644 --- a/ts/fixtures/lang/expected/core/result.ts +++ b/ts/fixtures/lang/expected/core/result.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "../rt.ts"; -import { drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally, drop$Result_tuple2_usize_u8_sync_Arc_dyn_Any_marker_Sync_marker_Send } from "../lib.ts"; +import { drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally, drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally, drop$Result_tuple2_usize_u8_sync_Arc_dyn_Any_marker_Sync_marker_Send } from "../lib.ts"; import { eq$09knsz6 as string_eq$09knsz6 } from "../alloc/string.ts"; import * as ByRef from "../collections.ByRef.ts"; import * as ByVal from "../collections.ByVal.ts"; @@ -697,6 +697,20 @@ export function mapOr$0666x4n(self: rt.Result, rt.Tr return _0; } +export function mapOr$1qkf3ab(self: rt.Result, rt.TryLockError>>, default_: rt.u8, f: (a0: rt.RefCell) => rt.u8): rt.u8 { + let _0!: rt.u8; + if (self.ok) { + _0 = f(self.value); + } else { + _0 = default_; + } + if (!self.ok) { + drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally(self); + return _0; + } + return _0; +} + export function mapOr$1mu5bvh(self: rt.Result, default_: rt.u8, f: (a0: rt.u8) => rt.u8): rt.u8 { if (self.ok) { return f(self.value); diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 9ad40226a..3c03effb1 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -5,7 +5,7 @@ import { args } from "./args.ts"; import { fromFns, maps as arrays_maps, lent as arrays_lent, asSlices, given, flattened as arrays_flattened } from "./arrays.ts"; import { bytes as ascii_bytes, chars as ascii_chars, slices as ascii_slices, parts as ascii_parts } from "./ascii.ts"; import { atomics } from "./atomics.ts"; -import { borrows as borrows_borrows, guards, maps as borrows_maps, tries } from "./borrows.ts"; +import { borrows as borrows_borrows, guards, maps as borrows_maps, tries, leaks } from "./borrows.ts"; import { bounds as bounds_bounds } from "./bounds.ts"; import { boxes, places as boxes_places } from "./boxes.ts"; import { vecs as capacity_vecs, deques as capacity_deques, heaps as capacity_heaps, collects } from "./capacity.ts"; @@ -39,7 +39,7 @@ import { keys as keys_keys, overrides, bytes as keys_bytes, lentKeys, lentValues import { lengths } from "./lengths.ts"; import { lists as lists_lists } from "./lists.ts"; import { locals } from "./locals.ts"; -import { locks, relock, rwlocks, mapped, mutexMapped, printed as locks_printed } from "./locks.ts"; +import { locks, relock, rwlocks, mapped, mutexMapped, printed as locks_printed, downgraded } from "./locks.ts"; import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as methods_u64s, u128s as methods_u128s, usizes as methods_usizes, i8s as methods_i8s, i16s as methods_i16s, i32s as methods_i32s, i64s as methods_i64s, i128s as methods_i128s, isizes as methods_isizes } from "./methods.ts"; import { nested as nested_nested } from "./nested.ts"; import { objects } from "./objects.ts"; @@ -273,7 +273,11 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) capacity_deques(a0153, a1153.data, a1153.start, a1153.length, a2153); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::heaps", (a0154: rt.u8, a1154: rt.ByteSlice, a2154: rt.ByteVec): undefined => { capacity_heaps(a0154, a1154.data, a1154.start, a1154.length, a2154); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["locks::downgraded", (_2196: rt.u8, data96: rt.ByteSlice, out97: rt.ByteVec): undefined => { + downgraded(data96, out97); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::leaks", (_2197: rt.u8, data97: rt.ByteSlice, out98: rt.ByteVec): undefined => { + leaks(data97, out98); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } /** Run one case. */ diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index 0998affce..a9ecd338b 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -864,6 +864,12 @@ export function drop$Result_cell_RefMut_borrows_Tagged_cell_BorrowMutError(_1: r } } +export function drop$Result_cell_RefMut_tuple2_u16_Vec_u8_cell_BorrowMutError(_1: rt.Result, rt.BorrowMutError>): void { + if (_1.ok) { + rt.refMutRelease(_1.value); + } +} + export function drop$Result_cell_RefMut_u32_cell_BorrowMutError(_1: rt.Result, rt.BorrowMutError>): void { if (_1.ok) { rt.refMutRelease(_1.value); @@ -904,6 +910,12 @@ export function drop$Result_cell_Ref_ref_str_tuple2_cell_Ref_tuple2_u8_ref_str_u } } +export function drop$Result_cell_Ref_tuple2_u16_Vec_u8_cell_BorrowError(_1: rt.Result, rt.BorrowError>): void { + if (_1.ok) { + rt.refRelease(_1.value); + } +} + export function drop$Result_cell_Ref_u32_cell_BorrowError(_1: rt.Result, rt.BorrowError>): void { if (_1.ok) { rt.refRelease(_1.value); @@ -950,6 +962,14 @@ export function drop$Result_sync_MappedRwLockWriteGuard_u64_sync_MappedRwLockWri } } +export function drop$Result_sync_MutexGuard_array4_u8_sync_TryLockError_sync_MutexGuard_array4_u8(_1: rt.Result, rt.TryLockError>>): void { + if (_1.ok) { + rt.mutexUnlock(_1.value); + } else { + drop$sync_TryLockError_sync_MutexGuard_array4_u8(_1.error); + } +} + export function drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally(_1: rt.Result, rt.TryLockError>>): void { if (_1.ok) { rt.mutexUnlock(_1.value); @@ -966,6 +986,14 @@ export function drop$Result_sync_MutexGuard_u32_sync_TryLockError_sync_MutexGuar } } +export function drop$Result_sync_RwLockReadGuard_array4_u8_sync_TryLockError_sync_RwLockReadGuard_array4_u8(_1: rt.Result, rt.TryLockError>>): void { + if (_1.ok) { + rt.refRelease(_1.value); + } else { + drop$sync_TryLockError_sync_RwLockReadGuard_array4_u8(_1.error); + } +} + export function drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally(_1: rt.Result, rt.TryLockError>>): void { if (_1.ok) { rt.refRelease(_1.value); @@ -974,6 +1002,22 @@ export function drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_s } } +export function drop$Result_sync_RwLockReadGuard_u32_sync_TryLockError_sync_RwLockReadGuard_u32(_1: rt.Result, rt.TryLockError>>): void { + if (_1.ok) { + rt.refRelease(_1.value); + } else { + drop$sync_TryLockError_sync_RwLockReadGuard_u32(_1.error); + } +} + +export function drop$Result_sync_RwLockWriteGuard_array4_u8_sync_TryLockError_sync_RwLockWriteGuard_array4_u8(_1: rt.Result, rt.TryLockError>>): void { + if (_1.ok) { + rt.refMutRelease(_1.value); + } else { + drop$sync_TryLockError_sync_RwLockWriteGuard_array4_u8(_1.error); + } +} + export function drop$Result_sync_RwLockWriteGuard_locks_Tally_sync_TryLockError_sync_RwLockWriteGuard_locks_Tally(_1: rt.Result, rt.TryLockError>>): void { if (_1.ok) { rt.refMutRelease(_1.value); @@ -982,6 +1026,14 @@ export function drop$Result_sync_RwLockWriteGuard_locks_Tally_sync_TryLockError_ } } +export function drop$Result_sync_RwLockWriteGuard_u32_sync_TryLockError_sync_RwLockWriteGuard_u32(_1: rt.Result, rt.TryLockError>>): void { + if (_1.ok) { + rt.refMutRelease(_1.value); + } else { + drop$sync_TryLockError_sync_RwLockWriteGuard_u32(_1.error); + } +} + export function drop$Result_tuple2_tuples_Logged_u8_u8(_1: rt.Result<[tuples_Logged.Logged, rt.u8], rt.u8>): void { if (_1.ok) { drop$tuple2_tuples_Logged_u8(_1.value); @@ -1050,6 +1102,12 @@ export function drop$statics_Lock_tuple2_u8_Rc_unit(_1: statics_Lock.Lock<[rt.u8 statics_Lock.drop$ops_Drop_tuple2_u8_Rc_unit(_1); } +export function drop$sync_TryLockError_sync_MutexGuard_array4_u8(_1: rt.TryLockError>): void { + if (_1.tag === "Poisoned") { + rt.mutexUnlock((_1 as { tag: "Poisoned"; _0: rt.Mutex })._0); + } +} + export function drop$sync_TryLockError_sync_MutexGuard_locks_Tally(_1: rt.TryLockError>): void { if (_1.tag === "Poisoned") { rt.mutexUnlock((_1 as { tag: "Poisoned"; _0: rt.Mutex })._0); @@ -1062,18 +1120,42 @@ export function drop$sync_TryLockError_sync_MutexGuard_u32(_1: rt.TryLockError>): void { + if (_1.tag === "Poisoned") { + rt.refRelease((_1 as { tag: "Poisoned"; _0: rt.RefCell })._0); + } +} + export function drop$sync_TryLockError_sync_RwLockReadGuard_locks_Tally(_1: rt.TryLockError>): void { if (_1.tag === "Poisoned") { rt.refRelease((_1 as { tag: "Poisoned"; _0: rt.RefCell })._0); } } +export function drop$sync_TryLockError_sync_RwLockReadGuard_u32(_1: rt.TryLockError>): void { + if (_1.tag === "Poisoned") { + rt.refRelease((_1 as { tag: "Poisoned"; _0: rt.RefCell })._0); + } +} + +export function drop$sync_TryLockError_sync_RwLockWriteGuard_array4_u8(_1: rt.TryLockError>): void { + if (_1.tag === "Poisoned") { + rt.refMutRelease((_1 as { tag: "Poisoned"; _0: rt.RefCell })._0); + } +} + export function drop$sync_TryLockError_sync_RwLockWriteGuard_locks_Tally(_1: rt.TryLockError>): void { if (_1.tag === "Poisoned") { rt.refMutRelease((_1 as { tag: "Poisoned"; _0: rt.RefCell })._0); } } +export function drop$sync_TryLockError_sync_RwLockWriteGuard_u32(_1: rt.TryLockError>): void { + if (_1.tag === "Poisoned") { + rt.refMutRelease((_1 as { tag: "Poisoned"; _0: rt.RefCell })._0); + } +} + export function drop$tuple2_Option_cell_Ref_borrows_Tagged_Option_cell_RefMut_borrows_Tagged(_1: [rt.RefCell | undefined, rt.RefCell | undefined]): void { drop$Option_cell_Ref_borrows_Tagged(_1[0]); drop$Option_cell_RefMut_borrows_Tagged(_1[1]); diff --git a/ts/fixtures/lang/expected/locks.ts b/ts/fixtures/lang/expected/locks.ts index 74de192f5..d893a4317 100644 --- a/ts/fixtures/lang/expected/locks.ts +++ b/ts/fixtures/lang/expected/locks.ts @@ -1,10 +1,10 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { drop$Result_Result_sync_MappedMutexGuard_u64_sync_MappedMutexGuard_u64_sync_MutexGuard_locks_Tally, drop$Result_Result_sync_MappedRwLockWriteGuard_u64_sync_MappedRwLockWriteGuard_u64_sync_RwLockWriteGuard_locks_Tally, drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally, drop$Result_sync_MutexGuard_u32_sync_TryLockError_sync_MutexGuard_u32, drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally, drop$Result_sync_RwLockWriteGuard_locks_Tally_sync_TryLockError_sync_RwLockWriteGuard_locks_Tally } from "./lib.ts"; +import { drop$Result_Result_sync_MappedMutexGuard_u64_sync_MappedMutexGuard_u64_sync_MutexGuard_locks_Tally, drop$Result_Result_sync_MappedRwLockWriteGuard_u64_sync_MappedRwLockWriteGuard_u64_sync_RwLockWriteGuard_locks_Tally, drop$Result_sync_MutexGuard_array4_u8_sync_TryLockError_sync_MutexGuard_array4_u8, drop$Result_sync_MutexGuard_locks_Tally_sync_TryLockError_sync_MutexGuard_locks_Tally, drop$Result_sync_MutexGuard_u32_sync_TryLockError_sync_MutexGuard_u32, drop$Result_sync_RwLockReadGuard_array4_u8_sync_TryLockError_sync_RwLockReadGuard_array4_u8, drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally, drop$Result_sync_RwLockReadGuard_u32_sync_TryLockError_sync_RwLockReadGuard_u32, drop$Result_sync_RwLockWriteGuard_array4_u8_sync_TryLockError_sync_RwLockWriteGuard_array4_u8, drop$Result_sync_RwLockWriteGuard_locks_Tally_sync_TryLockError_sync_RwLockWriteGuard_locks_Tally, drop$Result_sync_RwLockWriteGuard_u32_sync_TryLockError_sync_RwLockWriteGuard_u32 } from "./lib.ts"; import { thenSome$0sf2w1i, thenSome$0d5am30 } from "./core/bool.ts"; import { isSomeAnd$09tpccf, unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr } from "./core/option.ts"; -import { map$02txiwz as result_map$02txiwz, map$1g6jln5 as result_map$1g6jln5, mapOr$0666x4n as result_mapOr$0666x4n } from "./core/result.ts"; +import { map$02txiwz as result_map$02txiwz, map$1g6jln5 as result_map$1g6jln5, mapOr$0666x4n as result_mapOr$0666x4n, mapOr$1qkf3ab as result_mapOr$1qkf3ab } from "./core/result.ts"; import * as locks_Lock from "./locks.Lock.ts"; import * as locks_Tally from "./locks.Tally.ts"; @@ -587,4 +587,187 @@ export function printed(data: rt.ByteSlice, out: rt.ByteVec): void { rt.refMutRelease(text5); } +/** + * Each byte an operation on read-write locks through write guards downgraded, which keep + * the lock read, and guards mapped to the whole of the bytes a lock holds, its value the + * operand; each puts what it read through them and whether each lock is still held. + */ +export function downgraded(data: rt.ByteSlice, out: rt.ByteVec): void { + const lock: rt.RefCell = new rt.RefCell(locks_Tally.default$Default()); + const number: rt.RefCell = new rt.RefCell(0); + const quad: rt.RefCell = new rt.RefCell(Uint8Array.of(1, 2, 3, 4)); + const mutex: rt.Mutex = new rt.Mutex(Uint8Array.of(5, 6, 7, 8)); + for (const op of rt.byteSliceView(data)) { + const _16: rt.u8 = rt.u8Rem(op, 4); + if (_16 === 0) { + const write: rt.RefCell = rt.rwLockWrite(lock); + write.value.n = rt.u64Add(write.value.n, op as rt.u64); + rt.byteVecPush(write.value.seen, op); + const read: rt.RefCell = rt.rwLockDowngrade(write); + rt.byteVecPush(out, held$locks_Tally(lock)); + rt.byteVecPush(out, result_mapOr$1qkf3ab(rt.rwLockTryRead(lock), 254, (t2: rt.RefCell): rt.u8 => { + const _0: rt.u8 = t2.value.seen.length & 0xff; + rt.refRelease(t2); + return _0; + })); + rt.byteVecPush(out, rt.u64Low(read.value.n) & 0xff); + rt.refRelease(read); + rt.byteVecPush(out, held$locks_Tally(lock)); + } else if (_16 === 1) { + const write2: rt.RefCell = rt.rwLockWrite(number); + write2.value = rt.u32Add(write2.value, op as rt.u32); + const read2: rt.RefCell = rt.rwLockDowngrade(write2); + const other: rt.RefCell = rt.rwLockRead(number); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of((read2.value & 0xff) ^ (other.value & 0xff), held$u32(number)))); + if (!((op & 16) === 0)) { + rt.refRelease(other); + rt.byteVecPush(out, held$u32(number)); + rt.refRelease(read2); + } else { + rt.refRelease(read2); + rt.byteVecPush(out, held$u32(number)); + rt.refRelease(other); + } + rt.byteVecPush(out, held$u32(number)); + } else if (_16 === 2) { + const _90: rt.RefCell = rt.rwLockWrite(quad); + const f = (q: rt.ByteSlice): rt.ByteSlice => { + return q; + }; + const bytes: rt.RefCell = rt.refMutMap(_90, rt.partPlace(rt.byteSliceView(((held) => f(rt.byteSliceFrom(held)))(_90.value)))); + const _93: rt.ByteSlice = rt.byteSliceFrom(bytes.value); + const _95: rt.usize = (op & 3) as rt.usize; + if (!(_95 < 4)) { + rt.panic("index out of bounds: the len is " + 4 + " but the index is " + _95); + } + _93.data[_93.start + _95] = _93.data[_93.start + _95] ^ op; + rt.byteVecPush(out, held$array4_u8(quad)); + const _102: rt.RefCell = bytes; + const f2 = (q2: rt.ByteSlice): rt.ByteSlice => { + return q2; + }; + const again: rt.RefCell = rt.refMutMap(_102, rt.partPlace(rt.byteSliceView(((held2) => f2(rt.byteSliceFrom(held2)))(_102.value)))); + if ((op >> 4) === 15) { + const _107: rt.ByteSlice = rt.byteSliceFrom(again.value); + _107.data.set(rt.bytes(4).fill(op), _107.start); + } + rt.refMutRelease(again); + rt.byteVecPush(out, held$array4_u8(quad)); + const _115: rt.RefCell = rt.rwLockRead(quad); + const f3 = (q3: rt.ByteSlice): rt.ByteSlice => { + return q3; + }; + const read3: rt.RefCell = rt.refMap(_115, rt.byteSliceView(((held3) => f3(rt.byteSliceFrom(held3)))(_115.value))); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(read3.value)); + rt.byteVecPush(out, held$array4_u8(quad)); + rt.refRelease(read3); + const _127: rt.Mutex = rt.mutexLock(mutex); + const f4 = (q4: rt.ByteSlice): rt.ByteSlice => { + return q4; + }; + const bytes2: rt.Mutex = rt.mutexMap(_127, rt.partPlace(rt.byteSliceView(((held4) => f4(rt.byteSliceFrom(held4)))(_127.value)))); + rt.byteSliceRotateLeft(rt.byteSliceFrom(bytes2.value), 1); + if ((op >> 4) === 14) { + const _138: rt.ByteSlice = rt.byteSliceFrom(bytes2.value); + _138.data.set(rt.bytes(4).fill(op), _138.start); + } + const _144: rt.Result, rt.TryLockError>> = rt.mutexTryLock(mutex); + rt.byteVecPush(out, !_144.ok ? 1 : 0); + drop$Result_sync_MutexGuard_array4_u8_sync_TryLockError_sync_MutexGuard_array4_u8(_144); + rt.mutexUnlock(bytes2); + const _152: rt.Mutex = rt.mutexLock(mutex); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(_152.value)); + rt.mutexUnlock(_152); + } else { + const _159: rt.RefCell = rt.rwLockDowngrade(rt.rwLockWrite(lock)); + const seen: rt.RefCell = rt.refMap(_159, ((t: locks_Tally.Tally): rt.ByteVec => { + return t.seen; + })(_159.value)); + rt.byteVecPush(out, held$locks_Tally(lock)); + rt.byteVecPush(out, seen.value.length & 0xff); + rt.refRelease(seen); + rt.byteVecPush(out, held$locks_Tally(lock)); + const write4: rt.RefCell = rt.rwLockWrite(quad); + const _177: rt.ByteSlice = rt.byteSliceFrom(write4.value); + const _175: rt.u8 = rt.u8WrappingAdd(_177.data[_177.start], op); + const _181: rt.ByteSlice = rt.byteSliceFrom(write4.value); + _181.data[_181.start] = _175; + const read4: rt.RefCell = rt.rwLockDowngrade(write4); + const _192: rt.ByteSlice = rt.byteSliceFrom(read4.value); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of(_192.data[_192.start], held$array4_u8(quad)))); + rt.refRelease(read4); + } + } + rt.byteVecPush(out, held$locks_Tally(lock)); + const last: locks_Tally.Tally = lock.value; + rt.byteVecPush(out, rt.u64Low(last.n) & 0xff); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(last.seen)); + rt.byteVecPush(out, number.value & 0xff); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(quad.value)); + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(mutex.value)); +} + +/** Whether a read of `lock` would block, as 2, and whether a write would, as 1. */ +export function held$array4_u8(lock: rt.RefCell): rt.u8 { + let read!: boolean; + let write!: boolean; + const _3: rt.Result, rt.TryLockError>> = rt.rwLockTryRead(lock); + if (!_3.ok) { + read = true; + } else { + read = false; + } + drop$Result_sync_RwLockReadGuard_array4_u8_sync_TryLockError_sync_RwLockReadGuard_array4_u8(_3); + const _7: rt.Result, rt.TryLockError>> = rt.rwLockTryWrite(lock); + if (!_7.ok) { + write = true; + } else { + write = false; + } + drop$Result_sync_RwLockWriteGuard_array4_u8_sync_TryLockError_sync_RwLockWriteGuard_array4_u8(_7); + return ((((read ? 1 : 0)) << 1) & 0xff) | ((write ? 1 : 0)); +} + +/** Whether a read of `lock` would block, as 2, and whether a write would, as 1. */ +export function held$locks_Tally(lock: rt.RefCell): rt.u8 { + let read!: boolean; + let write!: boolean; + const _3: rt.Result, rt.TryLockError>> = rt.rwLockTryRead(lock); + if (!_3.ok) { + read = true; + } else { + read = false; + } + drop$Result_sync_RwLockReadGuard_locks_Tally_sync_TryLockError_sync_RwLockReadGuard_locks_Tally(_3); + const _7: rt.Result, rt.TryLockError>> = rt.rwLockTryWrite(lock); + if (!_7.ok) { + write = true; + } else { + write = false; + } + drop$Result_sync_RwLockWriteGuard_locks_Tally_sync_TryLockError_sync_RwLockWriteGuard_locks_Tally(_7); + return ((((read ? 1 : 0)) << 1) & 0xff) | ((write ? 1 : 0)); +} + +/** Whether a read of `lock` would block, as 2, and whether a write would, as 1. */ +export function held$u32(lock: rt.RefCell): rt.u8 { + let read!: boolean; + let write!: boolean; + const _3: rt.Result, rt.TryLockError>> = rt.rwLockTryRead(lock); + if (!_3.ok) { + read = true; + } else { + read = false; + } + drop$Result_sync_RwLockReadGuard_u32_sync_TryLockError_sync_RwLockReadGuard_u32(_3); + const _7: rt.Result, rt.TryLockError>> = rt.rwLockTryWrite(lock); + if (!_7.ok) { + write = true; + } else { + write = false; + } + drop$Result_sync_RwLockWriteGuard_u32_sync_TryLockError_sync_RwLockWriteGuard_u32(_7); + return ((((read ? 1 : 0)) << 1) & 0xff) | ((write ? 1 : 0)); +} + //# sourceMappingURL=locks.ts.map diff --git a/ts/fixtures/lang/src/borrows.rs b/ts/fixtures/lang/src/borrows.rs index aa759f971..5aaba630e 100644 --- a/ts/fixtures/lang/src/borrows.rs +++ b/ts/fixtures/lang/src/borrows.rs @@ -4,7 +4,8 @@ //! cloned, written through `get_mut`, taken apart, tried, and borrowed through mapped and //! cloned guards, each released as it drops; guards mapped, filtered, and split to parts //! JS copies and parts it doesn't, written through, released as the last drops; and -//! guards tried on their parts, given back beside an error, and printed. +//! guards tried on their parts, given back beside an error, and printed; and guards leaked, +//! which keep the cell borrowed until `undo_leak`, and mapped to the whole of its bytes. use std::cell::{Cell, Ref, RefCell, RefMut}; use std::sync::{Arc, Mutex}; @@ -601,6 +602,103 @@ pub fn tries(data: &[u8], out: &mut Vec) { flush(&log, out); } +/// Each byte an operation on `RefCell`s through guards leaked, which keep the cell borrowed +/// until `undo_leak`, and guards mapped, filtered, and tried to the whole of the bytes a +/// cell holds, its value the operand; each puts what it read through them and whether the +/// cell is still borrowed, and borrows again while a leak holds it, which panics. +pub fn leaks(data: &[u8], out: &mut Vec) { + let mut pair = RefCell::new((0u16, Vec::new())); + let mut number = RefCell::new(0u32); + let mut quad = RefCell::new([1u8, 2, 3, 4]); + for &op in data { + match op % 4 { + // Leaked shared, whole and mapped, so another shared borrow works until it is undone. + 0 => { + let whole = Ref::leak(pair.borrow()); + let other = pair.borrow(); + out.extend_from_slice(&[whole.0 as u8 ^ other.1.len() as u8, borrowed(&pair)]); + drop(other); + let bytes = Ref::leak(Ref::map(pair.borrow(), |p| &p.1)); + out.extend_from_slice(bytes); + out.push(borrowed(&pair)); + pair.undo_leak(); + out.push(borrowed(&pair)); + } + // Leaked exclusive, to a number JS copies and to a value it doesn't, written + // through and whole, until it is undone. + 1 => { + let n = RefMut::leak(number.borrow_mut()); + *n += u32::from(op); + out.push(borrowed(&number)); + *number.undo_leak() ^= 1; + out.extend_from_slice(&[borrowed(&number), *number.borrow() as u8]); + let whole = RefMut::leak(pair.borrow_mut()); + whole.0 += u16::from(op); + whole.1.push(op); + if op >> 4 == 0xf { + *whole = (u16::from(op), vec![op]); + } + out.push(borrowed(&pair)); + pair.undo_leak().1.push(0xfc); + out.push(borrowed(&pair)); + let bytes = RefMut::leak(RefMut::map(pair.borrow_mut(), |p| &mut p.1)); + bytes.push(op); + out.push(borrowed(&pair)); + out.extend_from_slice(&pair.get_mut().1); + } + // The whole of the bytes a cell holds, written through a guard mapped to them and + // that one's map, written whole, read through a shared one's, filtered, tried, + // and leaked. + 2 => { + let mut bytes = RefMut::map(quad.borrow_mut(), |q| q); + bytes[usize::from(op & 3)] ^= op; + let mut again = RefMut::map(bytes, |q| q); + if op >> 4 == 0xf { + *again = [op; 4]; + } + out.push(borrowed(&quad)); + drop(again); + let read = Ref::map(quad.borrow(), |q| q); + out.extend_from_slice(&*read); + out.push(borrowed(&quad)); + drop(read); + match RefMut::filter_map(quad.borrow_mut(), |q| (q[0] & 1 == 0).then_some(q)) { + Ok(mut q) => { + q[1] = q[1].wrapping_add(op); + out.push(borrowed(&quad)); + } + Err(whole) => out.push(whole[0] ^ borrowed(&quad)), + } + match Ref::try_map(quad.borrow(), |q| if q[2] & 1 == 0 { Ok(q) } else { Err(q[2]) }) { + Ok(q) => out.extend_from_slice(&*q), + Err((whole, e)) => out.push(whole[3] ^ e), + } + let leaked = RefMut::leak(RefMut::map(quad.borrow_mut(), |q| q)); + leaked.rotate_left(1); + out.push(borrowed(&quad)); + out.extend_from_slice(quad.undo_leak()); + } + // Borrowed again while a leak holds it, which panics. + _ => match op >> 4 { + 0xe => { + let _ = Ref::leak(pair.borrow()); + let _w = pair.borrow_mut(); + } + 0xf => { + let _ = RefMut::leak(number.borrow_mut()); + let _r = number.borrow(); + } + _ => out.push(borrowed(&pair)), + }, + } + } + let last = pair.into_inner(); + out.push(last.0 as u8); + out.extend_from_slice(&last.1); + out.push(number.into_inner() as u8); + out.extend_from_slice(&quad.into_inner()); +} + /// Whether `cell` is borrowed exclusively, as 2, shared, as 1, or not at all. fn borrowed(cell: &RefCell) -> u8 { match (cell.try_borrow().is_err(), cell.try_borrow_mut().is_err()) { diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 11c181f32..54e6b1f4f 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -282,6 +282,8 @@ fn cases() -> Vec<(&'static str, Case)> { ("capacity::deques", capacity::deques), ("capacity::heaps", capacity::heaps), ("capacity::collects", capacity::collects), + ("locks::downgraded", |_, data, out| locks::downgraded(data, out)), + ("borrows::leaks", |_, data, out| borrows::leaks(data, out)), ] } diff --git a/ts/fixtures/lang/src/lib.rs b/ts/fixtures/lang/src/lib.rs index 561911493..47fc86bec 100644 --- a/ts/fixtures/lang/src/lib.rs +++ b/ts/fixtures/lang/src/lib.rs @@ -26,6 +26,7 @@ //! `fmt::Fault` an error only `caller.ts` prints. #![feature( + cell_leak, iter_order_by, mapped_lock_guards, once_cell_try, diff --git a/ts/fixtures/lang/src/locks.rs b/ts/fixtures/lang/src/locks.rs index ce78e20d1..a192bb1a2 100644 --- a/ts/fixtures/lang/src/locks.rs +++ b/ts/fixtures/lang/src/locks.rs @@ -4,7 +4,8 @@ //! while held, which panics as wasm32's does; and read-write locks, read and written, //! tried while read and while written, written through `get_mut`, taken apart, and read //! and written through guards mapped and filtered to parts of their values, a mutex's -//! included; and each lock's guards printed. +//! included; write guards downgraded, which keep the lock read; guards mapped to the whole +//! of the bytes a lock holds; and each lock's guards printed. use std::fmt; use std::sync::{ @@ -369,3 +370,100 @@ pub fn printed(data: &[u8], out: &mut Vec) { let text = MappedRwLockWriteGuard::map(text, |t| t); out.extend_from_slice(format!("{text:>12}").as_bytes()); } + +/// Each byte an operation on read-write locks through write guards downgraded, which keep +/// the lock read, and guards mapped to the whole of the bytes a lock holds, its value the +/// operand; each puts what it read through them and whether each lock is still held. +pub fn downgraded(data: &[u8], out: &mut Vec) { + let lock = RwLock::new(Tally::default()); + let number = RwLock::new(0u32); + let quad = RwLock::new([1u8, 2, 3, 4]); + let mutex = Mutex::new([5u8, 6, 7, 8]); + for &op in data { + match op % 4 { + // Written, then downgraded, so a write still fails while a read shares it. + 0 => { + let mut write = lock.write().unwrap(); + write.n += u64::from(op); + write.seen.push(op); + let read = RwLockWriteGuard::downgrade(write); + out.push(held(&lock)); + out.push(lock.try_read().map_or(0xfe, |t| t.seen.len() as u8)); + out.push(read.n as u8); + drop(read); + out.push(held(&lock)); + } + // A number JS copies, downgraded while another read waits, and released as the + // last read drops in either order. + 1 => { + let mut write = number.write().unwrap(); + *write += u32::from(op); + let read = RwLockWriteGuard::downgrade(write); + let other = number.read().unwrap(); + out.extend_from_slice(&[*read as u8 ^ *other as u8, held(&number)]); + if op & 0x10 == 0 { + drop(read); + out.push(held(&number)); + drop(other); + } else { + drop(other); + out.push(held(&number)); + drop(read); + } + out.push(held(&number)); + } + // The whole of the bytes a lock holds, written through a write guard mapped to + // them and that one's map, written whole, and read through a read guard's map. + 2 => { + let mut bytes = RwLockWriteGuard::map(quad.write().unwrap(), |q| q); + bytes[usize::from(op & 3)] ^= op; + out.push(held(&quad)); + let mut again = MappedRwLockWriteGuard::map(bytes, |q| q); + if op >> 4 == 0xf { + *again = [op; 4]; + } + drop(again); + out.push(held(&quad)); + let read = RwLockReadGuard::map(quad.read().unwrap(), |q| q); + out.extend_from_slice(&*read); + out.push(held(&quad)); + drop(read); + let mut bytes = MutexGuard::map(mutex.lock().unwrap(), |q| q); + bytes.rotate_left(1); + if op >> 4 == 0xe { + *bytes = [op; 4]; + } + out.push(mutex.try_lock().is_err() as u8); + drop(bytes); + out.extend_from_slice(&*mutex.lock().unwrap()); + } + // Downgraded, then mapped to a part, whose drop releases the lock. + _ => { + let write = lock.write().unwrap(); + let seen = RwLockReadGuard::map(RwLockWriteGuard::downgrade(write), |t| &t.seen); + out.push(held(&lock)); + out.push(seen.len() as u8); + drop(seen); + out.push(held(&lock)); + let mut write = quad.write().unwrap(); + write[0] = write[0].wrapping_add(op); + let read = RwLockWriteGuard::downgrade(write); + out.extend_from_slice(&[read[0], held(&quad)]); + } + } + } + out.push(held(&lock)); + let last = lock.into_inner().unwrap(); + out.push(last.n as u8); + out.extend_from_slice(&last.seen); + out.push(number.into_inner().unwrap() as u8); + out.extend_from_slice(&quad.into_inner().unwrap()); + out.extend_from_slice(&mutex.into_inner().unwrap()); +} + +/// Whether a read of `lock` would block, as 2, and whether a write would, as 1. +fn held(lock: &RwLock) -> u8 { + let read = matches!(lock.try_read(), Err(TryLockError::WouldBlock)); + let write = matches!(lock.try_write(), Err(TryLockError::WouldBlock)); + u8::from(read) << 1 | u8::from(write) +} diff --git a/ts/runtime/rt/task.ts b/ts/runtime/rt/task.ts index 8304317b9..b600f31ca 100644 --- a/ts/runtime/rt/task.ts +++ b/ts/runtime/rt/task.ts @@ -461,6 +461,12 @@ export function rwLockTryWrite(lock: RefCell): Result, TryLockE return Ok(lock); } +/** `RwLockWriteGuard::downgrade`: `guard`, a lock's own, still holding it, now shared. */ +export function rwLockDowngrade(guard: RefCell): RefCell { + guard.borrows = 1; + return guard; +} + /** A guard a map of a cell's guard made: a `Mapped` of a part of its value, whose borrow that cell counts. */ export class RefMap extends Mapped implements RefCell { /** A cell's count, which a guard's cell keeps instead. */ @@ -570,6 +576,12 @@ export function refCellTryBorrowMut(cell: RefCell): Result, Bor return Ok(cell); } +/** `RefCell::undo_leak`: `cell`, borrowed no longer, as the guards it leaked are forgotten. */ +export function refCellUndoLeak(cell: RefCell): RefCell { + cell.borrows = 0; + return cell; +} + /** `Debug` of `BorrowError`. */ export function borrowErrorDebug(_: BorrowError, f: Formatter): FmtResult { return f.out.writeStr("BorrowError"); diff --git a/ts/src/lower/lend.rs b/ts/src/lower/lend.rs index 199930174..e0029fb6a 100644 --- a/ts/src/lower/lend.rs +++ b/ts/src/lower/lend.rs @@ -87,6 +87,8 @@ pub fn plan<'tcx>(t: &Translator<'_, 'tcx>, def_id: DefId, args: GenericArgsRef< return Plan::Untouched; } } + // A guard's map views each `&[u8; N]` part its closure returns, as `task::guard_map` does. + let views_parts = library::key(tcx, def_id).is_some_and(|(owner, name)| library::maps_guard(&owner, &name)); let sig = Sig::of(t, def_id, args); let mut lent = Lent { closures: Vec::new(), @@ -107,7 +109,7 @@ pub fn plan<'tcx>(t: &Translator<'_, 'tcx>, def_id: DefId, args: GenericArgsRef< // What the closure returns std holds as it is given it, and a closure std takes by // reference is no value to wrap. let returned = match output { - Some((g, out)) => lends(t, g, out) == Some(false), + Some((g, out)) => views_parts || lends(t, g, out) == Some(false), None => false, }; if !returned || by_ref { diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index a37cd15f0..8b47f8dcd 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -907,11 +907,9 @@ fn discards(owner: &str, name: &str) -> Option<&'static [usize]> { // `get_or_init::` drops its function uncalled if the cell is set. ("OnceCell" | "OnceLock", "get_or_init" | "get_or_try_init") => &[1], // A guard's map calls its function, filtered or not. - ( - "Ref" | "RefMut" | "MutexGuard" | "MappedMutexGuard" | "RwLockReadGuard" | "RwLockWriteGuard" - | "MappedRwLockReadGuard" | "MappedRwLockWriteGuard", - "map" | "filter_map" | "try_map" | "map_split", - ) => &[], + _ if maps_guard(owner, name) => &[], + // A guard it leaks or downgrades keeps its borrow or lock. + ("Ref" | "RefMut", "leak") | ("RwLockWriteGuard", "downgrade") => &[], ("Once", "call_once" | "call_once_force") => &[0], ("Arc" | "Rc", "new_cyclic") => &[1], // `pop_if::` drops its test uncalled if there is no element. @@ -923,6 +921,24 @@ fn discards(owner: &str, name: &str) -> Option<&'static [usize]> { }) } +/// Whether `owner::name` is a guard's map, filtered, tried, or split, to parts of what it lends. +pub fn maps_guard(owner: &str, name: &str) -> bool { + matches!( + (owner, name), + ( + "Ref" + | "RefMut" + | "MutexGuard" + | "MappedMutexGuard" + | "RwLockReadGuard" + | "RwLockWriteGuard" + | "MappedRwLockReadGuard" + | "MappedRwLockWriteGuard", + "map" | "filter_map" | "try_map" | "map_split", + ) + ) +} + /// Whether the mapping of `owner::name` tells an `Option`'s `None` from its `Some` where /// the `None` is `rt.None`, as the runtime's functions that take `None` to be `undefined` /// do not. diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index cf08209fb..b39c6cdeb 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -341,7 +341,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // Mappings that lend an element JS copies as its place, as a map's `get_mut` does, the // item a `Peekable` peeked, the place a `&mut dyn Any` wraps, a `RefCell` key's value, // a `Result`'s payload, the value of an `Arc`, a lock, or a cell, or what a write guard - // lends its map. + // lends its map or its leak. let lends = match library::key(tcx, def_id) { Some((owner, name)) => match (owner.as_str(), name.as_str()) { ("slice", "get_mut" | "first_mut" | "last_mut" | "iter_mut" | "split_first_mut" | "split_last_mut") @@ -352,6 +352,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { | ("Result", "iter_mut") | ("Arc" | "Rc", "get_mut" | "make_mut") | ("Mutex" | "RwLock" | "RefCell" | "OnceCell" | "OnceLock", "get_mut") + | ("RefMut", "leak") + | ("RefCell", "undo_leak") | ( "RefMut" | "MutexGuard" | "MappedMutexGuard" | "RwLockWriteGuard" | "MappedRwLockWriteGuard", "map" | "filter_map" | "try_map" | "map_split", diff --git a/ts/src/lower/task.rs b/ts/src/lower/task.rs index 2db6087ed..09a6b7d82 100644 --- a/ts/src/lower/task.rs +++ b/ts/src/lower/task.rs @@ -260,6 +260,10 @@ pub fn call<'tcx>( ("RefCell", "borrow_mut") => Expr::rt("refCellBorrowMut", args), ("RefCell", "try_borrow") => Expr::rt("refCellTryBorrow", args), ("RefCell", "try_borrow_mut") => Expr::rt("refCellTryBorrowMut", args), + // A leaked guard lends what it holds and keeps its borrow until `undo_leak`. + ("Ref", "leak") => first(args).member("value"), + ("RefMut", "leak") => lend_value(b, call, first(args)), + ("RefCell", "undo_leak") => lend_value(b, call, Expr::rt("refCellUndoLeak", args)), ("Ref" | "RwLockReadGuard" | "MappedRwLockReadGuard", "map" | "filter_map" | "try_map" | "map_split") => { guard_map(b, call, name, "ref", args, out) } @@ -280,6 +284,7 @@ pub fn call<'tcx>( ("RwLock", "write") => Expr::result(true, Expr::rt("rwLockWrite", args)), ("RwLock", "try_read") => Expr::rt("rwLockTryRead", args), ("RwLock", "try_write") => Expr::rt("rwLockTryWrite", args), + ("RwLockWriteGuard", "downgrade") => Expr::rt("rwLockDowngrade", args), ("Cell", _) => return cell(b, call, name, args, out), // A thread-local's value, which is never destroyed. ("LocalKey", "with") => { @@ -667,10 +672,10 @@ fn guard_map<'tcx>( } } -/// What `get_mut` of `holder`, a `Mutex`, a `RefCell`, or an `RwLock`, lends: its value, -/// or where that is where JS copies it. +/// What `get_mut` of `holder`, a `Mutex`, a `RefCell`, or an `RwLock`, or a leak of a +/// `RefMut`, lends: its value, or where that is where JS copies it. fn lend_value<'tcx>(b: &Body<'_, '_, 'tcx>, call: &Call<'tcx>, holder: Expr) -> Expr { - match b.t.needs_place(call.args.type_at(0)) { + match b.t.needs_place(call.args.types().next().expect("a held type")) { true => Expr::rt("valuePlace", vec![holder]), false => holder.member("value"), } From b296e69afd6a0588c92e52f4d6315b3247713e17 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 13:54:55 -0800 Subject: [PATCH 3/3] chore(ts): regenerate the lang snapshot after merging main Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/harness.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index b73620da1..b208d2561 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -5,7 +5,7 @@ import { args } from "./args.ts"; import { fromFns, maps as arrays_maps, lent as arrays_lent, asSlices, given, flattened as arrays_flattened } from "./arrays.ts"; import { bytes as ascii_bytes, chars as ascii_chars, slices as ascii_slices, parts as ascii_parts } from "./ascii.ts"; import { atomics } from "./atomics.ts"; -import { borrows as borrows_borrows, guards, maps as borrows_maps, tries } from "./borrows.ts"; +import { borrows as borrows_borrows, guards, maps as borrows_maps, tries, leaks } from "./borrows.ts"; import { bounds as bounds_bounds } from "./bounds.ts"; import { boxes, places as boxes_places } from "./boxes.ts"; import { vecs as capacity_vecs, deques as capacity_deques, heaps as capacity_heaps, collects, maps as capacity_maps } from "./capacity.ts"; @@ -39,7 +39,7 @@ import { keys as keys_keys, overrides, bytes as keys_bytes, lentKeys, lentValues import { lengths } from "./lengths.ts"; import { lists as lists_lists } from "./lists.ts"; import { locals } from "./locals.ts"; -import { locks, relock, rwlocks, mapped, mutexMapped, printed as locks_printed } from "./locks.ts"; +import { locks, relock, rwlocks, mapped, mutexMapped, printed as locks_printed, downgraded } from "./locks.ts"; import { u8s as methods_u8s, u16s as methods_u16s, u32s as methods_u32s, u64s as methods_u64s, u128s as methods_u128s, usizes as methods_usizes, i8s as methods_i8s, i16s as methods_i16s, i32s as methods_i32s, i64s as methods_i64s, i128s as methods_i128s, isizes as methods_isizes } from "./methods.ts"; import { nested as nested_nested } from "./nested.ts"; import { objects } from "./objects.ts"; @@ -273,7 +273,11 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) capacity_deques(a0153, a1153.data, a1153.start, a1153.length, a2153); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::heaps", (a0154: rt.u8, a1154: rt.ByteSlice, a2154: rt.ByteVec): undefined => { capacity_heaps(a0154, a1154.data, a1154.start, a1154.length, a2154); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::maps", capacity_maps as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::collects", collects as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["capacity::maps", capacity_maps as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["locks::downgraded", (_2196: rt.u8, data96: rt.ByteSlice, out97: rt.ByteVec): undefined => { + downgraded(data96, out97); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::leaks", (_2197: rt.u8, data97: rt.ByteSlice, out98: rt.ByteVec): undefined => { + leaks(data97, out98); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } /** Run one case. */