From 8934d0bea4fed4fa57f7849fc103dfacd50e652b Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:04:36 -0800 Subject: [PATCH 1/4] chore(quest): claim quest/m3/ts-needs-drop.md Quest-Claim: f6375f13537b9506 From 8e7b3ccc585848fd02ddabd5873caebde5cba28c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:08:55 -0800 Subject: [PATCH 2/4] feat(ts): translate mem::needs_drop and any::type_name as each instance's constant Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-needs-drop.md | 18 ---- ts/fixtures/lang/manual/expected/harness.ts | 6 ++ ts/fixtures/lang/manual/expected/lib.ts | 4 +- ts/fixtures/lang/manual/expected/manual.ts | 94 +++++++++++++++++++++ ts/fixtures/lang/manual/src/lib.rs | 5 +- ts/fixtures/lang/manual/src/manual.rs | 91 +++++++++++++++++++- ts/src/lower/library.rs | 7 ++ 8 files changed, 203 insertions(+), 23 deletions(-) delete mode 100644 quest/m3/ts-needs-drop.md diff --git a/quest/m3/README.md b/quest/m3/README.md index ced9594b0..a0a44aa8d 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -37,7 +37,6 @@ generic std gap is planned here even when moq never calls it. - [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 - [Popping a String that is reassigned elsewhere](/quest/m3/ts-reassigned-pop.md) - `pop`, `remove`, and `split_off` of a local some other path assigns whole 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..2c6fa9d2d 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,97 @@ 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]"]); + while (true) { + const _71: string | rt.Done = iter.next(); + if (_71 === rt.DONE) { + break; + } + put(out, _71); + } +} + +/** 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; +} + +/** + * 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 (!true) { + 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 (!false) { + 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..f72e9bcc0 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,84 @@ 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), + ] { + crate::harness::put(out, name); + } +} + +/// Whether the value's type needs dropping. +fn needs_drop_of(_: &T) -> bool { + needs_drop::() +} + +/// 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 de33b1969..82266877f 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") => { From d2b62ec2f5425c98c0cbcca6290aaef3abb867f8 Mon Sep 17 00:00:00 2001 From: quest fixer Date: Mon, 2 Nov 2026 21:28:56 -0800 Subject: [PATCH 3/4] test(ts): refresh needs-drop snapshot after base constant folding --- ts/fixtures/lang/manual/expected/manual.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/fixtures/lang/manual/expected/manual.ts b/ts/fixtures/lang/manual/expected/manual.ts index 2c6fa9d2d..e8ec46e87 100644 --- a/ts/fixtures/lang/manual/expected/manual.ts +++ b/ts/fixtures/lang/manual/expected/manual.ts @@ -146,7 +146,7 @@ export function needsDropOf$manual_needs_drops_closure2(_1: () => rt.usize): boo * that needs dropping, and forgets the rest. */ export function droppedByHand$String(items: string[]): rt.u32 { - if (!true) { + if (false) { return 0; } let n: rt.u32 = 0; @@ -161,7 +161,7 @@ export function droppedByHand$String(items: string[]): rt.u32 { * that needs dropping, and forgets the rest. */ export function droppedByHand$u8(items: rt.ByteVec): rt.u32 { - if (!false) { + if (true) { return 0; } let n: rt.u32 = 0; From 062e8b48fad846914c2181dc1d43856aa05477a1 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:41:05 -0800 Subject: [PATCH 4/4] test(ts): name a generic function's instances by type_name Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/manual/expected/manual.ts | 18 ++++++++++++++---- ts/fixtures/lang/manual/src/manual.rs | 7 +++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/ts/fixtures/lang/manual/expected/manual.ts b/ts/fixtures/lang/manual/expected/manual.ts index e8ec46e87..04a126c36 100644 --- a/ts/fixtures/lang/manual/expected/manual.ts +++ b/ts/fixtures/lang/manual/expected/manual.ts @@ -121,13 +121,13 @@ export function needsDrops(data: rt.ByteSlice, out: rt.ByteVec): void { 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]"]); + 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 _71: string | rt.Done = iter.next(); - if (_71 === rt.DONE) { + const _73: string | rt.Done = iter.next(); + if (_73 === rt.DONE) { break; } - put(out, _71); + put(out, _73); } } @@ -141,6 +141,16 @@ export function needsDropOf$manual_needs_drops_closure2(_1: () => rt.usize): boo 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. diff --git a/ts/fixtures/lang/manual/src/manual.rs b/ts/fixtures/lang/manual/src/manual.rs index f72e9bcc0..35a6b4ab9 100644 --- a/ts/fixtures/lang/manual/src/manual.rs +++ b/ts/fixtures/lang/manual/src/manual.rs @@ -148,6 +148,8 @@ pub fn needs_drops(data: &[u8], out: &mut Vec) { type_name::>(), type_name_of_val(&named), type_name_of_val(data), + name_of::(), + name_of::(), ] { crate::harness::put(out, name); } @@ -158,6 +160,11 @@ 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 {