diff --git a/quest/m1/README.md b/quest/m1/README.md index ea14ef0bf..78e781cf0 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -17,7 +17,6 @@ are folded into the [exporter's](/quest/m1/ffi-export/README.md). The ## Required - [Waiting: publish the UniFFI fork](/quest/m1/uniffi-release.md) - the fork publishes to crates.io under `rust2-*` names, once the maintainer decides it should -- [Vec views of the right type](/quest/m1/ts-vec-view.md) - rust2-ts never translates a `Vec` of non-bytes as bytes, and the exporter's pin bump unblocks - [Strings from UTF-8 bytes](/quest/m1/ts-utf8.md) - `str::from_utf8` translates, so the translator follows moq past its owned `Path` - [Exporter](/quest/m1/ffi-export/README.md) - moq's bindings exported from its crates' `pub` API, replacing the fork - [Land main through quest land](/quest/m1/land.md) - every queued PR lands as one tested batch through the shared gate diff --git a/quest/m1/ts-utf8.md b/quest/m1/ts-utf8.md index 3013ccce7..230e934d4 100644 --- a/quest/m1/ts-utf8.md +++ b/quest/m1/ts-utf8.md @@ -11,7 +11,7 @@ past [moq-dev/moq#6843](https://github.com/moq-dev/moq/pull/6843), whose decoder Decided with the maintainer on 2026-10-27: the exporter and the translator share one moq pin, so [the exporter's pin bump](https://github.com/kixelated/rust2/pull/144) merges once -this and [the `Vec` view fix](/quest/m1/ts-vec-view.md) pass at its revision. +this passes at its revision. - `TextDecoder` with `fatal` rejects what std rejects, but the error position must be std's `valid_up_to` and `error_len`, so check its edge cases (truncated sequences, surrogates, diff --git a/quest/m1/ts-vec-view.md b/quest/m1/ts-vec-view.md deleted file mode 100644 index a472cc912..000000000 --- a/quest/m1/ts-vec-view.md +++ /dev/null @@ -1,17 +0,0 @@ -# [S] Vec views of the right type - -## Goal - -[rust2-ts](/ts/README.md)'s `vec_view` (`ts/src/lower/library.rs`) decides whether a `Vec` -is a byte vector from the `Vec`'s own type, so `Vec::as_slice` no longer panics and a -`Vec>` is never translated as bytes, proven by differential tests against native -Rust. `just ts census` passes at the moq revision -[the exporter's pin bump](https://github.com/kixelated/rust2/pull/144) moves to. - -## Plan - -Found by [#144](https://github.com/kixelated/rust2/pull/144) on 2026-10-27: the `as_slice` -case passes the element type where the `Vec` type is expected. A silent miscompile on any -pin, so reproduce it on `main` first, then find every caller that mixes the two. - -Public API: the translator's output. diff --git a/ts/fixtures/lang/expected/collections.ts b/ts/fixtures/lang/expected/collections.ts index ac12c629c..fff48b8f9 100644 --- a/ts/fixtures/lang/expected/collections.ts +++ b/ts/fixtures/lang/expected/collections.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { copy$Bumped, copy$Tally, copy$u8_Tally, copy$u8_str, copy$u8_u16, copy$u8_u16s, copy$u8_u8, copy$u8_u8s } from "./lib.ts"; +import { copy$Bumped, copy$Tally, copy$Wrap_u8, copy$u8_Tally, copy$u8_str, copy$u8_u16, copy$u8_u16s, copy$u8_u8, copy$u8_u8s } from "./lib.ts"; import * as Clash from "./collections.Clash.ts"; import * as Cloned from "./collections.Cloned.ts"; import * as Dir from "./collections.Dir.ts"; @@ -12,6 +12,7 @@ import * as Pair from "./collections.Pair.ts"; import * as Rank from "./collections.Rank.ts"; import * as Tally from "./collections.Tally.ts"; import { unwrapOr$Option_u16, unwrapOr$Option_u8, mapOr$Option_Nibble_u8_sets_closure4, mapOr$Option_Rank_Tally_u8_hash_maps_closure11, mapOr$Option_Tally_u8_hash_maps_closure10, mapOr$Option_u16_u8_hash_maps_closure0, mapOr$Option_u8_u8_u8_btree_maps_closure0, mapOr$Option_u8_u8_u8s_stores_closure2_closure0 } from "./core/option.ts"; +import * as Wrap from "./generics.Wrap.ts"; import * as Bumped from "./iters.Bumped.ts"; /** A 64-bit key: small, or past 2^53, where it is no longer a JS number. */ @@ -958,7 +959,8 @@ export function vecs(data: rt.ByteSlice, out: rt.ByteVec): void { } else { name = "?"; } - rt.byteVecExtendFromSlice(out, rt.byteVecSlice(rt.byteVecFrom(Uint8Array.of(a, b, 66)))); + const bytes: rt.ByteVec = rt.byteVecFrom(Uint8Array.of(a, b, 66)); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(bytes)); const pairs: [rt.u8, rt.u16][] = copy$u8_u16s([[a, b as rt.u16] as [rt.u8, rt.u16]]).map(copy$u8_u16); pairs.push([b, a as rt.u16] as [rt.u8, rt.u16]); const at = rt.idx(pairs, 0); @@ -966,7 +968,8 @@ export function vecs(data: rt.ByteSlice, out: rt.ByteVec): void { for (const _39 of pairs) { rt.byteVecPush(out, _39[0] ^ (_39[1] & 0xff)); } - rt.byteVecPush(out, [name, "x", name].length & 0xff); + const names: string[] = [name, "x", name]; + rt.byteVecPush(out, names.length & 0xff); const one: Cloned.Cloned[] = [{ depth: a, name: name }]; rt.byteVecPush(out, one[rt.idx(one, 0)].depth); rt.byteVecExtendFromSlice(out, rt.byteVecSlice(rt.byteVecOf(b, n))); @@ -1013,6 +1016,29 @@ export function vecs(data: rt.ByteSlice, out: rt.ByteVec): void { rt.iterExtendBytes(out, rt.iterMap(rt.iterChain(rt.iterSlice(bumps), rt.iterSlice(kept)), (o2: Bumped.Bumped): rt.u8 => { return o2._0; })); + const grown: rt.ByteVec = rt.byteVecFrom(rt.byteVecView(bytes)); + const _169: rt.ByteSlice = rt.byteVecSlice(grown); + rt.idx(_169, 0); + _169.data[_169.start] = _169.data[_169.start] ^ 255; + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(grown)); + const iterS2: rt.ByteVec[] = [bytes, rt.byteVecOf(b, n)]; + for (const _187 of iterS2) { + rt.byteVecPush(out, _187.length & 0xff); + rt.byteVecExtendFromSlice(out, rt.byteVecSlice(_187)); + } + const wrapped: Wrap.Wrap[] = rt.vecFromElem(new Wrap.Wrap({ value: a, count: b }), n, copy$Wrap_u8); + if (n > 0) { + const _199: Wrap.Wrap[] = wrapped; + const _201: rt.usize = rt.usizeSub(n, 1); + rt.idx(_199, _201); + _199[_201].count = _199[_201].count ^ 255; + } + rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(wrapped), (w2: Wrap.Wrap): rt.u8 => { + return w2.value ^ w2.count; + })); + rt.iterExtendBytes(out, rt.iterMap(rt.iterSlice(names), (s2: string): rt.u8 => { + return rt.stringLen(s2) & 0xff; + })); } /** Stores `p` by value in `v`. */ diff --git a/ts/fixtures/lang/src/collections.rs b/ts/fixtures/lang/src/collections.rs index 27721da28..59418c1a3 100644 --- a/ts/fixtures/lang/src/collections.rs +++ b/ts/fixtures/lang/src/collections.rs @@ -7,6 +7,7 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque, btree_map, hash_map}; use std::hash::{DefaultHasher, Hash, Hasher}; +use crate::generics::Wrap; use crate::iters::Bumped; /// A key whose `Eq`, `Hash`, and `Ord` see only its low nibble, so two keys can be equal @@ -742,6 +743,22 @@ pub fn vecs(data: &[u8], out: &mut Vec) { out.extend_from_slice(&[pair.0, pair.1, listed[1].0, held.0]); out.extend(repeated.iter().chain(&again).map(|t| t.1)); out.extend(bumps.iter().chain(&kept).map(|o| o.0)); + + // A `Vec`'s slice is bytes only when its own elements are bytes. + let mut grown = bytes.clone(); + grown.as_mut_slice()[0] ^= 0xff; + out.extend_from_slice(grown.as_slice()); + let nested = vec![bytes, vec![b; n]]; + for v in nested.as_slice() { + out.push(v.len() as u8); + out.extend_from_slice(v); + } + let mut wrapped = vec![Wrap { value: a, count: b }; n]; + if n > 0 { + wrapped.as_mut_slice()[n - 1].count ^= 0xff; + } + out.extend(wrapped.as_slice().iter().map(|w| w.value ^ w.count)); + out.extend(names.as_slice().iter().map(|s| s.len() as u8)); } /// A struct that is not `Copy` holding a `Copy` aggregate by value. diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index 2aa198649..da4f480cb 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -944,8 +944,8 @@ fn mapping<'tcx>( let ty = targ(gargs, 0); let v = args.pop()?; match ty.kind() { - ty::Adt(adt, _) => match Std::of(tcx, adt.did()) { - Some(Std::Vec) => vec_view(v, ty), + ty::Adt(adt, vargs) => match Std::of(tcx, adt.did()) { + Some(Std::Vec) => vec_view(v, vargs.type_at(0)), Some(Std::String | Std::Box) => v, Some(Std::Cow) if string(tcx, ty) => v, _ => return None, @@ -1222,12 +1222,8 @@ fn primitive(ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Char) } -/// A `Vec`'s elements as a slice: an `rt.ByteSlice` of a byte vector, or the array itself. -pub fn vec_view(v: Expr, ty: Ty<'_>) -> Expr { - let elem = match ty.peel_refs().kind() { - ty::Adt(_, args) => args.type_at(0), - _ => ty, - }; +/// A `Vec` of `elem`s as a slice: an `rt.ByteSlice` of a byte vector, or the array itself. +pub fn vec_view(v: Expr, elem: Ty<'_>) -> Expr { match elem.kind() { ty::Uint(ty::UintTy::U8) => Expr::rt("byteVecSlice", vec![v]), _ => v, diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 9ddfa2063..cc333c1a0 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -849,7 +849,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // still held back. let (start, end) = self.range_bounds(&args[1].node, index_ty, span, out)?; let base = match container.kind() { - ty::Adt(..) => library::vec_view(base, container), + ty::Adt(..) => library::vec_view(base, elem_ty), _ => base, }; let base = self.path(base, "slice", out);