From 2fd4f48d6d5bd112f828fbbffcc0c884d26b531f Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 04:04:30 -0800 Subject: [PATCH 1/3] chore(quest): claim quest/m3/ts-boxed-slice-rest.md Quest-Claim: 7b6448a205b487f2 From 758616ca6ac91ae3f419496ba2649e982fb73038 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 04:51:20 -0800 Subject: [PATCH 2/3] feat(ts): translate the rest of a boxed slice's impls Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-boxed-slice-rest.md | 18 -- quest/m3/ts-ref-conversions.md | 7 +- ts/README.md | 2 +- ts/fixtures/lang/expected/boxes.ts | 325 ++++++++++++++++++++++++++- ts/fixtures/lang/expected/harness.ts | 8 +- ts/fixtures/lang/expected/lib.ts | 9 + ts/fixtures/lang/src/boxes.rs | 223 ++++++++++++++++++ ts/fixtures/lang/src/harness.rs | 1 + ts/runtime/rt.ts | 18 ++ ts/src/lower/library.rs | 72 +++++- ts/src/lower/mir/call.rs | 15 ++ 12 files changed, 664 insertions(+), 35 deletions(-) delete mode 100644 quest/m3/ts-boxed-slice-rest.md diff --git a/quest/m3/README.md b/quest/m3/README.md index a4f44d106..b82b4f8e0 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -32,5 +32,4 @@ generic std gap is planned here even when moq never calls it. - [std's sorts call their tests in std's order](/quest/m3/ts-sort-order.md) - `slice::sort` and a B-tree's `collect` call `lt` or `cmp` as std's driftsort does past 16 elements - [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - a B-tree's lookups and inserts call a key's own `Ord` in std's order and number - [The rest of a guard's maps](/quest/m3/ts-guard-maps.md) - `RefMut::map`, the guards' `filter_map` and `map_split`, and an `RwLock` guard's `map` translate -- [The rest of a boxed slice's impls](/quest/m3/ts-boxed-slice-rest.md) - `clone_from`, `AsRef`, `AsMut`, `TryFrom` into a boxed array, and `Box` into bytes translate for boxed slices - [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 diff --git a/quest/m3/ts-boxed-slice-rest.md b/quest/m3/ts-boxed-slice-rest.md deleted file mode 100644 index c9bf70a19..000000000 --- a/quest/m3/ts-boxed-slice-rest.md +++ /dev/null @@ -1,18 +0,0 @@ -# [S] The rest of a boxed slice's impls - -## Goal - -[rust2-ts](/ts/README.md) translates the `Box<[T]>` and `Box` impls still refused, -with std's semantics, proven by differential tests against native Rust. - -## Plan - -Found translating boxed slices: each of these refuses with its span as "unsupported call": - -- `Clone::clone_from` of a `Box<[T]>`, which std clones into in place when the lengths - match, by each element's `clone_from`, and otherwise replaces whole. -- `AsRef` and `AsMut` of a `Box`, for any `T`. -- `TryFrom>` and `TryFrom>` for `Box<[T; N]>`. -- `From>` for `Box<[u8]>`, and `str::into_boxed_bytes`. - -No consumer calls them yet; they are planned here as part of std. diff --git a/quest/m3/ts-ref-conversions.md b/quest/m3/ts-ref-conversions.md index 6e2521256..bf6cd98d5 100644 --- a/quest/m3/ts-ref-conversions.md +++ b/quest/m3/ts-ref-conversions.md @@ -6,8 +6,8 @@ copy, or measure what it points at, for any `T` and for `[u8; N]`, proven by differential tests against native Rust: `ToOwned::to_owned` and `clone_into`, `Clone::clone_from`, `slice::binary_search`, `mem::size_of_val` and `align_of_val`, `slice::from_mut`, -`array::from_ref` and `from_mut`, `AsRef`, `AsMut`, and `Borrow` of an array as a slice, -`Cell::from_mut`, and `ptr::from_ref` and `from_mut`. +`array::from_ref` and `from_mut`, `AsRef`, `AsMut`, and `Borrow` of an array or a `Vec` as +a slice, `Cell::from_mut`, and `ptr::from_ref` and `from_mut`. ## Plan @@ -18,6 +18,9 @@ shares the bytes, so reads, copies, and writes through it need nothing more. A p a `[u8; N]` is an `rt.ByteSlice` compared by the buffer and offset of its bytes, so `ptr::from_ref` of the view is `rt.byteSliceFrom` of it. +`Clone::clone_from` of a `Box` of a sized `T` forwards to `T`'s, so it is refused +until that translates. + No consumer calls them yet; they are planned here as part of std. Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index 83f33af81..b944f8e20 100644 --- a/ts/README.md +++ b/ts/README.md @@ -828,7 +828,7 @@ built natively for wasm32 and run beside its translation on the same inputs by - `lang` covers every integer width's arithmetic, casts, and inherent methods, with `NonZero`'s, `Wrapping`'s, and `Saturating`'s, at each width's edges, the ASCII methods of every byte, of chars either side of ASCII, Latin-1, and the surrogates, and of slices and strings, `char`s next to each - encoding's edges and the surrogates, `Cow` and `Cow<[T]>` borrowed and owned, `Box<[T]>` and `Box` made each way and turned back, `Wrapping` and `Saturating` at every width, `Duration`s whose + encoding's edges and the surrogates, `Cow` and `Cow<[T]>` borrowed and owned, `Box<[T]>` and `Box` made each way, cloned into, lent, and turned back, into a boxed array or bytes included, `Wrapping` and `Saturating` at every width, `Duration`s whose seconds pass 2^53 or reach `u64::MAX`, control flow, patterns, `Copy` aggregates, those a container stores included and those written into only in another module, behind a type parameter, or through a box, formatting, methods diff --git a/ts/fixtures/lang/expected/boxes.ts b/ts/fixtures/lang/expected/boxes.ts index 73c7f9a89..020a490ee 100644 --- a/ts/fixtures/lang/expected/boxes.ts +++ b/ts/fixtures/lang/expected/boxes.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { copy$array_tuple2_u8_u16, copy$array_u16, copy$array_u8, copy$tuple2_u8_u16 } from "./lib.ts"; +import { copy$array_tuple2_u8_u16, copy$array_u16, copy$array_u8, copy$tuple2_u8_u16, drop$Result_boxed_Box_array1_boxes_Logged_boxed_Box_slice_boxes_Logged } from "./lib.ts"; import * as boxes_Held from "./boxes.Held.ts"; import * as boxes_Logged from "./boxes.Logged.ts"; import type * as Owner from "./boxes.Owner.ts"; @@ -714,4 +714,327 @@ export function places(_1: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { } } +/** Clones `from` into `slot` by its `clone_from`. */ +export function refill$boxed_Box_slice_boxes_Logged(slot: rt.Place, from: boxes_Logged.Logged[]): void { + slot.v = rt.boxSliceCloneFrom(slot.v, from, boxes_Logged.clone$Clone, rt.drop); +} + +/** Clones `from` into `slot` by its `clone_from`. */ +export function refill$boxed_Box_slice_u8(slot: rt.Place, from: rt.ByteSlice): void { + slot.v = rt.byteBoxSliceCloneFrom(slot.v, from); +} + +/** The sum of the bytes `x` lends by its `AsRef`. */ +export function sum$boxed_Box_slice_u8(x: rt.ByteSlice): rt.u8 { + return rt.iterFold(rt.iterSlice(rt.byteSliceView(x)), 0, (a: rt.u8, _32: rt.u8): rt.u8 => { + return rt.u8WrappingAdd(a, _32); + }); +} + +/** Writes through what `x` lends by its `AsMut`. */ +export function flip$boxed_Box_slice_u8(x: rt.Place): void { + const iter: rt.Iter> = rt.iterSliceMut(rt.byteSliceView(x.v)); + while (true) { + const _5: rt.Place | rt.Done = iter.next(); + if (_5 === rt.DONE) { + break; + } + _5.v = _5.v ^ 15; + } +} + +/** Puts what boxing `r` into an array gave: its bytes, or a marker and what it gave back. */ +export function boxed$0_boxed_Box_slice_u8(r: rt.Result, out: rt.ByteVec): void { + if (r.ok) { + const a: Uint8Array = r.value; + const _5: rt.Place | undefined = rt.sliceFirstMut(rt.byteSliceView(rt.byteSliceFrom(a))); + if (_5 !== undefined) { + _5.v = _5.v ^ 255; + } + bytes(rt.byteSliceFrom(a), out); + } else { + const e: rt.ByteSlice = r.error; + rt.byteVecPush(out, 238); + bytes(e, out); + } +} + +/** Puts what boxing `r` into an array gave: its bytes, or a marker and what it gave back. */ +export function boxed$3_boxed_Box_slice_u8(r: rt.Result, out: rt.ByteVec): void { + if (r.ok) { + const a: Uint8Array = r.value; + const _5: rt.Place | undefined = rt.sliceFirstMut(rt.byteSliceView(rt.byteSliceFrom(a))); + if (_5 !== undefined) { + _5.v = _5.v ^ 255; + } + bytes(rt.byteSliceFrom(a), out); + } else { + const e: rt.ByteSlice = r.error; + rt.byteVecPush(out, 238); + bytes(e, out); + } +} + +/** Puts what boxing `r` into an array gave: its bytes, or a marker and what it gave back. */ +export function boxed$4_Vec_u8(r: rt.Result, out: rt.ByteVec): void { + if (r.ok) { + const a: Uint8Array = r.value; + const _5: rt.Place | undefined = rt.sliceFirstMut(rt.byteSliceView(rt.byteSliceFrom(a))); + if (_5 !== undefined) { + _5.v = _5.v ^ 255; + } + bytes(rt.byteSliceFrom(a), out); + } else { + const e: rt.ByteVec = r.error; + rt.byteVecPush(out, 238); + bytes(rt.byteVecSlice(e), out); + } +} + +/** + * The rest of a box's impls: `clone_from` of a boxed slice, into one of its length by + * each item's `clone_from` and into one of another replaced whole; `AsRef` and `AsMut` of + * a box of any value; `TryFrom` of a boxed slice and a `Vec` into a boxed array, of its + * length or not; and a boxed text's bytes. Each of bytes and of other values, empty or + * not, with what drops dropped in std's order. + */ +export function impls(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + const data2: rt.ByteSlice = rt.byteSliceRange(data, 0, rt.usizeRem(kind >> 3 as rt.usize, rt.usizeAdd(data.length, 1))); + const half: rt.usize = rt.usizeDiv(data2.length, 2); + const log: rt.Arc> = new rt.Arc(new rt.RefCell(new rt.ByteVec())); + const logged: (a0: rt.u8) => boxes_Logged.Logged = (id: rt.u8): boxes_Logged.Logged => { + return new boxes_Logged.Logged(id, rt.arcClone(log)); + }; + const _23: rt.u8 = rt.u8Rem(kind, 4); + if (_23 === 0) { + let a: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(rt.byteSliceRange(data2, 0, half))); + const b: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(rt.byteSliceRange(data2, half, rt.usizeMul(half, 2)))); + a = rt.byteBoxSliceCloneFrom(a, b); + const _36: rt.Place | undefined = rt.sliceFirstMut(rt.byteSliceView(a)); + if (_36 !== undefined) { + _36.v = _36.v ^ 255; + } + bytes(a, out); + bytes(b, out); + const _49: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(data2)); + refill$boxed_Box_slice_u8(rt.place(() => a, (value2) => a = value2), _49); + rt.byteSliceView(a).reverse(); + bytes(a, out); + let w: rt.u16[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, half))), (_216: rt.u8): rt.u16 => { + return rt.u16Mul(_216 as rt.u16, 257); + }), undefined, 2, true); + const v: rt.u16[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, 0, rt.usizeSub(data2.length, half)))), (_218: rt.u8): rt.u16 => { + return (((_218 as rt.u16)) << 3) & 0xffff; + }), undefined, 2, true); + w = rt.boxSliceCloneFrom(w, v); + w.reverse(); + wides(w, out); + wides(v, out); + const _82: rt.u16[] = [1, 2, 3]; + w = rt.boxSliceCloneFrom(w, _82); + wides(w, out); + let p: [rt.u8, rt.u16][] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, 0, half))), (_220: rt.u8): [rt.u8, rt.u16] => { + return [_220, 7] as [rt.u8, rt.u16]; + }), undefined, 4, true); + const q: [rt.u8, rt.u16][] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, half, rt.usizeMul(half, 2)))), (_221: rt.u8): [rt.u8, rt.u16] => { + return [_221 ^ 1, 9] as [rt.u8, rt.u16]; + }), undefined, 4, true); + p = rt.boxSliceCloneFrom(p, q, copy$tuple2_u8_u16); + const _102: [rt.u8, rt.u16] | undefined = rt.sliceFirst(p); + if (_102 !== undefined) { + _102[0] = 51; + _102[1] = 68; + } + const iter: rt.Iter<[rt.u8, rt.u16]> = rt.iterChain(rt.iterSlice(p), rt.iterSlice(q)); + while (true) { + const _113: [rt.u8, rt.u16] | rt.Done = iter.next(); + if (_113 === rt.DONE) { + break; + } + rt.byteVecPush(out, _113[0]); + rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(_113[1]).reverse()), undefined, true); + } + let s: string[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, 0, half))), (_222: rt.u8): string => { + return text(rt.byteSliceFrom(Uint8Array.of(_222))); + }), undefined, 12, true); + const t: string[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, half, rt.usizeMul(half, 2)))), (_223: rt.u8): string => { + const x11: rt.u8 = _223; + return text(rt.byteSliceFrom(Uint8Array.of(x11, x11))); + }), undefined, 12, true); + s = rt.boxSliceCloneFrom(s, t); + const _138: rt.Place | undefined = rt.sliceFirstMut(s); + if (_138 !== undefined) { + _138.v = _138.v + "!"; + } + const iter2: rt.Iter = rt.iterChain(rt.iterSlice(s), rt.iterSlice(t)); + while (true) { + const _150: string | rt.Done = iter2.next(); + if (_150 === rt.DONE) { + break; + } + fmt_put(out, _150); + } + let l: boxes_Logged.Logged[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, 0, half))), (_224: rt.u8): boxes_Logged.Logged => { + return logged(_224 & 126); + }), undefined, 8, true); + const m: boxes_Logged.Logged[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, half, rt.usizeMul(half, 2)))), (_226: rt.u8): boxes_Logged.Logged => { + return logged(_226 | 1); + }), undefined, 8, true); + flush(log.value, out); + l = rt.boxSliceCloneFrom(l, m, boxes_Logged.clone$Clone, rt.drop); + flush(log.value, out); + ids(l, out); + const _187: boxes_Logged.Logged[] = [logged(16), logged(18), logged(20)]; + refill$boxed_Box_slice_boxes_Logged(rt.place(() => l, (value3) => l = value3), _187); + rt.dropAll(_187); + flush(log.value, out); + ids(l, out); + const held: boxes_Held.Held = new boxes_Held.Held(rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data2, 0, rt.usizeSub(data2.length, half)))), (_227: rt.u8): boxes_Logged.Logged => { + return logged(_227 & 126); + }), undefined, 8, true), logged(80)); + flush(log.value, out); + held.items = rt.boxSliceCloneFrom(held.items, m, boxes_Logged.clone$Clone, rt.drop); + flush(log.value, out); + ids(held.items, out); + held[Symbol.dispose](); + flush(log.value, out); + const v2: rt.ByteSlice[] = [rt.byteSliceFrom(rt.byteSliceToArray(rt.byteSliceRange(data2, 0, half))), rt.byteSliceFrom(rt.byteSliceToArray(data2))]; + const at = rt.idx(v2, 1); + v2[at] = rt.byteBoxSliceCloneFrom(v2[at], b); + const at2 = rt.idx(v2, 0); + v2[at2] = rt.byteBoxSliceCloneFrom(v2[at2], a); + for (const _252 of v2) { + bytes(_252, out); + } + rt.dropAll(l); + rt.dropAll(m); + flush(log.value, out); + let t2: string = "ab"; + t2 = text(data2); + fmt_put(out, t2); + } else if (_23 === 1) { + let a2: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(data2)); + bytes(a2, out); + rt.byteSliceView(a2).reverse(); + flip$boxed_Box_slice_u8(rt.place(() => a2, (value) => a2 = value)); + rt.byteVecPush(out, sum$boxed_Box_slice_u8(a2)); + bytes(a2, out); + let n: rt.u32 = data2.length as rt.u32; + n = rt.u32Add(n, 7); + rt.iterExtendBytes(out, rt.iterSlice(rt.u32ToBeBytes(n).reverse()), undefined, true); + const pair: [rt.u8, rt.u16] = [option_unwrapOr$0r2bmpr(rt.byteSliceFirst(data2), 1), 2]; + const _306: [rt.u8, rt.u16] = pair; + _306[1] = _306[1] ^ 257; + const copy: [rt.u8, rt.u16] = copy$tuple2_u8_u16(pair); + pair[0] = 9; + rt.byteVecPush(out, copy[0]); + rt.iterExtendBytes(out, rt.iterSlice(rt.u16ToBeBytes(copy[1]).reverse()), undefined, true); + rt.byteVecPush(out, pair[0]); + const array: Uint8Array = Uint8Array.of(1, 2, 3, 4); + const _322: rt.ByteSlice = rt.byteSliceFrom(array); + const _324: rt.usize = rt.usizeRem(kind as rt.usize, 4); + if (!(_324 < 4)) { + rt.panic("index out of bounds: the len is " + 4 + " but the index is " + _324); + } + _322.data[_322.start + _324] = 85; + rt.byteSliceView(rt.byteSliceFrom(array)).reverse(); + bytes(rt.byteSliceFrom(array), out); + const v3: rt.ByteVec = rt.byteSliceToByteVec(data2); + rt.byteVecPush(v3, 102); + bytes(rt.byteVecSlice(v3), out); + let s2: string = text(data2); + s2 = s2 + "#"; + fmt_put(out, s2); + const w2: rt.u16[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data2)), (_28: rt.u8): rt.u16 => { + return rt.u16Add(_28 as rt.u16, 1); + }), undefined, 2, true); + rt.sliceSort(w2, rt.lt); + wides(w2, out); + const l2: boxes_Logged.Logged[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data2)), (_214: rt.u8): boxes_Logged.Logged => { + return logged(_214); + }), undefined, 8, true); + const _368: boxes_Logged.Logged[] = l2; + if (_368.length >= 2) { + const swapped = _368[0]; + _368[0] = _368[_368.length - 1]; + _368[_368.length - 1] = swapped; + } + ids(l2, out); + let o: rt.u8 | undefined = rt.byteSliceFirst(data2); + if (o !== undefined) { + o = rt.u8WrappingAdd(o, 1); + } + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(o, 238)); + rt.dropAll(l2); + } else if (_23 === 2) { + const _395: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(data2)); + boxed$3_boxed_Box_slice_u8(_395.length === 3 ? rt.Ok(rt.byteSliceToArray(_395)) : rt.Err(_395), out); + const _398: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(data2)); + boxed$0_boxed_Box_slice_u8(_398.length === 0 ? rt.Ok(rt.byteSliceToArray(_398)) : rt.Err(_398), out); + const _400: rt.ByteVec = rt.byteSliceToByteVec(data2); + const r: rt.Result = _400.length === 2 ? rt.Ok(rt.byteSliceToArray(rt.byteVecSlice(_400))) : rt.Err(_400); + if (r.ok) { + bytes(rt.byteSliceFrom(r.value), out); + } else { + const v4: rt.ByteVec = r.error; + rt.byteVecPush(v4, 119); + bytes(rt.byteVecSlice(v4), out); + } + const spare: rt.ByteVec = new rt.ByteVec(32); + rt.byteVecExtendFromSlice(spare, data2); + const _417: rt.ByteVec = spare; + boxed$4_Vec_u8(_417.length === 4 ? rt.Ok(rt.byteSliceToArray(rt.byteVecSlice(_417))) : rt.Err(_417), out); + const w3: rt.u16[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data2)), (_2: rt.u8): rt.u16 => { + return rt.u16Mul(_2 as rt.u16, 3); + }), undefined, 2, true); + const _422: rt.Result = w3.length === 2 ? rt.Ok(w3) : rt.Err(w3); + if (_422.ok) { + const a4: rt.u16[] = _422.value; + a4[1] = 4660; + wides(a4, out); + } else { + wides(_422.error, out); + } + const l3: boxes_Logged.Logged[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data2)), (_22: rt.u8): boxes_Logged.Logged => { + return logged(_22 & 126); + }), undefined, 8, true); + const _438: rt.Result = l3.length === 2 ? rt.Ok(l3) : rt.Err(l3); + if (_438.ok) { + const a5: boxes_Logged.Logged[] = _438.value; + ids(a5, out); + flush(log.value, out); + rt.dropAll(a5); + } else { + const l4: boxes_Logged.Logged[] = _438.error; + ids(l4, out); + flush(log.value, out); + rt.dropAll(l4); + } + flush(log.value, out); + const l5: boxes_Logged.Logged[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data2)), (_25: rt.u8): boxes_Logged.Logged => { + return logged(_25 | 1); + }), undefined, 8, true); + const r2: rt.Result = l5.length === 1 ? rt.Ok(l5) : rt.Err(l5); + flush(log.value, out); + drop$Result_boxed_Box_array1_boxes_Logged_boxed_Box_slice_boxes_Logged(r2); + flush(log.value, out); + } else { + const s3: string = text(data2); + const a6: rt.ByteSlice = rt.stringAsBytes(s3); + rt.byteSliceView(a6).reverse(); + bytes(a6, out); + const b2: rt.ByteSlice = rt.stringAsBytes(s3); + const _485: rt.Place | undefined = rt.sliceLastMut(rt.byteSliceView(b2)); + if (_485 !== undefined) { + _485.v = 33; + } + bytes(b2, out); + const v5: rt.ByteVec = rt.byteSliceToByteVec(rt.stringAsBytes(s3)); + rt.byteVecPush(v5, 46); + bytes(rt.byteVecSlice(v5), out); + bytes(rt.stringAsBytes(""), out); + } + log[Symbol.dispose](); +} + //# sourceMappingURL=boxes.ts.map diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 1b1bfd7d7..c2298a979 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -7,7 +7,7 @@ import { bytes as ascii_bytes, chars as ascii_chars, slices as ascii_slices, par import { atomics } from "./atomics.ts"; import { borrows as borrows_borrows, guards } from "./borrows.ts"; import { bounds as bounds_bounds } from "./bounds.ts"; -import { boxes, places as boxes_places } from "./boxes.ts"; +import { boxes, places as boxes_places, impls as boxes_impls } from "./boxes.ts"; import { captures } from "./captures.ts"; import { cells as cells_cells } from "./cells.ts"; import { chars as chars_chars } from "./chars.ts"; @@ -59,7 +59,7 @@ import { tuples as tuples_tuples } from "./tuples.ts"; import { decodes } from "./utf8.ts"; import { wrappers } from "./wrappers.ts"; import { u8s as wraps_u8s, u16s as wraps_u16s, u32s as wraps_u32s, u64s as wraps_u64s, u128s as wraps_u128s, usizes as wraps_usizes, i8s as wraps_i8s, i16s as wraps_i16s, i32s as wraps_i32s, i64s as wraps_i64s, i128s as wraps_i128s, isizes as wraps_isizes } from "./wraps.ts"; -import { zeros, impls, keys as zeros_keys, unread, writes as zeros_writes } from "./zeros.ts"; +import { zeros, impls as zeros_impls, keys as zeros_keys, unread, writes as zeros_writes } from "./zeros.ts"; /** * Each case's name and what it runs. The first byte picks one modulo their count, so a @@ -216,7 +216,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) arrays_lent(data70, out71); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["floats::mul_adds", floats_mulAdds as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["singles::mul_adds", singles_mulAdds as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::collections", (_2171: rt.u8, data71: rt.ByteSlice, out72: rt.ByteVec): undefined => { collections(data71, out72); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::unlike_eqs", unlikeEqs as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["zeros::impls", impls as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["zeros::keys", zeros_keys as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["keys::arrays", keys_arrays as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::zips", zips as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::zips_back", zipsBack as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::own_nes", ownNes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::iters", (_2172: rt.u8, data72: rt.ByteSlice, out73: rt.ByteVec): undefined => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::unlike_eqs", unlikeEqs as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["zeros::impls", zeros_impls as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["zeros::keys", zeros_keys as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["keys::arrays", keys_arrays as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::zips", zips as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::zips_back", zipsBack as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::own_nes", ownNes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fmt::iters", (_2172: rt.u8, data72: rt.ByteSlice, out73: rt.ByteVec): undefined => { fmt_iters(data72, out73); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["zeros::unread", unread as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::range_froms", rangeFroms as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::range_methods", rangeMethods as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["clones::iters", clones_iters as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::cell_eqs", cellEqs as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["iters::compares", compares as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::unlike_compares", unlikeCompares as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["collections::ref_orders", refOrders as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cycles::cycles", cycles as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["pointers::lapsed", (_2173: rt.u8, data73: rt.ByteSlice, out74: rt.ByteVec): undefined => { lapsed(data73, out74); @@ -254,7 +254,7 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) aimed(data88, out89); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["ascii::parts", ascii_parts as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["boxes::places", boxes_places as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["arrays::flattened", arrays_flattened as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["args::args", args as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["returns::returns", returns as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::guards", (_2189: rt.u8, data89: rt.ByteSlice, out90: rt.ByteVec): undefined => { guards(data89, out90); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["results::results", results_results as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fields::fields", fields_fields as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["strs::gets", gets 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], ["results::results", results_results as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["fields::fields", fields_fields as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["strs::gets", gets as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["boxes::impls", boxes_impls as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] 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 1c4afedfa..61387c61c 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -2,6 +2,7 @@ import * as rt from "./rt.ts"; import type * as borrows_Tagged from "./borrows.Tagged.ts"; +import type * as boxes_Logged from "./boxes.Logged.ts"; import type * as clones_Logged from "./clones.Logged.ts"; import type * as collections_Picky from "./collections.Picky.ts"; import type * as Quirky from "./collections.Quirky.ts"; @@ -808,6 +809,14 @@ export function drop$Option_cell_Ref_borrows_Tagged(_1: rt.RefCell): void { + if (_1.ok) { + rt.dropAll(_1.value); + } else { + rt.dropAll(_1.error); + } +} + export function drop$Result_cell_RefMut_Vec_u8_cell_BorrowMutError(_1: rt.Result, rt.BorrowMutError>): void { if (_1.ok) { rt.refMutRelease(_1.value); diff --git a/ts/fixtures/lang/src/boxes.rs b/ts/fixtures/lang/src/boxes.rs index dfae1e516..a6678ba6c 100644 --- a/ts/fixtures/lang/src/boxes.rs +++ b/ts/fixtures/lang/src/boxes.rs @@ -8,6 +8,7 @@ use std::borrow::Cow; use std::cell::RefCell; use std::hash::{DefaultHasher, Hash, Hasher}; +use std::ops::Deref; use std::rc::Rc; use crate::fmt::put; @@ -558,3 +559,225 @@ pub fn places(_: u8, data: &[u8], out: &mut Vec) { out.extend(boxed_opt.unwrap_or(0xee).to_le_bytes()); } } + +/// Clones `from` into `slot` by its `clone_from`. +fn refill(slot: &mut T, from: &T) { + slot.clone_from(from); +} + +/// The sum of the bytes `x` lends by its `AsRef`. +fn sum + ?Sized>(x: &T) -> u8 { + x.as_ref().iter().fold(0, |a, &b| a.wrapping_add(b)) +} + +/// Writes through what `x` lends by its `AsMut`. +fn flip + ?Sized>(x: &mut T) { + for b in x.as_mut() { + *b ^= 0x0f; + } +} + +/// Puts what boxing `r` into an array gave: its bytes, or a marker and what it gave back. +fn boxed>(r: Result, E>, out: &mut Vec) { + match r { + Ok(mut a) => { + if let Some(first) = a.first_mut() { + *first ^= 0xff; + } + bytes(&*a, out); + } + Err(e) => { + out.push(0xee); + bytes(&e, out); + } + } +} + +/// The rest of a box's impls: `clone_from` of a boxed slice, into one of its length by +/// each item's `clone_from` and into one of another replaced whole; `AsRef` and `AsMut` of +/// a box of any value; `TryFrom` of a boxed slice and a `Vec` into a boxed array, of its +/// length or not; and a boxed text's bytes. Each of bytes and of other values, empty or +/// not, with what drops dropped in std's order. +pub fn impls(kind: u8, data: &[u8], out: &mut Vec) { + let data = &data[..usize::from(kind >> 3) % (data.len() + 1)]; + let half = data.len() / 2; + let log = Rc::new(RefCell::new(Vec::new())); + let logged = |id: u8| Logged { id, log: log.clone() }; + match kind % 4 { + 0 => { + let mut a: Box<[u8]> = Box::from(&data[..half]); + let b: Box<[u8]> = Box::from(&data[half..half * 2]); + a.clone_from(&b); + if let Some(x) = a.first_mut() { + *x ^= 0xff; + } + bytes(&a, out); + bytes(&b, out); + refill(&mut a, &Box::from(data)); + a.reverse(); + bytes(&a, out); + let mut w: Box<[u16]> = data[half..].iter().map(|&x| u16::from(x) * 257).collect(); + let v: Box<[u16]> = data[..data.len() - half].iter().map(|&x| u16::from(x) << 3).collect(); + w.clone_from(&v); + w.reverse(); + wides(&w, out); + wides(&v, out); + w.clone_from(&Box::from([1, 2, 3])); + wides(&w, out); + let mut p: Box<[(u8, u16)]> = data[..half].iter().map(|&x| (x, 7)).collect(); + let q: Box<[(u8, u16)]> = data[half..half * 2].iter().map(|&x| (x ^ 1, 9)).collect(); + p.clone_from(&q); + if let Some(x) = p.first_mut() { + x.0 = 0x33; + x.1 = 0x44; + } + for (a, b) in p.iter().chain(q.iter()) { + out.push(*a); + out.extend(b.to_le_bytes()); + } + let mut s: Box<[String]> = data[..half].iter().map(|&x| text(&[x])).collect(); + let t: Box<[String]> = data[half..half * 2].iter().map(|&x| text(&[x, x])).collect(); + s.clone_from(&t); + if let Some(x) = s.first_mut() { + x.push('!'); + } + for x in s.iter().chain(t.iter()) { + put(out, x); + } + // Each item by its `clone_from`, which clones and drops what it writes over, and + // of another length, all cloned before the old drop. + let mut l: Box<[Logged]> = data[..half].iter().map(|&x| logged(x & 0x7e)).collect(); + let m: Box<[Logged]> = data[half..half * 2].iter().map(|&x| logged(x | 1)).collect(); + flush(&log, out); + l.clone_from(&m); + flush(&log, out); + ids(&l, out); + refill(&mut l, &Box::from([logged(0x10), logged(0x12), logged(0x14)])); + flush(&log, out); + ids(&l, out); + // Into a field, and into a `Vec`'s element. + let mut held = Held { + items: data[..data.len() - half].iter().map(|&x| logged(x & 0x7e)).collect(), + last: logged(0x50), + }; + flush(&log, out); + held.items.clone_from(&m); + flush(&log, out); + ids(&held.items, out); + drop(held); + flush(&log, out); + let mut v: Vec> = vec![Box::from(&data[..half]), Box::from(data)]; + v[1].clone_from(&b); + v[0].clone_from(&a); + for x in &v { + bytes(x, out); + } + drop(l); + drop(m); + flush(&log, out); + let mut t: Box = Box::from("ab"); + t.clone_from(&Box::from(text(data).as_str())); + put(out, &t); + } + 1 => { + let mut a: Box<[u8]> = Box::from(data); + bytes(a.as_ref(), out); + a.as_mut().reverse(); + flip(&mut a); + out.push(sum(&a)); + bytes(&a, out); + let mut n = Box::new(data.len() as u32); + *n.as_mut() += 7; + out.extend(n.as_ref().to_le_bytes()); + let mut pair = Box::new((data.first().copied().unwrap_or(1), 2u16)); + pair.as_mut().1 ^= 0x0101; + let copy = *pair.as_ref(); + pair.as_mut().0 = 9; + out.push(copy.0); + out.extend(copy.1.to_le_bytes()); + out.push(pair.0); + let mut array = Box::new([1u8, 2, 3, 4]); + array.as_mut()[usize::from(kind) % 4] = 0x55; + array.as_mut().reverse(); + bytes(array.as_ref(), out); + let mut v = Box::new(data.to_vec()); + v.as_mut().push(0x66); + bytes(v.as_ref(), out); + let mut s = Box::new(text(data)); + s.as_mut().push('#'); + put(out, s.as_ref()); + let mut w: Box<[u16]> = data.iter().map(|&x| u16::from(x) + 1).collect(); + w.as_mut().sort_unstable(); + wides(w.as_ref(), out); + let mut l: Box<[Logged]> = data.iter().map(|&x| logged(x)).collect(); + if let [first, .., last] = l.as_mut() { + std::mem::swap(first, last); + } + ids(l.as_ref(), out); + let mut o: Box> = Box::new(data.first().copied()); + if let Some(x) = o.as_mut() { + *x = x.wrapping_add(1); + } + out.push(o.as_ref().unwrap_or(0xee)); + } + 2 => { + boxed(>::try_from(Box::<[u8]>::from(data)), out); + boxed(>::try_from(Box::<[u8]>::from(data)), out); + let r: Result, Vec> = data.to_vec().try_into(); + match r { + Ok(a) => bytes(&*a, out), + Err(mut v) => { + v.push(0x77); + bytes(&v, out); + } + } + let mut spare = Vec::with_capacity(32); + spare.extend_from_slice(data); + boxed(>::try_from(spare), out); + let w: Box<[u16]> = data.iter().map(|&x| u16::from(x) * 3).collect(); + match >::try_from(w) { + Ok(mut a) => { + a[1] = 0x1234; + wides(&*a, out); + } + Err(w) => wides(&w, out), + } + let l: Vec = data.iter().map(|&x| logged(x & 0x7e)).collect(); + match >::try_from(l) { + Ok(a) => { + ids(&*a, out); + flush(&log, out); + drop(a); + } + Err(l) => { + ids(&l, out); + flush(&log, out); + drop(l); + } + } + flush(&log, out); + let l: Box<[Logged]> = data.iter().map(|&x| logged(x | 1)).collect(); + let r: Result, _> = l.try_into(); + flush(&log, out); + drop(r); + flush(&log, out); + } + _ => { + let s: Box = text(data).into_boxed_str(); + let mut a: Box<[u8]> = s.clone().into_boxed_bytes(); + a.reverse(); + bytes(&a, out); + let mut b: Box<[u8]> = Box::from(s.clone()); + if let Some(x) = b.last_mut() { + *x = 0x21; + } + bytes(&b, out); + let c: Box<[u8]> = s.into(); + let mut v = c.into_vec(); + v.push(0x2e); + bytes(&v, out); + let empty: Box<[u8]> = Box::::default().into_boxed_bytes(); + bytes(&empty, out); + } + } +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 4c1c26c90..c59d3c89d 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -272,6 +272,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("results::results", results::results), ("fields::fields", fields::fields), ("strs::gets", strs::gets), + ("boxes::impls", boxes::impls), ] } diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index b0d1a0f40..1d87ad474 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -577,6 +577,24 @@ export function sliceCloneFromSlice(dst: T[], src: ArrayLike, clone: ((val } } +/** `Clone::clone_from` of a `Box<[T]>`, `dst`: itself, by `clone_from_slice`, where the lengths match, or else a clone of `src`, made before `dst`'s elements drop in order. */ +export function boxSliceCloneFrom(dst: T[], src: T[], clone?: (value: T) => T, drop?: (item: T) => void): T[] { + if (dst.length === src.length) { + sliceCloneFromSlice(dst, src, clone, drop); + return dst; + } + const v = clone === undefined ? src.slice() : src.map(clone); + if (drop !== undefined) for (const item of dst) drop(item); + return v; +} + +/** `Clone::clone_from` of a `Box<[u8]>`, `dst`: itself, `src` copied in, where the lengths match, or else a copy of `src`. */ +export function byteBoxSliceCloneFrom(dst: ByteSlice, src: ByteSlice): ByteSlice { + if (dst.length !== src.length) return byteSliceFrom(byteSliceToArray(src)); + byteSliceCopyFromSlice(dst, src); + return dst; +} + function copyLengthFail(dst: number, src: number): never { panic(`copy_from_slice: source slice length (${src}) does not match destination slice length (${dst})`); } diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 5a70b9075..f30bea266 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -888,6 +888,8 @@ fn discards(owner: &str, name: &str) -> Option<&'static [usize]> { | ("hint", "must_use") | ("From", "from") | ("Into", "into") + | ("TryFrom", "try_from") + | ("TryInto", "try_into") // What a panic would drop, which aborts. | ("Result", "unwrap" | "expect" | "unwrap_err") | ("Vec", "insert") @@ -1347,8 +1349,8 @@ fn mapping<'tcx>( { return Some(cow_cloned(b, "IntoOwned", value, elem, span)); } - // A boxed slice clones what it borrows, moves an array's or a `Vec`'s elements, and - // owns the `Vec` a `Cow` gives up. + // A boxed slice clones what it borrows, moves an array's or a `Vec`'s elements, owns + // the `Vec` a `Cow` gives up, and takes a boxed text's bytes. if let Some(elem) = box_slice(tcx, to) { return match targ(gargs, 1).kind() { ty::Ref(_, s, _) if s.is_slice() => slice_boxed(b, value, elem, span), @@ -1357,6 +1359,8 @@ fn mapping<'tcx>( _ if cow_slice(tcx, from).is_some() => { Some(vec_boxed(cow_cloned(b, "IntoOwned", value, elem, span), elem)) } + // A `Box`'s bytes. + _ if string(tcx, from) => Some(Expr::rt("stringAsBytes", vec![value])), _ => None, }; } @@ -1404,6 +1408,36 @@ fn mapping<'tcx>( let value = b.convert(values.pop()?, from, to, span, out); return Some(Expr::rt("Ok", vec![value])); } + // A boxed slice or a `Vec` of `N` elements is a boxed array of them, and else + // itself again. + if let Some(ty::Array(elem, n)) = to.boxed_ty().map(|t| t.kind()) + && (box_slice(tcx, from) == Some(*elem) || b.t.std_of(from) == Some(Std::Vec)) + { + let n = n.try_to_target_usize(tcx)?; + let mut values = args; + let from_value = match values.pop()? { + v if v.is_path() => v, + v => b.spill(v, "boxed", out), + }; + let boxed = match b.t.std_of(from) { + Some(Std::Vec) => vec_boxed(from_value.clone(), *elem), + _ => from_value.clone(), + }; + let array = match is_u8(*elem) { + true => Expr::rt("byteSliceToArray", vec![boxed]), + false => boxed, + }; + return Some(Expr::Cond( + Box::new( + from_value + .clone() + .member("length") + .binary("===", Expr::lit(n.to_string())), + ), + Box::new(Expr::rt("Ok", vec![array])), + Box::new(Expr::rt("Err", vec![from_value])), + )); + } let (Some(int), Some(_)) = (Int::of(to), Int::of(from)) else { return None; }; @@ -1824,7 +1858,7 @@ fn mapping<'tcx>( if is_u8(elem) { return Some(rt2(args, "byteSlice", "fill")); } - let clone = clone_into(b, elem, &name, span); + let clone = clone_into(b, elem, "slice::fill", span); let mut values = args; // A value its own clone, which nothing drops, is JS's `fill`. if matches!(&clone, Expr::Lit(l) if l == "undefined") && !b.t.drops(elem) { @@ -1852,7 +1886,7 @@ fn mapping<'tcx>( } ("slice", "clone_from_slice") => { let mut values = args; - values.push(clone_into(b, targ(gargs, 0), &name, span)); + values.push(clone_into(b, targ(gargs, 0), "slice::clone_from_slice", span)); Expr::rt("sliceCloneFromSlice", values) } ("PartialEq", "eq") if byte_slice(targ(gargs, 0)) && byte_slice(targ(gargs, 1)) => { @@ -2081,6 +2115,8 @@ fn mapping<'tcx>( let v = args.pop()?; vec_view(v, targ(gargs, 0)) } + // A box is what it holds. + ("AsRef", "as_ref") | ("AsMut", "as_mut") if b.t.std_of(targ(gargs, 0)) == Some(Std::Box) => args.pop()?, ("Deref", "deref") | ("DerefMut", "deref_mut") => { let ty = targ(gargs, 0); let v = args.pop()?; @@ -2108,7 +2144,8 @@ fn mapping<'tcx>( } // Strings. - ("String" | "str", "as_bytes") => Expr::rt("stringAsBytes", args), + // A `Box`'s bytes are their own, as `as_bytes` gives them. + ("String" | "str", "as_bytes") | ("str", "into_boxed_bytes") => Expr::rt("stringAsBytes", args), ("String", "from_utf8") => Expr::rt("stringFromUtf8", args), // `str::from_utf8` is `core::str::converts::from_utf8`, or `str`'s own. ("converts" | "str", "from_utf8") => Expr::rt("strFromUtf8", args), @@ -2414,17 +2451,36 @@ fn owned<'tcx>(b: &mut Body<'_, '_, 'tcx>, value: Expr, ty: Ty<'tcx>, span: Span } } +/// The `Box<[elem]>` that `clone_from` of `source` leaves in place of `slot`: `slot`, each +/// element written by its `clone_from` as `clone_from_slice` writes them, where the lengths +/// match, and else a clone of `source`, made before `slot`'s elements drop. +pub(super) fn box_slice_clone_from<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + slot: Expr, + source: Expr, + elem: Ty<'tcx>, + span: Span, +) -> Option { + if matches!(elem.kind(), ty::Uint(ty::UintTy::U8)) { + return Some(Expr::rt("byteBoxSliceCloneFrom", vec![slot, source])); + } + let clone = clone_into(b, elem, "Box::clone_from", span); + let drop = b.t.droppers([elem], span)?; + let values = super::iter::tail(vec![slot, source], [vec![clone], drop].concat()); + Some(Expr::rt("boxSliceCloneFrom", values)) +} + /// What clones an `elem` into a slot of a slice, as `fill` and `clone_from_slice` do by /// `clone_from`: a copy for a `TrivialClone`, or else `clone_fn`, where `clone_from` does -/// as `*self = source.clone()` does. Refused for any other `clone_from`. -fn clone_into<'tcx>(b: &mut Body<'_, '_, 'tcx>, elem: Ty<'tcx>, name: &str, span: Span) -> Expr { +/// as `*self = source.clone()` does. Refused for any other `clone_from`, naming `path`. +fn clone_into<'tcx>(b: &mut Body<'_, '_, 'tcx>, elem: Ty<'tcx>, path: &str, span: Span) -> Expr { if super::iter::implements(b.t, elem, "TrivialClone") { return b.t.copy_fn(elem, span).unwrap_or_else(Expr::undefined); } if !clone_from_clones(b.t, elem) { return b.err( span, - format!("`slice::{name}` of `{elem}`, whose `clone_from` is its own, which is not supported yet"), + format!("`{path}` of `{elem}`, whose `clone_from` is its own, which is not supported yet"), ); } super::collections::clone_fn(b, elem, span) diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 0bb2f3acd..fe08220ed 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -653,6 +653,21 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.set_borrowed(&place, value, span, out); done(Expr::undefined(), false) } + // What `clone_from` leaves where the `&mut` borrows a boxed slice or text. + ("Clone", "clone_from") + if targ(0).is_some_and(|t| { + library::box_slice(tcx, t).is_some() || t.boxed_ty().is_some_and(|t| t.is_str()) + }) => + { + let place = self.borrowed_place(&args[0].node, span, out)?; + let source = self.operand(&args[1].node, span, out); + let value = match library::box_slice(tcx, targ(0)?) { + Some(elem) => library::box_slice_clone_from(self.b, place.0.clone(), source, elem, span)?, + None => source, + }; + self.set_borrowed(&place, value, span, out); + done(Expr::undefined(), false) + } // A `Cow` is the `String` it would hold, and a `String` is its `str`, so the // `&mut` borrows the same place. ("Cow", "to_mut") if targ(0).is_some_and(|t| t.is_str()) => self.same_place(args, dest, span, out), From 72ab1313ed904a4dfb08cdb860d72cae72d04f5b Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 05:48:04 -0800 Subject: [PATCH 3/3] fix(ts): keep a boxed byte slice's buffer as it becomes a boxed array Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/boxes.ts | 8 ++++---- ts/src/lower/library.rs | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ts/fixtures/lang/expected/boxes.ts b/ts/fixtures/lang/expected/boxes.ts index 020a490ee..3d0f4b061 100644 --- a/ts/fixtures/lang/expected/boxes.ts +++ b/ts/fixtures/lang/expected/boxes.ts @@ -968,11 +968,11 @@ export function impls(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { rt.dropAll(l2); } else if (_23 === 2) { const _395: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(data2)); - boxed$3_boxed_Box_slice_u8(_395.length === 3 ? rt.Ok(rt.byteSliceToArray(_395)) : rt.Err(_395), out); + boxed$3_boxed_Box_slice_u8(_395.length === 3 ? rt.Ok(rt.byteSliceView(_395)) : rt.Err(_395), out); const _398: rt.ByteSlice = rt.byteSliceFrom(rt.byteSliceToArray(data2)); - boxed$0_boxed_Box_slice_u8(_398.length === 0 ? rt.Ok(rt.byteSliceToArray(_398)) : rt.Err(_398), out); + boxed$0_boxed_Box_slice_u8(_398.length === 0 ? rt.Ok(rt.byteSliceView(_398)) : rt.Err(_398), out); const _400: rt.ByteVec = rt.byteSliceToByteVec(data2); - const r: rt.Result = _400.length === 2 ? rt.Ok(rt.byteSliceToArray(rt.byteVecSlice(_400))) : rt.Err(_400); + const r: rt.Result = _400.length === 2 ? rt.Ok(rt.byteSliceView(rt.byteVecSlice(_400))) : rt.Err(_400); if (r.ok) { bytes(rt.byteSliceFrom(r.value), out); } else { @@ -983,7 +983,7 @@ export function impls(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { const spare: rt.ByteVec = new rt.ByteVec(32); rt.byteVecExtendFromSlice(spare, data2); const _417: rt.ByteVec = spare; - boxed$4_Vec_u8(_417.length === 4 ? rt.Ok(rt.byteSliceToArray(rt.byteVecSlice(_417))) : rt.Err(_417), out); + boxed$4_Vec_u8(_417.length === 4 ? rt.Ok(rt.byteSliceView(rt.byteVecSlice(_417))) : rt.Err(_417), out); const w3: rt.u16[] = rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data2)), (_2: rt.u8): rt.u16 => { return rt.u16Mul(_2 as rt.u16, 3); }), undefined, 2, true); diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 45374b6db..955e53c21 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -1430,8 +1430,9 @@ fn mapping<'tcx>( Some(Std::Vec) => vec_boxed(from_value.clone(), *elem), _ => from_value.clone(), }; + // The bytes keep their buffer, as std keeps the allocation. let array = match is_u8(*elem) { - true => Expr::rt("byteSliceToArray", vec![boxed]), + true => Expr::rt("byteSliceView", vec![boxed]), false => boxed, }; return Some(Expr::Cond(