diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index 8e5ba9cb1..6af9b4f93 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -1,4 +1,4 @@ -# Translate moq-net's model to TypeScript +# [L] Translate moq-net's model to TypeScript ## Goal @@ -42,20 +42,19 @@ Decided with the maintainer on 2026-10-27, from a census of the model's 22 publi - The end-to-end work stays here: the model's roots, its differential harness, and the `frames.ts` workload. moq writes the binding files `model.toml` names, in its own tree. -The census at the pin on 2026-11-06: 71 refusals. `Timestamp` alone refuses nothing, and +The census at the pin on 2026-11-07: 51 refusals. `Timestamp` alone refuses nothing, and the [model fixture](/ts/README.md#tests)'s harness runs it against native moq-net at the pin; each case it adds as a type translates. Every other root reaches the rest of the -model, so each child below unblocks all of them together. The rest is moq's, for its -research quest: kio's `log.rs` reads bytes as its segments and retires them through -crossbeam-epoch, whose tagged pointers and inline closures (36 refusals) translation -refuses for good, and smallvec's union over a type parameter and raw parts (10) wait on -[unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops them, as it does the -`unsafe` slice of `model/frame.rs`'s `Filled`. +model, all of it moq's, for its research quest: kio's `log.rs` reads bytes as its segments +and retires them through crossbeam-epoch, whose tagged pointers and inline closures (36 +refusals) translation refuses for good, and smallvec's union over a type parameter and +raw parts (10) wait on [unsafe](/quest/m3/ts-unsafe/README.md), so moq rewrites or drops +them, as it does the `unsafe` slice of `model/frame.rs`'s `Filled`. Decided with the maintainer on 2026-11-04, from a census at the bumped pin -(moq-dev/rust2#390): the refusals no quest owned became the children below. ✅ arrayvec's -`ArrayString` needs three of them (const generic types, `begin_panic`, `str::as_ptr`), -kept separate since each lands alone; rejected: one quest for `ArrayString`. +(moq-dev/rust2#390): the refusals no quest owned became child quests, since finished. +✅ arrayvec's `ArrayString` needs three of them (const generic types, `begin_panic`, +`str::as_ptr`), kept separate since each lands alone; rejected: one quest for `ArrayString`. Decided with the maintainer on 2026-11-07, after three designs for addresses as identity ([#596](https://github.com/moq-dev/rust2/pull/596)) each left values whose ids were never freed @@ -74,8 +73,4 @@ or pointers whose comparison disagreed with Rust after an address was reused: starts, whether to rewrite one in safe Rust for both builds or wait for this translator. Rejected: twins until [unsafe](/quest/m3/ts-unsafe/README.md) translates them. -## Required - -- [A string's bytes through its pointer](/quest/m2/ts-model/str-as-ptr.md) - `str::as_ptr` reads the string's UTF-8 - Public API: the translator's output for moq-net's model. diff --git a/quest/m2/ts-model/str-as-ptr.md b/quest/m2/ts-model/str-as-ptr.md deleted file mode 100644 index 2c079ee74..000000000 --- a/quest/m2/ts-model/str-as-ptr.md +++ /dev/null @@ -1,15 +0,0 @@ -# [S] A string's bytes through its pointer - -## Goal - -[rust2-ts](/ts/README.md) translates `str::as_ptr` as a read-only pointer to the string's -UTF-8, so `ptr::copy_nonoverlapping` and `slice::from_raw_parts` read what Rust reads, -proven by differential tests against native Rust. At the moq pin arrayvec's -`ArrayString` refuses it once in the model and 3 times in moq-net whole. - -## Plan - -A `&str` stays a JS string (decided on #574), so the bytes are an encoding of it; a pointer into -them must not let a write through it go unnoticed, which Rust forbids anyway for a `&str`. - -Public API: the translator's output. diff --git a/quest/m3/ts-runtime-throws.md b/quest/m3/ts-runtime-throws.md index 94f502750..31a0d5c8c 100644 --- a/quest/m3/ts-runtime-throws.md +++ b/quest/m3/ts-runtime-throws.md @@ -26,10 +26,16 @@ The rest are gaps. At `main` they include a panic its hook cannot locate [#565](https://github.com/moq-dev/rust2/pull/565)), `offset_from` of one Rust allocation JS keeps in two arrays, a pointer moved past the array JS keeps it in (`AtIndex.reach`, such as one kept in a field), `Bytes::slice_ref` of other storage, `alloc`'s bytes read as -values, ranges stepped without their type, fmt widths written as a placeholder, and a raw +values, ranges stepped without their type, fmt widths written as a placeholder, a read +through a substring's `str::as_ptr` outside its own bytes but inside its text's +(`s[..1].as_ptr().add(1)`, `s[1..].as_ptr().wrapping_sub(1)`), and a raw slice of a byte in no array cast to `*const [u8; N]` by a route the body doesn't show, which [a byte local in a buffer of its own](/quest/m3/ts-unsafe/lone-byte-buffers.md) owns for locals. +The substring reads are UB under Stacked Borrows and allowed under Tree Borrows, so they +throw until Rust settles its memory model (decided 2026-11-07 in +[#634](https://github.com/moq-dev/rust2/pull/634)); if Rust rules them defined, a +substring's pointer carries its text's encoding and offset. `git grep 'rust2-ts: ' ts/runtime` lists them; classify each before fixing. The pointer comparisons [#584](https://github.com/moq-dev/rust2/pull/584) refuses at translation, where JS may make two `rs.Place`s for one place, are out of scope: addresses were their route, and diff --git a/ts/Cargo.lock b/ts/Cargo.lock index 21953f050..a657406fa 100644 --- a/ts/Cargo.lock +++ b/ts/Cargo.lock @@ -8,6 +8,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "base64-simd" version = "0.8.0" @@ -652,6 +658,7 @@ version = "0.0.0" name = "stock-fixture" version = "0.0.0" dependencies = [ + "arrayvec", "bytes", "getrandom", "hashbrown", diff --git a/ts/README.md b/ts/README.md index f12f6e843..30baa5cdb 100644 --- a/ts/README.md +++ b/ts/README.md @@ -110,7 +110,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Vec` | `rs.Vec`: its items, the array `items`, and the capacity std's holds; `rs.vecFrom(items)` makes one of an array with room for exactly them. `rs.ByteVec` of bytes | | `Cow<[T]>`, `Cow` | `rs.Cow`: the `Vec` it owns, or an `rs.Borrowed` holding the slice it borrows, `rs.Cow>` holding the arrays of a slice of arrays; `rs.ByteCow` for bytes, an `rs.ByteVec` or an `rs.ByteSlice`; `rs.StrCow` for text, the `rs.String` it owns or an `rs.Borrowed` holding the string it borrows, whose text `rs.strCowStr` reads | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero`, `AssertUnwindSafe` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rs.Place`, which for what a box holds is the place holding the box | -| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | +| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rs.Slice` of a `[T; N]`'s elements or an `rs.ByteSlice` of a `[u8; N]`'s bytes, which compares by the array or buffer and offset they are at, refused for an array of one, which `array::from_ref`, `from_mut` of a variable, and `Option::as_slice` make of their own, or an `rs.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rs.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, `write_bytes` of bytes, and `slice::from_raw_parts` move, measure, copy between, set, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, copy, or write of bytes the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in but for a `Vec`'s room up to its capacity, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to other values JS copies, each a place JS makes of its own; `rs.NULL` for null and `rs.DANGLING` for dangling. `str::as_ptr` points into the text's UTF-8, which JS encodes anew at each call, so a read through it reads what Rust's does and a write, or a mutable slice, through it, which Rust forbids, fails at run time, as do a read before its first byte, such as a substring's pointer moved back into its text's bytes, and `offset_from` between two calls' pointers, each refused where the body shows it. A pointer to a slice cast to one to an array of its elements is the `rs.Slice` or `rs.ByteSlice` of that many from its first, read through only where Rust reads. A `*const ()` is an identity, such as a waker's data, and `rs.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rs.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rs.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rs` class such as `rs.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined`, as `Box::new_uninit` and each of `Box::new_uninit_slice`'s is; a slice's `write_clone_of_slice` clones into its slots without dropping what they held. `Box::new_zeroed`'s holds the value of zero bytes in each part they are one of, such as a number, `false`, `None`, a null pointer, or an enum's variant of a tag or niche of zero, and `undefined` in each part they are none of, such as a reference, for a write to fill before a read | | `ManuallyDrop` | `T`, which nothing drops but its own `drop`; its `Debug`, comparisons, and constants are refused | | `union` | an object of slots named by offset, `at0` and `at8`, each holding the field part kept there; fields must overlap in the same type, or in a non-null pointer and an `Option` of it | @@ -1215,7 +1215,8 @@ so adding, removing, or reordering a case changes no other case's inputs. script of spawns, sends, polls, and drops, and logs every poll, wake, and drop, as the fixture's native executor does. The logs must match byte for byte. - `stock` calls `bytes`, `hashbrown`, `rand`, and `tracing` with no config, `hashlink` - over `hashbrown`, and `rangemap` over std's B-tree, against native Rust, whose + over `hashbrown`, `rangemap` over std's B-tree, and arrayvec's `ArrayString` over raw + pointers, against native Rust, whose `rand::rng()` reads the fixture's own getrandom backend on wasm32. It checks `Bytes`'s slices and panics, how each is made and printed, that no event evaluates a field, and that each random value is in its range, every value of a small range drawn. It splits, @@ -1230,7 +1231,8 @@ so adding, removing, or reordering a case changes no other case's inputs. that collide and that pass 2^53, checks their capacity by std's guarantees, and writes a `hashlink::LinkedHashMap` in its own order. It inserts, removes, looks up, overlaps, and gaps `rangemap`'s maps and sets, which search their B-trees by what each key lends by - rangemap's own `Borrow`. `caller.ts` names the inputs that read a `BytesMut` past + rangemap's own `Borrow`. It pushes, pops, truncates, and removes text of each UTF-8 + width in an `ArrayString` until it is full. `caller.ts` names the inputs that read a `BytesMut` past where `bytes` shares a vector's storage, 128 MiB, drop each pick of a storage's handles before reserving, and read a deque across where its ring wraps and past its end. - `bind` binds `buf::Blob`, a renamed dependency's type with `unsafe` inside, and a function and a type of its own that read through raw diff --git a/ts/fixtures/lang/strs/expected/lib.ts b/ts/fixtures/lang/strs/expected/lib.ts index f7600b2f5..173a98f19 100644 --- a/ts/fixtures/lang/strs/expected/lib.ts +++ b/ts/fixtures/lang/strs/expected/lib.ts @@ -1,7 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { strs, slices, edits, parses, iters, drains, hinted, gets, unicode, utf16 } from "./strs.ts"; +import { strs, slices, edits, parses, iters, drains, hinted, gets, unicode, utf16, pointers } from "./strs.ts"; /** * Each case's name and what it runs. The first two bytes, big-endian, pick one modulo @@ -12,7 +12,7 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By edits(a0, a1.data, a1.start, a1.length, a2); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::parses", parses as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::iters", iters as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::drains", (a02: rs.u8, a12: rs.ByteSlice, a22: rs.ByteVec): undefined => { drains(a02, a12.data, a12.start, a12.length, a22); - }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::hinted", hinted as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::gets", gets as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::unicode", unicode as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::utf16", utf16 as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::hinted", hinted as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::gets", gets as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::unicode", unicode as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::utf16", utf16 as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["strs::pointers", pointers as (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } //# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/strs/expected/strs.Fixed.ts b/ts/fixtures/lang/strs/expected/strs.Fixed.ts new file mode 100644 index 000000000..a0fe7a0ae --- /dev/null +++ b/ts/fixtures/lang/strs/expected/strs.Fixed.ts @@ -0,0 +1,32 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** Text in a fixed buffer, pushed as arrayvec's `ArrayString` pushes it. */ +export interface Fixed { + xs: Uint8Array; + len: rs.usize; +} + +function new_(): Fixed { + return { xs: rs.bytes(12).fill((0 as unknown) as rs.u8), len: 0 }; +} + +export function tryPushStr(self: Fixed, s: string): rs.Result { + if (!(rs.stringLen(s) > rs.usizeSub(rs.byteSliceFrom(self.xs).length, self.len))) { + const dst: rs.Place = rs.ptrAdd(rs.byteSliceAsPtr(rs.byteSliceFrom(self.xs)), self.len); + rs.ptrCopy(rs.strAsPtr(s), dst, rs.stringLen(s)); + const _20: rs.usize = rs.stringLen(s); + self.len = rs.usizeAdd(self.len, _20); + return rs.OK; + } + return rs.Err(s); +} + +export function asStr(self: Fixed): string { + return rs.strFromUtf8Unchecked(rs.ptrByteSlice(rs.byteSliceAsPtr(rs.byteSliceFrom(self.xs)), self.len, true)); +} + +export { new_ as new }; + +//# sourceMappingURL=strs.Fixed.ts.map diff --git a/ts/fixtures/lang/strs/expected/strs.ts b/ts/fixtures/lang/strs/expected/strs.ts index 3a564bd1e..394e6f18a 100644 --- a/ts/fixtures/lang/strs/expected/strs.ts +++ b/ts/fixtures/lang/strs/expected/strs.ts @@ -8,6 +8,7 @@ import { parse$1yfzdza, parse$1d17hvd, parse$0wntisu, parse$18h4mna, parse$1gh8p import { fromStr$1dw9076 as traits_fromStr$1dw9076 } from "./core/str/traits.ts"; import { put, text } from "./harness.ts"; import * as Counted from "./strs.Counted.ts"; +import * as Fixed from "./strs.Fixed.ts"; import * as Named from "./strs.Named.ts"; /** Whether the `i`th step reads from the back: by the byte's high bit. */ @@ -1965,4 +1966,80 @@ export function utf16(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): void { put(out, rs.fmtFormat([args12[0], "|", args12[1]]).v); } +/** + * A `str`'s UTF-8 read through `as_ptr`, of the text and of a substring from the char + * boundary the kind picks, whose bytes start past the text's: copied by + * `ptr::copy_nonoverlapping` and `ptr::copy`, viewed by `slice::from_raw_parts`, read a + * byte at a time between pointers `offset_from` measures, and pushed into a buffer of + * `MaybeUninit` bytes as arrayvec's `ArrayString::try_push_str` pushes. + */ +export function pointers(kind: rs.u8, data: rs.ByteSlice, out: rs.ByteVec): void { + const s: rs.String = text(data); + const starts: rs.Vec = rs.iterToVec(rs.iterChain(rs.iterMap(rs.strCharIndices(s.v), (_2: [rs.usize, rs.char]): rs.usize => { + return _2[0]; + }), rs.iterSlice([s.len])), "next", 4); + const sub: string = rs.strSlice(s.v, starts.items[rs.idx(starts.items, rs.usizeRem(rs.u8Div(kind, 4) as rs.usize, starts.items.length))]); + b0: { + const _27: rs.u8 = rs.u8Rem(kind, 4); + if (_27 === 0) { + const whole: rs.ByteVec = rs.byteVecWithCapacity(s.len); + rs.ptrCopy(rs.strAsPtr(s.v), rs.byteVecAsPtr(whole), s.len); + rs.byteVecSetLen(whole, s.len); + rs.byteVecExtendFromSlice(out, rs.byteVecSlice(whole)); + const framed: rs.ByteVec = rs.byteVecOf(170, rs.usizeAdd(rs.stringLen(sub), 2)); + rs.ptrCopy(rs.strAsPtr(sub), rs.ptrAdd(rs.byteVecAsPtr(framed), 1), rs.stringLen(sub)); + rs.byteVecExtendFromSlice(out, rs.byteVecSlice(framed)); + } else if (_27 === 1) { + const p: rs.Place = rs.strAsPtr(sub); + const iter: rs.RangeInclusive = new rs.RangeInclusive(0, rs.stringLen(sub), rs.usizeStep); + while (true) { + const _65: rs.usize | rs.Done = iter.next(); + if (_65 === rs.DONE) { + break; + } + const i: rs.usize = _65; + const tail: rs.ByteSlice = rs.ptrByteSlice(rs.ptrAdd(p, i), rs.usizeSub(rs.stringLen(sub), i), true); + rs.byteVecPush(out, tail.length & 0xff); + rs.byteVecExtendFromSlice(out, option_unwrapOr$0yvilko(rs.byteSliceGetRange(tail, 0, 2), tail)); + } + put(out, rs.strFromUtf8Unchecked(rs.ptrByteSlice(p, rs.stringLen(sub), true))); + } else { + if (_27 === 2) { + const p2: rs.Place = rs.strAsPtr(s.v); + const end: rs.Place = rs.ptrAdd(p2, s.len); + let q: rs.Place = p2; + while (rs.ptrOffsetFrom(end, q) > 0) { + rs.byteVecPush(out, q.v); + q = rs.ptrAdd(q, 1); + } + rs.byteVecPush(out, rs.ptrOffsetFrom(q, p2) & 0xff); + const pointer = end; + let back: rs.Place = rs.optionUnwrap(pointer === rs.NULL ? undefined : pointer); + while (true) { + if (!(rs.ptrOffsetFrom(back, p2) > 0)) { + break b0; + } + back = rs.ptrAdd(back, -1); + rs.byteVecPush(out, back.v); + } + } + const fixed: Fixed.Fixed = Fixed.new(); + const iter2: rs.Iter = rs.iterChain(rs.strSplit(s.v, "/"), rs.iterSlice([sub])); + while (true) { + const _126: string | rs.Done = iter2.next(); + if (_126 === rs.DONE) { + break; + } + const _130: rs.Result = Fixed.tryPushStr(fixed, _126); + if (_130.ok) { + rs.byteVecPush(out, fixed.len & 0xff); + } else { + put(out, _130.error); + } + } + put(out, Fixed.asStr(fixed)); + } + } +} + //# sourceMappingURL=strs.ts.map diff --git a/ts/fixtures/lang/strs/src/lib.rs b/ts/fixtures/lang/strs/src/lib.rs index 00731f104..f73e8b9bb 100644 --- a/ts/fixtures/lang/strs/src/lib.rs +++ b/ts/fixtures/lang/strs/src/lib.rs @@ -26,5 +26,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("strs::gets", strs::gets), ("strs::unicode", strs::unicode), ("strs::utf16", strs::utf16), + ("strs::pointers", strs::pointers), ] } diff --git a/ts/fixtures/lang/strs/src/strs.rs b/ts/fixtures/lang/strs/src/strs.rs index 81cf1e0cd..134902658 100644 --- a/ts/fixtures/lang/strs/src/strs.rs +++ b/ts/fixtures/lang/strs/src/strs.rs @@ -1,11 +1,15 @@ //! `str` and `String` over `char`: iterated from both ends, searched by a `char`, a //! predicate, or a `&str`, trimmed, split, sliced by byte offsets, built a char at a //! time, edited in place, parsed into a `bool`, a `char`, or a `NonZero`, mapped by case, -//! escaped, and encoded as and decoded from UTF-16. Text is made of chars of every UTF-8 -//! width and those next to UTF-16's surrogates, so byte offsets and code units part. +//! escaped, encoded as and decoded from UTF-16, and read through `as_ptr`. Text is made +//! of chars of every UTF-8 width and those next to UTF-16's surrogates, so byte offsets +//! and code units part. //! Predicates log each char they are called with, so where a search stops is output too. +use std::mem::MaybeUninit; use std::ops::Bound; +use std::ptr::{self, NonNull}; +use std::{slice, str}; use crate::harness::{ALPHABET, put, text}; @@ -1226,3 +1230,107 @@ pub fn utf16(kind: u8, data: &[u8], out: &mut Vec) { .collect(); put(out, &format!("{each:?}|{ok:?}")); } + +/// A `str`'s UTF-8 read through `as_ptr`, of the text and of a substring from the char +/// boundary the kind picks, whose bytes start past the text's: copied by +/// `ptr::copy_nonoverlapping` and `ptr::copy`, viewed by `slice::from_raw_parts`, read a +/// byte at a time between pointers `offset_from` measures, and pushed into a buffer of +/// `MaybeUninit` bytes as arrayvec's `ArrayString::try_push_str` pushes. +pub fn pointers(kind: u8, data: &[u8], out: &mut Vec) { + let s = text(data); + let starts: Vec = s.char_indices().map(|(i, _)| i).chain([s.len()]).collect(); + let sub = &s[starts[usize::from(kind / 4) % starts.len()]..]; + match kind % 4 { + 0 => { + // Into a `Vec`'s room, and between bytes of another `Vec`. + let mut whole = Vec::::with_capacity(s.len()); + // SAFETY: `whole` has room for `s`'s bytes, which it doesn't overlap. + unsafe { + ptr::copy_nonoverlapping(s.as_ptr(), whole.as_mut_ptr(), s.len()); + whole.set_len(s.len()); + } + out.extend_from_slice(&whole); + let mut framed = vec![0xaa; sub.len() + 2]; + // SAFETY: `framed` holds `sub`'s bytes after its first. + unsafe { ptr::copy(sub.as_ptr(), framed.as_mut_ptr().add(1), sub.len()) }; + out.extend_from_slice(&framed); + } + 1 => { + // Every tail, and the whole as text again. + let p = sub.as_ptr(); + for i in 0..=sub.len() { + // SAFETY: the `sub.len() - i` bytes from `i` are `sub`'s. + let tail = unsafe { slice::from_raw_parts(p.add(i), sub.len() - i) }; + out.push(tail.len() as u8); + out.extend_from_slice(tail.get(..2).unwrap_or(tail)); + } + // SAFETY: `sub`'s bytes, which are UTF-8. + put(out, unsafe { + str::from_utf8_unchecked(slice::from_raw_parts(p, sub.len())) + }); + } + 2 => { + // Forward to the end and back to the start, by `*` and through a `NonNull`. + let p = s.as_ptr(); + // SAFETY: one past `s`'s last byte, and each read is between it and `p`. + unsafe { + let end = p.add(s.len()); + let mut q = p; + while end.offset_from(q) > 0 { + out.push(*q); + q = q.add(1); + } + out.push(q.offset_from_unsigned(p) as u8); + let mut back = NonNull::new(end.cast_mut()).unwrap(); + while back.as_ptr().cast_const().offset_from(p) > 0 { + back = back.sub(1); + out.push(*back.as_ptr()); + } + } + } + _ => { + // Each part between `/`s, until one doesn't fit, then the substring. + let mut fixed = Fixed::new(); + for part in s.split('/').chain([sub]) { + match fixed.try_push_str(part) { + Ok(()) => out.push(fixed.len as u8), + Err(e) => put(out, e), + } + } + put(out, fixed.as_str()); + } + } +} + +/// Text in a fixed buffer, pushed as arrayvec's `ArrayString` pushes it. +struct Fixed { + xs: [MaybeUninit; 12], + len: usize, +} + +impl Fixed { + fn new() -> Self { + Fixed { + xs: [MaybeUninit::uninit(); 12], + len: 0, + } + } + + fn try_push_str<'a>(&mut self, s: &'a str) -> Result<(), &'a str> { + if s.len() > self.xs.len() - self.len { + return Err(s); + } + // SAFETY: there is room for `s` after the first `len` bytes. + unsafe { + let dst = (self.xs.as_mut_ptr() as *mut u8).add(self.len); + ptr::copy_nonoverlapping(s.as_ptr(), dst, s.len()); + } + self.len += s.len(); + Ok(()) + } + + fn as_str(&self) -> &str { + // SAFETY: the first `len` bytes were written, whole `str`s each. + unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.xs.as_ptr() as *const u8, self.len)) } + } +} diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index c0871e02a..155cd4036 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -226,6 +226,9 @@ fixtures/reject/src/pointers.rs:range_of+1:2: a raw pointer to `(u32, u32)` in ` fixtures/reject/src/pointers.rs:rc_raw_bytes+1:11: an `Rc`'s raw pointer to `[u8; 4]`, which JS makes anew each time fixtures/reject/src/pointers.rs:reached_out+7:6: a slice reaching past a value in no array, which JS keeps apart from what holds it in Rust fixtures/reject/src/pointers.rs:reached_out+8:27: a pointer reaching past a value in no array, which JS keeps apart from what holds it in Rust +fixtures/reject/src/pointers.rs:text_apart+1:27: `offset_from` between pointers of two `str::as_ptr` calls, each into an encoding of the text of its own, which JS keeps in different arrays +fixtures/reject/src/pointers.rs:text_apart+1:64: `offset_from` between pointers of two `str::as_ptr` calls, each into an encoding of the text of its own, which JS keeps in different arrays +fixtures/reject/src/pointers.rs:text_before+1:28: a pointer reaching past a `str`'s bytes, which JS keeps apart from what holds it in Rust fixtures/reject/src/pointers.rs:uninit_part_mut+5:25: a mutable reborrow of `std::mem::MaybeUninit<[u32; 2]>` through a raw pointer, which may point into part of an array with no JS array of its own, which is not supported yet fixtures/reject/src/pointers.rs:uninit_part_replaced+5:19: a raw pointer to `std::mem::MaybeUninit<(u32, u32)>` in `pointers::Gap`, which the crate may put another object in place of fixtures/reject/src/pointers.rs:uninit_part_replaced+6:19: a raw pointer to `std::mem::MaybeUninit<(u32, u32)>` in `pointers::Gap`, which the crate may put another object in place of diff --git a/ts/fixtures/reject/src/pointers.rs b/ts/fixtures/reject/src/pointers.rs index bcdb231fe..d81ff2fe0 100644 --- a/ts/fixtures/reject/src/pointers.rs +++ b/ts/fixtures/reject/src/pointers.rs @@ -408,6 +408,18 @@ pub fn pointers_apart(n: u32, h: Halves, r: Rows, byte: u8) -> isize { } } +/// `offset_from` between pointers into a `str`'s bytes, which JS encodes anew at each +/// `as_ptr`: a substring's against its text's, and two of one text. +pub fn text_apart(s: &str) -> isize { + unsafe { s[1..].as_ptr().offset_from(s.as_ptr()) + s.as_ptr().offset_from(s.as_ptr()) } +} + +/// A pointer into a substring's bytes moved back before them, into its text's, which JS +/// encodes apart, and read. +pub fn text_before(s: &str) -> u8 { + unsafe { *s[1..].as_ptr().sub(1) } +} + /// A raw and a mutable slice of a byte in no array, which no `Uint8Array` can share. pub fn lone_byte_slices(mut b: u8) -> u8 { let raw = std::ptr::slice_from_raw_parts_mut(&raw mut b, 1); diff --git a/ts/fixtures/stock/Cargo.toml b/ts/fixtures/stock/Cargo.toml index 7899e199f..6c5e47837 100644 --- a/ts/fixtures/stock/Cargo.toml +++ b/ts/fixtures/stock/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stock-fixture" -description = "Calls into bytes, hashbrown, rand, and tracing, which rust2-ts maps itself, hashlink, and rangemap" +description = "Calls into bytes, hashbrown, rand, and tracing, which rust2-ts maps itself, hashlink, rangemap, and arrayvec" version = "0.0.0" edition.workspace = true license.workspace = true @@ -11,6 +11,7 @@ publish = false crate-type = ["lib", "cdylib"] [dependencies] +arrayvec = "0.7.8" bytes = "1.12.1" # The native side's source of randomness on wasm32, which its tests select. getrandom = "0.4.3" diff --git a/ts/fixtures/stock/expected/arrayvec/array_string.ArrayString.ts b/ts/fixtures/stock/expected/arrayvec/array_string.ArrayString.ts new file mode 100644 index 000000000..bb3a5106f --- /dev/null +++ b/ts/fixtures/stock/expected/arrayvec/array_string.ArrayString.ts @@ -0,0 +1,341 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; +import { copy$arrayvec_ArrayString_12, out$usize } from "../lib.ts"; +import { encodeUtf8 } from "../arrayvec/char.ts"; +import type * as EncodeUtf8Error from "../arrayvec/char.EncodeUtf8Error.ts"; +import * as CapacityError from "../arrayvec/errors.CapacityError.ts"; + +/** + * A string with a fixed capacity. + * + * The `ArrayString` is a string backed by a fixed size array. It keeps track + * of its length, and is parameterized by `CAP` for the maximum capacity. + * + * `CAP` is of type `usize` but is range limited to `u32::MAX` (or `u16` on 16-bit targets); + * attempting to create larger arrayvecs with larger capacity will panic. + * + * The string is a contiguous value that you can store directly on the stack + * if needed. + */ +export interface ArrayString { + len: rs.u32; + xs: Uint8Array; +} + +/** + * Create a new empty `ArrayString`. + * + * Capacity is inferred from the type parameter. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<16>::new(); + * string.push_str("foo"); + * assert_eq!(&string[..], "foo"); + * assert_eq!(string.capacity(), 16); + * ``` + */ +export function new$12(): ArrayString { + if (false) { + if (false) { + rs.panic("ArrayVec: largest supported capacity is u32::MAX"); + } + } + return { len: 0, xs: (new Uint8Array(12) as unknown) as Uint8Array }; +} + +/** Return the length of the string. */ +export function len$12(self: ArrayString): rs.usize { + return self.len as rs.usize; +} + +/** Returns whether the string is empty. */ +export function isEmpty$12(self: ArrayString): boolean { + return len$12(self) === 0; +} + +/** + * Create a new `ArrayString` from a `str`. + * + * Capacity is inferred from the type parameter. + * + * **Errors** if the backing array is not large enough to fit the string. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<3>::from("foo").unwrap(); + * assert_eq!(&string[..], "foo"); + * assert_eq!(string.len(), 3); + * assert_eq!(string.capacity(), 3); + * ``` + */ +export function from$12(s: string): rs.Result> { + const arraystr: ArrayString = copy$arrayvec_ArrayString_12(new$12()); + const _3: rs.Result> = tryPushStr$12(arraystr, s); + if (_3.ok) { + return rs.Ok(copy$arrayvec_ArrayString_12(arraystr)); + } + return _3; +} + +/** + * Return the capacity of the `ArrayString`. + * + * ``` + * use arrayvec::ArrayString; + * + * let string = ArrayString::<3>::new(); + * assert_eq!(string.capacity(), 3); + * ``` + */ +export function capacity$12(_self: ArrayString): rs.usize { + return 12; +} + +/** + * Return if the `ArrayString` is completely filled. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<1>::new(); + * assert!(!string.is_full()); + * string.push_str("A"); + * assert!(string.is_full()); + * ``` + */ +export function isFull$12(self: ArrayString): boolean { + return len$12(self) === capacity$12(self); +} + +/** + * Returns the capacity left in the `ArrayString`. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<3>::from("abc").unwrap(); + * string.pop(); + * assert_eq!(string.remaining_capacity(), 1); + * ``` + */ +export function remainingCapacity$12(self: ArrayString): rs.usize { + return rs.usizeSub(capacity$12(self), len$12(self)); +} + +/** + * Adds the given char to the end of the string. + * + * Returns `Ok` if the push succeeds. + * + * **Errors** if the backing array is not large enough to fit the additional char. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<2>::new(); + * + * string.try_push('a').unwrap(); + * string.try_push('b').unwrap(); + * let overflow = string.try_push('c'); + * + * assert_eq!(&string[..], "ab"); + * assert_eq!(overflow.unwrap_err().element(), 'c'); + * ``` + */ +export function tryPush$12(self: ArrayString, c: rs.char): rs.Result> { + const len: rs.usize = len$12(self); + const part: rs.usize | undefined = encodeUtf8(c, rs.ptrAdd(asMutPtr$12(self), len), rs.usizeSub(capacity$12(self), len)); + if ((part === undefined ? out$usize.err as EncodeUtf8Error.EncodeUtf8Error : undefined) === undefined) { + setLen$12(self, rs.usizeAdd(len, part as rs.usize)); + return rs.OK; + } + return rs.Err(CapacityError.new$char(c)); +} + +/** + * Adds the given string slice to the end of the string. + * + * ***Panics*** if the backing array is not large enough to fit the string. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<2>::new(); + * + * string.push_str("a"); + * string.push_str("d"); + * + * assert_eq!(&string[..], "ad"); + * ``` + */ +export function pushStr$12(self: ArrayString, s: string, _caller: rs.Location): void { + rs.resultUnwrap(tryPushStr$12(self, s), (value: CapacityError.CapacityError, f: rs.Formatter) => CapacityError.fmt$fmt_Debug_ref_str(value, f)); +} + +/** + * Adds the given string slice to the end of the string. + * + * Returns `Ok` if the push succeeds. + * + * **Errors** if the backing array is not large enough to fit the string. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<2>::new(); + * + * string.try_push_str("a").unwrap(); + * let overflow1 = string.try_push_str("bc"); + * string.try_push_str("d").unwrap(); + * let overflow2 = string.try_push_str("ef"); + * + * assert_eq!(&string[..], "ad"); + * assert_eq!(overflow1.unwrap_err().element(), "bc"); + * assert_eq!(overflow2.unwrap_err().element(), "ef"); + * ``` + */ +export function tryPushStr$12(self: ArrayString, s: string): rs.Result> { + if (!(rs.stringLen(s) > rs.usizeSub(capacity$12(self), len$12(self)))) { + const dst: rs.Place = rs.ptrAdd(asMutPtr$12(self), len$12(self)); + rs.ptrCopy(rs.strAsPtr(s), dst, rs.stringLen(s)); + setLen$12(self, rs.usizeAdd(len$12(self), rs.stringLen(s))); + return rs.OK; + } + return rs.Err(CapacityError.new$ref_str(s)); +} + +/** + * Removes the last character from the string and returns it. + * + * Returns `None` if this `ArrayString` is empty. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut s = ArrayString::<3>::from("foo").unwrap(); + * + * assert_eq!(s.pop(), Some('o')); + * assert_eq!(s.pop(), Some('o')); + * assert_eq!(s.pop(), Some('f')); + * + * assert_eq!(s.pop(), None); + * ``` + */ +export function pop$12(self: ArrayString): rs.char | undefined { + const _2: rs.char | rs.Done = rs.iterRev(rs.strChars(deref$ops_Deref_12(self))).next(); + if (_2 === rs.DONE) { + return undefined; + } + const ch: rs.char = _2; + setLen$12(self, rs.usizeSub(len$12(self), rs.charLenUtf8(ch))); + return ch; +} + +/** + * Shortens this `ArrayString` to the specified length. + * + * If `new_len` is greater than the string’s current length, this has no + * effect. + * + * ***Panics*** if `new_len` does not lie on a `char` boundary. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut string = ArrayString::<6>::from("foobar").unwrap(); + * string.truncate(3); + * assert_eq!(&string[..], "foo"); + * string.truncate(4); + * assert_eq!(&string[..], "foo"); + * ``` + */ +export function truncate$12(self: ArrayString, newLen: rs.usize): void { + if (newLen <= len$12(self)) { + if (!rs.strIsCharBoundary(deref$ops_Deref_12(self), newLen)) { + rs.panic("assertion failed: self.is_char_boundary(new_len)"); + } else { + setLen$12(self, newLen); + } + } +} + +/** + * Removes a `char` from this `ArrayString` at a byte position and returns it. + * + * This is an `O(n)` operation, as it requires copying every element in the + * array. + * + * ***Panics*** if `idx` is larger than or equal to the `ArrayString`’s length, + * or if it does not lie on a `char` boundary. + * + * ``` + * use arrayvec::ArrayString; + * + * let mut s = ArrayString::<3>::from("foo").unwrap(); + * + * assert_eq!(s.remove(0), 'f'); + * assert_eq!(s.remove(1), 'o'); + * assert_eq!(s.remove(0), 'o'); + * ``` + */ +export function remove$12(self: ArrayString, idx: rs.usize): rs.char { + const _3: rs.char | rs.Done = rs.strChars(rs.strSlice(deref$ops_Deref_12(self), idx)).next(); + if (_3 === rs.DONE) { + rs.panic("cannot remove a char from the end of a string"); + } else { + const ch: rs.char = _3; + const next: rs.usize = rs.usizeAdd(idx, rs.charLenUtf8(ch)); + const len: rs.usize = len$12(self); + const ptr: rs.Place = asMutPtr$12(self); + rs.ptrCopy(rs.ptrAdd(ptr, next), rs.ptrAdd(ptr, idx), rs.usizeSub(len, next)); + setLen$12(self, rs.usizeSub(len, rs.usizeSub(next, idx))); + return ch; + } +} + +/** Make the string empty. */ +export function clear$12(self: ArrayString): void { + setLen$12(self, 0); +} + +/** + * Set the strings’s length. + * + * This function is `unsafe` because it changes the notion of the + * number of “valid” bytes in the string. Use with care. + * + * This method uses *debug assertions* to check the validity of `length` + * and may use other debug assertions. + */ +export function setLen$12(self: ArrayString, length: rs.usize): void { + if (!(length <= capacity$12(self))) { + rs.panic("assertion failed: length <= self.capacity()"); + } else { + self.len = length as rs.u32; + } +} + +/** Return a string slice of the whole `ArrayString`. */ +export function asStr$12(self: ArrayString): string { + return deref$ops_Deref_12(self); +} + +/** Return a raw pointer to the string's buffer. */ +export function asPtr$12(self: ArrayString): rs.Place { + return rs.byteSliceAsPtr(rs.byteSliceFrom(self.xs)); +} + +/** Return a raw mutable pointer to the string's buffer. */ +export function asMutPtr$12(self: ArrayString): rs.Place { + return rs.byteSliceAsPtr(rs.byteSliceFrom(self.xs)); +} + +export function deref$ops_Deref_12(self: ArrayString): string { + return rs.strFromUtf8Unchecked(rs.ptrByteSlice(asPtr$12(self), len$12(self), true)); +} + +//# sourceMappingURL=array_string.ArrayString.ts.map diff --git a/ts/fixtures/stock/expected/arrayvec/char.EncodeUtf8Error.ts b/ts/fixtures/stock/expected/arrayvec/char.EncodeUtf8Error.ts new file mode 100644 index 000000000..8815f8bd3 --- /dev/null +++ b/ts/fixtures/stock/expected/arrayvec/char.EncodeUtf8Error.ts @@ -0,0 +1,6 @@ +// Generated by rust2-ts. Do not edit. + +export interface EncodeUtf8Error { +} + +//# sourceMappingURL=char.EncodeUtf8Error.ts.map diff --git a/ts/fixtures/stock/expected/arrayvec/char.ts b/ts/fixtures/stock/expected/arrayvec/char.ts new file mode 100644 index 000000000..801a766c8 --- /dev/null +++ b/ts/fixtures/stock/expected/arrayvec/char.ts @@ -0,0 +1,40 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; +import { out$usize } from "../lib.ts"; + +export function encodeUtf8(ch: rs.char, ptr: rs.Place, len: rs.usize): rs.usize | undefined { + const code: rs.u32 = ch as rs.u32; + if (code < 128) { + if (len >= 1) { + rs.ptrAdd(ptr, 0).v = code & 0xff; + return 1; + } + } + if (code < 2048) { + if (len >= 2) { + rs.ptrAdd(ptr, 0).v = ((((code >>> 6) & 31) >>> 0) & 0xff) | 192; + rs.ptrAdd(ptr, 1).v = (((code & 63) >>> 0) & 0xff) | 128; + return 2; + } + } + if (code < 65536) { + if (len >= 3) { + rs.ptrAdd(ptr, 0).v = ((((code >>> 12) & 15) >>> 0) & 0xff) | 224; + rs.ptrAdd(ptr, 1).v = ((((code >>> 6) & 63) >>> 0) & 0xff) | 128; + rs.ptrAdd(ptr, 2).v = (((code & 63) >>> 0) & 0xff) | 128; + return 3; + } + } + if (!(len >= 4)) { + out$usize.err = {}; + return undefined; + } + rs.ptrAdd(ptr, 0).v = ((((code >>> 18) & 7) >>> 0) & 0xff) | 240; + rs.ptrAdd(ptr, 1).v = ((((code >>> 12) & 63) >>> 0) & 0xff) | 128; + rs.ptrAdd(ptr, 2).v = ((((code >>> 6) & 63) >>> 0) & 0xff) | 128; + rs.ptrAdd(ptr, 3).v = (((code & 63) >>> 0) & 0xff) | 128; + return 4; +} + +//# sourceMappingURL=char.ts.map diff --git a/ts/fixtures/stock/expected/arrayvec/errors.CapacityError.ts b/ts/fixtures/stock/expected/arrayvec/errors.CapacityError.ts new file mode 100644 index 000000000..160a86502 --- /dev/null +++ b/ts/fixtures/stock/expected/arrayvec/errors.CapacityError.ts @@ -0,0 +1,33 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "../rs.ts"; + +/** Error value indicating insufficient capacity */ +export class CapacityError { + element: T; + + constructor(element: T) { + this.element = element; + } +} + +/** Create a new `CapacityError` from `element`. */ +export function new$char(element: rs.char): CapacityError { + return new CapacityError(element); +} + +/** Create a new `CapacityError` from `element`. */ +export function new$ref_str(element: string): CapacityError { + return new CapacityError(element); +} + +/** Extract the overflowing element */ +export function element$ref_str(self: CapacityError): string { + return self.element; +} + +export function fmt$fmt_Debug_ref_str(_self: CapacityError, f: rs.Formatter): rs.Result { + return rs.fmtWriteFmt(f, ["CapacityError: ", (f: rs.Formatter) => rs.fmtPad(f, "insufficient capacity")]); +} + +//# sourceMappingURL=errors.CapacityError.ts.map diff --git a/ts/fixtures/stock/expected/core/option.ts b/ts/fixtures/stock/expected/core/option.ts index 529ef582f..249905ccd 100644 --- a/ts/fixtures/stock/expected/core/option.ts +++ b/ts/fixtures/stock/expected/core/option.ts @@ -140,6 +140,13 @@ export function map$1nxhy7e(self: [rs.u16, rs.u32] | undefined, f: (a0: [rs.u16, return f(self); } +export function mapOr$0yzb10z(self: rs.char | undefined, default_: rs.u32, _: (a0: rs.char) => rs.u32): rs.u32 { + if (self === undefined) { + return default_; + } + return self as rs.u32; +} + export function mapOr$00octnd(self: Logged.Logged | undefined, default_: rs.u8, f: (a0: Logged.Logged) => rs.u8): rs.u8 { if (self === undefined) { return default_; diff --git a/ts/fixtures/stock/expected/fixed.ts b/ts/fixtures/stock/expected/fixed.ts new file mode 100644 index 000000000..4ea3825c5 --- /dev/null +++ b/ts/fixtures/stock/expected/fixed.ts @@ -0,0 +1,80 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { copy$arrayvec_ArrayString_12 } from "./lib.ts"; +import * as ArrayString from "./arrayvec/array_string.ArrayString.ts"; +import * as CapacityError from "./arrayvec/errors.CapacityError.ts"; +import { mapOr$0yzb10z as option_mapOr$0yzb10z } from "./core/option.ts"; + +/** + * Each byte an edit of an `ArrayString` of 12 bytes, whose high bits pick a char or a + * length, writing what it returns, then the text. + */ +export function strings(data: rs.ByteSlice, out: rs.ByteVec): void { + let s: ArrayString.ArrayString = copy$arrayvec_ArrayString_12(ArrayString.new$12()); + for (const b of rs.byteSliceView(data)) { + const _12: rs.usize = rs.usizeRem(b >> 3 as rs.usize, rs.sliceFrom([47, 97, 233, 2048, 128512]).length); + if (!(_12 < 5)) { + rs.panic("index out of bounds: the len is " + 5 + " but the index is " + _12); + } + const c: rs.char = [47, 97, 233, 2048, 128512][_12]; + b2: { + const _23: rs.u8 = rs.u8Rem(b, 8); + if (_23 === 0) { + rs.byteVecPush(out, ArrayString.tryPush$12(s, c).ok ? 1 : 0); + break b2; + } + if (_23 === 1) { + const _31: rs.Iter = rs.iterRepeatN(c, b >> 5 as rs.usize); + const run: rs.String = rs.stringExtendChars(rs.stringNew(), _31); + const _36: rs.Result> = ArrayString.tryPushStr$12(s, run.v); + if (_36.ok) { + rs.byteVecPush(out, 0); + } else { + rs.byteVecExtendFromSlice(out, rs.stringAsBytes(CapacityError.element$ref_str(_36.error))); + } + break b2; + } + if (_23 === 2) { + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(option_mapOr$0yzb10z(ArrayString.pop$12(s), 0, (a0: rs.char): rs.u32 => { + return a0 as rs.u32; + })).reverse())); + break b2; + } + if (_23 === 3) { + const ends: rs.Vec = rs.iterToVec(rs.iterChain(rs.iterMap(rs.strCharIndices(ArrayString.deref$ops_Deref_12(s)), (_2: [rs.usize, rs.char]): rs.usize => { + return _2[0]; + }), rs.iterSlice([ArrayString.len$12(s)])), "next", 4); + ArrayString.truncate$12(s, ends.items[rs.idx(ends.items, rs.usizeRem(b >> 3 as rs.usize, ends.items.length))]); + break b2; + } + if (_23 === 4) { + if (!ArrayString.isEmpty$12(s)) { + rs.byteVecExtendFromSlice(out, rs.byteSliceFrom(rs.u32ToBeBytes(ArrayString.remove$12(s, 0) as rs.u32).reverse())); + break b2; + } + } else { + if (_23 === 5) { + const _80: rs.Result> = ArrayString.from$12(ArrayString.asStr$12(s)); + if (_80.ok) { + s = copy$arrayvec_ArrayString_12(_80.value); + break b2; + } + rs.byteVecPush(out, rs.stringLen(CapacityError.element$ref_str(_80.error)) & 0xff); + break b2; + } + if (_23 === 6) { + if (ArrayString.remainingCapacity$12(s) >= rs.charLenUtf8(c)) { + ArrayString.pushStr$12(s, rs.charEncodeUtf8(c, rs.byteSliceFrom(rs.bytes(4))).v, { file: "fixtures/stock/src/fixed.rs", line: 37, col: 62 }); + break b2; + } + } + } + ArrayString.clear$12(s); + } + rs.iterExtendBytes(out, rs.iterSlice(Uint8Array.of(ArrayString.len$12(s) & 0xff, ArrayString.remainingCapacity$12(s) & 0xff, ArrayString.isFull$12(s) ? 1 : 0)), undefined, true); + } + rs.byteVecExtendFromSlice(out, rs.stringAsBytes(ArrayString.deref$ops_Deref_12(s))); +} + +//# sourceMappingURL=fixed.ts.map diff --git a/ts/fixtures/stock/expected/harness.ts b/ts/fixtures/stock/expected/harness.ts index b04430a49..55701ccd9 100644 --- a/ts/fixtures/stock/expected/harness.ts +++ b/ts/fixtures/stock/expected/harness.ts @@ -1,7 +1,8 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { bytesMut, far, reclaims, reads, deques, dyns, late, vecSpares, chunks, slices as buf_slices, writes, edits, owners, chains, vectored, strings } from "./buf.ts"; +import { bytesMut, far, reclaims, reads, deques, dyns, late, vecSpares, chunks, slices as buf_slices, writes, edits, owners, chains, vectored, strings as buf_strings } from "./buf.ts"; +import { strings as fixed_strings } from "./fixed.ts"; import * as Owner from "./harness.Owner.ts"; import { map as maps_map, table, capacity, linked, linkedIters, nested, ownHasher, ownFromIter, equal, defaultHasher } from "./maps.ts"; import { ranges } from "./ranges.ts"; @@ -41,9 +42,9 @@ export function cases(): rs.Vec<[string, (a0: rs.ByteSlice, a1: rs.ByteVec) => u chunks(a014.data, a014.start, a014.length, a114); }] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::owners", (a015: rs.ByteSlice, a115: rs.ByteVec): undefined => { owners(a015.data, a015.start, a015.length, a115); - }] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::strings", strings as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["ranges", ranges as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::deques", (a016: rs.ByteSlice, a116: rs.ByteVec): undefined => { + }] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::strings", buf_strings as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["ranges", ranges as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::deques", (a016: rs.ByteSlice, a116: rs.ByteVec): undefined => { deques(a016.data, a016.start, a016.length, a116); - }] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::vectored", vectored as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::slices", (a017: rs.ByteSlice, a117: rs.ByteVec): undefined => { + }] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::vectored", vectored as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["fixed::strings", fixed_strings as (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined], ["buf::slices", (a017: rs.ByteSlice, a117: rs.ByteVec): undefined => { buf_slices(a017.data, a017.start, a017.length, a117); }] as [string, (a0: rs.ByteSlice, a1: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/stock/expected/lib.ts b/ts/fixtures/stock/expected/lib.ts index 9f919b8a4..d195af8a9 100644 --- a/ts/fixtures/stock/expected/lib.ts +++ b/ts/fixtures/stock/expected/lib.ts @@ -1,6 +1,7 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; +import type * as ArrayString from "./arrayvec/array_string.ArrayString.ts"; import * as buf_Counted from "./buf.Counted.ts"; import * as Pieces from "./buf.Pieces.ts"; import * as Provided from "./buf.Provided.ts"; @@ -20,6 +21,14 @@ import type * as DefaultHashBuilder from "./hashlink.DefaultHashBuilder.ts"; import type * as Held from "./maps.Held.ts"; import * as Logged from "./maps.Logged.ts"; +export function copy$array_mem_MaybeUninit_u8(v: Uint8Array): Uint8Array { + return v.slice(); +} + +export function copy$arrayvec_ArrayString_12(v: ArrayString.ArrayString): ArrayString.ArrayString { + return { len: v.len, xs: copy$array_mem_MaybeUninit_u8(v.xs) }; +} + export function copy$maps_Logged(v: Logged.Logged): Logged.Logged { return new Logged.Logged(v._0, v._1); } @@ -2156,4 +2165,6 @@ export const out$u64: { err: unknown } = { err: undefined }; export const out$u8: { err: unknown } = { err: undefined }; +export const out$usize: { err: unknown } = { err: undefined }; + //# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/stock/src/fixed.rs b/ts/fixtures/stock/src/fixed.rs new file mode 100644 index 000000000..c130a7842 --- /dev/null +++ b/ts/fixtures/stock/src/fixed.rs @@ -0,0 +1,43 @@ +//! arrayvec's `ArrayString`, which translates from its source: text pushed into a fixed +//! buffer of bytes through `str::as_ptr` and `ptr::copy_nonoverlapping`, and read back +//! through `slice::from_raw_parts`, as moq-net's `Path::concat` joins a long path. + +use arrayvec::ArrayString; + +/// A char of each UTF-8 width, and the separator a path joins on. +const CHARS: [char; 5] = ['/', 'a', 'é', '\u{800}', '😀']; + +/// Each byte an edit of an `ArrayString` of 12 bytes, whose high bits pick a char or a +/// length, writing what it returns, then the text. +pub fn strings(data: &[u8], out: &mut Vec) { + let mut s = ArrayString::<12>::new(); + for &b in data { + let c = CHARS[usize::from(b >> 3) % CHARS.len()]; + match b % 8 { + 0 => out.push(u8::from(s.try_push(c).is_ok())), + 1 => { + // A run of the char, which may not fit. + let run: String = std::iter::repeat_n(c, usize::from(b >> 5)).collect(); + match s.try_push_str(&run) { + Ok(()) => out.push(0), + Err(e) => out.extend_from_slice(e.element().as_bytes()), + } + } + 2 => out.extend_from_slice(&s.pop().map_or(0, u32::from).to_le_bytes()), + 3 => { + // To the char boundary the high bits pick. + let ends: Vec = s.char_indices().map(|(i, _)| i).chain([s.len()]).collect(); + s.truncate(ends[usize::from(b >> 3) % ends.len()]); + } + 4 if !s.is_empty() => out.extend_from_slice(&u32::from(s.remove(0)).to_le_bytes()), + 5 => match ArrayString::<12>::from(s.as_str()) { + Ok(copy) => s = copy, + Err(e) => out.push(e.element().len() as u8), + }, + 6 if s.remaining_capacity() >= c.len_utf8() => s.push_str(c.encode_utf8(&mut [0; 4])), + _ => s.clear(), + } + out.extend([s.len() as u8, s.remaining_capacity() as u8, u8::from(s.is_full())]); + } + out.extend_from_slice(s.as_bytes()); +} diff --git a/ts/fixtures/stock/src/harness.rs b/ts/fixtures/stock/src/harness.rs index 0a8b4723b..1d6bedd74 100644 --- a/ts/fixtures/stock/src/harness.rs +++ b/ts/fixtures/stock/src/harness.rs @@ -41,6 +41,7 @@ fn cases() -> Vec<(&'static str, Case)> { ("ranges", crate::ranges::ranges), ("buf::deques", crate::buf::deques), ("buf::vectored", crate::buf::vectored), + ("fixed::strings", crate::fixed::strings), ("buf::slices", crate::buf::slices), ] } diff --git a/ts/fixtures/stock/src/lib.rs b/ts/fixtures/stock/src/lib.rs index bd8e048a4..6fcd45edd 100644 --- a/ts/fixtures/stock/src/lib.rs +++ b/ts/fixtures/stock/src/lib.rs @@ -1,10 +1,12 @@ //! Calls into `bytes`, `hashbrown`, `rand`, and `tracing`, which translation maps itself, -//! into `hashlink`, which translates over `hashbrown`, and into `rangemap`, which translates -//! over std's B-tree, so this config binds and leaves out nothing. +//! into `hashlink`, which translates over `hashbrown`, into `rangemap`, which translates +//! over std's B-tree, and into `arrayvec`'s `ArrayString`, which translates over raw +//! pointers, so this config binds and leaves out nothing. //! //! `harness::run` is the entry point both sides of the differential test run. mod buf; +mod fixed; pub mod harness; mod maps; mod ranges; diff --git a/ts/runtime/rs.ts b/ts/runtime/rs.ts index 6847d64f9..7b834582f 100644 --- a/ts/runtime/rs.ts +++ b/ts/runtime/rs.ts @@ -288,6 +288,34 @@ export function byteSliceAsPtr(s: ByteSlice, index = 0): Place { return new AtIndex(s.data, s.start + index); } +/** + * A pointer into a `str`'s UTF-8, which JS encodes anew from its text, so a write through + * it, which Rust forbids, would change nothing the text reads: it fails instead. + */ +class TextAt extends AtIndex { + override get v(): number { + return super.v; + } + + override set v(_: number) { + throw textWrite(); + } + + override at(index: number): TextAt { + return new TextAt(this.array, index); + } +} + +/** The failure of a write, or a mutable slice, through a `TextAt`. */ +function textWrite(): Error { + return new Error("rust2-ts: a write through a pointer into a `str`'s bytes, which Rust forbids"); +} + +/** `str::as_ptr`: the read-only pointer to the first byte of its UTF-8. */ +export function strAsPtr(s: string): Place { + return new TextAt(encoder.encode(s), 0); +} + /** The pointer to the first byte of `v`'s storage. */ export function byteVecAsPtr(v: ByteVec): Place { return new AtIndex(v.data, 0); @@ -419,7 +447,7 @@ function wrapIndex(index: number, n: number, size: number): number { export function ptrAdd(p: Place, n: number): Place { if (n === 0) return p; const e = elemOf(p); - return new AtIndex(e.array, e.index + n); + return e.at(e.index + n); } /** @@ -435,7 +463,7 @@ export function ptrWrappingAdd(p: Place, n: number, size: number, align: n return addr === 0 ? NULL : addr === align ? DANGLING : (addressAt(addr) as Place); } const e = elemOf(p); - return new AtIndex(e.array, wrapIndex(e.index, n | 0, size)); + return e.at(wrapIndex(e.index, n | 0, size)); } /** `p.offset_from(origin)`: how many elements `p` is past `origin`, in the same array or byte buffer. */ @@ -458,6 +486,7 @@ export function ptrOffsetFrom(p: Place, origin: Place): number { export function ptrCopy(src: Place, dst: Place, n: number, copy?: (value: T) => T): void { if (n === 0) return; const [from, to] = [elemOf(src), elemOf(dst)]; + if (to instanceof TextAt) throw textWrite(); from.reach(n); to.reach(n); if (ArrayBuffer.isView(from.array) && ArrayBuffer.isView(to.array)) { @@ -633,6 +662,7 @@ export function ptrByteSlice(p: Place, len: number, shared: boolean): By } const at = elemOf(p); at.reach(len); + if (!shared && len !== 0 && at instanceof TextAt) throw textWrite(); if (at.array instanceof Uint8Array) return new ByteSlice(at.array, at.index, len); // A byte in no array, which no `Uint8Array` can share. if (!shared) throw new Error("rust2-ts: a raw or mutable slice of a byte in no array of bytes"); diff --git a/ts/runtime/rs/collections.ts b/ts/runtime/rs/collections.ts index 96306e7a6..f2568c160 100644 --- a/ts/runtime/rs/collections.ts +++ b/ts/runtime/rs/collections.ts @@ -420,6 +420,11 @@ export class AtIndex implements Place { else this.room(1).set(this.index, value); } + /** The place of element `index` of the same array, as pointer arithmetic moves to it. */ + at(index: number): AtIndex { + return new AtIndex(this.array, index); + } + /** * Fails where `n` elements from here run past either end of the array, which JS * doesn't keep Rust's memory around, but into the room of the `Vec` it is the items diff --git a/ts/src/census.rs b/ts/src/census.rs index d71ef11e3..151e6dd00 100644 --- a/ts/src/census.rs +++ b/ts/src/census.rs @@ -203,6 +203,8 @@ const VERBATIM: &[&str] = &[ "rs.Pending", "self", "skip", + "str", + "str::as_ptr", "undefined", "write_bytes", "write_str", diff --git a/ts/src/lower/mir/origin.rs b/ts/src/lower/mir/origin.rs index 4bd3cf011..b0b4a33f1 100644 --- a/ts/src/lower/mir/origin.rs +++ b/ts/src/lower/mir/origin.rs @@ -27,6 +27,8 @@ enum Array<'tcx> { Copy(BasicBlock, Option), /// The array of a variable of this body's own, or of a field of one, at this place. Owned(mir::Place<'tcx>), + /// A `str`'s UTF-8, which JS encodes anew at each `as_ptr`, the call ending this block. + Text(BasicBlock), } impl<'tcx> Array<'tcx> { @@ -35,7 +37,7 @@ impl<'tcx> Array<'tcx> { match self { Array::Lone(_) => Some(1), Array::Copy(_, len) => len, - Array::Owned(_) => None, + Array::Owned(_) | Array::Text(_) => None, } } @@ -45,6 +47,7 @@ impl<'tcx> Array<'tcx> { Array::Lone(_) => "a value in no array", Array::Copy(..) => "a shared slice JS copies", Array::Owned(_) => "an array of this body's own", + Array::Text(_) => "a `str`'s bytes", } } } @@ -110,6 +113,11 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } ("ptr" | "NonNull", "offset_from" | "offset_from_unsigned") => { let (a, b) = (self.origin(arg(0)?)?, self.origin(arg(1)?)?); + if let (Array::Text(_), Array::Text(_)) = (a.array, b.array) { + return (a.array != b.array).then(|| { + "`offset_from` between pointers of two `str::as_ptr` calls, each into an encoding of the text of its own, which JS keeps in different arrays".into() + }); + } self.apart(a.array, b.array).then(|| { format!( "`offset_from` between a pointer into {} and one into {}, which JS keeps in different arrays", @@ -235,6 +243,12 @@ impl<'tcx> Fx<'_, '_, '_, 'tcx> { } ("NonNull", "as_ptr" | "new_unchecked") => self.origin(&args[0].node), ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => self.start(&args[0].node), + // The start of its own text's encoding, though a substring's bytes start past its + // text's in Rust, so a move back before it is refused, as one off a copy is. + ("str", "as_ptr") => Some(Origin { + array: Array::Text(self.def_block(local)?), + index: Some(0), + }), _ => None, } } diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs index 2897fc374..d4538b033 100644 --- a/ts/src/lower/pointer.rs +++ b/ts/src/lower/pointer.rs @@ -864,6 +864,8 @@ pub fn call<'tcx>( ("UnsafeCell", "get") | ("Cell", "as_ptr") if array(b, pointee()) => to_pointer(b, arg().member("v")), ("UnsafeCell" | "Cell", "get_mut") if b.t.needs_place(pointee()) => arg(), ("UnsafeCell", "get" | "get_mut" | "into_inner") | ("Cell", "as_ptr" | "get_mut") => arg().member("v"), + // Into the UTF-8 of a `str`, which JS keeps as text: an encoding no write may reach. + ("str", "as_ptr") => Expr::rs("strAsPtr", vec![arg()]), // Pointers into an array: `rs.AtIndex`es, which a `Vec`'s and a slice's give. ("slice" | "Vec", "as_ptr" | "as_mut_ptr") => { let elem = targ(gargs, 0); diff --git a/ts/tests/pointers.ts b/ts/tests/pointers.ts index 70fd79509..ce5c4101c 100644 --- a/ts/tests/pointers.ts +++ b/ts/tests/pointers.ts @@ -151,3 +151,24 @@ const flagAt = rs.arcRaw(flag, rs.arcPlace(flag), "Arc"); assert.equal(rs.arcErase(rs.ptrAdd(rs.ptrAdd(flagAt, 1), -1), "bool", 1), flagAt); assert.equal(rs.arcErase(rs.NULL, "bool", 1), rs.NULL); assert.throws(() => rs.arcErase({ v: false }, "bool", 1), /only an `Arc`'s or `Rc`'s raw pointer keeps the place of/); + +// A `str`'s UTF-8 through `as_ptr`, which JS encodes anew from its text: read through, +// moved by arithmetic, and viewed, but a write, a copy into it, or a mutable slice of it, +// which Rust forbids, would change nothing the text reads. +const text = rs.strAsPtr("aé"); +assert.equal(rs.ptrAdd(text, 2).v, 0xa9); +assert.deepEqual(Array.from(rs.byteSliceView(rs.ptrByteSlice(text, 3, true))), [0x61, 0xc3, 0xa9]); +assert.equal(rs.ptrOffsetFrom(rs.ptrWrappingAdd(text, 2, 1, 1), text), 2); +unsure(() => { + rs.ptrAdd(text, 1).v = 0; +}); +unsure(() => { + rs.ptrWrappingAdd(text, 1, 1, 1).v = 0; +}); +unsure(() => rs.ptrCopy(rs.byteSliceAsPtr(rs.byteSliceFrom(Uint8Array.of(1))), rs.ptrAdd(text, 1), 1)); +unsure(() => rs.ptrByteSlice(text, 1, false)); +unsure(() => new rs.RawSlice(text, 3).derefBytes(false)); +// None of it is nothing written, and two calls' pointers are two encodings. +rs.ptrCopy(rs.byteSliceAsPtr(rs.byteSliceFrom(Uint8Array.of(1))), text, 0); +assert.equal(rs.ptrByteSlice(text, 0, false).length, 0); +unsure(() => rs.ptrOffsetFrom(rs.strAsPtr("aé"), text));