diff --git a/quest/m3/README.md b/quest/m3/README.md index 3cd92bbac..ed135662a 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -22,7 +22,6 @@ generic std gap is planned here even when moq never calls it. - [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 - [A boxed iterator folds as std's box does](/quest/m3/ts-box-folds.md) - a `Box` reads by `next` where std's `Iterator` for `Box` forwards nothing else -- [by_ref of a mutable reference to an iterator](/quest/m3/ts-by-ref-place.md) - `Iterator::by_ref` of a `&mut I` in a local translates - [mem::take as a function value](/quest/m3/ts-take-value.md) - `mem::take`, `replace`, and `swap` passed as a function value translate - [Debug of a RefCell](/quest/m3/ts-refcell-debug.md) - `{:?}` of a `RefCell`, `Ref`, and `RefMut` translates as std's writes 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 diff --git a/quest/m3/ts-by-ref-place.md b/quest/m3/ts-by-ref-place.md deleted file mode 100644 index 7c199a651..000000000 --- a/quest/m3/ts-by-ref-place.md +++ /dev/null @@ -1,15 +0,0 @@ -# [S] by_ref of a mutable reference to an iterator - -## Goal - -[rust2-ts](/ts/README.md) translates `Iterator::by_ref` of a `&mut I` held in a local, -such as `(&mut it).by_ref()` where `it: &mut I`, advancing the iterator it points at as -std's does, proven by a differential test against native Rust. - -## Plan - -Found writing [`dyn Iterator`](https://github.com/moq-dev/rust2/pull/437): it fails as -"`Iterator::by_ref` with a mutable reference to a value JS copies, which its mapping would -copy", since the `&mut I` itself is an `rt.Place`. - -Public API: the translator's output. diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 0c334813d..7ac6fe516 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -37,7 +37,7 @@ import { spills, remnants, layouts } from "./heap.ts"; import { hints, reserves } from "./hints.ts"; import { holds } from "./holds.ts"; import { u8Op, u16Op, u32Op, u64Op, u128Op, usizeOp, i8Op, i16Op, i32Op, i64Op, i128Op, isizeOp, i8Neg, i16Neg, i32Neg, i64Neg, i128Neg, isizeNeg, cast as ints_cast, convert, parses as ints_parses, widenings } from "./ints.ts"; -import { adaptors, consumers as iters_consumers, reducers, peekedOthers, skips, ranked as iters_ranked, ranges as iters_ranges, options as iters_options, optionItems, own as iters_own, values as iters_values, returned, zips, zipsBack, rangeFroms, rangeMethods, compares } from "./iters.ts"; +import { adaptors, consumers as iters_consumers, reducers, peekedOthers, skips, ranked as iters_ranked, ranges as iters_ranges, options as iters_options, optionItems, own as iters_own, values as iters_values, returned, zips, zipsBack, rangeFroms, rangeMethods, compares, lent as iters_lent } from "./iters.ts"; import { joins } from "./joins.ts"; import { keys as keys_keys, overrides, bytes as keys_bytes, lentKeys, lentValues, arrays as keys_arrays, byteRefs } from "./keys.ts"; import { lengths } from "./lengths.ts"; @@ -329,6 +329,8 @@ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) 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], ["iters::lent", (_2215: rt.u8, data115: rt.ByteSlice, out116: rt.ByteVec): undefined => { + iters_lent(data115, out116); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/expected/iters.ts b/ts/fixtures/lang/expected/iters.ts index c368e3d72..0a4c4a47c 100644 --- a/ts/fixtures/lang/expected/iters.ts +++ b/ts/fixtures/lang/expected/iters.ts @@ -9500,4 +9500,56 @@ export function compares(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void rt.refRelease(_1004); } +/** + * `by_ref` of a `&mut` to an iterator held in a local, of a `&mut` to that, and of a + * `&mut dyn Iterator`: each advances the iterator it points at, which the original reads + * on from where the borrow stopped, and writing through what it returns rebinds the local. + */ +export function lent(data: rt.ByteSlice, out: rt.ByteVec): void { + const n: rt.usize = option_mapOr$1t0muvr(rt.byteSliceFirst(data), 0, (_2: rt.u8): rt.usize => { + return rt.u8Rem(_2, 4) as rt.usize; + }); + const it: rt.Iter = rt.iterSlice(rt.byteSliceView(data)); + const lent: rt.Iter = it; + rt.iterExtendBytes(out, rt.iterTake(rt.iterBorrow(lent), n), undefined, true); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(lent.next()), 170)); + rt.iterExtendBytes(out, rt.iterTake(rt.iterBorrow(it), 1), undefined, true); + let once: rt.Iter = it; + const twice: rt.Place> = rt.place(() => once, (value2) => once = value2); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterNth(rt.iterBorrow(twice.v), n), 187)); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(twice.v.next()), 204)); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(it.next()), 221)); + const own: iters_Countdown.Countdown = { left: rt.u8Mul(n & 0xff, 3), step: 2 }; + const other: iters_Countdown.Countdown = { left: 9, step: 4 }; + let held: iters_Countdown.Countdown = own; + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(iters_Countdown.next$iter_Iterator(held)), 170)); + held = other; + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(iters_Countdown.next$iter_Iterator(held)), 187)); + rt.iterExtendBytes(out, rt.iterFrom(own, (it: iters_Countdown.Countdown) => iters_Countdown.next$iter_Iterator(it)), "next"); + rt.iterExtendBytes(out, rt.iterFrom(other, (it: iters_Countdown.Countdown) => iters_Countdown.next$iter_Iterator(it)), "next"); + const range: rt.Range = new rt.Range(0, rt.u8Mul(n & 0xff, 3)); + let erased: rt.Iter = rt.iterDyn(rt.iterBorrow(range)); + rt.iterExtendBytes(out, rt.iterTake(rt.iterStepBy(rt.iterBorrow(erased), 2), 2), "next"); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(erased.next()), 238)); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.iterToOption(erased.next()), 238)); + rt.iterExtendBytes(out, range, undefined, true); + let boxed: rt.Iter = rt.iterDyn(rt.iterRev(rt.iterSlice(rt.byteSliceView(data)))); + rt.iterExtendBytes(out, rt.iterTake(rt.iterBorrow(boxed), n), "next"); + rt.iterExtendBytes(out, rt.iterTake(boxed, 1), "next"); + const rest: rt.Iter = rt.iterSlice(rt.byteSliceView(data)); + rt.iterExtendBytes(out, rt.byteVecIntoIter(halves$mut_iter_Copied_slice_Iter_u8(rest, n)), undefined, true); + rt.iterExtendBytes(out, rest, undefined, true); + rt.iterExtendBytes(out, rt.byteVecIntoIter(halves$iter_Copied_slice_Iter_u8(rt.iterSlice(rt.byteSliceView(data)), n)), undefined, true); +} + +/** The first `n` items, then one more. */ +export function halves$iter_Copied_slice_Iter_u8(it: rt.Iter, n: rt.usize): rt.ByteVec { + return rt.byteSlicesJoin([rt.iterToByteVec(rt.iterTake(rt.iterBorrow(it), n), undefined, true), rt.iterToByteVec(rt.iterTake(it, 1), undefined, true)], undefined, rt.byteVecSlice); +} + +/** The first `n` items, then one more. */ +export function halves$mut_iter_Copied_slice_Iter_u8(it: rt.Iter, n: rt.usize): rt.ByteVec { + return rt.byteSlicesJoin([rt.iterToByteVec(rt.iterTake(rt.iterBorrow(it), n), undefined, true), rt.iterToByteVec(rt.iterTake(rt.iterBorrow(it), 1), undefined, true)], undefined, rt.byteVecSlice); +} + //# sourceMappingURL=iters.ts.map diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index a25e10e58..066bedc77 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -313,6 +313,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("searches::texts", searches::texts), ("dyns::iters", |_, data, out| dyns::iters(data, out)), ("errors::objects", |_, data, out| errors::objects(data, out)), + ("iters::lent", |_, data, out| iters::lent(data, out)), ] } diff --git a/ts/fixtures/lang/src/iters.rs b/ts/fixtures/lang/src/iters.rs index 44e1756a0..d66cee380 100644 --- a/ts/fixtures/lang/src/iters.rs +++ b/ts/fixtures/lang/src/iters.rs @@ -1840,3 +1840,58 @@ pub fn compares(kind: u8, data: &[u8], out: &mut Vec) { } out.extend_from_slice(&log.borrow()); } + +/// `by_ref` of a `&mut` to an iterator held in a local, of a `&mut` to that, and of a +/// `&mut dyn Iterator`: each advances the iterator it points at, which the original reads +/// on from where the borrow stopped, and writing through what it returns rebinds the local. +pub fn lent(data: &[u8], out: &mut Vec) { + let n = data.first().map_or(0, |&b| usize::from(b % 4)); + let mut it = data.iter().copied(); + let mut lent = &mut it; + out.extend((&mut lent).by_ref().take(n)); + out.push(lent.next().unwrap_or(0xaa)); + out.extend(it.by_ref().take(1)); + let mut once = &mut it; + let mut twice = &mut once; + out.push((&mut twice).by_ref().nth(n).unwrap_or(0xbb)); + out.push(twice.next().unwrap_or(0xcc)); + out.push(it.next().unwrap_or(0xdd)); + + // Rebound through the `&mut` that `by_ref` returns, the local reads the other iterator, + // and the first is left where it stopped. + let mut own = Countdown { + left: n as u8 * 3, + step: 2, + }; + let mut other = Countdown { left: 9, step: 4 }; + let mut held = &mut own; + out.push(held.next().unwrap_or(0xaa)); + *(&mut held).by_ref() = &mut other; + out.push(held.next().unwrap_or(0xbb)); + out.extend(own); + out.extend(other); + + let mut range = 0..n as u8 * 3; + let mut erased: &mut dyn Iterator = &mut range; + out.extend((&mut erased).by_ref().step_by(2).take(2)); + out.push(erased.next().unwrap_or(0xee)); + let mut deeper = &mut erased; + out.push((&mut deeper).by_ref().next().unwrap_or(0xee)); + out.extend(range); + let mut boxed: Box> = Box::new(data.iter().rev().copied()); + let mut lent = &mut boxed; + out.extend((&mut lent).by_ref().take(n)); + out.extend(boxed.take(1)); + + // Generic over an iterator that is itself a `&mut`, or one it owns. + let mut rest = data.iter().copied(); + out.extend(halves(&mut rest, n)); + out.extend(rest); + out.extend(halves(data.iter().copied(), n)); +} + +/// The first `n` items, then one more. +fn halves>(mut it: I, n: usize) -> Vec { + let front: Vec = it.by_ref().take(n).collect(); + [front, it.take(1).collect()].concat() +} diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 4efad521e..563e9b991 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -516,6 +516,11 @@ pub fn call<'tcx>( let value = args.pop()?; return into_iter(b, targ(0)?, value, span); } + // The `&mut` it was given, an `rt.Place` where that points at a `&mut`. + ("Iterator", "by_ref") => { + not_bound(b, targ(0)?, span); + return args.pop(); + } _ => {} } match path.as_slice() { @@ -607,11 +612,6 @@ pub fn call<'tcx>( }; return Some(Expr::rt("iterToOption", [item].into_iter().chain(none).collect())); } - // The same object, borrowed. - if name == "by_ref" { - return Some(recv); - } - 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| { diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index c20754836..5a203ab0f 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -371,8 +371,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // Mappings that lend an element JS copies as its place, as a map's `get_mut` does, the // item a `Peekable` peeked, the place a `&mut dyn Any` wraps, a `RefCell` key's value, // a `Result`'s payload, the value of an `Arc`, a lock, or a cell, what a write guard - // lends its map or its leak, or what `zip` makes of the collection it is given by - // `into_iter`, as the free `zip` does of either. + // lends its map or its leak, the `&mut` an iterator's `by_ref` is given, or what `zip` + // makes of the collection it is given by `into_iter`, as the free `zip` does of either. let lends = match library::key(tcx, def_id) { Some((owner, name)) => match (owner.as_str(), name.as_str()) { ("slice", "get_mut" | "first_mut" | "last_mut" | "iter_mut" | "split_first_mut" | "split_last_mut") @@ -385,6 +385,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { | ("Mutex" | "RwLock" | "RefCell" | "OnceCell" | "OnceLock", "get_mut") | ("RefMut", "leak") | ("RefCell", "undo_leak") + | ("Iterator", "by_ref") | ( "RefMut" | "MutexGuard" | "MappedMutexGuard" | "RwLockWriteGuard" | "MappedRwLockWriteGuard", "map" | "filter_map" | "try_map" | "map_split", diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 7233fbd44..16a0604e5 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -398,8 +398,8 @@ pub fn keeps<'tcx>(tcx: ty::TyCtxt<'tcx>, def_id: rustc_hir::def_id::DefId, args /// rather than what it points at: a `MaybeUninit`'s or a `ManuallyDrop`'s, which it points /// into, an `Arc`'s that `make_mut` replaces, a `String`'s that a drain edits as it drops, /// a `Result`'s whose payload JS copies, which `iter_mut` lends as a place that writes the -/// whole `Result`, and a well-known crate's, such as a `Bytes` that `Buf::advance` moves -/// past its front. +/// whole `Result`, an iterator's that `by_ref` returns, and a well-known crate's, such as a +/// `Bytes` that `Buf::advance` moves past its front. fn takes_place<'tcx>( t: &Translator<'_, 'tcx>, def_id: rustc_hir::def_id::DefId, @@ -410,6 +410,7 @@ fn takes_place<'tcx>( Some((owner, name)) if matches!(owner.as_str(), "MaybeUninit" | "ManuallyDrop") || matches!(owner.as_str(), "Arc" | "Rc") && name == "make_mut" || (owner.as_str(), name.as_str()) == ("String", "drain") + || (owner.as_str(), name.as_str()) == ("Iterator", "by_ref") || (owner.as_str(), name.as_str()) == ("Result", "iter_mut") && t.needs_place(args.type_at(0)) ) || super::stock::is_stock(t.tcx, def_id) }