From a7493bf8cb20bf79f52dd4c3bcf68b4eca158963 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 04:06:47 -0800 Subject: [PATCH 01/10] chore(quest): claim quest/m3/ts-dyn.md Quest-Claim: 63ea0c57a7775853 From 52523872c860fce8b42c7a549d42a7cc4ffa3e1a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 04:59:48 -0800 Subject: [PATCH 02/10] feat(ts): translate dyn Iterator and its subtraits as an rt.Iter Co-Authored-By: Claude Opus 5.5 --- quest/m3/ts-dyn.md | 8 +- ts/README.md | 17 ++- ts/fixtures/lang/expected/core/option.ts | 8 + ts/fixtures/lang/expected/dyns.Chunks.ts | 21 +++ ts/fixtures/lang/expected/dyns.Countdown.ts | 34 +++++ ts/fixtures/lang/expected/dyns.Lease.ts | 14 ++ ts/fixtures/lang/expected/dyns.Tagged.ts | 25 +++ ts/fixtures/lang/expected/dyns.ts | 159 +++++++++++++++++++- ts/fixtures/lang/expected/harness.ts | 4 +- ts/fixtures/lang/src/dyns.rs | 145 +++++++++++++++++- ts/fixtures/lang/src/harness.rs | 1 + ts/fixtures/reject/census.md | 8 +- ts/fixtures/reject/expected.txt | 4 +- ts/fixtures/reject/src/lib.rs | 4 +- ts/runtime/rt/iter.ts | 36 +++-- ts/src/lower/dynamic.rs | 4 +- ts/src/lower/iter.rs | 50 ++++++ ts/src/lower/mir/call.rs | 9 +- ts/src/lower/mir/rvalue.rs | 7 + ts/src/lower/mod.rs | 1 + 20 files changed, 528 insertions(+), 31 deletions(-) create mode 100644 ts/fixtures/lang/expected/dyns.Chunks.ts create mode 100644 ts/fixtures/lang/expected/dyns.Countdown.ts create mode 100644 ts/fixtures/lang/expected/dyns.Lease.ts create mode 100644 ts/fixtures/lang/expected/dyns.Tagged.ts diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index 681418e6a..858439611 100644 --- a/quest/m3/ts-dyn.md +++ b/quest/m3/ts-dyn.md @@ -3,7 +3,7 @@ ## Goal [rust2-ts](/ts/README.md) translates the trait objects of std's and mapped crates' traits -that moq still refuses (`dyn Error`, `dyn Iterator`, and `dyn bytes::Buf`), and behind any +that moq still refuses (`dyn Error` and `dyn bytes::Buf`), and behind any pointer std coerces to one, such as `Arc`, with Rust's dispatch and drop order, proven by differential tests against native Rust, so a consumer neither rewrites nor binds them. @@ -14,11 +14,11 @@ config, so trait objects move from never to not yet. - `&dyn Debug` and `&dyn Display` translate as functions writing their value, `dyn Future` as an `rt.Future`, `dyn Any` as the class a downcast checks for, `dyn Fn*` as the - closure itself, and the translated crates' traits as an object with the trait's methods. - Build the rest on those. + closure itself, `dyn Iterator` and its subtraits as an `rt.Iter`, and the translated + crates' traits as an object with the trait's methods. Build the rest on those. - `Error` needs `source` and its supertraits' `Debug` and `Display` on the object, and its `downcast_ref` can check classes as `dyn Any`'s does; moq's are thiserror's `source`. -- `Iterator` can be an `rt.Iter`, and `bytes::Buf` the `rt.ByteSlice` it reads. +- `bytes::Buf` can be the `rt.ByteSlice` it reads. - `Arc` is refused where it is built, found by [#247](https://github.com/moq-dev/rust2/pull/247) on 2026-10-29: `Arc`, `Rc`, and `Box` of a trait object take the same object as a `&dyn`, behind the pointer's own counts. diff --git a/ts/README.md b/ts/README.md index cadf317f4..2dfb9bbc2 100644 --- a/ts/README.md +++ b/ts/README.md @@ -123,6 +123,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `&dyn Trait`, `&mut dyn Trait`, `Box`, `Arc` | an object with the trait's methods, its supertraits' included: `shape.area$Shape()`; behind an `Arc`, an `rt.ArcDyn` sharing the count of the `Arc` it was made from | | `dyn Any` | an instance of the class its value's type was wrapped in, which a downcast checks for: `a instanceof Any.of$dyns_Square ? a.v : undefined` | | `dyn Fn`, `dyn FnMut`, `dyn FnOnce` | the closure or function it was made of: `(a0: rt.u8) => rt.u32` | +| `dyn Iterator`, `dyn DoubleEndedIterator`, `dyn ExactSizeIterator` | `rt.Iter`: `rt.iterDyn` of the iterator it was made of | | constant | its value, inlined as Rust inlines it, so each use of one holding a cell is a cell of its own; one with no value tree, or holding a cell, read from the memory rustc evaluated | | `static` | a module's value, named for the static, that every use shares, read from the memory rustc evaluated; one that reads another static or a class is an `rt.LazyCell` of it, made on first use, since that may not be made yet when its module is. A `static mut`, and a pointer in one into memory the translation can't name, fail translation | | a type that drops | a class with `[Symbol.dispose]`: its `Drop` impl, then its fields | @@ -465,9 +466,17 @@ A `dyn Fn`, `dyn FnMut`, or `dyn FnOnce` is the closure or function it was made a call calls as it does the closure, and which a box drops as it does the closure. One that `Fn` or `FnMut` calls, boxed as a `dyn FnOnce`, is `rt.once` of it, which drops it after the call, as an `FnOnce` closure's body drops what it owns, and a box of a -reference to one is `rt.lent` of it, which never drops it. A trait object of std's -or a mapped crate's trait other than `&dyn Debug`, `&dyn Display`, `dyn Future`, -`dyn Any`, and the function traits, of a generic +reference to one is `rt.lent` of it, which never drops it. + +A `dyn Iterator`, `dyn DoubleEndedIterator`, or `dyn ExactSizeIterator` behind `&`, +`&mut`, or `Box` is an `rt.Iter` of its items, `rt.iterDyn` of the iterator it was made +of, std's or the crate's own: it reads that iterator by the methods std's vtable holds, +such as `next`, `nth`, and `size_hint`, and folds it by `next`, as std's defaults read a +trait object, so a `count` or `last` of a boxed `Chain` drops its items in the order the +trait object's does. A box drops the iterator it owns. + +A trait object of std's or a mapped crate's trait other than `&dyn Debug`, `&dyn Display`, +`dyn Future`, `dyn Any`, the function traits, and those iterators', of a generic trait or one with an associated type, of a byte array, or behind any other pointer, such as a `Weak` or a struct's last field, is refused, and so are an upcast to `dyn Any` and `Any::type_id`. @@ -837,7 +846,7 @@ they need. Byte reinterpretation, pointer arithmetic and addresses, raw allocation, and unions whose fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than -`&dyn Debug`, `&dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, and the translated crates' traits' +`&dyn Debug`, `&dyn Display`, `dyn Future`, `dyn Any`, `dyn Fn*`, `dyn Iterator` and its subtraits, and the translated crates' traits' behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `dyn Error`, binds to hand-written TS ([bindings](#bindings)). diff --git a/ts/fixtures/lang/expected/core/option.ts b/ts/fixtures/lang/expected/core/option.ts index d560b69e0..aede80457 100644 --- a/ts/fixtures/lang/expected/core/option.ts +++ b/ts/fixtures/lang/expected/core/option.ts @@ -24,6 +24,7 @@ import type * as counts_Twin from "../counts.Twin.ts"; import type * as cycles_Logged from "../cycles.Logged.ts"; import * as defaults_Made from "../defaults.Made.ts"; import type * as dyns_Shape from "../dyns.Shape.ts"; +import type * as dyns_Tagged from "../dyns.Tagged.ts"; import * as edits_Named from "../edits.Named.ts"; import type * as Ticked from "../generics.Ticked.ts"; import type * as hints_Unit from "../hints.Unit.ts"; @@ -334,6 +335,13 @@ export function map$1u07cey(self: cycles_Logged.Logged | undefined, f: (a0: cycl return f(self); } +export function map$08a6hpg(self: dyns_Tagged.Tagged | undefined, f: (a0: dyns_Tagged.Tagged) => rt.u8): rt.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$146awhd(self: Hop.Hop | undefined, f: (a0: Hop.Hop) => [rt.u64, rt.u64]): [rt.u64, rt.u64] | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/expected/dyns.Chunks.ts b/ts/fixtures/lang/expected/dyns.Chunks.ts new file mode 100644 index 000000000..cffd3dd7e --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Chunks.ts @@ -0,0 +1,21 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import * as Lease from "./dyns.Lease.ts"; + +/** Lends its bytes as parts, as moq's transports lend a stream's chunks. */ +export type Chunks = { chunks$Chunks: () => rt.Iter }; + +export class of$dyns_Lease { + v: Lease.Lease; + + constructor(v: Lease.Lease) { + this.v = v; + } + + chunks$Chunks(): rt.Iter { + return Lease.chunks$dyns_Chunks(this.v); + } +} + +//# sourceMappingURL=dyns.Chunks.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Countdown.ts b/ts/fixtures/lang/expected/dyns.Countdown.ts new file mode 100644 index 000000000..0402b4d23 --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Countdown.ts @@ -0,0 +1,34 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { DROPS } from "./dyns.ts"; + +/** The crate's own iterator, counting down, which logs 200 as it drops. */ +export class Countdown { + _0: rt.u8; + + constructor(_0: rt.u8) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function next$iter_Iterator(self: Countdown): rt.u8 | rt.Done { + const _2: rt.u8 | undefined = rt.u8CheckedSub(self._0, 1); + if (_2 !== undefined) { + self._0 = _2; + return self._0; + } + return rt.DONE; +} + +export function drop$ops_Drop(_self: Countdown): void { + rt.localKeyWithBorrowMut(DROPS, (d: rt.ByteVec): undefined => { + rt.byteVecPush(d, 200); + }); +} + +//# sourceMappingURL=dyns.Countdown.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Lease.ts b/ts/fixtures/lang/expected/dyns.Lease.ts new file mode 100644 index 000000000..8d5b70a2e --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Lease.ts @@ -0,0 +1,14 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export interface Lease { + _0: rt.ByteVec; + _1: rt.usize; +} + +export function chunks$dyns_Chunks(self: Lease): rt.Iter { + return rt.iterDyn(rt.byteSliceChunks(rt.byteVecSlice(self._0), self._1)); +} + +//# sourceMappingURL=dyns.Lease.ts.map diff --git a/ts/fixtures/lang/expected/dyns.Tagged.ts b/ts/fixtures/lang/expected/dyns.Tagged.ts new file mode 100644 index 000000000..fc6438c2d --- /dev/null +++ b/ts/fixtures/lang/expected/dyns.Tagged.ts @@ -0,0 +1,25 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { DROPS } from "./dyns.ts"; + +/** Logs its tag as it drops. */ +export class Tagged { + _0: rt.u8; + + constructor(_0: rt.u8) { + this._0 = _0; + } + + [Symbol.dispose](): void { + drop$ops_Drop(this); + } +} + +export function drop$ops_Drop(self: Tagged): void { + rt.localKeyWithBorrowMut(DROPS, (d: rt.ByteVec): undefined => { + rt.byteVecPush(d, self._0); + }); +} + +//# sourceMappingURL=dyns.Tagged.ts.map diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index 8d043d1ba..f721cd886 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -2,8 +2,11 @@ import * as rt from "./rt.ts"; import * as Any from "./core/any.Any.ts"; -import { unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1i542n2 as option_map$1i542n2, mapOr$06rr3sb as option_mapOr$06rr3sb } from "./core/option.ts"; +import { thenSome$0d5am30 } from "./core/bool.ts"; +import { unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$08a6hpg as option_map$08a6hpg, map$1i542n2 as option_map$1i542n2, mapOr$06rr3sb as option_mapOr$06rr3sb } from "./core/option.ts"; import { ok$0lceojf, map$0zzqmaz as result_map$0zzqmaz } from "./core/result.ts"; +import * as Chunks from "./dyns.Chunks.ts"; +import * as dyns_Countdown from "./dyns.Countdown.ts"; import * as dyns_Erased from "./dyns.Erased.ts"; import type * as Hits from "./dyns.Hits.ts"; import * as Hook from "./dyns.Hook.ts"; @@ -11,6 +14,7 @@ import * as dyns_Named from "./dyns.Named.ts"; import * as dyns_Shape from "./dyns.Shape.ts"; import * as Shown from "./dyns.Shown.ts"; import * as Square from "./dyns.Square.ts"; +import * as dyns_Tagged from "./dyns.Tagged.ts"; import * as Tri from "./dyns.Tri.ts"; import { put as fmt_put } from "./fmt.ts"; @@ -666,4 +670,157 @@ export function fns(data: rt.ByteSlice, out: rt.ByteVec): void { } } +export const DROPS: rt.LocalKey> = /* @__PURE__ */ new rt.LocalKey(DROPS$init, false); + +export function DROPS$init(): rt.RefCell { + return new rt.RefCell(new rt.ByteVec()); +} + +/** Puts what dropped since the last call. */ +export function dropped(out: rt.ByteVec): void { + const part = rt.localKeyWithBorrowMut(DROPS, (d: rt.ByteVec): rt.ByteVec => { + return rt.replaceIn(d, new rt.ByteVec()); + }); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(part), f, rt.u8Debug)]; + fmt_put(out, rt.fmtFormat([args2[0], " "])); +} + +/** Each of the vtable's methods, then std's adaptors over the borrow, then by value. */ +export function read(it: rt.Iter, out: rt.ByteVec): void { + const hint: [rt.usize, rt.usize | undefined] = rt.iterBorrow(it).sizeHint(); + const first: rt.u8 | undefined = rt.iterToOption(it.next()); + const third: rt.u8 | undefined = rt.iterNth(it, 1); + const pair: rt.ByteVec = rt.iterToByteVec(rt.iterTake(rt.iterStepBy(rt.iterBorrow(it), 2), 2), "next"); + const rest: rt.u32 = rt.iterSum(rt.iterMap(rt.iterBorrow(it), (a0: rt.u8): rt.u32 => { + return a0 as rt.u32; + }), rt.u32Add, 0); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.usizeDebug(hint[0], f), (f: rt.Formatter) => rt.fmtOption(hint[1], f, rt.usizeDebug)], f), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(third, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(pair), f, rt.u8Debug), (f: rt.Formatter) => rt.u32Display(rest, f)]; + fmt_put(out, rt.fmtFormat([args2[0], " ", args2[1], " ", args2[2], " ", args2[3], " ", args2[4], " "])); +} + +/** + * The first value at or past `from` that `f` answers, from either end, as moq's model + * scans its groups. + */ +export function scan$dyns_iters_closure1(map: rt.BTreeMap, from: rt.u64, rev: boolean, f: (a0: rt.u8) => rt.u8 | undefined): rt.u8 | undefined { + const scan: (a0: rt.Iter<[rt.u64, rt.u8]>) => rt.u8 | undefined = (range: rt.Iter<[rt.u64, rt.u8]>): rt.u8 | undefined => { + if (!rev) { + return rt.iterFindMap(rt.iterMap(rt.iterBorrow(range), (_22: [rt.u64, rt.u8]): rt.u8 => { + return _22[1]; + }), f); + } + return rt.iterFindMap(rt.iterRev(rt.iterBorrow(range)), (_2: [rt.u64, rt.u8]): rt.u8 | undefined => { + return f(_2[1]); + }); + }; + const part2 = rt.iterDyn(rt.iterBorrow(rt.btreeRange(map, new rt.RangeFrom(from, rt.u64Step)))); + return scan(part2); +} + +/** The length on either side of an item, and an upcast that reads the rest. */ +export function lens(it: rt.Iter): string { + const before: rt.usize = rt.iterBorrow(it).len(); + const first: rt.u8 | undefined = rt.iterToOption(it.next()); + const after: rt.usize = rt.iterBorrow(it).len(); + const back: rt.u8 | undefined = rt.iterLast(rt.iterBorrow(it)); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.usizeDisplay(before, f), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.usizeDisplay(after, f), (f: rt.Formatter) => rt.fmtOption(back, f, rt.u8Debug)]; + return rt.fmtFormat([args2[0], " ", args2[1], " ", args2[2], " ", args2[3]]); +} + +export function tagged(tags: rt.ByteSlice): dyns_Tagged.Tagged[] { + return rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(tags)), (_22: rt.u8): dyns_Tagged.Tagged => { + return new dyns_Tagged.Tagged(_22); + }), undefined, 1, true); +} + +export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { + if (data.length >= 2) { + const _10: [rt.u8, rt.u8] = [data.data[data.start], data.data[data.start + 1]]; + const a: rt.u8 = _10[0]; + const b: rt.u8 = _10[1]; + read(rt.iterDyn(rt.iterBorrow(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data, 2))))), out); + read(rt.iterDyn(rt.iterBorrow(new rt.Range(a, b))), out); + const down: dyns_Countdown.Countdown = new dyns_Countdown.Countdown(rt.u8Rem(a, 16)); + read(rt.iterDyn(rt.iterFrom(down, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it))), out); + dropped(out); + down[Symbol.dispose](); + dropped(out); + const _33: rt.Iter<[rt.u64, rt.u8]> = rt.iterMap(rt.iterEnumerate(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data, 2)))), (_2: [rt.usize, rt.u8]): [rt.u64, rt.u8] => { + return [_2[0] as rt.u64, _2[1]] as [rt.u64, rt.u8]; + }); + const map: rt.BTreeMap = rt.btreeExtend(rt.btreeNew(rt.cmp) as rt.BTreeMap, _33, true, undefined, undefined, undefined, 16, true); + const _44: rt.u8 | undefined = scan$dyns_iters_closure1(map, rt.u8Rem(a, 8) as rt.u64, false, (v: rt.u8): rt.u8 | undefined => { + return thenSome$0d5am30(rt.u8Rem(v, 2) === 1, v); + }); + const _50: rt.u8 | undefined = scan$dyns_iters_closure1(map, rt.u8Rem(b, 8) as rt.u64, true, (v2: rt.u8): rt.u8 | undefined => { + return thenSome$0d5am30(rt.u8Rem(v2, 2) === 1, v2); + }); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(_44, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(_50, f, rt.u8Debug)]; + fmt_put(out, rt.fmtFormat([args2[0], " ", args2[1], " "])); + const part = lens(rt.iterDyn(rt.iterBorrow(rt.iterSlice(rt.byteSliceView(rt.byteSliceRange(data, 2)))))); + const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, part)]; + fmt_put(out, rt.fmtFormat([args4[0], " "])); + const _79: rt.usize = rt.usizeRem(a as rt.usize, rt.usizeAdd(rt.byteSliceRange(data, 2).length, 1)); + const slice: rt.ByteSlice = rt.byteSliceRange(data, 2); + if (_79 > slice.length) { + rt.panic("mid > len"); + } + const front: rt.ByteSlice = new rt.ByteSlice(slice.data, slice.start, _79); + const back: rt.ByteSlice = new rt.ByteSlice(slice.data, slice.start + _79, slice.length - _79); + const boxed: rt.Iter = rt.iterDyn(rt.iterSlice(tagged(front), rt.drop)); + const ends: [rt.u8 | undefined, rt.u8 | undefined] = [option_map$08a6hpg(rt.iterToOption(boxed.next()), (t: dyns_Tagged.Tagged): rt.u8 => { + const _04: rt.u8 = t._0; + t[Symbol.dispose](); + return _04; + }), option_map$08a6hpg(rt.iterToOption(boxed.nextBack()), (t2: dyns_Tagged.Tagged): rt.u8 => { + const _05: rt.u8 = t2._0; + t2[Symbol.dispose](); + return _05; + })]; + const nth: rt.u8 | undefined = option_map$08a6hpg(rt.iterNth(boxed, 1, rt.drop), (t3: dyns_Tagged.Tagged): rt.u8 => { + const _06: rt.u8 = t3._0; + t3[Symbol.dispose](); + return _06; + }); + dropped(out); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.fmtOption(ends[0], f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(ends[1], f, rt.u8Debug)], f), (f: rt.Formatter) => rt.fmtOption(nth, f, rt.u8Debug)]; + fmt_put(out, rt.fmtFormat([args6[0], " ", args6[1], " "])); + rt.drop(boxed); + dropped(out); + const last: rt.u8 | undefined = option_map$08a6hpg(rt.ownedLast(rt.iterDyn(rt.iterChain(rt.iterSlice(tagged(front), rt.drop), rt.iterSlice(tagged(back), rt.drop))), rt.drop), (t4: dyns_Tagged.Tagged): rt.u8 => { + const _07: rt.u8 = t4._0; + t4[Symbol.dispose](); + return _07; + }); + dropped(out); + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(last, f, rt.u8Debug)]; + fmt_put(out, rt.fmtFormat([args8[0], " "])); + const part2 = rt.ownedCount(rt.iterDyn(rt.iterChain(rt.iterSlice(tagged(back), rt.drop), rt.iterSlice(tagged(front), rt.drop))), rt.drop); + const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.usizeDisplay(part2, f)]; + fmt_put(out, rt.fmtFormat([args10[0], " "])); + dropped(out); + const own: rt.Iter = rt.iterDyn(rt.iterFrom(new dyns_Countdown.Countdown(rt.u8Rem(b, 16)), (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop)); + let sum: rt.u32 = 0; + const iter: rt.Iter = own; + while (true) { + const _166: rt.u8 | rt.Done = iter.next(); + if (_166 === rt.DONE) { + break; + } + sum = rt.u32WrappingAdd(sum, _166 as rt.u32); + } + rt.drop(iter); + const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(sum, f)]; + fmt_put(out, rt.fmtFormat([args12[0], " "])); + dropped(out); + const split: Chunks.Chunks = new Chunks.of$dyns_Lease({ _0: rt.byteSliceToByteVec(rt.byteSliceRange(data, 2)), _1: rt.usizeAdd(rt.u8Rem(b, 4) as rt.usize, 1) }); + const lens_: rt.usize[] = rt.iterToArray(rt.iterMap(split.chunks$Chunks(), (a0: rt.ByteSlice): rt.usize => { + return a0.length; + }), "next", 4); + const _206: rt.usize = rt.ownedCount(split.chunks$Chunks()); + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_206, f)]; + fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1]])); + } +} + //# sourceMappingURL=dyns.ts.map diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index a4b115d32..c6a7900de 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -22,7 +22,7 @@ import { cows, slices as cows_slices, bytes as cows_bytes } from "./cows.ts"; import { cycles } from "./cycles.ts"; import { defaults as defaults_defaults } from "./defaults.ts"; import { durations } from "./durations.ts"; -import { dyns, shared as dyns_shared, anys, fns } from "./dyns.ts"; +import { dyns, shared as dyns_shared, anys, fns, iters as dyns_iters } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; import { eqs } from "./errors.ts"; import { fields as fields_fields } from "./fields.ts"; @@ -280,6 +280,8 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) downgraded(data97, out98); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["borrows::leaks", (_2198: rt.u8, data98: rt.ByteSlice, out99: rt.ByteVec): undefined => { leaks(data98, out99); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::iters", (_2199: rt.u8, data99: rt.ByteSlice, out100: rt.ByteVec): undefined => { + dyns_iters(data99, out100); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index 7c710f946..4e9473d1f 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -4,9 +4,14 @@ //! them, `self: Box`, and an upcast to the supertrait. Behind an `Arc`, the object //! shares the count of the `Arc` it was made from. A `dyn Any` downcasts to the type it //! was made of, borrowed, boxed, or shared. A `dyn Fn*` of a closure, a function, or a -//! function pointer calls it, changing what it captured, borrowed, boxed, or shared. +//! function pointer calls it, changing what it captured, borrowed, boxed, or shared. A +//! `dyn Iterator`, `dyn DoubleEndedIterator`, or `dyn ExactSizeIterator` of std's +//! iterators or the crate's own, borrowed or boxed, reads by its vtable's methods and +//! folds by `next`, dropping what it owns in std's order. use std::any::Any; +use std::cell::RefCell; +use std::collections::BTreeMap; use std::fmt::{self, Debug}; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::{Arc, Weak}; @@ -526,3 +531,141 @@ pub fn fns(data: &[u8], out: &mut Vec) { &format!("{} {} {}", shared(a), other(b), Arc::strong_count(&other)), ); } + +thread_local! { + static DROPS: RefCell> = const { RefCell::new(Vec::new()) }; +} + +/// Puts what dropped since the last call. +fn dropped(out: &mut Vec) { + let tags = DROPS.with_borrow_mut(|d| std::mem::take(d)); + put(out, &format!("{tags:?} ")); +} + +/// Logs its tag as it drops. +struct Tagged(u8); + +impl Drop for Tagged { + fn drop(&mut self) { + DROPS.with_borrow_mut(|d| d.push(self.0)); + } +} + +/// The crate's own iterator, counting down, which logs 200 as it drops. +struct Countdown(u8); + +impl Iterator for Countdown { + type Item = u8; + + fn next(&mut self) -> Option { + self.0 = self.0.checked_sub(1)?; + Some(self.0) + } +} + +impl Drop for Countdown { + fn drop(&mut self) { + DROPS.with_borrow_mut(|d| d.push(200)); + } +} + +/// Each of the vtable's methods, then std's adaptors over the borrow, then by value. +fn read(it: &mut dyn Iterator, out: &mut Vec) { + let hint = it.size_hint(); + let first = it.next(); + let third = it.nth(1); + let pair: Vec = (&mut *it).step_by(2).take(2).collect(); + let rest: u32 = it.map(u32::from).sum(); + put(out, &format!("{hint:?} {first:?} {third:?} {pair:?} {rest} ")); +} + +/// The first value at or past `from` that `f` answers, from either end, as moq's model +/// scans its groups. +fn scan<'a>(map: &'a BTreeMap, from: u64, rev: bool, mut f: impl FnMut(&'a u8) -> Option) -> Option { + let mut scan = |range: &mut dyn DoubleEndedIterator| match rev { + true => range.rev().find_map(|(_, v)| f(v)), + false => range.map(|(_, v)| v).find_map(&mut f), + }; + scan(&mut map.range(from..)) +} + +/// The length on either side of an item, and an upcast that reads the rest. +fn lens(it: &mut dyn ExactSizeIterator) -> String { + let before = it.len(); + let first = it.next().copied(); + let after = it.len(); + let up: &mut dyn Iterator = it; + let back = up.last().copied(); + format!("{before} {first:?} {after} {back:?}") +} + +fn tagged(tags: &[u8]) -> Vec { + tags.iter().map(|&t| Tagged(t)).collect() +} + +/// Lends its bytes as parts, as moq's transports lend a stream's chunks. +trait Chunks { + fn chunks(&self) -> Box + '_>; +} + +struct Lease(Vec, usize); + +impl Chunks for Lease { + fn chunks(&self) -> Box + '_> { + Box::new(self.0.chunks(self.1)) + } +} + +pub fn iters(data: &[u8], out: &mut Vec) { + let [a, b, rest @ ..] = data else { + return; + }; + let (a, b) = (*a, *b); + + // Borrowed: std's iterators, a range, and the crate's own, which the borrow never drops. + read(&mut rest.iter().copied(), out); + read(&mut (a..b), out); + let mut down = Countdown(a % 16); + read(&mut down, out); + dropped(out); + drop(down); + dropped(out); + let map: BTreeMap = rest.iter().enumerate().map(|(i, &x)| (i as u64, x)).collect(); + let odd = |v: &u8| (v % 2 == 1).then_some(*v); + put( + out, + &format!("{:?} {:?} ", scan(&map, u64::from(a % 8), false, odd), scan(&map, u64::from(b % 8), true, odd)), + ); + put(out, &format!("{} ", lens(&mut rest.iter()))); + + // Boxed: what it owns drops as it reads, then the rest as the box drops, in std's order. + let (front, back) = rest.split_at(usize::from(a) % (rest.len() + 1)); + let mut boxed: Box> = Box::new(tagged(front).into_iter()); + let ends = (boxed.next().map(|t| t.0), boxed.next_back().map(|t| t.0)); + let nth = boxed.nth(1).map(|t| t.0); + dropped(out); + put(out, &format!("{ends:?} {nth:?} ")); + drop(boxed); + dropped(out); + // A chain's own `last` keeps the first half's last until the end; through the box, std's + // default reads by `next` instead. + let chained: Box> = Box::new(tagged(front).into_iter().chain(tagged(back))); + let last = chained.last().map(|t| t.0); + dropped(out); + put(out, &format!("{last:?} ")); + let counted: Box> = Box::new(tagged(back).into_iter().chain(tagged(front))); + put(out, &format!("{} ", counted.count())); + dropped(out); + let own: Box> = Box::new(Countdown(b % 16)); + let mut sum = 0u32; + for x in own { + sum = sum.wrapping_add(u32::from(x)); + } + put(out, &format!("{sum} ")); + dropped(out); + + // Returned through a trait object of the crate's own trait. + let split: &dyn Chunks = &Lease(rest.to_vec(), usize::from(b % 4) + 1); + let lens: Vec = split.chunks().map(<[u8]>::len).collect(); + put(out, &format!("{lens:?} {}", split.chunks().count())); +} diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 0a4253ba2..529f96d18 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -288,6 +288,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("capacity::maps", capacity::maps), ("locks::downgraded", |_, data, out| locks::downgraded(data, out)), ("borrows::leaks", |_, data, out| borrows::leaks(data, out)), + ("dyns::iters", |_, data, out| dyns::iters(data, out)), ] } diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 67e9a15d8..84909b6cd 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -122,7 +122,7 @@ The type or module each refused type or path belongs to. | 2 | `[u8; 3]` | fixtures/reject/src/lib.rs:1726:15 | | 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:464:23 | | 2 | `dyn std::any::Any` | fixtures/reject/src/lib.rs:532:32 | -| 2 | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | +| 2 | `dyn std::hash::Hasher` | fixtures/reject/src/lib.rs:11:16 | | 2 | `slice` | fixtures/reject/src/lib.rs:21:4 | | 2 | `std::any::TypeId` | fixtures/reject/src/lib.rs:538:2 | | 2 | `std::collections::BTreeSet` | fixtures/reject/src/lib.rs:235:75 | @@ -266,7 +266,7 @@ The type or module each refused type or path belongs to. | 3 | unsupported type `_` | `f16` | fixtures/reject/src/lib.rs:7:14 | | 2 | `_` is not supported: its `None` and `Some` would both be `rt.None` | `Option` | fixtures/reject/src/lib.rs:16:15 | | 2 | `_` of `_`, whose `None` is `rt.None`, which is not supported yet | `Iterator::scan`, `Option` | fixtures/reject/src/lib.rs:37:4 | -| 2 | `_`, a trait object of another crate's trait, which is not supported yet | `dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:11:16 | +| 2 | `_`, a trait object of another crate's trait, which is not supported yet | `dyn std::hash::Hasher` | fixtures/reject/src/lib.rs:11:16 | | 2 | `_`, a trait object of the generic trait `_`, which is not supported yet | `dyn Scaled`, `Scaled` | fixtures/reject/src/lib.rs:464:23 | | 2 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `std::mem::MaybeUninit`, `Gap` | fixtures/reject/src/lib.rs:922:19 | | 2 | a raw pointer to `_` in a variable assigned again while it may point there | `[u8; 4]` | fixtures/reject/src/lib.rs:1733:26 | @@ -687,8 +687,8 @@ The type or module each refused type or path belongs to.
`_`, a trait object of another crate's trait, which is not supported yet (2) -- fixtures/reject/src/lib.rs:11:16: `dyn std::iter::Iterator`, a trait object of another crate's trait, which is not supported yet -- fixtures/reject/src/lib.rs:12:4: `dyn std::iter::Iterator`, a trait object of another crate's trait, which is not supported yet +- fixtures/reject/src/lib.rs:11:16: `dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet +- fixtures/reject/src/lib.rs:12:4: `dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet
diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 1b991db4d..150cc1379 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -7,8 +7,8 @@ unsupported type `f16` at fixtures/reject/src/lib.rs:7:14 a scalar constant of type `f16` at fixtures/reject/src/lib.rs:8:2 arithmetic on `f16` at fixtures/reject/src/lib.rs:8:2 unsupported type `f16` at fixtures/reject/src/lib.rs:7:25 -`dyn std::iter::Iterator`, a trait object of another crate's trait, which is not supported yet at fixtures/reject/src/lib.rs:11:16 -`dyn std::iter::Iterator`, a trait object of another crate's trait, which is not supported yet at fixtures/reject/src/lib.rs:12:4 +`dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet at fixtures/reject/src/lib.rs:11:16 +`dyn std::hash::Hasher`, a trait object of another crate's trait, which is not supported yet at fixtures/reject/src/lib.rs:12:4 `Option>>` is not supported: its `None` and `Some` would both be `rt.None` at fixtures/reject/src/lib.rs:17:2 `Option>>` is not supported: its `None` and `Some` would both be `rt.None` at fixtures/reject/src/lib.rs:16:15 `slice::fill` of `Reused`, whose `clone_from` is its own, which is not supported yet at fixtures/reject/src/lib.rs:21:4 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index db1816a5a..34e16596f 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -8,8 +8,8 @@ pub fn float(a: f16) -> f16 { a * 2.0 } -pub fn dynamic(f: &mut dyn Iterator) -> Option { - f.next() +pub fn dynamic(h: &mut dyn std::hash::Hasher) -> u64 { + h.finish() } /// `None` and `Some(None)` would both be `rt.None`, as `Some(Some(None))` is `undefined`. diff --git a/ts/runtime/rt/iter.ts b/ts/runtime/rt/iter.ts index 6f1c6437e..ee088c6bd 100644 --- a/ts/runtime/rt/iter.ts +++ b/ts/runtime/rt/iter.ts @@ -532,6 +532,14 @@ export function iterBorrow(it: Iter): Iter { return new Borrowed(it); } +/** + * A `dyn Iterator` of `it`: what std's vtable has, as `it` does it, and the rest by `next`, as + * std's default methods read a trait object. It drops `it` as it drops. + */ +export function iterDyn(it: Iter): Iter { + return new Dyn(it); +} + // How std's `Vec` and `VecDeque` reserve for an iterator they collect or extend by, of // elements `size` bytes each, which panics past what they may hold: a `TrustedLen` one's // upper bound first, and else, after each item that finds them full, the lower bound and @@ -1898,11 +1906,8 @@ class SuccessorsIter extends Iter implements Disposable { } } -/** - * An iterator borrowed by `&mut`, which forwards only what std's `impl Iterator for &mut - * I` does, and which dropping drops nothing. - */ -class Borrowed extends Iter { +/** What std's vtable of an iterator holds, as `inner` does it, which it drops with itself. */ +class Dyn extends Iter implements Disposable { readonly inner: Iter; constructor(inner: Iter) { @@ -1918,10 +1923,6 @@ class Borrowed extends Iter { return this.inner.next(); } - override folded(): Iter { - return this.inner.folded(); - } - override nextBack(): T | Done { return this.inner.nextBack(); } @@ -1945,6 +1946,23 @@ class Borrowed extends Iter { override nthBack(n: number, drop?: Drop): T | Done { return this.inner.nthBack(n, drop); } + + [Symbol.dispose](): void { + dispose(this.inner); + } +} + +/** + * An iterator borrowed by `&mut`, which forwards only what std's `impl Iterator for &mut + * I` does, its folds included, as std's specializes them for a sized `I`, and which + * dropping drops nothing. + */ +class Borrowed extends Dyn { + override folded(): Iter { + return this.inner.folded(); + } + + override [Symbol.dispose](): void {} } class MapIter extends Calling U> { diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 2d73493a2..95bfa726f 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -185,7 +185,8 @@ impl<'tcx> Translator<'_, 'tcx> { /// Whether `instance` is a call through a trait object's method, whose wrapper takes /// its arguments as the crate's functions do. A `&dyn Debug` or `&dyn Display` is a - /// function writing its value instead, and a `dyn Future` an `rt.Future`. + /// function writing its value instead, a `dyn Future` an `rt.Future`, and a + /// `dyn Iterator` an `rt.Iter`. pub fn dispatches(&self, instance: Instance<'tcx>) -> bool { let ty::InstanceKind::Virtual(..) = instance.def else { return false; @@ -195,6 +196,7 @@ impl<'tcx> Translator<'_, 'tcx> { return false; }; self.dyn_future(object).is_none() + && self.dyn_iter(object).is_none() && self.dyn_fn(object).is_none() && preds .principal_def_id() diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 73205ac10..9efe64b60 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -46,6 +46,23 @@ impl<'tcx> Translator<'_, 'tcx> { tcx.try_normalize_erasing_regions(self.typing_env_pub(), ty::Unnormalized::new_wip(projection)) .ok() } + + /// `Iterator::Item` of `ty`, if it is a `dyn Iterator`, `dyn DoubleEndedIterator`, or + /// `dyn ExactSizeIterator`, whose JS value is an `rt.Iter` of its items. + pub fn dyn_iter(&self, ty: Ty<'tcx>) -> Option> { + let ty::Dynamic(preds, ..) = ty.kind() else { + return None; + }; + let principal = preds.principal_def_id()?; + let name = library::std_path(self.tcx, principal).pop()?; + if !matches!( + name.as_str(), + "Iterator" | "DoubleEndedIterator" | "ExactSizeIterator" + ) { + return None; + } + self.iter_item(ty) + } } /// The crate's own iterator a `ty` is, through any references: its `next`, whose @@ -114,6 +131,39 @@ pub fn next<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, iter: Expr) -> Expr } } +impl<'tcx> Body<'_, '_, 'tcx> { + /// `value`, a `&`, `&mut`, or `Box` of an iterator unsized to `to`, the same pointer to a + /// `dyn Iterator`: an `rt.iterDyn` of the `rt.Iter` it is, which reads it by the methods + /// std's vtable has and else by `next`, as std's defaults read a trait object, and drops + /// it if the box owned it. An upcast from another is the same `rt.Iter`. + pub fn dyn_iter(&mut self, from: Ty<'tcx>, to: Ty<'tcx>, value: Expr, span: Span) -> Expr { + let tcx = self.t.tcx; + let Some(pointee) = from.builtin_deref(true).filter(|_| to.builtin_deref(true).is_some()) else { + return self.err(span, format!("a coercion to `{to}`, which is not supported yet")); + }; + if self.t.dyn_iter(pointee).is_some() { + return value; + } + // Another trait object, whose object has its trait's methods and is no `rt.Iter`. + if pointee.is_trait() { + return self.err(span, format!("an upcast from `{pointee}` to `{to}`, which is not supported yet")); + } + // A census over type parameters: only an instance decides what the pointee is. + if self.t.polymorphic() && pointee.has_param() { + return Expr::undefined(); + } + // What a box owns iterates as itself, and what a reference borrows as a `&mut` does. + let ty = match from.is_box() { + true => pointee, + false => Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, pointee), + }; + match into_iter(self, ty, value, span) { + Some(it) => Expr::rt("iterDyn", vec![it]), + None => self.err(span, format!("a coercion of `{from}` to `{to}`, which is not supported yet")), + } + } +} + /// What `IntoIterator::into_iter` makes of `value`, a `ty`: an `rt.Iter`, or `None` for a /// type without a mapping. One that owns what it yields, by value, is given what drops /// each, and the crate's own iterator what drops it. diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 0b8443030..090c18e35 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -63,8 +63,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let self_ty = gargs.skip_binder().type_at(0); let (iter_ty, recv) = match args[0].node.place() { // Through std's `Iterator` for `&mut I` and `Box`: what the last `&mut` - // points at, read through the `rt.Place` of each `&mut` to a `&mut` before it. - Some(p) if self_ty.is_ref() || crate::lower::iter::through_mut(self_ty) => { + // points at, read through the `rt.Place` of each `&mut` to a `&mut` before it, + // or to a box of a trait object, which JS can't write in place. + Some(p) + if self_ty.is_ref() + || crate::lower::iter::through_mut(self_ty) + || self_ty.boxed_ty().is_some_and(|t| t.is_trait()) => + { let value = self.place_expr(tcx.mk_place_deref(p), span, &mut pre); let (last, value) = crate::lower::iter::peel_mut(self_ty, value); (last.builtin_deref(true).unwrap_or(last), value) diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index d0a8571d9..92ae43953 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -298,6 +298,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let value = self.operand(op, span, out); self.b.dyn_future(from, to, value, span) } + // An iterator, as the `rt.Iter` a `dyn Iterator` is. + PointerCoercion::Unsize + if unsized_to(from, to).is_some_and(|part| self.b.t.dyn_iter(part).is_some()) => + { + let value = self.operand(op, span, out); + self.b.dyn_iter(from, to, value, span) + } PointerCoercion::Unsize if unsized_to(from, to).is_some_and(|t| t.is_trait()) => { let value = self.operand(op, span, out); self.b.object_of(value, from, to, span) diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index a04f1c783..7e1b8a659 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1476,6 +1476,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::Pat(base, _) => self.ty(*base, span), ty::Coroutine(_, args) => self.future_ty(args.as_coroutine().return_ty(), span), ty::Dynamic(..) if let Some(output) = self.dyn_future(ty) => self.future_ty(output, span), + ty::Dynamic(..) if let Some(item) = self.dyn_iter(ty) => Type::Rt("Iter", vec![self.ty(item, span)]), // `&dyn Debug` and `&dyn Display` are values ready to write. ty::Dynamic(preds, ..) if preds From fd661b15ca550ea8143471a1ab79ac1807724972 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 05:02:40 -0800 Subject: [PATCH 03/10] style: format --- ts/fixtures/lang/src/dyns.rs | 6 +++++- ts/src/lower/iter.rs | 15 +++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index 4e9473d1f..4e9e578b8 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -634,7 +634,11 @@ pub fn iters(data: &[u8], out: &mut Vec) { let odd = |v: &u8| (v % 2 == 1).then_some(*v); put( out, - &format!("{:?} {:?} ", scan(&map, u64::from(a % 8), false, odd), scan(&map, u64::from(b % 8), true, odd)), + &format!( + "{:?} {:?} ", + scan(&map, u64::from(a % 8), false, odd), + scan(&map, u64::from(b % 8), true, odd) + ), ); put(out, &format!("{} ", lens(&mut rest.iter()))); diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 9efe64b60..ea9963f27 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -55,10 +55,7 @@ impl<'tcx> Translator<'_, 'tcx> { }; let principal = preds.principal_def_id()?; let name = library::std_path(self.tcx, principal).pop()?; - if !matches!( - name.as_str(), - "Iterator" | "DoubleEndedIterator" | "ExactSizeIterator" - ) { + if !matches!(name.as_str(), "Iterator" | "DoubleEndedIterator" | "ExactSizeIterator") { return None; } self.iter_item(ty) @@ -146,7 +143,10 @@ impl<'tcx> Body<'_, '_, 'tcx> { } // Another trait object, whose object has its trait's methods and is no `rt.Iter`. if pointee.is_trait() { - return self.err(span, format!("an upcast from `{pointee}` to `{to}`, which is not supported yet")); + return self.err( + span, + format!("an upcast from `{pointee}` to `{to}`, which is not supported yet"), + ); } // A census over type parameters: only an instance decides what the pointee is. if self.t.polymorphic() && pointee.has_param() { @@ -159,7 +159,10 @@ impl<'tcx> Body<'_, '_, 'tcx> { }; match into_iter(self, ty, value, span) { Some(it) => Expr::rt("iterDyn", vec![it]), - None => self.err(span, format!("a coercion of `{from}` to `{to}`, which is not supported yet")), + None => self.err( + span, + format!("a coercion of `{from}` to `{to}`, which is not supported yet"), + ), } } } From 9594f40ab41d74036e4ce543b5e48477f276f005 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 05:54:18 -0800 Subject: [PATCH 04/10] chore(quest): drop dyn Iterator from the trait objects quest's summary Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quest/m3/README.md b/quest/m3/README.md index 5c3286027..2b3e69fa5 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -17,7 +17,7 @@ generic std gap is planned here even when moq never calls it. - [Relink without rebuilding](/quest/m3/relink.md) - measure upstream's skip of dependency-only rebuilds on moq's edits once it reaches nightly - [Unsafe code the translator can model](/quest/m3/ts-unsafe.md) - a dependency using only `unsafe` the translator can model translates from source - [The capacity of a BytesMut](/quest/m3/ts-bytes-capacity.md) - `BytesMut::capacity` and what reads it translate with `bytes`' values -- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error`, `dyn Iterator`, `dyn Buf`, and `Arc` translate, so a consumer neither rewrites nor binds them +- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error`, `dyn Buf`, and `Arc` translate, so a consumer neither rewrites nor binds them - [Placeholders written to a crate's own fmt::Write](/quest/m3/ts-fmt-calls.md) - `write!` with placeholders into a crate's own writer translates, its `write_str` seeing each call std makes - [Waiting: type-check in process with tsrs](/quest/m3/ts-tsrs.md) - rust2-ts checks bindings through tsrs instead of `tsc` once it is a stable library - [Drop order in std's folds and iterators](/quest/m3/ts-drop-order.md) - the values that drop still refused in std's consumers, flattens, and collects drop as std's do From 191305a720e0e7e16460d0be3d050ac8269f2572 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:12:02 -0800 Subject: [PATCH 05/10] fix(ts): read a box of the crate's own iterator as the iterator std's Iterator for Box forwards to I, which JS holds as itself: a boxed crate iterator reached rt.iterDyn, adaptors, and next as its raw object. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/dyns.ts | 19 ++++++++++++++++--- ts/fixtures/lang/src/dyns.rs | 15 +++++++++++++++ ts/src/lower/iter.rs | 23 +++++++++++++++++++++++ ts/src/lower/mir/call.rs | 4 ++-- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index 9502c195b..0709a54f6 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -816,13 +816,26 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.u32Display(sum, f)]; fmt_put(out, rt.fmtFormat([args12[0], " "])); dropped(out); + const nested: rt.Iter = rt.iterDyn(rt.iterFrom(new dyns_Countdown.Countdown(rt.u8Rem(a, 8)), (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop)); + const inner: dyns_Countdown.Countdown = new dyns_Countdown.Countdown(rt.u8Rem(b, 8)); + const first: rt.u8 | undefined = rt.iterToOption(rt.iterDyn(rt.iterFrom(inner, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it))).next()); + const second: rt.u8 | undefined = rt.iterToOption(dyns_Countdown.next$iter_Iterator(inner)); + const mapped: rt.ByteVec = rt.iterToByteVec(rt.iterMap(rt.iterFrom(new dyns_Countdown.Countdown(rt.u8Rem(b, 8)), (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop), (x3: rt.u8): rt.u8 => { + return x3 ^ 1; + }), "next"); + const _215: rt.ByteVec = rt.iterToByteVec(nested, "next"); + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_215), f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(second, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(mapped), f, rt.u8Debug)]; + fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1], " ", args14[2], " ", args14[3], " "])); + dropped(out); + inner[Symbol.dispose](); + dropped(out); const split: Chunks.Chunks = new Chunks.of$dyns_Lease({ _0: rt.byteSliceToByteVec(rt.byteSliceRange(data, 2)), _1: rt.usizeAdd(rt.u8Rem(b, 4) as rt.usize, 1) }); const lens_: rt.usize[] = rt.iterToArray(rt.iterMap(split.chunks$Chunks(), (a0: rt.ByteSlice): rt.usize => { return a0.length; }), "next", 4); - const _206: rt.usize = rt.ownedCount(split.chunks$Chunks()); - const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_206, f)]; - fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1]])); + const _250: rt.usize = rt.ownedCount(split.chunks$Chunks()); + const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_250, f)]; + fmt_put(out, rt.fmtFormat([args16[0], " ", args16[1]])); } } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index 5c2da86b9..cfd262b8f 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -670,6 +670,21 @@ pub fn iters(data: &[u8], out: &mut Vec) { } put(out, &format!("{sum} ")); dropped(out); + // A box of the crate's own, which std's `Iterator` for `Box` forwards to: boxed again, + // borrowed, read itself, and by an adaptor. + let nested: Box> = Box::new(Box::new(Countdown(a % 8))); + let mut inner = Box::new(Countdown(b % 8)); + let lent: &mut dyn Iterator = &mut inner; + let first = lent.next(); + let second = inner.next(); + let mapped: Vec = Box::new(Countdown(b % 8)).map(|x| x ^ 1).collect(); + put( + out, + &format!("{:?} {first:?} {second:?} {mapped:?} ", nested.collect::>()), + ); + dropped(out); + drop(inner); + dropped(out); // Returned through a trait object of the crate's own trait. let split: &dyn Chunks = &Lease(rest.to_vec(), usize::from(b % 4) + 1); diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 6f5c3232a..5f1769e6b 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -199,6 +199,7 @@ impl<'tcx> Body<'_, '_, 'tcx> { pub fn into_iter<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, span: Span) -> Option { let tcx = b.t.tcx; let (ty, value) = peel_mut(ty, value); + let (ty, value) = unbox_iter(b, ty, value); let (ty, value) = match unbox_ref(tcx, ty) { unboxed if unboxed != ty => (unboxed, b.t.pointee_value(ty, value)), _ => (ty, value), @@ -406,6 +407,27 @@ pub fn peel_mut<'tcx>(mut ty: Ty<'tcx>, mut value: Expr) -> (Ty<'tcx>, Expr) { (ty, value) } +/// `value`, a `ty`, read through each box of an iterator, which std's `Iterator` for `Box` +/// forwards to and JS holds as itself, and each `&mut` to one, which reads through its +/// `rt.Place` to a `&mut I`. A box of a trait object is the `rt.Iter` itself. +pub fn unbox_iter<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr) -> (Ty<'tcx>, Expr) { + let tcx = b.t.tcx; + let held = |ty: Ty<'tcx>| { + ty.boxed_ty() + .filter(|held| !held.is_trait() && b.t.iter_item(*held).is_some()) + }; + match *ty.kind() { + ty::Ref(region, inner, ty::Mutability::Mut) if let Some(held) = held(inner) => { + let value = b.t.pointee_value(ty, value); + unbox_iter(b, Ty::new_mut_ref(tcx, region, held), value) + } + _ => match held(ty) { + Some(held) => unbox_iter(b, held, value), + None => (ty, value), + }, + } +} + /// Refuse iterating `ty`, an iterator, if a binding stands in for it: it is no `rt.Iter`, /// which std's adaptors and `for` take every iterator but the crate's own for. fn not_bound<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span) { @@ -535,6 +557,7 @@ pub fn call<'tcx>( not_bound(b, self_ty, span); let mut values = args.into_iter(); let (self_ty, recv) = peel_mut(self_ty, values.next()?); + let (self_ty, recv) = unbox_iter(b, self_ty, recv); let rest: Vec = values.collect(); // What a consumer returns for `None`, after its other arguments, where that is `rt.None`. let none = b.t.none_arg(item); diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 5fd7f518a..cd07d9b95 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -64,11 +64,11 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let (iter_ty, recv) = match args[0].node.place() { // Through std's `Iterator` for `&mut I` and `Box`: what the last `&mut` // points at, read through the `rt.Place` of each `&mut` to a `&mut` before it, - // or to a box of a trait object, which JS can't write in place. + // or to a box, which JS can't write in place. Some(p) if self_ty.is_ref() || crate::lower::iter::through_mut(self_ty) - || self_ty.boxed_ty().is_some_and(|t| t.is_trait()) => + || self_ty.boxed_ty().is_some() => { let value = self.place_expr(tcx.mk_place_deref(p), span, &mut pre); let (last, value) = crate::lower::iter::peel_mut(self_ty, value); From ef3bc43f891cb8f45cd67950c9762ab4f602df83 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:30:15 -0800 Subject: [PATCH 06/10] fix(ts): read a borrowed box of a trait object iterator through its place Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/dyns.ts | 13 ++++++++----- ts/fixtures/lang/src/dyns.rs | 11 +++++++++-- ts/src/lower/iter.rs | 7 ++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index 0709a54f6..b9397346a 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -820,12 +820,15 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { const inner: dyns_Countdown.Countdown = new dyns_Countdown.Countdown(rt.u8Rem(b, 8)); const first: rt.u8 | undefined = rt.iterToOption(rt.iterDyn(rt.iterFrom(inner, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it))).next()); const second: rt.u8 | undefined = rt.iterToOption(dyns_Countdown.next$iter_Iterator(inner)); + const erased: rt.Iter = rt.iterDyn(new rt.Range(rt.u8Rem(a, 4), rt.u8Rem(b, 8))); + const third: rt.u8 | undefined = rt.iterToOption(rt.iterDyn(rt.iterBorrow(erased)).next()); const mapped: rt.ByteVec = rt.iterToByteVec(rt.iterMap(rt.iterFrom(new dyns_Countdown.Countdown(rt.u8Rem(b, 8)), (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop), (x3: rt.u8): rt.u8 => { return x3 ^ 1; }), "next"); - const _215: rt.ByteVec = rt.iterToByteVec(nested, "next"); - const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_215), f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(second, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(mapped), f, rt.u8Debug)]; - fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1], " ", args14[2], " ", args14[3], " "])); + const _225: rt.ByteVec = rt.iterToByteVec(nested, "next"); + const _227: rt.usize = rt.ownedCount(erased); + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_225), f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(second, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(third, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(mapped), f, rt.u8Debug), (f: rt.Formatter) => rt.usizeDisplay(_227, f)]; + fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1], " ", args14[2], " ", args14[3], " ", args14[4], " ", args14[5], " "])); dropped(out); inner[Symbol.dispose](); dropped(out); @@ -833,8 +836,8 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { const lens_: rt.usize[] = rt.iterToArray(rt.iterMap(split.chunks$Chunks(), (a0: rt.ByteSlice): rt.usize => { return a0.length; }), "next", 4); - const _250: rt.usize = rt.ownedCount(split.chunks$Chunks()); - const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_250, f)]; + const _266: rt.usize = rt.ownedCount(split.chunks$Chunks()); + const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_266, f)]; fmt_put(out, rt.fmtFormat([args16[0], " ", args16[1]])); } } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index cfd262b8f..730f9a081 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -671,16 +671,23 @@ pub fn iters(data: &[u8], out: &mut Vec) { put(out, &format!("{sum} ")); dropped(out); // A box of the crate's own, which std's `Iterator` for `Box` forwards to: boxed again, - // borrowed, read itself, and by an adaptor. + // borrowed, read itself, and by an adaptor; and a box of a trait object, borrowed. let nested: Box> = Box::new(Box::new(Countdown(a % 8))); let mut inner = Box::new(Countdown(b % 8)); let lent: &mut dyn Iterator = &mut inner; let first = lent.next(); let second = inner.next(); + let mut erased: Box> = Box::new(a % 4..b % 8); + let reborrowed: &mut dyn Iterator = &mut erased; + let third = reborrowed.next(); let mapped: Vec = Box::new(Countdown(b % 8)).map(|x| x ^ 1).collect(); put( out, - &format!("{:?} {first:?} {second:?} {mapped:?} ", nested.collect::>()), + &format!( + "{:?} {first:?} {second:?} {third:?} {mapped:?} {} ", + nested.collect::>(), + erased.count() + ), ); dropped(out); drop(inner); diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 5f1769e6b..7d472005a 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -412,16 +412,13 @@ pub fn peel_mut<'tcx>(mut ty: Ty<'tcx>, mut value: Expr) -> (Ty<'tcx>, Expr) { /// `rt.Place` to a `&mut I`. A box of a trait object is the `rt.Iter` itself. pub fn unbox_iter<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr) -> (Ty<'tcx>, Expr) { let tcx = b.t.tcx; - let held = |ty: Ty<'tcx>| { - ty.boxed_ty() - .filter(|held| !held.is_trait() && b.t.iter_item(*held).is_some()) - }; + let held = |ty: Ty<'tcx>| ty.boxed_ty().filter(|held| b.t.iter_item(*held).is_some()); match *ty.kind() { ty::Ref(region, inner, ty::Mutability::Mut) if let Some(held) = held(inner) => { let value = b.t.pointee_value(ty, value); unbox_iter(b, Ty::new_mut_ref(tcx, region, held), value) } - _ => match held(ty) { + _ => match held(ty).filter(|held| !held.is_trait()) { Some(held) => unbox_iter(b, held, value), None => (ty, value), }, From df1213d6192fe526a73b60d816e6efe68a19e29c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:48:25 -0800 Subject: [PATCH 07/10] fix(ts): read a borrowed box of boxes through its place once Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/dyns.ts | 15 +++++++++------ ts/fixtures/lang/src/dyns.rs | 11 ++++++++--- ts/src/lower/iter.rs | 19 +++++++++++-------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index b9397346a..565d5d77c 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -822,13 +822,16 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { const second: rt.u8 | undefined = rt.iterToOption(dyns_Countdown.next$iter_Iterator(inner)); const erased: rt.Iter = rt.iterDyn(new rt.Range(rt.u8Rem(a, 4), rt.u8Rem(b, 8))); const third: rt.u8 | undefined = rt.iterToOption(rt.iterDyn(rt.iterBorrow(erased)).next()); + const doubled: dyns_Countdown.Countdown = new dyns_Countdown.Countdown(rt.u8Rem(a, 8)); + const fourth: rt.u8 | undefined = rt.iterToOption(rt.iterDyn(rt.iterFrom(doubled, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it))).next()); const mapped: rt.ByteVec = rt.iterToByteVec(rt.iterMap(rt.iterFrom(new dyns_Countdown.Countdown(rt.u8Rem(b, 8)), (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop), (x3: rt.u8): rt.u8 => { return x3 ^ 1; }), "next"); - const _225: rt.ByteVec = rt.iterToByteVec(nested, "next"); - const _227: rt.usize = rt.ownedCount(erased); - const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_225), f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(second, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(third, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(mapped), f, rt.u8Debug), (f: rt.Formatter) => rt.usizeDisplay(_227, f)]; - fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1], " ", args14[2], " ", args14[3], " ", args14[4], " ", args14[5], " "])); + const _233: rt.ByteVec = rt.iterToByteVec(nested, "next"); + const _235: rt.usize = rt.ownedCount(erased); + const _238: rt.usize = rt.ownedCount(rt.iterFrom(doubled, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop)); + const args14: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(rt.byteVecView(_233), f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(first, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(second, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(third, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(fourth, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(mapped), f, rt.u8Debug), (f: rt.Formatter) => rt.usizeDisplay(_235, f), (f: rt.Formatter) => rt.usizeDisplay(_238, f)]; + fmt_put(out, rt.fmtFormat([args14[0], " ", args14[1], " ", args14[2], " ", args14[3], " ", args14[4], " ", args14[5], " ", args14[6], " ", args14[7], " "])); dropped(out); inner[Symbol.dispose](); dropped(out); @@ -836,8 +839,8 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { const lens_: rt.usize[] = rt.iterToArray(rt.iterMap(split.chunks$Chunks(), (a0: rt.ByteSlice): rt.usize => { return a0.length; }), "next", 4); - const _266: rt.usize = rt.ownedCount(split.chunks$Chunks()); - const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_266, f)]; + const _280: rt.usize = rt.ownedCount(split.chunks$Chunks()); + const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_280, f)]; fmt_put(out, rt.fmtFormat([args16[0], " ", args16[1]])); } } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index 730f9a081..1aad61657 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -671,7 +671,8 @@ pub fn iters(data: &[u8], out: &mut Vec) { put(out, &format!("{sum} ")); dropped(out); // A box of the crate's own, which std's `Iterator` for `Box` forwards to: boxed again, - // borrowed, read itself, and by an adaptor; and a box of a trait object, borrowed. + // borrowed, read itself, and by an adaptor; and a box of a box and a box of a trait + // object, borrowed. let nested: Box> = Box::new(Box::new(Countdown(a % 8))); let mut inner = Box::new(Countdown(b % 8)); let lent: &mut dyn Iterator = &mut inner; @@ -680,13 +681,17 @@ pub fn iters(data: &[u8], out: &mut Vec) { let mut erased: Box> = Box::new(a % 4..b % 8); let reborrowed: &mut dyn Iterator = &mut erased; let third = reborrowed.next(); + let mut doubled = Box::new(Box::new(Countdown(a % 8))); + let deep: &mut dyn Iterator = &mut doubled; + let fourth = deep.next(); let mapped: Vec = Box::new(Countdown(b % 8)).map(|x| x ^ 1).collect(); put( out, &format!( - "{:?} {first:?} {second:?} {third:?} {mapped:?} {} ", + "{:?} {first:?} {second:?} {third:?} {fourth:?} {mapped:?} {} {} ", nested.collect::>(), - erased.count() + erased.count(), + doubled.count() ), ); dropped(out); diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 7d472005a..007db899b 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -408,20 +408,23 @@ pub fn peel_mut<'tcx>(mut ty: Ty<'tcx>, mut value: Expr) -> (Ty<'tcx>, Expr) { } /// `value`, a `ty`, read through each box of an iterator, which std's `Iterator` for `Box` -/// forwards to and JS holds as itself, and each `&mut` to one, which reads through its -/// `rt.Place` to a `&mut I`. A box of a trait object is the `rt.Iter` itself. +/// forwards to and JS holds as itself, and a `&mut` to one, which reads through its +/// `rt.Place` once to a `&mut I`. A box of a trait object is the `rt.Iter` itself. pub fn unbox_iter<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr) -> (Ty<'tcx>, Expr) { let tcx = b.t.tcx; let held = |ty: Ty<'tcx>| ty.boxed_ty().filter(|held| b.t.iter_item(*held).is_some()); + let unboxed = |mut ty: Ty<'tcx>, trait_box: bool| { + while let Some(inner) = held(ty).filter(|inner| trait_box || !inner.is_trait()) { + ty = inner; + } + ty + }; match *ty.kind() { - ty::Ref(region, inner, ty::Mutability::Mut) if let Some(held) = held(inner) => { + ty::Ref(region, inner, ty::Mutability::Mut) if held(inner).is_some() => { let value = b.t.pointee_value(ty, value); - unbox_iter(b, Ty::new_mut_ref(tcx, region, held), value) + (Ty::new_mut_ref(tcx, region, unboxed(inner, true)), value) } - _ => match held(ty).filter(|held| !held.is_trait()) { - Some(held) => unbox_iter(b, held, value), - None => (ty, value), - }, + _ => (unboxed(ty, false), value), } } From 1d5bee91f2b695391452f4c19e964560eebc788c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 17:57:26 -0800 Subject: [PATCH 08/10] fix(ts): peel an iterator's references and boxes in one pass, in any order A shared `&Box` as a `&dyn Iterator` read a place it never was, and a `Box<&mut &mut I>` borrowed kept its references, as references and boxes were peeled in two passes. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/dyns.ts | 23 +++++++++-- ts/fixtures/lang/src/dyns.rs | 15 +++++++ ts/src/lower/iter.rs | 66 ++++++++++++++++++++----------- ts/src/lower/mir/call.rs | 11 ++---- 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index 565d5d77c..eb858aef4 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -835,13 +835,30 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { dropped(out); inner[Symbol.dispose](); dropped(out); + const _262: [dyns_Countdown.Countdown, rt.Range] = [new dyns_Countdown.Countdown(rt.u8Rem(a, 8)), new rt.Range(rt.u8Rem(a, 4), rt.u8Rem(b, 8))]; + const own2: dyns_Countdown.Countdown = _262[0]; + const range: rt.Range = _262[1]; + const hints: [rt.usize, rt.usize | undefined][] = rt.arrayMap([rt.iterDyn(rt.iterFrom(own2, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it))), rt.iterDyn(rt.iterBorrow(range))], (it: rt.Iter): [rt.usize, rt.usize | undefined] => { + return it.sizeHint(); + }); + let held: dyns_Countdown.Countdown = new dyns_Countdown.Countdown(rt.u8Rem(b, 8)); + let lent2: rt.Place = rt.place(() => held, (value4) => held = value4); + const layered: rt.Place> = rt.place(() => lent2, (value5) => lent2 = value5); + const fifth: rt.u8 | undefined = rt.iterToOption(rt.iterDyn(rt.iterFrom(layered.v.v, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it))).next()); + const sixth: rt.u8 | undefined = rt.iterToOption(dyns_Countdown.next$iter_Iterator(layered.v.v)); + const _301: rt.usize = rt.ownedCount(rt.iterFrom(held, (it: dyns_Countdown.Countdown) => dyns_Countdown.next$iter_Iterator(it), rt.drop)); + const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(hints, f, (value: [rt.usize, rt.usize | undefined], f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.usizeDebug(value[0], f), (f: rt.Formatter) => rt.fmtOption(value[1], f, rt.usizeDebug)], f)), (f: rt.Formatter) => rt.fmtOption(fifth, f, rt.u8Debug), (f: rt.Formatter) => rt.fmtOption(sixth, f, rt.u8Debug), (f: rt.Formatter) => rt.usizeDisplay(_301, f)]; + fmt_put(out, rt.fmtFormat([args16[0], " ", args16[1], " ", args16[2], " ", args16[3], " "])); + dropped(out); + own2[Symbol.dispose](); + dropped(out); const split: Chunks.Chunks = new Chunks.of$dyns_Lease({ _0: rt.byteSliceToByteVec(rt.byteSliceRange(data, 2)), _1: rt.usizeAdd(rt.u8Rem(b, 4) as rt.usize, 1) }); const lens_: rt.usize[] = rt.iterToArray(rt.iterMap(split.chunks$Chunks(), (a0: rt.ByteSlice): rt.usize => { return a0.length; }), "next", 4); - const _280: rt.usize = rt.ownedCount(split.chunks$Chunks()); - const args16: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_280, f)]; - fmt_put(out, rt.fmtFormat([args16[0], " ", args16[1]])); + const _336: rt.usize = rt.ownedCount(split.chunks$Chunks()); + const args18: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_336, f)]; + fmt_put(out, rt.fmtFormat([args18[0], " ", args18[1]])); } } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index 1aad61657..b8078279a 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -697,6 +697,21 @@ pub fn iters(data: &[u8], out: &mut Vec) { dropped(out); drop(inner); dropped(out); + // Shared boxes, and boxes and references in any order, each of which std forwards through. + let (own, range) = (Box::new(Countdown(a % 8)), Box::new(a % 4..b % 8)); + let seen: [&dyn Iterator; 2] = [&own, &range]; + let hints = seen.map(|it| it.size_hint()); + let mut held = Box::new(Countdown(b % 8)); + let mut lent = &mut held; + let mut layered: Box<&mut &mut Box> = Box::new(&mut lent); + let through: &mut dyn Iterator = &mut layered; + let fifth = through.next(); + let sixth = layered.next(); + drop(layered); + put(out, &format!("{hints:?} {fifth:?} {sixth:?} {} ", held.count())); + dropped(out); + drop(own); + dropped(out); // Returned through a trait object of the crate's own trait. let split: &dyn Chunks = &Lease(rest.to_vec(), usize::from(b % 4) + 1); diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 6bdc0650b..531907044 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -178,10 +178,12 @@ impl<'tcx> Body<'_, '_, 'tcx> { if self.t.polymorphic() && pointee.has_param() { return Expr::undefined(); } - // What a box owns iterates as itself, and what a reference borrows as a `&mut` does. - let ty = match from.is_box() { - true => pointee, - false => Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, pointee), + // What a box owns iterates as itself, and what a reference borrows as a `&mut` does, + // which `into_iter` makes of a shared one. + let ty = match from.kind() { + _ if from.is_box() => pointee, + ty::Ref(..) => from, + _ => Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, pointee), }; match into_iter(self, ty, value, span) { Some(it) => Expr::rt("iterDyn", vec![it]), @@ -198,8 +200,7 @@ impl<'tcx> Body<'_, '_, 'tcx> { /// each, and the crate's own iterator what drops it. pub fn into_iter<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr, span: Span) -> Option { let tcx = b.t.tcx; - let (ty, value) = peel_mut(ty, value); - let (ty, value) = unbox_iter(b, ty, value); + let (ty, value) = peel_iter(b, ty, value); let (ty, value) = match unbox_ref(tcx, ty) { unboxed if unboxed != ty => (unboxed, b.t.pointee_value(ty, value)), _ => (ty, value), @@ -407,24 +408,46 @@ pub fn peel_mut<'tcx>(mut ty: Ty<'tcx>, mut value: Expr) -> (Ty<'tcx>, Expr) { (ty, value) } -/// `value`, a `ty`, read through each box of an iterator, which std's `Iterator` for `Box` -/// forwards to and JS holds as itself, and a `&mut` to one, which reads through its -/// `rt.Place` once to a `&mut I`. A box of a trait object is the `rt.Iter` itself. -pub fn unbox_iter<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, value: Expr) -> (Ty<'tcx>, Expr) { +/// `value`, a `ty`, read through each `&`, `&mut`, and box, in any order, that std's +/// `Iterator` for `&mut I` and `Box` forward through, down to the iterator: a `&mut` to it +/// and that `&mut`'s JS value where a reference was read, else the iterator itself, and a +/// box of a trait object where none was, which drops it. JS holds a box as what it holds. +pub fn peel_iter<'tcx>(b: &Body<'_, '_, 'tcx>, mut ty: Ty<'tcx>, mut value: Expr) -> (Ty<'tcx>, Expr) { let tcx = b.t.tcx; - let held = |ty: Ty<'tcx>| ty.boxed_ty().filter(|held| b.t.iter_item(*held).is_some()); - let unboxed = |mut ty: Ty<'tcx>, trait_box: bool| { - while let Some(inner) = held(ty).filter(|inner| trait_box || !inner.is_trait()) { + let held = |ty: Ty<'tcx>, borrowed: bool| { + ty.boxed_ty() + .filter(|held| b.t.iter_item(*held).is_some() && (borrowed || !held.is_trait())) + }; + let mut borrowed = false; + loop { + if let Some(inner) = held(ty, borrowed) { ty = inner; + continue; } - ty - }; - match *ty.kind() { - ty::Ref(region, inner, ty::Mutability::Mut) if held(inner).is_some() => { - let value = b.t.pointee_value(ty, value); - (Ty::new_mut_ref(tcx, region, unboxed(inner, true)), value) + let ty::Ref(region, inner, mutability) = *ty.kind() else { + return (ty, value); + }; + let mut end = inner; + while let Some(held) = held(end, true) { + end = held; } - _ => (unboxed(ty, false), value), + // A reference to what is no iterator, such as a slice, iterates as it is. + if b.t.iter_item(end).is_none() { + return (ty, value); + } + borrowed = true; + if end.is_ref() { + value = b.t.pointee_value(ty, value); + ty = inner; + continue; + } + // The last reference: a `&mut` to the iterator is the place of the boxes before it, as + // JS erases them, or the iterator itself, as a shared reference is too. + return match (b.t.needs_place(end), mutability) { + (true, ty::Mutability::Mut) => (Ty::new_mut_ref(tcx, region, end), value), + (false, _) => (Ty::new_mut_ref(tcx, region, end), b.t.pointee_value(ty, value)), + (true, ty::Mutability::Not) => (ty, value), + }; } } @@ -556,8 +579,7 @@ pub fn call<'tcx>( let item = b.t.iter_item(self_ty.peel_refs())?; not_bound(b, self_ty, span); let mut values = args.into_iter(); - let (self_ty, recv) = peel_mut(self_ty, values.next()?); - let (self_ty, recv) = unbox_iter(b, self_ty, recv); + let (self_ty, recv) = peel_iter(b, self_ty, values.next()?); let rest: Vec = values.collect(); // What a consumer returns for `None`, after its other arguments, where that is `rt.None`. let none = b.t.none_arg(item); diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index cd07d9b95..33ca98707 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -63,15 +63,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let self_ty = gargs.skip_binder().type_at(0); let (iter_ty, recv) = match args[0].node.place() { // Through std's `Iterator` for `&mut I` and `Box`: what the last `&mut` - // points at, read through the `rt.Place` of each `&mut` to a `&mut` before it, - // or to a box, which JS can't write in place. - Some(p) - if self_ty.is_ref() - || crate::lower::iter::through_mut(self_ty) - || self_ty.boxed_ty().is_some() => - { + // points at, read through each reference and box before it. + Some(p) if self_ty.is_ref() || self_ty.boxed_ty().is_some() => { let value = self.place_expr(tcx.mk_place_deref(p), span, &mut pre); - let (last, value) = crate::lower::iter::peel_mut(self_ty, value); + let (last, value) = crate::lower::iter::peel_iter(self.b, self_ty, value); (last.builtin_deref(true).unwrap_or(last), value) } _ => (self_ty, self.operand_frozen(&args[0].node, span, &mut pre)), From 8231fa0db09b917a7f7382a0879b368a985ded1b Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 18:07:01 -0800 Subject: [PATCH 09/10] fix(ts): refuse a raw pointer to an iterator as one to a dyn Iterator Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.md | 14 ++++++++++++-- ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/lib.rs | 7 +++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/iter.rs | 11 ++++++++--- 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 47786b07c..2d4b568ff 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,7 +1,7 @@ # Census of `reject-fixture` -- Refusals: 258 -- Function instances: 294 +- Refusals: 259 +- Function instances: 295 - Generic functions no instance reached, lowered over their type parameters: 13 ## By construct @@ -61,6 +61,7 @@ | 1 | `offset_from` of pointers to `()`, which has no bytes | fixtures/reject/src/lib.rs:2107:13 | | 1 | `offset_from` of pointers to `_`, which JS keeps as an object that knows no array it is in | fixtures/reject/src/lib.rs:2100:36 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:68:2 | +| 1 | a coercion of `_` to `_`, which is not supported yet | fixtures/reject/src/lib.rs:2401:40 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:234:2 | | 1 | a coercion to `_`, which is not supported yet | fixtures/reject/src/lib.rs:420:30 | | 1 | a constant of type `_` | fixtures/reject/src/lib.rs:2301:34 | @@ -161,6 +162,8 @@ The type or module each refused type or path belongs to. | 1 | `(u16, InTuple)` | fixtures/reject/src/lib.rs:1281:33 | | 1 | `*const [u8; 4]` | fixtures/reject/src/lib.rs:1651:5 | | 1 | `*mut [u8; 4]` | fixtures/reject/src/lib.rs:1647:41 | +| 1 | `*mut dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:2401:40 | +| 1 | `*mut std::ops::Range` | fixtures/reject/src/lib.rs:2401:40 | | 1 | `Aliased` | fixtures/reject/src/lib.rs:1055:29 | | 1 | `Assigned` | fixtures/reject/src/lib.rs:1947:20 | | 1 | `Bare` | fixtures/reject/src/lib.rs:663:70 | @@ -320,6 +323,7 @@ The type or module each refused type or path belongs to. | 1 | a cast from `_` to `_` | `u64`, `f16` | fixtures/reject/src/lib.rs:68:2 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `[u32; 2]` | fixtures/reject/src/lib.rs:2112:13 | | 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u8`, `Halves` | fixtures/reject/src/lib.rs:2268:16 | +| 1 | a coercion of `_` to `_`, which is not supported yet | `*mut std::ops::Range`, `*mut dyn std::iter::Iterator` | fixtures/reject/src/lib.rs:2401:40 | | 1 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:234:2 | | 1 | a coercion to `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:420:30 | | 1 | a constant of type `_` | `std::mem::ManuallyDrop` | fixtures/reject/src/lib.rs:2301:34 | @@ -940,6 +944,12 @@ The type or module each refused type or path belongs to. +
a coercion of `_` to `_`, which is not supported yet (1) + +- fixtures/reject/src/lib.rs:2401:40: a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, which is not supported yet + +
+
a coercion to `_` (1) - fixtures/reject/src/lib.rs:234:2: a coercion to `std::sync::Arc<[u8]>` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 4609a8241..bc0ba0f3d 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -239,6 +239,7 @@ memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the byte memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2284:17 memory from `alloc` cast to pointers to `u32` and to `i32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2285:21 memory from `alloc` cast to pointers to `i32` and to `u32`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:2287:17 +a coercion of `*mut std::ops::Range` to `*mut dyn std::iter::Iterator`, which is not supported yet at fixtures/reject/src/lib.rs:2401:40 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:125:2 overriding `DoubleEndedIterator::nth_back`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:150:2 a raw pointer to `std::cell::UnsafeCell<(u16, u16)>` in `Escaped`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:1007:43 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index e9016c748..3bf19eefa 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -2394,3 +2394,10 @@ fn given_slots(fill: [T; 2]) -> u8 { } tagged_slots!(slots) } + +/// A raw pointer to an iterator as one to a trait object, whose `rt.Place` no `rt.Iter` is. +pub fn raw_dyn_iter(n: u8) -> Option { + let mut range = 0..n; + let p: *mut dyn Iterator = &raw mut range; + unsafe { (&mut *p).next() } +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index a0fda861c..b3961fdb3 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -185,5 +185,6 @@ roots = [ "dropped_slots", "ranked_slots", "touched_slots", + "raw_dyn_iter", ] printable = ["Meters"] diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 531907044..2fd91c696 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -160,7 +160,6 @@ impl<'tcx> Body<'_, '_, 'tcx> { /// std's vtable has and else by `next`, as std's defaults read a trait object, and drops /// it if the box owned it. An upcast from another is the same `rt.Iter`. pub fn dyn_iter(&mut self, from: Ty<'tcx>, to: Ty<'tcx>, value: Expr, span: Span) -> Expr { - let tcx = self.t.tcx; let Some(pointee) = from.builtin_deref(true).filter(|_| to.builtin_deref(true).is_some()) else { return self.err(span, format!("a coercion to `{to}`, which is not supported yet")); }; @@ -179,11 +178,17 @@ impl<'tcx> Body<'_, '_, 'tcx> { return Expr::undefined(); } // What a box owns iterates as itself, and what a reference borrows as a `&mut` does, - // which `into_iter` makes of a shared one. + // which `into_iter` makes of a shared one. A raw pointer can be an `rt.Place`, which + // no `rt.Iter` reads through. let ty = match from.kind() { _ if from.is_box() => pointee, ty::Ref(..) => from, - _ => Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, pointee), + _ => { + return self.err( + span, + format!("a coercion of `{from}` to `{to}`, which is not supported yet"), + ); + } }; match into_iter(self, ty, value, span) { Some(it) => Expr::rt("iterDyn", vec![it]), From 237cd7ecd3120609ee8be5846bc467aad6d16cf1 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 18:25:08 -0800 Subject: [PATCH 10/10] fix(ts): pick a boxed iterator's specializations by the box A box of a `TrustedLen` zip collected through the zip's own fold, though std reads a box by `next`. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/expected/dyns.ts | 22 +++++++++++++++++++--- ts/fixtures/lang/src/dyns.rs | 16 ++++++++++++++++ ts/src/lower/iter.rs | 12 +++++++----- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/ts/fixtures/lang/expected/dyns.ts b/ts/fixtures/lang/expected/dyns.ts index eb858aef4..39b0d541a 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -852,13 +852,29 @@ export function iters(data: rt.ByteSlice, out: rt.ByteVec): void { dropped(out); own2[Symbol.dispose](); dropped(out); + const seen2: rt.RefCell = new rt.RefCell(new rt.ByteVec()); + const zipped: () => rt.Iter<[rt.u8, rt.u8]> = (): rt.Iter<[rt.u8, rt.u8]> => { + return rt.iterZip(rt.iterMap(rt.iterChain(new rt.Range(0, rt.u8Rem(a, 4)), new rt.Range(2, 2)), (x4: rt.u8): rt.u8 => { + const _66: rt.RefCell = rt.refCellBorrowMut(seen2); + rt.byteVecPush(_66.value, x4); + rt.refMutRelease(_66); + return x4; + }), new rt.Range(0, rt.u8Rem(b, 4)), "len"); + }; + const owned: [rt.u8, rt.u8][] = rt.iterToArray(zipped(), "next", 2); + const lent3: [rt.u8, rt.u8][] = rt.iterToArray(rt.iterBorrow(zipped()), "next", 2); + const _339: rt.RefCell = rt.refCellBorrow(seen2); + const args18: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(owned, f, (value: [rt.u8, rt.u8], f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(value[0], f), (f: rt.Formatter) => rt.u8Debug(value[1], f)], f)), (f: rt.Formatter) => rt.fmtList(lent3, f, (value: [rt.u8, rt.u8], f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.u8Debug(value[0], f), (f: rt.Formatter) => rt.u8Debug(value[1], f)], f)), (f: rt.Formatter) => rt.fmtList(rt.byteVecView(_339.value), f, rt.u8Debug)]; + const _335: string = rt.fmtFormat([args18[0], " ", args18[1], " ", args18[2], " "]); + rt.refRelease(_339); + fmt_put(out, _335); const split: Chunks.Chunks = new Chunks.of$dyns_Lease({ _0: rt.byteSliceToByteVec(rt.byteSliceRange(data, 2)), _1: rt.usizeAdd(rt.u8Rem(b, 4) as rt.usize, 1) }); const lens_: rt.usize[] = rt.iterToArray(rt.iterMap(split.chunks$Chunks(), (a0: rt.ByteSlice): rt.usize => { return a0.length; }), "next", 4); - const _336: rt.usize = rt.ownedCount(split.chunks$Chunks()); - const args18: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_336, f)]; - fmt_put(out, rt.fmtFormat([args18[0], " ", args18[1]])); + const _369: rt.usize = rt.ownedCount(split.chunks$Chunks()); + const args20: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(lens_, f, rt.usizeDebug), (f: rt.Formatter) => rt.usizeDisplay(_369, f)]; + fmt_put(out, rt.fmtFormat([args20[0], " ", args20[1]])); } } diff --git a/ts/fixtures/lang/src/dyns.rs b/ts/fixtures/lang/src/dyns.rs index b8078279a..c5f442f4b 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -712,6 +712,22 @@ pub fn iters(data: &[u8], out: &mut Vec) { dropped(out); drop(own); dropped(out); + // A box is no `TrustedLen` iterator, though what it holds is, so std collects it by `next`. + let seen = RefCell::new(Vec::new()); + let zipped = || { + let seen = &seen; + (0..a % 4) + .chain(2..2) + .map(move |x| { + seen.borrow_mut().push(x); + x + }) + .zip(0..b % 4) + }; + let owned: Vec<(u8, u8)> = Box::new(zipped()).collect(); + let mut boxed = Box::new(zipped()); + let lent: Vec<(u8, u8)> = (&mut boxed).collect(); + put(out, &format!("{owned:?} {lent:?} {:?} ", seen.borrow())); // Returned through a trait object of the crate's own trait. let split: &dyn Chunks = &Lease(rest.to_vec(), usize::from(b % 4) + 1); diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 2fd91c696..dd880e9c9 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -584,7 +584,9 @@ pub fn call<'tcx>( let item = b.t.iter_item(self_ty.peel_refs())?; not_bound(b, self_ty, span); let mut values = args.into_iter(); - let (self_ty, recv) = peel_iter(b, self_ty, values.next()?); + // What it reads through boxes and references, while std picks each specialization by + // `self_ty`, which a box implements none of. + let (iter_ty, recv) = peel_iter(b, self_ty, values.next()?); let rest: Vec = values.collect(); // What a consumer returns for `None`, after its other arguments, where that is `rt.None`. let none = b.t.none_arg(item); @@ -592,13 +594,13 @@ pub fn call<'tcx>( // `next` by the protocol, as an `Option`. if matches!(name, "next" | "next_back" | "nth_back") { let item = match name { - "next" => next(b, self_ty, recv), - "next_back" => match own_back(b, self_ty, recv.clone()) { + "next" => next(b, iter_ty, recv), + "next_back" => match own_back(b, iter_ty, recv.clone()) { Some(back) => back, None => recv.member("nextBack").call(Vec::new()), }, _ => { - let recv = into_iter(b, self_ty, recv, span)?; + let recv = into_iter(b, iter_ty, recv, span)?; let droppers = [b.t.dropper(item, span)].into_iter().chain(none).collect(); return Some(Expr::rt("iterNthBack", tail([vec![recv], rest].concat(), droppers))); } @@ -610,7 +612,7 @@ pub fn call<'tcx>( return Some(recv); } - let recv = into_iter(b, self_ty, recv, span)?; + let recv = into_iter(b, iter_ty, recv, span)?; // `rt.iter(recv, ...args)`, or `peekable` of a `Peekable`'s own. let method = |name: &str, args: Vec| { let of = match owner {