diff --git a/quest/m3/README.md b/quest/m3/README.md index abf69e7e8..b2bd224af 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -29,6 +29,5 @@ generic std gap is planned here even when moq never calls it. - [A string's exact capacity](/quest/m3/ts-string-capacity.md) - a `String` holds std's capacity, so a program making a `Bytes` of one reads its capacity - [A B-tree's searches call Ord as std's do](/quest/m3/ts-btree-search-order.md) - a B-tree's lookups and inserts call a key's own `Ord` in std's order and number - [Every panic of a hooked program has its location](/quest/m3/ts-panic-sites.md) - no panic in a program that sets a hook lacks the location native code gives it -- [A string Cow's variant](/quest/m3/ts-cow-str-variant.md) - matching a `Cow`'s `Borrowed` or `Owned`, and `to_mut`'s change of it, translate as std's - [The chunk a BufMut lends](/quest/m3/ts-chunk-mut.md) - `BufMut::chunk_mut` and its `UninitSlice`'s writes translate, reaching the buffer's storage - [Slices not yet written](/quest/m3/ts-uninit-slices.md) - `Box::new_uninit_slice` and `write_clone_of_slice` translate diff --git a/quest/m3/ts-cow-str-variant.md b/quest/m3/ts-cow-str-variant.md deleted file mode 100644 index 206bda79d..000000000 --- a/quest/m3/ts-cow-str-variant.md +++ /dev/null @@ -1,42 +0,0 @@ -# [L] A string Cow's variant - -## Goal - -[rust2-ts](/ts/README.md) translates matching `Cow::Borrowed` and `Cow::Owned` of a -`Cow`, and `into_owned` and `to_mut` keep std's variant: `to_mut` of a borrowed one -makes it owned. Differential tests against native Rust observe the variant wherever std -decides it, including `String::from_utf8_lossy`, which borrows valid input and owns -replaced text. `Cow::is_borrowed` and `is_owned` are unstable and stay refused. - -## Plan - -Decided with the maintainer on 2026-11-03: plan this, found while translating -`from_utf8_lossy` in [#537](https://github.com/moq-dev/rust2/pull/537). - -Today a `Cow` is a plain JS string ([#105](https://github.com/moq-dev/rust2/pull/105)), -so it keeps no variant. Code that reads one is refused with `library::cow_variant`, at a -match or `if let` of its discriminant (`mir/place.rs` and `mir/rvalue.rs`), at a read of a -variant's field (`mir/place.rs`), and at `is_borrowed` and `is_owned` (`library.rs`). The -`reject` fixture's `strs.rs` holds these cases. `to_mut` is the same place and -`into_owned` the same text, which is right only while nothing reads the variant. - -Representation options: - -- A wrapper only where some match observes the variant: a whole-program pass finds every - `Cow` whose variant some match or field read can see, and only those values become - a `{ borrowed, text }`-like object. Every other `Cow` stays a string. It costs - nothing where nothing observes, but values flow through generics, fields, collections, - and calls, so the pass has to follow each one and refuse where it can't. -- A wrapper everywhere: every `Cow` is an object. It is simple and uniform, but costs - an allocation and an indirection on every use, and changes `Display`, `==`, hashing, - keys, `Deref`, and the conversions the plain string gets for free. -- Others found along the way, such as a variant kept beside the string only in the - locals and fields that need it. - -Measure each candidate against the plain string on the TS bench (`just ts bench`) and -on moq's translated codec (`just ts moq`) before choosing. Ask the maintainer if no -option is free where nothing observes the variant. - -`Cow<[T]>` already keeps its variant (`rt.Borrowed`, or a `ByteSlice` against a -`ByteVec`), which may suggest a shape. The tests belong in the `lang/cows` and `lang/utf8` -fixtures. The `reject` fixture's `Cow` cases move to them. diff --git a/ts/MIGRATE.md b/ts/MIGRATE.md index 8c001e6ef..3d36ed9b6 100644 --- a/ts/MIGRATE.md +++ b/ts/MIGRATE.md @@ -2,6 +2,12 @@ ## Unreleased +- A `Cow` is an `rt.StrCow`, the string it owns or an `rt.Borrowed` of the one it + borrows, rather than a `string`: read its text with `rt.strCowStr(c)`, and pass a + borrowed one as `new rt.Borrowed(s)` or an owned one as the string. `rt.Borrowed` + takes the type of what it borrows, so a `Cow<[T]>`'s is an `rt.Borrowed`. + `rt.stringFromUtf8Lossy` returns an `rt.StrCow`, and `rt.strFromUtf8Unchecked` is the + plain decode it was. - `rt.handleAllocError`, and a `LocalKey` initialized while it drops, throw `rt.Abort` rather than an `rt.Panic`, as std aborts there without running the panic hook. An `rt.Abort` is an `rt.Panic`, so a caller catching one still catches both; one telling diff --git a/ts/README.md b/ts/README.md index b569de40c..25f7a30c6 100644 --- a/ts/README.md +++ b/ts/README.md @@ -89,7 +89,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | Rust | TypeScript | | --- | --- | -| `bool`, `str`, `String`, `Box`, `Cow` | `boolean`, `string` | +| `bool`, `str`, `String`, `Box` | `boolean`, `string` | | `char` | `rt.char`: a `number` branded so, its Unicode scalar value | | `u8`-`u32`, `i8`-`i32`, `usize`, `isize` | `rt.u8` and so on: a `number` branded with its type, checked: overflow panics | | `u64`, `i64`, `u128`, `i128` | `rt.u64` and so on: a branded `number` while a safe integer, 32-bit limbs past 2^53, checked the same way | @@ -105,7 +105,7 @@ crate translation reaches is refused, and so are two versions of one crate it re | `Result` | `rt.Result`: `{ ok: true, value }` or `{ ok: false, error }`; `rt.OK` or `rt.ERR` where the payload is `undefined`, as in `Ok(())` | | `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]`, `Box<[T]>`, `Vec` | an array; for bytes, `u8` or `MaybeUninit`, `Uint8Array` for `[u8; N]`, `rt.ByteSlice` for `&[u8]`, `&[u8; N]`, and `Box<[u8]>`, and `rt.ByteVec` for `Vec` | -| `Cow<[T]>` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice` | +| `Cow<[T]>`, `Cow` | `rt.Cow`: the `Vec` it owns, or an `rt.Borrowed` holding the slice it borrows; `rt.ByteCow` for bytes, an `rt.ByteVec` or an `rt.ByteSlice`; `rt.StrCow` for text, the string it owns or an `rt.Borrowed` holding the one it borrows, which `rt.strCowStr` reads | | `&T`, `&mut T`, `Box`, `Reverse`, `NonZero`, `AssertUnwindSafe` | the value; a `&mut` to a value JS copies, such as a number, an `Option`, a `String`, or a reference, or to an `Arc`, a `Waker`, or a `LocalKey`, whose copies share what it points at, is an `rt.Place`, which for what a box holds is the place holding the box | | `*const T`, `*mut T`, `NonNull` | what a `&mut T` is: the object, an `rt.ByteSlice` of a `[u8; N]`'s bytes, which compares by the buffer and offset they are at, or an `rt.Place` to a value JS copies and to a library type such as a `Vec` or an `Arc`, which into an array, slice, or `Vec` is the `rt.AtIndex` of its element, which `add`, `sub`, `offset`, their wrapping forms, `offset_from`, `ptr::copy`, and `slice::from_raw_parts` move, measure, copy between, and view; one to a value in no array is element 0 of an array of one, as in Rust; JS keeps that array, and a shared subslice, which JS copies, apart from what holds it in Rust, so a pointer, slice, or copy the body shows reaching past either, a raw or mutable slice of a byte in no array, and `offset_from` between pointers the body shows JS keeps in different arrays are refused, and through a pointer from elsewhere, such as a field, any of these, or a read or write past the array JS keeps it in, fails at run time; a pointer into an array of values JS keeps as objects is refused, as is comparing pointers to an array of other values than bytes, which a slice lends as a copy of its own; `rt.NULL` for null and `rt.DANGLING` for dangling. A `*const ()` is an identity, such as a waker's data, and `rt.DANGLING_UNIT` when dangling; one to a place of `()` or from a reference to one is refused. A pointer to a value JS keeps as an object cast to one, by `as` or `cast`, is the same object, which `rt.ptrErase` notes the type of, so a cast back to a pointer to another type fails at run time, and null and dangling stay so, dangling at the alignment of the type it was cast from; one to a value JS copies is the same place where an `Arc`'s or `Rc`'s `into_raw` or `as_ptr` gave it, its value's one place, which `rt.arcErase` checks where the body doesn't show it, and refused where the body shows a borrow's, a new place each borrow, as is casting one to a view of bytes, a new one each borrow, or one of no fixed size. A write of a whole object copies into it one object deep, as an assignment through a `&mut` does, the state an `rt` class such as `rt.LazyCell` keeps private included, so a pointer to a part kept as an object, such as a field of a tuple or struct type, is refused wherever the crate may put another object there while a pointer into that memory may still be read, which a write into a fresh allocation, or a variable before any pointer into it is taken, can't, whether by index or through std's iterators over its slots; a read copies what the crate writes into in place | | `MaybeUninit` | `T`: `undefined` until written, `0` for a byte, or an object whose parts kept as objects are there, holding `undefined` | @@ -240,7 +240,7 @@ their lossy forms over bytes, decode as `char::decode_utf16` does: an unpaired s fails with `rt.FromUtf16Error`, `{ kind }`, its private `FromUtf16ErrorKind`'s name, or becomes U+FFFD, and so does an odd byte at the end. `String::from_utf8_lossy` is a `TextDecoder`'s, which writes one U+FFFD for each maximal -invalid sequence, as std's does; its `Cow` is the text either way. `utf8_chunks` splits +invalid sequence, as std's does, and borrows the text of valid UTF-8, as std's does. `utf8_chunks` splits where `str::from_utf8` fails, so each `Utf8Chunk`'s `invalid` bytes are std's, and `Debug` of it writes what is left as std's does, each invalid byte as `\xNN`. `from_utf8_unchecked`, of a `str` or a `String`, decodes as `from_utf8_lossy` does the UTF-8 its contract promises. @@ -395,8 +395,8 @@ one `&s[a..b]` or `&s[a..=b]` takes, which reads the string up to it and panics does inside a char, where `get` gives `None`. A JS string can't change in place, so `String`'s edits, `push_str`, `truncate`, `insert`, `retain`, `replace_range`, and the rest, assign the edited one to the local or field the `&mut` borrows, taking and checking byte offsets as std does, and `retain` then -drops its closure. `+`, of a `String` or a `Cow`, is JS's `+` of the moved text, as -`+=` is. A `&mut str` is +drops its closure. `+`, of a `String`, is JS's `+` of the moved text, as `+=` is, and of a +`Cow` takes either side whole when the other is empty, as std's does. A `&mut str` is the `rt.Place` of its `String` wherever that is, and a part of one, from `&mut s[a..b]`, `get_mut`, or `split_at_mut`, is `rt.strPart(p, a, b)`, a place of its own that writes the whole string anew, checking its offsets as `&s[a..b]` does. `as_bytes_mut`, @@ -405,12 +405,13 @@ whole string anew, checking its offsets as `&s[a..b]` does. `as_bytes_mut`, size hint's lower bound, which panics past `isize::MAX`. A `drain` holds the `&mut`'s place and writes the string without the drained text as it drops, so one that `mem::forget` leaks leaves it as it was, as std's does. A `String` keeps no capacity: -`with_capacity`, `reserve`, and `shrink_to_fit` do nothing, and `capacity` is refused. A `Cow` is its text, borrowed or owned, so -`to_mut` borrows the `Cow`'s own place, and code that tells its variants apart, a `match` -on them or `Cow::is_borrowed`, is refused. A `Cow<[T]>` keeps its variant in its class, -so a `match` reads it, and `to_mut` of a borrowed one stores a clone of its slice in the -`Cow`'s place before lending it. Its `clone_from` of an owned one over an owned one clones -into the `Vec` it owns, keeping its buffer, as std's does. +`with_capacity`, `reserve`, and `shrink_to_fit` do nothing, and `capacity` is refused. A `Cow` keeps its +variant in its class, so a `match` reads it, and `to_mut` of a borrowed one stores what it +owns in the `Cow`'s place before lending it: a `Cow<[T]>`'s clone of its slice, or a +`Cow`'s text, which a `String` is, so the `&mut` borrows the `Cow`'s own place. A +`Cow<[T]>`'s `clone_from` of an owned one over an owned one clones into the `Vec` it owns, +keeping its buffer, as std's does. `Cow::is_borrowed` and `is_owned` are unstable, and +refused. Functions and members are camelCase. Functions are monomorphized: each instance is its own function, suffixed after a `$` by the items it is nested in below its module, its diff --git a/ts/fixtures/lang/boxes/expected/boxes.ts b/ts/fixtures/lang/boxes/expected/boxes.ts index 5176ecfaf..95fb9feb4 100644 --- a/ts/fixtures/lang/boxes/expected/boxes.ts +++ b/ts/fixtures/lang/boxes/expected/boxes.ts @@ -612,11 +612,11 @@ export function places(_1: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { }); } if (res.ok) { - res = rt.Ok(rt.u32WrappingMul(res.value, by as rt.u32)); + res = { ok: true, value: rt.u32WrappingMul(res.value, by as rt.u32) }; } const _165: rt.Place> = rt.place(() => said, (value12) => said = value12); if (_165.v.ok) { - _165.v = rt.Ok(_165.v.value + String.fromCodePoint(rt.u8Add(97, rt.u8Rem(by, 26)) as rt.char)); + _165.v = { ok: true, value: _165.v.value + String.fromCodePoint(rt.u8Add(97, rt.u8Rem(by, 26)) as rt.char) }; } if (rt.usizeRem(i, 5) === 4) { said = rt.Err(by); diff --git a/ts/fixtures/lang/cows/expected/core/option.ts b/ts/fixtures/lang/cows/expected/core/option.ts index 478ebb9be..fa7115f2f 100644 --- a/ts/fixtures/lang/cows/expected/core/option.ts +++ b/ts/fixtures/lang/cows/expected/core/option.ts @@ -1,6 +1,14 @@ // Generated by rust2-ts. Do not edit. import * as rt from "../rt.ts"; +import { borrows } from "../cows.ts"; + +export function unwrapOr$1qh6z5m(self: rt.StrCow | undefined, default_: rt.StrCow): rt.StrCow { + if (self === undefined) { + return default_; + } + return self; +} export function unwrapOr$1iemzn8(self: string | undefined, default_: string): string { if (self === undefined) { @@ -23,11 +31,39 @@ export function unwrapOr$0r2bmpr(self: rt.u8 | undefined, default_: rt.u8): rt.u return self; } -export function map$1uo1d4o(self: string | undefined, _: (a0: string) => string): string | undefined { +export function map$00b7lvf(self: rt.StrCow | undefined, _: (a0: rt.StrCow) => string): string | undefined { if (self === undefined) { return undefined; } - return self; + return rt.strCowStr(self); +} + +export function map$1li8gst(self: string | undefined, _: (a0: string) => rt.StrCow): rt.StrCow | undefined { + if (self === undefined) { + return undefined; + } + return new rt.Borrowed(self); +} + +export function mapOr$0lcrgk1(self: string | undefined, default_: rt.u8, f: (a0: string) => rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return f(self); +} + +export function mapOr$1612aex(self: rt.char | undefined, default_: rt.usize, _: (a0: rt.char) => rt.usize): rt.usize { + if (self === undefined) { + return default_; + } + return rt.charLenUtf8(self); +} + +export function mapOr$1nwl1hk(self: rt.StrCow | undefined, default_: rt.u8, _: (a0: rt.StrCow) => rt.u8): rt.u8 { + if (self === undefined) { + return default_; + } + return borrows(self); } //# sourceMappingURL=option.ts.map diff --git a/ts/fixtures/lang/cows/expected/cows.Named.ts b/ts/fixtures/lang/cows/expected/cows.Named.ts index 73a2262c3..82262d455 100644 --- a/ts/fixtures/lang/cows/expected/cows.Named.ts +++ b/ts/fixtures/lang/cows/expected/cows.Named.ts @@ -3,13 +3,13 @@ import * as rt from "./rt.ts"; export function clone$Clone(self: Named): Named { - return { name: self.name, id: self.id }; + return { name: rt.strCowClone(self.name), id: self.id }; } export function fmt$fmt_Debug(self: Named, f: rt.Formatter): rt.Result { const _3: string = "Named"; const _4: string = "name"; - const _5: rt.Fmt = (f: rt.Formatter) => rt.fmtStr(self.name, f); + const _5: rt.Fmt = (f: rt.Formatter) => rt.fmtStr(rt.strCowStr(self.name), f); const _7: string = "id"; const _8: rt.Fmt = (f: rt.Formatter) => rt.u8Debug(self.id, f); return rt.debugStructFinish(rt.debugStructField(rt.debugStructField(rt.fmtDebugStruct(f, _3), _4, _5), _7, _8)); @@ -23,11 +23,11 @@ export function eq$cmp_PartialEq(self: Named, other: Named): boolean { if (!(self.id === other.id)) { return false; } - return self.name === other.name; + return rt.strCowStr(self.name) === rt.strCowStr(other.name); } export function hash$Hash_hash_DefaultHasher(self: Named, state: rt.Hasher): void { - rt.hashStr(self.name, state); + rt.hashCow(rt.hashStr)(self.name, state); rt.hash8(self.id, state); } @@ -36,7 +36,7 @@ export function partialCmp$cmp_PartialOrd(self: Named, other: Named): rt.Orderin } export function cmp$cmp_Ord(self: Named, other: Named): rt.Ordering { - const cmp: rt.Ordering = rt.cmpStr(self.name, other.name); + const cmp: rt.Ordering = rt.cowBy(rt.cmpStr)(self.name, other.name); if (cmp === 0) { return rt.cmp(self.id, other.id); } @@ -45,16 +45,16 @@ export function cmp$cmp_Ord(self: Named, other: Named): rt.Ordering { /** A `Cow` in a field, with each derived impl reading it. */ export interface Named { - name: string; + name: rt.StrCow; id: rt.u8; } /** Borrowed unless it has to make the text, as moq-net's `Relative::new` is. */ function new_(s: string, id: rt.u8): Named { - let name!: string; + let name!: rt.StrCow; const trimmed: string = rt.strTrimEndMatches(s, "/"); if (!(trimmed.length === 0)) { - name = trimmed; + name = new rt.Borrowed(trimmed); } else { const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u8Display(id, f)]; name = rt.fmtFormat(["#", args2[0]]); @@ -64,11 +64,12 @@ function new_(s: string, id: rt.u8): Named { /** Through `to_mut` of the field. */ export function tag(self: Named, c: rt.char): void { + self.name = rt.strCowStr(self.name); self.name = self.name + String.fromCodePoint(c); } export function intoOwned(self: Named): Named { - return { name: self.name, id: self.id }; + return { name: rt.strCowStr(self.name), id: self.id }; } export { new_ as new }; diff --git a/ts/fixtures/lang/cows/expected/cows.ts b/ts/fixtures/lang/cows/expected/cows.ts index 456253586..15a4d73d9 100644 --- a/ts/fixtures/lang/cows/expected/cows.ts +++ b/ts/fixtures/lang/cows/expected/cows.ts @@ -1,88 +1,92 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$1j4op5y as option_unwrapOr$1j4op5y, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$1uo1d4o as option_map$1uo1d4o } from "./core/option.ts"; +import { unwrapOr$1qh6z5m as option_unwrapOr$1qh6z5m, unwrapOr$1iemzn8 as option_unwrapOr$1iemzn8, unwrapOr$1j4op5y as option_unwrapOr$1j4op5y, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, map$00b7lvf as option_map$00b7lvf, map$1li8gst as option_map$1li8gst, mapOr$0lcrgk1 as option_mapOr$0lcrgk1, mapOr$1612aex as option_mapOr$1612aex, mapOr$1nwl1hk as option_mapOr$1nwl1hk } from "./core/option.ts"; import * as Named from "./cows.Named.ts"; import * as Run from "./cows.Run.ts"; import * as Tally from "./cows.Tally.ts"; import { put, text } from "./harness.ts"; /** `to_mut` of a `Result`'s payload, which the store rebuilds the `Result` around. */ -export function exclaimed(r: rt.Result): rt.Result { +export function exclaimed(r: rt.Result): rt.Result { if (r.ok) { - return rt.Ok(r.value + "!"); + r = { ok: true, value: rt.strCowStr(r.value) }; + return { ok: true, value: r.value + "!" }; } return r; } -export function cow(s: string, owned: boolean): string { +export function cow(s: string, owned: boolean): rt.StrCow { if (!owned) { - return s; + return new rt.Borrowed(s); } return s; } export function cows(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { - let r!: rt.Result; + let r!: rt.Result; const s: string = text(data); const owned: boolean = (kind & 128) !== 0; const first: string = option_unwrapOr$1iemzn8(rt.iterToOption(rt.strSplit(s, "/").next()), ""); const _14: rt.u8 = rt.u8Rem(kind, 6); if (_14 === 0) { - const c: string = cow(s, owned); - rt.byteVecPush(out, rt.stringLen(c) & 0xff); - rt.byteVecPush(out, c.length === 0 ? 1 : 0); - rt.byteVecPush(out, rt.iterCount(rt.strChars(c)) & 0xff); - put(out, c); - put(out, c); - put(out, c); - put(out, c); - put(out, c); - put(out, ""); - put(out, "fallback"); - put(out, s); - const shared: rt.Arc = new rt.Arc(s); + const c: rt.StrCow = cow(s, owned); + rt.byteVecPush(out, rt.stringLen(rt.strCowStr(c)) & 0xff); + rt.byteVecPush(out, rt.strCowStr(c).length === 0 ? 1 : 0); + rt.byteVecPush(out, rt.iterCount(rt.strChars(rt.strCowStr(c))) & 0xff); + put(out, rt.strCowStr(c)); + put(out, rt.strCowStr(c)); + put(out, rt.strCowStr(rt.strCowClone(c))); + put(out, rt.strCowStr(c)); + put(out, rt.strCowStr(c)); + put(out, rt.strCowStr("")); + put(out, rt.strCowStr(new rt.Borrowed("fallback"))); + put(out, rt.strCowStr(s)); + const shared: rt.Arc = new rt.Arc(rt.strCowStr(new rt.Borrowed(s))); put(out, shared.value); - put(out, option_unwrapOr$1iemzn8(option_map$1uo1d4o(rt.iterNth(rt.strSplit(s, "\\"), 1), (a0: string): string => { - return a0; - }), "fallback")); - put(out, cow(first, owned)); + put(out, rt.strCowStr(option_unwrapOr$1qh6z5m(option_map$1li8gst(rt.iterNth(rt.strSplit(s, "\\"), 1), (a0: string): rt.StrCow => { + return new rt.Borrowed(a0); + }), new rt.Borrowed("fallback")))); + put(out, rt.strCowStr(cow(first, owned))); put(out, first); shared[Symbol.dispose](); } else if (_14 === 1) { - let c2: string = cow(s, owned); + let c2: rt.StrCow = cow(s, owned); + c2 = rt.strCowStr(c2); c2 = c2 + "!"; + c2 = rt.strCowStr(c2); c2 = c2 + "?"; c2 = c2 + "𐀀"; - put(out, c2); + put(out, rt.strCowStr(c2)); put(out, s); - const v: string[] = [cow(first, owned), cow(s, !owned)]; + const v: rt.StrCow[] = [cow(first, owned), cow(s, !owned)]; const at = rt.idx(v, 1); + v[at] = rt.strCowStr(v[at]); v[at] = v[at] + "v"; for (const _124 of v) { - put(out, _124); - } - put(out, c2); - let joined: string = cow("", owned) + first + cow(s, !owned); - const _145: string = cow("", !owned); - joined = joined + _145; - joined = joined + "+"; - put(out, joined); + put(out, rt.strCowStr(_124)); + } + put(out, rt.strCowStr(c2)); + let joined: rt.StrCow = rt.strCowAdd(rt.strCowAdd(cow("", owned), new rt.Borrowed(first)), cow(s, !owned)); + const _145: rt.StrCow = cow("", !owned); + joined = rt.strCowAdd(joined, _145); + joined = rt.strCowAdd(joined, new rt.Borrowed("+")); + put(out, rt.strCowStr(joined)); if ((kind & 64) === 0) { r = rt.Ok(cow(first, owned)); } else { r = rt.Err(kind); } - const _156: rt.Result = exclaimed(r); + const _156: rt.Result = exclaimed(r); if (_156.ok) { - put(out, _156.value); + put(out, rt.strCowStr(_156.value)); } else { rt.byteVecPush(out, _156.error); } } else if (_14 === 2) { - const a: string = cow(s, owned); - const b: string = cow(first, !owned); - const iter2: rt.Iter = rt.iterSlice([a === first, a === first, a === s, first === a, s === a, a === b, a !== b, rt.cmpStr(a, b) < 0, rt.cmpStr(a, b) >= 0, rt.cmpStr(a, b) === 1]); + const a: rt.StrCow = cow(s, owned); + const b: rt.StrCow = cow(first, !owned); + const iter2: rt.Iter = rt.iterSlice([rt.strCowStr(a) === first, rt.strCowStr(a) === first, rt.strCowStr(a) === s, first === rt.strCowStr(a), s === rt.strCowStr(a), rt.strCowStr(a) === rt.strCowStr(b), rt.strCowStr(a) !== rt.strCowStr(b), rt.cowBy(rt.cmpStr)(a, b) < 0, rt.cowBy(rt.cmpStr)(a, b) >= 0, rt.cowBy(rt.cmpStr)(a, b) === 1]); while (true) { const _205: boolean | rt.Done = iter2.next(); if (_205 === rt.DONE) { @@ -90,7 +94,8 @@ export function cows(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { } rt.byteVecPush(out, _205 ? 1 : 0); } - rt.byteVecPush(out, rt.ordMax(a, b, rt.cmpLt(rt.cmpStr)) === b ? 1 : 0); + const _216: rt.StrCow = rt.strCowClone(b); + rt.byteVecPush(out, rt.strCowStr(rt.ordMax(a, _216, rt.cmpLt(rt.cowBy(rt.cmpStr)))) === rt.strCowStr(b) ? 1 : 0); } else if (_14 === 3) { const n: Named.Named = Named.new(first, kind); const m2: Named.Named = Named.clone$Clone(n); @@ -100,30 +105,30 @@ export function cows(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { rt.byteVecPush(out, Named.eq$cmp_PartialEq(n, m2) ? 1 : 0); rt.byteVecPush(out, Named.cmp$cmp_Ord(n, m2) > 0 ? 1 : 0); rt.byteVecPush(out, Named.partialCmp$cmp_PartialOrd(n, m2) === -1 ? 1 : 0); - put(out, Named.intoOwned(n).name); + put(out, rt.strCowStr(Named.intoOwned(n).name)); const _270: Named.Named = Named.default$Default(); const _272: Named.Named = Named.new("", kind); const args4: rt.Fmt[] = [(f: rt.Formatter) => Named.fmt$fmt_Debug(_270, f), (f: rt.Formatter) => Named.fmt$fmt_Debug(_272, f)]; put(out, rt.fmtFormat([args4[0], " ", args4[1]])); } else if (_14 === 4) { - const counts: rt.BTreeMap = rt.btreeNew(rt.cmpStr) as rt.BTreeMap; + const counts: rt.BTreeMap = rt.btreeNew(rt.cowBy(rt.cmpStr)) as rt.BTreeMap; const iter3: rt.Iter<[rt.usize, string]> = rt.iterEnumerate(rt.strSplit(s, "/")); while (true) { const _286: [rt.usize, string] | rt.Done = iter3.next(); if (_286 === rt.DONE) { break; } - const _294: string = cow(_286[1], rt.usizeRem(_286[0], 2) === 1 === owned); + const _294: rt.StrCow = cow(_286[1], rt.usizeRem(_286[0], 2) === 1 === owned); const _291: rt.Place = rt.entryOrInsertMut(rt.entry(counts, _294), 0); _291.v = rt.u8Add(_291.v, 1); } - const iter4: rt.Iter<[string, rt.u8]> = rt.btreeIter(counts); + const iter4: rt.Iter<[rt.StrCow, rt.u8]> = rt.btreeIter(counts); while (true) { - const _303: [string, rt.u8] | rt.Done = iter4.next(); + const _303: [rt.StrCow, rt.u8] | rt.Done = iter4.next(); if (_303 === rt.DONE) { break; } - put(out, _303[0]); + put(out, rt.strCowStr(_303[0])); rt.byteVecPush(out, _303[1]); } rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.btreeGet(counts, first), 255)); @@ -141,15 +146,245 @@ export function cows(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { const _339: Named.Named = Named.new(first, 0); rt.byteVecPush(out, rt.hashContains(names, _339) ? 1 : 0); } else { - const c4: string = cow(s, owned); - const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, c4), (f: rt.Formatter) => rt.fmtStr(c4, f)]; + const c4: rt.StrCow = cow(s, owned); + const args6: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, rt.strCowStr(c4)), (f: rt.Formatter) => rt.fmtStr(rt.strCowStr(c4), f)]; put(out, rt.fmtFormat(["[", args6[0], "|", rt.fmtSpec({ align: ">", width: 6 }, args6[0]), "|", rt.fmtSpec({ align: "<", width: 5 }, args6[0]), "|", rt.fmtSpec({ align: "^", width: 7 }, args6[0]), "|", rt.fmtSpec({ fill: "*", align: "^", width: 3 }, args6[0]), "|", args6[1], "]"])); const part2 = cow(first, owned); - const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(part2, f, rt.fmtStr)]; + const args8: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtOption(part2, f, (value: rt.StrCow, f: rt.Formatter) => rt.fmtStr(rt.strCowStr(value), f))]; put(out, rt.fmtFormat([args8[0]])); } } +/** Whether `c` borrows, as a match tells. */ +export function borrows(c: rt.StrCow): rt.u8 { + if (c instanceof rt.Borrowed) { + return 1; + } + return 0; +} + +/** Point `s` at `to`, through the `&mut` a caller passes. */ +export function repointStr(s: rt.Place, to: string): void { + s.v = to; +} + +/** A clone of `t`, through a generic. */ +export function cloned$borrow_Cow_str(t: rt.StrCow): rt.StrCow { + return rt.strCowClone(t); +} + +/** + * A `Cow`'s variant, as std decides it: kept or changed by what makes, converts, + * clones, joins, and changes one, matched on by reference, in place, and by value, and + * held in collections and fields. + */ +export function variants(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + let made2!: string; + let _184!: rt.StrCow; + const s: string = text(data); + const owned: boolean = (kind & 128) !== 0; + const first: string = option_unwrapOr$1iemzn8(rt.iterToOption(rt.strSplit(s, "/").next()), ""); + const rest: string = option_unwrapOr$1iemzn8(rt.iterToOption(rt.strSplit(s, "/").nextBack()), ""); + const _21: rt.u8 = rt.u8Rem(kind, 3); + if (_21 === 0) { + const c: rt.StrCow = cow(s, owned); + const _27: rt.StrCow = rt.strCowClone(c); + const _29: rt.StrCow = cloned$borrow_Cow_str(c); + const _31: rt.StrCow = new rt.Borrowed(first); + const _32: rt.StrCow = s; + const _35: rt.StrCow = new rt.Borrowed(s); + const _37: rt.StrCow = new rt.Borrowed(first); + const _38: rt.StrCow = ""; + const _39: rt.StrCow = rt.stringExtendChars("", rt.iterRev(rt.strChars(first))); + const iterS: rt.StrCow[] = [_27, _29, _31, _32, _35, _37, _38, new rt.Borrowed("fallback"), _39]; + for (const _45 of iterS) { + rt.byteVecPush(out, borrows(_45)); + put(out, rt.strCowStr(_45)); + } + let m: rt.StrCow = cow(first, owned); + m = rt.strCowStr(m); + rt.byteVecPush(out, borrows(m)); + put(out, rt.strCowStr(cow(rest, owned))); + const iter2: rt.Iter<[string, string]> = rt.iterSlice([["", rest] as [string, string], [first, ""] as [string, string], [first, rest] as [string, string], ["", ""] as [string, string]]); + while (true) { + const _74: [string, string] | rt.Done = iter2.next(); + if (_74 === rt.DONE) { + break; + } + const iter3: rt.Iter<[boolean, boolean]> = rt.iterSlice([[false, false] as [boolean, boolean], [false, true] as [boolean, boolean], [true, false] as [boolean, boolean], [true, true] as [boolean, boolean]]); + while (true) { + const _86: [boolean, boolean] | rt.Done = iter3.next(); + if (_86 === rt.DONE) { + break; + } + const lo: boolean = _86[0]; + const ro: boolean = _86[1]; + const joined: rt.StrCow = rt.strCowAdd(cow(_74[0], lo), cow(_74[1], ro)); + rt.byteVecPush(out, borrows(joined)); + put(out, rt.strCowStr(joined)); + let j: rt.StrCow = cow(_74[0], lo); + j = rt.strCowAdd(j, new rt.Borrowed(_74[1])); + rt.byteVecPush(out, borrows(j)); + rt.byteVecPush(out, borrows(rt.strCowAdd(cow(_74[0], lo), new rt.Borrowed(_74[1])))); + } + } + } else if (_21 === 1) { + let c2: rt.StrCow = cow(s, owned); + if (c2 instanceof rt.Borrowed) { + put(out, (c2 as rt.Borrowed).slice); + } else { + rt.byteVecPush(out, 238); + put(out, c2 as string); + } + if (!(c2 instanceof rt.Borrowed)) { + (c2 as string) = (c2 as string) + "!"; + } + put(out, rt.strCowStr(c2)); + const kept: rt.StrCow = rt.strCowClone(c2); + if (c2 instanceof rt.Borrowed) { + (c2 as rt.Borrowed).slice = first; + } + if (!(c2 instanceof rt.Borrowed)) { + const _137: string = rt.stringExtendChars("", rt.iterRev(rt.strChars(first))); + (c2 as string) = _137; + } + put(out, rt.strCowStr(c2)); + put(out, rt.strCowStr(kept)); + rt.byteVecPush(out, borrows(kept)); + let e: rt.StrCow = cow(s, !owned); + const kept2: rt.StrCow = rt.strCowClone(e); + if (e instanceof rt.Borrowed) { + const borrowed = e as rt.Borrowed; + repointStr(rt.place(() => borrowed.slice as string, (value) => borrowed.slice = value), rest); + put(out, borrowed.slice); + borrowed.slice = first; + } else { + const _162: rt.usize = option_mapOr$1612aex(rt.iterToOption(rt.strChars(e as string).next()), 0, (a05: rt.char): rt.usize => { + return rt.charLenUtf8(a05); + }); + (e as string) = rt.stringTruncate(e as string, _162); + (e as string) = (e as string) + "?"; + } + put(out, rt.strCowStr(e)); + put(out, rt.strCowStr(kept2)); + if (c2 instanceof rt.Borrowed) { + made2 = rt.strToUppercase((c2 as rt.Borrowed).slice); + } else { + made2 = c2 as string; + } + put(out, made2); + let d: rt.StrCow = cow(first, !owned); + b18: { + if (d instanceof rt.Borrowed) { + if (!((d as rt.Borrowed).slice.length === 0)) { + _184 = rt.stringExtendChars("", rt.iterSkip(rt.strChars((d as rt.Borrowed).slice), 1)); + break b18; + } + } + _184 = d; + } + d = _184; + rt.byteVecPush(out, borrows(d)); + put(out, rt.strCowStr(d)); + const _199: rt.StrCow | undefined = cow(rest, owned); + if (_199 !== undefined) { + if (_199 instanceof rt.Borrowed) { + put(out, (_199 as rt.Borrowed).slice); + } + } + } else { + const parts: rt.StrCow[] = rt.iterToArray(rt.iterMap(rt.iterEnumerate(rt.strSplit(s, "/")), (_2: [rt.usize, string]): rt.StrCow => { + return cow(_2[1], rt.usizeRem(_2[0], 2) === 1 === owned); + }), "next", 12); + for (const _216 of parts) { + rt.byteVecPush(out, borrows(_216)); + } + put(out, rt.strsJoin(parts.map(rt.strCowStr), "+")); + put(out, rt.strsJoin(parts.map(rt.strCowStr), "")); + const collected: string = rt.stringFromStrings(rt.iterMap(rt.iterMap(rt.iterRev(rt.iterSlice(parts)), (a0: rt.StrCow): rt.StrCow => { + return rt.strCowClone(a0); + }), rt.strCowStr)); + let extended: string = rest; + const _244: rt.Iter = rt.iterMap(rt.iterSlice(parts), (a02: rt.StrCow): rt.StrCow => { + return rt.strCowClone(a02); + }); + extended = rt.stringExtend(extended, rt.iterMap(_244, rt.strCowStr)); + const _250: [string, rt.usize[]] = rt.tupleExtend(["", []] as [string, rt.usize[]], rt.iterMap(rt.iterSlice(parts), (p: rt.StrCow): [rt.StrCow, rt.usize] => { + return [rt.strCowClone(p), rt.stringLen(rt.strCowStr(p))] as [rt.StrCow, rt.usize]; + }), [rt.STRING_COW_PART, rt.vecPart(4)]); + const texts: string = _250[0]; + const lens: rt.usize[] = _250[1]; + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, collected), (f: rt.Formatter) => rt.fmtPad(f, extended), (f: rt.Formatter) => rt.fmtPad(f, texts), (f: rt.Formatter) => rt.fmtList(lens, f, rt.usizeDebug)]; + put(out, rt.fmtFormat([args2[0], "|", args2[1], "|", args2[2], "|", args2[3]])); + const counts: rt.BTreeMap = rt.btreeNew(rt.cowBy(rt.cmpStr)) as rt.BTreeMap; + const iter5: rt.Iter = rt.iterRev(rt.iterSlice(parts)); + while (true) { + const _280: rt.StrCow | rt.Done = iter5.next(); + if (_280 === rt.DONE) { + break; + } + const _287: rt.StrCow = rt.strCowClone(_280); + const _284: rt.Place = rt.entryOrInsertMut(rt.entry(counts, _287), 0); + _284.v = rt.u8Add(_284.v, 1); + } + const iter6: rt.Iter<[rt.StrCow, rt.u8]> = rt.btreeIter(counts); + while (true) { + const _292: [rt.StrCow, rt.u8] | rt.Done = iter6.next(); + if (_292 === rt.DONE) { + break; + } + rt.byteVecPush(out, borrows(_292[0])); + rt.byteVecPush(out, _292[1]); + } + const _301: [rt.StrCow, rt.u8] | undefined = rt.btreeGetKeyValue(counts, first); + if (_301 !== undefined) { + rt.byteVecPush(out, borrows(_301[0])); + } + const iter7: rt.Iter<[rt.StrCow, rt.u8]> = rt.btreeRange(counts, [{ tag: "Included", _0: first } as rt.RangeBound, { tag: "Unbounded" } as rt.RangeBound] as [rt.RangeBound, rt.RangeBound]); + while (true) { + const _314: [rt.StrCow, rt.u8] | rt.Done = iter7.next(); + if (_314 === rt.DONE) { + break; + } + put(out, rt.strCowStr(_314[0])); + rt.byteVecPush(out, _314[1]); + } + const iter8: rt.Iter<[rt.StrCow, rt.Place]> = rt.btreeRangeMut(counts, [{ tag: "Excluded", _0: rest } as rt.RangeBound, { tag: "Unbounded" } as rt.RangeBound] as [rt.RangeBound, rt.RangeBound]); + while (true) { + const _330: [rt.StrCow, rt.Place] | rt.Done = iter8.next(); + if (_330 === rt.DONE) { + break; + } + _330[1].v = rt.u8Add(_330[1].v, 1); + } + const _336: rt.Iter = rt.iterMap(rt.btreeKeys(counts), (a03: rt.StrCow): rt.StrCow => { + return rt.strCowClone(a03); + }); + rt.iterExtendBytes(out, rt.iterMap(rt.btreeRangeKeys(rt.btreeExtendKeys(rt.btreeNew(rt.cowBy(rt.cmpStr)) as rt.BTreeSet, _336, true, undefined, undefined, 12, true), [{ tag: "Unbounded" } as rt.RangeBound, { tag: "Included", _0: rest } as rt.RangeBound] as [rt.RangeBound, rt.RangeBound]), borrows), "next"); + const _347: rt.Iter<[rt.StrCow, rt.u8]> = rt.btreeIter(counts); + const hashed: rt.HashMap = rt.hashExtend(rt.hashNew(rt.hashing(rt.hashCow(rt.hashStr), rt.cowBy(rt.same), true)) as rt.HashMap, _347, undefined, undefined, undefined, 58720256); + rt.byteVecPush(out, option_unwrapOr$0r2bmpr(rt.hashGet(hashed, first), 255)); + rt.byteVecPush(out, rt.hashContains(hashed, rest) ? 1 : 0); + const both: rt.HashSet = rt.hashNew(rt.hashing(rt.hashCow(rt.hashStr), rt.cowBy(rt.same), true)) as rt.HashSet; + const _361: rt.StrCow = cow(first, owned); + rt.hashAdd(both, _361); + const _366: rt.StrCow = cow(first, !owned); + rt.byteVecPush(out, rt.hashAdd(both, _366) ? 1 : 0); + rt.byteVecPush(out, rt.hashLen(both) & 0xff); + const named: Named.Named = Named.new(first, kind); + rt.byteVecPush(out, borrows(named.name)); + Named.tag(named, 120); + rt.byteVecPush(out, borrows(named.name)); + const o2: rt.StrCow | undefined = rt.iterNth(rt.iterSlice(parts), 1); + rt.byteVecPush(out, option_mapOr$1nwl1hk(o2, 2, borrows)); + rt.byteVecPush(out, option_mapOr$0lcrgk1(option_map$00b7lvf(o2, (a04: rt.StrCow): string => { + return rt.strCowStr(a04); + }), 0, (t: string): rt.u8 => { + return rt.stringLen(t) & 0xff; + })); + } +} + /** A slice of `u16`s read from `data`, two bytes each. */ export function words(data: rt.ByteSlice): rt.u16[] { return rt.iterToArray(rt.iterMap(rt.byteSliceChunks(data, 2), (c: rt.ByteSlice): rt.u16 => { @@ -312,7 +547,7 @@ export function slices(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { } else if (_15 === 3) { let c3: rt.Cow = cowOf(v, owned); if (c3 instanceof rt.Borrowed) { - putWords(out, (c3 as rt.Borrowed).slice); + putWords(out, (c3 as rt.Borrowed).slice); } else { rt.byteVecPush(out, 238); putWords(out, c3 as rt.u16[]); @@ -323,7 +558,7 @@ export function slices(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { putWords(out, rt.cowSlice(c3)); const kept: rt.Cow = rt.cowClone(c3, 2); if (c3 instanceof rt.Borrowed) { - (c3 as rt.Borrowed).slice = half; + (c3 as rt.Borrowed).slice = half; } if (!(c3 instanceof rt.Borrowed)) { const _302: rt.u16[] = rt.iterToArray(rt.iterRev(rt.iterSlice(half)), undefined, 2, true); @@ -334,7 +569,7 @@ export function slices(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { let e: rt.Cow = cowOf(v, !owned); const kept2: rt.Cow = rt.cowClone(e, 2); if (e instanceof rt.Borrowed) { - const borrowed = e as rt.Borrowed; + const borrowed = e as rt.Borrowed; repoint$u16(rt.place(() => borrowed.slice as rt.u16[], (value5) => borrowed.slice = value5), half); rt.byteVecPush(out, borrowed.slice.length & 0xff); borrowed.slice = rt.sliceRange(v, 0, rt.min(v.length, 1)); @@ -348,7 +583,7 @@ export function slices(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { putWords(out, rt.cowSlice(e)); putWords(out, rt.cowSlice(kept2)); if (c3 instanceof rt.Borrowed) { - made = rt.iterToArray(rt.iterRev(rt.iterSlice((c3 as rt.Borrowed).slice)), undefined, 2, true); + made = rt.iterToArray(rt.iterRev(rt.iterSlice((c3 as rt.Borrowed).slice)), undefined, 2, true); } else { made = c3 as rt.u16[]; } @@ -356,8 +591,8 @@ export function slices(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { let d: rt.Cow = cowOf(half, !owned); b16: { if (d instanceof rt.Borrowed) { - if ((d as rt.Borrowed).slice.length > 1) { - _359 = rt.vecExact(rt.sliceRange((d as rt.Borrowed).slice, 1).slice(), 2); + if ((d as rt.Borrowed).slice.length > 1) { + _359 = rt.vecExact(rt.sliceRange((d as rt.Borrowed).slice, 1).slice(), 2); break b16; } } @@ -422,7 +657,7 @@ export function slices(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { const _490: rt.Place> = rt.place(() => c4, (value) => c4 = value); if (_490.v instanceof rt.Borrowed) { const _493: Tally.Tally[] = rt.sliceRange(items, rt.min(items.length, 1)); - (_490.v as rt.Borrowed).slice = _493; + (_490.v as rt.Borrowed).slice = _493; } rt.byteVecPush(out, rt.cowSlice(c4).length & 0xff); const _506: rt.Place> = rt.place(() => c4, (value2) => c4 = value2); @@ -705,9 +940,9 @@ export function cloneFroms(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): vo const _179: [string, string] = [text(left), text(right)]; const to: string = _179[0]; const from4: string = _179[1]; - let c5: string = cow(to, intoOwned); - c5 = cow(from4, fromOwned); - put(out, c5); + let c5: rt.StrCow = cow(to, intoOwned); + c5 = rt.strCowClone(cow(from4, fromOwned)); + put(out, rt.strCowStr(c5)); } } diff --git a/ts/fixtures/lang/cows/expected/lib.ts b/ts/fixtures/lang/cows/expected/lib.ts index 120462f2f..4e740a9b5 100644 --- a/ts/fixtures/lang/cows/expected/lib.ts +++ b/ts/fixtures/lang/cows/expected/lib.ts @@ -1,14 +1,14 @@ // Generated by rust2-ts. Do not edit. import * as rt from "./rt.ts"; -import { cows, slices, bytes, cloneFroms } from "./cows.ts"; +import { cows, variants, slices, bytes, cloneFroms } from "./cows.ts"; /** * Each case's name and what it runs. The first two bytes, big-endian, pick one modulo * their count, so a case added at the end moves no other. */ export function cases(): [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined][] { - return [["cows::cows", cows as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::slices", slices as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::bytes", bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::clone_froms", cloneFroms as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; + return [["cows::cows", cows as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::slices", slices as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::bytes", bytes as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::clone_froms", cloneFroms as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined], ["cows::variants", variants as (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined] as [string, (a0: rt.u8, a1: rt.ByteSlice, a2: rt.ByteVec) => undefined]]; } //# sourceMappingURL=lib.ts.map diff --git a/ts/fixtures/lang/cows/src/cows.rs b/ts/fixtures/lang/cows/src/cows.rs index 2419451ba..d50784640 100644 --- a/ts/fixtures/lang/cows/src/cows.rs +++ b/ts/fixtures/lang/cows/src/cows.rs @@ -6,8 +6,9 @@ use std::borrow::{Borrow, Cow}; use std::cell::Cell; use std::cmp::Ordering; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::hash::{DefaultHasher, Hash, Hasher}; +use std::ops::Bound; use std::sync::Arc; use crate::harness::{put, text}; @@ -184,6 +185,188 @@ pub fn cows(kind: u8, data: &[u8], out: &mut Vec) { } } +/// Whether `c` borrows, as a match tells. +fn borrows(c: &Cow<'_, str>) -> u8 { + match c { + Cow::Borrowed(_) => 1, + Cow::Owned(_) => 0, + } +} + +/// Point `s` at `to`, through the `&mut` a caller passes. +fn repoint_str<'a>(s: &mut &'a str, to: &'a str) { + *s = to; +} + +/// A clone of `t`, through a generic. +fn cloned(t: &T) -> T { + t.clone() +} + +/// A `Cow`'s variant, as std decides it: kept or changed by what makes, converts, +/// clones, joins, and changes one, matched on by reference, in place, and by value, and +/// held in collections and fields. +pub fn variants(kind: u8, data: &[u8], out: &mut Vec) { + let s = text(data); + let owned = kind & 0x80 != 0; + let first = s.split('/').next().unwrap_or(""); + let rest = s.split('/').next_back().unwrap_or(""); + match kind % 3 { + 0 => { + // What makes one, converts one, and clones one. + let c = cow(&s, owned); + let made: [Cow<'_, str>; 9] = [ + c.clone(), + cloned(&c), + Cow::from(first), + Cow::from(s.clone()), + Cow::from(&s), + first.into(), + Default::default(), + FALLBACK, + first.chars().rev().collect(), + ]; + for m in &made { + out.push(borrows(m)); + put(out, m); + } + // `to_mut` owns, even unchanged, and `into_owned` gives the text either way. + let mut m = cow(first, owned); + let _ = m.to_mut(); + out.push(borrows(&m)); + put(out, &cow(rest, owned).into_owned()); + // `+` and `+=` take the right side when the left is empty, keep the left when the + // right is, and else own what they join. + for (l, r) in [("", rest), (first, ""), (first, rest), ("", "")] { + for (lo, ro) in [(false, false), (false, true), (true, false), (true, true)] { + let joined = cow(l, lo) + cow(r, ro); + out.push(borrows(&joined)); + put(out, &joined); + let mut j = cow(l, lo); + j += r; + out.push(borrows(&j)); + let k = cow(l, lo) + r; + out.push(borrows(&k)); + } + } + } + 1 => { + // Matched on, by reference, in place, and by value. + let mut c = cow(&s, owned); + match &c { + Cow::Borrowed(b) => put(out, b), + Cow::Owned(o) => { + out.push(0xee); + put(out, o); + } + } + if let Cow::Owned(o) = &mut c { + o.push('!'); + } + put(out, &c); + // A payload replaced whole, which leaves a clone as it was. + let kept = c.clone(); + if let Cow::Borrowed(b) = &mut c { + *b = first; + } + if let Cow::Owned(o) = &mut c { + *o = first.chars().rev().collect(); + } + put(out, &c); + put(out, &kept); + out.push(borrows(&kept)); + // A payload passed on as a `&mut`, then replaced and read through the same borrow. + let mut e = cow(&s, !owned); + let kept = e.clone(); + match &mut e { + Cow::Borrowed(b) => { + repoint_str(b, rest); + put(out, b); + *b = first; + } + Cow::Owned(o) => { + o.truncate(o.chars().next().map_or(0, char::len_utf8)); + o.push('?'); + } + } + put(out, &e); + put(out, &kept); + let made: String = match c { + Cow::Borrowed(b) => b.to_uppercase(), + Cow::Owned(o) => o, + }; + put(out, &made); + let mut d = cow(first, !owned); + d = match d { + Cow::Borrowed(b) if !b.is_empty() => Cow::Owned(b.chars().skip(1).collect()), + d => d, + }; + out.push(borrows(&d)); + put(out, &d); + if let Some(Cow::Borrowed(b)) = Some(cow(rest, owned)) { + put(out, b); + } + } + _ => { + // Held in a `Vec`, an `Option`, a field, and a B-tree, which keeps the key it had. + let parts: Vec> = s + .split('/') + .enumerate() + .map(|(i, p)| cow(p, (i % 2 == 1) == owned)) + .collect(); + for p in &parts { + out.push(borrows(p)); + } + put(out, &parts.join("+")); + put(out, &parts.concat()); + let collected: String = parts.iter().rev().cloned().collect(); + let mut extended = String::from(rest); + extended.extend(parts.iter().cloned()); + let (texts, lens): (String, Vec) = parts.iter().map(|p| (p.clone(), p.len())).unzip(); + put(out, &format!("{collected}|{extended}|{texts}|{lens:?}")); + let mut counts: BTreeMap, u8> = BTreeMap::new(); + for p in parts.iter().rev() { + *counts.entry(p.clone()).or_insert(0) += 1; + } + for (k, n) in &counts { + out.push(borrows(k)); + out.push(*n); + } + if let Some((k, _)) = counts.get_key_value(first) { + out.push(borrows(k)); + } + // Ranges and lookups by what the keys borrow. + for (k, n) in counts.range::((Bound::Included(first), Bound::Unbounded)) { + put(out, k); + out.push(*n); + } + for (_, n) in counts.range_mut::((Bound::Excluded(rest), Bound::Unbounded)) { + *n += 1; + } + let keys: BTreeSet> = counts.keys().cloned().collect(); + out.extend( + keys.range::((Bound::Unbounded, Bound::Included(rest))) + .map(borrows), + ); + let hashed: HashMap, u8> = counts.into_iter().collect(); + out.push(hashed.get(first).copied().unwrap_or(0xff)); + out.push(hashed.contains_key(rest) as u8); + // Equal keys of either variant are one key. + let mut both: HashSet> = HashSet::new(); + both.insert(cow(first, owned)); + out.push(both.insert(cow(first, !owned)) as u8); + out.push(both.len() as u8); + let mut named = Named::new(first, kind); + out.push(borrows(&named.name)); + named.tag('x'); + out.push(borrows(&named.name)); + let o: Option> = parts.into_iter().nth(1); + out.push(o.as_ref().map_or(2, borrows)); + out.push(o.map(Cow::into_owned).map_or(0, |t| t.len() as u8)); + } + } +} + /// A slice of `u16`s read from `data`, two bytes each. fn words(data: &[u8]) -> Vec { data.chunks(2) diff --git a/ts/fixtures/lang/cows/src/lib.rs b/ts/fixtures/lang/cows/src/lib.rs index 69b4a06fd..45a39d3b9 100644 --- a/ts/fixtures/lang/cows/src/lib.rs +++ b/ts/fixtures/lang/cows/src/lib.rs @@ -20,5 +20,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("cows::slices", cows::slices), ("cows::bytes", cows::bytes), ("cows::clone_froms", cows::clone_froms), + ("cows::variants", cows::variants), ] } diff --git a/ts/fixtures/lang/iters/expected/core/option.ts b/ts/fixtures/lang/iters/expected/core/option.ts index eadc72b5d..d2a64be4b 100644 --- a/ts/fixtures/lang/iters/expected/core/option.ts +++ b/ts/fixtures/lang/iters/expected/core/option.ts @@ -68,6 +68,13 @@ export function map$1httady(self: string[] | undefined, f: (a0: string[]) => str return f(self); } +export function map$00b7lvf(self: rt.StrCow | undefined, _: (a0: rt.StrCow) => string): string | undefined { + if (self === undefined) { + return undefined; + } + return rt.strCowStr(self); +} + export function map$0of9gds(self: rt.u8 | undefined, f: (a0: rt.u8) => Badge.Badge): Badge.Badge | undefined { if (self === undefined) { return undefined; @@ -257,6 +264,12 @@ export function asDeref$0w8at8i(self: string | undefined): string | undefined { }); } +export function asDeref$1qc2lbh(self: rt.StrCow | undefined): string | undefined { + return map$00b7lvf(self, (a0: rt.StrCow): string => { + return rt.strCowStr(a0); + }); +} + export function filter$0v80fk8(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/iters/expected/iters.ts b/ts/fixtures/lang/iters/expected/iters.ts index 2a1745387..7c9841334 100644 --- a/ts/fixtures/lang/iters/expected/iters.ts +++ b/ts/fixtures/lang/iters/expected/iters.ts @@ -3,7 +3,7 @@ import * as rt from "./rt.ts"; import { drop$iters_Ticks, of$u8 } from "./lib.ts"; import { thenSome$185ufyt, thenSome$0b23a33, thenSome$0d5am30, thenSome$1j8foe9 } from "./core/bool.ts"; -import { unwrapOr$0k15iai as option_unwrapOr$0k15iai, unwrapOr$00uis1j as option_unwrapOr$00uis1j, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, unwrapOr$14m5xjm as option_unwrapOr$14m5xjm, unwrapOr$1tvry1i as option_unwrapOr$1tvry1i, unwrapOrDefault$165pyh5 as option_unwrapOrDefault$165pyh5, unwrapOrDefault$0wx44sw as option_unwrapOrDefault$0wx44sw, map$1uo1d4o as option_map$1uo1d4o, map$1httady as option_map$1httady, map$0of9gds as option_map$0of9gds, map$1c7zybz as option_map$1c7zybz, map$0cs071d as option_map$0cs071d, map$0p22lts as option_map$0p22lts, map$05nbzug as option_map$05nbzug, mapOr$0lcrgk1 as option_mapOr$0lcrgk1, mapOr$1kiffmd as option_mapOr$1kiffmd, mapOr$1a3irjf as option_mapOr$1a3irjf, mapOr$0sl5app as option_mapOr$0sl5app, mapOr$0atjiti as option_mapOr$0atjiti, mapOr$0okqj9y as option_mapOr$0okqj9y, mapOr$1e4a1e0 as option_mapOr$1e4a1e0, mapOr$1lznlgz as option_mapOr$1lznlgz, mapOr$0tmtgfi as option_mapOr$0tmtgfi, mapOr$0ev76sb as option_mapOr$0ev76sb, mapOr$1surhce as option_mapOr$1surhce, mapOr$1t0muvr as option_mapOr$1t0muvr, mapOr$0a96v80 as option_mapOr$0a96v80, mapOr$1vjqo89 as option_mapOr$1vjqo89, mapOr$1h04da8 as option_mapOr$1h04da8, mapOr$0f73bb7 as option_mapOr$0f73bb7, mapOr$0mmpeqx as option_mapOr$0mmpeqx, mapOr$06gg5x7 as option_mapOr$06gg5x7, mapOr$1dmz9kd as option_mapOr$1dmz9kd, mapOr$1sxa8ki as option_mapOr$1sxa8ki, mapOr$02k4ito as option_mapOr$02k4ito, asDeref$0w8at8i as option_asDeref$0w8at8i, filter$0v80fk8 } from "./core/option.ts"; +import { unwrapOr$0k15iai as option_unwrapOr$0k15iai, unwrapOr$00uis1j as option_unwrapOr$00uis1j, unwrapOr$0r2bmpr as option_unwrapOr$0r2bmpr, unwrapOr$14m5xjm as option_unwrapOr$14m5xjm, unwrapOr$1tvry1i as option_unwrapOr$1tvry1i, unwrapOrDefault$165pyh5 as option_unwrapOrDefault$165pyh5, unwrapOrDefault$0wx44sw as option_unwrapOrDefault$0wx44sw, map$1uo1d4o as option_map$1uo1d4o, map$1httady as option_map$1httady, map$0of9gds as option_map$0of9gds, map$1c7zybz as option_map$1c7zybz, map$0cs071d as option_map$0cs071d, map$0p22lts as option_map$0p22lts, map$05nbzug as option_map$05nbzug, mapOr$0lcrgk1 as option_mapOr$0lcrgk1, mapOr$1kiffmd as option_mapOr$1kiffmd, mapOr$1a3irjf as option_mapOr$1a3irjf, mapOr$0sl5app as option_mapOr$0sl5app, mapOr$0atjiti as option_mapOr$0atjiti, mapOr$0okqj9y as option_mapOr$0okqj9y, mapOr$1e4a1e0 as option_mapOr$1e4a1e0, mapOr$1lznlgz as option_mapOr$1lznlgz, mapOr$0tmtgfi as option_mapOr$0tmtgfi, mapOr$0ev76sb as option_mapOr$0ev76sb, mapOr$1surhce as option_mapOr$1surhce, mapOr$1t0muvr as option_mapOr$1t0muvr, mapOr$0a96v80 as option_mapOr$0a96v80, mapOr$1vjqo89 as option_mapOr$1vjqo89, mapOr$1h04da8 as option_mapOr$1h04da8, mapOr$0f73bb7 as option_mapOr$0f73bb7, mapOr$0mmpeqx as option_mapOr$0mmpeqx, mapOr$06gg5x7 as option_mapOr$06gg5x7, mapOr$1dmz9kd as option_mapOr$1dmz9kd, mapOr$1sxa8ki as option_mapOr$1sxa8ki, mapOr$02k4ito as option_mapOr$02k4ito, asDeref$0w8at8i as option_asDeref$0w8at8i, asDeref$1qc2lbh as option_asDeref$1qc2lbh, filter$0v80fk8 } from "./core/option.ts"; import { unwrapOr$01jdcm7 as result_unwrapOr$01jdcm7 } from "./core/result.ts"; import type * as Badge from "./iters.Badge.ts"; import * as Bag from "./iters.Bag.ts"; @@ -485,17 +485,17 @@ export function peekedOthers(data: rt.ByteSlice, out: rt.ByteVec): void { return v4[rt.idx(v4, 0)]; }); rt.byteVecPush(out, _4(part10)); - const it5: rt.Peekable = rt.iterPeekable(rt.iterMap(rt.iterSlice(words), (w3: string): string => { - return w3; + const it5: rt.Peekable = rt.iterPeekable(rt.iterMap(rt.iterSlice(words), (w3: string): rt.StrCow => { + return new rt.Borrowed(w3); })); - const part11 = option_asDeref$0w8at8i(rt.peekableNextIfEq(it5, "b", (a011: string, a17: string): boolean => { - return a011 === a17; + const part11 = option_asDeref$1qc2lbh(rt.peekableNextIfEq(it5, "b", (a011: rt.StrCow, a17: string): boolean => { + return rt.strCowStr(a011) === a17; })); rt.byteVecPush(out, _4(part11)); - const part12 = option_asDeref$0w8at8i(rt.iterToOption(it5.next())); + const part12 = option_asDeref$1qc2lbh(rt.iterToOption(it5.next())); rt.byteVecPush(out, _4(part12)); - const part13 = option_asDeref$0w8at8i(rt.peekableNextIfEq(rt.iterPeekable(rt.iterSlice(rt.vecExact(words.slice(), 12))), first, (a012: string, a18: string): boolean => { - return a012 === a18; + const part13 = option_asDeref$0w8at8i(rt.peekableNextIfEq(rt.iterPeekable(rt.iterSlice(rt.vecExact(words.slice(), 12))), first, (a012: string, a18: rt.StrCow): boolean => { + return a012 === rt.strCowStr(a18); })); rt.byteVecPush(out, _4(part13)); const it7: rt.Peekable = rt.iterPeekable(rt.iterMap(rt.iterSlice(rt.byteSliceView(data)), (_26: rt.u8): rt.u16[] => { diff --git a/ts/fixtures/lang/joins/expected/joins.ts b/ts/fixtures/lang/joins/expected/joins.ts index d5e000673..4ada46c81 100644 --- a/ts/fixtures/lang/joins/expected/joins.ts +++ b/ts/fixtures/lang/joins/expected/joins.ts @@ -224,14 +224,14 @@ export function joins(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { }), undefined, 12, true); wides(rt.slicesJoin(wide3, 2, undefined, rt.cowSlice), out); wides(rt.slicesJoin(wide3, 2, [65535], rt.cowSlice), out); - const texts: string[] = rt.iterToArray(rt.iterMap(rt.iterEnumerate(rt.iterSlice(parts)), (_214: [rt.usize, rt.ByteSlice]): string => { + const texts: rt.StrCow[] = rt.iterToArray(rt.iterMap(rt.iterEnumerate(rt.iterSlice(parts)), (_214: [rt.usize, rt.ByteSlice]): rt.StrCow => { if (rt.usizeRem(_214[0], 2) === 0) { - return "ab"; + return new rt.Borrowed("ab"); } return text(_214[1]); }), undefined, 12, true); - put(out, rt.strsJoin(texts, "")); - put(out, rt.strsJoin(texts, "--")); + put(out, rt.strsJoin(texts.map(rt.strCowStr), "")); + put(out, rt.strsJoin(texts.map(rt.strCowStr), "--")); } else { const big: rt.ByteVec = rt.byteVecOf(1, 1 << 16); const wide4: rt.u16[] = rt.vecFromElem(1, 1 << 16, undefined); diff --git a/ts/fixtures/lang/shapes/expected/shapes.ts b/ts/fixtures/lang/shapes/expected/shapes.ts index 06bcd50eb..c91b98aa8 100644 --- a/ts/fixtures/lang/shapes/expected/shapes.ts +++ b/ts/fixtures/lang/shapes/expected/shapes.ts @@ -1431,29 +1431,29 @@ export function payloads(_dataData: Uint8Array, _dataStart: number, dataLen: num let res: rt.Result = part === undefined ? rt.Err(out$u8.err as rt.u8) : rt.Ok(part); const before: rt.Result = res; if (!res.ok) { - res = rt.Err(rt.u8WrappingAdd(res.error, 1)); + res = { ok: false, error: rt.u8WrappingAdd(res.error, 1) }; } if (res.ok) { - res = rt.Ok(res.value ^ 85); + res = { ok: true, value: res.value ^ 85 }; } let err: rt.Result = rt.Err(n); if (!err.ok) { - err = rt.Err(rt.u8WrappingAdd(err.error, 3)); + err = { ok: false, error: rt.u8WrappingAdd(err.error, 3) }; } let owned: rt.Result = rt.Ok(n); if (owned.ok) { - owned = rt.Ok(rt.u8WrappingAdd(owned.value, 2)); + owned = { ok: true, value: rt.u8WrappingAdd(owned.value, 2) }; } const part2: rt.u8 | undefined = parity(rt.u8WrappingAdd(n, 1)); const err2: rt.u8 | undefined = part2 === undefined ? out$u8.err as rt.u8 : undefined; const slot: Slot.Slot = { res: (err2 !== undefined ? rt.Err(err2 as rt.u8) : rt.Ok(part2 as rt.u8)) as rt.Result }; if (!slot.res.ok) { const _33: rt.u8 = rt.u8WrappingSub(slot.res.error, 4); - slot.res = rt.Err(_33); + slot.res = { ok: false, error: _33 }; } let flow: rt.Result = rt.Err(n); if (!flow.ok) { - flow = rt.Err(rt.u8WrappingAdd(flow.error, 9)); + flow = { ok: false, error: rt.u8WrappingAdd(flow.error, 9) }; } let opt: rt.u8 | undefined = n; if (opt !== undefined) { @@ -1469,7 +1469,7 @@ export function payloads(_dataData: Uint8Array, _dataStart: number, dataLen: num if (nested !== rt.Pending) { if (nested !== undefined) { if (!nested.ok) { - nested = rt.Err(rt.u8WrappingAdd(nested.error, 6)); + nested = { ok: false, error: rt.u8WrappingAdd(nested.error, 6) }; } } } @@ -1477,7 +1477,7 @@ export function payloads(_dataData: Uint8Array, _dataStart: number, dataLen: num let caught: rt.Result = part4 === undefined ? rt.Err(out$u8.err as rt.u8) : rt.Ok(part4); const bump: () => undefined = (): undefined => { if (!caught.ok) { - caught = rt.Err(rt.u8WrappingAdd(caught.error, 5)); + caught = { ok: false, error: rt.u8WrappingAdd(caught.error, 5) }; } }; bump(); @@ -1486,12 +1486,12 @@ export function payloads(_dataData: Uint8Array, _dataStart: number, dataLen: num let replaced: rt.Result = part5 === undefined ? rt.Err(out$u8.err as rt.u8) : rt.Ok(part5); if (replaced.ok) { const old = replaced.value; - replaced = rt.Ok(66); + replaced = { ok: true, value: 66 }; rt.byteVecPush(out, old); } let named: rt.Result = rt.Ok(""); if (named.ok) { - named = rt.Ok(named.value + "r"); + named = { ok: true, value: named.value + "r" }; } if (named.ok) { _86 = rt.stringLen(named.value) & 0xff; @@ -2482,7 +2482,7 @@ export function flows(data: rt.ByteSlice, out: rt.ByteVec): void { const err = unset(); let set: rt.Result = err !== undefined ? rt.Err(err as rt.u8) : rt.Ok(out$Option_u8.p); if (set.ok) { - set = rt.Ok(a); + set = { ok: true, value: a }; } const _108: rt.Result = set; const err2: rt.u8 | undefined = unset(); diff --git a/ts/fixtures/lang/shares/expected/shares.ts b/ts/fixtures/lang/shares/expected/shares.ts index 9bf551649..7f689eb0d 100644 --- a/ts/fixtures/lang/shares/expected/shares.ts +++ b/ts/fixtures/lang/shares/expected/shares.ts @@ -570,8 +570,8 @@ export function strs(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { let source: string = s; const fromMut: rt.Arc = new rt.Arc(source); source = rt.strToAsciiUppercase(source); - const borrowed: rt.Arc = new rt.Arc(s); - const owned: rt.Arc = new rt.Arc(s); + const borrowed: rt.Arc = new rt.Arc(rt.strCowStr(new rt.Borrowed(s))); + const owned: rt.Arc = new rt.Arc(rt.strCowStr(s)); const into: rt.Arc = new rt.Arc(s); const empty: rt.Arc = new rt.Arc(""); const arcEmpty: rt.Arc = new rt.Arc(""); diff --git a/ts/fixtures/lang/strs/expected/strs.ts b/ts/fixtures/lang/strs/expected/strs.ts index 478718c21..03762fd2c 100644 --- a/ts/fixtures/lang/strs/expected/strs.ts +++ b/ts/fixtures/lang/strs/expected/strs.ts @@ -1255,14 +1255,14 @@ export function iters(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { }))); rt.iterEmpty(); put(out, rt.stringFromStrings(rt.iterEmpty() as rt.Iter)); - put(out, rt.stringExtendChars("", rt.iterRev(rt.strChars(s)))); + put(out, rt.strCowStr(rt.stringExtendChars("", rt.iterRev(rt.strChars(s))))); put(out, rt.stringExtend("", rt.iterSlice(parts))); const owned: string[] = rt.iterToArray(rt.iterMap(rt.iterSlice(parts), (p6: string): string => { return p6; }), undefined, 12, true); - put(out, rt.stringFromStrings(rt.iterMap(rt.iterSlice(owned), (p7: string): string => { - return p7; - }))); + put(out, rt.stringFromStrings(rt.iterMap(rt.iterMap(rt.iterSlice(owned), (p7: string): rt.StrCow => { + return new rt.Borrowed(p7); + }), rt.strCowStr))); put(out, rt.stringFromStrings(rt.iterFrom({ items: owned, nexts }, Counted.next$iter_Iterator_String))); } else if (_21 === 2) { let t: string = "é"; @@ -1287,10 +1287,10 @@ export function iters(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { t2 = rt.stringExtend(t2, _184); const _190: rt.Iter = rt.iterRev(rt.iterSlice(parts)); named.name = rt.stringExtend(named.name, _190); - const _197: rt.Iter = rt.iterMap(rt.iterSlice(parts), (p2: string): string => { + const _197: rt.Iter = rt.iterMap(rt.iterSlice(parts), (p2: string): rt.StrCow => { return p2; }); - named.name = rt.stringExtend(named.name, _197); + named.name = rt.stringExtend(named.name, rt.iterMap(_197, rt.strCowStr)); rt.iterEmpty(); named.name = rt.stringExtend(named.name, rt.iterEmpty() as rt.Iter); const counted4: Counted.Counted = { items: parts, nexts }; diff --git a/ts/fixtures/lang/utf8/expected/utf8.ts b/ts/fixtures/lang/utf8/expected/utf8.ts index 790fb23b5..8c5b66e95 100644 --- a/ts/fixtures/lang/utf8/expected/utf8.ts +++ b/ts/fixtures/lang/utf8/expected/utf8.ts @@ -53,6 +53,7 @@ export function decodes(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void } export function lossy(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { + let _20!: boolean; const bytes: rt.ByteVec = input(kind, data); const iter: rt.RangeInclusive = new rt.RangeInclusive(0, bytes.length, rt.usizeStep); while (true) { @@ -61,24 +62,30 @@ export function lossy(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { break; } const rest: rt.ByteSlice = rt.byteSliceRange(rt.byteVecSlice(bytes), _10); - const text: string = rt.stringFromUtf8Lossy(rest); - const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, text), (f: rt.Formatter) => rt.fmtStr(text, f)]; + const text: rt.StrCow = rt.stringFromUtf8Lossy(rest); + if (text instanceof rt.Borrowed) { + _20 = true; + } else { + _20 = false; + } + rt.byteVecPush(out, _20 ? 1 : 0); + const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtPad(f, rt.strCowStr(text)), (f: rt.Formatter) => rt.fmtStr(rt.strCowStr(text), f)]; put(out, rt.fmtFormat([args2[0], "|", args2[1]])); - rt.byteVecPush(out, rt.stringLen(text) & 0xff); - const owned: string = text; + rt.byteVecPush(out, rt.stringLen(rt.strCowStr(text)) & 0xff); + const owned: string = rt.strCowStr(text); put(out, owned); - put(out, rt.stringFromUtf8Lossy(rt.stringAsBytes(owned))); + put(out, rt.strFromUtf8Unchecked(rt.stringAsBytes(owned))); rt.byteVecExtendFromSlice(out, rt.byteVecSlice(rt.stringIntoBytes(owned))); rt.byteVecExtendFromSlice(out, rt.byteVecSlice(rt.stringIntoBytes(owned))); - put(out, rt.stringFromUtf8Lossy(rt.byteVecSlice(rt.stringIntoBytes(owned)))); + put(out, rt.strFromUtf8Unchecked(rt.byteVecSlice(rt.stringIntoBytes(owned)))); const chunks: rt.Iter = rt.byteSliceUtf8Chunks(rest); const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtUtf8Chunks(chunks, f)]; put(out, rt.fmtFormat([args4[0], "|", rt.fmtSpec({ alternate: true }, args4[0])])); let last: rt.Utf8Chunk | undefined = undefined; while (true) { - const _75: rt.Utf8Chunk | rt.Done = chunks.next(); - if (_75 !== rt.DONE) { - const chunk: rt.Utf8Chunk = _75; + const _79: rt.Utf8Chunk | rt.Done = chunks.next(); + if (_79 !== rt.DONE) { + const chunk: rt.Utf8Chunk = _79; put(out, chunk.valid); rt.byteVecExtendFromSlice(out, chunk.invalid); rt.byteVecPush(out, chunk.invalid.length & 0xff); @@ -88,9 +95,9 @@ export function lossy(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { put(out, rt.fmtFormat([args8[0]])); rt.byteVecPush(out, rt.iterCount(chunks.clone()) & 0xff); rt.byteVecPush(out, lossy_eq$00vlkdw(chunk, chunk) ? 1 : 0); - const _130: rt.Utf8Chunk | undefined = last; - const _133: rt.Utf8Chunk | undefined = chunk; - rt.byteVecPush(out, rt.eqOption(lossy_eq$00vlkdw)(_130, _133) ? 1 : 0); + const _134: rt.Utf8Chunk | undefined = last; + const _137: rt.Utf8Chunk | undefined = chunk; + rt.byteVecPush(out, rt.eqOption(lossy_eq$00vlkdw)(_134, _137) ? 1 : 0); last = chunk; } else { break; @@ -99,11 +106,11 @@ export function lossy(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { const args10: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtUtf8Chunks(chunks, f)]; put(out, rt.fmtFormat([args10[0]])); } - const _148: rt.Result = rt.stringFromUtf8(bytes); - if (_148.ok) { - put(out, _148.value); + const _152: rt.Result = rt.stringFromUtf8(bytes); + if (_152.ok) { + put(out, _152.value); } else { - const e: rt.FromUtf8Error = _148.error; + const e: rt.FromUtf8Error = _152.error; rt.byteVecExtendFromSlice(out, rt.byteVecSlice(e.bytes)); const u: rt.Utf8Error = e.error; rt.byteVecPush(out, u.validUpTo & 0xff); @@ -112,7 +119,7 @@ export function lossy(kind: rt.u8, data: rt.ByteSlice, out: rt.ByteVec): void { })); const args12: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtUtf8ErrorDisplay(u, f)]; put(out, rt.fmtFormat([args12[0]])); - put(out, rt.stringFromUtf8Lossy(rt.byteVecSlice(e.bytes))); + put(out, rt.strCowStr(rt.stringFromUtf8Lossy(rt.byteVecSlice(e.bytes)))); rt.byteVecPush(out, string_eq$09knsz6(rt.fromUtf8ErrorClone(e), e) ? 1 : 0); rt.byteVecExtendFromSlice(out, rt.byteVecSlice(e.bytes)); } diff --git a/ts/fixtures/lang/utf8/src/utf8.rs b/ts/fixtures/lang/utf8/src/utf8.rs index c646ac87a..96fc44fc6 100644 --- a/ts/fixtures/lang/utf8/src/utf8.rs +++ b/ts/fixtures/lang/utf8/src/utf8.rs @@ -51,6 +51,7 @@ pub fn lossy(kind: u8, data: &[u8], out: &mut Vec) { // A `Cow` of the bytes if they are valid, else of a `String` with each invalid // sequence replaced. let text = String::from_utf8_lossy(rest); + out.push(matches!(text, std::borrow::Cow::Borrowed(_)) as u8); put(out, &format!("{text}|{text:?}")); out.push(text.len() as u8); let owned: String = text.into_owned(); diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 2e3ac15ca..38d22cd55 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -268,12 +268,9 @@ fixtures/reject/src/rebound.rs:unboxed_rebound+2:16: a reference to `rebound::Un fixtures/reject/src/statics.rs:pointer_statics+3:15: comparing pointers to `u32` in an atomic, which JS has no address of fixtures/reject/src/statics.rs:pointer_statics+4:3: a constant of type `&u32`, a pointer into memory translation can't name fixtures/reject/src/statics.rs:statics+1:11: a mutable static, which any code may write -fixtures/reject/src/strs.rs:cow_borrowed+1:2: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string +fixtures/reject/src/strs.rs:cow_borrowed+1:2: unsupported call to `std::borrow::Cow::<'_, str>::is_borrowed` fixtures/reject/src/strs.rs:cow_bytes+1:2: unsupported call to `std::borrow::Cow::<'_, [u8]>::is_borrowed` fixtures/reject/src/strs.rs:cow_made+2:53: constructing `std::borrow::Cow<'_, std::sync::Arc>` -fixtures/reject/src/strs.rs:cow_matched+1:2: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string -fixtures/reject/src/strs.rs:cow_matched+2:30: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string -fixtures/reject/src/strs.rs:cow_matched+3:27: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string fixtures/reject/src/strs.rs:str_bytes_mut+1:13: unsupported call to `core::str::::as_bytes_mut` fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unchecked_mut` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/strs.rs:str_from_bytes_mut+1:20: `std::str::from_utf8_mut` with a mutable reference to a value JS copies, which its mapping would copy diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index ed872dd32..3cd991f9b 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -269,12 +269,9 @@ fixtures/reject/src/rebound.rs:unboxed_rebound+2:16: a reference to `rebound::Un fixtures/reject/src/statics.rs:pointer_statics+3:15: comparing pointers to `u32` in an atomic, which JS has no address of fixtures/reject/src/statics.rs:pointer_statics+4:3: a constant of type `&u32`, a pointer into memory translation can't name fixtures/reject/src/statics.rs:statics+1:11: a mutable static, which any code may write -fixtures/reject/src/strs.rs:cow_borrowed+1:2: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string +fixtures/reject/src/strs.rs:cow_borrowed+1:2: unsupported call to `std::borrow::Cow::<'_, str>::is_borrowed` fixtures/reject/src/strs.rs:cow_bytes+1:2: unsupported call to `std::borrow::Cow::<'_, [u8]>::is_borrowed` fixtures/reject/src/strs.rs:cow_made+2:53: constructing `std::borrow::Cow<'_, std::sync::Arc>` -fixtures/reject/src/strs.rs:cow_matched+1:2: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string -fixtures/reject/src/strs.rs:cow_matched+2:30: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string -fixtures/reject/src/strs.rs:cow_matched+3:27: a variant of `std::borrow::Cow<'_, str>`, which is not supported: either is the same JS string fixtures/reject/src/strs.rs:str_bytes_mut+1:13: unsupported call to `core::str::::as_bytes_mut` fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unchecked_mut` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/strs.rs:str_from_bytes_mut+1:20: `std::str::from_utf8_mut` with a mutable reference to a value JS copies, which its mapping would copy diff --git a/ts/fixtures/reject/src/strs.rs b/ts/fixtures/reject/src/strs.rs index 9b171ca97..42afa4956 100644 --- a/ts/fixtures/reject/src/strs.rs +++ b/ts/fixtures/reject/src/strs.rs @@ -1,18 +1,10 @@ //! Strings and `Cow`s, which JS keeps as primitives. -/// A `Cow` is a JS string, which keeps no variant to match on or ask about. -pub fn cow_matched(c: std::borrow::Cow<'_, str>) -> String { - match c { - std::borrow::Cow::Borrowed(s) => s.to_string(), - std::borrow::Cow::Owned(s) => s, - } -} - +/// A `Cow`'s variant, which a match reads, but asking is unstable. pub fn cow_borrowed(c: &std::borrow::Cow<'_, str>) -> bool { std::borrow::Cow::is_borrowed(c) } -/// A slice's tells its variants apart, but asking is unstable. pub fn cow_bytes(c: &std::borrow::Cow<'_, [u8]>) -> bool { std::borrow::Cow::is_borrowed(c) } diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 4fba27a2a..78ba87d8d 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -197,7 +197,6 @@ roots = [ "strs::cow_borrowed", "strs::cow_bytes", "strs::cow_made", - "strs::cow_matched", "strs::str_bytes_mut", "strs::str_from_bytes_mut", "values::addr_parsed", diff --git a/ts/runtime/rt.ts b/ts/runtime/rt.ts index de5c3d472..339708690 100644 --- a/ts/runtime/rt.ts +++ b/ts/runtime/rt.ts @@ -16,6 +16,7 @@ import { drop as dispose, dropAll, partPlace, type Pending } from "./rt/task.ts" import { allocError, CAPACITY_OVERFLOW, type TryReserveError } from "./rt/alloc.ts"; import { AtIndex } from "./rt/collections.ts"; import { escapeDebug } from "./rt/char.ts"; +import { strFromUtf8Unchecked } from "./rt/str.ts"; export * from "./rt/alloc.ts"; export * from "./rt/ascii.ts"; export * from "./rt/bytes.ts"; @@ -2399,26 +2400,28 @@ export function byteVecShrinkTo(v: ByteVec, min: number): void { if (cap !== v.data.length) realloc(v, cap); } -// Cows of slices. An owned one is its `Vec` and a borrowed one its slice, told apart by -// class: a `Cow<[u8]>` is an `rt.ByteVec` or an `rt.ByteSlice`, and any other is an array -// or the `rt.Borrowed` wrapping the array it borrows. +// Cows. An owned one is what it owns and a borrowed one what it borrows, told apart by +// class: a `Cow<[u8]>` is an `rt.ByteVec` or an `rt.ByteSlice`, and any other is the array +// or string it owns or the `rt.Borrowed` wrapping the slice it borrows. /** - * The slice a `Cow<[T]>` borrows, which it clones before it changes. A `&mut` to the - * borrow writes `slice`, so no two `Cow`s share one. + * The slice a `Cow<[T]>` or a `Cow` borrows, which it clones before it changes. A + * `&mut` to the borrow writes `slice`, so no two `Cow`s share one. */ -export class Borrowed { - slice: T[]; +export class Borrowed { + slice: S; - constructor(slice: T[]) { + constructor(slice: S) { this.slice = slice; } } /** `Cow<[T]>`: the `Vec` it owns, or the slice it borrows. */ -export type Cow = T[] | Borrowed; +export type Cow = T[] | Borrowed; /** `Cow<[u8]>`: the `Vec` it owns, or the bytes it borrows. */ export type ByteCow = ByteVec | ByteSlice; +/** `Cow`: the `String` it owns, or the `str` it borrows. */ +export type StrCow = string | Borrowed; /** `Deref` of a `Cow<[T]>`: its slice. */ export function cowSlice(c: Cow): T[] { @@ -2489,14 +2492,40 @@ export function cowDrop(c: Cow, f?: (item: T) => void): void { if (!(c instanceof Borrowed)) dropAll(c, f); } -/** `f` of the slices two `Cow<[T]>`s deref to, as their `PartialEq` and `Ord` compare them. */ -export function cowBy(f: (a: T[], b: T[]) => R): (a: Cow, b: Cow) => R { - return (a, b) => f(cowSlice(a), cowSlice(b)); +/** What a `Cow` other than of bytes derefs to. */ +function cowDeref(c: S | Borrowed): S { + return c instanceof Borrowed ? c.slice : c; +} + +/** `f` of what two `Cow`s deref to, as their `PartialEq` and `Ord` compare them. */ +export function cowBy(f: (a: S, b: S) => R): (a: S | Borrowed, b: S | Borrowed) => R { + return (a, b) => f(cowDeref(a), cowDeref(b)); +} + +/** `Hash::hash` of a `Cow`: what it derefs to's. */ +export function hashCow(hash: (value: B, s: S) => void): (value: B | Borrowed, s: S) => void { + return (value, s) => hash(cowDeref(value), s); +} + +/** `Deref` of a `Cow`, and its `into_owned`: its text. */ +export function strCowStr(c: StrCow): string { + return c instanceof Borrowed ? c.slice : c; +} + +/** `Clone` of a `Cow`: a borrow of the same text, or its text. */ +export function strCowClone(c: StrCow): StrCow { + return c instanceof Borrowed ? new Borrowed(c.slice) : c; } -/** `Hash::hash` of a `Cow<[T]>`: its slice's. */ -export function hashCow(hash: (value: T[], s: S) => void): (value: Cow, s: S) => void { - return (value, s) => hash(cowSlice(value), s); +/** + * `+` of two `Cow`s, and `+=`: the right one if the left is empty, the left if the + * right is, and else the text they join, owned. + */ +export function strCowAdd(a: StrCow, b: StrCow): StrCow { + const left = strCowStr(a); + if (left === "") return b; + const right = strCowStr(b); + return right === "" ? a : left + right; } // Strings @@ -2565,6 +2594,16 @@ export function strFromUtf8(bytes: ByteSlice): Result { } } +/** `String::from_utf8_lossy`: the text of valid UTF-8, borrowed, or else with each invalid sequence replaced by U+FFFD, owned. */ +export function stringFromUtf8Lossy(bytes: ByteSlice): StrCow { + const v = byteSliceView(bytes); + try { + return new Borrowed(decoder.decode(v)); + } catch { + return strFromUtf8Unchecked(bytes); + } +} + /** `String::from_utf8`. */ export function stringFromUtf8(bytes: ByteVec): Result { try { diff --git a/ts/runtime/rt/str.ts b/ts/runtime/rt/str.ts index 69a9ba33d..a94331c9d 100644 --- a/ts/runtime/rt/str.ts +++ b/ts/runtime/rt/str.ts @@ -25,6 +25,8 @@ import { panic, panicFmt, type Result, + type StrCow, + strCowStr, stringLen, utf8Check, } from "../rt.ts"; @@ -822,8 +824,8 @@ export function strBytes(s: string): Iter { // `ignoreBOM` keeps a leading U+FEFF, as Rust does. const lossy = /* @__PURE__ */ new TextDecoder("utf-8", { ignoreBOM: true }); -/** `String::from_utf8_lossy`: the text, each invalid sequence replaced by U+FFFD. */ -export function stringFromUtf8Lossy(bytes: ByteSlice): string { +/** `str::from_utf8_unchecked`: the text, each invalid sequence, which its contract rules out, replaced by U+FFFD. */ +export function strFromUtf8Unchecked(bytes: ByteSlice): string { return lossy.decode(byteSliceView(bytes)); } @@ -1118,6 +1120,9 @@ export const STRING_CHARS_PART: Part = { /** `String` as a tuple's part, of strings, which never reserves. */ export const STRING_PART: Part = { one: (s, text) => s + text }; +/** `String` as a tuple's part, of `Cow`s, which never reserves. */ +export const STRING_COW_PART: Part = { one: (s, c) => s + strCowStr(c) }; + /** `Extend` of `s` by strings, `&str`, `String`, or `Cow`, and `collect` of `&str`s from `""`. */ export function stringExtend(s: string, it: Iter, drops = false): string { iterForEach( diff --git a/ts/src/lower/body.rs b/ts/src/lower/body.rs index 3d3614761..75460d464 100644 --- a/ts/src/lower/body.rs +++ b/ts/src/lower/body.rs @@ -256,9 +256,10 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { let mut values = values; return match (std, variant.index()) { (library::Std::Option, 1) | (library::Std::Poll, 0) => values.pop().unwrap(), - (library::Std::Cow, _) if library::string(tcx, Ty::new_adt(tcx, adt, gargs)) => values.pop().unwrap(), - (library::Std::Cow, _) if let Some(elem) = library::cow_slice(tcx, Ty::new_adt(tcx, adt, gargs)) => { - library::cow_made(elem, variant, values.pop().unwrap()) + (library::Std::Cow, _) + if let Some(borrowed) = library::cow_borrowed(tcx, Ty::new_adt(tcx, adt, gargs)) => + { + library::cow_made(borrowed, variant, values.pop().unwrap()) } (library::Std::Result, i) => Expr::result(i == 0, values.pop().unwrap()), ( diff --git a/ts/src/lower/collections.rs b/ts/src/lower/collections.rs index 2e0fa411c..988e32293 100644 --- a/ts/src/lower/collections.rs +++ b/ts/src/lower/collections.rs @@ -213,10 +213,15 @@ pub fn call<'tcx>( && !same_key(b, key, by) && !alike(b, key, by) { - // An `Arc`'s `Hash`, `Eq`, and `Ord` are its value's, so the value it is looked up by - // stands in as an `Arc` of it, which the collection only compares. - let arc = arc_of(b, peel(b, key)).is_some_and(|inner| same_key(b, inner, by)); + // A `Cow`'s `Hash`, `Eq`, and `Ord` are what it borrows', so what it is looked up by, + // or a range's bounds, stands in as it is, as JS reads it as an owned `Cow`. An `Arc`'s + // are its value's, so the value it is looked up by stands in as an `Arc` of it, which + // the collection only compares. + let key = peel(b, key); + let cow = cow_of(b, key).is_some_and(|borrowed| same_key(b, borrowed, by)); + let arc = arc_of(b, key).is_some_and(|inner| same_key(b, inner, by)); match args.get_mut(1) { + _ if cow => {} Some(key) if arc && !matches!(name, "range" | "range_mut") => { let value = std::mem::replace(key, Expr::undefined()); *key = Expr::New(Box::new(Expr::Rt("Arc".into())), vec![value]); @@ -1028,8 +1033,9 @@ fn hashing<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, key: Ty<'tcx>, span }; let mut values = vec![hash_fn(b, key, hasher, span), eq_fn(b, key, span)]; // A fieldless enum is a JS string, and a bound value may be any primitive, which its - // own `Eq` may tell apart where JS can't, as is the `Some` of an `Option` of one, and a - // `Reverse`, a `Wrapping`, or a `Saturating` of one. + // own `Eq` may tell apart where JS can't, and an owned `Cow` is a string its + // borrowed one equals, as is the `Some` of an `Option` of one, and a `Reverse`, a + // `Wrapping`, or a `Saturating` of one. let mut held = peeled; while let Some(inner) = option_of(b, held) .or_else(|| reverse_of(b, held)) @@ -1037,7 +1043,9 @@ fn hashing<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, key: Ty<'tcx>, span { held = peel(b, inner); } - if matches!(held.kind(), ty::Adt(adt, _) if fieldless(*adt) || b.t.bound_drops(adt.did()).is_some()) { + if library::cow_str(b.t.tcx, held) + || matches!(held.kind(), ty::Adt(adt, _) if fieldless(*adt) || b.t.bound_drops(adt.did()).is_some()) + { values.push(Expr::lit("true")); } Some(Some(Expr::rt("hashing", values))) @@ -2190,8 +2198,12 @@ fn part<'tcx>(b: &mut Body<'_, '_, 'tcx>, coll: Ty<'tcx>, item: Ty<'tcx>, span: )); }; if std == Std::String { - let chars = !super::iter::text_items(b.t, item)?; - return Some(Expr::Rt(if chars { "STRING_CHARS_PART" } else { "STRING_PART" }.into())); + let part = match super::iter::text_items(b.t, item)? { + false => "STRING_CHARS_PART", + true if library::cow_str(b.t.tcx, item.peel_refs()) => "STRING_COW_PART", + true => "STRING_PART", + }; + return Some(Expr::Rt(part.into())); } let size = |b: &mut Body<'_, '_, 'tcx>| vec![super::iter::size_of(b, arg(coll, 0), span)]; let part = match std { @@ -2652,22 +2664,22 @@ fn overflowing_of<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> Option b.t.std_of(ty).and_then(Std::overflow).map(|_| arg(ty, 0)) } -/// What `ty` holds if it is a `Cow<[T]>` other than of bytes, `T`, which compares, orders, -/// and hashes as its slice. A `Cow<[u8]>` is bytes already. +/// What `ty` borrows if it is a `Cow` other than of bytes, a `[T]` or a `str`, which it +/// compares, orders, and hashes as. A `Cow<[u8]>` is bytes already. fn cow_of<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>) -> Option> { - library::cow_slice(b.t.tcx, ty).filter(|elem| !super::byte(*elem)) + library::cow_borrowed(b.t.tcx, ty).filter(|t| !bytes(b, *t)) } -/// `ty`, the type of `args[n]`, as the slice it derefs to if it is a `Cow<[T]>` other than -/// of bytes, whose value in `args` becomes that slice. +/// `ty`, the type of `args[n]`, as what it derefs to if it is a `Cow` other than of bytes, +/// whose value in `args` becomes that. fn cow_deref<'tcx>(b: &Body<'_, '_, 'tcx>, ty: Ty<'tcx>, args: &mut [Expr], n: usize) -> Ty<'tcx> { - let Some(elem) = cow_of(b, peel(b, ty)) else { + let Some(borrowed) = cow_of(b, peel(b, ty)) else { return ty; }; if let Some(at) = args.get_mut(n) { - *at = library::cow_deref(std::mem::replace(at, Expr::undefined()), elem); + *at = library::cow_deref(std::mem::replace(at, Expr::undefined()), borrowed); } - Ty::new_slice(b.t.tcx, elem) + borrowed } /// The elements of `ty` if it is a `Vec`, a slice, or an array that is a JS array: of any @@ -2870,8 +2882,8 @@ pub fn cmp_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, partial: bool, spa Expr::rt("cmpReverse", vec![cmp_fn(b, inner, partial, span)]) } ty::Adt(..) if let Some(inner) = overflowing_of(b, ty) => cmp_fn(b, inner, partial, span), - ty::Adt(..) if let Some(elem) = cow_of(b, ty) => { - Expr::rt("cowBy", vec![cmp_fn(b, Ty::new_slice(tcx, elem), partial, span)]) + ty::Adt(..) if let Some(borrowed) = cow_of(b, ty) => { + Expr::rt("cowBy", vec![cmp_fn(b, borrowed, partial, span)]) } _ if let Some(elem) = elements_of(b, ty) => Expr::rt("cmpSlice", vec![cmp_fn(b, elem, partial, span)]), // A deque's is its iterator's, which compares each item by its own impls, as a slice's does. @@ -2935,9 +2947,7 @@ pub fn eq_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span) -> Expr eq => Expr::rt("eqOption", [eq].into_iter().chain(b.t.none_arg(inner)).collect()), }, ty::Adt(..) if let Some(inner) = reverse_of(b, ty).or_else(|| overflowing_of(b, ty)) => eq_fn(b, inner, span), - ty::Adt(..) if let Some(elem) = cow_of(b, ty) => { - Expr::rt("cowBy", vec![eq_fn(b, Ty::new_slice(tcx, elem), span)]) - } + ty::Adt(..) if let Some(borrowed) = cow_of(b, ty) => Expr::rt("cowBy", vec![eq_fn(b, borrowed, span)]), ty::Adt(..) if let Some(bound) = range_of(b, ty) => Expr::rt("rangeEq", vec![eq_fn(b, bound, span)]), // A slice compares its elements by `!=`, as a deque does each of its halves. _ if let Some(elem) = elements_of(b, ty) => Expr::rt("eqSlice", vec![not_ne_fn(b, elem, span)]), @@ -3170,8 +3180,8 @@ fn hash_fn<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, hasher: Ty<'tcx>, spa ty::Adt(..) if let Some(inner) = reverse_of(b, ty).or_else(|| overflowing_of(b, ty)) => { hash_fn(b, inner, hasher, span) } - ty::Adt(..) if let Some(elem) = cow_of(b, ty) => { - Expr::rt("hashCow", vec![hash_fn(b, Ty::new_slice(tcx, elem), hasher, span)]) + ty::Adt(..) if let Some(borrowed) = cow_of(b, ty) => { + Expr::rt("hashCow", vec![hash_fn(b, borrowed, hasher, span)]) } ty::Adt(..) if let Some(bound) = range_of(b, ty) => { Expr::rt("rangeHash", vec![hash_fn(b, bound, hasher, span)]) diff --git a/ts/src/lower/consts.rs b/ts/src/lower/consts.rs index f1f1ae0fa..2d2aeaed1 100644 --- a/ts/src/lower/consts.rs +++ b/ts/src/lower/consts.rs @@ -143,9 +143,8 @@ impl<'b, 'a, 'tcx> Body<'b, 'a, 'tcx> { (Std::Option, _) => values.pop().unwrap(), (Std::Poll, 1) => self.t.pending(args.type_at(0)), (Std::Poll, _) => values.pop().unwrap(), - (Std::Cow, _) if crate::lower::library::string(tcx, ty) => values.pop().unwrap(), - (Std::Cow, _) if let Some(elem) = super::library::cow_slice(tcx, ty) => { - super::library::cow_made(elem, variant, values.pop().unwrap()) + (Std::Cow, _) if let Some(borrowed) = super::library::cow_borrowed(tcx, ty) => { + super::library::cow_made(borrowed, variant, values.pop().unwrap()) } (Std::Result, i) => Expr::result(i == 0, values.pop().unwrap()), // `start..=end`, not yet stepped past its end. diff --git a/ts/src/lower/fmt.rs b/ts/src/lower/fmt.rs index c01ce4212..989c5e94e 100644 --- a/ts/src/lower/fmt.rs +++ b/ts/src/lower/fmt.rs @@ -370,15 +370,15 @@ pub fn std_impl<'tcx>( e } ty::Adt(adt, args) => match (Std::of(tcx, adt.did())?, tr) { - (Std::String | Std::Cow, Trait::Display) if library::string(tcx, ty) => Expr::rt("fmtPad", vec![f, value]), - (Std::String | Std::Cow, Trait::Debug) if library::string(tcx, ty) => rt("str", vec![value, f]), + (Std::String, Trait::Display) => Expr::rt("fmtPad", vec![f, value]), + (Std::String, Trait::Debug) => rt("str", vec![value, f]), // A `NonZero`, a `Wrapping`, and a `Saturating` write their integer, by each trait. (Std::Box | Std::NonZero | Std::Wrapping | Std::Saturating, _) => { b.fmt_call(tr, args.type_at(0), value, f, span) } - // A `Cow<[T]>` writes its slice. - (Std::Cow, Trait::Debug) if let Some(elem) = library::cow_slice(tcx, ty) => { - b.fmt_call(tr, Ty::new_slice(tcx, elem), library::cow_deref(value, elem), f, span) + // A `Cow` writes what it derefs to. + (Std::Cow, _) if let Some(borrowed) = library::cow_borrowed(tcx, ty) => { + b.fmt_call(tr, borrowed, library::cow_deref(value, borrowed), f, span) } (Std::Arc, Trait::Display | Trait::Debug) => { b.fmt_call(tr, args.type_at(0), value.member("value"), f, span) diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index a7e66c9e7..33fcad635 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -2148,7 +2148,8 @@ fn collect<'tcx>( match b.t.std_of(item) { Some(Std::String | Std::Cow) => { let drops = iter_drops(b, from, span); - Some(Expr::rt("stringFromStrings", tail(vec![iter], vec![drops]))) + let texts = texts(b.t, iter, item); + Some(Expr::rt("stringFromStrings", tail(vec![texts], vec![drops]))) } _ => string_extend(b, Expr::Str(String::new()), iter, item, from, span), } @@ -2199,7 +2200,15 @@ pub(super) fn string_extend<'tcx>( "stringExtendChars" }; let drops = iter_drops(b, iter, span); - Some(Expr::rt(extend, tail(vec![s, items], vec![drops]))) + Some(Expr::rt(extend, tail(vec![s, texts(b.t, items, item)], vec![drops]))) +} + +/// `items`, an iterator of `item`s, as one of the strings they are: a `Cow`'s text. +fn texts<'tcx>(t: &Translator<'_, 'tcx>, items: Expr, item: Ty<'tcx>) -> Expr { + match library::cow_str(t.tcx, item.peel_refs()) { + true => Expr::rt("iterMap", vec![items, Expr::Rt("strCowStr".into())]), + false => items, + } } /// Whether a `String` extends by `item`s of text, a `&str`, `String`, `Box`, or diff --git a/ts/src/lower/library.rs b/ts/src/lower/library.rs index a526cd3d9..f7ca139dd 100644 --- a/ts/src/lower/library.rs +++ b/ts/src/lower/library.rs @@ -28,7 +28,8 @@ pub enum Std { Vec, /// A JS string. String, - /// A JS string too, for `Cow`, which keeps no variant: either is the text. + /// What it owns, or an `rt.Borrowed` of what it borrows, or for bytes an `rt.ByteVec` + /// or an `rt.ByteSlice`. Cow, /// Its contents. Box, @@ -484,20 +485,38 @@ pub fn empty_default(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { matches!(ty.kind(), ty::Adt(adt, _) if empty_iter(tcx, adt.did()) || list_iter(tcx, adt.did())) } -/// Whether a `ty`'s value is a JS string: a `str`, a `String`, a `Box`, or a `Cow`. +/// Whether a `ty`'s value is a JS string: a `str`, a `String`, or a `Box`. pub fn string<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind() { ty::Str => true, ty::Adt(adt, args) => match Std::of(tcx, adt.did()) { Some(Std::String) => true, - // A `Cow`'s first argument is its lifetime. - Some(Std::Cow | Std::Box) => args.types().next().is_some_and(|t| t.is_str()), + Some(Std::Box) => args.type_at(0).is_str(), _ => false, }, _ => false, } } +/// Whether `ty` is a `Cow`: an `rt.StrCow`, the string it owns or the `rt.Borrowed` +/// one it borrows. +pub fn cow_str<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + cow_borrowed(tcx, ty).is_some_and(|t| t.is_str()) +} + +/// Whether `ty` is text: a JS string, or a `Cow`, which [`text`] reads as one. +pub fn textual<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool { + string(tcx, ty) || cow_str(tcx, ty) +} + +/// `value`, of type `ty`, as the JS string it is or, for a `Cow`, derefs to. +pub fn text<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, value: Expr) -> Expr { + match cow_str(tcx, ty) { + true => Expr::rt("strCowStr", vec![value]), + false => value, + } +} + /// What a value of `ty` is in JS if `ty` wraps one value it is represented as: `Reverse`'s, /// `NonZero`'s, `Wrapping`'s, `Saturating`'s, and `AssertUnwindSafe`'s `T`, which /// differ only in their order, range, arithmetic, and unwind safety, a pattern type's base, @@ -559,49 +578,68 @@ pub fn box_slice<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { } } -/// What a `Cow<[T]>` of type `ty` holds, `T`, or `None` for any other type. -pub fn cow_slice<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { +/// `value`, a `&str` or a `Cow` of type `ty`, as a `Cow`, borrowing a `&str`. +pub fn str_cow<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, value: Expr) -> Expr { + match cow_str(tcx, ty) { + true => value, + false => cow_made(tcx.types.str_, rustc_abi::FIRST_VARIANT, value), + } +} + +/// What a `Cow` of type `ty` borrows, a `[T]` or a `str`, or `None` for any other type. +pub fn cow_borrowed<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { match ty.kind() { // Its first argument is its lifetime. - ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::Cow) => match args.types().next()?.kind() { - ty::Slice(elem) => Some(*elem), - _ => None, - }, + ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::Cow) => { + let borrowed = args.types().next()?; + (borrowed.is_slice() || borrowed.is_str()).then_some(borrowed) + } _ => None, } } -/// The slice `cow`, a `Cow<[elem]>`, derefs to: an `rt.ByteSlice` of bytes, or an array. -pub fn cow_deref(cow: Expr, elem: Ty<'_>) -> Expr { - match elem.kind() { - _ if byte(elem) => Expr::rt("byteCowSlice", vec![cow]), +/// What a `Cow<[T]>` of type `ty` holds, `T`, or `None` for any other type. +pub fn cow_slice<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { + match cow_borrowed(tcx, ty)?.kind() { + ty::Slice(elem) => Some(*elem), + _ => None, + } +} + +/// Whether `borrowed`, what a `Cow` borrows, is bytes, which it keeps as an `rt.ByteSlice`. +fn cow_bytes(borrowed: Ty<'_>) -> bool { + matches!(borrowed.kind(), ty::Slice(elem) if byte(*elem)) +} + +/// What `cow`, a `Cow` of `borrowed`, derefs to: an `rt.ByteSlice` of bytes, an array, or +/// a string. +pub fn cow_deref(cow: Expr, borrowed: Ty<'_>) -> Expr { + match borrowed.kind() { + ty::Str => Expr::rt("strCowStr", vec![cow]), + _ if cow_bytes(borrowed) => Expr::rt("byteCowSlice", vec![cow]), _ => Expr::rt("cowSlice", vec![cow]), } } -/// A `Cow<[elem]>` made by its variant `variant` of `value`: the `Vec` it owns, or the -/// slice it borrows, wrapped in an `rt.Borrowed` unless it is bytes. -pub fn cow_made(elem: Ty<'_>, variant: rustc_abi::VariantIdx, value: Expr) -> Expr { - match variant.index() == 0 && !byte(elem) { +/// A `Cow` of `borrowed` made by its variant `variant` of `value`: what it owns, or what +/// it borrows, wrapped in an `rt.Borrowed` unless it is bytes. +pub fn cow_made(borrowed: Ty<'_>, variant: rustc_abi::VariantIdx, value: Expr) -> Expr { + match variant.index() == 0 && !cow_bytes(borrowed) { true => Expr::New(Box::new(Expr::Rt("Borrowed".into())), vec![value]), false => value, } } -/// Whether `cow`, a `Cow<[elem]>`, borrows: whether it is of the class its borrowed variant is. -pub fn cow_borrows(cow: Expr, elem: Ty<'_>) -> Expr { - let class = match elem.kind() { - _ if byte(elem) => "ByteSlice", - _ => "Borrowed", +/// Whether `cow`, a `Cow` of `borrowed`, borrows: whether it is of the class its borrowed +/// variant is. +pub fn cow_borrows(cow: Expr, borrowed: Ty<'_>) -> Expr { + let class = match cow_bytes(borrowed) { + true => "ByteSlice", + false => "Borrowed", }; cow.binary("instanceof", Expr::Rt(class.into())) } -/// The refusal of code that tells a `Cow`'s variants apart. -pub fn cow_variant(ty: Ty<'_>) -> String { - format!("a variant of `{ty}`, which is not supported: either is the same JS string") -} - /// The path of a std item within its crate, as `["slice", "iter", "Iter"]`; empty for /// any other crate's. pub fn std_path(tcx: TyCtxt<'_>, did: DefId) -> Vec { @@ -1518,17 +1556,29 @@ fn mapping<'tcx>( let (to, from) = (targ(gargs, 0), unwrapped(tcx, targ(gargs, 1))); let mut values = args; let value = values.pop()?; - // One between strings, such as into a `Cow` or out of one, keeps the text, and // `Some` or `Ready` of a value is the value. let some = matches!(to.kind(), ty::Adt(adt, args) if matches!(Std::of(tcx, adt.did()), Some(Std::Option | Std::Poll)) && args.type_at(0) == targ(gargs, 1)); if to == from || some { return Some(value); } - // A `&mut str` is the `rt.Place` of its text. + // One between strings keeps the text, and a `&mut str` is the `rt.Place` of its text. if string(tcx, to) && string(tcx, from.peel_refs()) { return Some(b.t.pointee_value(targ(gargs, 1), value)); } + // A `Cow` borrows the text a reference does and owns a `String`, and gives up + // its text. + if cow_str(tcx, to) && string(tcx, from.peel_refs()) { + let owned = rustc_abi::VariantIdx::from_u32((!targ(gargs, 1).is_ref()).into()); + return Some(cow_made( + tcx.types.str_, + owned, + b.t.pointee_value(targ(gargs, 1), value), + )); + } + if string(tcx, to) && cow_str(tcx, from) { + return Some(text(tcx, from, value)); + } // A `Vec` of a `String`'s or a `&str`'s UTF-8. if b.t.std_of(to) == Some(Std::Vec) && string(tcx, from.peel_refs()) { if !from.is_ref() { @@ -1546,7 +1596,11 @@ fn mapping<'tcx>( true => vec_view(value, elem), false => value, }; - return Some(cow_made(elem, rustc_abi::VariantIdx::from_u32(owned.into()), value)); + return Some(cow_made( + Ty::new_slice(tcx, elem), + rustc_abi::VariantIdx::from_u32(owned.into()), + value, + )); } if let Some(elem) = cow_slice(tcx, from) && b.t.std_of(to) == Some(Std::Vec) @@ -1609,7 +1663,7 @@ fn mapping<'tcx>( let error = targ(gargs, 1); if let Some(object) = to.boxed_ty() && super::dynamic::is_error_object(tcx, object) - && !string(tcx, error.peel_refs()) + && !textual(tcx, error.peel_refs()) { return Some(b.object_of(value, Ty::new_box(tcx, error), to, span)); } @@ -1793,6 +1847,7 @@ fn mapping<'tcx>( Expr::rt("byteVecFrom", vec![Expr::rt("byteVecView", vec![value])]) } ty::Adt(..) if string(tcx, ty) => value, + ty::Adt(..) if cow_str(tcx, ty) => Expr::rt("strCowClone", vec![value]), ty::Adt(adt, args) if Std::of(tcx, adt.did()) == Some(Std::Vec) => { return Some(vec_clone(b, value, args.type_at(0), span)); } @@ -2641,8 +2696,7 @@ fn mapping<'tcx>( ty::Adt(adt, vargs) => match Std::of(tcx, adt.did()) { Some(Std::Vec) => vec_view(v, vargs.type_at(0)), Some(Std::String | Std::Box | Std::ManuallyDrop | Std::AssertUnwindSafe) => v, - Some(Std::Cow) if string(tcx, ty) => v, - Some(Std::Cow) if let Some(elem) = cow_slice(tcx, ty) => cow_deref(v, elem), + Some(Std::Cow) if let Some(borrowed) = cow_borrowed(tcx, ty) => cow_deref(v, borrowed), // The greatest element of a heap, which `DerefMut` marks to sift once lent. Some(Std::PeekMut) => { let p = match v.is_path() { @@ -2668,13 +2722,13 @@ fn mapping<'tcx>( b.t.string_bytes(); Expr::rt("stringIntoBytes", args) } - // A `Cow` is its text, whether std borrows the bytes or replaces any. ("String", "from_utf8_lossy") => Expr::rt("stringFromUtf8Lossy", args), // UTF-8 by their contract, which the lossy decoder reads as the text. - ("converts" | "str", "from_utf8_unchecked") => Expr::rt("stringFromUtf8Lossy", args), - ("String", "from_utf8_unchecked") => { - Expr::rt("stringFromUtf8Lossy", vec![Expr::rt("byteVecSlice", vec![args.pop()?])]) - } + ("converts" | "str", "from_utf8_unchecked") => Expr::rt("strFromUtf8Unchecked", args), + ("String", "from_utf8_unchecked") => Expr::rt( + "strFromUtf8Unchecked", + vec![Expr::rt("byteVecSlice", vec![args.pop()?])], + ), ("FromUtf8Error", "as_bytes") => Expr::rt("byteVecSlice", vec![args.pop()?.member("bytes")]), ("FromUtf8Error", "into_bytes") => args.pop()?.member("bytes"), ("FromUtf8Error", "utf8_error") => args.pop()?.member("error"), @@ -2693,11 +2747,15 @@ fn mapping<'tcx>( // A `Box` is its text, as a `String` is. ("String", "as_str" | "into_boxed_str") | ("str", "into_string") => args.pop()?, ("String", "new") => Expr::Str(String::new()), - // `String + &str` and `Cow`'s, which push onto the moved text as `+=` does. + // `String + &str`, which pushes onto the moved text as `+=` does. ("Add", "add") if string(tcx, targ(gargs, 0)) => { let text = args.pop()?; args.pop()?.binary("+", text) } + ("Add", "add") if cow_str(tcx, targ(gargs, 0)) => { + let rhs = str_cow(tcx, targ(gargs, 1), args.pop()?); + Expr::rt("strCowAdd", vec![args.pop()?, rhs]) + } ("String", "with_capacity") => Expr::rt("stringWithCapacity", args), // Of the place the `&mut` is, which it writes as it drops, and of a range, as `Vec::drain`. ("String", "drain") => { @@ -2768,9 +2826,12 @@ fn mapping<'tcx>( ("str", "char_indices" | "lines" | "split_whitespace") => Expr::rt_of("str", &super::camel(&name)).call(args), // A slice of strings concatenated, or joined by a `&str`. ("slice", "concat" | "join") - if string(tcx, targ(gargs, 0).peel_refs()) && targ(gargs, 1).peel_refs().is_str() => + if textual(tcx, targ(gargs, 0).peel_refs()) && targ(gargs, 1).peel_refs().is_str() => { let mut values = args; + if cow_str(tcx, targ(gargs, 0).peel_refs()) { + values[0] = values[0].clone().member("map").call(vec![Expr::Rt("strCowStr".into())]); + } if name == "concat" { values.push(Expr::Str(String::new())); } @@ -2797,21 +2858,16 @@ fn mapping<'tcx>( let of = if owner == "slice" { "byteSlice" } else { "str" }; Expr::rt_of(of, &super::camel(&name)).call(args) } - // A `Cow` is its text, whichever variant holds it. - ("Cow", "into_owned") if targ(gargs, 0).is_str() => args.pop()?, + // A `Cow`'s text, whichever variant holds it. + ("Cow", "into_owned") if targ(gargs, 0).is_str() => Expr::rt("strCowStr", args), ("AsRef", "as_ref") if string(tcx, targ(gargs, 0)) && targ(gargs, 1).is_str() => args.pop()?, // A `Cow<[T]>` borrows its slice, and its `Vec` is its own or a clone of what it // borrows, so it drops nothing. ("Cow", "into_owned") if let ty::Slice(elem) = targ(gargs, 0).kind() => { return Some(cow_cloned(b, "IntoOwned", args.pop()?, *elem, span)); } - ("AsRef", "as_ref") | ("Borrow", "borrow") if let Some(elem) = cow_slice(tcx, targ(gargs, 0)) => { - cow_deref(args.pop()?, elem) - } - // A slice's are unstable, and left to the refusal of any other call. - ("Cow", "is_borrowed" | "is_owned") if !targ(gargs, 0).is_slice() => { - let cow = b.t.sig(call.def_id, gargs).inputs()[0].peel_refs(); - return Some(b.err(span, cow_variant(cow))); + ("AsRef", "as_ref") | ("Borrow", "borrow") if let Some(borrowed) = cow_borrowed(tcx, targ(gargs, 0)) => { + cow_deref(args.pop()?, borrowed) } ("ToString", "to_string") | ("ToOwned", "to_owned") if matches!(self_ty()?.peel_refs().kind(), ty::Str) => { b.t.pointee_value(self_ty()?, args.pop()?) @@ -3023,7 +3079,7 @@ fn mapping<'tcx>( let ty = targ(gargs, 0); let value = args.pop()?; match ty.kind() { - ty::Adt(..) if string(tcx, ty) => value, + ty::Adt(..) if textual(tcx, ty) => text(tcx, ty, value), _ => { let value = b.fmt_value(fmt::Trait::Display, ty, value, span, out); Expr::rt("fmtToString", vec![value]) @@ -3325,7 +3381,7 @@ pub(super) fn clone_from_clones<'tcx>(t: &mut Translator<'_, 'tcx>, ty: Ty<'tcx> // A hash table's and a B-tree set's clone each element anew, by its `clone`. Text is // a JS string, which keeps no buffer. (Some(Std::HashMap | Std::HashSet | Std::HashTable | Std::BTreeSet), _) => vec![], - _ if string(tcx, ty) => vec![], + _ if textual(tcx, ty) => vec![], (_, ty::Adt(adt, args)) if is_std(tcx, adt.did()) => args.types().collect(), (_, ty::Array(elem, _)) => vec![*elem], _ => return false, @@ -3939,8 +3995,8 @@ pub fn default_value<'tcx>(b: &mut Body<'_, '_, 'tcx>, ty: Ty<'tcx>, span: Span, _ => Expr::Array(Vec::new()), }, Std::String => Expr::Str(String::new()), - Std::Cow if string(tcx, ty) => Expr::Str(String::new()), - // An owned empty `Vec`. + // An owned empty `String` or `Vec`. + Std::Cow if cow_str(tcx, ty) => Expr::Str(String::new()), Std::Cow if let Some(elem) = cow_slice(tcx, ty) => match elem.kind() { _ if byte(elem) => Expr::New(Box::new(Expr::Rt("ByteVec".into())), Vec::new()), _ => Expr::Array(Vec::new()), diff --git a/ts/src/lower/mir/call.rs b/ts/src/lower/mir/call.rs index 93aace341..186d75f65 100644 --- a/ts/src/lower/mir/call.rs +++ b/ts/src/lower/mir/call.rs @@ -714,8 +714,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { ); done(next, true) } - // A JS string can't change in place, so what the `&mut` borrows gets a new one. A - // `Cow` is its text, as a `String` is. + // A JS string can't change in place, so what the `&mut` borrows gets a new one. ("String", "push" | "push_str") | ("AddAssign", "add_assign") if owner == "String" || (targ(0).is_some_and(|t| library::string(tcx, t)) && !self.b.t.translated(def_id, gargs)) => @@ -730,6 +729,16 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.set_borrowed(&place, pushed, span, out); done(Expr::undefined(), false) } + ("AddAssign", "add_assign") + if targ(0).is_some_and(|t| library::cow_str(tcx, t)) && !self.b.t.translated(def_id, gargs) => + { + let place = self.borrowed_place(&args[0].node, span, out)?; + let rhs = self.operand(&args[1].node, span, out); + let rhs = library::str_cow(tcx, targ(1)?, rhs); + let joined = Expr::rt("strCowAdd", vec![place.0.clone(), rhs]); + self.set_borrowed(&place, joined, span, out); + done(Expr::undefined(), false) + } // What the `&mut` borrows gets the value of the other case, as a byte, a char, and a // JS string can't change in place. ("u8" | "char" | "str", "make_ascii_uppercase" | "make_ascii_lowercase") => { @@ -858,9 +867,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { self.set_borrowed(&place, value, span, out); done(Expr::undefined(), false) } - // A `Cow` is the `String` it would hold, and a `String` is its `str`, so the - // `&mut` borrows the same place. - ("Cow", "to_mut") if targ(0).is_some_and(|t| t.is_str()) => self.same_place(args, dest, span, out), + // A borrowed `Cow` owns its text first, the `String` it then is, and a `String` + // is its `str`, so the `&mut` borrows the same place. + ("Cow", "to_mut") if targ(0).is_some_and(|t| t.is_str()) => { + let place = self.borrowed_place(&args[0].node, span, out)?; + self.set_borrowed(&place, Expr::rt("strCowStr", vec![place.0.clone()]), span, out); + self.same_place(args, dest, span, out) + } ("String", "as_mut_str") => self.same_place(args, dest, span, out), ("DerefMut", "deref_mut") | ("AsMut", "as_mut") | ("BorrowMut", "borrow_mut") if targ(0).is_some_and(|t| t.is_str() || self.b.t.std_of(t) == Some(Std::String)) diff --git a/ts/src/lower/mir/place.rs b/ts/src/lower/mir/place.rs index 73dd6e153..357027e48 100644 --- a/ts/src/lower/mir/place.rs +++ b/ts/src/lower/mir/place.rs @@ -626,15 +626,19 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Some(std) if std.record() => { base.member(crate::lower::camel(adt.non_enum_variant().fields[field].name.as_str())) } - // Cast to its variant's class: the slice an `rt.Borrowed` wraps, or the `Vec`. - Some(Std::Cow) if let Some(elem) = crate::lower::library::cow_slice(tcx, ty) => { - let bytes = crate::lower::byte(elem); + // Cast to its variant's class: what an `rt.Borrowed` wraps, or what it owns. + Some(Std::Cow) if let Some(borrowed) = crate::lower::library::cow_borrowed(tcx, ty) => { + let bytes = crate::lower::library::cow_slice(tcx, ty).is_some_and(crate::lower::byte); let owned = variant.is_some_and(|v| v.index() == 1); + let held = match borrowed.kind() { + ty::Slice(elem) => crate::ts::Type::Array(Box::new(self.b.t.ty(*elem, span))), + _ => crate::ts::Type::Builtin("string"), + }; let class = match (bytes, owned) { (true, false) => crate::ts::Type::Rt("ByteSlice", Vec::new()), (true, true) => crate::ts::Type::Rt("ByteVec", Vec::new()), - (false, false) => crate::ts::Type::Rt("Borrowed", vec![self.b.t.ty(elem, span)]), - (false, true) => crate::ts::Type::Array(Box::new(self.b.t.ty(elem, span))), + (false, false) => crate::ts::Type::Rt("Borrowed", vec![held]), + (false, true) => held, }; let cast = Expr::As(Box::new(base), class); match bytes || owned { @@ -642,7 +646,6 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { false => cast.member("slice"), } } - Some(Std::Cow) => self.err(span, crate::lower::library::cow_variant(ty)), Some(Std::ControlFlow) => super::call::control_flow_field(self, base, ty, variant), _ if transparent(self.b.t, ty) || crate::lower::library::wrapped(tcx, ty).is_some() => base, None if self.b.t.own(adt.did()) => { @@ -900,7 +903,7 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { ) { break; } - value = self.adt(adt.did(), *variant, args, vec![value], ty, span); + value = narrowed(self.adt(adt.did(), *variant, args, vec![value], ty, span)); (at, rebuilt) = (base, true); } match rebuilt { @@ -1236,14 +1239,13 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { Some(std @ (Std::Entry | Std::TableEntry)) => { crate::lower::collections::entry_test(std, *adt, index, scrut.clone()) } - Some(Std::Cow) if let Some(elem) = crate::lower::library::cow_slice(tcx, ty) => { - let borrows = crate::lower::library::cow_borrows(scrut.clone(), elem); + Some(Std::Cow) if let Some(borrowed) = crate::lower::library::cow_borrowed(tcx, ty) => { + let borrows = crate::lower::library::cow_borrows(scrut.clone(), borrowed); match index.index() { 0 => borrows, _ => borrows.not(), } } - Some(Std::Cow) => self.err(span, crate::lower::library::cow_variant(ty)), Some(Std::Ordering) => scrut .clone() .binary("===", crate::lower::collections::ordering_variant(index)), @@ -2345,3 +2347,24 @@ fn bytes_lit(bytes: &[u8]) -> Expr { vec![Expr::ident("Uint8Array").member("of").call(items)], ) } + +/// `rt.Ok(value)` or `rt.Err(value)` as the object literal they make, which TS types as the +/// variant the store's own test narrowed its `Result` to, so a later read of the payload +/// type-checks. Any other value is as it is. +fn narrowed(value: Expr) -> Expr { + let Expr::Call(f, args) = &value else { + return value; + }; + let (ok, key) = match &**f { + Expr::Rt(name) if name == "Ok" => ("true", "value"), + Expr::Rt(name) if name == "Err" => ("false", "error"), + _ => return value, + }; + let [payload] = args.as_slice() else { + return value; + }; + Expr::Object(vec![ + (crate::ts::Key::Name("ok".into()), Expr::lit(ok)), + (crate::ts::Key::Name(key.into()), payload.clone()), + ]) +} diff --git a/ts/src/lower/mir/rvalue.rs b/ts/src/lower/mir/rvalue.rs index 16fdff23a..0b5c9b622 100644 --- a/ts/src/lower/mir/rvalue.rs +++ b/ts/src/lower/mir/rvalue.rs @@ -569,15 +569,14 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { } // The value is its discriminant. Some(Std::Ordering | Std::AtomicOrdering) => scrut, - Some(Std::Cow) if let Some(elem) = crate::lower::library::cow_slice(tcx, ty) => { - let borrows = crate::lower::library::cow_borrows(scrut, elem); + Some(Std::Cow) if let Some(borrowed) = crate::lower::library::cow_borrowed(tcx, ty) => { + let borrows = crate::lower::library::cow_borrows(scrut, borrowed); Expr::Cond( Box::new(borrows), Box::new(int.lit(discrs[0].1 as i128)), Box::new(int.lit(discrs[1].1 as i128)), ) } - Some(Std::Cow) => self.err(span, crate::lower::library::cow_variant(ty)), Some(std @ (Std::Bound | Std::TryLockError | Std::ErrorKind)) => { let table = discrs .iter() @@ -783,9 +782,8 @@ impl<'m, 'b, 'a, 'tcx> Fx<'m, 'b, 'a, 'tcx> { (Std::Poll, 1) => self.b.t.pending(args.type_at(0)), (Std::Option | Std::Poll | Std::Pin, _) => values.pop().unwrap(), // Either variant is its text. - (Std::Cow, _) if crate::lower::library::string(tcx, ty) => values.pop().unwrap(), - (Std::Cow, _) if let Some(elem) = crate::lower::library::cow_slice(tcx, ty) => { - crate::lower::library::cow_made(elem, variant, values.pop().unwrap()) + (Std::Cow, _) if let Some(borrowed) = crate::lower::library::cow_borrowed(tcx, ty) => { + crate::lower::library::cow_made(borrowed, variant, values.pop().unwrap()) } (Std::Result, i) => Expr::result(i == 0, values.pop().unwrap()), (Std::FmtError, _) => Expr::New(Box::new(Expr::Rt("FmtError".into())), Vec::new()), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 8167cba13..46000cc14 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1695,7 +1695,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { false => Type::Array(Box::new(self.ty(args.type_at(0), span))), }, library::Std::String => Type::Builtin("string"), - library::Std::Cow if library::string(tcx, Ty::new_adt(tcx, adt, args)) => Type::Builtin("string"), + library::Std::Cow if library::cow_str(tcx, Ty::new_adt(tcx, adt, args)) => Type::Rt("StrCow", vec![]), library::Std::Cow if let Some(elem) = library::cow_slice(tcx, Ty::new_adt(tcx, adt, args)) => { match elem.kind() { _ if self.open_elem(elem) => Type::Rt("CowOf", vec![self.ty(elem, span)]),