diff --git a/quest/m3/README.md b/quest/m3/README.md index 36498ed54..fa33b7f98 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 Iterator` and `dyn Error`'s downcasts translate, so a consumer neither rewrites nor binds them +- [Trait objects](/quest/m3/ts-dyn.md) - `dyn Error`'s downcasts, `dyn FusedIterator`, and `Arc` translate, so a consumer neither rewrites nor binds them - [A trait object in a struct's last field](/quest/m3/ts-dyn-tail.md) - `Rc>` and the like translate, the last field as the object - [bytes' Chain as a Buf object](/quest/m3/ts-buf-chain.md) - `Chain::copy_to_bytes` across both halves translates, and with it a `Chain` as a `dyn Buf` - [A Buf object's size](/quest/m3/ts-dyn-buf-size.md) - a `dyn Buf` object holds only the methods some call dispatches to diff --git a/quest/m3/ts-dyn.md b/quest/m3/ts-dyn.md index a6f7406ba..e09e68083 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 Iterator`), and `dyn FusedIterator`, behind +that it still refuses, `dyn Error`'s downcasts and `dyn FusedIterator`, behind any pointer std coerces to one, such as `&mut dyn Debug` and `Arc`, with Rust's dispatch and drop order, proven by differential tests against native Rust, so a consumer neither rewrites nor binds them. @@ -16,13 +16,12 @@ config, so trait objects move from never to not yet. - `dyn Debug` and `dyn Display` translate as functions writing their value, behind `&`, `&mut`, `Box`, `Arc`, and `Rc`, `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. - `dyn Error` translates as an object of `source`, `Debug`, and `Display`. Its downcasts remain: checking the class of `Error.of$T` as `dyn Any`'s do would miss a value upcast from the crate's own trait over `Error`, whose object is that trait's class, so a downcast needs the class of the value's own type, as an upcast to `dyn Any` does. -- `Iterator` can be an `rt.Iter`. - `bytes::Buf` is an object of its methods, as the crate's own traits are, decided with the maintainer on 2026-11-02, so it works for any implementor. - Generic traits and associated types name their arguments in the object's type. diff --git a/ts/README.md b/ts/README.md index 7e97ef0ff..92dae8c82 100644 --- a/ts/README.md +++ b/ts/README.md @@ -126,6 +126,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `dyn Error` | an object of its `source`, `fmt$Debug`, and `fmt$Display`: `e.source$Error()` | | `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 | @@ -490,10 +491,17 @@ that keeps `Error`'s own. `From` an error into a `Box`, which `?` con makes one. Its deprecated `description` and `cause`, and its downcasts, are refused, and so is `From` a string, which boxes std's own private error type. +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` and `dyn Display` behind `&`, `&mut`, `Box`, `Arc`, or `Rc`, `dyn Future`, -`dyn Any`, `dyn Error`, `dyn Buf`, and the function traits, of a generic +`dyn Any`, `dyn Error`, `dyn Buf`, 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 struct's last field, is refused, and so are an upcast to `dyn Any` and `Any::type_id`. @@ -873,7 +881,7 @@ they need. Byte reinterpretation, pointer addresses, pointer arithmetic on a value JS keeps as an object (other than wrapping arithmetic on a null or dangling pointer), memory from `alloc` other than an array of values JS copies, 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 Error`, `dyn Fn*`, `dyn Buf`, and the translated crates' traits' +`dyn Debug`, `dyn Display`, `dyn Future`, `dyn Any`, `dyn Error`, `dyn Fn*`, `dyn Buf`, `dyn Iterator` and its subtraits, and the translated crates' traits' behind `&`, `&mut`, `Box`, `Arc`, `Rc`, `Weak`, or `Pin` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a `Box` made `From` a string, 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 24f7f07a1..100fd0b87 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 heap_Fault from "../heap.Fault.ts"; @@ -349,6 +350,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 989a185a8..39b0d541a 100644 --- a/ts/fixtures/lang/expected/dyns.ts +++ b/ts/fixtures/lang/expected/dyns.ts @@ -3,8 +3,11 @@ import * as rt from "./rt.ts"; import { drop$Result_Rc_dyns_Noisy_Rc_dyn_Any, drop$Result_Rc_dyns_Square_Rc_dyn_Any, drop$Result_Rc_dyns_Tri_Rc_dyn_Any } from "./lib.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, map$0j05j69 as option_map$0j05j69, 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$1i542n2 as option_map$1i542n2, map$08a6hpg as option_map$08a6hpg, map$0j05j69 as option_map$0j05j69, mapOr$06rr3sb as option_mapOr$06rr3sb } from "./core/option.ts"; import { ok$00p9ap8, 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"; @@ -14,6 +17,7 @@ import * as dyns_Noisy from "./dyns.Noisy.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"; @@ -669,6 +673,211 @@ 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 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 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 _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); + 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 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 _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]])); + } +} + /** * Trait objects behind the rest of the pointers std coerces to one: an `Rc`, a `Weak` of * an `Rc` or an `Arc`, a `Pin`, and an `Rc` or an `Arc` made from a box of one. Counts, diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index da044d898..0c334813d 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, pointers } from "./dyns.ts"; +import { dyns, shared as dyns_shared, anys, fns, iters as dyns_iters, pointers } from "./dyns.ts"; import { edits as edits_edits, bytes as edits_bytes } from "./edits.ts"; import { vecs as elements_vecs, arrays as elements_arrays, segments as elements_segments, inline, edges as elements_edges, rawSlices, emptyParts } from "./elements.ts"; import { ends as ends_ends } from "./ends.ts"; @@ -325,8 +325,10 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) sorts_btrees(a0165, a1165.data, a1165.start, a1165.length, a2165); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::pointers", (_2212: rt.u8, data112: rt.ByteSlice, out113: rt.ByteVec): undefined => { pointers(data112.data, data112.start, data112.length, out113); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::searches", searches as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::texts", texts as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["errors::objects", (_2213: rt.u8, data113: rt.ByteSlice, out114: rt.ByteVec): undefined => { - errors_objects(data113.data, data113.start, data113.length, out114); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::searches", searches as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["searches::texts", texts as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["dyns::iters", (_2213: rt.u8, data113: rt.ByteSlice, out114: rt.ByteVec): undefined => { + dyns_iters(data113, out114); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["errors::objects", (_2214: rt.u8, data114: rt.ByteSlice, out115: rt.ByteVec): undefined => { + errors_objects(data114.data, data114.start, data114.length, out115); }] 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 90bdc4817..c5f442f4b 100644 --- a/ts/fixtures/lang/src/dyns.rs +++ b/ts/fixtures/lang/src/dyns.rs @@ -5,10 +5,14 @@ //! 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. An -//! `Rc`, a `Weak`, a `Pin`, and an `Rc` or an `Arc` made from a box hold one too. +//! `Rc`, a `Weak`, a `Pin`, and an `Rc` or an `Arc` made from a box hold one too. 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::pin::Pin; use std::rc::{self, Rc}; @@ -531,6 +535,206 @@ pub fn fns(data: &[u8], out: &mut Vec) { ); } +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); + // 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 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; + 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 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:?} {fourth:?} {mapped:?} {} {} ", + nested.collect::>(), + erased.count(), + doubled.count() + ), + ); + 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); + // 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); + let lens: Vec = split.chunks().map(<[u8]>::len).collect(); + put(out, &format!("{lens:?} {}", split.chunks().count())); +} + /// A shape that notes its id in a log as it drops. struct Noisy(u8, Rc>>); diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 69b1cd5c6..a25e10e58 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -311,6 +311,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("dyns::pointers", |_, data, out| dyns::pointers(data, out)), ("searches::searches", searches::searches), ("searches::texts", searches::texts), + ("dyns::iters", |_, data, out| dyns::iters(data, out)), ("errors::objects", |_, data, out| errors::objects(data, out)), ] } diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index af9c6e285..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 | @@ -137,7 +138,7 @@ The type or module each refused type or path belongs to. | 2 | `[u8; 3]` | fixtures/reject/src/lib.rs:1661:15 | | 2 | `dyn Keyed` | fixtures/reject/src/lib.rs:461:32 | | 2 | `dyn Scaled` | fixtures/reject/src/lib.rs:393:23 | -| 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:473:2 | | 2 | `std::collections::BTreeSet` | fixtures/reject/src/lib.rs:193:75 | @@ -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 | @@ -275,7 +278,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:393:23 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/reject/src/lib.rs:490:27 | | 2 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u8`, `u32` | fixtures/reject/src/lib.rs:2267:15 | @@ -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 | @@ -738,8 +742,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
@@ -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 45865fa13..bc0ba0f3d 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 @@ -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 3c1b0d1f8..3bf19eefa 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`. @@ -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/runtime/rt/iter.ts b/ts/runtime/rt/iter.ts index 3d7f8bd8d..e884e48f8 100644 --- a/ts/runtime/rt/iter.ts +++ b/ts/runtime/rt/iter.ts @@ -581,6 +581,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 @@ -2051,11 +2059,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) { @@ -2071,10 +2076,6 @@ class Borrowed extends Iter { return this.inner.next(); } - override folded(): Iter { - return this.inner.folded(); - } - override nextBack(): T | Done { return this.inner.nextBack(); } @@ -2098,6 +2099,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 65f9ab8e5..7713d55f2 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -194,7 +194,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; @@ -204,6 +205,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 8f1904892..dd880e9c9 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -46,6 +46,20 @@ 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 @@ -140,12 +154,58 @@ 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 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, + // 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, + _ => { + 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]), + 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. 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) = 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), @@ -353,6 +413,49 @@ pub fn peel_mut<'tcx>(mut ty: Ty<'tcx>, mut value: Expr) -> (Ty<'tcx>, Expr) { (ty, value) } +/// `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>, 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; + } + 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; + } + // 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), + }; + } +} + /// 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) { @@ -481,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_mut(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); @@ -489,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))); } @@ -507,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 { diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 02d351d92..33ca98707 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -63,10 +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. - Some(p) if self_ty.is_ref() || crate::lower::iter::through_mut(self_ty) => { + // 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)), diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 579a0d2d0..5192e1252 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -374,6 +374,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 20769533d..c73333560 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1479,6 +1479,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