diff --git a/quest/m2/ts-model/README.md b/quest/m2/ts-model/README.md index 43608c20b..f2acaa588 100644 --- a/quest/m2/ts-model/README.md +++ b/quest/m2/ts-model/README.md @@ -49,6 +49,8 @@ Public API: the translator's output for moq-net's model. - [Shared ownership](/quest/m2/ts-shared.md) - kio's waiters are `Arc`/`Weak` with observable counts, and its atomics and locks are plain values - [Float formatting](/quest/m2/ts-model/floats.md) - `f64` formats and parses as std does - [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - `Vec` and `VecDeque` capacity, and `RandomState` -- [std gaps kio reaches](/quest/m2/ts-model/kio-std.md) - `array::from_fn`, `Default` of arrays and `()`, and maps of `Option`s +- [std gaps kio reaches](/quest/m2/ts-model/kio-std.md) - `array::from_fn`, `Default` of arrays and `()`, maps of `Option`s, and `trailing_zeros` +- [hashbrown as the runtime's map](/quest/m2/ts-model/hashbrown.md) - hashlink's `hashbrown` tables translate as `rt.HashMap`, with std's capacity +- [A write through a mutable reference to a library object](/quest/m2/ts-model/library-place.md) - `*r = v` out of an `Option` writes the slot, not a copy, and `mem::take` of a `&mut Vec` and writes through guards and map lookups translate - [bytes' BytesMut, Buf, and BufMut](/quest/m2/ts-model/bytes.md) - map without config, so the model's `BytesMut` binding goes - [Async in the TypeScript translator](/quest/m2/ts-async.md) - kio's producers and consumers are futures diff --git a/quest/m2/ts-model/hashbrown.md b/quest/m2/ts-model/hashbrown.md new file mode 100644 index 000000000..ede730c7a --- /dev/null +++ b/quest/m2/ts-model/hashbrown.md @@ -0,0 +1,27 @@ +# [M] hashbrown as the runtime's map + +## Goal + +rust2-ts translates `hashbrown`'s `HashMap`, `HashSet`, and `HashTable`, which hashlink +keeps its entries in, as it translates std's: an `rt.HashMap` and its runtime functions, +proven by differential tests against native Rust. + +## Plan + +Found by [#201](https://github.com/kixelated/rust2/pull/201) on 2026-10-28: with hashlink's +raw pointers translated, hashbrown is the model's largest source of refusals left +(251 lines of its census), its SIMD groups, raw allocation, and byte reinterpretation +being what JS has no counterpart of. + +- Map it as std's map is: std's `HashMap` is hashbrown's, so the same runtime functions + stand in for both, with no config. +- `capacity` follows hashbrown's `capacity_to_buckets` and `allocation_size` exactly, as + [the rest of std](/quest/m2/ts-model/std-rest.md) decided for `Vec`'s capacity. +- `HashTable`'s methods take the hash and the equality from the caller, so it needs a + table keyed by the caller's hash, not the runtime's own. + +Public API: the translator's output. + +## Required + +- [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - `RandomState` and std's exact capacity, which hashbrown's default hasher and growth reuse diff --git a/quest/m2/ts-model/kio-std.md b/quest/m2/ts-model/kio-std.md index 6bc7b0240..ae57ada5b 100644 --- a/quest/m2/ts-model/kio-std.md +++ b/quest/m2/ts-model/kio-std.md @@ -4,8 +4,8 @@ rust2-ts translates the std that kio reaches and no other quest owns, proven by differential tests against native Rust: `array::from_fn`, `Default` of arrays (kio's -`[Atomic; 8]::default()`) and of `()`, and a `HashMap` whose values are `Option`s -(`shared.rs`). +`[Atomic; 8]::default()`) and of `()`, a `HashMap` whose values are `Option`s +(`shared.rs`), and `trailing_zeros` of every integer width and `NonZero` (`task.rs`). ## Plan diff --git a/quest/m2/ts-model/library-place.md b/quest/m2/ts-model/library-place.md new file mode 100644 index 000000000..0482f1fa5 --- /dev/null +++ b/quest/m2/ts-model/library-place.md @@ -0,0 +1,38 @@ +# [M] A write through a mutable reference to a library object + +## Goal + +A whole write through a `&mut` to a library object, such as a `Vec`, an `Arc`, or a +`BTreeSet`, writes the slot the reference points at, wherever the reference came from, +proven by differential tests against native Rust: + +- `*r = v` where `r` came out of an `Option`, as in + `if let Some(r) = v.get_mut(0) { *r = Arc::new(1) }`, which translates today but + silently differs from native. +- The replacements refused today: `mem::take(v)` and `mem::replace` with `v: &mut Vec<_>`, + and `*r = v` through a `MutexGuard`'s deref or a map lookup. + +## Plan + +Found by [#201](https://github.com/kixelated/rust2/pull/201) on 2026-10-28, in safe code: +the translation assigns the local the `Option` was matched into, so the `Vec` keeps its old +`Arc`, whose count stays up. It silently differs from native, so it is fixed first. + +Both have one root: a `&mut` to a library object is the object, which a write can't put +another object in place of, and so is one to the crate's own struct or enum. On main, at +moq `30f25c04`, moq-net's census refuses eight such sites, after +[#204](https://github.com/kixelated/rust2/pull/204) translated the write through a +closure's capture: + +- "replacing the value behind a reference": a `Vec` at `ietf/publisher.rs:3794` (through a + `MutexGuard`), `lite/publisher.rs:4043`, and `model/broadcast.rs:1226`; a `BTreeSet` at + `lite/subscriber.rs:7620`; and the crate's own `Subscription` at + `ietf/publisher.rs:16954`, `TrackState` at `ietf/subscriber/tracks.rs:1118`, and `Parked` + at `model/expiry.rs:316`. +- "an assignment through a reference": an `Arc` at `model/frame.rs:752`. + +- A whole write through such a reference goes through the `rt.Place` of where the object + is kept, as a `&mut` to a value JS copies does, or into the object in place where the + runtime type allows it. Where neither is known, the write stays refused with its span. + +Public API: the translator's output. diff --git a/quest/m2/ts-model/std-rest.md b/quest/m2/ts-model/std-rest.md index 6f4390731..f0fedc62c 100644 --- a/quest/m2/ts-model/std-rest.md +++ b/quest/m2/ts-model/std-rest.md @@ -4,7 +4,8 @@ rust2-ts translates the std calls the model's census still refuses outside other quests: `Vec`'s and `VecDeque`'s `capacity`, `reserve`, and `shrink_to`, and `RandomState` with -`BuildHasher::hash_one`, proven by differential tests against native Rust. +`BuildHasher::hash_one`, also where another crate names it, as hashbrown's and hashlink's +default hasher does, proven by differential tests against native Rust. ## Plan diff --git a/quest/m3/ts-unsafe.md b/quest/m3/ts-unsafe.md index 1892cb0a7..13551cb41 100644 --- a/quest/m3/ts-unsafe.md +++ b/quest/m3/ts-unsafe.md @@ -3,20 +3,40 @@ ## Goal [rust2-ts](/ts/README.md) translates the `unsafe` operations it can give exact semantics in -JS, such as `get_unchecked`, `ptr::read` and `ptr::write` of a local or element, and -`MaybeUninit` written before it is read, so a dependency using only those translates from -source instead of being bound by hand. +JS, so a dependency using only those translates from source instead of being bound by +hand. What is left: pointers into a slice or `Vec`, `ManuallyDrop`, unions over a type +parameter, and a raw pointer to a value JS copies taken through a reference. ## Plan Decided with the maintainer on 2026-10-27: a dependency containing `unsafe` is bound by the consumer [at first](/ts/README.md#bindings), and moq gives its own `unsafe` safe `cfg(rust2_ts)` twins. Both are stopgaps, since a stock crate should translate with no -annotations or config; this quest is what retires them. The census of moq-net's model -found `bytes`, `hashlink`, `slab`, and `smallvec` bound, and kio's lock-free `log`. +annotations or config; this quest is what retires them. + +Decided with the maintainer on 2026-10-28, typed pointers: a raw pointer is what its +reference is, the object, an `rt.Place`, or `rt.NULL`, and a pointer into a slice or `Vec` +is `{ array, index }`. Byte reinterpretation, pointer-to-integer casts, and raw allocation +are refused with a span. [#201](https://github.com/kixelated/rust2/pull/201) landed `Box` +to raw and back, `NonNull`, `MaybeUninit`, raw reads and writes, `get_unchecked`, and +unions whose overlapping fields share a type, which took hashlink from 375 refusals to +none of its own `unsafe`. - An `unsafe` operation outside the modeled set is refused with its span, as today. - Undefined behavior in Rust need not be reproduced, but a translated program never silently differs from native where Rust's behavior is defined. +- Element pointers, `{ array, index }`, with `add`, `offset`, `offset_from`, and + `slice::from_raw_parts` over them: smallvec, arrayvec, and moq-net's `frame.rs`. +- `ManuallyDrop` as `T` with its drop skipped, as `MaybeUninit` is: crossbeam-epoch's + `Local` reaches it, though crossbeam's tagged pointers stay refused. +- A union over a type parameter, whose layout has no fixed offsets until an instance + fixes them (smallvec's `SmallVecData`): lay out each instance instead of the identity. +- `&raw const *r` where `r` is a `&T` to a value JS copies or a library object, such as + foldhash's seed and arrayvec's `as_ptr`: the reference is the value, not where it is + kept, so it needs the place the reference came from. +- A pointer to a part kept as an object is refused where the crate may put another + object there, judged by type over the whole program: a generic function writing a whole + `T` counts for every `T` its bounds allow. Judge by the instances translation reaches + if a consumer's pointer is refused only by that. Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index ddf75501c..1dfbbeb74 100644 --- a/ts/README.md +++ b/ts/README.md @@ -12,9 +12,9 @@ would; the configured library is also translated, after type and borrow checking what they reach. Every crate it depends on but std's translates with it as one program, as far as the roots reach: each compiles with every function's MIR, and its types and calls translate as the package's own do. They compile with `--cfg rust2_ts`, so a crate -can give translation a safe twin of its `unsafe`. Their `unsafe` is -refused where it is, as the package's is, unless a binding stands in for the item holding -it. Only the writes of the dependencies' functions translation reaches decide what it +can give translation a safe twin of its `unsafe`. Their `unsafe` translates as the +package's does, and what translation can't model is refused where it is, unless a binding +stands in for the item holding it. Only the writes of the dependencies' functions translation reaches decide what it copies, so it runs again when it reaches one it didn't read. A construct outside the subset is a compile error with its span, and then nothing is written. A type without a JS value is refused where a value of it is made or received, such as a parameter, a field, or a call, rather than again at each variable @@ -87,6 +87,9 @@ crate translation reaches is refused, and so are two versions of one crate it re | `ControlFlow` | `rt.Result`, `Ok` to continue; what `?` branches on is the value it was applied to, but for a `Poll`'s `Pending` and `Ready(None)`, which continue as `Ok` of themselves | | tuple, `[T; N]`, `&[T]`, `Vec` | an array; for bytes, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]` and `&[u8; N]`, and `rt.ByteVec` for `Vec` | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, or a `String`, is an `rt.Place` | +| `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`; `rt.NULL` for null and `rt.DANGLING` for dangling. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, 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; a read copies what the crate writes into in place | +| `MaybeUninit` | `T`: `undefined` until written, or an object whose parts kept as objects are there, holding `undefined` | +| `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 | | an iterator | `rt.Iter`, whose adaptors are runtime functions: `rt.iterMap(rt.iterSlice(v), f)` | | `a..b`, `a..=b` | `rt.Range`, `rt.RangeInclusive`, which are iterators too | | `a..`, `..b`, `..=b`, `Bound` | `{ start }`, `{ end }`, `{ end, inclusive: true }`; `rt.RangeBound`, a union tagged by variant | @@ -505,7 +508,8 @@ they need. ## Not yet -`unsafe` and raw pointers ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than +Byte reinterpretation, pointer arithmetic and addresses, raw allocation, and unions whose +fields overlap in different types ([unsafe](/quest/m3/ts-unsafe.md)), and trait objects other than `&dyn Debug`, `&dyn Display`, `dyn Future`, `dyn Any`, and the translated crates' traits' behind `&`, `&mut`, `Box`, or `Arc` ([trait objects](/quest/m3/ts-dyn.md)), fail translation. Until they translate, an item that needs one, such as a boxed closure, binds to hand-written TS ([bindings](#bindings)). @@ -564,7 +568,9 @@ built natively for wasm32 and run beside its translation on the same inputs by disagree with JS's, `vec![x; n]` of types whose `Clone` counts its clones, `&mut` to values JS copies, of locals, fields, elements, and a map's values, passed on, returned, kept in a struct, and captured, a `Vec`'s edits and sorts, with closures - that log what each is called with, and std's `Bound`, `Reverse`, and `NonZero`. Its + that log what each is called with, std's `Bound`, `Reverse`, and `NonZero`, and raw + pointers: a linked list of boxed nodes as `hashlink` keeps one, through a union and a + `MaybeUninit` entry, and pointers to locals, fields, and whole structs. Its differential test runs through `caller.ts`, a hand-written caller that also prints a `printable` error nothing translated prints, and adds every 64- and 128-bit case over each pair of values next to a power of two, diff --git a/ts/fixtures/bind/refused.txt b/ts/fixtures/bind/refused.txt index 0f97b1f49..3527835d7 100644 --- a/ts/fixtures/bind/refused.txt +++ b/ts/fixtures/bind/refused.txt @@ -10,12 +10,10 @@ binding `raw::Tally::new`: rt/tally.ts would replace the runtime, rt.ts and rt/ binding `raw::checksum`: `checksum` binds the same item binding `std::vec::Vec`: the type has a representation of its own reading bind/missing.ts: No such file or directory (os error 2) at fixtures/bind/src/harness.rs:25:12 -unsupported type `*const u32` at fixtures/bind/src/raw.rs:36:2 `raw::Held` binds without drop, so it would never drop its `raw::Tally` at fixtures/bind/src/harness.rs:61:13 a binding of `raw::value`, whose `N` one export for every instance can't recover from its arguments at fixtures/bind/src/raw.rs:65:1 a binding of `raw::zero`, whose `T` one export for every instance can't recover from its arguments at fixtures/bind/src/raw.rs:60:1 `raw::Held` binds without drop, so it would never drop its `raw::Tally` at fixtures/bind/src/harness.rs:61:6 unsupported call to `std::sync::Arc::::from_raw` at fixtures/bind/src/raw.rs:55:17 unsupported call to `std::sync::Arc::::into_raw` at fixtures/bind/src/raw.rs:42:11 -a raw pointer dereferenced at fixtures/bind/src/raw.rs:48:12 an assignment through a reference to `blob::Blob` at fixtures/bind/src/harness.rs:68:2 diff --git a/ts/fixtures/crates/refused.txt b/ts/fixtures/crates/refused.txt index 43dfc317d..bfc265279 100644 --- a/ts/fixtures/crates/refused.txt +++ b/ts/fixtures/crates/refused.txt @@ -1,3 +1,2 @@ unsupported call to `core::slice::::as_ptr` at fixtures/crates/dep/src/lib.rs:129:18 unsupported call to `std::ptr::const_ptr::::add` at fixtures/crates/dep/src/lib.rs:129:27 -a raw pointer dereferenced at fixtures/crates/dep/src/lib.rs:129:11 diff --git a/ts/fixtures/lang/expected/core/option.ts b/ts/fixtures/lang/expected/core/option.ts index 09c4a34fe..e719d8f53 100644 --- a/ts/fixtures/lang/expected/core/option.ts +++ b/ts/fixtures/lang/expected/core/option.ts @@ -17,6 +17,7 @@ import { side, order } from "../keys.ts"; import type * as Hop from "../keys.Hop.ts"; import type * as Side from "../keys.Side.ts"; import type * as Way from "../keys.Way.ts"; +import type * as Pair from "../pointers.Pair.ts"; import { from$From_Dir_u8 } from "../shapes.ts"; import type * as Dir from "../shapes.Dir.ts"; import type * as Meters from "../shapes.Meters.ts"; @@ -388,6 +389,13 @@ export function mapOr$Option_Ordering_u8_order(self: rt.Ordering | undefined, de return order(self); } +export function mapOr$Option_Pair_u8_copies_closure0(self: Pair.Pair | undefined, default_: rt.u8, f: (a0: Pair.Pair) => rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function mapOr$Option_Point_u8_known_closure0_closure0(self: Point.Point | undefined, default_: rt.u8, f: (a0: Point.Point) => rt.u8): rt.u8 { if (self === undefined) { return default_; @@ -472,6 +480,13 @@ export function mapOr$Option_u32_u32_lent_closure4(self: rt.Place | unde return f(self); } +export function mapOr$Option_u8_Arc_u8_u8_shared_closure0(self: [rt.u8, rt.Arc] | undefined, default_: rt.u8, f: (a0: [rt.u8, rt.Arc]) => rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + export function mapOr$Option_u8_Option_u8_anys_closure0(self: rt.u8 | undefined, default_: rt.u8 | undefined, f: (a0: rt.u8) => rt.u8 | undefined): rt.u8 | undefined { if (self === undefined) { return default_; @@ -698,6 +713,16 @@ export function filter$Option_String_owned_closure1(self: string | undefined, pr return undefined; } +export function filter$Option_u32_sentinels_closure0(self: rt.u32 | undefined, predicate: (a0: rt.u32) => boolean): rt.u32 | undefined { + if (self !== undefined) { + const x: rt.u32 = self; + if (predicate(x)) { + return x; + } + } + return undefined; +} + export function filter$Option_u8_combinators_closure0(self: rt.u8 | undefined, predicate: (a0: rt.u8) => boolean): rt.u8 | undefined { if (self !== undefined) { const x: rt.u8 = self; diff --git a/ts/fixtures/lang/expected/harness.ts b/ts/fixtures/lang/expected/harness.ts index 950b610de..6df3ec5ed 100644 --- a/ts/fixtures/lang/expected/harness.ts +++ b/ts/fixtures/lang/expected/harness.ts @@ -22,8 +22,9 @@ import { adaptors, consumers, skips, ranked, ranges, options, own, values } from import { keys, overrides } from "./keys.ts"; import { locks, relock } from "./locks.ts"; import { nested } from "./nested.ts"; +import { lists, shared as pointers_shared, places, unchecked, nested as pointers_nested, copies, sentinels } from "./pointers.ts"; import { refs } from "./refs.ts"; -import { copies, order, discriminants, loops, patterns, closures, library, flows } from "./shapes.ts"; +import { copies as shapes_copies, order, discriminants, loops, patterns, closures, library, flows } from "./shapes.ts"; import { splits } from "./splits.ts"; import { strs, slices } from "./strs.ts"; import { decodes } from "./utf8.ts"; @@ -34,72 +35,86 @@ import { wrappers } from "./wrappers.ts"; * case added at the end moves no other. */ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void][] { - return [["ints::u8_op", u8Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u16_op", u16Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u32_op", u32Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u64_op", u64Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u128_op", u128Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::usize_op", usizeOp] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i8_op", i8Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i16_op", i16Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i32_op", i32Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i64_op", i64Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i128_op", i128Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::isize_op", isizeOp] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::neg", neg] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::cast", cast] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::convert", (_225: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void => { + return [["ints::u8_op", u8Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u16_op", u16Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u32_op", u32Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u64_op", u64Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::u128_op", u128Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::usize_op", usizeOp] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i8_op", i8Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i16_op", i16Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i32_op", i32Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i64_op", i64Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::i128_op", i128Op] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::isize_op", isizeOp] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::neg", neg] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::cast", cast] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["ints::convert", (_246: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void => { convert(data, out); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::copies", (_226: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): void => { - copies(data2, out2); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::loops", (_227: rt.u8, data3: rt.ByteSlice, out3: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::copies", (_247: rt.u8, data2: rt.ByteSlice, out2: rt.ByteVec): void => { + shapes_copies(data2, out2); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::loops", (_248: rt.u8, data3: rt.ByteSlice, out3: rt.ByteVec): void => { loops(data3, out3); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::patterns", (_228: rt.u8, data4: rt.ByteSlice, out4: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::patterns", (_249: rt.u8, data4: rt.ByteSlice, out4: rt.ByteVec): void => { patterns(data4, out4); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::closures", (_229: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::closures", (_250: rt.u8, data5: rt.ByteSlice, out5: rt.ByteVec): void => { closures(data5, out5); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::discriminants", (_230: rt.u8, data6: rt.ByteSlice, out6: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::discriminants", (_251: rt.u8, data6: rt.ByteSlice, out6: rt.ByteVec): void => { discriminants(data6, out6); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::library", (_231: rt.u8, data7: rt.ByteSlice, out7: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::library", (_252: rt.u8, data7: rt.ByteSlice, out7: rt.ByteVec): void => { library(data7, out7); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::ints", ints] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::strings", (_232: rt.u8, data8: rt.ByteSlice, out8: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::ints", ints] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::strings", (_253: rt.u8, data8: rt.ByteSlice, out8: rt.ByteVec): void => { strings(data8, out8); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::derived", (_233: rt.u8, data9: rt.ByteSlice, out9: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::derived", (_254: rt.u8, data9: rt.ByteSlice, out9: rt.ByteVec): void => { derived(data9, out9); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::written", (_234: rt.u8, data10: rt.ByteSlice, out10: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::written", (_255: rt.u8, data10: rt.ByteSlice, out10: rt.ByteVec): void => { written(data10, out10); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::panics", panics] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["generics::methods", (_235: rt.u8, data11: rt.ByteSlice, out11: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::panics", panics] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["generics::methods", (_256: rt.u8, data11: rt.ByteSlice, out11: rt.ByteVec): void => { methods(data11, out11); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::adaptors", adaptors] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::consumers", consumers] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::ranges", (_236: rt.u8, data12: rt.ByteSlice, out12: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::adaptors", adaptors] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::consumers", consumers] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::ranges", (_257: rt.u8, data12: rt.ByteSlice, out12: rt.ByteVec): void => { ranges(data12, out12); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::options", (_237: rt.u8, data13: rt.ByteSlice, out13: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::options", (_258: rt.u8, data13: rt.ByteSlice, out13: rt.ByteVec): void => { options(data13, out13); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::own", (_238: rt.u8, data14: rt.ByteSlice, out14: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::own", (_259: rt.u8, data14: rt.ByteSlice, out14: rt.ByteVec): void => { own(data14, out14); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::values", (_239: rt.u8, data15: rt.ByteSlice, out15: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::values", (_260: rt.u8, data15: rt.ByteSlice, out15: rt.ByteVec): void => { values(data15, out15); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::order", (_240: rt.u8, data16: rt.ByteSlice, out16: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::order", (_261: rt.u8, data16: rt.ByteSlice, out16: rt.ByteVec): void => { order(data16, out16); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::hash_maps", hashMaps] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::sets", sets] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::btree_maps", btreeMaps] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::deques", (_241: rt.u8, data17: rt.ByteSlice, out17: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::hash_maps", hashMaps] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::sets", sets] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::btree_maps", btreeMaps] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::deques", (_262: rt.u8, data17: rt.ByteSlice, out17: rt.ByteVec): void => { deques(data17, out17); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::flows", (_242: rt.u8, data18: rt.ByteSlice, out18: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["shapes::flows", (_263: rt.u8, data18: rt.ByteSlice, out18: rt.ByteVec): void => { flows(data18, out18); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::keys", keys] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["chars::chars", chars] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["strs::strs", strs] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::vecs", (_243: rt.u8, data19: rt.ByteSlice, out19: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::keys", keys] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["chars::chars", chars] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["strs::strs", strs] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::vecs", (_264: rt.u8, data19: rt.ByteSlice, out19: rt.ByteVec): void => { vecs(data19, out19); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["strs::slices", slices] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::stores", (_244: rt.u8, data20: rt.ByteSlice, out20: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["strs::slices", slices] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["collections::stores", (_265: rt.u8, data20: rt.ByteSlice, out20: rt.ByteVec): void => { stores(data20, out20); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["chunks::chunks", chunks] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["cows::cows", cows] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["durations::durations", durations] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["combinators::combinators", combinators] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["splits::splits", splits] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["nested::nested", nested] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["refs::refs", (_245: rt.u8, data21: rt.ByteSlice, out21: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["chunks::chunks", chunks] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["cows::cows", cows] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["durations::durations", durations] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["combinators::combinators", combinators] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["splits::splits", splits] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["nested::nested", nested] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["refs::refs", (_266: rt.u8, data21: rt.ByteSlice, out21: rt.ByteVec): void => { refs(data21, out21); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::counts", (_246: rt.u8, data22: rt.ByteSlice, out22: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::counts", (_267: rt.u8, data22: rt.ByteSlice, out22: rt.ByteVec): void => { counts(data22, out22); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::rounding", (_247: rt.u8, _310: rt.ByteSlice, out23: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["fmt::rounding", (_268: rt.u8, _310: rt.ByteSlice, out23: rt.ByteVec): void => { rounding(out23); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::skips", skips] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["wrappers::wrappers", wrappers] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["edits::edits", edits] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["utf8::decodes", decodes] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["floats::floats", floats] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["atomics::atomics", atomics] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::overrides", overrides] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::dyns", (_248: rt.u8, data23: rt.ByteSlice, out24: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::skips", skips] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["wrappers::wrappers", wrappers] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["edits::edits", edits] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["utf8::decodes", decodes] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["floats::floats", floats] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["atomics::atomics", atomics] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["keys::overrides", overrides] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::dyns", (_269: rt.u8, data23: rt.ByteSlice, out24: rt.ByteVec): void => { dyns(data23, out24); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["cells::cells", cells] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::shared", (_249: rt.u8, data24: rt.ByteSlice, out25: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["cells::cells", cells] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::shared", (_270: rt.u8, data24: rt.ByteSlice, out25: rt.ByteVec): void => { shared(data24, out25); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::ranked", ranked] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["captures::captures", captures] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["locks::locks", (_250: rt.u8, data25: rt.ByteSlice, out26: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["iters::ranked", ranked] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["captures::captures", captures] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["locks::locks", (_271: rt.u8, data25: rt.ByteSlice, out26: rt.ByteVec): void => { locks(data25, out26); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["locks::relock", (_251: rt.u8, data26: rt.ByteSlice, out27: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["locks::relock", (_272: rt.u8, data26: rt.ByteSlice, out27: rt.ByteVec): void => { relock(data26, out27); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["defaults::defaults", (_252: rt.u8, data27: rt.ByteSlice, out28: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["defaults::defaults", (_273: rt.u8, data27: rt.ByteSlice, out28: rt.ByteVec): void => { defaults(data27, out28); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::arcs", (_253: rt.u8, data28: rt.ByteSlice, out29: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::arcs", (_274: rt.u8, data28: rt.ByteSlice, out29: rt.ByteVec): void => { arcs(data28, out29); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::rcs", (_254: rt.u8, data29: rt.ByteSlice, out30: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::rcs", (_275: rt.u8, data29: rt.ByteSlice, out30: rt.ByteVec): void => { rcs(data29, out30); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::cyclic_arcs", (_255: rt.u8, data30: rt.ByteSlice, out31: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::cyclic_arcs", (_276: rt.u8, data30: rt.ByteSlice, out31: rt.ByteVec): void => { cyclicArcs(data30, out31); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::cyclic_rcs", (_256: rt.u8, data31: rt.ByteSlice, out32: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["counts::cyclic_rcs", (_277: rt.u8, data31: rt.ByteSlice, out32: rt.ByteVec): void => { cyclicRcs(data31, out32); - }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::anys", (_257: rt.u8, data32: rt.ByteSlice, out33: rt.ByteVec): void => { + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["dyns::anys", (_278: rt.u8, data32: rt.ByteSlice, out33: rt.ByteVec): void => { anys(data32, out33); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::copies", (_279: rt.u8, data33: rt.ByteSlice, out34: rt.ByteVec): void => { + copies(data33, out34); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::lists", (_280: rt.u8, data34: rt.ByteSlice, out35: rt.ByteVec): void => { + lists(data34, out35); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::nested", (_281: rt.u8, data35: rt.ByteSlice, out36: rt.ByteVec): void => { + pointers_nested(data35, out36); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::places", (_282: rt.u8, data36: rt.ByteSlice, out37: rt.ByteVec): void => { + places(data36, out37); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::sentinels", (_283: rt.u8, data37: rt.ByteSlice, out38: rt.ByteVec): void => { + sentinels(data37, out38); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::shared", (_284: rt.u8, data38: rt.ByteSlice, out39: rt.ByteVec): void => { + pointers_shared(data38, out39); + }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void], ["pointers::unchecked", (_285: rt.u8, data39: rt.ByteSlice, out40: rt.ByteVec): void => { + unchecked(data39, out40); }] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => void]]; } diff --git a/ts/fixtures/lang/expected/lib.ts b/ts/fixtures/lang/expected/lib.ts index 17b243b4c..e4b7aebf6 100644 --- a/ts/fixtures/lang/expected/lib.ts +++ b/ts/fixtures/lang/expected/lib.ts @@ -11,6 +11,11 @@ import type * as Hop from "./keys.Hop.ts"; import type * as Horizon from "./keys.Horizon.ts"; import type * as Lopsided from "./keys.Lopsided.ts"; import type * as locks_Tally from "./locks.Tally.ts"; +import type * as Halves from "./pointers.Halves.ts"; +import type * as Inner from "./pointers.Inner.ts"; +import * as List from "./pointers.List.ts"; +import type * as Outer from "./pointers.Outer.ts"; +import type * as Pair from "./pointers.Pair.ts"; import type * as Gauge from "./shapes.Gauge.ts"; import type * as Held from "./shapes.Held.ts"; import type * as Lens from "./shapes.Lens.ts"; @@ -49,6 +54,10 @@ export function copy$Gauge(v: Gauge.Gauge): Gauge.Gauge { return { level: v.level }; } +export function copy$Halves(v: Halves.Halves): Halves.Halves { + return ({ at0: copy$MaybeUninit_u32s(v.at0) } as unknown) as Halves.Halves; +} + export function copy$Held(v: Held.Held): Held.Held { return { at: copy$Point(v.at) }; } @@ -61,6 +70,10 @@ export function copy$Horizon(v: Horizon.Horizon): Horizon.Horizon { return { exclude: copy$Option_Hop(v.exclude), local: v.local }; } +export function copy$Inner(v: Inner.Inner): Inner.Inner { + return { a: v.a, b: v.b }; +} + export function copy$Lens(v: Lens.Lens): Lens.Lens { return { slot: v.slot, n: v.n }; } @@ -73,6 +86,13 @@ export function copy$Mark(v: Mark.Mark): Mark.Mark { return { at: v.at }; } +export function copy$MaybeUninit_u32s(v: rt.u32[]): rt.u32[] { + if (v === undefined) { + return v; + } + return v.slice(); +} + export function copy$Mutex_u8_u8(v: [rt.Mutex, rt.u8]): [rt.Mutex, rt.u8] { return [v[0], v[1]]; } @@ -89,6 +109,10 @@ export function copy$Option_Mutex_u8_u8(v: [rt.Mutex, rt.u8] | undefined) return v === undefined ? v : copy$Mutex_u8_u8(v); } +export function copy$Option_Pair(v: Pair.Pair | undefined): Pair.Pair | undefined { + return v === undefined ? v : copy$Pair(v); +} + export function copy$Option_Shape(v: Shape.Shape | undefined): Shape.Shape | undefined { return v === undefined ? v : copy$Shape(v); } @@ -109,6 +133,14 @@ export function copy$Option_usize_u8(v: [rt.usize, rt.u8] | undefined): [rt.usiz return v === undefined ? v : copy$usize_u8(v); } +export function copy$Outer(v: Outer.Outer): Outer.Outer { + return { inner: copy$Inner(v.inner), pair: copy$u32_u32(v.pair), n: v.n }; +} + +export function copy$Pair(v: Pair.Pair): Pair.Pair { + return { a: v.a, b: v.b }; +} + export function copy$Point(v: Point.Point): Point.Point { return { x: v.x, y: v.y }; } @@ -171,10 +203,22 @@ export function copy$chars(v: rt.char[]): rt.char[] { return v.slice(); } +export function copy$u32_u32(v: [rt.u32, rt.u32]): [rt.u32, rt.u32] { + return [v[0], v[1]]; +} + export function copy$u32s(v: rt.u32[]): rt.u32[] { return v.slice(); } +export function copy$u32s_u8(v: [rt.u32[], rt.u8]): [rt.u32[], rt.u8] { + return [copy$u32s(v[0]), v[1]]; +} + +export function copy$u8_Arc_u8(v: [rt.u8, rt.Arc]): [rt.u8, rt.Arc] { + return [v[0], v[1]]; +} + export function copy$u8_Horizon(v: [rt.u8, Horizon.Horizon]): [rt.u8, Horizon.Horizon] { return [v[0], copy$Horizon(v[1])]; } @@ -183,6 +227,10 @@ export function copy$u8_Reverse_Option_u8(v: [rt.u8, rt.u8 | undefined]): [rt.u8 return [v[0], v[1]]; } +export function copy$u8_String(v: [rt.u8, string]): [rt.u8, string] { + return [v[0], v[1]]; +} + export function copy$u8_Tally(v: [rt.u8, Tally.Tally]): [rt.u8, Tally.Tally] { return [v[0], copy$Tally(v[1])]; } @@ -215,10 +263,22 @@ export function copy$u8s_u8s(v: [rt.ByteSlice, rt.ByteSlice]): [rt.ByteSlice, rt return [v[0], v[1]]; } +export function copy$units(v: undefined[]): undefined[] { + return v.slice(); +} + export function copy$usize_u8(v: [rt.usize, rt.u8]): [rt.usize, rt.u8] { return [v[0], v[1]]; } +export function drop$List_Arc_u8(_1: List.List>): void { + List.drop$Drop_Arc_u8(_1); +} + +export function drop$List_String(_1: List.List): void { + List.drop$Drop_String(_1); +} + export function drop$Result_MutexGuard_Tally_TryLockError_MutexGuard_Tally(_1: rt.Result, rt.TryLockError>>): void { if (_1.ok) { rt.mutexUnlock(_1.value); @@ -253,6 +313,10 @@ export function drop$TryLockError_MutexGuard_u32(_1: rt.TryLockError]): void { + _1[1][Symbol.dispose](); +} + export function drop$u8_Reverse_Arc_Lopsided(_1: [rt.u8, rt.Arc]): void { _1[1][Symbol.dispose](); } diff --git a/ts/fixtures/lang/expected/pointers.Free.ts b/ts/fixtures/lang/expected/pointers.Free.ts new file mode 100644 index 000000000..d909c8985 --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Free.ts @@ -0,0 +1,9 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Node from "./pointers.Node.ts"; + +export interface Free { + next: Node.Node | undefined; +} + +//# sourceMappingURL=pointers.Free.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Halves.ts b/ts/fixtures/lang/expected/pointers.Halves.ts new file mode 100644 index 000000000..8d64cfc3f --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Halves.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export type Halves = { at0: rt.u32[] }; + +//# sourceMappingURL=pointers.Halves.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Inner.ts b/ts/fixtures/lang/expected/pointers.Inner.ts new file mode 100644 index 000000000..210a5f5f2 --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Inner.ts @@ -0,0 +1,10 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export interface Inner { + a: rt.u32; + b: rt.u32; +} + +//# sourceMappingURL=pointers.Inner.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Link.ts b/ts/fixtures/lang/expected/pointers.Link.ts new file mode 100644 index 000000000..fbbf5dec0 --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Link.ts @@ -0,0 +1,8 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Node from "./pointers.Node.ts"; + +/** A node's links while it is in the list, or while it is free. */ +export type Link = { at0: Node.Node | undefined; at4: Node.Node }; + +//# sourceMappingURL=pointers.Link.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Links.ts b/ts/fixtures/lang/expected/pointers.Links.ts new file mode 100644 index 000000000..03bee7790 --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Links.ts @@ -0,0 +1,10 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Node from "./pointers.Node.ts"; + +export interface Links { + next: Node.Node; + prev: Node.Node; +} + +//# sourceMappingURL=pointers.Links.ts.map diff --git a/ts/fixtures/lang/expected/pointers.List.ts b/ts/fixtures/lang/expected/pointers.List.ts new file mode 100644 index 000000000..6fa4af5b3 --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.List.ts @@ -0,0 +1,241 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$u8_Arc_u8, copy$u8_String, drop$u8_Arc_u8 } from "./lib.ts"; +import { attachBefore$Arc_u8, attachBefore$String, detach$Arc_u8, detach$String } from "./pointers.ts"; +import type * as Free from "./pointers.Free.ts"; +import type * as Links from "./pointers.Links.ts"; +import * as Node from "./pointers.Node.ts"; + +/** Entries in the order they were pushed, but for those moved to the front. */ +export class List { + /** The guard: `next` is the first entry and `prev` the last, or itself if none. */ + guard: Node.Node | undefined; + free: Node.Node | undefined; + len: rt.usize; + + constructor(guard: Node.Node | undefined, free: Node.Node | undefined, len: rt.usize) { + this.guard = guard; + this.free = free; + this.len = len; + } +} + +export function new$Arc_u8(): List> { + return new List(undefined, undefined, 0); +} + +export function new$String(): List { + return new List(undefined, undefined, 0); +} + +export function guard$Arc_u8(self: List>): Node.Node> { + if (self.guard !== undefined) { + return self.guard; + } + const guard2: Node.Node> = Node.new$Arc_u8(); + const _8: Links.Links> = { next: guard2, prev: guard2 }; + const _11: Node.Node> = guard2; + _11.link.at0 = _8.next; + _11.link.at4 = _8.prev; + self.guard = guard2; + return guard2; +} + +export function guard$String(self: List): Node.Node { + if (self.guard !== undefined) { + return self.guard; + } + const guard2: Node.Node = Node.new$String(); + const _8: Links.Links = { next: guard2, prev: guard2 }; + const _11: Node.Node = guard2; + _11.link.at0 = _8.next; + _11.link.at4 = _8.prev; + self.guard = guard2; + return guard2; +} + +export function alloc$Arc_u8(self: List>): Node.Node> { + if (self.free === undefined) { + return Node.new$Arc_u8(); + } + const free: Node.Node> = self.free; + self.free = free.link.at0; + return free; +} + +export function alloc$String(self: List): Node.Node { + if (self.free === undefined) { + return Node.new$String(); + } + const free: Node.Node = self.free; + self.free = free.link.at0; + return free; +} + +export function pushBack$Arc_u8(self: List>, key: rt.u8, value: rt.Arc): void { + const guard: Node.Node> = guard$Arc_u8(self); + const node: Node.Node> = alloc$Arc_u8(self); + rt.ptrWrite(node.entry, [key, value] as [rt.u8, rt.Arc]); + attachBefore$Arc_u8(node, guard); + self.len = rt.usizeAdd(self.len, 1); +} + +export function pushBack$String(self: List, key: rt.u8, value: string): void { + const guard: Node.Node = guard$String(self); + const node: Node.Node = alloc$String(self); + rt.ptrWrite(node.entry, [key, value] as [rt.u8, string]); + attachBefore$String(node, guard); + self.len = rt.usizeAdd(self.len, 1); +} + +export function find$Arc_u8(self: List>, key: rt.u8): Node.Node> | undefined { + const _4: Node.Node> | undefined = self.guard; + if (_4 !== undefined) { + const guard: Node.Node> = _4; + let at: Node.Node> = guard.link.at0 as Node.Node>; + while (at !== guard) { + if (!(at.entry[0] === key)) { + at = at.link.at0 as Node.Node>; + } else { + return at; + } + } + return undefined; + } + return undefined; +} + +export function find$String(self: List, key: rt.u8): Node.Node | undefined { + const _4: Node.Node | undefined = self.guard; + if (_4 !== undefined) { + const guard: Node.Node = _4; + let at: Node.Node = guard.link.at0 as Node.Node; + while (at !== guard) { + if (!(at.entry[0] === key)) { + at = at.link.at0 as Node.Node; + } else { + return at; + } + } + return undefined; + } + return undefined; +} + +export function remove$Arc_u8(self: List>, key: rt.u8): [rt.u8, rt.Arc] | undefined { + const _4: Node.Node> | undefined = find$Arc_u8(self, key); + if (_4 !== undefined) { + const node: Node.Node> = _4; + detach$Arc_u8(node); + const entry: [rt.u8, rt.Arc] = copy$u8_Arc_u8(node.entry); + const _16: Free.Free> = { next: self.free }; + node.link.at0 = _16.next; + self.free = node; + self.len = rt.usizeSub(self.len, 1); + return entry; + } + return undefined; +} + +export function remove$String(self: List, key: rt.u8): [rt.u8, string] | undefined { + const _4: Node.Node | undefined = find$String(self, key); + if (_4 !== undefined) { + const node: Node.Node = _4; + detach$String(node); + const entry: [rt.u8, string] = copy$u8_String(node.entry); + const _16: Free.Free = { next: self.free }; + node.link.at0 = _16.next; + self.free = node; + self.len = rt.usizeSub(self.len, 1); + return entry; + } + return undefined; +} + +export function toFront$Arc_u8(self: List>, key: rt.u8): boolean { + const _4: Node.Node> | undefined = find$Arc_u8(self, key); + if (_4 !== undefined) { + const node: Node.Node> = _4; + if (self.guard !== undefined) { + const guard: Node.Node> = self.guard; + detach$Arc_u8(node); + attachBefore$Arc_u8(node, guard.link.at0 as Node.Node>); + return true; + } + return false; + } + return false; +} + +export function toFront$String(self: List, key: rt.u8): boolean { + const _4: Node.Node | undefined = find$String(self, key); + if (_4 !== undefined) { + const node: Node.Node = _4; + if (self.guard !== undefined) { + const guard: Node.Node = self.guard; + detach$String(node); + attachBefore$String(node, guard.link.at0 as Node.Node); + return true; + } + return false; + } + return false; +} + +export function getMut$String(self: List, key: rt.u8): rt.Place | undefined { + const _3: Node.Node | undefined = find$String(self, key); + if (_3 !== undefined) { + const _9: [rt.u8, string] = _3.entry; + return rt.place(() => _9[1], (value) => _9[1] = value); + } + return undefined; +} + +export function each$String(self: List, out: rt.ByteVec): void { + b0: { + if (self.guard !== undefined) { + const guard: Node.Node = self.guard; + let at: Node.Node = guard.link.at0 as Node.Node; + while (true) { + if (!((at !== guard))) { + break b0; + } + const _13: [rt.u8, string] = at.entry; + rt.byteVecPush(out, _13[0]); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(_13[1])); + at = at.link.at0 as Node.Node; + } + } + } +} + +export function drop$Drop_Arc_u8(self: List>): void { + if (self.guard !== undefined) { + const guard: Node.Node> = self.guard; + let at: Node.Node> = guard.link.at0 as Node.Node>; + while (at !== guard) { + const next: Node.Node> = at.link.at0 as Node.Node>; + drop$u8_Arc_u8(at.entry); + at = next; + } + } + while (self.free !== undefined) { + self.free = self.free.link.at0; + } +} + +export function drop$Drop_String(self: List): void { + if (self.guard !== undefined) { + const guard: Node.Node = self.guard; + let at: Node.Node = guard.link.at0 as Node.Node; + while (at !== guard) { + at = at.link.at0 as Node.Node; + } + } + while (self.free !== undefined) { + self.free = self.free.link.at0; + } +} + +//# sourceMappingURL=pointers.List.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Node.ts b/ts/fixtures/lang/expected/pointers.Node.ts new file mode 100644 index 000000000..77c147be2 --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Node.ts @@ -0,0 +1,24 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import type * as Link from "./pointers.Link.ts"; +import type * as Links from "./pointers.Links.ts"; + +export interface Node { + entry: [rt.u8, V]; + link: Link.Link; +} + +export function new$Arc_u8(): Node> { + const _1: [rt.u8, rt.Arc] = ([undefined, undefined] as unknown) as [rt.u8, rt.Arc]; + const _3: Links.Links> = { next: rt.DANGLING, prev: rt.DANGLING }; + return { entry: _1, link: ({ at0: _3.next, at4: _3.prev } as unknown) as Link.Link> }; +} + +export function new$String(): Node { + const _1: [rt.u8, string] = ([undefined, undefined] as unknown) as [rt.u8, string]; + const _3: Links.Links = { next: rt.DANGLING, prev: rt.DANGLING }; + return { entry: _1, link: ({ at0: _3.next, at4: _3.prev } as unknown) as Link.Link }; +} + +//# sourceMappingURL=pointers.Node.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Outer.ts b/ts/fixtures/lang/expected/pointers.Outer.ts new file mode 100644 index 000000000..73bb035ad --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Outer.ts @@ -0,0 +1,12 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import type * as Inner from "./pointers.Inner.ts"; + +export interface Outer { + inner: Inner.Inner; + pair: [rt.u32, rt.u32]; + n: rt.u32; +} + +//# sourceMappingURL=pointers.Outer.ts.map diff --git a/ts/fixtures/lang/expected/pointers.Pair.ts b/ts/fixtures/lang/expected/pointers.Pair.ts new file mode 100644 index 000000000..4889c478e --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.Pair.ts @@ -0,0 +1,10 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; + +export interface Pair { + a: rt.u32; + b: rt.u32; +} + +//# sourceMappingURL=pointers.Pair.ts.map diff --git a/ts/fixtures/lang/expected/pointers.ts b/ts/fixtures/lang/expected/pointers.ts new file mode 100644 index 000000000..cb92b9b0b --- /dev/null +++ b/ts/fixtures/lang/expected/pointers.ts @@ -0,0 +1,356 @@ +// Generated by rust2-ts. Do not edit. + +import * as rt from "./rt.ts"; +import { copy$Halves, copy$MaybeUninit_u32s, copy$Option_Pair, copy$Outer, copy$Pair, copy$u32_u32, copy$u32s_u8, copy$units, drop$List_Arc_u8, drop$List_String } from "./lib.ts"; +import { mapOr$Option_Pair_u8_copies_closure0, mapOr$Option_u8_Arc_u8_u8_shared_closure0, filter$Option_u32_sentinels_closure0 } from "./core/option.ts"; +import type * as Halves from "./pointers.Halves.ts"; +import type * as Inner from "./pointers.Inner.ts"; +import type * as Links from "./pointers.Links.ts"; +import * as List from "./pointers.List.ts"; +import type * as Node from "./pointers.Node.ts"; +import type * as Outer from "./pointers.Outer.ts"; +import type * as Pair from "./pointers.Pair.ts"; + +/** Put `node` in the list just before `at`. */ +export function attachBefore$Arc_u8(node: Node.Node>, at: Node.Node>): void { + const _3: Links.Links> = { next: at, prev: at.link.at4 }; + const _8: Node.Node> = node; + _8.link.at0 = _3.next; + _8.link.at4 = _3.prev; + at.link.at4 = node; + (node.link.at4.link.at0 as Node.Node>) = node; +} + +/** Put `node` in the list just before `at`. */ +export function attachBefore$String(node: Node.Node, at: Node.Node): void { + const _3: Links.Links = { next: at, prev: at.link.at4 }; + const _8: Node.Node = node; + _8.link.at0 = _3.next; + _8.link.at4 = _3.prev; + at.link.at4 = node; + (node.link.at4.link.at0 as Node.Node) = node; +} + +export function detach$Arc_u8(node: Node.Node>): void { + (node.link.at4.link.at0 as Node.Node>) = node.link.at0 as Node.Node>; + ((node.link.at0 as Node.Node>)).link.at4 = node.link.at4; +} + +export function detach$String(node: Node.Node): void { + (node.link.at4.link.at0 as Node.Node) = node.link.at0 as Node.Node; + ((node.link.at0 as Node.Node)).link.at4 = node.link.at4; +} + +/** + * Steps on a list of the operation and key each pair of bytes picks, putting what each + * finds and then the list in order. + */ +export function lists(data: rt.ByteSlice, out: rt.ByteVec): void { + const list: List.List = List.new$String(); + let dataData: Uint8Array = data.data; + let dataStart: number = data.start; + let dataLen: number = data.length; + while (dataLen >= 2) { + const op: rt.u8 = dataData[dataStart]; + const key: rt.u8 = dataData[dataStart + 1]; + const restData: Uint8Array = dataData; + const restStart: number = dataStart + 2; + const restLen: number = dataLen - 2; + dataData = restData; + dataStart = restStart; + dataLen = restLen; + const _13: [rt.u8, rt.u8] = [op, rt.u8Rem(key, 8)]; + const op2: rt.u8 = op; + const key2: rt.u8 = _13[1]; + const _18: rt.u8 = rt.u8Rem(op2, 4); + if (_18 === 0) { + const _26: rt.usize = rt.u8Rem(op2, 3) as rt.usize; + if (!(_26 < 3)) { + rt.panic("index out of bounds: the len is " + 3 + " but the index is " + _26); + } + List.pushBack$String(list, key2, ["", "v", "vv"][_26]); + } else if (_18 === 1) { + const _30: [rt.u8, string] | undefined = List.remove$String(list, key2); + if (_30 === undefined) { + rt.byteVecPush(out, 255); + } else { + const k: rt.u8 = _30[0]; + const v: string = _30[1]; + rt.byteVecPush(out, k); + rt.byteVecExtendFromSlice(out, rt.stringAsBytes(v)); + } + } else if (_18 === 2) { + rt.byteVecPush(out, List.toFront$String(list, key2) ? 1 : 0); + } else { + const _42: rt.Place | undefined = List.getMut$String(list, key2); + if (_42 === undefined) { + rt.byteVecPush(out, 254); + } else { + _42.v = _42.v + "w"; + } + } + } + rt.byteVecPush(out, list.len & 0xff); + List.each$String(list, out); + drop$List_String(list); +} + +/** + * Steps on a list of shared counts, putting how many hold the one each push clones + * while the list lives, and after it drops. + */ +export function shared(data: rt.ByteSlice, out: rt.ByteVec): void { + const held: rt.Arc = new rt.Arc(0); + const list: List.List> = List.new$Arc_u8(); + let dataData: Uint8Array = data.data; + let dataStart: number = data.start; + let dataLen: number = data.length; + while (dataLen >= 2) { + const op: rt.u8 = dataData[dataStart]; + const key: rt.u8 = dataData[dataStart + 1]; + const restData: Uint8Array = dataData; + const restStart: number = dataStart + 2; + const restLen: number = dataLen - 2; + dataData = restData; + dataStart = restStart; + dataLen = restLen; + const _13: [rt.u8, rt.u8] = [op, rt.u8Rem(key, 4)]; + const op2: rt.u8 = op; + const key2: rt.u8 = _13[1]; + const _19: rt.u8 = rt.u8Rem(op2, 3); + if (_19 === 0) { + const _22: rt.Arc = rt.arcClone(held); + List.pushBack$Arc_u8(list, key2, _22); + } else if (_19 === 1) { + rt.byteVecPush(out, mapOr$Option_u8_Arc_u8_u8_shared_closure0(List.remove$Arc_u8(list, key2), 255, (_2: [rt.u8, rt.Arc]): rt.u8 => { + const v: rt.Arc = _2[1]; + const _0: rt.u8 = v.value; + v[Symbol.dispose](); + return _0; + })); + } else { + rt.byteVecPush(out, List.toFront$Arc_u8(list, key2) ? 1 : 0); + } + rt.byteVecPush(out, held.strong & 0xff); + } + drop$List_Arc_u8(list); + rt.byteVecPush(out, held.strong & 0xff); + held[Symbol.dispose](); +} + +/** + * Pointers to numbers, to a field, and to a whole struct: read, written, swapped, + * replaced, compared, and null. + */ +export function places(data: rt.ByteSlice, out: rt.ByteVec): void { + let _29!: rt.Place; + let _42!: rt.u8; + let x: rt.u32 = 0; + let y: rt.u32 = 1; + const pair: Pair.Pair = { a: 2, b: 3 }; + const px: rt.Place = rt.place(() => x, (value) => x = value); + const py: rt.Place = rt.place(() => y, (value2) => y = value2); + const pa: rt.Place = rt.place(() => pair.a, (value3) => pair.a = value3); + const pp: Pair.Pair = pair; + let held: rt.Place = rt.NULL; + for (const byte of rt.byteSliceView(data)) { + const v: rt.u32 = byte as rt.u32; + const _19: rt.u8 = rt.u8Rem(byte, 9); + if (_19 === 0) { + px.v = rt.u32Add(px.v, v); + } else if (_19 === 1) { + py.v = v; + } else if (_19 === 2) { + const x2 = px; + const y2 = py; + const old3 = x2.v; + x2.v = y2.v; + y2.v = old3; + } else if (_19 === 3) { + const pointer = pa; + const old2 = pointer.v; + pointer.v = v; + rt.byteVecPush(out, old2 & 0xff); + } else if (_19 === 4) { + pp.b = v; + } else if (_19 === 5) { + const old: Pair.Pair = copy$Pair(pp); + rt.ptrWrite(pp, { a: old.b, b: old.a }); + } else if (_19 === 6) { + if (!((byte & 1) === 0)) { + _29 = pa; + } else { + _29 = px; + } + held = _29; + } else if (_19 === 7) { + rt.byteVecPush(out, (held === rt.NULL ? 1 : 0) | (((pp === pair ? 1 : 0) << 1) & 0xff)); + } else { + if (!((held === rt.NULL))) { + _42 = held.v & 0xff; + } else { + _42 = 255; + } + rt.byteVecPush(out, _42); + } + } + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of(x & 0xff, y & 0xff, pair.a & 0xff, pair.b & 0xff))); +} + +/** + * Unchecked indexing, a boxed number behind a pointer, a `MaybeUninit` number, and a + * value dropped in place. + */ +export function unchecked(data: rt.ByteSlice, out: rt.ByteVec): void { + const counts: rt.u32[] = Array.from({ length: 4 }, (): rt.u32 => { + return 0; + }); + const boxed: rt.Place = { v: 0 }; + let slot: rt.u32 = (undefined as unknown) as rt.u32; + const uninit = rt.place(() => slot, (value) => slot = value); + uninit.v = 7; + uninit; + const held: rt.Arc = new rt.Arc(0); + let dropped: rt.Arc = rt.arcClone(held); + for (const byte of rt.byteSliceView(data)) { + const at: rt.usize = rt.u8Rem(byte, 4) as rt.usize; + const _23: rt.u32[] = counts; + const at2 = rt.idx(_23, at); + _23[at2] = rt.u32Add(_23[at2], 1); + boxed.v = rt.u32Add(boxed.v, byte as rt.u32); + const _28: rt.u32 = byte as rt.u32; + const _29: rt.Place = rt.place(() => slot, (value2) => slot = value2); + _29.v = (_29.v ^ _28) >>> 0; + const _35: rt.u32[] = counts; + rt.byteVecPush(out, _35[rt.idx(_35, at)] & 0xff); + } + rt.byteVecPush(out, boxed.v & 0xff); + rt.byteVecPush(out, slot & 0xff); + rt.byteVecPush(out, held.strong & 0xff); + rt.place(() => dropped, (value3) => dropped = value3).v[Symbol.dispose](); + rt.byteVecPush(out, held.strong & 0xff); + held[Symbol.dispose](); +} + +/** + * Pointers to the parts of a struct kept as objects: initialized one by one from nothing, + * written through the struct's pointer and their own, and read out before a write. + */ +export function nested(data: rt.ByteSlice, out: rt.ByteVec): void { + const slot: Outer.Outer = ({ inner: { a: undefined, b: undefined }, pair: [undefined, undefined], n: undefined } as unknown) as Outer.Outer; + const p: Outer.Outer = slot; + const _8: [Inner.Inner, [rt.u32, rt.u32]] = [p.inner, p.pair]; + const inner: Inner.Inner = _8[0]; + const pair: [rt.u32, rt.u32] = _8[1]; + rt.ptrWrite(inner, { a: 1, b: 2 }); + rt.ptrWrite(pair, [3, 4] as [rt.u32, rt.u32]); + rt.place(() => p.n, (value) => p.n = value).v = 5; + for (const byte of rt.byteSliceView(data)) { + const v: rt.u32 = byte as rt.u32; + const _25: rt.u8 = rt.u8Rem(byte, 6); + if (_25 === 0) { + p.inner.a = v; + } else if (_25 === 1) { + rt.ptrWrite(inner, { a: v, b: inner.a }); + } else if (_25 === 2) { + pair[1] = rt.u32Add(pair[1], v); + } else if (_25 === 3) { + const old: Outer.Outer = copy$Outer(p); + inner.b = v; + rt.byteVecPush(out, old.inner.b & 0xff); + } else if (_25 === 4) { + const old2: [rt.u32, rt.u32] = copy$u32_u32(pair); + p.pair[0] = v; + rt.byteVecPush(out, old2[0] & 0xff); + } else { + rt.byteVecPush(out, (inner.a & 0xff) ^ (pair[0] & 0xff)); + } + } + const outer: Outer.Outer = slot; + const inner2: Inner.Inner = outer.inner; + const pair2: [rt.u32, rt.u32] = outer.pair; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of(inner2.a & 0xff, inner2.b & 0xff, pair2[0] & 0xff, pair2[1] & 0xff, outer.n & 0xff))); +} + +/** + * Copies of a `MaybeUninit` and of a union holding arrays, each changed after, and values + * read out through pointers before what they point at changes. + */ +export function copies(data: rt.ByteSlice, out: rt.ByteVec): void { + for (const _5 of rt.byteSliceView(data)) { + const v: rt.u32 = _5 as rt.u32; + const x: rt.u32[] = copy$MaybeUninit_u32s(Array.from({ length: 4 }, (): rt.u32 => { + return v; + })); + const y: rt.u32[] = copy$MaybeUninit_u32s(x); + const _16: rt.u32 = (v ^ 1) >>> 0; + const uninit = x; + rt.ptrWrite(uninit, Array.from({ length: 4 }, (): rt.u32 => { + return _16; + })); + uninit; + const h: Halves.Halves = ({ at0: [v, rt.u32Add(v, 1)] } as unknown) as Halves.Halves; + const g: Halves.Halves = copy$Halves(h); + g.at0[0] = rt.u32Add(v, 2); + let held: Pair.Pair | undefined = { a: v, b: 0 }; + const p: rt.Place = rt.place(() => held, (value) => held = value); + const old: Pair.Pair | undefined = copy$Option_Pair(p.v); + if (p.v !== undefined) { + p.v.a = rt.u32Add(v, 3); + } + const _35: [rt.u8, rt.u8] = snapshot(v); + const before: rt.u8 = _35[0]; + const after: rt.u8 = _35[1]; + rt.byteVecExtendFromSlice(out, rt.byteSliceFrom(Uint8Array.of(y[0] & 0xff, x[0] & 0xff, h.at0[0] & 0xff, g.at0[0] & 0xff, mapOr$Option_Pair_u8_copies_closure0(old, 0, (o: Pair.Pair): rt.u8 => { + return o.a & 0xff; + }), mapOr$Option_Pair_u8_copies_closure0(held, 0, (o2: Pair.Pair): rt.u8 => { + return o2.a & 0xff; + }), before, after))); + } +} + +/** What a pointer to a tuple holding an array reads, and what the tuple holds after. */ +export function snapshot(v: rt.u32): [rt.u8, rt.u8] { + const nest: [rt.u32[], rt.u8] = [[v, 1], 0]; + const before: [rt.u32[], rt.u8] = copy$u32s_u8(nest); + nest[0][0] = rt.u32Add(v, 4); + return [before[0][0] & 0xff, nest[0][0] & 0xff] as [rt.u8, rt.u8]; +} + +/** + * Null and dangling pointers to values JS keeps as `undefined`: an `Option` read through + * `as_ref`, and `()` written through a dangling pointer; and values of no bytes read + * through dangling pointers. + */ +export function sentinels(data: rt.ByteSlice, out: rt.ByteVec): void { + let p!: rt.Place; + let _19!: rt.u8; + for (const byte of rt.byteSliceView(data)) { + let some: rt.u32 | undefined = filter$Option_u32_sentinels_closure0(byte as rt.u32, (v: rt.u32): boolean => { + return rt.u32Rem(v, 2) === 0; + }); + if (rt.u8Rem(byte, 3) === 0) { + p = rt.NULL; + } else { + p = rt.place(() => some, (value) => some = value); + } + const pointer = p; + const _20: rt.u32 | undefined | rt.None = pointer === rt.NULL ? rt.None : pointer.v; + if (_20 === rt.None) { + _19 = 0; + } else if (_20 === undefined) { + _19 = 1; + } else { + _19 = rt.u8Add(2, rt.u32Rem(_20, 100) & 0xff); + } + rt.byteVecPush(out, _19); + const pointer2 = p; + rt.byteVecPush(out, ((pointer2 === rt.NULL ? rt.None : pointer2.v)) === rt.None ? 1 : 0); + const units: undefined[] = (new Array(2) as unknown) as undefined[]; + copy$units(units); + rt.byteVecPush(out, rt.iterCount(rt.iterSlice([undefined, undefined])) & 0xff); + rt.byteVecPush(out, units.length & 0xff); + } +} + +//# sourceMappingURL=pointers.ts.map diff --git a/ts/fixtures/lang/src/harness.rs b/ts/fixtures/lang/src/harness.rs index 19a0ba288..251b10cd3 100644 --- a/ts/fixtures/lang/src/harness.rs +++ b/ts/fixtures/lang/src/harness.rs @@ -3,7 +3,7 @@ use crate::{ atomics, captures, cells, chars, chunks, collections, combinators, counts, cows, defaults, durations, dyns, edits, - floats, fmt, generics, ints, iters, keys, locks, nested, refs, shapes, splits, strs, utf8, wrappers, + floats, fmt, generics, ints, iters, keys, locks, nested, pointers, refs, shapes, splits, strs, utf8, wrappers, }; /// The native side's entry: [`run`], except that a case starting with 0xff prints a @@ -96,6 +96,13 @@ fn cases() -> Vec<(&'static str, Case)> { ("counts::cyclic_arcs", |_, data, out| counts::cyclic_arcs(data, out)), ("counts::cyclic_rcs", |_, data, out| counts::cyclic_rcs(data, out)), ("dyns::anys", |_, data, out| dyns::anys(data, out)), + ("pointers::copies", |_, data, out| pointers::copies(data, out)), + ("pointers::lists", |_, data, out| pointers::lists(data, out)), + ("pointers::nested", |_, data, out| pointers::nested(data, out)), + ("pointers::places", |_, data, out| pointers::places(data, out)), + ("pointers::sentinels", |_, data, out| pointers::sentinels(data, out)), + ("pointers::shared", |_, data, out| pointers::shared(data, out)), + ("pointers::unchecked", |_, data, out| pointers::unchecked(data, out)), ] } diff --git a/ts/fixtures/lang/src/lib.rs b/ts/fixtures/lang/src/lib.rs index bd196839c..5ac28c917 100644 --- a/ts/fixtures/lang/src/lib.rs +++ b/ts/fixtures/lang/src/lib.rs @@ -33,6 +33,7 @@ mod iters; mod keys; mod locks; mod nested; +mod pointers; mod refs; mod shapes; mod splits; diff --git a/ts/fixtures/lang/src/pointers.rs b/ts/fixtures/lang/src/pointers.rs new file mode 100644 index 000000000..4e348beee --- /dev/null +++ b/ts/fixtures/lang/src/pointers.rs @@ -0,0 +1,450 @@ +//! Raw pointers: a circular list of boxed nodes as `hashlink`'s `LinkedHashMap` keeps one, +//! linked through a union and holding a `MaybeUninit` entry, with a free list of nodes to +//! reuse; pointers to numbers, to fields, and to whole values, read, written, swapped, +//! and compared; and pointers to the parts of a struct kept as objects. + +use std::mem::MaybeUninit; +use std::ptr::{self, NonNull}; +use std::sync::Arc; + +struct Links { + next: NonNull>, + prev: NonNull>, +} + +impl Clone for Links { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Links {} + +struct Free { + next: Option>>, +} + +impl Clone for Free { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Free {} + +/// A node's links while it is in the list, or while it is free. +union Link { + value: Links, + free: Free, +} + +struct Node { + entry: MaybeUninit<(u8, V)>, + link: Link, +} + +/// Entries in the order they were pushed, but for those moved to the front. +struct List { + /// The guard: `next` is the first entry and `prev` the last, or itself if none. + guard: Option>>, + free: Option>>, + len: usize, +} + +impl List { + fn new() -> Self { + List { + guard: None, + free: None, + len: 0, + } + } + + fn guard(&mut self) -> NonNull> { + if let Some(guard) = self.guard { + return guard; + } + let mut guard = unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(Node::new()))) }; + unsafe { + guard.as_mut().link.value = Links { + next: guard, + prev: guard, + } + }; + self.guard = Some(guard); + guard + } + + fn alloc(&mut self) -> NonNull> { + match self.free { + Some(free) => { + self.free = unsafe { free.as_ref().link.free.next }; + free + } + None => NonNull::from(Box::leak(Box::new(Node::new()))), + } + } + + fn push_back(&mut self, key: u8, value: V) { + let guard = self.guard(); + let mut node = self.alloc(); + unsafe { + node.as_mut().entry.as_mut_ptr().write((key, value)); + attach_before(node, guard); + } + self.len += 1; + } + + fn find(&self, key: u8) -> Option>> { + let guard = self.guard?; + let mut at = unsafe { guard.as_ref().link.value.next }; + while at != guard { + unsafe { + if (*at.as_ptr()).entry.assume_init_ref().0 == key { + return Some(at); + } + at = at.as_ref().link.value.next; + } + } + None + } + + fn remove(&mut self, key: u8) -> Option<(u8, V)> { + let mut node = self.find(key)?; + unsafe { + detach(node); + let entry = node.as_mut().entry.as_ptr().read(); + node.as_mut().link.free = Free { next: self.free }; + self.free = Some(node); + self.len -= 1; + Some(entry) + } + } + + fn to_front(&mut self, key: u8) -> bool { + let Some(node) = self.find(key) else { + return false; + }; + unsafe { + let Some(guard) = self.guard else { return false }; + detach(node); + attach_before(node, guard.as_ref().link.value.next); + } + true + } + + fn get_mut(&mut self, key: u8) -> Option<&mut V> { + let node = self.find(key)?; + unsafe { Some(&mut (*(*node.as_ptr()).entry.as_mut_ptr()).1) } + } +} + +impl List { + fn each(&self, out: &mut Vec) { + let Some(guard) = self.guard else { + return; + }; + let mut at = unsafe { guard.as_ref().link.value.next }; + while at != guard { + let (key, value) = unsafe { at.as_ref().entry.assume_init_ref() }; + out.push(*key); + out.extend_from_slice(value.as_bytes()); + at = unsafe { at.as_ref().link.value.next }; + } + } +} + +impl Node { + fn new() -> Self { + Node { + entry: MaybeUninit::uninit(), + link: Link { + value: Links { + next: NonNull::dangling(), + prev: NonNull::dangling(), + }, + }, + } + } +} + +/// Put `node` in the list just before `at`. +unsafe fn attach_before(mut node: NonNull>, mut at: NonNull>) { + unsafe { + node.as_mut().link.value = Links { + prev: at.as_ref().link.value.prev, + next: at, + }; + at.as_mut().link.value.prev = node; + (*node.as_ref().link.value.prev.as_ptr()).link.value.next = node; + } +} + +unsafe fn detach(mut node: NonNull>) { + unsafe { + node.as_mut().link.value.prev.as_mut().link.value.next = node.as_ref().link.value.next; + node.as_mut().link.value.next.as_mut().link.value.prev = node.as_ref().link.value.prev; + } +} + +impl Drop for List { + fn drop(&mut self) { + if let Some(guard) = self.guard { + unsafe { + let mut at = guard.as_ref().link.value.next; + while at != guard { + let next = at.as_ref().link.value.next; + let mut node = Box::from_raw(at.as_ptr()); + node.entry.assume_init_drop(); + at = next; + } + drop(Box::from_raw(guard.as_ptr())); + } + } + while let Some(free) = self.free { + unsafe { + self.free = free.as_ref().link.free.next; + drop(Box::from_raw(free.as_ptr())); + } + } + } +} + +/// Steps on a list of the operation and key each pair of bytes picks, putting what each +/// finds and then the list in order. +pub fn lists(data: &[u8], out: &mut Vec) { + let mut list = List::::new(); + let mut data = data; + while let [op, key, rest @ ..] = data { + data = rest; + let (op, key) = (*op, *key % 8); + match op % 4 { + 0 => list.push_back(key, String::from(["", "v", "vv"][usize::from(op % 3)])), + 1 => match list.remove(key) { + Some((k, v)) => { + out.push(k); + out.extend_from_slice(v.as_bytes()); + } + None => out.push(0xff), + }, + 2 => out.push(list.to_front(key) as u8), + _ => match list.get_mut(key) { + Some(value) => value.push('w'), + None => out.push(0xfe), + }, + } + } + out.push(list.len as u8); + list.each(out); +} + +/// Steps on a list of shared counts, putting how many hold the one each push clones +/// while the list lives, and after it drops. +pub fn shared(data: &[u8], out: &mut Vec) { + let held = Arc::new(0u8); + { + let mut list = List::>::new(); + let mut data = data; + while let [op, key, rest @ ..] = data { + data = rest; + let (op, key) = (*op, *key % 4); + match op % 3 { + 0 => list.push_back(key, Arc::clone(&held)), + 1 => out.push(list.remove(key).map_or(0xff, |(_, v)| *v)), + _ => out.push(list.to_front(key) as u8), + } + out.push(Arc::strong_count(&held) as u8); + } + } + out.push(Arc::strong_count(&held) as u8); +} + +#[derive(Clone, Copy)] +struct Pair { + a: u32, + b: u32, +} + +/// Pointers to numbers, to a field, and to a whole struct: read, written, swapped, +/// replaced, compared, and null. +pub fn places(data: &[u8], out: &mut Vec) { + let mut x = 0u32; + let mut y = 1u32; + let mut pair = Pair { a: 2, b: 3 }; + let px = &raw mut x; + let py = &raw mut y; + let pa = &raw mut pair.a; + let pp = &raw mut pair; + let mut held: *const u32 = ptr::null(); + for &byte in data { + let v = u32::from(byte); + unsafe { + match byte % 9 { + 0 => *px += v, + 1 => py.write(v), + 2 => ptr::swap(px, py), + 3 => out.push(ptr::replace(pa, v) as u8), + 4 => (*pp).b = v, + 5 => { + let old = pp.read(); + pp.write(Pair { a: old.b, b: old.a }); + } + 6 => held = if byte & 1 == 0 { px } else { pa }, + 7 => out.push(held.is_null() as u8 | ((pp == &raw mut pair) as u8) << 1), + _ => out.push(if held.is_null() { 0xff } else { *held as u8 }), + } + } + } + out.extend_from_slice(&[x as u8, y as u8, pair.a as u8, pair.b as u8]); +} + +/// Unchecked indexing, a boxed number behind a pointer, a `MaybeUninit` number, and a +/// value dropped in place. +pub fn unchecked(data: &[u8], out: &mut Vec) { + let mut counts = [0u32; 4]; + let boxed = Box::into_raw(Box::new(0u32)); + let mut slot = MaybeUninit::::uninit(); + slot.write(7); + let held = Arc::new(0u8); + let mut dropped = MaybeUninit::new(Arc::clone(&held)); + for &byte in data { + let at = usize::from(byte % 4); + unsafe { + *counts.get_unchecked_mut(at) += 1; + *boxed += u32::from(byte); + *slot.as_mut_ptr() ^= u32::from(byte); + out.push(*counts.get_unchecked(at) as u8); + } + } + unsafe { + out.push(*Box::from_raw(boxed) as u8); + out.push(slot.assume_init_read() as u8); + out.push(Arc::strong_count(&held) as u8); + ptr::drop_in_place(dropped.as_mut_ptr()); + } + out.push(Arc::strong_count(&held) as u8); +} + +#[derive(Clone, Copy)] +struct Inner { + a: u32, + b: u32, +} + +struct Outer { + inner: Inner, + pair: (u32, u32), + n: u32, +} + +/// Pointers to the parts of a struct kept as objects: initialized one by one from nothing, +/// written through the struct's pointer and their own, and read out before a write. +pub fn nested(data: &[u8], out: &mut Vec) { + let mut slot = MaybeUninit::::uninit(); + let p = slot.as_mut_ptr(); + let (inner, pair) = unsafe { (&raw mut (*p).inner, &raw mut (*p).pair) }; + unsafe { + inner.write(Inner { a: 1, b: 2 }); + pair.write((3, 4)); + (&raw mut (*p).n).write(5); + } + for &byte in data { + let v = u32::from(byte); + unsafe { + match byte % 6 { + 0 => (*p).inner.a = v, + 1 => inner.write(Inner { a: v, b: (*inner).a }), + 2 => (*pair).1 += v, + 3 => { + let old = p.read(); + (*inner).b = v; + out.push(old.inner.b as u8); + } + 4 => { + let old = pair.read(); + (*p).pair.0 = v; + out.push(old.0 as u8); + } + _ => out.push((*inner).a as u8 ^ (*pair).0 as u8), + } + } + } + let outer = unsafe { slot.assume_init() }; + let Outer { inner, pair, n } = outer; + out.extend_from_slice(&[inner.a as u8, inner.b as u8, pair.0 as u8, pair.1 as u8, n as u8]); +} + +#[derive(Clone, Copy)] +union Halves { + a: [u32; 2], + b: [u32; 2], +} + +/// Copies of a `MaybeUninit` and of a union holding arrays, each changed after, and values +/// read out through pointers before what they point at changes. +pub fn copies(data: &[u8], out: &mut Vec) { + for &byte in data { + let v = u32::from(byte); + let mut x = MaybeUninit::new([v; 4]); + let y = x; + x.write([v ^ 1; 4]); + let h = Halves { a: [v, v + 1] }; + let mut g = h; + unsafe { g.b[0] = v + 2 }; + let mut held = Some(Pair { a: v, b: 0 }); + let p = &raw mut held; + let old = unsafe { p.read() }; + if let Some(pair) = unsafe { &mut *p } { + pair.a = v + 3; + } + let (before, after) = snapshot(v); + unsafe { + out.extend_from_slice(&[ + y.assume_init()[0] as u8, + x.assume_init()[0] as u8, + h.a[0] as u8, + g.a[0] as u8, + old.map_or(0, |o| o.a as u8), + held.map_or(0, |o| o.a as u8), + before, + after, + ]); + } + } +} + +/// What a pointer to a tuple holding an array reads, and what the tuple holds after. +fn snapshot(v: u32) -> (u8, u8) { + let mut nest = ([v, 1u32], 0u8); + let q = &raw mut nest; + let before = unsafe { q.read() }; + nest.0[0] = v + 4; + (before.0[0] as u8, nest.0[0] as u8) +} + +/// Null and dangling pointers to values JS keeps as `undefined`: an `Option` read through +/// `as_ref`, and `()` written through a dangling pointer; and values of no bytes read +/// through dangling pointers. +pub fn sentinels(data: &[u8], out: &mut Vec) { + for &byte in data { + let some = Some(u32::from(byte)).filter(|v| v % 2 == 0); + let p = match byte % 3 { + 0 => ptr::null(), + _ => &raw const some, + }; + let unit = NonNull::<()>::dangling().as_ptr(); + unsafe { + unit.write(()); + *unit = (); + out.push(match p.as_ref() { + None => 0, + Some(None) => 1, + Some(Some(v)) => 2 + (*v % 100) as u8, + }); + out.push(p.as_ref().is_none() as u8); + let units = NonNull::<[(); 2]>::dangling(); + out.push(units.as_ptr().read().into_iter().count() as u8); + out.push(units.as_ref().len() as u8); + } + } +} diff --git a/ts/fixtures/reject/census.md b/ts/fixtures/reject/census.md index 07afe5b58..4a20bb109 100644 --- a/ts/fixtures/reject/census.md +++ b/ts/fixtures/reject/census.md @@ -1,14 +1,14 @@ # Census of `reject-fixture` -- Refusals: 82 -- Function instances: 93 +- Refusals: 95 +- Function instances: 104 - Generic functions no instance reached, lowered over their type parameters: 7 ## By construct | Count | Construct | First | | ---: | --- | --- | -| 13 | unsupported call to `_` | fixtures/reject/src/lib.rs:21:4 | +| 16 | unsupported call to `_` | fixtures/reject/src/lib.rs:21:4 | | 4 | a variant of `_`, which is not supported: either is the same JS string | fixtures/reject/src/lib.rs:382:2 | | 4 | an assignment through a reference to `_` | fixtures/reject/src/lib.rs:101:2 | | 3 | a copy of a closure that changes what it captured | fixtures/reject/src/lib.rs:257:14 | @@ -40,12 +40,19 @@ | 1 | a `zip` of `_` and `_`, which std reads by index, of items that drop, which is not supported yet | fixtures/reject/src/lib.rs:221:30 | | 1 | a `zip` of `_`, whose closures std calls one item further through `next` than through `fold` | fixtures/reject/src/lib.rs:172:51 | | 1 | a cast from `_` to `_` | fixtures/reject/src/lib.rs:96:2 | +| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | fixtures/reject/src/lib.rs:577:27 | | 1 | a coercion to `_` | fixtures/reject/src/lib.rs:341:2 | | 1 | a constant of type `_` | fixtures/reject/src/lib.rs:80:2 | +| 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | fixtures/reject/src/lib.rs:662:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | fixtures/reject/src/lib.rs:110:34 | | 1 | a mutable borrow of a subslice of `_`, which JS would copy, having no views | fixtures/reject/src/lib.rs:226:13 | | 1 | a mutable reference into a `Result`'s payload, passed on or kept | fixtures/reject/src/lib.rs:413:12 | +| 1 | a pointer's address, which JS has no counterpart of | fixtures/reject/src/lib.rs:582:17 | | 1 | a printable type must implement `Display` or `Debug` | fixtures/reject/src/lib.rs:92:1 | +| 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | fixtures/reject/src/lib.rs:599:10 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | fixtures/reject/src/lib.rs:630:10 | +| 1 | a raw pointer to `_` in a variable assigned again while it may point there | fixtures/reject/src/lib.rs:638:10 | +| 1 | a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet | fixtures/reject/src/lib.rs:646:10 | | 1 | a scalar constant of type `_` | fixtures/reject/src/lib.rs:8:2 | | 1 | a trait object of `_`, which is not supported yet | fixtures/reject/src/lib.rs:496:26 | | 1 | a zero-sized constant of type `_` | fixtures/reject/src/lib.rs:62:2 | @@ -53,10 +60,13 @@ | 1 | arithmetic on `_` | fixtures/reject/src/lib.rs:8:2 | | 1 | collecting `_`, whose `None` is `rt.None`, which is not supported yet | fixtures/reject/src/lib.rs:36:43 | | 1 | comparing `_` as a key, which is not supported yet | fixtures/reject/src/lib.rs:268:52 | +| 1 | comparing pointers to `()`, which has no bytes and so no address of its own | fixtures/reject/src/lib.rs:653:2 | +| 1 | comparing pointers to `_`, each a place JS makes of its own | fixtures/reject/src/lib.rs:618:2 | | 1 | constructing `_` | fixtures/reject/src/lib.rs:400:53 | | 1 | hashing `_` as a key, which is not supported yet | fixtures/reject/src/lib.rs:268:52 | | 1 | hashing `_` through `_`'s own `Hash::hash_slice`, which is not supported yet | fixtures/reject/src/lib.rs:360:16 | | 1 | passing `_`, which drops, to `_`, which is not supported yet | fixtures/reject/src/lib.rs:377:4 | +| 1 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | fixtures/reject/src/lib.rs:604:1 | ## By API @@ -69,8 +79,10 @@ The type or module each refused type or path belongs to. | 6 | `f32` | fixtures/reject/src/lib.rs:7:14 | | 4 | `Iterator` | fixtures/reject/src/lib.rs:41:42 | | 4 | `std::sync::Arc` | fixtures/reject/src/lib.rs:101:2 | -| 3 | `std::vec::Vec` | fixtures/reject/src/lib.rs:268:52 | -| 3 | `u8` | fixtures/reject/src/lib.rs:80:2 | +| 4 | `std::vec::Vec` | fixtures/reject/src/lib.rs:268:52 | +| 4 | `u32` | fixtures/reject/src/lib.rs:226:13 | +| 4 | `u8` | fixtures/reject/src/lib.rs:80:2 | +| 3 | `(u32, u32)` | fixtures/reject/src/lib.rs:630:10 | | 3 | `{closure}` | fixtures/reject/src/lib.rs:369:13 | | 2 | `HashMap` | fixtures/reject/src/lib.rs:263:6 | | 2 | `Meters` | fixtures/reject/src/lib.rs:85:2 | @@ -85,14 +97,18 @@ The type or module each refused type or path belongs to. | 2 | `std::ops::Range` | fixtures/reject/src/lib.rs:180:8 | | 2 | `std::pin::Pin` | fixtures/reject/src/lib.rs:116:2 | | 2 | `std::slice` | fixtures/reject/src/lib.rs:21:4 | +| 2 | `std::std` | fixtures/reject/src/lib.rs:589:11 | | 2 | `std::vec::IntoIter` | fixtures/reject/src/lib.rs:444:17 | | 1 | `Batched` | fixtures/reject/src/lib.rs:360:16 | +| 1 | `Bits` | fixtures/reject/src/lib.rs:604:1 | | 1 | `DoubleEndedIterator` | fixtures/reject/src/lib.rs:159:1 | | 1 | `Entry` | fixtures/reject/src/lib.rs:369:13 | | 1 | `FusedIterator` | fixtures/reject/src/lib.rs:213:1 | | 1 | `Hasher` | fixtures/reject/src/lib.rs:294:9 | | 1 | `Maybe` | fixtures/reject/src/lib.rs:31:2 | | 1 | `Noisy` | fixtures/reject/src/lib.rs:53:13 | +| 1 | `Only` | fixtures/reject/src/lib.rs:662:11 | +| 1 | `Span2` | fixtures/reject/src/lib.rs:630:10 | | 1 | `T \| undefined` | fixtures/reject/src/lib.rs:31:2 | | 1 | `Tail` | fixtures/reject/src/lib.rs:506:30 | | 1 | `Waiting` | fixtures/reject/src/lib.rs:517:2 | @@ -109,13 +125,13 @@ The type or module each refused type or path belongs to. | 1 | `std::iter::Rev` | fixtures/reject/src/lib.rs:166:14 | | 1 | `std::option::Option` | fixtures/reject/src/lib.rs:420:2 | | 1 | `std::slice::Iter` | fixtures/reject/src/lib.rs:126:11 | +| 1 | `std::std::Layout` | fixtures/reject/src/lib.rs:587:15 | | 1 | `std::string::String` | fixtures/reject/src/lib.rs:74:10 | | 1 | `std::sync::MutexGuard` | fixtures/reject/src/lib.rs:121:2 | | 1 | `std::sync::Mutex` | fixtures/reject/src/lib.rs:536:12 | | 1 | `std::sync::Weak` | fixtures/reject/src/lib.rs:470:40 | | 1 | `std::task::Poll` | fixtures/reject/src/lib.rs:110:34 | | 1 | `str` | fixtures/reject/src/lib.rs:377:4 | -| 1 | `u32` | fixtures/reject/src/lib.rs:226:13 | | 1 | `u64` | fixtures/reject/src/lib.rs:96:2 | | 1 | `value` | fixtures/reject/src/lib.rs:31:2 | | 1 | `{async closure}` | fixtures/reject/src/lib.rs:62:2 | @@ -150,12 +166,18 @@ The type or module each refused type or path belongs to. | 1 | a `zip` of `_` and `_`, which std reads by index, of items that drop, which is not supported yet | `std::iter::Map`, `std::ops::Range` | fixtures/reject/src/lib.rs:221:30 | | 1 | a `zip` of `_`, whose closures std calls one item further through `next` than through `fold` | `std::iter::Chain` | fixtures/reject/src/lib.rs:172:51 | | 1 | a cast from `_` to `_` | `u64`, `f32` | fixtures/reject/src/lib.rs:96:2 | +| 1 | a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another | `u32`, `u8` | fixtures/reject/src/lib.rs:577:27 | | 1 | a coercion to `_` | `std::sync::Arc` | fixtures/reject/src/lib.rs:341:2 | | 1 | a coercion to `_`, which is not supported yet | `&Tail` | fixtures/reject/src/lib.rs:506:30 | | 1 | a coercion to `_`, which is not supported yet | `std::sync::Weak` | fixtures/reject/src/lib.rs:470:40 | | 1 | a constant of type `_` | `&u8` | fixtures/reject/src/lib.rs:80:2 | +| 1 | a dangling pointer to `_`, a value of no bytes made of more than structs and tuples | `Only` | fixtures/reject/src/lib.rs:662:11 | | 1 | a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is | `std::task::Poll` | fixtures/reject/src/lib.rs:110:34 | | 1 | a mutable borrow of a subslice of `_`, which JS would copy, having no views | `u32` | fixtures/reject/src/lib.rs:226:13 | +| 1 | a raw pointer to `_` behind a reference, which is the object and not where it is kept | `std::vec::Vec` | fixtures/reject/src/lib.rs:599:10 | +| 1 | a raw pointer to `_` in `_`, which the crate may put another object in place of | `(u32, u32)`, `Span2` | fixtures/reject/src/lib.rs:630:10 | +| 1 | a raw pointer to `_` in a variable assigned again while it may point there | `(u32, u32)` | fixtures/reject/src/lib.rs:638:10 | +| 1 | a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet | `(u32, u32)` | fixtures/reject/src/lib.rs:646:10 | | 1 | a scalar constant of type `_` | `f32` | fixtures/reject/src/lib.rs:8:2 | | 1 | a trait object of `_`, which is not supported yet | `[u8; 4]` | fixtures/reject/src/lib.rs:496:26 | | 1 | a zero-sized constant of type `_` | `{async closure}` | fixtures/reject/src/lib.rs:62:2 | @@ -167,6 +189,7 @@ The type or module each refused type or path belongs to. | 1 | arithmetic on `_` | `f32` | fixtures/reject/src/lib.rs:8:2 | | 1 | collecting `_`, whose `None` is `rt.None`, which is not supported yet | `Option` | fixtures/reject/src/lib.rs:36:43 | | 1 | comparing `_` as a key, which is not supported yet | `std::vec::Vec` | fixtures/reject/src/lib.rs:268:52 | +| 1 | comparing pointers to `_`, each a place JS makes of its own | `u32` | fixtures/reject/src/lib.rs:618:2 | | 1 | constructing `_` | `std::borrow::Cow` | fixtures/reject/src/lib.rs:400:53 | | 1 | constructing `_` from another crate | `std::iter::Map` | fixtures/reject/src/lib.rs:145:23 | | 1 | constructing `_` from another crate | `std::iter::Rev` | fixtures/reject/src/lib.rs:166:14 | @@ -178,6 +201,7 @@ The type or module each refused type or path belongs to. | 1 | passing `_`, a closure that owns a value that drops, to `_`, which is not supported yet | `{closure}`, `Iterator::for_each` | fixtures/reject/src/lib.rs:370:16 | | 1 | passing `_`, which drops, to `_`, which is not supported yet | `{closure}`, `str::contains` | fixtures/reject/src/lib.rs:377:4 | | 1 | replacing the value behind a reference to `_` | `[u16; 1]` | fixtures/reject/src/lib.rs:240:22 | +| 1 | union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart | `Bits`, `u32` | fixtures/reject/src/lib.rs:604:1 | | 1 | unsupported call to `_` | `::fmt` | fixtures/reject/src/lib.rs:454:2 | | 1 | unsupported call to `_` | `::eq` | fixtures/reject/src/lib.rs:560:2 | | 1 | unsupported call to `_` | `::deref` | fixtures/reject/src/lib.rs:394:2 | @@ -189,10 +213,13 @@ The type or module each refused type or path belongs to. | 1 | unsupported call to `_` | `std::ops::Range::contains` | fixtures/reject/src/lib.rs:180:8 | | 1 | unsupported call to `_` | `std::slice::copy_within` | fixtures/reject/src/lib.rs:336:4 | | 1 | unsupported call to `_` | `std::slice::sort` | fixtures/reject/src/lib.rs:21:4 | +| 1 | unsupported call to `_` | `std::std::Layout::new` | fixtures/reject/src/lib.rs:587:15 | +| 1 | unsupported call to `_` | `std::std::alloc` | fixtures/reject/src/lib.rs:589:11 | +| 1 | unsupported call to `_` | `std::std::dealloc` | fixtures/reject/src/lib.rs:592:3 | ## Spans -
unsupported call to `_` (13) +
unsupported call to `_` (16) - fixtures/reject/src/lib.rs:21:4: unsupported call to `std::slice::::sort` - fixtures/reject/src/lib.rs:74:10: unsupported call to `::write_fmt` @@ -207,6 +234,9 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:454:2: unsupported call to `::fmt` - fixtures/reject/src/lib.rs:560:2: unsupported call to `::eq` - fixtures/reject/src/lib.rs:560:17: unsupported call to `std::any::TypeId::of::` +- fixtures/reject/src/lib.rs:587:15: unsupported call to `std::alloc::Layout::new::` +- fixtures/reject/src/lib.rs:589:11: unsupported call to `std::alloc::alloc` +- fixtures/reject/src/lib.rs:592:3: unsupported call to `std::alloc::dealloc`
@@ -417,6 +447,12 @@ The type or module each refused type or path belongs to.
+
a cast from a pointer to `_` to one to `_`, which reads the bytes of one type as another (1) + +- fixtures/reject/src/lib.rs:577:27: a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another + +
+
a coercion to `_` (1) - fixtures/reject/src/lib.rs:341:2: a coercion to `std::sync::Arc<[u8]>` @@ -429,6 +465,12 @@ The type or module each refused type or path belongs to.
+
a dangling pointer to `_`, a value of no bytes made of more than structs and tuples (1) + +- fixtures/reject/src/lib.rs:662:11: a dangling pointer to `Only`, a value of no bytes made of more than structs and tuples + +
+
a future of `_`, which can be `rt.Pending`, as the future's own `Pending` is (1) - fixtures/reject/src/lib.rs:110:34: a future of `std::task::Poll`, which can be `rt.Pending`, as the future's own `Pending` is @@ -447,12 +489,42 @@ The type or module each refused type or path belongs to.
+
a pointer's address, which JS has no counterpart of (1) + +- fixtures/reject/src/lib.rs:582:17: a pointer's address, which JS has no counterpart of + +
+
a printable type must implement `Display` or `Debug` (1) - fixtures/reject/src/lib.rs:92:1: a printable type must implement `Display` or `Debug`
+
a raw pointer to `_` behind a reference, which is the object and not where it is kept (1) + +- fixtures/reject/src/lib.rs:599:10: a raw pointer to `std::vec::Vec` behind a reference, which is the object and not where it is kept + +
+ +
a raw pointer to `_` in `_`, which the crate may put another object in place of (1) + +- fixtures/reject/src/lib.rs:630:10: a raw pointer to `(u32, u32)` in `Span2`, which the crate may put another object in place of + +
+ +
a raw pointer to `_` in a variable assigned again while it may point there (1) + +- fixtures/reject/src/lib.rs:638:10: a raw pointer to `(u32, u32)` in a variable assigned again while it may point there + +
+ +
a raw pointer to `_` in an element, a payload, or a union field, which is not supported yet (1) + +- fixtures/reject/src/lib.rs:646:10: a raw pointer to `(u32, u32)` in an element, a payload, or a union field, which is not supported yet + +
+
a scalar constant of type `_` (1) - fixtures/reject/src/lib.rs:8:2: a scalar constant of type `f32` @@ -495,6 +567,18 @@ The type or module each refused type or path belongs to.
+
comparing pointers to `()`, which has no bytes and so no address of its own (1) + +- fixtures/reject/src/lib.rs:653:2: comparing pointers to `()`, which has no bytes and so no address of its own + +
+ +
comparing pointers to `_`, each a place JS makes of its own (1) + +- fixtures/reject/src/lib.rs:618:2: comparing pointers to `u32`, each a place JS makes of its own + +
+
constructing `_` (1) - fixtures/reject/src/lib.rs:400:53: constructing `std::borrow::Cow<'_, std::sync::Arc>` @@ -518,3 +602,9 @@ The type or module each refused type or path belongs to. - fixtures/reject/src/lib.rs:377:4: passing `{closure@fixtures/reject/src/lib.rs:377:13: 377:27}`, which drops, to `str::contains`, which is not supported yet
+ +
union `_`, whose fields overlap in `_` and `f64`, which JS keeps apart (1) + +- fixtures/reject/src/lib.rs:604:1: union `Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart + +
diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 0123f025d..968bf61ca 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -74,6 +74,19 @@ an upcast from `dyn Keyed` to `dyn std::any::Any`, which is not supported yet at `std::any::Any::type_id` of `dyn std::any::Any`, which is not supported yet at fixtures/reject/src/lib.rs:560:4 unsupported call to `std::any::TypeId::of::` at fixtures/reject/src/lib.rs:560:17 unsupported call to `::eq` at fixtures/reject/src/lib.rs:560:2 +a cast from a pointer to `u32` to one to `u8`, which reads the bytes of one type as another at fixtures/reject/src/lib.rs:577:27 +a pointer's address, which JS has no counterpart of at fixtures/reject/src/lib.rs:582:17 +unsupported call to `std::alloc::Layout::new::` at fixtures/reject/src/lib.rs:587:15 +unsupported call to `std::alloc::alloc` at fixtures/reject/src/lib.rs:589:11 +unsupported call to `std::alloc::dealloc` at fixtures/reject/src/lib.rs:592:3 +a raw pointer to `std::vec::Vec` behind a reference, which is the object and not where it is kept at fixtures/reject/src/lib.rs:599:10 +union `Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart at fixtures/reject/src/lib.rs:610:10 +comparing pointers to `u32`, each a place JS makes of its own at fixtures/reject/src/lib.rs:618:2 +a raw pointer to `(u32, u32)` in `Span2`, which the crate may put another object in place of at fixtures/reject/src/lib.rs:630:10 +a raw pointer to `(u32, u32)` in a variable assigned again while it may point there at fixtures/reject/src/lib.rs:638:10 +a raw pointer to `(u32, u32)` in an element, a payload, or a union field, which is not supported yet at fixtures/reject/src/lib.rs:646:10 +comparing pointers to `()`, which has no bytes and so no address of its own at fixtures/reject/src/lib.rs:653:2 +a dangling pointer to `Only`, a value of no bytes made of more than structs and tuples at fixtures/reject/src/lib.rs:662:11 overriding `Iterator::fold`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:139:2 an iterator of the crate implementing `DoubleEndedIterator`, which is not supported yet at fixtures/reject/src/lib.rs:159:1 overriding `Iterator::size_hint`, which std's adaptors call in place of `next` or beside it at fixtures/reject/src/lib.rs:193:2 diff --git a/ts/fixtures/reject/src/lib.rs b/ts/fixtures/reject/src/lib.rs index e77c5c397..90f1b0f9e 100644 --- a/ts/fixtures/reject/src/lib.rs +++ b/ts/fixtures/reject/src/lib.rs @@ -571,3 +571,93 @@ pub fn source_reading(s: &S) -> u8 { let r: &dyn Reading = s.item(); r.read() } + +/// A pointer read as another type's bytes. +pub fn reinterpreted(n: u32) -> u8 { + unsafe { *(&raw const n).cast::() } +} + +/// A pointer's address, as an integer. +pub fn addressed(n: u32) -> usize { + (&raw const n).addr() +} + +/// Memory from the allocator, which has no JS object to point at. +pub fn allocated() -> u8 { + let layout = std::alloc::Layout::new::(); + unsafe { + let p = std::alloc::alloc(layout); + *p = 1; + let n = *p; + std::alloc::dealloc(p, layout); + n + } +} + +/// A pointer to a `Vec`, whose JS array is its heap, not where Rust keeps the `Vec`. +pub fn vec_pointer(v: &mut Vec) -> usize { + let p = &raw const *v; + unsafe { (*p).len() } +} + +/// A union whose fields overlap in values JS keeps apart. +pub union Bits { + pub int: u32, + pub float: f64, +} + +pub fn bits(n: u32) -> u32 { + let b = Bits { int: n }; + unsafe { b.int } +} + +/// Two `rt.Place`s of one number, which are two objects. +pub fn places_compared(n: &mut u32) -> bool { + let a = &raw mut *n; + let b = &raw mut *n; + a == b +} + +pub struct Span2 { + pub start: (u32, u32), + pub len: u32, +} + +/// A pointer to a part kept as an object, which a write of the whole part elsewhere +/// replaces with another object. +pub fn part_replaced(n: u32) -> u32 { + let mut s = Span2 { start: (n, n), len: n }; + let p = &raw mut s.start; + s.start = (0, 0); + unsafe { (*p).0 + s.len } +} + +/// A pointer to a variable's object, which an assignment of the whole variable replaces. +pub fn variable_reassigned(n: u32) -> u32 { + let mut pair = (n, n); + let p = &raw mut pair; + pair = (0, 0); + unsafe { (*p).0 + pair.1 } +} + +/// A pointer to an element kept as an object. +pub fn element_pointer(n: u32) -> u32 { + let mut pairs = [(n, n); 2]; + let p = &raw mut pairs[1]; + unsafe { (*p).0 } +} + +/// Two pointers to values of no bytes, which Rust may put anywhere. +pub fn units_compared() -> bool { + let unit = || std::ptr::NonNull::<()>::dangling().as_ptr(); + std::ptr::eq(unit(), unit()) +} + +pub enum Only { + One, +} + +/// A dangling pointer to an enum of no bytes, whose value is its variant's tag. +pub fn dangling_enum() -> bool { + unsafe { std::ptr::NonNull::::dangling().as_ptr().as_ref().is_some() } +} diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 4551be517..5d9e78c3f 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -64,5 +64,16 @@ roots = [ "rebound_reference", "any_upcast", "any_type_id", + "reinterpreted", + "addressed", + "allocated", + "vec_pointer", + "bits", + "places_compared", + "part_replaced", + "variable_reassigned", + "element_pointer", + "units_compared", + "dangling_enum", ] printable = ["Meters"] diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index a64f9f669..583628cbd 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -95,6 +95,23 @@ export function place(get: () => T, set: (value: T) => void): Place { return new Accessed(get, set); } +// Raw pointers + +/** A null raw pointer, of any type: nothing reads through it. */ +export const NULL: never = null as never; + +/** A dangling raw pointer, `NonNull::dangling`, of any type: nothing reads through it. */ +export const DANGLING: never = /* @__PURE__ */ Object.freeze({}) as never; + +/** + * `ptr::write` of `value` through a pointer to an object: `value` copied into it, one + * object deep, so every pointer to it sees the write. + */ +export function ptrWrite(p: T, value: T): void { + if (ArrayBuffer.isView(p)) (p as unknown as Uint8Array).set(value as unknown as Uint8Array); + else Object.assign(p, value); +} + // Bindings /** diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 2c45f483f..4c74a89bb 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -219,6 +219,7 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { let ts_ty = self.t.ty(ty, span); Expr::As(Box::new(Expr::Str(adt.variant(variant).name.to_string())), ts_ty) } + AdtRepr::Slots(_) => self.err(span, format!("constructing union `{}`", tcx.def_path_str(adt_id))), AdtRepr::Union => { let mut props = vec![( Key::Name("tag".into()), diff --git a/ts/src/lower/consts.rs b/ts/src/lower/consts.rs index 7f34e2b5a..7bbfa748a 100644 --- a/ts/src/lower/consts.rs +++ b/ts/src/lower/consts.rs @@ -158,6 +158,7 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { let ts_ty = self.t.ty(ty, span); Expr::As(Box::new(Expr::Str(def.name.to_string())), ts_ty) } + AdtRepr::Slots(_) => self.err(span, format!("a constant of type `{ty}`")), AdtRepr::Union => { let mut props = vec![(Key::Name("tag".into()), Expr::Str(def.name.to_string()))]; for (i, (field, value)) in def.fields.iter().zip(values).enumerate() { diff --git a/ts/src/lower/copy.rs b/ts/src/lower/copy.rs index 396014909..2f69a370c 100644 --- a/ts/src/lower/copy.rs +++ b/ts/src/lower/copy.rs @@ -40,6 +40,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { (false, args.types().collect()) } Some(Std::Bound) => (true, args.types().collect()), + Some(Std::MaybeUninit) => return self.changes(args.type_at(0)), Some(_) => return false, None if self.own(adt.did()) && adt.all_fields().next().is_some() => { (true, adt.all_fields().map(|f| self.field_ty(f.did, args)).collect()) @@ -48,7 +49,23 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }, _ => return false, }; - object && self.writes.written(self.tcx, ty) || parts.into_iter().any(|t| self.changes(t)) + object && self.writes.into.contains(self.tcx, ty) || parts.into_iter().any(|t| self.changes(t)) + } + + /// Whether the crate may give a part of a `ty` kept as a JS object another object: by + /// a write of the part, or of the whole value one object deep. + pub fn replaced(&self, ty: Ty<'tcx>) -> bool { + self.writes.replaced.contains(self.tcx, ty) + } + + /// `value`, a `ty` read out of where it is kept, as a value of its own, as Rust's read + /// copies its bytes: the JS objects it holds by value copied where the crate's code + /// writes into them in place. + pub fn snapshot(&mut self, value: Expr, ty: Ty<'tcx>, span: Span) -> Expr { + match self.changes(ty).then(|| self.shim(ty, span)).flatten() { + Some(copy) => copy.call(vec![value]), + None => value, + } } /// `value`, copied if a `ty` is. @@ -64,9 +81,19 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if !self.needs_copy(ty) { return None; } + self.shim(ty, span) + } + + /// The function copying a `ty`, made the first time. + fn shim(&mut self, ty: Ty<'tcx>, span: Span) -> Option { // An array's copy is the same at every length. + let tcx = self.tcx; let ty = match ty.kind() { - ty::Array(elem, _) => Ty::new_array(self.tcx, *elem, 0), + ty::Array(elem, _) => Ty::new_array(tcx, *elem, 0), + ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::MaybeUninit) => match args.type_at(0).kind() { + ty::Array(elem, _) => Ty::new_maybe_uninit(tcx, Ty::new_array(tcx, *elem, 0)), + _ => ty, + }, _ => ty, }; if let Some(&sym) = self.copy_shims.get(&ty) { @@ -106,30 +133,52 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// The body of the function copying `v`, a `ty`. fn copy_body(&mut self, ty: Ty<'tcx>, span: Span) -> Vec { + self.copy_parts(ty, false, span) + } + + /// The body of the function copying `v`, a `ty`, or a `MaybeUninit` of one if `uninit`: + /// then any of its parts kept as objects may hold nothing yet. + fn copy_parts(&mut self, ty: Ty<'tcx>, uninit: bool, span: Span) -> Vec { let v = || Expr::ident("v"); let ret = |e: Expr| vec![StmtKind::Return(Some(e.typed())).at(None)]; let tcx = self.tcx; + let part = |t: Ty<'tcx>| match uninit { + true => Ty::new_maybe_uninit(tcx, t), + false => t, + }; match ty.kind() { ty::Tuple(tys) => { let parts = tys .iter() .enumerate() - .map(|(i, t)| self.copied(v().index(Expr::lit(i.to_string())), t, span)) + .map(|(i, t)| self.snapshot(v().index(Expr::lit(i.to_string())), part(t), span)) .collect(); ret(Expr::Array(parts)) } - ty::Array(elem, _) => ret(match self.copy_fn(*elem, span) { - Some(copy) => v().member("map").call(vec![copy]), - None => v().member("slice").call(Vec::new()), - }), + ty::Array(elem, _) => ret( + match self.changes(*elem).then(|| self.shim(part(*elem), span)).flatten() { + Some(copy) => v().member("map").call(vec![copy]), + None => v().member("slice").call(Vec::new()), + }, + ), ty::Adt(adt, args) => match Std::of(tcx, adt.did()) { + Some(Std::MaybeUninit) => { + let none = StmtKind::If { + cond: v().binary("===", Expr::undefined()), + then: ret(v()), + els: Vec::new(), + }; + let mut body = vec![none.at(None)]; + body.extend(self.copy_parts(args.type_at(0), true, span)); + body + } Some(Std::Option) => ret(self.copy_unless(self.none(args.type_at(0)), args.type_at(0), span)), Some(Std::Poll) => ret(self.copy_unless(self.pending(args.type_at(0)), args.type_at(0), span)), Some(Std::Result) => ret(self.copy_result(args.type_at(0), args.type_at(1), span)), - Some(Std::Reverse) => ret(self.copied(v(), args.type_at(0), span)), + Some(Std::Reverse) => ret(self.snapshot(v(), args.type_at(0), span)), Some(Std::Bound) => { let ts_ty = self.ty(ty, span); - let held = self.copied(v().member("_0"), args.type_at(0), span); + let held = self.snapshot(v().member("_0"), args.type_at(0), span); let copy = Expr::Object(vec![ (Key::Name("tag".into()), v().member("tag")), (Key::Name("_0".into()), held), @@ -156,7 +205,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let values = (variant.fields.iter().zip(&fields)) .map(|(field, &id)| { let field_ty = self.field_ty(field.did, args); - self.copied(v().field(id), field_ty, span) + self.snapshot(v().field(id), part(field_ty), span) }) .collect(); ret(self.construct(info.sym, &ctor, &fields, values)) @@ -170,8 +219,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { for (i, field) in variant.fields.iter().enumerate() { let name = field_name(field.name.as_str(), i); let field_ty = self.field_ty(field.did, args); - props - .push((Key::Name(name.clone()), self.copied(v().member(name), field_ty, span))); + props.push(( + Key::Name(name.clone()), + self.snapshot(v().member(name), field_ty, span), + )); } body.push( StmtKind::If { @@ -186,6 +237,24 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { body.extend(ret(v())); body } + // A slot no field was written into yet holds nothing. + AdtRepr::Slots(_) => { + let (_, slots) = self.overlay(*adt, span).expect("a union is laid out where it is made"); + let ts_ty = self.ty(ty, span); + let slots = slots + .into_iter() + .map(|(name, slot_ty)| { + let slot_ty = self.instantiate(slot_ty, args); + let value = self.snapshot( + v().member(name.clone()), + Ty::new_maybe_uninit(tcx, slot_ty), + span, + ); + (name, value) + }) + .collect(); + ret(super::unions::made(slots, ts_ty)) + } AdtRepr::Tags => unreachable!("an enum without fields is shared"), } } @@ -199,8 +268,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let v = || Expr::ident("v"); Expr::Cond( Box::new(v().member("ok")), - Box::new(Expr::rt("Ok", vec![self.copied(v().member("value"), ok, span)])), - Box::new(Expr::rt("Err", vec![self.copied(v().member("error"), err, span)])), + Box::new(Expr::rt("Ok", vec![self.snapshot(v().member("value"), ok, span)])), + Box::new(Expr::rt("Err", vec![self.snapshot(v().member("error"), err, span)])), ) } @@ -210,16 +279,21 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Expr::Cond( Box::new(v().binary("===", none)), Box::new(v()), - Box::new(self.copied(v(), some, span)), + Box::new(self.snapshot(v(), some, span)), ) } /// The type of field `did` of an ADT with `args`. pub fn field_ty(&self, did: rustc_hir::def_id::DefId, args: ty::GenericArgsRef<'tcx>) -> Ty<'tcx> { - let field = EarlyBinder::bind(self.tcx, self.tcx.type_of(did).instantiate_identity().skip_norm_wip()); + self.instantiate(self.tcx.type_of(did).instantiate_identity().skip_norm_wip(), args) + } + + /// `ty`, over an item's own type parameters, with `args` for them. + pub fn instantiate(&self, ty: Ty<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Ty<'tcx> { + let ty = EarlyBinder::bind(self.tcx, ty); self.tcx - .try_instantiate_and_normalize_erasing_regions(args, self.typing_env_pub(), field) - .unwrap_or_else(|_| field.instantiate(self.tcx, args).skip_norm_wip()) + .try_instantiate_and_normalize_erasing_regions(args, self.typing_env_pub(), ty) + .unwrap_or_else(|_| ty.instantiate(self.tcx, args).skip_norm_wip()) } } @@ -232,12 +306,23 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// bounds let it be. #[derive(Default)] pub struct Writes<'tcx> { - /// Each type written without type parameters, an array as a slice of its element, - /// since the two share storage in JS. + pub into: Types<'tcx>, + /// The types a part of which, kept as a JS object, the code may give another object: + /// by a write of the part, as `p.x = Inner {..}`; by a write of the whole value, which + /// goes one object deep, as `*r = v` does; or by a call of a std function, as a + /// mutable reference to the type may do with any part of it. + pub replaced: Types<'tcx>, +} + +/// Types, each without type parameters or over those of the body naming it. +#[derive(Default)] +pub struct Types<'tcx> { + /// Each type without type parameters, an array as a slice of its element, since the + /// two share storage in JS. concrete: FxHashSet>, /// Each type over the type parameters of the body writing it, and that body. generic: FxIndexSet<(DefId, Ty<'tcx>)>, - /// What [`Writes::written`] found of each type it was asked about. + /// What [`Types::contains`] found of each type it was asked about. found: RefCell, bool>>, } @@ -277,7 +362,9 @@ impl<'tcx> Writes<'tcx> { } writes } +} +impl<'tcx> Types<'tcx> { fn insert(&mut self, tcx: TyCtxt<'tcx>, def_id: DefId, ty: Ty<'tcx>) { let ty = storage(tcx, ty); match ty.has_param() { @@ -286,9 +373,9 @@ impl<'tcx> Writes<'tcx> { } } - /// Whether the crate writes into a value of `ty` in place. Over type parameters, as a - /// census lowers some functions, it may. - fn written(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + /// Whether `ty` is one of them. Over type parameters, as a census lowers some + /// functions, it may be. + pub fn contains(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { let ty = tcx.erase_and_anonymize_regions(ty); let stored = storage(tcx, ty); if ty.has_param() || self.concrete.contains(&stored) { @@ -307,9 +394,15 @@ impl<'tcx> Writes<'tcx> { } } -/// `ty` as [`Writes`] keys it: without regions, and an array as a slice of its element. +/// `ty` as [`Types`] keys it: without regions, a `MaybeUninit` as what it holds, and an +/// array as a slice of its element. fn storage<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - let ty = tcx.erase_and_anonymize_regions(ty); + let mut ty = tcx.erase_and_anonymize_regions(ty); + while let ty::Adt(adt, args) = ty.kind() + && Std::of(tcx, adt.did()) == Some(Std::MaybeUninit) + { + ty = args.type_at(0); + } match ty.kind() { ty::Array(elem, _) => Ty::new_slice(tcx, *elem), _ => ty, @@ -400,20 +493,46 @@ struct BodyWrites<'w, 'm, 'tcx> { impl<'tcx> BodyWrites<'_, '_, 'tcx> { fn insert(&mut self, ty: Ty<'tcx>) { - self.writes.insert(self.tcx, self.def_id, ty); + self.writes.into.insert(self.tcx, self.def_id, ty); + } + + /// `ty` as replaced, if a part of it may be kept as a JS object: a write of the whole + /// goes one object deep, so it replaces only the parts it holds directly. + fn replace(&mut self, ty: Ty<'tcx>) { + if self.inline(ty) { + self.writes.replaced.insert(self.tcx, self.def_id, ty); + } + } + + /// Whether a `ty` may be kept as a JS object that a pointer to it is: a tuple, an + /// array, a struct or union of a crate that translates, or a `MaybeUninit` of one. + fn inline(&self, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Tuple(tys) => !tys.is_empty(), + ty::Array(..) | ty::Param(_) | ty::Alias(..) => true, + ty::Adt(adt, args) => match Std::of(self.tcx, adt.did()) { + Some(Std::MaybeUninit) => self.inline(args.type_at(0)), + Some(_) => false, + None => !adt.is_enum() && (self.own)(adt.did()), + }, + _ => false, + } } /// What each mutable reference or pointer a value of `ty` holds points at, where code - /// given the value can reach it. What a closure or a coroutine holds only its own body - /// reads, a function holds nothing, and what a trait object holds counted where it - /// became one. - fn pointees(&mut self, ty: Ty<'tcx>) { + /// given the value can reach it, written into, and if `replace`, replaced. What a + /// closure or a coroutine holds only its own body reads, a function holds nothing, + /// and what a trait object holds counted where it became one. + fn pointees(&mut self, ty: Ty<'tcx>, replace: bool) { let mut stack = vec![ty]; while let Some(ty) = stack.pop() { match *ty.kind() { ty::Ref(_, inner, mutability) | ty::RawPtr(inner, mutability) => { if mutability.is_mut() { self.insert(inner); + if replace { + self.replace(inner); + } } stack.push(inner); } @@ -439,14 +558,14 @@ impl<'tcx> BodyWrites<'_, '_, 'tcx> { impl<'tcx> Visitor<'tcx> for BodyWrites<'_, '_, 'tcx> { fn visit_assign(&mut self, place: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) { if place.as_local().is_none() { - self.pointees(place.ty(self.body, self.tcx).ty); + self.pointees(place.ty(self.body, self.tcx).ty, true); } // A `dyn Any` has no method that writes: a write through its downcast counts where it is. let object = |t: Ty<'tcx>| t.is_trait() && !super::dynamic::is_any_object(self.tcx, t); if let Rvalue::Cast(mir::CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize, _), op, to) = rvalue && to.walk().any(|arg| arg.as_type().is_some_and(object)) { - self.pointees(op.ty(self.body, self.tcx)); + self.pointees(op.ty(self.body, self.tcx), true); } self.super_assign(place, rvalue, location); } @@ -454,9 +573,9 @@ impl<'tcx> Visitor<'tcx> for BodyWrites<'_, '_, 'tcx> { fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) { match &terminator.kind { mir::TerminatorKind::Call { destination, .. } if destination.as_local().is_none() => { - self.pointees(destination.ty(self.body, self.tcx).ty) + self.pointees(destination.ty(self.body, self.tcx).ty, true) } - mir::TerminatorKind::Yield { value, .. } => self.pointees(value.ty(self.body, self.tcx)), + mir::TerminatorKind::Yield { value, .. } => self.pointees(value.ty(self.body, self.tcx), true), _ => {} } self.super_terminator(terminator, location); @@ -476,8 +595,10 @@ impl<'tcx> Visitor<'tcx> for BodyWrites<'_, '_, 'tcx> { .fn_sig(def_id) .instantiate(self.tcx, args.skip_binder()) .skip_norm_wip(); + // Those that point a pointer elsewhere or read through it replace nothing. + let replace = !super::pointer::keeps(self.tcx, def_id); for &input in sig.skip_binder().inputs() { - self.pointees(input); + self.pointees(input, replace); } } self.super_operand(operand, location); @@ -501,5 +622,27 @@ impl<'tcx> Visitor<'tcx> for BodyWrites<'_, '_, 'tcx> { { self.insert(place.ty(self.body, self.tcx).ty); } + // A whole part written gives what holds it another object there, and a whole value + // written through a pointer each of its parts, as it goes one object deep. + if !matches!( + kind, + MutatingUseContext::Store + | MutatingUseContext::Call + | MutatingUseContext::AsmOutput + | MutatingUseContext::Yield + ) { + return; + } + let ty = place.ty(self.body, self.tcx).ty; + match place.iter_projections().last() { + Some((_, ProjectionElem::Deref)) => self.replace(ty), + Some((base, ProjectionElem::Field(..))) if self.inline(ty) => { + let base = base.ty(self.body, self.tcx).ty; + if !base.is_union() { + self.replace(base); + } + } + _ => {} + } } } diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index f3ea876fc..f00d17dbb 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -101,8 +101,10 @@ pub enum Std { NonZero, /// A `format_args!` argument: a function writing its value, `rt.Fmt`. Argument, - /// Its pointee: what an async body passes its context in. + /// A raw pointer to its pointee, as `pointer` has it. NonNull, + /// What it holds, or nothing yet, as `pointer` has it. + MaybeUninit, /// No value: `never`. Infallible, /// `rt.HashMap`, and `rt.HashSet`, a map to `undefined`. @@ -214,6 +216,7 @@ impl Std { ["ops", "control_flow", "ControlFlow"] => Some(Self::ControlFlow), ["fmt", "rt", "Argument"] => Some(Self::Argument), ["ptr", "non_null", "NonNull"] => Some(Self::NonNull), + ["mem", "maybe_uninit", "MaybeUninit"] => Some(Self::MaybeUninit), ["future", "poll_fn", "PollFn"] => Some(Self::PollFn), ["future", "pending", "Pending"] => Some(Self::Pending), ["convert", "Infallible"] => Some(Self::Infallible), @@ -555,7 +558,10 @@ fn discards(owner: &str, name: &str) -> Option<&'static [usize]> { ("Option", "unwrap" | "expect") | ("Vec", "push") | ("Box", "new" | "pin") - | ("mem", "replace" | "take" | "swap") + | ("mem", "replace" | "take" | "swap" | "forget") + | ("ptr", "write" | "write_unaligned" | "write_volatile" | "replace") + | ("MaybeUninit", "new" | "write" | "assume_init") + | ("Box", "into_raw" | "leak" | "from_raw") | ("hint", "must_use") | ("From", "from") | ("Into", "into") @@ -589,6 +595,7 @@ fn takes_none(owner: &str, name: &str) -> bool { ) | ("DoubleEndedIterator", "next_back" | "nth_back") | ("dyn Any", "downcast_ref" | "downcast_mut") + | ("ptr", "as_ref" | "as_mut") ) } @@ -776,6 +783,8 @@ pub fn key(tcx: TyCtxt<'_>, def_id: DefId) -> Option<(String, String)> { ty::Bool => "bool".into(), ty::Char => "char".into(), ty::Int(_) | ty::Uint(_) | ty::Float(_) => self_ty.to_string(), + // `<*const T>::read` is `ptr::read`. + ty::RawPtr(..) => "ptr".into(), ty::Dynamic(preds, ..) => format!("dyn {}", tcx.item_name(preds.principal_def_id()?)), _ => return None, }; @@ -846,6 +855,10 @@ fn mapping<'tcx>( Ok(e) => return e, Err(args) => args, }; + let args = match super::pointer::call(b, call, &owner, &name, args, out) { + Ok(e) => return e, + Err(args) => args, + }; let args = match super::time::call(b, call, &owner, &name, args) { Ok(e) => return e, Err(args) => args, diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 587871117..351cab39b 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -306,11 +306,12 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { if let Some(err) = self.std_places(def_id, gargs, result, span) { return err; } + let takes_place = crate::lower::pointer::takes_place(tcx, def_id); let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); let inputs = tcx.instantiate_bound_regions_with_erased(sig).inputs().to_vec(); inputs .iter() - .map(|t| matches!(t.kind(), ty::Ref(_, _, rustc_ast::Mutability::Mut))) + .map(|t| !takes_place && matches!(t.kind(), ty::Ref(_, _, rustc_ast::Mutability::Mut))) .collect() } }; @@ -336,7 +337,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { args: gargs, span, }; - if crate::lower::collections::place_call(self.b, &call) { + if crate::lower::collections::place_call(self.b, &call) || crate::lower::pointer::lends(tcx, def_id) { return None; } let t = &*self.b.t; @@ -562,6 +563,16 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } // Indexing, whose result a `&mut` reads in place. ("Index" | "IndexMut", "index" | "index_mut") => self.index(&name, gargs, args, dest, span, out), + // Out of bounds is undefined, so the checked index's panic is one outcome of it. + ("slice", "get_unchecked" | "get_unchecked_mut") => { + let slice = ty::Ty::new_slice(tcx, targ(0)?); + let gargs = tcx.mk_args(&[slice.into(), targ(1)?.into()]); + let name = match name.as_str() { + "get_unchecked" => "index", + _ => "index_mut", + }; + self.index(name, gargs, args, dest, span, out) + } // A `&mut` to a value JS copies, such as through a `MutexGuard`, reads and writes // the place it borrows, as an index does. ("DerefMut", "deref_mut") diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index c179a5dbe..176efaa4c 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_middle::mir::interpret::{GlobalId, Scalar}; use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor}; use rustc_middle::mir::{self, ConstValue, Local, Operand, Place, PlaceTy, ProjectionElem}; -use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use super::{Env, Fx, Pending, Slot}; @@ -172,25 +172,34 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // Whether `e` is an `rt.ByteSlice` of the place's bytes: a `[u8]`, or a `[u8; N]` // behind a reference. let mut in_bytes = false; + // The projections a union's field consumed with it, down to a slot. + let mut consumed = 0; for (read, elem) in place.projection.iter().enumerate().skip(from) { let ty = pty.ty; + if consumed > 0 { + consumed -= 1; + pty = pty.projection_ty(tcx, elem); + continue; + } in_bytes = in_bytes || byte_slice(ty); match elem { + ProjectionElem::Field(field, field_ty) + if let ty::Adt(adt, _) = ty.kind() + && adt.is_union() => + { + let (parts, n, part_ty) = + self.union_part(*adt, field.as_usize(), field_ty, &place.projection[read + 1..], span); + consumed = n; + e = self.b.t.of_slots(e, part_ty, &parts, span); + } ProjectionElem::Deref => { // Drop glue's pointer is the value it drops. let glue = matches!(self.env, Env::Shim) && place.local.as_u32() == 1 && pty.ty == decls[place.local].ty; - if self.b.t.place_pointee(ty).is_some() && !glue { + // A box's pointer is the box, which is its contents. + if self.b.t.place_pointee(ty).is_some() && !glue && !self.box_pointer(place.local) { e = e.member("v"); } - // A box's pointer is the box in JS: one to an object shares it. - if let ty::RawPtr(pointee, _) = ty.kind() - && !matches!(self.slots[place.local], Slot::Alias(_)) - && !(matches!(self.env, Env::Shim) && place.local.as_u32() == 1) - && !(self.box_pointer(place.local) && self.b.t.object_kind(*pointee).is_some()) - { - return self.err(span, "a raw pointer dereferenced"); - } } ProjectionElem::Field(field, _) => { e = self.field(e, ty, pty.variant_index, field, span); @@ -311,6 +320,54 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { e } + /// The parts of field `field` of union `adt` that `rest`, the projections after it, + /// go down to, how many of them that takes, and the type they make: one slot, or the + /// parts of an aggregate kept in several. + pub(super) fn union_part( + &mut self, + adt: ty::AdtDef<'tcx>, + field: usize, + field_ty: Ty<'tcx>, + rest: &[ProjectionElem>], + span: Span, + ) -> (Vec, usize, Ty<'tcx>) { + let mut parts = self.b.t.union_field(adt, field, span); + let (mut n, mut ty) = (0, field_ty); + while !crate::lower::unions::whole(&parts) + && let Some(ProjectionElem::Field(inner, inner_ty)) = rest.get(n) + { + parts = crate::lower::unions::within(&parts, inner.as_usize()); + (n, ty) = (n + 1, *inner_ty); + } + (parts, n, ty) + } + + /// The union field part `place` ends in, if it ends in one kept in more than one slot: + /// the union's place and the parts. + pub(super) fn union_whole( + &mut self, + place: Place<'tcx>, + span: Span, + ) -> Option<(Place<'tcx>, Vec)> { + let tcx = self.tcx; + for (i, (base, elem)) in place.iter_projections().enumerate() { + let ProjectionElem::Field(field, field_ty) = elem else { + continue; + }; + let ty::Adt(adt, _) = base.ty(&self.mir.local_decls, tcx).ty.kind() else { + continue; + }; + if !adt.is_union() { + continue; + } + let rest = &place.projection[i + 1..]; + let (parts, n, _) = self.union_part(*adt, field.as_usize(), field_ty, rest, span); + let whole = crate::lower::unions::whole(&parts); + return (n == rest.len() && !whole).then(|| (base.to_place(tcx), parts)); + } + None + } + /// Whether `place` is a `[u8; N]` read through a reference, whose JS value is an /// `rt.ByteSlice` of its bytes rather than a `Uint8Array`, as [`Self::place_expr`] /// reads it. @@ -507,6 +564,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let info = self.b.t.adt(*adt, span); match info.repr { AdtRepr::Struct { fields, .. } => base.field(fields[field.index()]), + AdtRepr::Slots(_) => { + self.err(span, format!("a field of union `{ty}` read apart from its place")) + } AdtRepr::Union | AdtRepr::Tags => { let variant = variant.unwrap_or(rustc_abi::FIRST_VARIANT); let def = &adt.variant(variant).fields[field]; @@ -580,6 +640,24 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { pub(super) fn store(&mut self, place: Place<'tcx>, value: Expr, span: Span) -> Vec { let (place, value) = self.rebuilt(place, value, span); let mut out = Vec::new(); + // A raw pointer to a value of no bytes may dangle, and a write through it writes nothing. + if let [.., ProjectionElem::Deref] = place.projection.as_slice() + && matches!(self.mir.local_decls[place.local].ty.kind(), ty::RawPtr(..)) + && self.b.t.zero_sized(place.ty(&self.mir.local_decls, self.tcx).ty) + { + return out; + } + // A union's field written whole is written into each slot it is kept in. + if let Some((union, parts)) = self.union_whole(place, span) { + let ty = place.ty(&self.mir.local_decls, self.tcx).ty; + let value = self.path(value, "field", &mut out); + let target = self.place_expr(union, span, &mut out); + let target = self.path(target, "union", &mut out); + for (slot, part) in self.b.t.slots_of(value, ty, &parts, span) { + out.push(StmtKind::Expr(target.clone().member(slot).assign(part)).at(self.b.t.loc(span))); + } + return out; + } // Bytes through a reference are copied into the slice of them it holds. if self.in_bytes(place) { let target = self.place_expr(place, span, &mut out); @@ -601,7 +679,10 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let target = self.local_expr(place.local, span); // A binding's value may keep what it holds where `Object.assign` can't reach. let bound = matches!(pointee.kind(), ty::Adt(adt, _) if self.b.t.bound_drops(adt.did()).is_some()); + let raw = + matches!(self.mir.local_decls[place.local].ty.kind(), ty::RawPtr(..)) && !self.box_pointer(place.local); let e = match self.b.t.object_kind(pointee) { + _ if raw => Some(Expr::rt("ptrWrite", vec![target, value.clone()])), Some(ObjectKind::Class) if !shared(self, pointee) && !bound => { Some(Expr::ident("Object").member("assign").call(vec![target, value.clone()])) } @@ -916,7 +997,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { match info.repr { AdtRepr::Tags => scrut.clone().binary("===", name), AdtRepr::Union => scrut.clone().member("tag").binary("===", name), - AdtRepr::Struct { .. } => self.err(span, format!("a switch on `{ty}`")), + AdtRepr::Struct { .. } | AdtRepr::Slots(_) => self.err(span, format!("a switch on `{ty}`")), } } Some(Std::ControlFlow) => super::call::control_flow_test(self, scrut, ty, index), @@ -1327,3 +1408,93 @@ fn from_held(path: Expr, held: &Expr) -> Expr { _ => held.clone(), } } + +impl<'tcx> Fx<'_, '_, '_, 'tcx> { + /// Why a raw pointer made of `place` here would not keep pointing where Rust's does, if + /// it would not: JS keeps each part on its path that is an object, from what the last + /// pointer on it points at or from a local, in an object the crate may give another + /// object in its place, which the pointer would not see. + pub(super) fn out_of_step(&self, place: Place<'tcx>) -> Option { + let (tcx, decls, t) = (self.tcx, &self.mir.local_decls, &self.b.t); + let elems = place.projection.as_slice(); + let start = elems + .iter() + .rposition(|e| matches!(e, ProjectionElem::Deref)) + .map_or(0, |i| i + 1); + let object = |ty: Ty<'tcx>| { + !(t.polymorphic() && ty.has_param()) && t.pointee(ty) == crate::lower::pointer::Pointee::Object + }; + for i in start..elems.len() { + let base = Place::ty_from(place.local, &elems[..i], decls, tcx).ty; + let ty = Place::ty_from(place.local, &elems[..=i], decls, tcx).ty; + if !object(ty) + || matches!( + elems[i], + ProjectionElem::OpaqueCast(_) | ProjectionElem::UnwrapUnsafeBinder(_) + ) { + continue; + } + let field = matches!(elems[i], ProjectionElem::Field(..)) + && matches!(base.kind(), ty::Tuple(_) | ty::Adt(..)) + && !base.is_union() + && !base.is_enum() + && elems[..i] + .last() + .is_none_or(|e| !matches!(e, ProjectionElem::Downcast(..))); + if !field { + return Some(format!( + "a raw pointer to `{ty}` in an element, a payload, or a union field, which is not supported yet" + )); + } + if t.replaced(base) { + return Some(format!( + "a raw pointer to `{ty}` in `{base}`, which the crate may put another object in place of" + )); + } + } + let ty = place.ty(decls, tcx).ty; + if start == 0 && object(ty) && self.assigned_after(place.local) { + return Some(format!( + "a raw pointer to `{ty}` in a variable assigned again while it may point there" + )); + } + None + } + + /// Whether `local` may be assigned whole after the statement being lowered, before its + /// storage ends. + fn assigned_after(&self, local: Local) -> bool { + let blocks = &self.mir.basic_blocks; + let mut seen = FxHashSet::default(); + let mut stack = vec![(self.at.block, self.at.statement_index + 1)]; + while let Some((bb, from)) = stack.pop() { + let data = &blocks[bb]; + let mut ended = false; + for stmt in &data.statements[from.min(data.statements.len())..] { + match &stmt.kind { + mir::StatementKind::StorageDead(l) | mir::StatementKind::StorageLive(l) if *l == local => { + ended = true; + break; + } + mir::StatementKind::Assign(assign) if assign.0.as_local() == Some(local) => return true, + _ => {} + } + } + if ended { + continue; + } + let term = data.terminator(); + if let mir::TerminatorKind::Call { destination, .. } = &term.kind + && destination.as_local() == Some(local) + { + return true; + } + for next in term.successors() { + if seen.insert(next) { + stack.push((next, 0)); + } + } + } + false + } +} diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 9aca67afa..ada55af96 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -12,6 +12,7 @@ use rustc_span::Span; use super::{Capture, Env, Fx, Slot}; use crate::lower::library::Std; use crate::lower::num::{self, Int}; +use crate::lower::pointer::Pointee; use crate::lower::{AdtRepr, byte_array, field_name}; use crate::ts::{Expr, Key, Stmt, StmtKind, Type}; @@ -31,6 +32,9 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Rvalue::CopyForDeref(place) => self.place_expr(*place, span, out), Rvalue::Repeat(op, count) => self.repeat(op, *count, span, out), Rvalue::Ref(_, kind, place) => { + if self.union_whole(*place, span).is_some() { + return Some(self.err(span, "a reference to a union's field kept in more than one slot")); + } if let Some(e) = self.mut_subslice(*kind, *place, span) { return Some(e); } @@ -60,7 +64,46 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { // A bounds check of a `&mut` slice reads its length through a raw pointer that // only `PtrMetadata` reads, which reads the slice's. Rvalue::RawPtr(mir::RawPtrKind::FakeForPtrMetadata, place) => self.place_expr(*place, span, out), - Rvalue::RawPtr(..) => self.err(span, "raw pointers are not supported"), + Rvalue::RawPtr(_, place) => { + if self.union_whole(*place, span).is_some() { + return Some(self.err(span, "a pointer to a union's field kept in more than one slot")); + } + if let Some(why) = self.out_of_step(*place) { + return Some(self.err(span, why)); + } + let pointee = place.ty(&self.mir.local_decls, tcx).ty; + match self.b.t.pointee(pointee) { + Pointee::Object => self.place_expr(*place, span, out), + // `&raw mut *p` is `p`. + Pointee::Place => match place.projection.as_slice() { + [rest @ .., mir::ProjectionElem::Deref] + if !self.box_pointer(place.local) + && self + .b + .t + .place_pointee(Place::ty_from(place.local, rest, &self.mir.local_decls, tcx).ty) + .is_some() => + { + let base = Place { + local: place.local, + projection: tcx.mk_place_elems(rest), + }; + self.place_expr(base, span, out) + } + // A reference to an object is the object, not the place holding it. + [.., mir::ProjectionElem::Deref] if !self.box_pointer(place.local) => self.err( + span, + format!( + "a raw pointer to `{pointee}` behind a reference, which is the object and not where it is kept" + ), + ), + _ => { + let target = self.place_expr(*place, span, out); + self.make_place(*place, target, span, out) + } + }, + } + } Rvalue::Reborrow(..) => self.err(span, "reborrowing a type by its `Reborrow` impl is not supported"), Rvalue::ThreadLocalRef(_) => self.err(span, "thread locals are not supported"), Rvalue::WrapUnsafeBinder(..) => self.err(span, "unsafe binders are not supported"), @@ -86,6 +129,17 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { BinOp::ShrUnchecked => BinOp::Shr, op => op, }; + if let ty::RawPtr(pointee, _) = lty.kind() + && matches!(op, BinOp::Eq | BinOp::Ne) + { + let x = self.operand(l, span, out); + let y = self.operand(r, span, out); + let same = crate::lower::pointer::compare(self.b, x, y, *pointee, span); + return Some(match op { + BinOp::Eq => same, + _ => same.not(), + }); + } if matches!(op, BinOp::Cmp | BinOp::Offset) { return Some(self.err(span, format!("`{op:?}` is not supported"))); } @@ -233,11 +287,28 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => self.err(span, format!("a coercion to `{to}`")), } } - PointerCoercion::Unsize | PointerCoercion::ReifyFnPointer(_) | PointerCoercion::ClosureFnPointer(_) => { - self.operand(op, span, out) - } + PointerCoercion::Unsize + | PointerCoercion::ReifyFnPointer(_) + | PointerCoercion::ClosureFnPointer(_) + | PointerCoercion::MutToConstPointer => self.operand(op, span, out), _ => self.err(span, format!("unsupported pointer coercion {coercion:?}")), }, + // The same pointer, as one to the same pointee: `*mut T` as `*const T`, or a + // `MaybeUninit` as the `T` it holds. + CastKind::PtrToPtr + if let (Some(a), Some(b)) = (from.builtin_deref(true), to.builtin_deref(true)) + && self.b.t.uninit_inner(a) == self.b.t.uninit_inner(b) => + { + self.operand(op, span, out) + } + CastKind::PtrToPtr => self.err( + span, + format!("a cast from `{from}` to `{to}`, which reads the bytes of one type as another"), + ), + CastKind::PointerExposeProvenance | CastKind::PointerWithExposedProvenance => self.err( + span, + format!("a cast from `{from}` to `{to}`, between a pointer and an integer"), + ), // A box's pointer is its contents in JS, which a read shares: the pointer's // type is `Copy`, but what it points to is not copied. CastKind::BoxDerefTransmute => match op { @@ -346,7 +417,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { return Some(super::coroutine::construct(self, *def_id, args, ops, span, out)); } AggregateKind::CoroutineClosure(..) => return Some(self.err(span, "async closures are not supported")), - AggregateKind::RawPtr(..) => return Some(self.err(span, "raw pointers are not supported")), + AggregateKind::RawPtr(..) => return Some(self.err(span, "a raw pointer made from parts")), _ => {} } // A reference field read as its operand may be read more than once, or later: what @@ -377,8 +448,20 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { _ => Expr::Array(values), }, AggregateKind::Adt(def_id, variant, args, _, active) => { - if active.is_some() { - return Some(self.err(span, "unions are not supported")); + if let Some(active) = active { + let ty::Adt(adt, _) = ty.kind() else { + unreachable!("a union") + }; + let parts = self.b.t.union_field(*adt, active.as_usize(), span); + let field_ty = ops[FieldIdx::ZERO].ty(&self.mir.local_decls, self.tcx); + let value = values.pop().expect("a union's field"); + let value = match parts.len() { + 0 | 1 => value, + _ => self.path(value, "field", out), + }; + let slots = self.b.t.slots_of(value, field_ty, &parts, span); + let ts_ty = self.b.t.ty(ty, span); + return Some(crate::lower::unions::made(slots, ts_ty)); } if let Some(same) = self.rebuilt_result(kind, ops, &values, ty, span, out) { return Some(same); @@ -553,6 +636,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { let ts_ty = self.b.t.ty(ty, span); Expr::As(Box::new(Expr::Str(def.name.to_string())), ts_ty) } + AdtRepr::Slots(_) => unreachable!("a union is made by its field"), AdtRepr::Union => { let mut props = vec![(Key::Name("tag".into()), Expr::Str(def.name.to_string()))]; for (i, value) in values.into_iter().enumerate() { diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index afe314679..2ba8049c0 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -19,9 +19,11 @@ mod iter; mod library; mod mir; mod num; +pub mod pointer; mod stock; mod task; mod time; +pub mod unions; use std::collections::VecDeque; @@ -268,6 +270,8 @@ pub enum AdtRepr { Tags, /// An enum: an object per variant, tagged by its name. Union, + /// A union: an object of slots, and each field's parts in them, as `unions` has it. + Slots(unions::Fields), } /// A JS object that a `&mut` can change in place. @@ -923,6 +927,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { fn define(&mut self, adt: AdtDef<'tcx>, span: Span) -> Adt { let tcx = self.tcx; let did = adt.did(); + let mut overlay = Vec::new(); let repr = match () { _ if adt.is_struct() => { let variant = adt.non_enum_variant(); @@ -938,10 +943,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } _ if adt.is_enum() && adt.variants().iter().all(|v| v.fields.is_empty()) => AdtRepr::Tags, _ if adt.is_enum() => AdtRepr::Union, - _ => { - self.err(span, format!("unions are not supported: `{}`", tcx.def_path_str(did))); - AdtRepr::Union - } + _ => match self.overlay(adt, span) { + Ok((fields, slots)) => { + overlay = slots; + AdtRepr::Slots(fields) + } + Err(msg) => { + self.err(span, msg); + AdtRepr::Slots(vec![Vec::new(); adt.non_enum_variant().fields.len()]) + } + }, }; let kind = match repr { AdtRepr::Struct { .. } => SymKind::Struct, @@ -1030,6 +1041,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }, doc: self.doc(did), }, + AdtRepr::Slots(_) => Item::Union { + sym, + params, + ty: self.overlay_ty(overlay, span), + doc: self.doc(did), + }, AdtRepr::Union => { let variants = adt .variants() @@ -1089,7 +1106,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::Adt(adt, _) if self.bound_drops(adt.did()).is_some() => { (!self.is_copy(ty)).then_some(ObjectKind::Class) } - ty::Adt(adt, _) => match library::Std::of(self.tcx, adt.did()) { + ty::Adt(adt, args) => match library::Std::of(self.tcx, adt.did()) { Some(library::Std::Vec) => Some(ObjectKind::Vec), Some( library::Std::Formatter @@ -1120,8 +1137,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | library::Std::Hasher | library::Std::ThreadRng, ) => Some(ObjectKind::Class), + // A place, unless a pointer to what it holds is that object. + Some(library::Std::MaybeUninit) => match self.pointee(args.type_at(0)) { + pointer::Pointee::Object => self.object_kind(args.type_at(0)), + pointer::Pointee::Place => None, + }, Some(_) => None, - None if adt.is_struct() => Some(ObjectKind::Class), + None if adt.is_struct() || adt.is_union() => Some(ObjectKind::Class), None => None, }, // Its captures change through it, as a JS function's do. @@ -1143,6 +1165,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { pub fn place_pointee(&self, ty: Ty<'tcx>) -> Option> { match ty.kind() { ty::Ref(_, inner, rustc_ast::Mutability::Mut) if self.needs_place(*inner) => Some(*inner), + ty::RawPtr(..) => self.raw_place(ty), _ => None, } } @@ -1193,6 +1216,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ); Type::Builtin("unknown") } + ty::RawPtr(inner, _) => self.pointer_ty(*inner, span), ty::Ref(..) if bytes_ref(ty) => Type::Rt("ByteSlice", vec![]), ty::Ref(_, inner, _) => self.ty(*inner, span), ty::Array(elem, _) => match elem.kind() { @@ -1413,7 +1437,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { library::Std::RangeFrom => Type::Object(vec![("start".into(), self.ty(args.type_at(0), span))]), library::Std::RangeFull => Type::Builtin("undefined"), library::Std::Argument => Type::Rt("Fmt", vec![]), - library::Std::NonNull => self.ty(args.type_at(0), span), + library::Std::NonNull => self.pointer_ty(args.type_at(0), span), + library::Std::MaybeUninit => self.ty(args.type_at(0), span), library::Std::Peekable => { let item = self.iter_item(Ty::new_adt(tcx, adt, args)); Type::Rt("Peekable", vec![self.item_ty(item, span)]) @@ -1621,6 +1646,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { | library::Std::NonZero | library::Std::PoisonError, ) => self.erases_to(args.type_at(0)), + // `undefined` while it holds nothing. + Some(library::Std::MaybeUninit) => Erased { + undefined: true, + ..self.erases_to(args.type_at(0)) + }, Some(library::Std::Option) => self.erases_to(args.type_at(0)).option(), Some(library::Std::Poll) => self.erases_to(args.type_at(0)).poll(), Some(library::Std::ControlFlow) if library::Flow::of(self.tcx, args) == library::Flow::Option => { diff --git a/ts/src/lower/pointer.rs b/ts/src/lower/pointer.rs new file mode 100644 index 000000000..cea49e48b --- /dev/null +++ b/ts/src/lower/pointer.rs @@ -0,0 +1,437 @@ +//! Raw pointers, `NonNull`, and `MaybeUninit`. +//! +//! A raw pointer is what a `&mut` to its pointee is where JS keeps the pointee where Rust +//! does: the object, a tuple's or array's or the crate's own struct's. To anything else it +//! is an `rt.Place` of the place holding the value, as a `&mut` to a value JS copies is, +//! which a `Vec` or an `Arc` also is here: its JS object is what it owns, not where Rust +//! keeps it. Null is `rt.NULL`, and a dangling pointer `rt.DANGLING`. +//! +//! A whole value written through a pointer to an object is copied into that object, so +//! every pointer to it sees the write. That goes one object deep, as `*r = v` does through +//! a `&mut`, so a pointer to a part kept as an object is refused wherever the crate may +//! give that part another object. A value read out is a copy of what the crate writes +//! into in place, so a later write leaves it be. +//! +//! A `MaybeUninit` is a `T`: `undefined` where JS copies a `T` and it holds none yet, +//! or an object whose parts hold none yet where a pointer to it is the object. + +use rustc_middle::ty::{self, Ty, TypeVisitableExt}; +use rustc_span::Span; + +use super::body::Body; +use super::library::{Call, Std, targ}; +use super::{AdtRepr, ObjectKind, Translator}; +use crate::ts::{Expr, Key, Stmt, StmtKind, Type}; + +/// What a raw pointer to a type is in JS. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Pointee { + /// An `rt.Place` of the value, which JS copies. + Place, + /// The object it points at. + Object, +} + +impl<'a, 'tcx> Translator<'a, 'tcx> { + /// What a raw pointer to `ty` is. + pub fn pointee(&self, ty: Ty<'tcx>) -> Pointee { + // Only an instance decides what a parameter is. + if self.polymorphic() && ty.has_param() { + return Pointee::Object; + } + let ty = self.uninit_inner(ty); + let object = match (self.object_kind(ty), ty.kind()) { + (Some(ObjectKind::Array | ObjectKind::Bytes), ty::Array(..) | ty::Tuple(_)) => true, + // An async body passes its context as a pointer to it. + (Some(ObjectKind::Class), ty::Adt(adt, _)) => match Std::of(self.tcx, adt.did()) { + Some(std) => std == Std::Context, + None => self.bound_drops(adt.did()).is_none(), + }, + _ => false, + }; + match object { + true => Pointee::Object, + false => Pointee::Place, + } + } + + /// What a `MaybeUninit` holds, through any number of them, or `ty` itself. + pub fn uninit_inner(&self, ty: Ty<'tcx>) -> Ty<'tcx> { + let mut ty = ty; + while let ty::Adt(adt, args) = ty.kind() + && Std::of(self.tcx, adt.did()) == Some(Std::MaybeUninit) + { + ty = args.type_at(0); + } + ty + } + + /// The TypeScript type of a raw pointer or `NonNull` to `pointee`. + pub fn pointer_ty(&mut self, pointee: Ty<'tcx>, span: Span) -> Type { + match self.pointee(pointee) { + Pointee::Place => Type::Rt("Place", vec![self.ty(pointee, span)]), + Pointee::Object => self.ty(pointee, span), + } + } + + /// Whether a `ty` has no bytes, so a write of one through any pointer, even a dangling + /// one, writes nothing. + pub fn zero_sized(&self, ty: Ty<'tcx>) -> bool { + let env = self.typing_env_pub(); + self.tcx + .layout_of(env.as_query_input(ty)) + .is_ok_and(|layout| layout.is_zst()) + } + + /// Whether a `ty` of no bytes is the value [`Self::shell`] makes of it: built of + /// tuples, arrays, structs, and `()`, with no enum to tag. + fn empty(&self, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Tuple(tys) => tys.iter().all(|t| self.empty(t)), + ty::Array(elem, _) => self.empty(*elem), + ty::Adt(adt, args) => match Std::of(self.tcx, adt.did()) { + Some(Std::MaybeUninit) => self.empty(args.type_at(0)), + Some(_) => false, + None => adt.is_struct() && adt.all_fields().all(|f| self.empty(self.field_ty(f.did, args))), + }, + _ => false, + } + } + + /// What `ty` points at, if it is a raw pointer that is an `rt.Place`. + pub fn raw_place(&self, ty: Ty<'tcx>) -> Option> { + match ty.kind() { + ty::RawPtr(inner, _) if self.pointee(*inner) == Pointee::Place => Some(*inner), + _ => None, + } + } + + /// A `MaybeUninit` that holds nothing yet: its parts kept as objects are there, so + /// a pointer to one can initialize it, holding nothing yet themselves. + fn uninit(&mut self, ty: Ty<'tcx>, span: Span) -> Expr { + let ts_ty = self.ty(ty, span); + let empty = self.shell(ty, span); + Expr::As(Box::new(Expr::As(Box::new(empty), Type::Builtin("unknown"))), ts_ty) + } + + /// What a `MaybeUninit` that holds nothing yet is, untyped. + pub fn shell(&mut self, ty: Ty<'tcx>, span: Span) -> Expr { + let ty = self.uninit_inner(ty); + match (self.pointee(ty), ty.kind()) { + (Pointee::Object, ty::Tuple(tys)) => Expr::Array(tys.iter().map(|t| self.shell(t, span)).collect()), + (Pointee::Object, ty::Array(elem, len)) => { + let len = Expr::lit(len.try_to_target_usize(self.tcx).unwrap_or(0).to_string()); + match (elem.kind(), self.pointee(*elem)) { + (ty::Uint(ty::UintTy::U8), _) => Expr::New(Box::new(Expr::ident("Uint8Array")), vec![len]), + (_, Pointee::Place) => Expr::New(Box::new(Expr::ident("Array")), vec![len]), + (_, Pointee::Object) => { + let elem_ty = self.ty(*elem, span); + let each = Expr::Arrow( + Vec::new(), + elem_ty, + vec![StmtKind::Return(Some(self.uninit(*elem, span))).at(None)], + ); + let length = Expr::Object(vec![(Key::Name("length".into()), len)]); + Expr::ident("Array").member("from").call(vec![length, each]) + } + } + } + (Pointee::Object, ty::Adt(adt, args)) => { + let info = self.adt(*adt, span); + match info.repr { + AdtRepr::Struct { ctor, fields } => { + let tys: Vec> = adt + .non_enum_variant() + .fields + .iter() + .map(|f| self.field_ty(f.did, args)) + .collect(); + let values = tys.into_iter().map(|t| self.shell(t, span)).collect(); + self.construct(info.sym, &ctor, &fields, values) + } + AdtRepr::Slots(_) => Expr::Object(Vec::new()), + _ => unreachable!("only a struct or union is an object a pointer points at"), + } + } + _ => Expr::undefined(), + } + } +} + +/// Whether std's `def_id` is one this module maps that lends a place: it makes the +/// `rt.Place` of the `&mut` it returns. +pub fn lends(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> bool { + matches!( + super::library::key(tcx, def_id), + Some((owner, name)) if matches!( + (owner.as_str(), name.as_str()), + ("MaybeUninit", "as_mut_ptr" | "write" | "assume_init_mut" | "assume_init_drop") + | ("NonNull" | "ptr", "as_mut") + | ("Box", "leak") + ) + ) +} + +/// Whether std's `def_id` is one this module maps that only points a pointer elsewhere, +/// reads through it, or drops what it points at, so it gives no part another object. +pub fn keeps(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> bool { + matches!( + super::library::key(tcx, def_id), + Some((owner, name)) if matches!( + (owner.as_str(), name.as_str()), + ( + "MaybeUninit", + "new" | "uninit" | "as_ptr" | "as_mut_ptr" | "assume_init" | "assume_init_ref" | "assume_init_mut" + | "assume_init_read" | "assume_init_drop" + ) | ( + "NonNull", + "new" | "new_unchecked" | "dangling" | "as_ptr" | "as_ref" | "as_mut" | "cast" | "cast_const" | "cast_mut" + ) | ( + "ptr", + "null" | "null_mut" | "dangling" | "dangling_mut" | "is_null" | "as_ref" | "as_mut" | "cast" | "cast_const" + | "cast_mut" | "eq" | "read" | "read_unaligned" | "read_volatile" | "drop_in_place" + ) | ("Box", "into_raw" | "leak" | "from_raw") + ) + ) +} + +/// Whether std's `def_id` is one this module maps that takes the `rt.Place` a `&mut self` +/// is, rather than what it points at: a `MaybeUninit`'s, which it points into. +pub fn takes_place(tcx: ty::TyCtxt<'_>, def_id: rustc_hir::def_id::DefId) -> bool { + matches!(super::library::key(tcx, def_id), Some((owner, _)) if owner == "MaybeUninit") +} + +/// A call of std's raw pointer, `NonNull`, `MaybeUninit`, or `Box` functions that make or +/// take a raw pointer: its mapping, `None` if it has none, or its arguments back if it is +/// no such call. +pub fn call<'tcx>( + b: &mut Body<'_, '_, 'tcx>, + call: &Call<'tcx>, + owner: &str, + name: &str, + args: Vec, + out: &mut Vec, +) -> Result, Vec> { + let span = call.span; + let gargs = call.args; + let pointee = || targ(gargs, 0); + let kind = |b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>| Some(b.t.pointee(ty)); + let mut args = args.into_iter(); + let mut arg = || args.next().expect("an argument"); + let e = match (owner, name) { + ("ptr", "null" | "null_mut") => Expr::Rt("NULL".into()), + // One to a value of no bytes points at one, which every read of it reads. + ("NonNull", "dangling") | ("ptr", "dangling" | "dangling_mut") if b.t.zero_sized(pointee()) => { + match (b.t.empty(pointee()), kind(b, pointee())) { + (false, _) => b.err( + span, + format!( + "a dangling pointer to `{}`, a value of no bytes made of more than structs and tuples", + pointee() + ), + ), + (true, Some(Pointee::Place)) => Expr::Object(vec![(Key::Name("v".into()), b.t.shell(pointee(), span))]), + (true, _) => b.t.uninit(pointee(), span), + } + } + ("NonNull", "dangling") | ("ptr", "dangling" | "dangling_mut") => Expr::Rt("DANGLING".into()), + // The same pointer, as one to what a `MaybeUninit` holds or the other way. + ("ptr" | "NonNull", "cast") => { + let to = targ(gargs, 1); + match b.t.uninit_inner(pointee()) == b.t.uninit_inner(to) { + true => arg(), + false => b.err( + span, + format!( + "a cast from a pointer to `{}` to one to `{to}`, which reads the bytes of one type as another", + pointee() + ), + ), + } + } + ("ptr" | "NonNull", "addr" | "expose_provenance" | "with_addr" | "map_addr" | "without_provenance") => { + b.err(span, "a pointer's address, which JS has no counterpart of") + } + ("ptr", "is_null") => arg().binary("===", Expr::Rt("NULL".into())), + // The same pointer, as another type of the same pointee. + ("ptr", "cast_const" | "cast_mut") + | ("NonNull", "as_ptr" | "new_unchecked" | "cast_const" | "cast_mut") + | ("MaybeUninit", "new" | "assume_init" | "assume_init_mut") + | ("NonNull", "as_mut") + | ("Clone", "clone") + if owner != "Clone" || pointer_self(b, call).is_some() => + { + arg() + } + // A shared reference to a value JS copies is the value, which no write changes. + ("From", "from") if let Some(ty) = pointer_self(b, call) => { + let from = targ(gargs, 1); + match (kind(b, ty), from.kind()) { + (Some(Pointee::Place), ty::Ref(_, _, ty::Mutability::Not)) => { + Expr::Object(vec![(crate::ts::Key::Name("v".into()), arg())]) + } + _ => arg(), + } + } + ("NonNull", "new") => { + let p = b.spill(arg(), "pointer", out); + Expr::Cond( + Box::new(p.clone().binary("===", Expr::Rt("NULL".into()))), + Box::new(Expr::undefined()), + Box::new(p), + ) + } + // `Option<&T>`: a shared reference to a value JS copies is the value. + ("ptr", "as_ref" | "as_mut") => { + let p = b.spill(arg(), "pointer", out); + let shared = name == "as_ref" && kind(b, pointee()) == Some(super::pointer::Pointee::Place); + let (some, none) = match shared { + true => (p.clone().member("v"), b.t.none(pointee())), + false => (p.clone(), Expr::undefined()), + }; + Expr::Cond( + Box::new(p.binary("===", Expr::Rt("NULL".into()))), + Box::new(none), + Box::new(some), + ) + } + ("NonNull", "as_ref") | ("MaybeUninit", "assume_init_ref") => match kind(b, pointee()) { + Some(Pointee::Place) if owner == "NonNull" => arg().member("v"), + _ => arg(), + }, + // A pointer to what a box held, which no box holds any longer. + ("Box", "into_raw" | "leak") => match kind(b, pointee()) { + Some(Pointee::Place) => Expr::Object(vec![(crate::ts::Key::Name("v".into()), arg())]), + _ => arg(), + }, + ("Box", "from_raw") => match kind(b, pointee()) { + Some(Pointee::Place) => arg().member("v"), + _ => arg(), + }, + ("ptr", "read" | "read_unaligned" | "read_volatile") => { + let p = arg(); + read(b, p, pointee(), span) + } + ("ptr", "write" | "write_unaligned" | "write_volatile") => { + let (p, value) = (arg(), arg()); + write(b, p, value, pointee(), span) + } + ("ptr", "replace") => { + let p = b.spill(arg(), "pointer", out); + let value = arg(); + let old = read(b, p.clone(), pointee(), span); + let old = b.spill(old, "old", out); + let write = write(b, p, value, pointee(), span); + out.push(StmtKind::Expr(write).at(None)); + old + } + ("ptr", "swap") => { + let (x, y) = (b.spill(arg(), "x", out), b.spill(arg(), "y", out)); + let old = read(b, x.clone(), pointee(), span); + let old = b.spill(old, "old", out); + let from_y = read(b, y.clone(), pointee(), span); + let into_x = write(b, x, from_y, pointee(), span); + out.push(StmtKind::Expr(into_x).at(None)); + write(b, y, old, pointee(), span) + } + ("ptr", "drop_in_place") => { + let p = arg(); + let value = match kind(b, pointee()) { + Some(Pointee::Place) => p.member("v"), + _ => p, + }; + b.t.drop_value(value, pointee(), span).unwrap_or_else(Expr::undefined) + } + ("ptr", "eq") => { + let (x, y) = (arg(), arg()); + compare(b, x, y, pointee(), span) + } + ("PartialEq", "eq" | "ne") if let Some(ty) = pointer_self(b, call) => { + let (x, y) = (arg(), arg()); + let same = compare(b, x, y, ty, span); + match name { + "eq" => same, + _ => same.not(), + } + } + ("MaybeUninit", "uninit") => b.t.uninit(pointee(), span), + // A shared reference to a value JS copies is the value, which no write changes. + ("MaybeUninit", "as_ptr") if kind(b, pointee()) == Some(Pointee::Place) => { + Expr::Object(vec![(crate::ts::Key::Name("v".into()), arg())]) + } + ("MaybeUninit", "as_ptr" | "as_mut_ptr") => arg(), + ("MaybeUninit", "write") => { + let p = b.spill(arg(), "uninit", out); + let value = arg(); + let write = write(b, p.clone(), value, pointee(), span); + out.push(StmtKind::Expr(write).at(None)); + p + } + // A shared reference to a value JS copies is the value. + ("MaybeUninit", "assume_init_read") => b.t.snapshot(arg(), pointee(), span), + ("MaybeUninit", "assume_init_drop") => { + let p = arg(); + let value = match kind(b, pointee()) { + Some(Pointee::Place) => p.member("v"), + _ => p, + }; + b.t.drop_value(value, pointee(), span).unwrap_or_else(Expr::undefined) + } + ("mem", "forget") => Expr::undefined(), + _ => return Err(args.collect()), + }; + Ok(Some(e)) +} + +/// What a `PartialEq` or `Clone` call's `Self` points at, if it is a raw pointer or a +/// `NonNull`. +fn pointer_self<'tcx>(b: &Body<'_, '_, 'tcx>, call: &Call<'tcx>) -> Option> { + let this = call.args.types().next()?; + match this.kind() { + ty::RawPtr(pointee, _) => Some(*pointee), + ty::Adt(adt, args) if Std::of(b.t.tcx, adt.did()) == Some(Std::NonNull) => Some(args.type_at(0)), + _ => None, + } +} + +/// The value `p`, a pointer to `ty`, points at, as a value of its own. +fn read<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, ty: Ty<'tcx>, span: Span) -> Expr { + let value = match b.t.pointee(ty) { + Pointee::Place => p.member("v"), + Pointee::Object => p, + }; + b.t.snapshot(value, ty, span) +} + +/// The write of `value`, a `ty`, where `p` points. +fn write<'tcx>(b: &mut Body<'_, '_, 'tcx>, p: Expr, value: Expr, ty: Ty<'tcx>, span: Span) -> Expr { + let _ = span; + if b.t.zero_sized(ty) { + return Expr::undefined(); + } + match b.t.pointee(ty) { + Pointee::Place => p.member("v").assign(value), + Pointee::Object => Expr::rt("ptrWrite", vec![p, value]), + } +} + +/// Whether pointers `x` and `y` to `ty` point at the same place: the same object, or +/// null both. Two `rt.Place`s of one place are two objects, and Rust leaves where a value +/// of no bytes is unspecified, so both are refused. +pub fn compare<'tcx>(b: &mut Body<'_, '_, 'tcx>, x: Expr, y: Expr, ty: Ty<'tcx>, span: Span) -> Expr { + let null = |e: &Expr| matches!(e, Expr::Rt(n) if n == "NULL"); + if b.t.zero_sized(ty) && !null(&x) && !null(&y) { + return b.err( + span, + format!("comparing pointers to `{ty}`, which has no bytes and so no address of its own"), + ); + } + match b.t.pointee(ty) { + Pointee::Object => x.binary("===", y), + Pointee::Place if null(&x) || null(&y) => x.binary("===", y), + Pointee::Place => b.err( + span, + format!("comparing pointers to `{ty}`, each a place JS makes of its own"), + ), + } +} diff --git a/ts/src/lower/unions.rs b/ts/src/lower/unions.rs new file mode 100644 index 000000000..5dbc0bc9b --- /dev/null +++ b/ts/src/lower/unions.rs @@ -0,0 +1,288 @@ +//! The crate's own unions. +//! +//! A union is an object of slots, one per place in its layout that a field's parts are +//! kept at: `at0`, `at4`. Each field is its parts, through tuples and structs down to the +//! values JS keeps whole, each in the slot at its offset. Fields that overlap must keep +//! the same values there, so a read of one field after a write of another reads what +//! Rust would: the same type, or a non-null pointer and an `Option` of it. A field read +//! whole is made from its slots, and one written whole is written into them. + +use rustc_abi::FieldIdx; +use rustc_middle::ty::{self, AdtDef, Ty, TypingEnv}; +use rustc_span::Span; + +use super::library::Std; +use super::{AdtRepr, Translator}; +use crate::ts::{Expr, Key, Type}; + +/// Each field's parts. +pub type Fields = Vec>; + +/// A part of a union's field, kept whole in a slot. +#[derive(Clone)] +pub struct Part { + /// The field indices to it from the field. + pub path: Vec, + pub slot: String, + /// Whether the slot holds an `Option` of it, which a read narrows. + pub narrow: bool, +} + +/// A part of a field kept whole, at `offset` for `size` bytes. +struct Leaf<'tcx> { + field: usize, + path: Vec, + offset: u64, + size: u64, + ty: Ty<'tcx>, +} + +impl<'a, 'tcx> Translator<'a, 'tcx> { + /// The slots of union `adt`, and each one's type: an error if its fields overlap in + /// values JS keeps apart. + pub fn overlay(&mut self, adt: AdtDef<'tcx>, span: Span) -> Result<(Fields, Vec<(String, Ty<'tcx>)>), String> { + let tcx = self.tcx; + let env = TypingEnv::post_analysis(tcx, adt.did()); + let identity = ty::GenericArgs::identity_for_item(tcx, adt.did()); + let mut leaves = Vec::new(); + for (i, field) in adt.non_enum_variant().fields.iter().enumerate() { + let ty = self.field_ty(field.did, identity); + self.leaves(env, ty, i, Vec::new(), 0, &mut leaves)?; + } + let mut slots: Vec<(u64, u64, Ty<'tcx>)> = Vec::new(); + for leaf in &leaves { + match slots + .iter_mut() + .find(|(o, s, _)| *o < leaf.offset + leaf.size && leaf.offset < o + s) + { + None => slots.push((leaf.offset, leaf.size, leaf.ty)), + Some((o, s, ty)) => { + if (*o, *s) != (leaf.offset, leaf.size) || !self.same_bits(*ty, leaf.ty) { + let path = tcx.def_path_str(adt.did()); + return Err(format!( + "union `{path}`, whose fields overlap in `{ty}` and `{}`, which JS keeps apart", + leaf.ty + )); + } + // The slot holds the wider of the two: an `Option` holds the pointer. + if self.is_option(leaf.ty) { + *ty = leaf.ty; + } + } + } + } + let _ = span; + let mut fields: Fields = vec![Vec::new(); adt.non_enum_variant().fields.len()]; + for leaf in leaves { + let held = slots.iter().find(|(o, ..)| *o == leaf.offset).map(|(.., ty)| *ty); + let narrow = held.is_some_and(|t| self.is_option(t)) && !self.is_option(leaf.ty); + fields[leaf.field].push(Part { + path: leaf.path, + slot: slot_name(leaf.offset), + narrow, + }); + } + slots.sort_by_key(|(o, ..)| *o); + Ok((fields, slots.into_iter().map(|(o, _, ty)| (slot_name(o), ty)).collect())) + } + + /// Push the parts of a `ty` at `offset` in field `field`, by the `path` to it. + fn leaves( + &self, + env: TypingEnv<'tcx>, + ty: Ty<'tcx>, + field: usize, + path: Vec, + offset: u64, + out: &mut Vec>, + ) -> Result<(), String> { + let tcx = self.tcx; + let layout = tcx + .layout_of(env.as_query_input(ty)) + .map_err(|e| format!("a union field of `{ty}`, whose layout is not known: {e}"))?; + if layout.is_zst() { + return Ok(()); + } + let parts: Option>> = match ty.kind() { + ty::Tuple(tys) => Some(tys.to_vec()), + ty::Adt(adt, args) if adt.is_struct() && Std::of(tcx, adt.did()).is_none() => Some( + adt.non_enum_variant() + .fields + .iter() + .map(|f| self.field_ty(f.did, args)) + .collect(), + ), + _ => None, + }; + match parts { + Some(parts) => { + for (i, part) in parts.into_iter().enumerate() { + let mut path = path.clone(); + path.push(i); + let at = offset + layout.fields.offset(i).bytes(); + self.leaves(env, part, field, path, at, out)?; + } + } + None => out.push(Leaf { + field, + path, + offset, + size: layout.size.bytes(), + ty, + }), + } + Ok(()) + } + + /// Whether every value of `a` and of `b` that both can hold has the same JS value. + fn same_bits(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool { + let tcx = self.tcx; + let base = |t: Ty<'tcx>| -> Ty<'tcx> { + let t = match t.kind() { + ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::Option) => { + let inner = args.type_at(0); + match self.non_null(inner) { + true => inner, + false => t, + } + } + _ => t, + }; + tcx.erase_and_anonymize_regions(t) + }; + base(a) == base(b) + } + + /// Whether a `ty` is never null, so an `Option` of it is the same pointer. + fn non_null(&self, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Ref(..) => true, + ty::Adt(adt, _) => matches!(Std::of(self.tcx, adt.did()), Some(Std::NonNull | Std::Box)), + _ => false, + } + } + + fn is_option(&self, ty: Ty<'tcx>) -> bool { + matches!(ty.kind(), ty::Adt(adt, _) if Std::of(self.tcx, adt.did()) == Some(Std::Option)) + } + + /// The TypeScript type of union `adt`'s slots. + pub fn overlay_ty(&mut self, slots: Vec<(String, Ty<'tcx>)>, span: Span) -> Type { + Type::Object(slots.into_iter().map(|(name, ty)| (name, self.ty(ty, span))).collect()) + } + + /// The slots `value`, a field of a union of type `ty`, puts in it: each part's + /// expression, by its slot. + pub fn slots_of(&mut self, value: Expr, ty: Ty<'tcx>, parts: &[Part], span: Span) -> Vec<(String, Expr)> { + parts + .iter() + .map(|part| (part.slot.clone(), self.part_of(value.clone(), ty, &part.path, span))) + .collect() + } + + /// The part of `value`, a `ty`, at `path`. + fn part_of(&mut self, value: Expr, ty: Ty<'tcx>, path: &[usize], span: Span) -> Expr { + let Some((&first, rest)) = path.split_first() else { + return value; + }; + match ty.kind() { + ty::Tuple(tys) => self.part_of(value.index(Expr::lit(first.to_string())), tys[first], rest, span), + ty::Adt(adt, args) => { + let info = self.adt(*adt, span); + let AdtRepr::Struct { fields, .. } = info.repr else { + unreachable!("a union's part is in a struct") + }; + let field = &adt.non_enum_variant().fields[FieldIdx::from_usize(first)]; + let part_ty = self.field_ty(field.did, args); + self.part_of(value.field(fields[first]), part_ty, rest, span) + } + _ => unreachable!("a union's part is in a tuple or a struct"), + } + } + + /// A `ty`, made of the parts in `parts` of the union `base`. + pub fn of_slots(&mut self, base: Expr, ty: Ty<'tcx>, parts: &[Part], span: Span) -> Expr { + if let [part] = parts + && part.path.is_empty() + { + let value = base.member(part.slot.clone()); + return match part.narrow { + true => Expr::As(Box::new(value), self.ty(ty, span)), + false => value, + }; + } + let within = |i: usize| -> Vec { within(parts, i) }; + match ty.kind() { + ty::Tuple(tys) if tys.is_empty() => Expr::undefined(), + ty::Tuple(tys) => { + let values = (0..tys.len()) + .map(|i| { + let part = within(i); + self.of_slots(base.clone(), tys[i], &part, span) + }) + .collect(); + let ts_ty = self.ty(ty, span); + Expr::As(Box::new(Expr::Array(values)), ts_ty) + } + ty::Adt(adt, args) if adt.is_struct() => { + let info = self.adt(*adt, span); + let AdtRepr::Struct { ctor, fields } = info.repr else { + unreachable!("a struct") + }; + let tys: Vec> = adt + .non_enum_variant() + .fields + .iter() + .map(|f| self.field_ty(f.did, args)) + .collect(); + let values = tys + .into_iter() + .enumerate() + .map(|(i, t)| { + let part = within(i); + self.of_slots(base.clone(), t, &part, span) + }) + .collect(); + self.construct(info.sym, &ctor, &fields, values) + } + // Nothing JS holds, as `PhantomData`. + _ => Expr::undefined(), + } + } + + /// The parts of field `field` of union `adt`. + pub fn union_field(&mut self, adt: AdtDef<'tcx>, field: usize, span: Span) -> Vec { + match self.adt(adt, span).repr { + AdtRepr::Slots(fields) => fields[field].clone(), + _ => unreachable!("a union"), + } + } +} + +/// The parts of `parts` within part `i` of what they make, by their paths from it. +pub fn within(parts: &[Part], i: usize) -> Vec { + parts + .iter() + .filter(|part| part.path.first() == Some(&i)) + .map(|part| Part { + path: part.path[1..].to_vec(), + ..part.clone() + }) + .collect() +} + +/// Whether `parts` are one slot's, whole. +pub fn whole(parts: &[Part]) -> bool { + matches!(parts, [part] if part.path.is_empty()) +} + +/// The name of the slot at `offset`. +fn slot_name(offset: u64) -> String { + format!("at{offset}") +} + +/// A union of slots, `slots` set and every other `undefined`, as a `ty`. +pub fn made(slots: Vec<(String, Expr)>, ty: Type) -> Expr { + let object = Expr::Object(slots.into_iter().map(|(name, e)| (Key::Name(name), e)).collect()); + Expr::As(Box::new(Expr::As(Box::new(object), Type::Builtin("unknown"))), ty) +}