diff --git a/quest/m3/README.md b/quest/m3/README.md index f951f0aa6..8ca2a7ea0 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -35,7 +35,6 @@ generic std gap is planned here even when moq never calls it. - [A vector's exact capacity as it shrinks](/quest/m3/ts-vec-exact-capacity.md) - a deque made from a `Vec` splits its slices where std's does - [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - a B-tree's lookups and inserts call a key's own `Ord` in std's order and number - [Rc and Arc of a slice](/quest/m3/ts-shared-slices.md) - `Rc<[T]>` and `Arc<[T]>` from a `Vec`, a slice, or an iterator translate -- [Whether a type needs drop](/quest/m3/ts-needs-drop.md) - `mem::needs_drop::()` translates as rustc's constant for each instance - [Operator trait methods called by path](/quest/m3/ts-operator-paths.md) - `::add_assign` and every other `std::ops` operator method translate called by path or as a value - [The rest of a vector's methods](/quest/m3/ts-vec-rest.md) - `append`, `resize`, `split_off`, `try_reserve`, and `Vec::from(VecDeque)` translate - [A reversed zip's per-pair cost](/quest/m3/ts-zip-rev-overhead.md) - a reversed by-index zip costs per pair what a forward one does, or the gap is explained diff --git a/quest/m3/ts-needs-drop.md b/quest/m3/ts-needs-drop.md deleted file mode 100644 index 3cbfba6d7..000000000 --- a/quest/m3/ts-needs-drop.md +++ /dev/null @@ -1,18 +0,0 @@ -# [XS] Whether a type needs drop - -## Goal - -[rust2-ts](/ts/README.md) translates `std::mem::needs_drop::()` as the constant rustc -gives each instance. That includes a `T` whose JS value drops nothing, such as a `String`, -since a caller may branch on it as std's collections do. A differential test against -native Rust proves it for a `T` of each outcome. - -## Plan - -Found on 2026-11-02 while reproducing a comparison of two constants -([moq-dev/rust2#480](https://github.com/moq-dev/rust2/pull/480)): `needs_drop::()` is an -"unsupported call to `std::mem::needs_drop::`", while `size_of::()` already -translates as its value. The value is rustc's, not whether translation emits a drop. -Fold in any other per-instance constant of a type that is refused the same way. - -Public API: the translator's output. diff --git a/ts/fixtures/lang/manual/expected/harness.ts b/ts/fixtures/lang/manual/expected/harness.ts index 58ec1cf62..3b7bbcfb0 100644 --- a/ts/fixtures/lang/manual/expected/harness.ts +++ b/ts/fixtures/lang/manual/expected/harness.ts @@ -37,4 +37,10 @@ export function op(name: string): rt.u16 { } } +/** Write `s` and the byte no UTF-8 holds, which ends it. */ +export function put(out: rt.ByteVec, s: string): void { + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(s)); + rt.byteVecPush(out, 255); +} + //# sourceMappingURL=harness.ts.map diff --git a/ts/fixtures/lang/manual/expected/lib.ts b/ts/fixtures/lang/manual/expected/lib.ts index fa45cef6e..3f09c194a 100644 --- a/ts/fixtures/lang/manual/expected/lib.ts +++ b/ts/fixtures/lang/manual/expected/lib.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { arcs } from "./manual.ts"; +import { arcs, needsDrops } from "./manual.ts"; export function copy$array_u8(v: Uint8Array): Uint8Array { return v.slice(); @@ -25,6 +25,8 @@ export function copy$tuple2_u8_u16(v: [rt.u8, rt.u16]): [rt.u8, rt.u16] { export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { return [["manual::arcs", (_22: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): undefined => { arcs(data, out); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["manual::needs_drops", (_23: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): undefined => { + needsDrops(data2, out2); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } diff --git a/ts/fixtures/lang/manual/expected/manual.ts b/ts/fixtures/lang/manual/expected/manual.ts index 154f4626c..04a126c36 100644 --- a/ts/fixtures/lang/manual/expected/manual.ts +++ b/ts/fixtures/lang/manual/expected/manual.ts @@ -2,6 +2,7 @@ import * as rt from "./rt.ts"; import { copy$array_u8, copy$mem_MaybeUninit_mem_ManuallyDrop_tuple2_u8_u16, copy$tuple2_u8_u16 } from "./lib.ts"; +import { put } from "./harness.ts"; import * as ByHand from "./manual.ByHand.ts"; /** A value's `ManuallyDrop`, as a generic one makes it, which only an instance lays out. */ @@ -77,4 +78,107 @@ export function arcs(data: rt.ByteSlice, out: rt.ByteVec): void { shared[Symbol.dispose](); } +/** Whether `T` needs dropping, as rustc says of each instance. */ +export function needs$String(): boolean { + return true; +} + +/** Whether `T` needs dropping, as rustc says of each instance. */ +export function needs$manual_ByHand(): boolean { + return true; +} + +/** Whether `T` needs dropping, as rustc says of each instance. */ +export function needs$u8(): boolean { + return false; +} + +/** Whether `T` needs dropping, as rustc says of each instance. */ +export function needs$unit(): boolean { + return false; +} + +/** + * Whether each type needs dropping, of values JS copies or drops nothing of, such as a + * `String`, held by hand, in an empty array, and of a closure by what it captures; what a + * generic body that branches on it does, as std's collections do; and types' names. + */ +export function needsDrops(data: rt.ByteSlice, out: rt.ByteVec): void { + const captured = rt.stringExtendChars("", rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_2: rt.u8): rt.char => { + return _2 as rt.char; + })); + const named: () => rt.usize = (): rt.usize => { + return rt.stringLen(captured); + }; + const copied: () => rt.usize = (): rt.usize => { + return data.length; + }; + rt.iterExtendBytes(out, rt.iterSlice(rt.bytesMap([false, false, true, false, false, false, true, true, true, true, true, true, false, true, true, false, true, false, false, true, false, false, false, true, true, false, needs$String(), needs$u8(), needs$manual_ByHand(), needs$unit(), needsDropOf$manual_needs_drops_closure1(named), needsDropOf$manual_needs_drops_closure2(copied)], (a0: boolean): rt.u8 => { + return a0 ? 1 : 0; + })), undefined, true); + rt.byteVecPush(out, (named() & 0xff) ^ (copied() & 0xff)); + rt.iterExtendBytes(out, rt.iterSlice(rt.u32ToBeBytes(droppedByHand$String(rt.iterToArray(rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (b2: rt.u8): string => { + return rt.fmtToString((f: rt.Formatter) => rt.u8Display(b2, f)); + }), undefined, 12, true))).reverse()), undefined, true); + rt.iterExtendBytes(out, rt.iterSlice(rt.u32ToBeBytes(droppedByHand$u8(rt.byteSliceToByteVec(data))).reverse()), undefined, true); + const iter: rt.Iter = rt.iterSlice(["lang_manual_fixture::manual::ByHand", "core::option::Option<&[alloc::string::String]>", "lang_manual_fixture::manual::needs_drops::{{closure}}", "[u8]", nameOf$u8(), nameOf$String()]); + while (true) { + const _73: string | rt.Done = iter.next(); + if (_73 === rt.DONE) { + break; + } + put(out, _73); + } +} + +/** Whether the value's type needs dropping. */ +export function needsDropOf$manual_needs_drops_closure1(_1: () => rt.usize): boolean { + return true; +} + +/** Whether the value's type needs dropping. */ +export function needsDropOf$manual_needs_drops_closure2(_1: () => rt.usize): boolean { + return false; +} + +/** `T`'s name, as a generic body names it. */ +export function nameOf$String(): string { + return "alloc::string::String"; +} + +/** `T`'s name, as a generic body names it. */ +export function nameOf$u8(): string { + return "u8"; +} + +/** + * How many items a generic body drops one by one, as std's collections do only of a type + * that needs dropping, and forgets the rest. + */ +export function droppedByHand$String(items: string[]): rt.u32 { + if (false) { + return 0; + } + let n: rt.u32 = 0; + for (const _7 of items) { + n = rt.u32Add(n, 1); + } + return n; +} + +/** + * How many items a generic body drops one by one, as std's collections do only of a type + * that needs dropping, and forgets the rest. + */ +export function droppedByHand$u8(items: rt.ByteVec): rt.u32 { + if (true) { + return 0; + } + let n: rt.u32 = 0; + for (const _7 of rt.byteVecView(items)) { + n = rt.u32Add(n, 1); + } + return n; +} + //# sourceMappingURL=manual.ts.map diff --git a/ts/fixtures/lang/manual/src/lib.rs b/ts/fixtures/lang/manual/src/lib.rs index b7fdb594e..9c2a24ed1 100644 --- a/ts/fixtures/lang/manual/src/lib.rs +++ b/ts/fixtures/lang/manual/src/lib.rs @@ -15,5 +15,8 @@ use harness::Case; /// Each case's name and what it runs. The first two bytes, big-endian, pick one modulo /// their count, so a case added at the end moves no other. fn cases() -> Vec<(&'static str, Case)> { - vec![("manual::arcs", |_, data, out| manual::arcs(data, out))] + vec![ + ("manual::arcs", |_, data, out| manual::arcs(data, out)), + ("manual::needs_drops", |_, data, out| manual::needs_drops(data, out)), + ] } diff --git a/ts/fixtures/lang/manual/src/manual.rs b/ts/fixtures/lang/manual/src/manual.rs index 70adf59c2..35a6b4ab9 100644 --- a/ts/fixtures/lang/manual/src/manual.rs +++ b/ts/fixtures/lang/manual/src/manual.rs @@ -1,8 +1,14 @@ //! `ManuallyDrop`: a value nothing drops but its own `drop`, given back by `into_inner` //! and `take`, cloned, and read and written through it as `Deref` and `DerefMut` lend it, -//! as crossbeam-epoch's `Local` holds its collector and futures-util a waker. +//! as crossbeam-epoch's `Local` holds its collector and futures-util a waker; and +//! `needs_drop` and `type_name`, which rustc answers for each instance. -use std::mem::{ManuallyDrop, MaybeUninit}; +use std::any::{type_name, type_name_of_val}; +use std::cell::Cell; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::{ManuallyDrop, MaybeUninit, needs_drop}; +use std::rc::Rc; use std::sync::Arc; /// The crate's struct holding values by hand, which its `Drop` drops one of. @@ -85,3 +91,91 @@ pub fn arcs(data: &[u8], out: &mut Vec) { drop(kept); out.push(Arc::strong_count(&shared) as u8); } + +/// Whether `T` needs dropping, as rustc says of each instance. +fn needs() -> bool { + needs_drop::() +} + +/// Whether each type needs dropping, of values JS copies or drops nothing of, such as a +/// `String`, held by hand, in an empty array, and of a closure by what it captures; what a +/// generic body that branches on it does, as std's collections do; and types' names. +pub fn needs_drops(data: &[u8], out: &mut Vec) { + let text: String = data.iter().map(|&b| char::from(b)).collect(); + let named = move || text.len(); + let copied = move || data.len(); + let flags = [ + needs_drop::(), + needs_drop::(), + needs_drop::(), + needs_drop::<&String>(), + needs_drop::<&mut Vec>(), + needs_drop::(), + needs_drop::<[String]>(), + needs_drop::(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::<(u8, u16)>(), + needs_drop::<(u8, String)>(), + needs_drop::<[u8; 4]>(), + needs_drop::<[String; 0]>(), + needs_drop::<[String; 2]>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::>(), + needs_drop::(), + needs_drop::(), + needs::(), + needs::(), + needs::(), + needs::<()>(), + needs_drop_of(&named), + needs_drop_of(&copied), + ]; + out.extend(flags.map(u8::from)); + out.push(named() as u8 ^ copied() as u8); + let strings: Vec = data.iter().map(|b| b.to_string()).collect(); + out.extend(dropped_by_hand(strings).to_le_bytes()); + out.extend(dropped_by_hand(data.to_vec()).to_le_bytes()); + for name in [ + type_name::(), + type_name::>(), + type_name_of_val(&named), + type_name_of_val(data), + name_of::(), + name_of::(), + ] { + crate::harness::put(out, name); + } +} + +/// Whether the value's type needs dropping. +fn needs_drop_of(_: &T) -> bool { + needs_drop::() +} + +/// `T`'s name, as a generic body names it. +fn name_of() -> &'static str { + type_name::() +} + +/// How many items a generic body drops one by one, as std's collections do only of a type +/// that needs dropping, and forgets the rest. +fn dropped_by_hand(items: Vec) -> u32 { + if !needs_drop::() { + std::mem::forget(items); + return 0; + } + let mut n = 0; + for item in items { + drop(item); + n += 1; + } + n +} diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 526dc8a48..41a65c4df 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -2464,6 +2464,13 @@ fn mapping<'tcx>( let layout = tcx.layout_of(env.as_query_input(targ(gargs, 0))).ok()?; Expr::lit(layout.align.abi.bytes().to_string()) } + // rustc's answers for the instance, which a JS value that drops nothing, such as a + // `String`'s, leaves as they are. + ("mem", "needs_drop") => { + let needs = targ(gargs, 0).needs_drop(tcx, b.t.typing_env_pub()); + Expr::lit(needs.to_string()) + } + ("any", "type_name" | "type_name_of_val") => Expr::Str(rustc_const_eval::util::type_name(tcx, targ(gargs, 0))), // What wasm32 lays out the value in: its type's, or a slice's elements' or a `str`'s // bytes'. A trait object's is its vtable's, which is left to the refusal of any other call. ("mem", "size_of_val" | "align_of_val") => {