From dafe20fc49c0bf74cbc6ebfa10306edd75682d60 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Fri, 6 Nov 2026 23:18:12 -0800 Subject: [PATCH 01/21] chore(quest): claim quest/m3/ts-assoc-type-fields.md Quest-Claim: 8744fe16662b1e24 From e68fe2bab1f6128123e3a8886f67a5c9960b2f58 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:28:52 -0800 Subject: [PATCH 02/21] feat(ts): fields typed by an associated type of a generic type's parameter A projection a generic type's fields name, such as smallvec's ::Item, directly, through a generic type of the crate's they hold, or as a std iterator's item, is a TypeScript type parameter of its own after the type's, named for its associated type, which each instance gives what the projection normalizes to there. A field's Option of one is an rs.OptionOf, checked per instance as a parameter's is. Co-Authored-By: Claude Opus 5.5 --- quest/m3/README.md | 1 - quest/m3/ts-assoc-type-fields.md | 20 -- ts/README.md | 10 +- .../lang/shapes/expected/core/option.ts | 22 ++ .../lang/shapes/expected/generics.Ahead.ts | 49 ++++ .../lang/shapes/expected/generics.Batch.ts | 57 +++++ .../lang/shapes/expected/generics.Deep.ts | 19 ++ .../lang/shapes/expected/generics.Drained.ts | 85 +++++++ .../lang/shapes/expected/generics.Flags.ts | 7 + .../lang/shapes/expected/generics.Pile.ts | 48 ++++ ts/fixtures/lang/shapes/expected/generics.ts | 201 +++++++++++++++- ts/fixtures/lang/shapes/expected/lib.ts | 4 +- ts/fixtures/lang/shapes/src/generics.rs | 214 ++++++++++++++++++ ts/fixtures/lang/shapes/src/lib.rs | 1 + ts/fixtures/reject/census.txt | 3 + ts/fixtures/reject/expected.txt | 3 + ts/fixtures/reject/src/values.rs | 35 +++ ts/fixtures/reject/ts.toml | 2 + ts/src/lower/iter.rs | 2 +- ts/src/lower/mod.rs | 162 +++++++++++-- 20 files changed, 898 insertions(+), 47 deletions(-) delete mode 100644 quest/m3/ts-assoc-type-fields.md create mode 100644 ts/fixtures/lang/shapes/expected/generics.Ahead.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Batch.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Deep.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Drained.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Flags.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Pile.ts diff --git a/quest/m3/README.md b/quest/m3/README.md index 73adb9533..2856bf758 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -25,4 +25,3 @@ generic std gap is planned here even when moq never calls it. - [std's panic messages hold std's capacity](/quest/m3/ts-panic-payload-capacity.md) - a hook reads native's `String` capacity from std's own formatted panics - [Browser APIs through wasm-bindgen](/quest/m3/ts-wasm-bindgen/README.md) - web-sys and js-sys imports become direct JS calls, so idiomatic crates such as web-rs's web-codecs translate as stock crates - [BufMut on a byte slice](/quest/m3/ts-bufmut-slices.md) - `bytes`' `BufMut` for `&mut [u8]` and `&mut [MaybeUninit]` translates -- [Fields typed by an associated type](/quest/m3/ts-assoc-type-fields.md) - a generic struct's field of `::Item` translates at each instance diff --git a/quest/m3/ts-assoc-type-fields.md b/quest/m3/ts-assoc-type-fields.md deleted file mode 100644 index 1efd288ba..000000000 --- a/quest/m3/ts-assoc-type-fields.md +++ /dev/null @@ -1,20 +0,0 @@ -# [M] Fields typed by an associated type - -## Goal - -[rust2-ts](/ts/README.md) translates a generic struct whose field is typed by an -associated type of its parameter, such as smallvec's `Drain<'a, T: Array>` holding -`::Item`, by resolving the projection at each instance, proven by differential -tests against native Rust. - -## Plan - -Suggested on [#600](https://github.com/moq-dev/rust2/pull/600) (rangemap): it is 1 of -smallvec's 10 model refusals at the moq pin. The rest are `unsafe` that the -[ts-unsafe epic](/quest/m3/ts-unsafe/README.md) owns, and moq drops or binds smallvec, so -this is a language gap rather than a consumer's. - -Decided with the maintainer on 2026-11-07: ✅ m3 (rejected: m2, drop), as a core language -gap, not a smallvec-specific one. - -Public API: the translator's output. diff --git a/ts/README.md b/ts/README.md index a64f2f0b1..49e53aa19 100644 --- a/ts/README.md +++ b/ts/README.md @@ -471,7 +471,11 @@ function is suffixed instead by a hash of its text, with what it calls by name, translation reaches. Types are translated once, generically over their type parameters but not their const ones, which type nothing in TS: arrayvec's `ArrayString<4>` and `ArrayString<16>` are one `ArrayString`, and each instance's functions read its own -`CAP`. So a function of a generic type is one instance's, declaring the instance it +`CAP`. A projection its fields name, such as smallvec's `::Item`, directly, +through a generic type of the crate's they hold, or as a std iterator's item, is a type +parameter of its own after them, named for its associated type and numbered past any it +would shadow, which each instance gives what the projection is there: +`Drain<[u8; 4]>` is a `Drain`. So a function of a generic type is one instance's, declaring the instance it takes as `self`: `fmt$fmt_Debug_fmt_Pair(self: Wrap, f: rs.Formatter)`. A reference or pointer in a generic type to what only an instance decides the form of, such as `*mut T` or @@ -1126,8 +1130,8 @@ that drops given to a std function that keeps it other than in an iterator or a `poll_fn`, or whose mapping doesn't drop it where std's does, a `Cow` of other than `str` or a slice, a std function without a mapping of its own that makes or reads an `Option` whose `None` is `rs.None`, an `Option` of an `Option` of `()` or another -`Option`, an instance of a generic type whose field is an `Option` of a type parameter -that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` +`Option`, an instance of a generic type whose field is an `Option` of a type parameter, +or of a projection from one, that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS copies that std makes, other than a map's, or hands a closure, as `iter_mut` and diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index a836601c1..df445ca7b 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -50,6 +50,28 @@ export function map$0ggarad(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.Vec boolean): boolean | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$0ydco3f(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u16): rs.u16 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$057a36e(self: rs.u8 | undefined, f: (a0: rs.u8) => undefined): undefined | rs.None { + if (self === undefined) { + return rs.None; + } + f(self); + return undefined; +} + export function map$0600mq0(self: Dir.Dir | undefined, f: (a0: Dir.Dir) => Shape.Shape): Shape.Shape | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Ahead.ts b/ts/fixtures/lang/shapes/expected/generics.Ahead.ts new file mode 100644 index 000000000..c0f15e68e --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Ahead.ts @@ -0,0 +1,49 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Flags from "./generics.Flags.ts"; + +/** + * A std iterator's items, which only its `Iterator` impl names, beside the same + * projection named again; a parameter named for an associated type, so each projection's + * name is numbered past it. + */ +export interface Ahead<_Item, _I, Item2, Item3> { + iter: rs.Peekable; + seen: rs.VecOf; + stored: rs.OptionOf; +} + +/** Takes each item that repeats the one before it. */ +export function repeats$array1_u16_iter_Map_slice_Iter_u8_generics_projections_closure1(self: Ahead, rs.u8, rs.u16>): rs.usize { + while (true) { + const _2: rs.u8 | rs.Done = self.iter.next(); + if (_2 !== rs.DONE) { + const item: rs.u8 = _2; + if (rs.peekablePeek(self.iter) === item) { + rs.byteVecPush(self.seen as rs.ByteVec, item); + } + } else { + break; + } + } + return (self.seen as rs.ByteVec).length; +} + +/** Takes each item that repeats the one before it. */ +export function repeats$generics_Flags_slice_Iter_u8(self: Ahead, rs.u8, boolean>): rs.usize { + while (true) { + const _2: rs.u8 | rs.Done = self.iter.next(); + if (_2 !== rs.DONE) { + const item: rs.u8 = _2; + if (rs.peekablePeek(self.iter) === item) { + (self.seen as rs.Vec).items.push(item); + } + } else { + break; + } + } + return (self.seen as rs.Vec).items.length; +} + +//# sourceMappingURL=generics.Ahead.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Batch.ts b/ts/fixtures/lang/shapes/expected/generics.Batch.ts new file mode 100644 index 000000000..3a9f373a8 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Batch.ts @@ -0,0 +1,57 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import * as Drained from "./generics.Drained.ts"; +import type * as Flags from "./generics.Flags.ts"; + +/** A [`Drained`] in another generic type, which names the projection only through it. */ +export interface Batch { + drain: Drained.Drained; + taken: rs.u8; +} + +export function take$array1_Option_u8(self: Batch, rs.u8 | undefined>): rs.u8 | undefined | rs.None { + const _2: rs.u8 | undefined | rs.None = Drained.next$array1_Option_u8(self.drain); + if (_2 !== rs.None) { + self.taken = rs.u8WrappingAdd(self.taken, 1); + return _2; + } + return rs.None; +} + +export function take$array2_u16(self: Batch): rs.u16 | undefined { + const _2: rs.u16 | undefined = Drained.next$array2_u16(self.drain); + if (_2 !== undefined) { + self.taken = rs.u8WrappingAdd(self.taken, 1); + return _2; + } + return undefined; +} + +export function take$array3_unit(self: Batch): undefined | rs.None { + if (Drained.next$array3_unit(self.drain) !== rs.None) { + self.taken = rs.u8WrappingAdd(self.taken, 1); + return undefined; + } + return rs.None; +} + +export function take$array4_u8(self: Batch): rs.u8 | undefined { + const _2: rs.u8 | undefined = Drained.next$array4_u8(self.drain); + if (_2 !== undefined) { + self.taken = rs.u8WrappingAdd(self.taken, 1); + return _2; + } + return undefined; +} + +export function take$generics_Flags(self: Batch): boolean | undefined { + const _2: boolean | undefined = Drained.next$generics_Flags(self.drain); + if (_2 !== undefined) { + self.taken = rs.u8WrappingAdd(self.taken, 1); + return _2; + } + return undefined; +} + +//# sourceMappingURL=generics.Batch.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Deep.ts b/ts/fixtures/lang/shapes/expected/generics.Deep.ts new file mode 100644 index 000000000..4d5a9c0f5 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Deep.ts @@ -0,0 +1,19 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** A projection from a projection, which is one parameter whole. */ +export interface Deep<_S, Item> { + inner: rs.OptionOf; + len: rs.usize; +} + +export function new$array3_array2_u8(inner: rs.u8 | undefined): Deep { + return { inner: inner as rs.OptionOf, len: 3 * 2 }; +} + +export function new$array4_array2_unit(inner: undefined | rs.None): Deep { + return { inner: inner as rs.OptionOf, len: 4 * 2 }; +} + +//# sourceMappingURL=generics.Deep.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Drained.ts b/ts/fixtures/lang/shapes/expected/generics.Drained.ts new file mode 100644 index 000000000..1123188c4 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Drained.ts @@ -0,0 +1,85 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Flags from "./generics.Flags.ts"; + +/** + * A drain over a store's items, as smallvec's `Drain` holds `::Item`s: + * directly, in an `Option`, whose `None` each instance decides, and through a slice's + * iterator. + */ +export interface Drained<_S, Item> { + last: rs.OptionOf; + iter: rs.Iter>; + first: Item; +} + +export function new$array1_Option_u8(first: rs.u8 | undefined, items: rs.Slice): Drained, rs.u8 | undefined> { + return { last: rs.None as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first }; +} + +export function new$array2_u16(first: rs.u16, items: rs.Slice): Drained { + return { last: undefined as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first }; +} + +export function new$array3_unit(_: undefined, items: rs.Slice): Drained { + return { last: rs.None as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first: undefined }; +} + +export function new$array4_u8(first: rs.u8, items: rs.ByteSlice): Drained { + return { last: undefined as rs.OptionOf, iter: rs.iterSlice(rs.byteSliceView(items)) as rs.Iter>, first }; +} + +export function new$generics_Flags(first: boolean, items: rs.Slice): Drained { + return { last: undefined as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first }; +} + +export function next$array1_Option_u8(self: Drained, rs.u8 | undefined>): rs.u8 | undefined | rs.None { + const _3: rs.u8 | undefined | rs.Done = (self.iter as rs.Iter).next(); + if (_3 !== rs.DONE) { + const item: rs.u8 | undefined = _3; + self.last = item; + return item; + } + return rs.None; +} + +export function next$array2_u16(self: Drained): rs.u16 | undefined { + const _3: rs.u16 | rs.Done = (self.iter as rs.Iter).next(); + if (_3 !== rs.DONE) { + const item: rs.u16 = _3; + self.last = item; + return item; + } + return undefined; +} + +export function next$array3_unit(self: Drained): undefined | rs.None { + if ((self.iter as rs.Iter).next() !== rs.DONE) { + self.last = undefined; + return undefined; + } + return rs.None; +} + +export function next$array4_u8(self: Drained): rs.u8 | undefined { + const _3: rs.u8 | rs.Done = (self.iter as rs.Iter).next(); + if (_3 !== rs.DONE) { + const item: rs.u8 = _3; + self.last = item; + return item; + } + return undefined; +} + +export function next$generics_Flags(self: Drained): boolean | undefined { + const _3: boolean | rs.Done = (self.iter as rs.Iter).next(); + if (_3 !== rs.DONE) { + const item: boolean = _3; + self.last = item; + return item; + } + return undefined; +} + +//# sourceMappingURL=generics.Drained.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Flags.ts b/ts/fixtures/lang/shapes/expected/generics.Flags.ts new file mode 100644 index 000000000..840693580 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Flags.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A store of flags, whose items are no array's. */ +export interface Flags { +} + +//# sourceMappingURL=generics.Flags.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Pile.ts b/ts/fixtures/lang/shapes/expected/generics.Pile.ts new file mode 100644 index 000000000..601fcc4f7 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Pile.ts @@ -0,0 +1,48 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Flags from "./generics.Flags.ts"; + +/** A projection in an enum's variants, and in a type that holds itself. */ +export type Pile = + | { tag: "Empty" } + | { tag: "Full"; _0: Item } + | { tag: "Linked"; item: Item; next: Pile }; + +export function push$array2_u8(self: Pile, item: rs.u8): Pile { + if (self.tag === "Empty") { + return { tag: "Full", _0: item } as Pile; + } + return { tag: "Linked", item, next: self } as Pile; +} + +export function push$generics_Flags(self: Pile, item: boolean): Pile { + if (self.tag === "Empty") { + return { tag: "Full", _0: item } as Pile; + } + return { tag: "Linked", item, next: self } as Pile; +} + +export function items$array2_u8(self: Pile, out: rs.ByteVec): void { + if (self.tag !== "Empty") { + if (self.tag === "Full") { + rs.byteVecPush(out, self._0); + } else { + rs.byteVecPush(out, self.item); + items$array2_u8(self.next, out); + } + } +} + +export function items$generics_Flags(self: Pile, out: rs.Vec): void { + if (self.tag !== "Empty") { + if (self.tag === "Full") { + out.items.push(self._0); + } else { + out.items.push(self.item); + items$generics_Flags(self.next, out); + } + } +} + +//# sourceMappingURL=generics.Pile.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 810fdb373..f892f92e0 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -3,13 +3,19 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Run_generics_Ticked } from "./lib.ts"; import { thenSome$1ctlqen, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52 } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$0ggarad as option_map$0ggarad } from "./core/option.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; +import * as Ahead from "./generics.Ahead.ts"; +import * as Batch from "./generics.Batch.ts"; +import * as Deep from "./generics.Deep.ts"; +import * as Drained from "./generics.Drained.ts"; import * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; +import type * as Flags from "./generics.Flags.ts"; import type * as Lends from "./generics.Lends.ts"; import * as Parked from "./generics.Parked.ts"; +import * as Pile from "./generics.Pile.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; import * as Tagged from "./generics.Tagged.ts"; @@ -395,4 +401,197 @@ export function units(data: rs.ByteSlice, out: rs.ByteVec): void { put(out, rs.fmtFormat([args12[0], " ", args12[1], " ", args12[2], " ", args12[3]]).v); } +/** + * Generic types whose fields are typed by an associated type of their parameters, at + * arrays of bytes, of `u16`s, of `()`, whose `None` is `rs.None`, and of `Option`s, + * and at a store that is no array. + */ +export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { + const items: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b2: rs.u8): rs.u8 => { + return b2; + }), undefined, true); + const drain: Drained.Drained = Drained.new$array4_u8(((b3: rs.u8): rs.u8 => { + return b3; + })(90), rs.byteVecSlice(items)); + let count: rs.usize = 0; + while (Drained.next$array4_u8(drain) !== undefined) { + count = rs.usizeAdd(count, 1); + } + const _21: rs.u8 = ((b4: rs.u8): rs.u8 => { + return b4; + })(1); + const part = rs.usizeDiv(items.length, 2); + const batch: Batch.Batch = { drain: Drained.new$array4_u8(_21, rs.byteSliceRange(rs.byteVecSlice(items), part)), taken: 0 }; + const half: rs.u8 | undefined = Batch.take$array4_u8(batch); + const _46: rs.usize = count; + const arg = drain.last; + const _49: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg, f, rs.u8Debug); + const arg2 = drain.first; + const _50: rs.Fmt = (f: rs.Formatter) => rs.u8Debug(arg2, f); + const _51: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(_46, f); + const _52: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(4, f); + const _53: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(half, f, rs.u8Debug); + const arg3 = batch.drain.last; + const _54: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg3, f, rs.u8Debug); + const arg4 = batch.taken; + const args19 = [_49, _50, _51, _52, _53, _54, (f: rs.Formatter) => rs.u8Display(arg4, f)]; + put(out, rs.fmtFormat([args19[0], " ", args19[1], " ", args19[2], " ", args19[3], " ", args19[4], " ", args19[5], " ", args19[6]]).v); + const items2: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b5: rs.u8): rs.u16 => { + return rs.u16Mul(b5 as rs.u16, 257); + }), undefined, 2, true); + const drain2: Drained.Drained = Drained.new$array2_u16(((b6: rs.u8): rs.u16 => { + return rs.u16Mul(b6 as rs.u16, 257); + })(90), rs.sliceFrom(items2.items)); + let count2: rs.usize = 0; + while (Drained.next$array2_u16(drain2) !== undefined) { + count2 = rs.usizeAdd(count2, 1); + } + const _76: rs.u16 = ((b7: rs.u8): rs.u16 => { + return rs.u16Mul(b7 as rs.u16, 257); + })(1); + const part2 = rs.usizeDiv(items2.items.length, 2); + const batch2: Batch.Batch = { drain: Drained.new$array2_u16(_76, rs.sliceRange(rs.sliceFrom(items2.items), part2)), taken: 0 }; + const half2: rs.u16 | undefined = Batch.take$array2_u16(batch2); + const _101: rs.usize = count2; + const arg5 = drain2.last; + const _104: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg5, f, rs.u16Debug); + const arg6 = drain2.first; + const _105: rs.Fmt = (f: rs.Formatter) => rs.u16Debug(arg6, f); + const _106: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(_101, f); + const _107: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(2, f); + const _108: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(half2, f, rs.u16Debug); + const arg7 = batch2.drain.last; + const _109: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg7, f, rs.u16Debug); + const arg8 = batch2.taken; + const args20 = [_104, _105, _106, _107, _108, _109, (f: rs.Formatter) => rs.u8Display(arg8, f)]; + put(out, rs.fmtFormat([args20[0], " ", args20[1], " ", args20[2], " ", args20[3], " ", args20[4], " ", args20[5], " ", args20[6]]).v); + const items3: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (_2: rs.u8): undefined => {}), undefined, 0, true); + ((_26: rs.u8): undefined => {})(90); + const drain3: Drained.Drained = Drained.new$array3_unit(undefined, rs.sliceFrom(items3.items)); + let count3: rs.usize = 0; + while (Drained.next$array3_unit(drain3) !== rs.None) { + count3 = rs.usizeAdd(count3, 1); + } + ((_210: rs.u8): undefined => {})(1); + const part3 = rs.usizeDiv(items3.items.length, 2); + const batch3: Batch.Batch = { drain: Drained.new$array3_unit(undefined, rs.sliceRange(rs.sliceFrom(items3.items), part3)), taken: 0 }; + const half3: undefined | rs.None = Batch.take$array3_unit(batch3); + const _156: rs.usize = count3; + const arg9 = drain3.last; + const _159: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg9, f, rs.fmtUnit, rs.None); + const _160: rs.Fmt = (f: rs.Formatter) => rs.fmtUnit(undefined, f); + const _161: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(_156, f); + const _162: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(3, f); + const _163: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(half3, f, rs.fmtUnit, rs.None); + const arg10 = batch3.drain.last; + const _164: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg10, f, rs.fmtUnit, rs.None); + const arg11 = batch3.taken; + const args21 = [_159, _160, _161, _162, _163, _164, (f: rs.Formatter) => rs.u8Display(arg11, f)]; + put(out, rs.fmtFormat([args21[0], " ", args21[1], " ", args21[2], " ", args21[3], " ", args21[4], " ", args21[5], " ", args21[6]]).v); + const items4: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b8: rs.u8): rs.u8 | undefined => { + return thenSome$0quywni(rs.u8Rem(b8, 3) !== 0, b8); + }), undefined, 2, true); + const drain4: Drained.Drained, rs.u8 | undefined> = Drained.new$array1_Option_u8(((b9: rs.u8): rs.u8 | undefined => { + return thenSome$0quywni(rs.u8Rem(b9, 3) !== 0, b9); + })(90), rs.sliceFrom(items4.items)); + let count4: rs.usize = 0; + while (Drained.next$array1_Option_u8(drain4) !== rs.None) { + count4 = rs.usizeAdd(count4, 1); + } + const _186: rs.u8 | undefined = ((b10: rs.u8): rs.u8 | undefined => { + return thenSome$0quywni(rs.u8Rem(b10, 3) !== 0, b10); + })(1); + const part4 = rs.usizeDiv(items4.items.length, 2); + const batch4: Batch.Batch, rs.u8 | undefined> = { drain: Drained.new$array1_Option_u8(_186, rs.sliceRange(rs.sliceFrom(items4.items), part4)), taken: 0 }; + const half4: rs.u8 | undefined | rs.None = Batch.take$array1_Option_u8(batch4); + const _211: rs.usize = count4; + const arg12 = drain4.last; + const _214: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg12, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None); + const arg13 = drain4.first; + const _215: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg13, f, rs.u8Debug); + const _216: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(_211, f); + const _217: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(1, f); + const _218: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(half4, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None); + const arg14 = batch4.drain.last; + const _219: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg14, f, (value: rs.u8 | undefined, f: rs.Formatter) => rs.fmtOption(value, f, rs.u8Debug), rs.None); + const arg15 = batch4.taken; + const args22 = [_214, _215, _216, _217, _218, _219, (f: rs.Formatter) => rs.u8Display(arg15, f)]; + put(out, rs.fmtFormat([args22[0], " ", args22[1], " ", args22[2], " ", args22[3], " ", args22[4], " ", args22[5], " ", args22[6]]).v); + const items5: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b11: rs.u8): boolean => { + return (b11 & 1) !== 0; + }), undefined, 1, true); + const drain5: Drained.Drained = Drained.new$generics_Flags(((b12: rs.u8): boolean => { + return (b12 & 1) !== 0; + })(90), rs.sliceFrom(items5.items)); + let count5: rs.usize = 0; + while (Drained.next$generics_Flags(drain5) !== undefined) { + count5 = rs.usizeAdd(count5, 1); + } + const _241: boolean = ((b13: rs.u8): boolean => { + return (b13 & 1) !== 0; + })(1); + const part5 = rs.usizeDiv(items5.items.length, 2); + const batch5: Batch.Batch = { drain: Drained.new$generics_Flags(_241, rs.sliceRange(rs.sliceFrom(items5.items), part5)), taken: 0 }; + const half5: boolean | undefined = Batch.take$generics_Flags(batch5); + const _266: rs.usize = count5; + const arg16 = drain5.last; + const _269: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg16, f, rs.fmtBool); + const arg17 = drain5.first; + const _270: rs.Fmt = (f: rs.Formatter) => rs.fmtBool(arg17, f); + const _271: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(_266, f); + const _272: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(8, f); + const _273: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(half5, f, rs.fmtBool); + const arg18 = batch5.drain.last; + const _274: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg18, f, rs.fmtBool); + const arg19 = batch5.taken; + const args23 = [_269, _270, _271, _272, _273, _274, (f: rs.Formatter) => rs.u8Display(arg19, f)]; + put(out, rs.fmtFormat([args23[0], " ", args23[1], " ", args23[2], " ", args23[3], " ", args23[4], " ", args23[5], " ", args23[6]]).v); + const ahead: Ahead.Ahead, rs.u8, boolean> = { iter: rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), seen: new rs.Vec(), stored: option_map$132wgz4(rs.byteSliceFirst(data), (b14: rs.u8): boolean => { + return (b14 & 2) !== 0; + }) as rs.OptionOf }; + const repeats: rs.usize = Ahead.repeats$generics_Flags_slice_Iter_u8(ahead); + const part6 = ahead.seen as rs.Vec; + const _297: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(repeats, f); + const _298: rs.Fmt = (f: rs.Formatter) => rs.fmtList(part6.items, f, rs.u8Debug); + const arg20 = ahead.stored; + const args24 = [_297, _298, (f: rs.Formatter) => rs.fmtOption(arg20, f, rs.fmtBool)]; + put(out, rs.fmtFormat([args24[0], " ", args24[1], " ", args24[2]]).v); + const ahead2: Ahead.Ahead, rs.u8, rs.u16> = { iter: rs.iterPeekable(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b15: rs.u8): rs.u8 => { + return rs.u8Rem(b15, 4); + })), seen: new rs.ByteVec(), stored: option_map$0ydco3f(rs.byteSliceLast(data), (_212: rs.u8): rs.u16 => { + return _212 as rs.u16; + }) as rs.OptionOf }; + const repeats2: rs.usize = Ahead.repeats$array1_u16_iter_Map_slice_Iter_u8_generics_projections_closure1(ahead2); + const part7 = ahead2.seen as rs.ByteVec; + const _322: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(repeats2, f); + const _323: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(part7), f, rs.u8Debug); + const arg21 = ahead2.stored; + const args25 = [_322, _323, (f: rs.Formatter) => rs.fmtOption(arg21, f, rs.u16Debug)]; + put(out, rs.fmtFormat([args25[0], " ", args25[1], " ", args25[2]]).v); + let pile: Pile.Pile = { tag: "Empty" }; + let flags: Pile.Pile = { tag: "Empty" }; + for (const b of rs.byteSliceView(data)) { + pile = Pile.push$array2_u8(pile, b); + flags = Pile.push$generics_Flags(flags, b > 127); + } + const _342: [rs.ByteVec, rs.Vec] = [new rs.ByteVec(), new rs.Vec()]; + const bytes: rs.ByteVec = _342[0]; + const bits: rs.Vec = _342[1]; + Pile.items$array2_u8(pile, bytes); + Pile.items$generics_Flags(flags, bits); + const args26 = [(f: rs.Formatter) => rs.fmtList(rs.byteVecView(bytes), f, rs.u8Debug), (f: rs.Formatter) => rs.fmtList(bits.items, f, rs.fmtBool)]; + put(out, rs.fmtFormat([args26[0], " ", args26[1]]).v); + const deep: Deep.Deep = Deep.new$array3_array2_u8(rs.byteSliceFirst(data)); + const units: Deep.Deep = Deep.new$array4_array2_unit(option_map$057a36e(rs.byteSliceGet(data, 1), (_213: rs.u8): undefined => {})); + const arg22 = deep.inner; + const _383: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg22, f, rs.u8Debug); + const arg23 = deep.len; + const _384: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(arg23, f); + const arg24 = units.inner; + const _385: rs.Fmt = (f: rs.Formatter) => rs.fmtOption(arg24, f, rs.fmtUnit, rs.None); + const arg25 = units.len; + const args27 = [_383, _384, _385, (f: rs.Formatter) => rs.usizeDisplay(arg25, f)]; + put(out, rs.fmtFormat([args27[0], " ", args27[1], " ", args27[2], " ", args27[3]]).v); +} + //# sourceMappingURL=generics.ts.map diff --git a/ts/fixtures/lang/shapes/expected/lib.ts b/ts/fixtures/lang/shapes/expected/lib.ts index 4900e09af..170fe7066 100644 --- a/ts/fixtures/lang/shapes/expected/lib.ts +++ b/ts/fixtures/lang/shapes/expected/lib.ts @@ -2,7 +2,7 @@ import * as rs from "./rs.ts"; import { ints, strings, derived, written, counts, rounding, panics, wrapped, collections, enums, iters, adaptors, writes, objects, sources, sets, calls, cells, searches } from "./fmt.ts"; -import { methods, runs, units } from "./generics.ts"; +import { methods, runs, units, projections } from "./generics.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Run from "./generics.Run.ts"; @@ -220,6 +220,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By searches(data27, out28); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::units", (_238: rs.u8, data28: rs.ByteSlice, out29: rs.ByteVec): undefined => { units(data28, out29); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::projections", (_239: rs.u8, data29: rs.ByteSlice, out30: rs.ByteVec): undefined => { + projections(data29, out30); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 442327807..596b3abbb 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -461,3 +461,217 @@ pub fn units(data: &[u8], out: &mut Vec) { parked_at!(data, out, [Poll::Pending, Poll::Ready(1u8)]); parked_at!(data, out, [3u8, 4]); } + +/// What a fixed buffer holds, as smallvec's `Array` names its arrays' items. +pub trait Store { + type Item; + const LEN: usize; +} + +impl Store for [T; N] { + type Item = T; + const LEN: usize = N; +} + +/// A store of flags, whose items are no array's. +struct Flags; + +impl Store for Flags { + type Item = bool; + const LEN: usize = 8; +} + +/// A drain over a store's items, as smallvec's `Drain` holds `::Item`s: +/// directly, in an `Option`, whose `None` each instance decides, and through a slice's +/// iterator. +struct Drained<'a, S: Store> { + last: Option, + iter: std::slice::Iter<'a, S::Item>, + first: S::Item, +} + +impl<'a, S: Store> Drained<'a, S> +where + S::Item: Copy, +{ + fn new(first: S::Item, items: &'a [S::Item]) -> Self { + Self { + last: None, + iter: items.iter(), + first, + } + } + + fn next(&mut self) -> Option { + let item = *self.iter.next()?; + self.last = Some(item); + Some(item) + } +} + +/// A [`Drained`] in another generic type, which names the projection only through it. +struct Batch<'a, S: Store> { + drain: Drained<'a, S>, + taken: u8, +} + +impl Batch<'_, S> +where + S::Item: Copy, +{ + fn take(&mut self) -> Option { + let item = self.drain.next()?; + self.taken = self.taken.wrapping_add(1); + Some(item) + } +} + +/// A std iterator's items, which only its `Iterator` impl names, beside the same +/// projection named again; a parameter named for an associated type, so each projection's +/// name is numbered past it. +struct Ahead { + iter: std::iter::Peekable, + seen: Vec, + stored: Option, +} + +impl Ahead +where + I::Item: Copy + PartialEq, +{ + /// Takes each item that repeats the one before it. + fn repeats(&mut self) -> usize { + while let Some(item) = self.iter.next() { + if self.iter.peek() == Some(&item) { + self.seen.push(item); + } + } + self.seen.len() + } +} + +/// A projection in an enum's variants, and in a type that holds itself. +enum Pile { + Empty, + Full(S::Item), + Linked { item: S::Item, next: Box> }, +} + +impl Pile +where + S::Item: Copy, +{ + fn push(self, item: S::Item) -> Self { + match self { + Pile::Empty => Pile::Full(item), + next => Pile::Linked { + item, + next: Box::new(next), + }, + } + } + + fn items(&self, out: &mut Vec) { + match self { + Pile::Empty => {} + Pile::Full(item) => out.push(*item), + Pile::Linked { item, next } => { + out.push(*item); + next.items(out); + } + } + } +} + +/// A projection from a projection, which is one parameter whole. +struct Deep +where + S::Item: Store, +{ + inner: Option<::Item>, + len: usize, +} + +impl Deep +where + S::Item: Store, +{ + fn new(inner: Option<::Item>) -> Self { + Self { + inner, + len: S::LEN * ::LEN, + } + } +} + +/// [`Drained`] and [`Batch`] of `$store`, of the items `$item` makes of each byte. +macro_rules! drained_at { + ($data:expr, $out:expr, $store:ty, $item:expr) => {{ + let items: Vec<<$store as Store>::Item> = $data.iter().map($item).collect(); + let mut drain = Drained::<$store>::new($item(&0x5a), &items); + let mut count = 0usize; + while drain.next().is_some() { + count += 1; + } + let mut batch = Batch { + drain: Drained::<$store>::new($item(&1), &items[items.len() / 2..]), + taken: 0, + }; + let half = batch.take(); + put( + $out, + &format!( + "{:?} {:?} {count} {} {half:?} {:?} {}", + drain.last, + drain.first, + <$store>::LEN, + batch.drain.last, + batch.taken + ), + ); + }}; +} + +/// Generic types whose fields are typed by an associated type of their parameters, at +/// arrays of bytes, of `u16`s, of `()`, whose `None` is `rs.None`, and of `Option`s, +/// and at a store that is no array. +pub fn projections(data: &[u8], out: &mut Vec) { + drained_at!(data, out, [u8; 4], |b: &u8| *b); + drained_at!(data, out, [u16; 2], |b: &u8| u16::from(*b) * 257); + drained_at!(data, out, [(); 3], |_: &u8| ()); + drained_at!(data, out, [Option; 1], |b: &u8| (b % 3 != 0).then_some(*b)); + drained_at!(data, out, Flags, |b: &u8| b & 1 != 0); + + let mut ahead = Ahead:: { + iter: data.iter().peekable(), + seen: Vec::new(), + stored: data.first().map(|b| b & 2 != 0), + }; + let repeats = ahead.repeats(); + put(out, &format!("{repeats} {:?} {:?}", ahead.seen, ahead.stored)); + let mut ahead = Ahead::<[u16; 1], _> { + iter: data.iter().map(|b| b % 4).peekable(), + seen: Vec::new(), + stored: data.last().map(|&b| u16::from(b)), + }; + let repeats = ahead.repeats(); + put(out, &format!("{repeats} {:?} {:?}", ahead.seen, ahead.stored)); + + let mut pile = Pile::<[u8; 2]>::Empty; + let mut flags = Pile::::Empty; + for &b in data { + pile = pile.push(b); + flags = flags.push(b > 0x7f); + } + let (mut bytes, mut bits) = (Vec::new(), Vec::new()); + pile.items(&mut bytes); + flags.items(&mut bits); + put(out, &format!("{bytes:?} {bits:?}")); + + let deep = Deep::<[[u8; 2]; 3]>::new(data.first().copied()); + let units = Deep::<[[(); 2]; 4]>::new(data.get(1).map(|_| ())); + put( + out, + &format!("{:?} {} {:?} {}", deep.inner, deep.len, units.inner, units.len), + ); +} diff --git a/ts/fixtures/lang/shapes/src/lib.rs b/ts/fixtures/lang/shapes/src/lib.rs index d3eb97926..250e52927 100644 --- a/ts/fixtures/lang/shapes/src/lib.rs +++ b/ts/fixtures/lang/shapes/src/lib.rs @@ -56,5 +56,6 @@ fn cases() -> Vec<(&'static str, Case)> { ("fmt::cells", |_, data, out| fmt::cells(data, out)), ("fmt::searches", |_, data, out| fmt::searches(data, out)), ("generics::units", |_, data, out| generics::units(data, out)), + ("generics::projections", |_, data, out| generics::projections(data, out)), ] } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 15daa35e1..adfefc4f3 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -285,6 +285,9 @@ fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to `` fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:70: comparing pointers to `places::Bare`, which has no bytes and so no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index af035c139..c39633b7e 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -285,6 +285,9 @@ fixtures/reject/src/values.rs:manual_impls+2:34: a constant of type `std::mem::M fixtures/reject/src/values.rs:mixed_byte+0:19: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:70: comparing pointers to `places::Bare`, which has no bytes and so no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 8ffa3a8e0..60e22f8c8 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -70,6 +70,41 @@ pub fn generic_uninit_byte_open(b: u8) -> bool { .is_some() } +/// What a type holds, named by an associated type. +pub trait Holds { + type Held; +} + +impl Holds for u32 { + type Held = std::mem::MaybeUninit; +} + +impl Holds for () { + type Held = Option<()>; +} + +/// A generic type's `Option` of a projection is an `rs.OptionOf` of it too, which each +/// instance resolves as [`Maybe`]'s. +pub struct Projected { + pub value: Option, +} + +/// Its `MaybeUninit`, whose `None` `rs.OptionOf` would make `undefined`. +pub fn projected_uninit(value: u32) -> bool { + Projected:: { + value: Some(std::mem::MaybeUninit::new(value)), + } + .value + .is_some() +} + +/// Its `Option<()>`, whose `None` and `Some(None)` would both be `rs.None`. +pub fn projected_nested(unit: bool) -> bool { + let projected: Projected<()> = Projected { value: None }; + drop(projected); + unit +} + /// Parsing an address is not translated yet. pub fn addr_parsed(s: &str) -> Result { s.parse() diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 4d5efb53d..795226199 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -207,6 +207,8 @@ roots = [ "values::manual_impls", "values::mixed_byte", "values::nested", + "values::projected_nested", + "values::projected_uninit", "values::units_compared", ] printable = ["config::Meters"] diff --git a/ts/src/lower/iter.rs b/ts/src/lower/iter.rs index 577f8368a..fc7439f38 100644 --- a/ts/src/lower/iter.rs +++ b/ts/src/lower/iter.rs @@ -44,7 +44,7 @@ impl<'tcx> Translator<'_, 'tcx> { let tcx = self.tcx; let (_, item) = trait_items(tcx)?; let projection = Ty::new_projection(tcx, ty::IsRigid::No, item, [ty]); - let env = self.defining.unwrap_or(self.typing_env_pub()); + let env = self.defining.map_or(self.typing_env_pub(), |(_, env)| env); tcx.try_normalize_erasing_regions(env, ty::Unnormalized::new_wip(projection)) .ok() } diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 413c14bc1..b29542289 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -206,15 +206,18 @@ pub struct Translator<'a, 'tcx> { /// by that type, which an instance's value is cast to so TypeScript infers the /// instance's arguments from it rather than from the `None` or `Pending` it may be. decided_fields: rustc_data_structures::fx::FxHashMap, + /// The projections from each generic type's own parameters that its fields name, by + /// [`Self::projections`]. + projections: rustc_data_structures::fx::FxHashMap, String)>>, /// Source files by name, as indexes into `program.sources`. sources: rustc_data_structures::fx::FxHashMap>, failed: bool, /// Where trait bounds come from: none outside a census, which lowers some functions /// over their own type parameters. env: TypingEnv<'tcx>, - /// Where the bounds of a generic type's parameters come from while its fields lower, by - /// which an iterator's item type over them normalizes. - defining: Option>, + /// The generic type whose fields lower, and where the bounds of its parameters come + /// from, by which an iterator's item type over them normalizes. + defining: Option<(DefId, TypingEnv<'tcx>)>, /// Whether this is a census, which lowers some functions over their type parameters and /// writes nothing. census: bool, @@ -502,6 +505,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { overlays: Default::default(), instanced_fields: Default::default(), decided_fields: Default::default(), + projections: Default::default(), sources: Default::default(), failed: false, env: TypingEnv::fully_monomorphized(), @@ -1101,6 +1105,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { fn define(&mut self, adt: AdtDef<'tcx>, span: Span) -> Adt { let tcx = self.tcx; let did = adt.did(); + // Before any field is typed, which names them. + let projections = self.projections(did); let mut overlay = None; let repr = match () { _ if adt.is_struct() => { @@ -1171,7 +1177,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } self.adts.insert(did, info.clone()); - let params: Vec = tcx + let mut params: Vec = tcx .generics_of(did) .own_params .iter() @@ -1181,6 +1187,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::GenericParamDefKind::Lifetime | ty::GenericParamDefKind::Const { .. } => None, }) .collect(); + params.extend(projections.into_iter().map(|(_, name)| name)); let item = match &info.repr { AdtRepr::Struct { ctor, fields } => { let variant = adt.non_enum_variant(); @@ -1526,6 +1533,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { false => Type::Rs("Slice", vec![self.ty(*elem, span)]), }, ty::Param(param) => Type::Param(param.name.to_string()), + // A projection a generic type's fields name, which each instance decides. + ty::Alias(..) + if let Some((adt, _)) = self.defining + && let Some((_, name)) = self + .projections + .get(&adt) + .and_then(|p| p.iter().find(|(p, _)| *p == ty)) => + { + Type::Param(name.clone()) + } ty::FnDef(def_id, args) => { let sig = self.sig(*def_id, args.skip_binder()); self.fn_ty(sig.inputs(), sig.output(), span) @@ -1887,10 +1904,113 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if let Some(msg) = self.generic_erasure(adt, args, span) { self.err(span, msg); } - let args = args.types().map(|t| self.ty(t, span)).collect(); + let args = self + .instance_params(adt.did(), args, span) + .into_iter() + .map(|(_, t)| t) + .collect(); Type::Sym(info.sym, args) } + /// The TypeScript type arguments of an instance of the crate's generic `adt` with `args`, + /// by the name of the parameter each is: one per type parameter, then one per projection + /// its fields name, which is what the projection normalizes to at `args`. + fn instance_params(&mut self, adt: DefId, args: GenericArgsRef<'tcx>, span: Span) -> Vec<(String, Type)> { + let tcx = self.tcx; + let mut params: Vec<(String, Type)> = (tcx.generics_of(adt).own_params.iter()) + .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) + .map(|param| { + ( + param.name.to_string(), + self.ty(args.type_at(param.index as usize), span), + ) + }) + .collect(); + let env = self.defining.map_or(self.typing_env(), |(_, env)| env); + for (projection, name) in self.projections(adt) { + let at = copy::instantiate(tcx, env, projection, args); + params.push((name, self.ty(at, span))); + } + params + } + + /// The projections from the crate's generic `adt`'s own type parameters that its fields' + /// types name, such as smallvec's `::Item`, each a TypeScript type parameter + /// of its own, by its name: the associated type's, numbered past any it would shadow. + /// They include those of the crate's generic types its fields hold, at its arguments, and + /// the item type of an iterator from another crate, which `rs.Iter` types. + fn projections(&mut self, adt: DefId) -> Vec<(Ty<'tcx>, String)> { + if let Some(named) = self.projections.get(&adt) { + return named.clone(); + } + let tcx = self.tcx; + // A type that holds itself holds no projection it doesn't name otherwise. + self.projections.insert(adt, Vec::new()); + let identity = ty::GenericArgs::identity_for_item(tcx, adt); + let env = TypingEnv::post_analysis(tcx, adt); + let outer = self.defining.replace((adt, env)); + let mut found = Vec::new(); + for field in tcx.adt_def(adt).all_fields() { + self.find_projections(copy::field_ty(tcx, env, field.did, identity), env, &mut found); + } + self.defining = outer; + let mut names: Vec = (tcx.generics_of(adt).own_params.iter()) + .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) + .map(|param| param.name.to_string()) + .collect(); + let mut named = Vec::with_capacity(found.len()); + for projection in found { + let ty::Alias(_, alias) = projection.kind() else { + unreachable!("a projection") + }; + let item = alias.kind.try_to_projection().expect("a projection"); + let base = tcx.item_name(item).to_string(); + let name = (1..) + .map(|n| match n { + 1 => base.clone(), + n => format!("{base}{n}"), + }) + .find(|name| !names.contains(name)) + .unwrap(); + names.push(name.clone()); + named.push((projection, name)); + } + self.projections.insert(adt, named.clone()); + named + } + + /// Add to `found` each projection over type parameters that typing `ty` in `env` names. + fn find_projections(&mut self, ty: Ty<'tcx>, env: TypingEnv<'tcx>, found: &mut Vec>) { + let tcx = self.tcx; + let mut walker = ty.walk(); + while let Some(arg) = walker.next() { + let Some(t) = arg.as_type() else { continue }; + match t.kind() { + ty::Alias(_, alias) if alias.kind.try_to_projection().is_some() && t.has_param() => { + if !found.contains(&t) { + found.push(t); + } + walker.skip_current_subtree(); + } + ty::Adt(adt, args) + if self.crates.translates(adt.did()) + && library::Std::of(tcx, adt.did()).is_none() + && self.bound_drops(adt.did()).is_none() => + { + for (projection, _) in self.projections(adt.did()) { + self.find_projections(copy::instantiate(tcx, env, projection, args), env, found); + } + } + ty::Adt(adt, _) if !self.crates.translates(adt.did()) => { + if let Some(item) = self.iter_item(t) { + self.find_projections(item, env, found); + } + } + _ => {} + } + } + } + /// The TypeScript type of `field` in the definition of `adt`, over its own parameters, /// noting a field that holds a form only an instance decides for /// [`Self::held_field`]. @@ -1901,7 +2021,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let identity = ty::GenericArgs::identity_for_item(tcx, adt); let env = TypingEnv::post_analysis(tcx, adt); let ty = copy::field_ty(tcx, env, field.did, identity); - let outer = self.defining.replace(env); + let outer = self.defining.replace((adt, env)); let ty = self.ty(ty, span); self.defining = outer; if ty.any(&instanced) { @@ -1934,15 +2054,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { None => return value, }, }; - let params: Vec<(String, Type)> = (self.tcx.generics_of(adt).own_params.iter()) - .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) - .map(|param| { - ( - param.name.to_string(), - self.ty(args.type_at(param.index as usize), span), - ) - }) - .collect(); + let params = self.instance_params(adt, args, span); let at = defined.subst(&|name| params.iter().find(|(p, _)| p == name).map(|(_, t)| t.clone())); ts::Expr::As(Box::new(value), at) } @@ -1965,8 +2077,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { fn generic_erasure(&mut self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, span: Span) -> Option { let tcx = self.tcx; let ty = Ty::new_adt(tcx, adt, args); + let identity = ty::GenericArgs::identity_for_item(tcx, adt.did()); + let defining = (adt.did(), TypingEnv::post_analysis(tcx, adt.did())); + let env = self.defining.map_or(self.typing_env(), |(_, env)| env); for field in adt.all_fields() { - let generic = tcx.type_of(field.did).instantiate_identity().skip_norm_wip(); + let generic = copy::field_ty(tcx, defining.1, field.did, identity); for part in generic.walk().filter_map(|arg| arg.as_type()) { let ty::Adt(wrapper, wrapper_args) = part.kind() else { continue; @@ -1981,7 +2096,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if !erased.param { continue; } - let held = ty::EarlyBinder::bind(tcx, part).instantiate(tcx, args).skip_norm_wip(); + let held = copy::instantiate(tcx, env, part, args); if let Some(msg) = self.erasure_conflict(held) { return Some(format!( "`{ty}` is not supported: its field `{}` is `{held}`: {msg}", @@ -1992,17 +2107,20 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if held.has_param() { continue; } - let instance = ty::EarlyBinder::bind(tcx, inner).instantiate(tcx, args).skip_norm_wip(); + let instance = copy::instantiate(tcx, env, inner, args); let at = self.erases_to(instance); + let outer = self.defining.replace(defining); let defined = self.ty(inner, span); + self.defining = outer; let msg = match defined { // What TypeScript's type of the parameter decides, which must be what the // instance's erasure does. Type::Param(_) if if option { !erased.undefined } else { !erased.pending } => { - let Some(param) = inner.walk().filter_map(|arg| arg.as_type()).find(|t| matches!(t.kind(), ty::Param(_))) else { + // The parameter, or the projection from one, that it types. + let Some(param) = inner.walk().filter_map(|arg| arg.as_type()).find(|t| matches!(t.kind(), ty::Param(_) | ty::Alias(..))) else { continue; }; - let arg = ty::EarlyBinder::bind(tcx, param).instantiate(tcx, args).skip_norm_wip(); + let arg = copy::instantiate(tcx, env, param, args); let decided = self.ty(arg, span); let (holds, alias) = match option { true => (holds_undefined(&decided) == at.undefined, "OptionOf"), @@ -2142,6 +2260,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { param: true, ..Erased::default() }, + ty::Alias(..) if ty.has_param() => Erased { + param: true, + ..Erased::default() + }, // An `rs.Place`, which is an object. ty::Ref(..) if self.place_pointee(ty).is_some() => Erased::default(), ty::Ref(_, inner, _) => self.erases_to(*inner), From 55256a3c4d34f078b3265b49101ec5494f0565a8 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 04:28:58 -0800 Subject: [PATCH 03/21] fix(ts): drop a generic type's projected fields, and skip what types nothing A projection from a type parameter may drop at an instance, as the parameter may, so a generic type holding one keeps its glue, which drops it through its [Symbol.dispose], and an instance shares that glue only where what each of its projections is can be disposed of. Projections only a PhantomData or a std iterator's own arguments hold name no type parameter, as typing them names nothing. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/expected/core/bool.ts | 8 ++ .../lang/shapes/expected/generics.Ahead.ts | 8 +- .../lang/shapes/expected/generics.Batch.ts | 7 +- .../lang/shapes/expected/generics.Deep.ts | 11 +- .../lang/shapes/expected/generics.Drained.ts | 18 ++- .../lang/shapes/expected/generics.Kept.ts | 30 +++++ .../lang/shapes/expected/generics.Marked.ts | 11 ++ .../lang/shapes/expected/generics.Ticks.ts | 7 ++ ts/fixtures/lang/shapes/expected/generics.ts | 115 ++++++++++++------ ts/fixtures/lang/shapes/expected/lib.ts | 13 ++ ts/fixtures/lang/shapes/src/generics.rs | 65 ++++++++++ ts/src/lower/drop.rs | 27 +++- ts/src/lower/mod.rs | 17 ++- 13 files changed, 279 insertions(+), 58 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Kept.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Marked.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Ticks.ts diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index e30f27734..4ce5d18e7 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -2,6 +2,7 @@ import * as rs from "../rs.ts"; import type * as Pair from "../fmt.Pair.ts"; +import type * as Ticked from "../generics.Ticked.ts"; import type * as Void from "../shapes.Void.ts"; export function thenSome$1ctlqen(self: boolean, t: rs.u8 | undefined): rs.u8 | undefined | rs.None { @@ -67,6 +68,13 @@ export function then$0y7on52(self: boolean, _: () => rs.ByteVec): rs.ByteVec | u return new rs.ByteVec(); } +export function then$1qflv5x(self: boolean, f: () => Ticked.Ticked): Ticked.Ticked | undefined { + if (!self) { + return undefined; + } + return f(); +} + export function then$1ldvmdq(self: boolean, f: () => rs.u8): rs.u8 | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Ahead.ts b/ts/fixtures/lang/shapes/expected/generics.Ahead.ts index c0f15e68e..00e606cfe 100644 --- a/ts/fixtures/lang/shapes/expected/generics.Ahead.ts +++ b/ts/fixtures/lang/shapes/expected/generics.Ahead.ts @@ -8,10 +8,16 @@ import type * as Flags from "./generics.Flags.ts"; * projection named again; a parameter named for an associated type, so each projection's * name is numbered past it. */ -export interface Ahead<_Item, _I, Item2, Item3> { +export class Ahead<_Item, _I, Item2, Item3> { iter: rs.Peekable; seen: rs.VecOf; stored: rs.OptionOf; + + constructor(iter: rs.Peekable, seen: rs.VecOf, stored: rs.OptionOf) { + this.iter = iter; + this.seen = seen; + this.stored = stored; + } } /** Takes each item that repeats the one before it. */ diff --git a/ts/fixtures/lang/shapes/expected/generics.Batch.ts b/ts/fixtures/lang/shapes/expected/generics.Batch.ts index 3a9f373a8..0a54d4b55 100644 --- a/ts/fixtures/lang/shapes/expected/generics.Batch.ts +++ b/ts/fixtures/lang/shapes/expected/generics.Batch.ts @@ -5,9 +5,14 @@ import * as Drained from "./generics.Drained.ts"; import type * as Flags from "./generics.Flags.ts"; /** A [`Drained`] in another generic type, which names the projection only through it. */ -export interface Batch { +export class Batch { drain: Drained.Drained; taken: rs.u8; + + constructor(drain: Drained.Drained, taken: rs.u8) { + this.drain = drain; + this.taken = taken; + } } export function take$array1_Option_u8(self: Batch, rs.u8 | undefined>): rs.u8 | undefined | rs.None { diff --git a/ts/fixtures/lang/shapes/expected/generics.Deep.ts b/ts/fixtures/lang/shapes/expected/generics.Deep.ts index 4d5a9c0f5..f30e996a3 100644 --- a/ts/fixtures/lang/shapes/expected/generics.Deep.ts +++ b/ts/fixtures/lang/shapes/expected/generics.Deep.ts @@ -3,17 +3,22 @@ import * as rs from "./rs.ts"; /** A projection from a projection, which is one parameter whole. */ -export interface Deep<_S, Item> { +export class Deep<_S, Item> { inner: rs.OptionOf; len: rs.usize; + + constructor(inner: rs.OptionOf, len: rs.usize) { + this.inner = inner; + this.len = len; + } } export function new$array3_array2_u8(inner: rs.u8 | undefined): Deep { - return { inner: inner as rs.OptionOf, len: 3 * 2 }; + return new Deep(inner as rs.OptionOf, 3 * 2); } export function new$array4_array2_unit(inner: undefined | rs.None): Deep { - return { inner: inner as rs.OptionOf, len: 4 * 2 }; + return new Deep(inner as rs.OptionOf, 4 * 2); } //# sourceMappingURL=generics.Deep.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Drained.ts b/ts/fixtures/lang/shapes/expected/generics.Drained.ts index 1123188c4..dc0a5615a 100644 --- a/ts/fixtures/lang/shapes/expected/generics.Drained.ts +++ b/ts/fixtures/lang/shapes/expected/generics.Drained.ts @@ -8,30 +8,36 @@ import type * as Flags from "./generics.Flags.ts"; * directly, in an `Option`, whose `None` each instance decides, and through a slice's * iterator. */ -export interface Drained<_S, Item> { +export class Drained<_S, Item> { last: rs.OptionOf; iter: rs.Iter>; first: Item; + + constructor(last: rs.OptionOf, iter: rs.Iter>, first: Item) { + this.last = last; + this.iter = iter; + this.first = first; + } } export function new$array1_Option_u8(first: rs.u8 | undefined, items: rs.Slice): Drained, rs.u8 | undefined> { - return { last: rs.None as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first }; + return new Drained(rs.None as rs.OptionOf, rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first); } export function new$array2_u16(first: rs.u16, items: rs.Slice): Drained { - return { last: undefined as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first }; + return new Drained(undefined as rs.OptionOf, rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first); } export function new$array3_unit(_: undefined, items: rs.Slice): Drained { - return { last: rs.None as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first: undefined }; + return new Drained(rs.None as rs.OptionOf, rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, undefined); } export function new$array4_u8(first: rs.u8, items: rs.ByteSlice): Drained { - return { last: undefined as rs.OptionOf, iter: rs.iterSlice(rs.byteSliceView(items)) as rs.Iter>, first }; + return new Drained(undefined as rs.OptionOf, rs.iterSlice(rs.byteSliceView(items)) as rs.Iter>, first); } export function new$generics_Flags(first: boolean, items: rs.Slice): Drained { - return { last: undefined as rs.OptionOf, iter: rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first }; + return new Drained(undefined as rs.OptionOf, rs.iterSlice(rs.sliceItems(items)) as rs.Iter>, first); } export function next$array1_Option_u8(self: Drained, rs.u8 | undefined>): rs.u8 | undefined | rs.None { diff --git a/ts/fixtures/lang/shapes/expected/generics.Kept.ts b/ts/fixtures/lang/shapes/expected/generics.Kept.ts new file mode 100644 index 000000000..19d385c00 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Kept.ts @@ -0,0 +1,30 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** + * Items that drop beside the store that names them, which drops nothing, so only the + * items' type decides whether and how each instance drops. + */ +export class Kept { + item: Item; + store: S; + rest: rs.VecOf; + maybe: rs.OptionOf; + + constructor(item: Item, store: S, rest: rs.VecOf, maybe: rs.OptionOf) { + this.item = item; + this.store = store; + this.rest = rest; + this.maybe = maybe; + } + + [Symbol.dispose](): void { + rs.drop(this.item); + rs.drop(this.store); + rs.dropVec(this.rest as rs.Vec); + rs.drop(this.maybe); + } +} + +//# sourceMappingURL=generics.Kept.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Marked.ts b/ts/fixtures/lang/shapes/expected/generics.Marked.ts new file mode 100644 index 000000000..2dbd2b8c9 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Marked.ts @@ -0,0 +1,11 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** A projection that only a `PhantomData` holds, which types nothing. */ +export interface Marked<_S> { + marker: undefined; + count: rs.u8; +} + +//# sourceMappingURL=generics.Marked.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Ticks.ts b/ts/fixtures/lang/shapes/expected/generics.Ticks.ts new file mode 100644 index 000000000..e7e61dd41 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Ticks.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A store of [`Ticked`]s, which drops nothing itself. */ +export interface Ticks { +} + +//# sourceMappingURL=generics.Ticks.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index f892f92e0..63b269ebe 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -1,8 +1,8 @@ // Generated by rust2-ts. Do not edit. import * as rs from "./rs.ts"; -import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Run_generics_Ticked } from "./lib.ts"; -import { thenSome$1ctlqen, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52 } from "./core/bool.ts"; +import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; +import { thenSome$1ctlqen, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; @@ -13,6 +13,7 @@ import * as Drained from "./generics.Drained.ts"; import * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Flags from "./generics.Flags.ts"; +import * as Kept from "./generics.Kept.ts"; import type * as Lends from "./generics.Lends.ts"; import * as Parked from "./generics.Parked.ts"; import * as Pile from "./generics.Pile.ts"; @@ -20,6 +21,7 @@ import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; import * as Tagged from "./generics.Tagged.ts"; import * as Ticked from "./generics.Ticked.ts"; +import type * as Ticks from "./generics.Ticks.ts"; import * as Wrap from "./generics.Wrap.ts"; import { put } from "./harness.ts"; import * as shapes_Gauge from "./shapes.Gauge.ts"; @@ -407,21 +409,22 @@ export function units(data: rs.ByteSlice, out: rs.ByteVec): void { * and at a store that is no array. */ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { - const items: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b2: rs.u8): rs.u8 => { - return b2; - }), undefined, true); - const drain: Drained.Drained = Drained.new$array4_u8(((b3: rs.u8): rs.u8 => { + let _428!: rs.u8; + const items: rs.ByteVec = rs.iterToByteVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): rs.u8 => { return b3; + }), undefined, true); + const drain: Drained.Drained = Drained.new$array4_u8(((b4: rs.u8): rs.u8 => { + return b4; })(90), rs.byteVecSlice(items)); let count: rs.usize = 0; while (Drained.next$array4_u8(drain) !== undefined) { count = rs.usizeAdd(count, 1); } - const _21: rs.u8 = ((b4: rs.u8): rs.u8 => { - return b4; + const _21: rs.u8 = ((b5: rs.u8): rs.u8 => { + return b5; })(1); const part = rs.usizeDiv(items.length, 2); - const batch: Batch.Batch = { drain: Drained.new$array4_u8(_21, rs.byteSliceRange(rs.byteVecSlice(items), part)), taken: 0 }; + const batch: Batch.Batch = new Batch.Batch(Drained.new$array4_u8(_21, rs.byteSliceRange(rs.byteVecSlice(items), part)), 0); const half: rs.u8 | undefined = Batch.take$array4_u8(batch); const _46: rs.usize = count; const arg = drain.last; @@ -436,21 +439,21 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const arg4 = batch.taken; const args19 = [_49, _50, _51, _52, _53, _54, (f: rs.Formatter) => rs.u8Display(arg4, f)]; put(out, rs.fmtFormat([args19[0], " ", args19[1], " ", args19[2], " ", args19[3], " ", args19[4], " ", args19[5], " ", args19[6]]).v); - const items2: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b5: rs.u8): rs.u16 => { - return rs.u16Mul(b5 as rs.u16, 257); - }), undefined, 2, true); - const drain2: Drained.Drained = Drained.new$array2_u16(((b6: rs.u8): rs.u16 => { + const items2: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b6: rs.u8): rs.u16 => { return rs.u16Mul(b6 as rs.u16, 257); + }), undefined, 2, true); + const drain2: Drained.Drained = Drained.new$array2_u16(((b7: rs.u8): rs.u16 => { + return rs.u16Mul(b7 as rs.u16, 257); })(90), rs.sliceFrom(items2.items)); let count2: rs.usize = 0; while (Drained.next$array2_u16(drain2) !== undefined) { count2 = rs.usizeAdd(count2, 1); } - const _76: rs.u16 = ((b7: rs.u8): rs.u16 => { - return rs.u16Mul(b7 as rs.u16, 257); + const _76: rs.u16 = ((b8: rs.u8): rs.u16 => { + return rs.u16Mul(b8 as rs.u16, 257); })(1); const part2 = rs.usizeDiv(items2.items.length, 2); - const batch2: Batch.Batch = { drain: Drained.new$array2_u16(_76, rs.sliceRange(rs.sliceFrom(items2.items), part2)), taken: 0 }; + const batch2: Batch.Batch = new Batch.Batch(Drained.new$array2_u16(_76, rs.sliceRange(rs.sliceFrom(items2.items), part2)), 0); const half2: rs.u16 | undefined = Batch.take$array2_u16(batch2); const _101: rs.usize = count2; const arg5 = drain2.last; @@ -474,7 +477,7 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { } ((_210: rs.u8): undefined => {})(1); const part3 = rs.usizeDiv(items3.items.length, 2); - const batch3: Batch.Batch = { drain: Drained.new$array3_unit(undefined, rs.sliceRange(rs.sliceFrom(items3.items), part3)), taken: 0 }; + const batch3: Batch.Batch = new Batch.Batch(Drained.new$array3_unit(undefined, rs.sliceRange(rs.sliceFrom(items3.items), part3)), 0); const half3: undefined | rs.None = Batch.take$array3_unit(batch3); const _156: rs.usize = count3; const arg9 = drain3.last; @@ -488,21 +491,21 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const arg11 = batch3.taken; const args21 = [_159, _160, _161, _162, _163, _164, (f: rs.Formatter) => rs.u8Display(arg11, f)]; put(out, rs.fmtFormat([args21[0], " ", args21[1], " ", args21[2], " ", args21[3], " ", args21[4], " ", args21[5], " ", args21[6]]).v); - const items4: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b8: rs.u8): rs.u8 | undefined => { - return thenSome$0quywni(rs.u8Rem(b8, 3) !== 0, b8); - }), undefined, 2, true); - const drain4: Drained.Drained, rs.u8 | undefined> = Drained.new$array1_Option_u8(((b9: rs.u8): rs.u8 | undefined => { + const items4: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b9: rs.u8): rs.u8 | undefined => { return thenSome$0quywni(rs.u8Rem(b9, 3) !== 0, b9); + }), undefined, 2, true); + const drain4: Drained.Drained, rs.u8 | undefined> = Drained.new$array1_Option_u8(((b10: rs.u8): rs.u8 | undefined => { + return thenSome$0quywni(rs.u8Rem(b10, 3) !== 0, b10); })(90), rs.sliceFrom(items4.items)); let count4: rs.usize = 0; while (Drained.next$array1_Option_u8(drain4) !== rs.None) { count4 = rs.usizeAdd(count4, 1); } - const _186: rs.u8 | undefined = ((b10: rs.u8): rs.u8 | undefined => { - return thenSome$0quywni(rs.u8Rem(b10, 3) !== 0, b10); + const _186: rs.u8 | undefined = ((b11: rs.u8): rs.u8 | undefined => { + return thenSome$0quywni(rs.u8Rem(b11, 3) !== 0, b11); })(1); const part4 = rs.usizeDiv(items4.items.length, 2); - const batch4: Batch.Batch, rs.u8 | undefined> = { drain: Drained.new$array1_Option_u8(_186, rs.sliceRange(rs.sliceFrom(items4.items), part4)), taken: 0 }; + const batch4: Batch.Batch, rs.u8 | undefined> = new Batch.Batch(Drained.new$array1_Option_u8(_186, rs.sliceRange(rs.sliceFrom(items4.items), part4)), 0); const half4: rs.u8 | undefined | rs.None = Batch.take$array1_Option_u8(batch4); const _211: rs.usize = count4; const arg12 = drain4.last; @@ -517,21 +520,21 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const arg15 = batch4.taken; const args22 = [_214, _215, _216, _217, _218, _219, (f: rs.Formatter) => rs.u8Display(arg15, f)]; put(out, rs.fmtFormat([args22[0], " ", args22[1], " ", args22[2], " ", args22[3], " ", args22[4], " ", args22[5], " ", args22[6]]).v); - const items5: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b11: rs.u8): boolean => { - return (b11 & 1) !== 0; - }), undefined, 1, true); - const drain5: Drained.Drained = Drained.new$generics_Flags(((b12: rs.u8): boolean => { + const items5: rs.Vec = rs.iterToVec(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b12: rs.u8): boolean => { return (b12 & 1) !== 0; + }), undefined, 1, true); + const drain5: Drained.Drained = Drained.new$generics_Flags(((b13: rs.u8): boolean => { + return (b13 & 1) !== 0; })(90), rs.sliceFrom(items5.items)); let count5: rs.usize = 0; while (Drained.next$generics_Flags(drain5) !== undefined) { count5 = rs.usizeAdd(count5, 1); } - const _241: boolean = ((b13: rs.u8): boolean => { - return (b13 & 1) !== 0; + const _241: boolean = ((b14: rs.u8): boolean => { + return (b14 & 1) !== 0; })(1); const part5 = rs.usizeDiv(items5.items.length, 2); - const batch5: Batch.Batch = { drain: Drained.new$generics_Flags(_241, rs.sliceRange(rs.sliceFrom(items5.items), part5)), taken: 0 }; + const batch5: Batch.Batch = new Batch.Batch(Drained.new$generics_Flags(_241, rs.sliceRange(rs.sliceFrom(items5.items), part5)), 0); const half5: boolean | undefined = Batch.take$generics_Flags(batch5); const _266: rs.usize = count5; const arg16 = drain5.last; @@ -546,9 +549,9 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const arg19 = batch5.taken; const args23 = [_269, _270, _271, _272, _273, _274, (f: rs.Formatter) => rs.u8Display(arg19, f)]; put(out, rs.fmtFormat([args23[0], " ", args23[1], " ", args23[2], " ", args23[3], " ", args23[4], " ", args23[5], " ", args23[6]]).v); - const ahead: Ahead.Ahead, rs.u8, boolean> = { iter: rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), seen: new rs.Vec(), stored: option_map$132wgz4(rs.byteSliceFirst(data), (b14: rs.u8): boolean => { - return (b14 & 2) !== 0; - }) as rs.OptionOf }; + const ahead: Ahead.Ahead, rs.u8, boolean> = new Ahead.Ahead(rs.iterPeekable(rs.iterSlice(rs.byteSliceView(data))), new rs.Vec(), option_map$132wgz4(rs.byteSliceFirst(data), (b15: rs.u8): boolean => { + return (b15 & 2) !== 0; + }) as rs.OptionOf); const repeats: rs.usize = Ahead.repeats$generics_Flags_slice_Iter_u8(ahead); const part6 = ahead.seen as rs.Vec; const _297: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(repeats, f); @@ -556,11 +559,11 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const arg20 = ahead.stored; const args24 = [_297, _298, (f: rs.Formatter) => rs.fmtOption(arg20, f, rs.fmtBool)]; put(out, rs.fmtFormat([args24[0], " ", args24[1], " ", args24[2]]).v); - const ahead2: Ahead.Ahead, rs.u8, rs.u16> = { iter: rs.iterPeekable(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b15: rs.u8): rs.u8 => { - return rs.u8Rem(b15, 4); - })), seen: new rs.ByteVec(), stored: option_map$0ydco3f(rs.byteSliceLast(data), (_212: rs.u8): rs.u16 => { + const ahead2: Ahead.Ahead, rs.u8, rs.u16> = new Ahead.Ahead(rs.iterPeekable(rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b16: rs.u8): rs.u8 => { + return rs.u8Rem(b16, 4); + })), new rs.ByteVec(), option_map$0ydco3f(rs.byteSliceLast(data), (_212: rs.u8): rs.u16 => { return _212 as rs.u16; - }) as rs.OptionOf }; + }) as rs.OptionOf); const repeats2: rs.usize = Ahead.repeats$array1_u16_iter_Map_slice_Iter_u8_generics_projections_closure1(ahead2); const part7 = ahead2.seen as rs.ByteVec; const _322: rs.Fmt = (f: rs.Formatter) => rs.usizeDisplay(repeats2, f); @@ -592,6 +595,42 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const arg25 = units.len; const args27 = [_383, _384, _385, (f: rs.Formatter) => rs.usizeDisplay(arg25, f)]; put(out, rs.fmtFormat([args27[0], " ", args27[1], " ", args27[2], " ", args27[3]]).v); + const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); + const tick: (a0: rs.u8) => Ticked.Ticked = (id2: rs.u8): Ticked.Ticked => { + return new Ticked.Ticked(id2, rs.arcClone(log)); + }; + for (const b2 of rs.byteSliceView(data)) { + new Kept.Kept(tick(b2), {}, rs.vecFrom([tick(b2 ^ 1), tick(b2 ^ 2)]), then$1qflv5x(rs.u8Rem(b2, 2) === 0, (): Ticked.Ticked => { + return tick(b2 ^ 3); + }) as rs.OptionOf)[Symbol.dispose](); + let ticks: Pile.Pile = { tag: "Empty" }; + const _425: rs.u8 = rs.u8Rem(b2, 3); + let iterStart: rs.u8 = 0; + const iterEnd: rs.u8 = _425; + while ((_428 = iterStart++) < iterEnd) { + ticks = { tag: "Linked", item: tick(_428), next: ticks } as Pile.Pile; + } + const _441: rs.RefCell = rs.refCellBorrowMut(log.value); + rs.byteVecPush(_441.value, 254); + rs.refMutRelease(_441); + drop$generics_Pile_generics_Ticks(ticks); + const _447: Uint8Array = Uint8Array.of(b2); + const _448: rs.ByteVec = rs.byteVecFrom(Uint8Array.of(b2)); + const kept2: Kept.Kept = new Kept.Kept(b2, _447, _448, undefined as rs.OptionOf); + const _454: rs.RefCell = rs.refCellBorrowMut(log.value); + const _458: rs.u8 = kept2.item; + const _459: rs.u8 = kept2.store[0]; + const slice6 = kept2.rest as rs.ByteVec; + rs.iterExtendBytes(_454.value, rs.iterSlice(Uint8Array.of(_458, _459, slice6.data[rs.idx(slice6, 0)])), undefined, true); + rs.refMutRelease(_454); + } + const _471: rs.RefCell = rs.refCellBorrowMut(log.value); + rs.byteVecPush(_471.value, data.length & 0xff); + rs.refMutRelease(_471); + const _480: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_480.value))), undefined, true); + rs.refRelease(_480); + log[Symbol.dispose](); } //# sourceMappingURL=generics.ts.map diff --git a/ts/fixtures/lang/shapes/expected/lib.ts b/ts/fixtures/lang/shapes/expected/lib.ts index 170fe7066..4a7d2bdf6 100644 --- a/ts/fixtures/lang/shapes/expected/lib.ts +++ b/ts/fixtures/lang/shapes/expected/lib.ts @@ -5,8 +5,10 @@ import { ints, strings, derived, written, counts, rounding, panics, wrapped, col import { methods, runs, units, projections } from "./generics.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; +import type * as Pile from "./generics.Pile.ts"; import type * as Run from "./generics.Run.ts"; import type * as Ticked from "./generics.Ticked.ts"; +import type * as Ticks from "./generics.Ticks.ts"; import * as Wrap from "./generics.Wrap.ts"; import { copies, order, discriminants, loops, patterns, uninhabited, closures, library, flows } from "./shapes.ts"; import type * as shapes_Gauge from "./shapes.Gauge.ts"; @@ -137,6 +139,17 @@ export function copy$tuple2_u8_u8(v: [rs.u8, rs.u8]): [rs.u8, rs.u8] { return [v[0], v[1]]; } +export function drop$generics_Pile_generics_Ticks(_1: Pile.Pile): void { + if (_1.tag !== "Empty") { + if (_1.tag === "Full") { + _1._0[Symbol.dispose](); + } else { + _1.item[Symbol.dispose](); + drop$generics_Pile_generics_Ticks(_1.next); + } + } +} + export function drop$generics_Run_generics_Ticked(_1: Run.Run): void { if (_1.tag === "Listed") { rs.dropVec(_1._0 as rs.Vec); diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 596b3abbb..a73072741 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -604,6 +604,37 @@ where } } +/// A store of [`Ticked`]s, which drops nothing itself. +struct Ticks; + +impl Store for Ticks { + type Item = Ticked; + const LEN: usize = 1; +} + +/// Items that drop beside the store that names them, which drops nothing, so only the +/// items' type decides whether and how each instance drops. +struct Kept { + item: S::Item, + store: S, + rest: Vec, + maybe: Option, +} + +/// A store of what translation types no other way, which only a `PhantomData` names. +struct Paths; + +impl Store for Paths { + type Item = std::path::PathBuf; + const LEN: usize = 0; +} + +/// A projection that only a `PhantomData` holds, which types nothing. +struct Marked { + marker: std::marker::PhantomData, + count: u8, +} + /// [`Drained`] and [`Batch`] of `$store`, of the items `$item` makes of each byte. macro_rules! drained_at { ($data:expr, $out:expr, $store:ty, $item:expr) => {{ @@ -674,4 +705,38 @@ pub fn projections(data: &[u8], out: &mut Vec) { out, &format!("{:?} {} {:?} {}", deep.inner, deep.len, units.inner, units.len), ); + + let log = Rc::new(RefCell::new(Vec::new())); + let tick = |id: u8| Ticked { id, log: log.clone() }; + for &b in data { + let kept = Kept:: { + item: tick(b), + store: Ticks, + rest: vec![tick(b ^ 1), tick(b ^ 2)], + maybe: (b % 2 == 0).then(|| tick(b ^ 3)), + }; + drop(kept); + let mut ticks = Pile::::Empty; + for id in 0..b % 3 { + ticks = Pile::Linked { + item: tick(id), + next: Box::new(ticks), + }; + } + log.borrow_mut().push(0xfe); + drop(ticks); + let kept = Kept::<[u8; 1]> { + item: b, + store: [b], + rest: vec![b], + maybe: None, + }; + log.borrow_mut().extend([kept.item, kept.store[0], kept.rest[0]]); + } + let marked = Marked:: { + marker: std::marker::PhantomData, + count: data.len() as u8, + }; + log.borrow_mut().push(marked.count); + out.extend(log.borrow().iter()); } diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index 499ddb0b0..b0fe0089f 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -124,8 +124,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::Closure(..) | ty::CoroutineClosure(..) => { super::captured(ty).is_some_and(|tys| tys.iter().any(|t| self.drops(t))) } - // Whatever it was made of may drop. + // Whatever it was made of may drop, as may what a projection from a type + // parameter is at an instance. ty::Coroutine(..) | ty::Param(_) | ty::Dynamic(..) => true, + ty::Alias(..) => ty.has_param(), _ => false, }; self.drops_open.remove(&ty); @@ -200,7 +202,14 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { None | Some(Std::Peekable) if self.std_iter(ty) => self.closures_held(ty), Some(_) => false, None if self.shared_glue(*adt) => { - if !args.types().all(|t| self.disposable(t, glue)) { + // What its glue drops through its type parameters, and the projections + // from them its fields name. + let env = self.typing_env_pub(); + let projections = self.projections(adt.did()).into_iter().map(|(p, _)| p); + let held: Vec> = (args.types()) + .chain(projections.map(|p| super::copy::instantiate(self.tcx, env, p, args))) + .collect(); + if !held.into_iter().all(|t| self.disposable(t, glue)) { return false; } if let Some(span) = glue { @@ -213,7 +222,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }, // A closure's `[Symbol.dispose]` drops what it owns, a `dyn Future`'s what it is, and // another trait object's what it wraps. - ty::Closure(..) | ty::CoroutineClosure(..) | ty::Coroutine(..) | ty::Param(_) | ty::Dynamic(..) => true, + ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::Param(_) + | ty::Alias(..) + | ty::Dynamic(..) => true, _ => false, } } @@ -673,8 +687,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // Only an instance whose parameters drop runs the glue, so none is a byte: a field // whose definition's type an instance decides is read as an array of them holds it. let glue = std::mem::replace(&mut self.glue, true); + // Its fields' types over its parameters, and the projections from them it names. + let identity = ty::GenericArgs::identity_for_item(tcx, adt.did()); + let env = ty::TypingEnv::post_analysis(tcx, adt.did()); + let defining = self.defining.replace((adt.did(), env)); for (field, &id) in adt.non_enum_variant().fields.iter().zip(fields) { - let ty = tcx.type_of(field.did).instantiate_identity().skip_norm_wip(); + let ty = super::copy::field_ty(tcx, env, field.did, identity); let read = match self.instanced_fields.contains_key(&field.did) { true => Expr::As(Box::new(Translator::read_field(Expr::This, id)), self.ty(ty, span)), false => Translator::read_field(Expr::This, id), @@ -684,6 +702,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } } self.glue = glue; + self.defining = defining; let sym = self.program.sym(Symbol { module: self.module_of(adt.did()), base: "[Symbol.dispose]".into(), diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index b29542289..d99dfc6f8 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1938,7 +1938,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// types name, such as smallvec's `::Item`, each a TypeScript type parameter /// of its own, by its name: the associated type's, numbered past any it would shadow. /// They include those of the crate's generic types its fields hold, at its arguments, and - /// the item type of an iterator from another crate, which `rs.Iter` types. + /// the item type of an iterator from another crate, which `rs.Iter` types, but none that + /// only what types nothing holds, such as a `PhantomData`. fn projections(&mut self, adt: DefId) -> Vec<(Ty<'tcx>, String)> { if let Some(named) = self.projections.get(&adt) { return named.clone(); @@ -2001,10 +2002,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.find_projections(copy::instantiate(tcx, env, projection, args), env, found); } } - ty::Adt(adt, _) if !self.crates.translates(adt.did()) => { - if let Some(item) = self.iter_item(t) { - self.find_projections(item, env, found); - } + // `undefined`, whatever it holds. + ty::Adt(..) if library::nothing(tcx, t) => walker.skip_current_subtree(), + // An `rs.Iter` of its items, whatever it iterates. + ty::Adt(adt, _) + if !self.crates.translates(adt.did()) + && matches!(library::Std::of(tcx, adt.did()), None | Some(library::Std::Peekable)) + && let Some(item) = self.iter_item(t) => + { + self.find_projections(item, env, found); + walker.skip_current_subtree(); } _ => {} } From 22e7e428185cb092391f2f84c25881deade24981 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 04:40:11 -0800 Subject: [PATCH 04/21] fix(ts): an instance's projections decide whether its glue is inert What a generic struct's glue drops through is its type arguments and what each projection its fields name is at them, for whether the instance's values need disposing as for whether its glue can dispose of them. An Option or Poll over a projection its definition types nothing of, as a PhantomData's, is not checked for its erasure. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/expected/core/bool.ts | 7 +++++ .../lang/shapes/expected/generics.Lent.ts | 8 +++++ .../lang/shapes/expected/generics.Marked.ts | 3 +- .../lang/shapes/expected/generics.Paths.ts | 7 +++++ ts/fixtures/lang/shapes/expected/generics.ts | 31 ++++++++++++++----- ts/fixtures/lang/shapes/src/generics.rs | 31 +++++++++++++++++-- ts/src/lower/drop.rs | 21 +++++++------ ts/src/lower/mod.rs | 19 +++++++++++- 8 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Lent.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Paths.ts diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index 4ce5d18e7..25a236022 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -33,6 +33,13 @@ export function thenSome$05ku0zn(self: boolean, t: rs.i32): rs.i32 | undefined { return t; } +export function thenSome$0tbnivw(self: boolean, t: Ticked.Ticked): Ticked.Ticked | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$19pue01(self: boolean, t: rs.Poll): rs.Poll | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Lent.ts b/ts/fixtures/lang/shapes/expected/generics.Lent.ts new file mode 100644 index 000000000..2fae72189 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Lent.ts @@ -0,0 +1,8 @@ +// Generated by rust2-ts. Do not edit. + +/** A store that lends [`Ticked`]s, whose items drop nothing. */ +export interface Lent { + _0: undefined; +} + +//# sourceMappingURL=generics.Lent.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Marked.ts b/ts/fixtures/lang/shapes/expected/generics.Marked.ts index 2dbd2b8c9..46837a308 100644 --- a/ts/fixtures/lang/shapes/expected/generics.Marked.ts +++ b/ts/fixtures/lang/shapes/expected/generics.Marked.ts @@ -2,9 +2,10 @@ import * as rs from "./rs.ts"; -/** A projection that only a `PhantomData` holds, which types nothing. */ +/** A projection that only a `PhantomData` holds, which types nothing, in an `Option` too. */ export interface Marked<_S> { marker: undefined; + maybe: undefined; count: rs.u8; } diff --git a/ts/fixtures/lang/shapes/expected/generics.Paths.ts b/ts/fixtures/lang/shapes/expected/generics.Paths.ts new file mode 100644 index 000000000..0b0a8c306 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Paths.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A store of what translation types no other way, which only a `PhantomData` names. */ +export interface Paths { +} + +//# sourceMappingURL=generics.Paths.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 63b269ebe..7d05779b6 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -2,7 +2,7 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; -import { thenSome$1ctlqen, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; +import { thenSome$1ctlqen, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; @@ -15,7 +15,10 @@ import * as Fixed from "./generics.Fixed.ts"; import type * as Flags from "./generics.Flags.ts"; import * as Kept from "./generics.Kept.ts"; import type * as Lends from "./generics.Lends.ts"; +import type * as Lent from "./generics.Lent.ts"; +import type * as Marked from "./generics.Marked.ts"; import * as Parked from "./generics.Parked.ts"; +import type * as Paths from "./generics.Paths.ts"; import * as Pile from "./generics.Pile.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; @@ -623,13 +626,27 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { const slice6 = kept2.rest as rs.ByteVec; rs.iterExtendBytes(_454.value, rs.iterSlice(Uint8Array.of(_458, _459, slice6.data[rs.idx(slice6, 0)])), undefined, true); rs.refMutRelease(_454); + const owner: Ticked.Ticked = tick(b2 ^ 4); + const _472: rs.Vec = rs.vecFrom([owner]); + const _475: Ticked.Ticked | undefined = thenSome$0tbnivw(rs.u8Rem(b2, 2) === 1, owner); + const lent: rs.Arc> = new rs.Arc(new Kept.Kept(owner, { _0: undefined }, _472, _475 as rs.OptionOf)); + const _483: rs.RefCell = rs.refCellBorrowMut(log.value); + rs.byteVecPush(_483.value, lent.value.item.id); + rs.refMutRelease(_483); + rs.arcDrop(lent); + const _493: rs.RefCell = rs.refCellBorrowMut(log.value); + rs.byteVecPush(_493.value, 253); + rs.refMutRelease(_493); + owner[Symbol.dispose](); } - const _471: rs.RefCell = rs.refCellBorrowMut(log.value); - rs.byteVecPush(_471.value, data.length & 0xff); - rs.refMutRelease(_471); - const _480: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_480.value))), undefined, true); - rs.refRelease(_480); + const marked: Marked.Marked = { marker: undefined, maybe: undefined, count: data.length & 0xff }; + const bytes2: Marked.Marked = { marker: undefined, maybe: undefined, count: ~marked.count & 0xff }; + const _506: rs.RefCell = rs.refCellBorrowMut(log.value); + rs.iterExtendBytes(_506.value, rs.iterSlice(Uint8Array.of(marked.count, bytes2.count)), undefined, true); + rs.refMutRelease(_506); + const _517: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_517.value))), undefined, true); + rs.refRelease(_517); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index a73072741..f7a0eb38c 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -621,6 +621,14 @@ struct Kept { maybe: Option, } +/// A store that lends [`Ticked`]s, whose items drop nothing. +struct Lent<'a>(std::marker::PhantomData<&'a ()>); + +impl<'a> Store for Lent<'a> { + type Item = &'a Ticked; + const LEN: usize = 1; +} + /// A store of what translation types no other way, which only a `PhantomData` names. struct Paths; @@ -629,9 +637,10 @@ impl Store for Paths { const LEN: usize = 0; } -/// A projection that only a `PhantomData` holds, which types nothing. +/// A projection that only a `PhantomData` holds, which types nothing, in an `Option` too. struct Marked { marker: std::marker::PhantomData, + maybe: std::marker::PhantomData>, count: u8, } @@ -732,11 +741,29 @@ pub fn projections(data: &[u8], out: &mut Vec) { maybe: None, }; log.borrow_mut().extend([kept.item, kept.store[0], kept.rest[0]]); + // What lends its items drops none of them, behind an `Rc` too. + let owner = tick(b ^ 4); + let lent = Rc::new(Kept::> { + item: &owner, + store: Lent(std::marker::PhantomData), + rest: vec![&owner], + maybe: (b % 2 == 1).then_some(&owner), + }); + log.borrow_mut().push(lent.item.id); + drop(lent); + log.borrow_mut().push(0xfd); + drop(owner); } let marked = Marked:: { marker: std::marker::PhantomData, + maybe: std::marker::PhantomData, count: data.len() as u8, }; - log.borrow_mut().push(marked.count); + let bytes = Marked::<[u8; 2]> { + marker: std::marker::PhantomData, + maybe: std::marker::PhantomData, + count: !marked.count, + }; + log.borrow_mut().extend([marked.count, bytes.count]); out.extend(log.borrow().iter()); } diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index b0fe0089f..e1e830aa0 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -202,14 +202,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { None | Some(Std::Peekable) if self.std_iter(ty) => self.closures_held(ty), Some(_) => false, None if self.shared_glue(*adt) => { - // What its glue drops through its type parameters, and the projections - // from them its fields name. - let env = self.typing_env_pub(); - let projections = self.projections(adt.did()).into_iter().map(|(p, _)| p); - let held: Vec> = (args.types()) - .chain(projections.map(|p| super::copy::instantiate(self.tcx, env, p, args))) - .collect(); - if !held.into_iter().all(|t| self.disposable(t, glue)) { + if !self.glue_held(*adt, args).into_iter().all(|t| self.disposable(t, glue)) { return false; } if let Some(span) = glue { @@ -232,6 +225,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } } + /// What a generic struct's glue drops through, at an instance with `args`: each type + /// argument, and what each projection from them its fields name is there. + fn glue_held(&mut self, adt: AdtDef<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Vec> { + let env = self.typing_env_pub(); + let projections = self.projections(adt.did()).into_iter().map(|(p, _)| p); + (args.types()) + .chain(projections.map(|p| super::copy::instantiate(self.tcx, env, p, args))) + .collect() + } + /// Whether every JS value of `ty`, a type that drops and is [`Self::disposable`], is an /// object with `[Symbol.dispose]`, which a drop calls directly rather than through /// `rs.drop`'s one megamorphic site. An `Option` may be `None`, and a type parameter, a @@ -404,7 +407,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Context) => false, Some(_) => true, // Its glue, if an instance that drops gave it one, drops its type parameters' fields. - None if adt.is_struct() => args.types().all(|t| self.inert(t)), + None if adt.is_struct() => self.glue_held(*adt, args).into_iter().all(|t| self.inert(t)), None => true, }, _ => true, diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index d99dfc6f8..a6b1cd132 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -2087,6 +2087,23 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let identity = ty::GenericArgs::identity_for_item(tcx, adt.did()); let defining = (adt.did(), TypingEnv::post_analysis(tcx, adt.did())); let env = self.defining.map_or(self.typing_env(), |(_, env)| env); + let projections: Vec> = self.projections(adt.did()).into_iter().map(|(p, _)| p).collect(); + // Whether `t` names a projection its definition types nothing of, as a `PhantomData`'s. + let untyped = |t: Ty<'tcx>| { + let mut walker = t.walk(); + while let Some(arg) = walker.next() { + let Some(t) = arg.as_type() else { continue }; + if let ty::Alias(..) = t.kind() + && t.has_param() + { + if !projections.contains(&t) { + return true; + } + walker.skip_current_subtree(); + } + } + false + }; for field in adt.all_fields() { let generic = copy::field_ty(tcx, defining.1, field.did, identity); for part in generic.walk().filter_map(|arg| arg.as_type()) { @@ -2100,7 +2117,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }; let inner = wrapper_args.type_at(0); let erased = self.erases_to(inner); - if !erased.param { + if !erased.param || untyped(inner) { continue; } let held = copy::instantiate(tcx, env, part, args); From 5a5ba712b6a4459cde762a52d50f9416a1dce3aa Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 07:05:30 -0800 Subject: [PATCH 05/21] refactor(ts): typing a definition is the one record of its projections and Options A generic type's projection parameters and the Option/Poll parts its instances are checked against are recorded by ty() while its fields are typed, instead of found by separate walks over the Rust types, so what types nothing (a PhantomData) is never checked or made a parameter. Types typed with each other complete together: an instance of a definition still being typed gets its projection arguments filled in once every definition in the cycle is, and its drop glue and checks run then. A projection whose value at an instance holds that instance again is refused with its span instead of overflowing the translator's stack. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 7 +- .../lang/shapes/expected/generics.Branch.ts | 19 + .../lang/shapes/expected/generics.Even.ts | 27 ++ .../lang/shapes/expected/generics.Ghost.ts | 17 + .../lang/shapes/expected/generics.Odd.ts | 16 + .../lang/shapes/expected/generics.Swap.ts | 36 ++ .../lang/shapes/expected/generics.Tree.ts | 24 ++ ts/fixtures/lang/shapes/expected/generics.ts | 59 +++ ts/fixtures/lang/shapes/expected/lib.ts | 4 +- ts/fixtures/lang/shapes/src/generics.rs | 137 ++++++ ts/fixtures/lang/shapes/src/lib.rs | 3 + ts/fixtures/reject/census.txt | 11 + ts/fixtures/reject/expected.txt | 11 + ts/fixtures/reject/src/values.rs | 81 ++++ ts/fixtures/reject/ts.toml | 3 + ts/src/lower/defined.rs | 396 ++++++++++++++++++ ts/src/lower/drop.rs | 16 +- ts/src/lower/mod.rs | 350 ++++++---------- ts/src/print.rs | 2 + ts/src/ts.rs | 52 ++- 20 files changed, 1036 insertions(+), 235 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Branch.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Even.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Ghost.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Odd.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Swap.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Tree.ts create mode 100644 ts/src/lower/defined.rs diff --git a/ts/README.md b/ts/README.md index 641faa015..c652cc667 100644 --- a/ts/README.md +++ b/ts/README.md @@ -475,7 +475,8 @@ but not their const ones, which type nothing in TS: arrayvec's `ArrayString<4>` through a generic type of the crate's they hold, or as a std iterator's item, is a type parameter of its own after them, named for its associated type and numbered past any it would shadow, which each instance gives what the projection is there: -`Drain<[u8; 4]>` is a `Drain`. So a function of a generic type is one instance's, declaring the instance it +`Drain<[u8; 4]>` is a `Drain`. Types typed with each other, as a tree +and its branches are, each take the projections either names. So a function of a generic type is one instance's, declaring the instance it takes as `self`: `fmt$fmt_Debug_fmt_Pair(self: Wrap, f: rs.Formatter)`. A reference or pointer in a generic type to what only an instance decides the form of, such as `*mut T` or @@ -1132,7 +1133,9 @@ function without a mapping of its own that makes or reads an `Option` whose `Non `rs.None`, an `Option` of an `Option` of `()` or another `Option`, an instance of a generic type whose field is an `Option` of a type parameter, or of a projection from one, that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` -can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, a `String`'s `capacity`, looking up a key by +can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, an instance of a generic type +whose projection is a type holding that instance again (`Held` where `Src::Item` is a `Held`), a generic type that +holds itself at other arguments than its own parameters whose projections there are new ones each time, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS copies that std makes, other than a map's, or hands a closure, as `iter_mut` and `and_modify` do, one into a `Result`'s payload passed on or kept, the adaptors diff --git a/ts/fixtures/lang/shapes/expected/generics.Branch.ts b/ts/fixtures/lang/shapes/expected/generics.Branch.ts new file mode 100644 index 000000000..862b4260f --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Branch.ts @@ -0,0 +1,19 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Tree from "./generics.Tree.ts"; + +/** + * One branch of a [`Tree`], whose projection only the tree's other type names, typed with + * it, so each learns its projections from the other. + */ +export class Branch { + item: Item; + below: Tree.Tree | undefined; + + constructor(item: Item, below: Tree.Tree | undefined) { + this.item = item; + this.below = below; + } +} + +//# sourceMappingURL=generics.Branch.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Even.ts b/ts/fixtures/lang/shapes/expected/generics.Even.ts new file mode 100644 index 000000000..e7d496855 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Even.ts @@ -0,0 +1,27 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Flags from "./generics.Flags.ts"; +import type * as Odd from "./generics.Odd.ts"; + +/** + * Two types typed with each other, each naming a projection the other doesn't, so each + * takes both, in the order it named them. + */ +export type Even = + | { tag: "End" } + | { tag: "Next"; _0: Item; _1: Odd.Odd }; + +export function walk$generics_Flags_vec_IntoIter_u8(self: Even, boolean, rs.u8>, out: rs.String): void { + if (self.tag === "Next") { + const _22: Odd.Odd, rs.u8, boolean> = self._1; + const arg = self._0; + const _16: rs.Fmt = (f: rs.Formatter) => rs.fmtBool(arg, f); + const arg2 = _22.seen; + const args3 = [_16, (f: rs.Formatter) => rs.fmtOption(arg2, f, rs.u8Debug)]; + rs.stringPushStr(out, rs.fmtFormat([args3[0], " ", args3[1], " "]).v); + walk$generics_Flags_vec_IntoIter_u8(self._1.next, out); + } +} + +//# sourceMappingURL=generics.Even.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Ghost.ts b/ts/fixtures/lang/shapes/expected/generics.Ghost.ts new file mode 100644 index 000000000..bb30ef2bf --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Ghost.ts @@ -0,0 +1,17 @@ +// Generated by rust2-ts. Do not edit. + +/** + * A projection beside an `Option` of it only a `PhantomData` holds, which types nothing, so + * no instance is refused for what that `Option` would erase to. + */ +export class Ghost<_S, Item> { + item: Item; + marker: undefined; + + constructor(item: Item, marker: undefined) { + this.item = item; + this.marker = marker; + } +} + +//# sourceMappingURL=generics.Ghost.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Odd.ts b/ts/fixtures/lang/shapes/expected/generics.Odd.ts new file mode 100644 index 000000000..6c5535720 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Odd.ts @@ -0,0 +1,16 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Even from "./generics.Even.ts"; + +export class Odd { + seen: rs.OptionOf; + next: Even.Even; + + constructor(seen: rs.OptionOf, next: Even.Even) { + this.seen = seen; + this.next = next; + } +} + +//# sourceMappingURL=generics.Odd.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Swap.ts b/ts/fixtures/lang/shapes/expected/generics.Swap.ts new file mode 100644 index 000000000..31331d188 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Swap.ts @@ -0,0 +1,36 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Flags from "./generics.Flags.ts"; + +/** + * A type that holds itself with its parameters swapped, so its projections at the inner + * instance are its own in the other order. + */ +export class Swap { + item: Item; + next: Swap | undefined; + + constructor(item: Item, next: Swap | undefined) { + this.item = item; + this.next = next; + } +} + +export function show$array1_u16_generics_Flags(self: Swap, out: rs.String): void { + const arg = self.item; + rs.stringPushStr(out, rs.fmtFormat([(f: rs.Formatter) => rs.u16Debug(arg, f), " "]).v); + if (self.next !== undefined) { + show$generics_Flags_array1_u16(self.next, out); + } +} + +export function show$generics_Flags_array1_u16(self: Swap, out: rs.String): void { + const arg = self.item; + rs.stringPushStr(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtBool(arg, f), " "]).v); + if (self.next !== undefined) { + show$array1_u16_generics_Flags(self.next, out); + } +} + +//# sourceMappingURL=generics.Swap.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Tree.ts b/ts/fixtures/lang/shapes/expected/generics.Tree.ts new file mode 100644 index 000000000..b67499ddb --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Tree.ts @@ -0,0 +1,24 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Branch from "./generics.Branch.ts"; + +/** Branches that each may hold a tree again. */ +export class Tree { + branches: rs.Vec>; + + constructor(branches: rs.Vec>) { + this.branches = branches; + } +} + +export function items$array1_u8(self: Tree, out: rs.ByteVec): void { + for (const _6 of self.branches.items) { + rs.byteVecPush(out, _6.item); + if (_6.below !== undefined) { + items$array1_u8(_6.below, out); + } + } +} + +//# sourceMappingURL=generics.Tree.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 7d05779b6..11cb447e2 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -8,23 +8,29 @@ import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, is import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; import * as Batch from "./generics.Batch.ts"; +import * as Branch from "./generics.Branch.ts"; import * as Deep from "./generics.Deep.ts"; import * as Drained from "./generics.Drained.ts"; import * as Either from "./generics.Either.ts"; +import * as Even from "./generics.Even.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Flags from "./generics.Flags.ts"; +import * as Ghost from "./generics.Ghost.ts"; import * as Kept from "./generics.Kept.ts"; import type * as Lends from "./generics.Lends.ts"; import type * as Lent from "./generics.Lent.ts"; import type * as Marked from "./generics.Marked.ts"; +import * as Odd from "./generics.Odd.ts"; import * as Parked from "./generics.Parked.ts"; import type * as Paths from "./generics.Paths.ts"; import * as Pile from "./generics.Pile.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; +import * as Swap from "./generics.Swap.ts"; import * as Tagged from "./generics.Tagged.ts"; import * as Ticked from "./generics.Ticked.ts"; import type * as Ticks from "./generics.Ticks.ts"; +import * as Tree from "./generics.Tree.ts"; import * as Wrap from "./generics.Wrap.ts"; import { put } from "./harness.ts"; import * as shapes_Gauge from "./shapes.Gauge.ts"; @@ -650,4 +656,57 @@ export function projections(data: rs.ByteSlice, out: rs.ByteVec): void { log[Symbol.dispose](); } +/** Types typed with themselves, or with each other, that name projections. */ +export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { + const tree: Tree.Tree = rs.iterFold(rs.byteSliceChunks(data, 2), new Tree.Tree(new rs.Vec()), (below: Tree.Tree, chunk: rs.ByteSlice): Tree.Tree => { + return new Tree.Tree(rs.iterToVec(rs.iterChain(rs.iterMap(rs.iterSlice(rs.byteSliceView(chunk)), (_2: rs.u8): Branch.Branch => { + return new Branch.Branch(_2, undefined); + }), rs.iterSlice([new Branch.Branch(255, below)])), undefined, 8, true)); + }); + const items: rs.ByteVec = new rs.ByteVec(); + Tree.items$array1_u8(tree, items); + put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(items), f, rs.u8Debug)]).v); + const seen: rs.Iter = rs.iterMap(rs.iterSlice(rs.byteSliceView(data)), (b3: rs.u8): rs.u8 => { + return rs.u8Rem(b3, 5); + }); + let even: Even.Even, boolean, rs.u8> = { tag: "End" }; + const iter: rs.Iter = rs.iterRev(rs.iterSlice(rs.byteSliceView(data))); + while (true) { + const _30: rs.u8 | rs.Done = iter.next(); + if (_30 === rs.DONE) { + break; + } + even = { tag: "Next", _0: (_30 & 1) !== 0, _1: new Odd.Odd(rs.iterToOption(seen.next()) as rs.OptionOf, even) } as Even.Even, boolean, rs.u8>; + } + const walked: rs.String = rs.stringNew(); + Even.walk$generics_Flags_vec_IntoIter_u8(even, walked); + put(out, walked.v); + let swap: Swap.Swap | undefined = undefined; + const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterRev(rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data)))); + while (true) { + const _55: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_55 === rs.DONE) { + break; + } + const i: rs.usize = _55[0]; + const b2: rs.u8 = _55[1]; + const _61: boolean = b2 > 64; + const _63: rs.Place | undefined> = rs.place(() => swap, (value) => swap = value); + const taken = _63.v; + _63.v = undefined; + const inner: Swap.Swap = new Swap.Swap(_61, taken); + if (rs.usizeRem(i, 2) === 0) { + swap = new Swap.Swap(((b2 as rs.u16) << 4) & 0xffff, inner); + } + } + const shown: rs.String = rs.stringNew(); + const _78: Swap.Swap | undefined = swap; + if (_78 !== undefined) { + Swap.show$array1_u16_generics_Flags(_78, shown); + } + put(out, shown.v); + const arg = new Ghost.Ghost(option_map$057a36e(rs.byteSliceFirst(data), (_23: rs.u8): undefined => {}), undefined).item; + put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtOption(arg, f, rs.fmtUnit, rs.None)]).v); +} + //# sourceMappingURL=generics.ts.map diff --git a/ts/fixtures/lang/shapes/expected/lib.ts b/ts/fixtures/lang/shapes/expected/lib.ts index 4a7d2bdf6..1ffea0768 100644 --- a/ts/fixtures/lang/shapes/expected/lib.ts +++ b/ts/fixtures/lang/shapes/expected/lib.ts @@ -2,7 +2,7 @@ import * as rs from "./rs.ts"; import { ints, strings, derived, written, counts, rounding, panics, wrapped, collections, enums, iters, adaptors, writes, objects, sources, sets, calls, cells, searches } from "./fmt.ts"; -import { methods, runs, units, projections } from "./generics.ts"; +import { methods, runs, units, projections, projectedCycles } from "./generics.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Pile from "./generics.Pile.ts"; @@ -235,6 +235,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By units(data28, out29); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::projections", (_239: rs.u8, data29: rs.ByteSlice, out30: rs.ByteVec): undefined => { projections(data29, out30); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::projected_cycles", (_240: rs.u8, data30: rs.ByteSlice, out31: rs.ByteVec): undefined => { + projectedCycles(data30, out31); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index f7a0eb38c..f6b750bf9 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -767,3 +767,140 @@ pub fn projections(data: &[u8], out: &mut Vec) { log.borrow_mut().extend([marked.count, bytes.count]); out.extend(log.borrow().iter()); } + +/// One branch of a [`Tree`], whose projection only the tree's other type names, typed with +/// it, so each learns its projections from the other. +struct Branch { + item: S::Item, + below: Option>>, +} + +/// Branches that each may hold a tree again. +struct Tree { + branches: Vec>, +} + +impl Tree +where + S::Item: Copy, +{ + fn items(&self, out: &mut Vec) { + for branch in &self.branches { + out.push(branch.item); + if let Some(below) = &branch.below { + below.items(out); + } + } + } +} + +/// Two types typed with each other, each naming a projection the other doesn't, so each +/// takes both, in the order it named them. +enum Even { + End, + Next(S::Item, Box>), +} + +struct Odd { + seen: Option, + next: Even, +} + +impl Even +where + S::Item: Copy + std::fmt::Debug, + I::Item: Copy + std::fmt::Debug, +{ + fn walk(&self, out: &mut String) { + if let Even::Next(item, odd) = self { + out.push_str(&format!("{item:?} {:?} ", odd.seen)); + odd.next.walk(out); + } + } +} + +/// A type that holds itself with its parameters swapped, so its projections at the inner +/// instance are its own in the other order. +struct Swap { + item: A::Item, + next: Option>>, +} + +impl Swap +where + A::Item: std::fmt::Debug, + B::Item: std::fmt::Debug, +{ + fn show(&self, out: &mut String) { + out.push_str(&format!("{:?} ", self.item)); + if let Some(next) = &self.next { + next.show(out); + } + } +} + +/// A projection beside an `Option` of it only a `PhantomData` holds, which types nothing, so +/// no instance is refused for what that `Option` would erase to. +struct Ghost { + item: S::Item, + marker: std::marker::PhantomData>, +} + +/// Types typed with themselves, or with each other, that name projections. +pub fn projected_cycles(data: &[u8], out: &mut Vec) { + let tree = data + .chunks(2) + .fold(Tree::<[u8; 1]> { branches: Vec::new() }, |below, chunk| Tree { + branches: chunk + .iter() + .map(|&item| Branch { item, below: None }) + .chain([Branch { + item: 0xff, + below: Some(Box::new(below)), + }]) + .collect(), + }); + let mut items = Vec::new(); + tree.items(&mut items); + put(out, &format!("{items:?}")); + + let mut seen = data.iter().map(|b| b % 5); + let mut even = Even::>::End; + for &b in data.iter().rev() { + even = Even::Next( + b & 1 != 0, + Box::new(Odd { + seen: seen.next(), + next: even, + }), + ); + } + let mut walked = String::new(); + even.walk(&mut walked); + put(out, &walked); + + let mut swap = None::>>; + for (i, &b) in data.iter().enumerate().rev() { + let inner = Swap:: { + item: b > 0x40, + next: swap.take(), + }; + if i % 2 == 0 { + swap = Some(Box::new(Swap { + item: u16::from(b) << 4, + next: Some(Box::new(inner)), + })); + } + } + let mut shown = String::new(); + if let Some(swap) = &swap { + swap.show(&mut shown); + } + put(out, &shown); + + let ghost = Ghost::<[Option<()>; 2]> { + item: data.first().map(|_| ()), + marker: std::marker::PhantomData, + }; + put(out, &format!("{:?}", ghost.item)); +} diff --git a/ts/fixtures/lang/shapes/src/lib.rs b/ts/fixtures/lang/shapes/src/lib.rs index 250e52927..264263e42 100644 --- a/ts/fixtures/lang/shapes/src/lib.rs +++ b/ts/fixtures/lang/shapes/src/lib.rs @@ -57,5 +57,8 @@ fn cases() -> Vec<(&'static str, Case)> { ("fmt::searches", |_, data, out| fmt::searches(data, out)), ("generics::units", |_, data, out| generics::units(data, out)), ("generics::projections", |_, data, out| generics::projections(data, out)), + ("generics::projected_cycles", |_, data, out| { + generics::projected_cycles(data, out) + }), ] } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index adfefc4f3..af556466c 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -266,6 +266,7 @@ fixtures/reject/src/values.rs:Beside+0:1: union `values::Beside`, whose fields o fixtures/reject/src/values.rs:Bits+0:1: union `values::Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Mixed+0:1: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart +fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` @@ -287,6 +288,16 @@ fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:26: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:31: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index c39633b7e..c452b77cb 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -264,6 +264,7 @@ fixtures/reject/src/strs.rs:str_bytes_mut+1:13: unsupported call to `core::str:: 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 fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` +fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` @@ -287,6 +288,16 @@ fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:26: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:31: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 60e22f8c8..840770753 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -105,6 +105,87 @@ pub fn projected_nested(unit: bool) -> bool { unit } +/// What a source yields, named by an associated type. +pub trait Source { + type Item; +} + +/// What a source holds next. +pub struct Held { + pub next: Option>, +} + +/// A source whose items hold it again. +pub struct Looped; + +impl Source for Looped { + type Item = Held; +} + +/// `Held`'s projection argument is `Held` itself, which TypeScript can name +/// only through a type alias of its own. +pub fn projected_self(deep: bool) -> bool { + let held: Held = Held { + next: deep.then(|| Box::new(Held { next: None })), + }; + held.next.is_some() +} + +/// Two types a source names, each holding the other. +pub trait Duo { + type Ping; + type Pong; +} + +/// What holds a [`Duo`]'s pong. +pub struct Ping { + pub pong: Option>, +} + +/// What holds a [`Duo`]'s ping. +pub struct Pong { + pub ping: Option>, +} + +/// A duo of its own ping and pong. +pub struct Rally; + +impl Duo for Rally { + type Ping = Ping; + type Pong = Pong; +} + +/// `Ping`'s projection argument is `Pong`, whose own is `Ping` again. +pub fn projected_rally(deep: bool) -> bool { + let ping: Ping = Ping { + pong: deep.then(|| Box::new(Pong { ping: None })), + }; + ping.pong.is_some() +} + +/// A chain of types, each naming the next. +pub trait Chain { + type Item; + type Next: Chain; +} + +/// A type that holds itself at its parameter's next, whose projections at each are new. +pub struct Nested { + pub item: C::Item, + pub next: Option>>, +} + +impl Chain for u8 { + type Item = u8; + type Next = u8; +} + +/// `Nested` would need a projection argument for every link of the chain. +pub fn projected_chain(item: u8) -> u8 { + let nested: Nested = Nested { item, next: None }; + nested.item +} + /// Parsing an address is not translated yet. pub fn addr_parsed(s: &str) -> Result { s.parse() diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 795226199..1ded3f94a 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -207,7 +207,10 @@ roots = [ "values::manual_impls", "values::mixed_byte", "values::nested", + "values::projected_chain", "values::projected_nested", + "values::projected_rally", + "values::projected_self", "values::projected_uninit", "values::units_compared", ] diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs new file mode 100644 index 000000000..e8798579d --- /dev/null +++ b/ts/src/lower/defined.rs @@ -0,0 +1,396 @@ +//! What typing a generic type's definition named: the one account its instances, its drop +//! glue, and the check of its instances' `Option`s and `Poll`s read. +//! +//! A projection from a generic type's parameters that typing its fields names, such as +//! smallvec's `::Item`, becomes a TypeScript type parameter of its own, which +//! each instance gives what the projection is there. A type typed with itself, directly or +//! through others, names instances of a definition that is still being typed, whose +//! projections are not all known yet: those instances' projection arguments are filled in +//! once every definition typed with them is. + +use rustc_hir::def_id::DefId; +use rustc_middle::ty::{self, GenericArgsRef, Ty, TypeVisitableExt, TypingEnv}; +use rustc_span::Span; + +use super::Translator; +use crate::ts::{Item, Type}; + +/// The account of one generic type's definition. +#[derive(Default)] +pub struct Defined<'tcx> { + /// Each projection from its type parameters its fields' types name, by the name of the + /// TypeScript type parameter it is, after its own. + pub projections: Vec<(Ty<'tcx>, String)>, + /// Each `Option`, `Poll`, or `ControlFlow` its fields' types type over a type parameter. + pub decided: Vec>, + /// While it is typed, the shallowest definition still being typed that it was typed + /// with, by its depth in [`Defining::typing`]; `None` once it is complete. + open: Option, +} + +/// An `Option` or a `Poll` over a type parameter, or a projection from one, that a generic +/// type's definition typed. +#[derive(Clone)] +pub struct Decided<'tcx> { + /// The field whose type holds it. + pub field: DefId, + /// It, over the definition's own parameters. + pub part: Ty<'tcx>, + /// What it holds, or for a `ControlFlow`, continues with. + pub inner: Ty<'tcx>, + /// TypeScript's type of `inner` in the definition. + pub defined: Type, + /// An `Option`, rather than a `Poll`. + pub option: bool, +} + +/// The state of typing definitions. +#[derive(Default)] +pub struct Defining<'tcx> { + records: rustc_data_structures::fx::FxHashMap>, + /// The definitions being typed, innermost last. + typing: Vec, + /// Definitions typed, waiting on one shallower in [`Self::typing`] they were typed with. + members: Vec, + /// Instances of definitions still being typed, by the [`Type::Later`] each typed. + pending: Vec>, + /// How many projections every record holds, which grows while instances are filled. + recorded: usize, + /// The field of the definition being typed whose type is. + pub field: Option, + /// The instances whose projection arguments are being typed. + instancing: Vec>, +} + +struct Typing { + adt: DefId, + members: usize, + pending: usize, + items: usize, +} + +/// A definition typed, and the type of each of its fields there, which may hold +/// [`Type::Later`]s until its cycle completes. +struct Member { + adt: DefId, + item: usize, + fields: Vec<(DefId, Type)>, + span: Span, +} + +struct Pending<'tcx> { + adt: DefId, + args: GenericArgsRef<'tcx>, + /// The definition typing it, and the field whose type did. + at: Option<(DefId, TypingEnv<'tcx>)>, + field: Option, + span: Span, + /// Its projection arguments as last filled in. + filled: Option>, + /// Whether it was refused, and so is filled for good. + refused: bool, +} + +impl<'tcx> Translator<'_, 'tcx> { + /// The record of the crate's generic `adt`, defining it if it isn't yet. + pub fn defined(&mut self, adt: DefId) -> &Defined<'tcx> { + if !self.defining_state.records.contains_key(&adt) { + let tcx = self.tcx; + self.adt(tcx.adt_def(adt), tcx.def_span(adt)); + } + &self.defining_state.records[&adt] + } + + /// Start typing the definition of `adt`. + pub(super) fn start_definition(&mut self, adt: DefId) { + let state = &mut self.defining_state; + state.records.insert( + adt, + Defined { + open: Some(state.typing.len()), + ..Default::default() + }, + ); + state.typing.push(Typing { + adt, + members: state.members.len(), + pending: state.pending.len(), + items: self.program.items.len(), + }); + } + + /// Finish typing the definition of `adt`, whose item is `item` and whose fields typed as + /// `fields`: complete it, with every definition typed with it, unless it was typed with + /// one shallower that is still being typed. + pub(super) fn finish_definition(&mut self, adt: DefId, item: usize, fields: Vec<(DefId, Type)>, span: Span) { + let state = &mut self.defining_state; + let depth = state.typing.len() - 1; + state.members.push(Member { + adt, + item, + fields, + span, + }); + let low = state.records[&adt].open.expect("an open definition"); + if low < depth { + state.typing.pop(); + let parent = state.typing.len() - 1; + state.lower_typing(parent, low); + return; + } + self.complete(depth); + self.defining_state.typing.pop(); + } + + /// The TypeScript parameter a projection `ty` from type parameters is in the definition + /// being typed, recording it while that definition is open. + pub(super) fn projection_param(&mut self, ty: Ty<'tcx>) -> Option { + let (adt, _) = self.defining?; + let ty::Alias(_, alias) = ty.kind() else { + return None; + }; + let item = alias.kind.try_to_projection()?; + if !ty.has_param() { + return None; + } + let tcx = self.tcx; + let record = self.defining_state.records.get_mut(&adt)?; + if let Some((_, name)) = record.projections.iter().find(|(p, _)| *p == ty) { + return Some(Type::Param(name.clone())); + } + record.open?; + // Named for its associated type, numbered past any name it would shadow. + let taken: Vec = own_params(tcx, adt) + .chain(record.projections.iter().map(|(_, name)| name.clone())) + .collect(); + let base = tcx.item_name(item).to_string(); + let name = (1..) + .map(|n| match n { + 1 => base.clone(), + n => format!("{base}{n}"), + }) + .find(|name| !taken.contains(name)) + .unwrap(); + record.projections.push((ty, name.clone())); + self.defining_state.recorded += 1; + Some(Type::Param(name)) + } + + /// Note that the definition being typed typed `part`, an `Option`, `Poll`, or + /// `ControlFlow` of `inner`, typing `inner` as `defined`, for the check of its instances. + pub(super) fn note_decided(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, defined: &Type, option: bool) { + let (Some((adt, _)), Some(field)) = (self.defining, self.defining_state.field) else { + return; + }; + if !self.erases_to(inner).param { + return; + } + let Some(record) = self.defining_state.records.get_mut(&adt) else { + return; + }; + if record.open.is_some() && !record.decided.iter().any(|d| d.part == part) { + record.decided.push(Decided { + field, + part, + inner, + defined: defined.clone(), + option, + }); + } + } + + /// The projection arguments of an instance of the crate's generic `adt` with `args`, a + /// definition still being typed, as a [`Type::Later`] filled in once it is complete. + pub(super) fn later_projections(&mut self, adt: DefId, args: GenericArgsRef<'tcx>, span: Span) -> Option { + let state = &mut self.defining_state; + let low = state.records.get(&adt)?.open?; + // Whatever types it is typed with it. + if let Some(top) = state.typing.len().checked_sub(1) { + state.lower_typing(top, low); + } + // Typed by the same definition before, so filling either in can't name it again + // without end. + let at = self.defining.map(|(did, _)| did); + if at.is_some() + && let Some(id) = + (state.pending.iter()).position(|p| p.adt == adt && p.args == args && p.at.map(|(did, _)| did) == at) + { + return Some(Type::Later(id)); + } + state.pending.push(Pending { + adt, + args, + at: self.defining, + field: state.field, + span, + filled: None, + refused: false, + }); + Some(Type::Later(state.pending.len() - 1)) + } + + /// Start typing `instance`, an instance of the crate's generic type, unless it is + /// typed already further out: a projection of its parameters is then a type that holds + /// it again, so its TypeScript type arguments would never end, which is refused. + pub(super) fn enter_instance(&mut self, instance: Ty<'tcx>, span: Span) -> bool { + let instancing = &self.defining_state.instancing; + if instancing.contains(&instance) || instancing.len() > self.tcx.recursion_limit().0 { + self.err( + span, + format!( + "`{instance}` is not supported yet: a projection of its parameters is a type that holds `{instance}` again, so its TypeScript type arguments would never end" + ), + ); + return false; + } + self.defining_state.instancing.push(instance); + true + } + + /// Finish typing the instance [`Self::enter_instance`] started. + pub(super) fn leave_instance(&mut self) { + self.defining_state.instancing.pop(); + } + + /// Complete the definition at `depth` in [`Defining::typing`] and every one typed with + /// it: fill in the projection arguments of the instances they typed until no record + /// grows, then close their records and settle what reads them. + fn complete(&mut self, depth: usize) { + let tcx = self.tcx; + let (members, pending, items) = { + let typing = &self.defining_state.typing[depth]; + (typing.members, typing.pending, typing.items) + }; + loop { + let recorded = self.defining_state.recorded; + let count = self.defining_state.pending.len(); + for i in pending..count { + self.fill_pending(i); + } + if self.defining_state.recorded == recorded && self.defining_state.pending.len() == count { + break; + } + } + let done: Vec = self.defining_state.members.drain(members..).collect(); + for member in &done { + let record = self.defining_state.records.get_mut(&member.adt).expect("a record"); + record.open = None; + } + // Each instance typed while open now has its projection arguments. + let filled: Vec> = (self.defining_state.pending[pending..].iter()) + .map(|p| p.filled.clone().unwrap_or_default()) + .collect(); + let later = |id: usize| filled[id.checked_sub(pending).expect("an instance this cycle typed")].clone(); + let mut endless = false; + for item in &mut self.program.items[items..] { + let tys: Vec<&mut Type> = match item { + Item::Struct { fields, .. } => fields.iter_mut().map(|f| &mut f.ty).collect(), + Item::Union { ty, .. } => vec![ty], + _ => continue, + }; + for ty in tys { + endless |= ty.fill(&later).is_none(); + } + } + let mut members_done = Vec::with_capacity(done.len()); + for mut member in done { + for (_, ty) in &mut member.fields { + endless |= ty.fill(&later).is_none(); + } + // Its own parameters, then one per projection. + let names = (self.defining_state.records[&member.adt].projections.iter()).map(|(_, name)| name.clone()); + let item = &mut self.program.items[member.item]; + if let Item::Struct { params, .. } | Item::Union { params, .. } = item { + params.extend(names); + } + super::unnamed_params(item); + members_done.push(member); + } + // The definition completing them all, which finished typing last. + if endless && let Some(root) = members_done.last() { + self.err( + root.span, + "a type whose projection is, at its own parameters, a type that holds it again, so its TypeScript type arguments would never end", + ); + } + for member in &members_done { + for (field, ty) in &member.fields { + self.note_field(*field, ty); + } + } + // The checks of instances typed while their definitions were open. + let checks: Vec> = self.defining_state.pending.drain(pending..).collect(); + for check in checks { + let outer = std::mem::replace(&mut self.defining, check.at); + let adt = tcx.adt_def(check.adt); + if let Some(msg) = self.generic_erasure(adt, check.args, check.span) { + self.err(check.span, msg); + } + self.defining = outer; + } + for member in members_done { + let adt = tcx.adt_def(member.adt); + let info = self.adts[&member.adt].clone(); + self.drop_glue(adt, &info, member.span); + } + } + + /// Fill in the projection arguments of the instance `pending[i]` typed, from its + /// definition's projections so far, refusing an instance at other than type parameters + /// that grows a record, as a type that holds itself at other arguments would without end. + fn fill_pending(&mut self, i: usize) { + let p = &self.defining_state.pending[i]; + let projections: Vec> = (self.defining_state.records[&p.adt].projections.iter()) + .map(|(p, _)| *p) + .collect(); + if p.refused || p.filled.as_ref().is_some_and(|f| f.len() == projections.len()) { + return; + } + let (adt, args, at, field, span) = (p.adt, p.args, p.at, p.field, p.span); + let env = at.map_or(self.typing_env(), |(_, env)| env); + let named = |state: &Defining<'tcx>| at.map_or(0, |(did, _)| state.records[&did].projections.len()); + let before = named(&self.defining_state); + let outer = std::mem::replace(&mut self.defining, at); + let outer_field = std::mem::replace(&mut self.defining_state.field, field); + let mut tys: Vec = projections + .iter() + .map(|&p| { + let at = super::copy::instantiate(self.tcx, env, p, args); + self.ty(at, span) + }) + .collect(); + self.defining = outer; + self.defining_state.field = outer_field; + let refused = named(&self.defining_state) != before && !args.types().all(|t| matches!(t.kind(), ty::Param(_))); + if refused { + let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); + self.err( + span, + format!( + "`{instance}` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end" + ), + ); + tys = vec![Type::Builtin("unknown"); projections.len()]; + } + let p = &mut self.defining_state.pending[i]; + p.filled = Some(tys); + p.refused = refused; + } +} + +impl Defining<'_> { + /// Note that the definition at `depth` in [`Self::typing`] was typed with the one at `low`. + fn lower_typing(&mut self, depth: usize, low: usize) { + let adt = self.typing[depth].adt; + if let Some(open) = &mut self.records.get_mut(&adt).expect("a record").open { + *open = (*open).min(low); + } + } +} + +/// The names of `adt`'s own type parameters. +fn own_params(tcx: ty::TyCtxt<'_>, adt: DefId) -> impl Iterator + '_ { + (tcx.generics_of(adt).own_params.iter()) + .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) + .map(|param| param.name.to_string()) +} diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index e1e830aa0..d4b247d4c 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -228,11 +228,17 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// What a generic struct's glue drops through, at an instance with `args`: each type /// argument, and what each projection from them its fields name is there. fn glue_held(&mut self, adt: AdtDef<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Vec> { - let env = self.typing_env_pub(); - let projections = self.projections(adt.did()).into_iter().map(|(p, _)| p); - (args.types()) - .chain(projections.map(|p| super::copy::instantiate(self.tcx, env, p, args))) - .collect() + let mut held: Vec> = args.types().collect(); + if self.crates.translates(adt.did()) { + let env = self.typing_env_pub(); + let projections: Vec> = self.defined(adt.did()).projections.iter().map(|(p, _)| *p).collect(); + held.extend( + projections + .into_iter() + .map(|p| super::copy::instantiate(self.tcx, env, p, args)), + ); + } + held } /// Whether every JS value of `ty`, a type that drops and is [`Self::disposable`], is an diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index a6b1cd132..037b487f0 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -13,6 +13,7 @@ mod caller; mod collections; mod consts; mod copy; +mod defined; mod drop; mod dynamic; mod float; @@ -206,9 +207,8 @@ pub struct Translator<'a, 'tcx> { /// by that type, which an instance's value is cast to so TypeScript infers the /// instance's arguments from it rather than from the `None` or `Pending` it may be. decided_fields: rustc_data_structures::fx::FxHashMap, - /// The projections from each generic type's own parameters that its fields name, by - /// [`Self::projections`]. - projections: rustc_data_structures::fx::FxHashMap, String)>>, + /// What typing each generic type's definition named, and the definitions being typed. + defining_state: defined::Defining<'tcx>, /// Source files by name, as indexes into `program.sources`. sources: rustc_data_structures::fx::FxHashMap>, failed: bool, @@ -505,7 +505,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { overlays: Default::default(), instanced_fields: Default::default(), decided_fields: Default::default(), - projections: Default::default(), + defining_state: Default::default(), sources: Default::default(), failed: false, env: TypingEnv::fully_monomorphized(), @@ -1097,16 +1097,17 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // Its fields' types are refused where they are written, whatever reached it. let held = std::mem::replace(&mut self.held, false); let glue = std::mem::replace(&mut self.glue, false); + let defining = self.defining.take(); + let field = self.defining_state.field.take(); let info = self.define(adt, span); - (self.held, self.glue) = (held, glue); + (self.held, self.glue, self.defining, self.defining_state.field) = (held, glue, defining, field); info } fn define(&mut self, adt: AdtDef<'tcx>, span: Span) -> Adt { let tcx = self.tcx; let did = adt.did(); - // Before any field is typed, which names them. - let projections = self.projections(did); + self.start_definition(did); let mut overlay = None; let repr = match () { _ if adt.is_struct() => { @@ -1177,7 +1178,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } self.adts.insert(did, info.clone()); - let mut params: Vec = tcx + // Its own, then one per projection typing its fields names, once they all are. + let params: Vec = tcx .generics_of(did) .own_params .iter() @@ -1187,7 +1189,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ty::GenericParamDefKind::Lifetime | ty::GenericParamDefKind::Const { .. } => None, }) .collect(); - params.extend(projections.into_iter().map(|(_, name)| name)); + let mut typed = Vec::new(); let item = match &info.repr { AdtRepr::Struct { ctor, fields } => { let variant = adt.non_enum_variant(); @@ -1196,12 +1198,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let span = tcx.def_span(field.did); let doc = self.doc(field.did); match held { - StructField::Whole(id) => defined.push(ts::Field { - id, - ty: self.defined_field_ty(field, did, span), - doc, - init: ts::FieldInit::Ctor, - }), + StructField::Whole(id) => { + let ty = self.defined_field_ty(field, did, span); + typed.push((field.did, ty.clone())); + defined.push(ts::Field { + id, + ty, + doc, + init: ts::FieldInit::Ctor, + }); + } StructField::Slice(ids) => { let tys = [ Type::Builtin("Uint8Array"), @@ -1263,6 +1269,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { for (i, field) in v.fields.iter().enumerate() { let span = tcx.def_span(field.did); let ty = self.defined_field_ty(field, did, span); + typed.push((field.did, ty.clone())); fields.push((field_name(field.name.as_str(), i), ty)); } Type::Object(fields) @@ -1276,8 +1283,8 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } } }; - self.program.items.push(unnamed_params(item)); - self.drop_glue(adt, &info, span); + self.program.items.push(item); + self.finish_definition(did, self.program.items.len() - 1, typed, span); info } @@ -1534,15 +1541,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }, ty::Param(param) => Type::Param(param.name.to_string()), // A projection a generic type's fields name, which each instance decides. - ty::Alias(..) - if let Some((adt, _)) = self.defining - && let Some((_, name)) = self - .projections - .get(&adt) - .and_then(|p| p.iter().find(|(p, _)| *p == ty)) => - { - Type::Param(name.clone()) - } + ty::Alias(..) if let Some(param) = self.projection_param(ty) => param, ty::FnDef(def_id, args) => { let sig = self.sig(*def_id, args.skip_binder()); self.fn_ty(sig.inputs(), sig.output(), span) @@ -1687,7 +1686,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if let Some(msg) = self.erasure_conflict(Ty::new_adt(tcx, adt, args)) { self.err(span, msg); } - self.option_ty(args.type_at(0), span) + self.option_ty(Ty::new_adt(tcx, adt, args), args.type_at(0), span) } library::Std::Result => Type::Rs( "Result", @@ -1791,7 +1790,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if let Some(msg) = self.erasure_conflict(Ty::new_adt(tcx, adt, args)) { self.err(span, msg); } - self.poll_ty(args.type_at(0), span) + self.poll_ty(Ty::new_adt(tcx, adt, args), args.type_at(0), span) } library::Std::Pin => self.ty(args.type_at(0), span), library::Std::PollFn | library::Std::Pending => { @@ -1874,7 +1873,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }; Type::Rs("Result", vec![self.ty(value, span), self.ty(residual.type_at(1), span)]) } - library::Flow::Option => self.option_ty(value, span), + library::Flow::Option => self.option_ty(Ty::new_adt(tcx, adt, args), value, span), library::Flow::Other => Type::Rs("Result", vec![self.ty(value, span), self.ty(residual, span)]), } } @@ -1901,6 +1900,15 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { return Type::Builtin("unknown"); } let info = self.adt(adt, span); + // Of a definition still being typed, whose instances are checked once it is complete. + if let Some(later) = self.later_projections(adt.did(), args, span) { + let mut tys: Vec = args.types().map(|t| self.ty(t, span)).collect(); + tys.push(later); + return Type::Sym(info.sym, tys); + } + if !self.enter_instance(Ty::new_adt(tcx, adt, args), span) { + return Type::Builtin("unknown"); + } if let Some(msg) = self.generic_erasure(adt, args, span) { self.err(span, msg); } @@ -1909,12 +1917,13 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { .into_iter() .map(|(_, t)| t) .collect(); + self.leave_instance(); Type::Sym(info.sym, args) } /// The TypeScript type arguments of an instance of the crate's generic `adt` with `args`, /// by the name of the parameter each is: one per type parameter, then one per projection - /// its fields name, which is what the projection normalizes to at `args`. + /// typing its fields named, which is what the projection normalizes to at `args`. fn instance_params(&mut self, adt: DefId, args: GenericArgsRef<'tcx>, span: Span) -> Vec<(String, Type)> { let tcx = self.tcx; let mut params: Vec<(String, Type)> = (tcx.generics_of(adt).own_params.iter()) @@ -1926,101 +1935,16 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ) }) .collect(); + let (projections, names): (Vec>, Vec) = self.defined(adt).projections.iter().cloned().unzip(); let env = self.defining.map_or(self.typing_env(), |(_, env)| env); - for (projection, name) in self.projections(adt) { + for (projection, name) in projections.into_iter().zip(names) { let at = copy::instantiate(tcx, env, projection, args); params.push((name, self.ty(at, span))); } params } - /// The projections from the crate's generic `adt`'s own type parameters that its fields' - /// types name, such as smallvec's `::Item`, each a TypeScript type parameter - /// of its own, by its name: the associated type's, numbered past any it would shadow. - /// They include those of the crate's generic types its fields hold, at its arguments, and - /// the item type of an iterator from another crate, which `rs.Iter` types, but none that - /// only what types nothing holds, such as a `PhantomData`. - fn projections(&mut self, adt: DefId) -> Vec<(Ty<'tcx>, String)> { - if let Some(named) = self.projections.get(&adt) { - return named.clone(); - } - let tcx = self.tcx; - // A type that holds itself holds no projection it doesn't name otherwise. - self.projections.insert(adt, Vec::new()); - let identity = ty::GenericArgs::identity_for_item(tcx, adt); - let env = TypingEnv::post_analysis(tcx, adt); - let outer = self.defining.replace((adt, env)); - let mut found = Vec::new(); - for field in tcx.adt_def(adt).all_fields() { - self.find_projections(copy::field_ty(tcx, env, field.did, identity), env, &mut found); - } - self.defining = outer; - let mut names: Vec = (tcx.generics_of(adt).own_params.iter()) - .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) - .map(|param| param.name.to_string()) - .collect(); - let mut named = Vec::with_capacity(found.len()); - for projection in found { - let ty::Alias(_, alias) = projection.kind() else { - unreachable!("a projection") - }; - let item = alias.kind.try_to_projection().expect("a projection"); - let base = tcx.item_name(item).to_string(); - let name = (1..) - .map(|n| match n { - 1 => base.clone(), - n => format!("{base}{n}"), - }) - .find(|name| !names.contains(name)) - .unwrap(); - names.push(name.clone()); - named.push((projection, name)); - } - self.projections.insert(adt, named.clone()); - named - } - - /// Add to `found` each projection over type parameters that typing `ty` in `env` names. - fn find_projections(&mut self, ty: Ty<'tcx>, env: TypingEnv<'tcx>, found: &mut Vec>) { - let tcx = self.tcx; - let mut walker = ty.walk(); - while let Some(arg) = walker.next() { - let Some(t) = arg.as_type() else { continue }; - match t.kind() { - ty::Alias(_, alias) if alias.kind.try_to_projection().is_some() && t.has_param() => { - if !found.contains(&t) { - found.push(t); - } - walker.skip_current_subtree(); - } - ty::Adt(adt, args) - if self.crates.translates(adt.did()) - && library::Std::of(tcx, adt.did()).is_none() - && self.bound_drops(adt.did()).is_none() => - { - for (projection, _) in self.projections(adt.did()) { - self.find_projections(copy::instantiate(tcx, env, projection, args), env, found); - } - } - // `undefined`, whatever it holds. - ty::Adt(..) if library::nothing(tcx, t) => walker.skip_current_subtree(), - // An `rs.Iter` of its items, whatever it iterates. - ty::Adt(adt, _) - if !self.crates.translates(adt.did()) - && matches!(library::Std::of(tcx, adt.did()), None | Some(library::Std::Peekable)) - && let Some(item) = self.iter_item(t) => - { - self.find_projections(item, env, found); - walker.skip_current_subtree(); - } - _ => {} - } - } - } - - /// The TypeScript type of `field` in the definition of `adt`, over its own parameters, - /// noting a field that holds a form only an instance decides for - /// [`Self::held_field`]. + /// The TypeScript type of `field` in the definition of `adt`, over its own parameters. fn defined_field_ty(&mut self, field: &ty::FieldDef, adt: DefId, span: Span) -> Type { let tcx = self.tcx; // A projection it names, such as `::Hasher` of a known `S`, is the @@ -2029,16 +1953,23 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let env = TypingEnv::post_analysis(tcx, adt); let ty = copy::field_ty(tcx, env, field.did, identity); let outer = self.defining.replace((adt, env)); + let outer_field = self.defining_state.field.replace(field.did); let ty = self.ty(ty, span); self.defining = outer; + self.defining_state.field = outer_field; + ty + } + + /// Note `field`'s type `ty` in its type's definition for [`Self::field_value`] and + /// [`Self::held_field`], once its definition is complete. + fn note_field(&mut self, field: DefId, ty: &Type) { if ty.any(&instanced) { self.instanced_fields - .insert(field.did, ty.takes(&instanced).then(|| ty.clone())); + .insert(field, ty.takes(&instanced).then(|| ty.clone())); } if ty.any(&|t| matches!(t, Type::Rs("OptionOf" | "PollOf", _))) { - self.decided_fields.insert(field.did, ty.clone()); + self.decided_fields.insert(field, ty.clone()); } - ty } /// `value`, for `field` of an instance of `adt` with `args`, cast to the field's type in @@ -2084,108 +2015,77 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { fn generic_erasure(&mut self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, span: Span) -> Option { let tcx = self.tcx; let ty = Ty::new_adt(tcx, adt, args); - let identity = ty::GenericArgs::identity_for_item(tcx, adt.did()); - let defining = (adt.did(), TypingEnv::post_analysis(tcx, adt.did())); let env = self.defining.map_or(self.typing_env(), |(_, env)| env); - let projections: Vec> = self.projections(adt.did()).into_iter().map(|(p, _)| p).collect(); - // Whether `t` names a projection its definition types nothing of, as a `PhantomData`'s. - let untyped = |t: Ty<'tcx>| { - let mut walker = t.walk(); - while let Some(arg) = walker.next() { - let Some(t) = arg.as_type() else { continue }; - if let ty::Alias(..) = t.kind() - && t.has_param() - { - if !projections.contains(&t) { - return true; - } - walker.skip_current_subtree(); - } + // Each its definition typed, which leaves out what types nothing, such as a `PhantomData`. + let decided = self.defined(adt.did()).decided.clone(); + for defined::Decided { + field, + part, + inner, + defined, + option, + } in decided + { + let erased = self.erases_to(inner); + let field = tcx.item_name(field); + let held = copy::instantiate(tcx, env, part, args); + if let Some(msg) = self.erasure_conflict(held) { + return Some(format!( + "`{ty}` is not supported: its field `{field}` is `{held}`: {msg}" + )); } - false - }; - for field in adt.all_fields() { - let generic = copy::field_ty(tcx, defining.1, field.did, identity); - for part in generic.walk().filter_map(|arg| arg.as_type()) { - let ty::Adt(wrapper, wrapper_args) = part.kind() else { - continue; - }; - let option = match library::Std::of(tcx, wrapper.did()) { - Some(library::Std::Option) => true, - Some(library::Std::Poll) => false, - _ => continue, - }; - let inner = wrapper_args.type_at(0); - let erased = self.erases_to(inner); - if !erased.param || untyped(inner) { - continue; - } - let held = copy::instantiate(tcx, env, part, args); - if let Some(msg) = self.erasure_conflict(held) { - return Some(format!( - "`{ty}` is not supported: its field `{}` is `{held}`: {msg}", - field.name - )); - } - // Over a parameter of the instance's own, which its instances decide. - if held.has_param() { - continue; - } - let instance = copy::instantiate(tcx, env, inner, args); - let at = self.erases_to(instance); - let outer = self.defining.replace(defining); - let defined = self.ty(inner, span); - self.defining = outer; - let msg = match defined { - // What TypeScript's type of the parameter decides, which must be what the - // instance's erasure does. - Type::Param(_) if if option { !erased.undefined } else { !erased.pending } => { - // The parameter, or the projection from one, that it types. - let Some(param) = inner.walk().filter_map(|arg| arg.as_type()).find(|t| matches!(t.kind(), ty::Param(_) | ty::Alias(..))) else { - continue; - }; - let arg = copy::instantiate(tcx, env, param, args); - let decided = self.ty(arg, span); - let (holds, alias) = match option { - true => (holds_undefined(&decided) == at.undefined, "OptionOf"), - false => (holds_pending(&decided) == at.pending, "PollOf"), - }; - (!holds).then(|| { - format!( - "`{ty}` is not supported: its field `{}` is `{part}`, typed `rs.{alias}<{inner}>`, which decides by TypeScript's type of `{arg}` otherwise than `{held}` erases", - field.name - ) - }) - } - _ if option && !erased.undefined => at.undefined.then(|| { - format!( - "`{ty}` is not supported: its field `{}` is `Option<{inner}>`, typed `{inner} | undefined`, whose `None` would be `rs.None` as `Option<{instance}>`", - field.name - ) - }), - _ if !option && !erased.pending => at.pending.then(|| { - format!( - "`{ty}` is not supported: its field `{}` is `Poll<{inner}>`, typed `rs.Poll<{inner}>`, whose `Pending` would be `rs.OuterPending` as `Poll<{instance}>`", - field.name - ) - }), - // A `MaybeUninit`, which can be `undefined` but for a byte. - _ if option => (!at.undefined).then(|| { - format!( - "`{ty}` is not supported: its field `{}` is `Option<{inner}>`, typed `{inner} | rs.None`, whose `None` would be `undefined` as `Option<{instance}>`", - field.name - ) - }), - _ => (!at.pending).then(|| { + // Over a parameter of the instance's own, which its instances decide. + if held.has_param() { + continue; + } + let instance = copy::instantiate(tcx, env, inner, args); + let at = self.erases_to(instance); + let msg = match defined { + // What TypeScript's type of the parameter decides, which must be what the + // instance's erasure does. + Type::Param(_) if if option { !erased.undefined } else { !erased.pending } => { + // The parameter, or the projection from one, that it types. + let Some(param) = (inner.walk().filter_map(|arg| arg.as_type())) + .find(|t| matches!(t.kind(), ty::Param(_) | ty::Alias(..))) + else { + continue; + }; + let arg = copy::instantiate(tcx, env, param, args); + let decided = self.ty(arg, span); + let (holds, alias) = match option { + true => (holds_undefined(&decided) == at.undefined, "OptionOf"), + false => (holds_pending(&decided) == at.pending, "PollOf"), + }; + (!holds).then(|| { format!( - "`{ty}` is not supported: its field `{}` is `Poll<{inner}>`, typed `{inner} | rs.OuterPending`, whose `Pending` would be `rs.Pending` as `Poll<{instance}>`", - field.name + "`{ty}` is not supported: its field `{field}` is `{part}`, typed `rs.{alias}<{inner}>`, which decides by TypeScript's type of `{arg}` otherwise than `{held}` erases" ) - }), - }; - if msg.is_some() { - return msg; + }) } + _ if option && !erased.undefined => at.undefined.then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Option<{inner}>`, typed `{inner} | undefined`, whose `None` would be `rs.None` as `Option<{instance}>`" + ) + }), + _ if !option && !erased.pending => at.pending.then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Poll<{inner}>`, typed `rs.Poll<{inner}>`, whose `Pending` would be `rs.OuterPending` as `Poll<{instance}>`" + ) + }), + // A `MaybeUninit`, which can be `undefined` but for a byte. + _ if option => (!at.undefined).then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Option<{inner}>`, typed `{inner} | rs.None`, whose `None` would be `undefined` as `Option<{instance}>`" + ) + }), + _ => (!at.pending).then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Poll<{inner}>`, typed `{inner} | rs.OuterPending`, whose `Pending` would be `rs.Pending` as `Poll<{instance}>`" + ) + }), + }; + if msg.is_some() { + return msg; } } None @@ -2205,8 +2105,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// The TypeScript type of an `Option`: `T | undefined`, or `T | rs.None` where /// `T` can already be `undefined`, and in a generic type's definition, of a type /// parameter, an `rs.OptionOf`, which is either as each instance's `T` decides. - fn option_ty(&mut self, inner: Ty<'tcx>, span: Span) -> Type { + /// `part` is the `Option`, or the `ControlFlow` that is one. + fn option_ty(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, span: Span) -> Type { let some = self.ty(inner, span); + self.note_decided(part, inner, &some, true); let erased = self.erases_to(inner); if matches!(some, Type::Param(_)) && !erased.undefined { return Type::Rs("OptionOf", vec![some]); @@ -2233,8 +2135,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// The TypeScript type of a `Poll`: `rs.Poll`, or `T | rs.OuterPending` where /// `T` can already be `rs.Pending`, and in a generic type's definition, of a type /// parameter, an `rs.PollOf`, which is either as each instance's `T` decides. - fn poll_ty(&mut self, inner: Ty<'tcx>, span: Span) -> Type { + fn poll_ty(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, span: Span) -> Type { let ready = self.ty(inner, span); + self.note_decided(part, inner, &ready, false); if matches!(ready, Type::Param(_)) && !self.erases_to(inner).pending { return Type::Rs("PollOf", vec![ready]); } @@ -2550,18 +2453,17 @@ pub fn camel(name: &str) -> String { /// `item` with each type parameter its type never names marked unused, as TS wants of /// one only a `PhantomData` held. -fn unnamed_params(mut item: Item) -> Item { - let (params, types): (&mut Vec, Vec<&Type>) = match &mut item { +fn unnamed_params(item: &mut Item) { + let (params, types): (&mut Vec, Vec<&Type>) = match item { Item::Struct { params, fields, .. } => (params, fields.iter().map(|f| &f.ty).collect()), Item::Union { params, ty, .. } => (params, vec![&*ty]), - _ => return item, + _ => return, }; for param in params.iter_mut() { if !types.iter().any(|t| t.names(param)) { *param = format!("_{param}"); } } - item } /// A field's property name: `camelCase`, or `_0` for a tuple field. diff --git a/ts/src/print.rs b/ts/src/print.rs index c66a73e33..c28bd09d6 100644 --- a/ts/src/print.rs +++ b/ts/src/print.rs @@ -354,6 +354,7 @@ impl Refs { self.ty(program, ret); } Type::Object(fields) => fields.iter().for_each(|(_, t)| self.ty(program, t)), + Type::Later(_) => unreachable!("unfilled projection arguments"), } } @@ -1159,6 +1160,7 @@ impl Printer<'_> { .collect(); format!("{{ {} }}", parts.join("; ")) } + Type::Later(_) => unreachable!("unfilled projection arguments"), } } diff --git a/ts/src/ts.rs b/ts/src/ts.rs index 41dfed5ed..6b419ed4d 100644 --- a/ts/src/ts.rs +++ b/ts/src/ts.rs @@ -686,6 +686,9 @@ pub enum Type { StrLit(String), /// An object type; a `Some` key is a class field. Object(Vec<(String, Type)>), + /// In a generic type's arguments, the projection arguments of an instance of a type + /// whose definition is still being typed, which [`Type::fill`] splices in once it is. + Later(usize), } impl Type { @@ -699,7 +702,7 @@ impl Type { pub fn takes(&self, f: &impl Fn(&Type) -> bool) -> bool { match self { Type::Func(params, ret) => params.iter().any(|t| t.any(f)) || ret.takes(f), - Type::Param(_) | Type::Builtin(_) | Type::StrLit(_) => false, + Type::Param(_) | Type::Builtin(_) | Type::StrLit(_) | Type::Later(_) => false, Type::Rs(_, tys) | Type::Sym(_, tys) if !f(self) => tys.iter().any(|t| t.any(f)), Type::Rs(_, tys) | Type::Sym(_, tys) | Type::Union(tys) | Type::Tuple(tys) => { tys.iter().any(|t| t.takes(f)) @@ -714,7 +717,7 @@ impl Type { let all = |tys: &[Type]| tys.iter().map(|t| t.subst(f)).collect(); match self { Type::Param(name) => f(name).unwrap_or_else(|| self.clone()), - Type::Builtin(_) | Type::StrLit(_) => self.clone(), + Type::Builtin(_) | Type::StrLit(_) | Type::Later(_) => self.clone(), Type::Rs(name, tys) => Type::Rs(name, all(tys)), Type::Sym(sym, tys) => Type::Sym(*sym, all(tys)), Type::Union(tys) => Type::Union(all(tys)), @@ -729,7 +732,7 @@ impl Type { pub fn any(&self, f: &impl Fn(&Type) -> bool) -> bool { f(self) || match self { - Type::Param(_) | Type::Builtin(_) | Type::StrLit(_) => false, + Type::Param(_) | Type::Builtin(_) | Type::StrLit(_) | Type::Later(_) => false, Type::Rs(_, tys) | Type::Sym(_, tys) | Type::Union(tys) | Type::Tuple(tys) => { tys.iter().any(|t| t.any(f)) } @@ -738,6 +741,49 @@ impl Type { Type::Object(fields) => fields.iter().any(|(_, t)| t.any(f)), } } + + /// It with each [`Type::Later`] replaced by the arguments `later` gives it, or `None` + /// if one reaches itself. + pub fn fill(&mut self, later: &impl Fn(usize) -> Vec) -> Option<()> { + self.fill_from(later, &mut Vec::new()) + } + + fn fill_from(&mut self, later: &impl Fn(usize) -> Vec, open: &mut Vec) -> Option<()> { + match self { + Type::Param(_) | Type::Builtin(_) | Type::StrLit(_) => Some(()), + Type::Later(_) => unreachable!("projection arguments outside a generic type's arguments"), + Type::Rs(_, tys) | Type::Union(tys) | Type::Tuple(tys) => { + tys.iter_mut().try_for_each(|t| t.fill_from(later, open)) + } + Type::Sym(_, tys) => { + let mut filled = Vec::with_capacity(tys.len()); + for mut t in std::mem::take(tys) { + let Type::Later(id) = t else { + t.fill_from(later, open)?; + filled.push(t); + continue; + }; + if open.contains(&id) { + return None; + } + open.push(id); + for mut t in later(id) { + t.fill_from(later, open)?; + filled.push(t); + } + open.pop(); + } + *tys = filled; + Some(()) + } + Type::Array(t) => t.fill_from(later, open), + Type::Func(tys, ret) => { + tys.iter_mut().try_for_each(|t| t.fill_from(later, open))?; + ret.fill_from(later, open) + } + Type::Object(fields) => fields.iter_mut().try_for_each(|(_, t)| t.fill_from(later, open)), + } + } } #[derive(Clone, Debug)] From a9b1ef9c58e8243ece6ffda4a625c9a9f8ade1b7 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 07:36:13 -0800 Subject: [PATCH 06/21] fix(ts): refuse only an instance its own projection arguments name again Typing an instance's own type arguments, or a definition or trait object they reach, ends at a named type, so only re-entry while typing projection values or the erasure check is endless. The census at the moq pin had 9 false refusals (kio::Shared, crossbeam's Atomic>, Shared). Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 4 +-- ts/fixtures/reject/expected.txt | 4 +-- ts/src/lower/defined.rs | 10 ++++--- ts/src/lower/mod.rs | 48 +++++++++++++++++++++++---------- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index af556466c..4a7e18ab6 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -289,10 +289,10 @@ fixtures/reject/src/values.rs:nested+1:2: `Option` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+1:26: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:31: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index c452b77cb..a54c8074b 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -289,10 +289,10 @@ fixtures/reject/src/values.rs:nested+1:2: `Option` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+1:26: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:31: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index e8798579d..d7b195739 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -58,8 +58,9 @@ pub struct Defining<'tcx> { recorded: usize, /// The field of the definition being typed whose type is. pub field: Option, - /// The instances whose projection arguments are being typed. - instancing: Vec>, + /// The instances being typed in the type expression being typed, which a definition + /// starts afresh. + pub(super) instancing: Vec>, } struct Typing { @@ -230,8 +231,9 @@ impl<'tcx> Translator<'_, 'tcx> { } /// Start typing `instance`, an instance of the crate's generic type, unless it is - /// typed already further out: a projection of its parameters is then a type that holds - /// it again, so its TypeScript type arguments would never end, which is refused. + /// typed already further out in the same type expression: a projection of its + /// parameters is then a type whose arguments hold it again, so its TypeScript type + /// arguments would never end, which is refused. pub(super) fn enter_instance(&mut self, instance: Ty<'tcx>, span: Span) -> bool { let instancing = &self.defining_state.instancing; if instancing.contains(&instance) || instancing.len() > self.tcx.recursion_limit().0 { diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 037b487f0..f814d1901 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1099,8 +1099,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let glue = std::mem::replace(&mut self.glue, false); let defining = self.defining.take(); let field = self.defining_state.field.take(); + // A named type, so an instance its fields hold again ends there. + let instancing = std::mem::take(&mut self.defining_state.instancing); let info = self.define(adt, span); (self.held, self.glue, self.defining, self.defining_state.field) = (held, glue, defining, field); + self.defining_state.instancing = instancing; info } @@ -1906,27 +1909,40 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { tys.push(later); return Type::Sym(info.sym, tys); } + // Its own arguments first: one that holds the instance again names a type that ends. + let mut tys: Vec = self + .own_args(adt.did(), args, span) + .into_iter() + .map(|(_, t)| t) + .collect(); if !self.enter_instance(Ty::new_adt(tcx, adt, args), span) { return Type::Builtin("unknown"); } if let Some(msg) = self.generic_erasure(adt, args, span) { self.err(span, msg); } - let args = self - .instance_params(adt.did(), args, span) - .into_iter() - .map(|(_, t)| t) - .collect(); + tys.extend(self.projection_args(adt.did(), args, span).into_iter().map(|(_, t)| t)); self.leave_instance(); - Type::Sym(info.sym, args) + Type::Sym(info.sym, tys) } /// The TypeScript type arguments of an instance of the crate's generic `adt` with `args`, /// by the name of the parameter each is: one per type parameter, then one per projection /// typing its fields named, which is what the projection normalizes to at `args`. fn instance_params(&mut self, adt: DefId, args: GenericArgsRef<'tcx>, span: Span) -> Vec<(String, Type)> { - let tcx = self.tcx; - let mut params: Vec<(String, Type)> = (tcx.generics_of(adt).own_params.iter()) + let mut params = self.own_args(adt, args, span); + let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); + if !self.enter_instance(instance, span) { + return params; + } + params.extend(self.projection_args(adt, args, span)); + self.leave_instance(); + params + } + + /// The TypeScript type of each type argument of an instance of `adt`, by its parameter. + fn own_args(&mut self, adt: DefId, args: GenericArgsRef<'tcx>, span: Span) -> Vec<(String, Type)> { + (self.tcx.generics_of(adt).own_params.iter()) .filter(|param| matches!(param.kind, ty::GenericParamDefKind::Type { .. })) .map(|param| { ( @@ -1934,14 +1950,18 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.ty(args.type_at(param.index as usize), span), ) }) - .collect(); + .collect() + } + + /// What each projection typing the crate's generic `adt`'s fields named normalizes to at + /// `args`, by its parameter. + fn projection_args(&mut self, adt: DefId, args: GenericArgsRef<'tcx>, span: Span) -> Vec<(String, Type)> { let (projections, names): (Vec>, Vec) = self.defined(adt).projections.iter().cloned().unzip(); let env = self.defining.map_or(self.typing_env(), |(_, env)| env); - for (projection, name) in projections.into_iter().zip(names) { - let at = copy::instantiate(tcx, env, projection, args); - params.push((name, self.ty(at, span))); - } - params + let tcx = self.tcx; + (projections.into_iter().zip(names)) + .map(|(projection, name)| (name, self.ty(copy::instantiate(tcx, env, projection, args), span))) + .collect() } /// The TypeScript type of `field` in the definition of `adt`, over its own parameters. From d144d1d22b32e9359efced0753ec46ad5bd57cb8 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:44:29 -0800 Subject: [PATCH 07/21] fix(ts): end every walk a projection holding deeper instances drives A pending instance at other arguments than its definition's parameters is refused when filling it in names another instance still being typed, not only a new projection, and the instances it named are never filled. Drop classification stops at an instance it is already asking about or past the crate's recursion limit. Instance types are memoized within one type expression, so typing a deep chain is linear, and the decided-place analysis's `lends` walk stops past the recursion limit. Each ends in a spanned refusal where rustc accepts the input. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 22 ++++++----- ts/fixtures/reject/expected.txt | 22 ++++++----- ts/fixtures/reject/src/values.rs | 63 ++++++++++++++++++++++++++++++++ ts/fixtures/reject/ts.toml | 3 ++ ts/src/lower/defined.rs | 62 +++++++++++++++++++++++-------- ts/src/lower/drop.rs | 24 +++++++++++- ts/src/lower/mir/decided.rs | 15 ++++++-- ts/src/lower/mod.rs | 21 +++++++---- 8 files changed, 183 insertions(+), 49 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 48ee0a8b7..a28e5abfa 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -269,6 +269,7 @@ fixtures/reject/src/values.rs:Bits+0:1: union `values::Bits`, whose fields overl fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Mixed+0:1: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` @@ -290,16 +291,17 @@ fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values name ever deeper instances, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index c245c90b5..ca5439425 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -267,6 +267,7 @@ fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unch 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 fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` @@ -290,16 +291,17 @@ fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type that holds `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type that holds `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type that holds `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` +fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values name ever deeper instances, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 840770753..56fb5eb28 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -131,6 +131,69 @@ pub fn projected_self(deep: bool) -> bool { held.next.is_some() } +/// What a source lends next, which drops nothing itself. +pub struct Lent { + pub next: Option<&'static S::Item>, +} + +/// A source whose items lend it again. +pub struct Lends; + +impl Source for Lends { + type Item = Lent; +} + +/// Dropping an `Rc` of `Lent` asks whether its glue is inert, through its projection, +/// which ends rather than overflow; it translates, as nothing names the instance's type. +pub fn projected_inert(deep: bool) -> bool { + let lent = std::rc::Rc::new(Lent:: { next: None }); + drop(lent); + deep +} + +/// What a source lends next, beside what it owns, so it drops. +pub struct Owning { + pub next: Option<&'static S::Item>, + pub owned: std::rc::Rc<()>, +} + +/// A source whose items own it again. +pub struct Owns; + +impl Source for Owns { + type Item = Owning; +} + +/// Dropping an `Rc` of `Owning` asks whether its glue is shared, through its projection, +/// which ends rather than overflow; it translates, as nothing names the instance's type. +pub fn projected_disposable(deep: bool) -> bool { + let owning = std::rc::Rc::new(Owning:: { + next: None, + owned: std::rc::Rc::new(()), + }); + drop(owning); + deep +} + +/// A source wrapped once more. +pub struct Wrapped(pub T); + +impl Source for Wrapped { + type Item = Spread>>; +} + +/// A type that holds itself at a wrapped source, whose item is it at a source wrapped again. +pub struct Spread { + pub item: Option<&'static S::Item>, + pub next: Option<&'static Spread>>, +} + +/// `Spread` would name an instance at each wrapping. +pub fn projected_spread(deep: bool) -> bool { + let spread: Spread> = Spread { item: None, next: None }; + spread.next.is_none() && deep +} + /// Two types a source names, each holding the other. pub trait Duo { type Ping; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index e876d752d..c1ef13256 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -209,9 +209,12 @@ roots = [ "values::mixed_byte", "values::nested", "values::projected_chain", + "values::projected_disposable", + "values::projected_inert", "values::projected_nested", "values::projected_rally", "values::projected_self", + "values::projected_spread", "values::projected_uninit", "values::units_compared", ] diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index d7b195739..a21d1b91c 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -61,6 +61,11 @@ pub struct Defining<'tcx> { /// The instances being typed in the type expression being typed, which a definition /// starts afresh. pub(super) instancing: Vec>, + /// The TypeScript type of each instance typed in that type expression, which one span + /// refuses alike wherever in it, so a deep one is typed once. + pub(super) instanced: rustc_data_structures::fx::FxHashMap, Type>, + /// The instances whose drop glue is being classified, by what it drops through. + pub(super) glue_open: rustc_data_structures::fx::FxHashSet>, } struct Typing { @@ -236,22 +241,40 @@ impl<'tcx> Translator<'_, 'tcx> { /// arguments would never end, which is refused. pub(super) fn enter_instance(&mut self, instance: Ty<'tcx>, span: Span) -> bool { let instancing = &self.defining_state.instancing; - if instancing.contains(&instance) || instancing.len() > self.tcx.recursion_limit().0 { - self.err( - span, - format!( - "`{instance}` is not supported yet: a projection of its parameters is a type that holds `{instance}` again, so its TypeScript type arguments would never end" - ), - ); - return false; + let msg = match () { + _ if instancing.contains(&instance) => format!( + "`{instance}` is not supported yet: a projection of its parameters is a type whose arguments hold `{instance}` again, so its TypeScript type arguments would never end" + ), + // Named by the outermost, which the source wrote. + _ if instancing.len() > self.tcx.recursion_limit().0 => format!( + "`{}` is not supported: its projections' values name ever deeper instances, so its TypeScript type arguments would never end", + instancing[0] + ), + _ => { + self.defining_state.instancing.push(instance); + return true; + } + }; + self.err(span, msg); + false + } + + /// Finish typing the instance [`Self::enter_instance`] started, as `ty` if typed whole. + pub(super) fn leave_instance(&mut self, instance: Ty<'tcx>, ty: Option<&Type>) { + let state = &mut self.defining_state; + state.instancing.pop(); + match (state.instancing.is_empty(), ty) { + (true, _) => state.instanced.clear(), + (false, Some(ty)) => { + state.instanced.insert(instance, ty.clone()); + } + (false, None) => {} } - self.defining_state.instancing.push(instance); - true } - /// Finish typing the instance [`Self::enter_instance`] started. - pub(super) fn leave_instance(&mut self) { - self.defining_state.instancing.pop(); + /// The TypeScript type of `instance` if the type expression being typed typed it already. + pub(super) fn instanced(&self, instance: Ty<'tcx>) -> Option { + self.defining_state.instanced.get(&instance).cloned() } /// Complete the definition at `depth` in [`Defining::typing`] and every one typed with @@ -339,7 +362,8 @@ impl<'tcx> Translator<'_, 'tcx> { /// Fill in the projection arguments of the instance `pending[i]` typed, from its /// definition's projections so far, refusing an instance at other than type parameters - /// that grows a record, as a type that holds itself at other arguments would without end. + /// whose projections there name a new projection or another instance still being typed, + /// as a type that holds itself at other arguments would without end. fn fill_pending(&mut self, i: usize) { let p = &self.defining_state.pending[i]; let projections: Vec> = (self.defining_state.records[&p.adt].projections.iter()) @@ -351,7 +375,7 @@ impl<'tcx> Translator<'_, 'tcx> { let (adt, args, at, field, span) = (p.adt, p.args, p.at, p.field, p.span); let env = at.map_or(self.typing_env(), |(_, env)| env); let named = |state: &Defining<'tcx>| at.map_or(0, |(did, _)| state.records[&did].projections.len()); - let before = named(&self.defining_state); + let before = (named(&self.defining_state), self.defining_state.pending.len()); let outer = std::mem::replace(&mut self.defining, at); let outer_field = std::mem::replace(&mut self.defining_state.field, field); let mut tys: Vec = projections @@ -363,7 +387,8 @@ impl<'tcx> Translator<'_, 'tcx> { .collect(); self.defining = outer; self.defining_state.field = outer_field; - let refused = named(&self.defining_state) != before && !args.types().all(|t| matches!(t.kind(), ty::Param(_))); + let after = (named(&self.defining_state), self.defining_state.pending.len()); + let refused = after != before && !args.types().all(|t| matches!(t.kind(), ty::Param(_))); if refused { let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); self.err( @@ -373,6 +398,11 @@ impl<'tcx> Translator<'_, 'tcx> { ), ); tys = vec![Type::Builtin("unknown"); projections.len()]; + // Nor are the instances it named filled in, which would name more. + for named in &mut self.defining_state.pending[before.1..] { + named.filled = Some(Vec::new()); + named.refused = true; + } } let p = &mut self.defining_state.pending[i]; p.filled = Some(tys); diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index d4b247d4c..bf6e93121 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -202,7 +202,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { None | Some(Std::Peekable) if self.std_iter(ty) => self.closures_held(ty), Some(_) => false, None if self.shared_glue(*adt) => { - if !self.glue_held(*adt, args).into_iter().all(|t| self.disposable(t, glue)) { + if !self.glue_all(ty, *adt, args, |t, held| t.disposable(held, glue)) { return false; } if let Some(span) = glue { @@ -225,6 +225,26 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } } + /// Whether `f` holds of everything [`Self::glue_held`] has `ty`, an instance of `adt` with + /// `args`, drop through. It holds of an instance already being asked about, or past the + /// crate's recursion limit, which only a projection whose value holds the instance again + /// or ever deeper ones reaches, and which typing the instance refuses. + fn glue_all( + &mut self, + ty: Ty<'tcx>, + adt: AdtDef<'tcx>, + args: ty::GenericArgsRef<'tcx>, + f: impl Fn(&mut Self, Ty<'tcx>) -> bool, + ) -> bool { + let open = &mut self.defining_state.glue_open; + if open.len() > self.tcx.recursion_limit().0 || !open.insert(ty) { + return true; + } + let all = self.glue_held(adt, args).into_iter().all(|held| f(self, held)); + self.defining_state.glue_open.remove(&ty); + all + } + /// What a generic struct's glue drops through, at an instance with `args`: each type /// argument, and what each projection from them its fields name is there. fn glue_held(&mut self, adt: AdtDef<'tcx>, args: ty::GenericArgsRef<'tcx>) -> Vec> { @@ -413,7 +433,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Context) => false, Some(_) => true, // Its glue, if an instance that drops gave it one, drops its type parameters' fields. - None if adt.is_struct() => self.glue_held(*adt, args).into_iter().all(|t| self.inert(t)), + None if adt.is_struct() => self.glue_all(ty, *adt, args, |t, held| t.inert(held)), None => true, }, _ => true, diff --git a/ts/src/lower/mir/decided.rs b/ts/src/lower/mir/decided.rs index 6ed916198..09296b3a4 100644 --- a/ts/src/lower/mir/decided.rs +++ b/ts/src/lower/mir/decided.rs @@ -1015,11 +1015,15 @@ fn union(a: &[u128], b: &[u128]) -> Vec { } /// Whether a value of `ty` can change what it points at: it is or holds a `&mut` or a -/// `*mut`, or hides what it holds. +/// `*mut`, or hides what it holds, as a type whose fields hold ever deeper instances, past +/// the crate's recursion limit, may. fn lends<'tcx>(tcx: TyCtxt<'tcx>, crates: &Crates<'tcx>, ty: Ty<'tcx>) -> bool { let mut seen = FxHashSet::default(); - let mut stack = vec![ty]; - while let Some(ty) = stack.pop() { + let mut stack = vec![(ty, 0)]; + while let Some((ty, depth)) = stack.pop() { + if depth > tcx.recursion_limit().0 { + return true; + } for t in ty.walk().filter_map(|arg| arg.as_type()) { match t.kind() { ty::Ref(_, _, ty::Mutability::Mut) @@ -1028,7 +1032,10 @@ fn lends<'tcx>(tcx: TyCtxt<'tcx>, crates: &Crates<'tcx>, ty: Ty<'tcx>) -> bool { | ty::Alias(..) | ty::Param(_) => return true, ty::Adt(adt, args) if crates.translates(adt.did()) && seen.insert(t) => { - stack.extend(adt.all_fields().map(|field| field.ty(tcx, args).skip_norm_wip())); + stack.extend( + adt.all_fields() + .map(|field| (field.ty(tcx, args).skip_norm_wip(), depth + 1)), + ); } _ => {} } diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index f814d1901..f1a20be0d 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -208,7 +208,7 @@ pub struct Translator<'a, 'tcx> { /// instance's arguments from it rather than from the `None` or `Pending` it may be. decided_fields: rustc_data_structures::fx::FxHashMap, /// What typing each generic type's definition named, and the definitions being typed. - defining_state: defined::Defining<'tcx>, + pub(super) defining_state: defined::Defining<'tcx>, /// Source files by name, as indexes into `program.sources`. sources: rustc_data_structures::fx::FxHashMap>, failed: bool, @@ -1101,9 +1101,10 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let field = self.defining_state.field.take(); // A named type, so an instance its fields hold again ends there. let instancing = std::mem::take(&mut self.defining_state.instancing); + let instanced = std::mem::take(&mut self.defining_state.instanced); let info = self.define(adt, span); (self.held, self.glue, self.defining, self.defining_state.field) = (held, glue, defining, field); - self.defining_state.instancing = instancing; + (self.defining_state.instancing, self.defining_state.instanced) = (instancing, instanced); info } @@ -1909,21 +1910,26 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { tys.push(later); return Type::Sym(info.sym, tys); } + let instance = Ty::new_adt(tcx, adt, args); + if let Some(ty) = self.instanced(instance) { + return ty; + } // Its own arguments first: one that holds the instance again names a type that ends. let mut tys: Vec = self .own_args(adt.did(), args, span) .into_iter() .map(|(_, t)| t) .collect(); - if !self.enter_instance(Ty::new_adt(tcx, adt, args), span) { + if !self.enter_instance(instance, span) { return Type::Builtin("unknown"); } if let Some(msg) = self.generic_erasure(adt, args, span) { self.err(span, msg); } tys.extend(self.projection_args(adt.did(), args, span).into_iter().map(|(_, t)| t)); - self.leave_instance(); - Type::Sym(info.sym, tys) + let ty = Type::Sym(info.sym, tys); + self.leave_instance(instance, Some(&ty)); + ty } /// The TypeScript type arguments of an instance of the crate's generic `adt` with `args`, @@ -1935,8 +1941,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { if !self.enter_instance(instance, span) { return params; } - params.extend(self.projection_args(adt, args, span)); - self.leave_instance(); + let projections = self.projection_args(adt, args, span); + self.leave_instance(instance, None); + params.extend(projections); params } From 683789dea7da8cf937f67c112732a6a740451a47 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 10:42:14 -0800 Subject: [PATCH 08/21] fix(ts): end typing, glue and holding walks past the recursion limit A type whose projections name ever deeper instances, twice at each level, now refuses at the recursion limit without building the doubling type it typed, the drop walk refuses at the outermost type's definition instead of answering inert, and the walk over what a type holds answers that it may hold anything past the limit. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 8 ++++- ts/fixtures/reject/expected.txt | 8 ++++- ts/fixtures/reject/src/values.rs | 53 ++++++++++++++++++++++++++++++++ ts/fixtures/reject/ts.toml | 2 ++ ts/src/lower/defined.rs | 24 +++++++++++---- ts/src/lower/drop.rs | 27 ++++++++++++---- ts/src/lower/mir/mod.rs | 15 +++++++-- ts/src/lower/mod.rs | 8 +++++ 8 files changed, 128 insertions(+), 17 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index a28e5abfa..b787cbec1 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -267,6 +267,7 @@ fixtures/reject/src/strs.rs:str_from_bytes_mut+1:20: `std::str::from_utf8_mut` w fixtures/reject/src/values.rs:Beside+0:1: union `values::Beside`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:Bits+0:1: union `values::Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` +fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit fixtures/reject/src/values.rs:Mixed+0:1: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end @@ -289,6 +290,11 @@ fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to `` fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` +fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_fork+1:23: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end @@ -301,7 +307,7 @@ fixtures/reject/src/values.rs:projected_self+1:27: `values::Held fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values name ever deeper instances, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index ca5439425..cf4ac0f69 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -266,6 +266,7 @@ fixtures/reject/src/strs.rs:str_bytes_mut+1:13: unsupported call to `core::str:: 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 fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` +fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate @@ -289,6 +290,11 @@ fixtures/reject/src/values.rs:manual_impls+2:34: a constant of type `std::mem::M fixtures/reject/src/values.rs:mixed_byte+0:19: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` +fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_fork+1:23: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end @@ -301,7 +307,7 @@ fixtures/reject/src/values.rs:projected_self+1:27: `values::Held fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values name ever deeper instances, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 56fb5eb28..173769e82 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -194,6 +194,59 @@ pub fn projected_spread(deep: bool) -> bool { spread.next.is_none() && deep } +/// What a duo's ping and pong both are at a wrapped duo: the same type at one wrapped again. +pub struct Fork { + pub ping: Option<&'static D::Ping>, + pub pong: Option<&'static D::Pong>, +} + +impl Duo for Wrapped { + type Ping = Fork>>; + type Pong = Fork>>; +} + +/// `Fork>` names ever deeper instances twice over at each level. +pub fn projected_fork(deep: bool) -> Option<&'static Fork>> { + deep.then_some(None).flatten() +} + +/// The end of a [`Links`], which drops. +pub struct Loud; + +impl Drop for Loud { + fn drop(&mut self) {} +} + +/// The source past the last link. +pub struct Zero; + +impl Source for Zero { + type Item = &'static Loud; +} + +/// One link more than its source. +pub struct Succ(pub T); + +impl Source for Succ { + type Item = &'static Links; +} + +/// A link lending the next, down to a [`Loud`]. +pub struct Links { + pub item: S::Item, +} + +/// Ten links more. +type Ten = Succ>>>>>>>>>; + +/// A chain of 140 links, which ends, but past the recursion limit. +pub type Deep = Ten>>>>>>>>>>>>>; + +/// Dropping a shared chain drops none of what it lends. +pub fn projected_deep(chain: std::rc::Rc>) { + drop(chain); +} + /// Two types a source names, each holding the other. pub trait Duo { type Ping; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index c1ef13256..052fdff5d 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -209,7 +209,9 @@ roots = [ "values::mixed_byte", "values::nested", "values::projected_chain", + "values::projected_deep", "values::projected_disposable", + "values::projected_fork", "values::projected_inert", "values::projected_nested", "values::projected_rally", diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index a21d1b91c..c03193e5f 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -64,8 +64,11 @@ pub struct Defining<'tcx> { /// The TypeScript type of each instance typed in that type expression, which one span /// refuses alike wherever in it, so a deep one is typed once. pub(super) instanced: rustc_data_structures::fx::FxHashMap, Type>, + /// Whether that type expression nested past the crate's recursion limit, which is + /// refused, so nothing else in it is typed. + pub(super) too_deep: bool, /// The instances whose drop glue is being classified, by what it drops through. - pub(super) glue_open: rustc_data_structures::fx::FxHashSet>, + pub(super) glue_open: Vec>, } struct Typing { @@ -241,15 +244,21 @@ impl<'tcx> Translator<'_, 'tcx> { /// arguments would never end, which is refused. pub(super) fn enter_instance(&mut self, instance: Ty<'tcx>, span: Span) -> bool { let instancing = &self.defining_state.instancing; + if self.defining_state.too_deep { + return false; + } let msg = match () { _ if instancing.contains(&instance) => format!( "`{instance}` is not supported yet: a projection of its parameters is a type whose arguments hold `{instance}` again, so its TypeScript type arguments would never end" ), // Named by the outermost, which the source wrote. - _ if instancing.len() > self.tcx.recursion_limit().0 => format!( - "`{}` is not supported: its projections' values name ever deeper instances, so its TypeScript type arguments would never end", - instancing[0] - ), + _ if instancing.len() > self.tcx.recursion_limit().0 => { + self.defining_state.too_deep = true; + format!( + "`{}` is not supported: its projections' values nest instances past the crate's recursion limit", + instancing[0] + ) + } _ => { self.defining_state.instancing.push(instance); return true; @@ -264,7 +273,10 @@ impl<'tcx> Translator<'_, 'tcx> { let state = &mut self.defining_state; state.instancing.pop(); match (state.instancing.is_empty(), ty) { - (true, _) => state.instanced.clear(), + (true, _) => { + state.instanced.clear(); + state.too_deep = false; + } (false, Some(ty)) => { state.instanced.insert(instance, ty.clone()); } diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index bf6e93121..2a6d25a95 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -226,9 +226,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } /// Whether `f` holds of everything [`Self::glue_held`] has `ty`, an instance of `adt` with - /// `args`, drop through. It holds of an instance already being asked about, or past the - /// crate's recursion limit, which only a projection whose value holds the instance again - /// or ever deeper ones reaches, and which typing the instance refuses. + /// `args`, drop through. It holds of an instance already being asked about, which only a + /// projection whose value holds the instance again reaches, and which typing the + /// instance refuses. What nests past the crate's recursion limit is refused. fn glue_all( &mut self, ty: Ty<'tcx>, @@ -236,12 +236,27 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { args: ty::GenericArgsRef<'tcx>, f: impl Fn(&mut Self, Ty<'tcx>) -> bool, ) -> bool { - let open = &mut self.defining_state.glue_open; - if open.len() > self.tcx.recursion_limit().0 || !open.insert(ty) { + if self.defining_state.glue_open.len() > self.tcx.recursion_limit().0 { + // At the definition of the outermost, which it names. + let outer = self.defining_state.glue_open[0]; + let span = match outer.kind() { + ty::Adt(outer, _) => self.tcx.def_span(outer.did()), + _ => self.tcx.def_span(adt.did()), + }; + self.err( + span, + format!( + "dropping `{outer}` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit" + ), + ); + return false; + } + if self.defining_state.glue_open.contains(&ty) { return true; } + self.defining_state.glue_open.push(ty); let all = self.glue_held(adt, args).into_iter().all(|held| f(self, held)); - self.defining_state.glue_open.remove(&ty); + self.defining_state.glue_open.pop(); all } diff --git a/ts/src/lower/mir/mod.rs b/ts/src/lower/mir/mod.rs index 8af131767..67d4e50cf 100644 --- a/ts/src/lower/mir/mod.rs +++ b/ts/src/lower/mir/mod.rs @@ -3413,10 +3413,13 @@ impl<'tcx> Translator<'_, 'tcx> { /// Whether `f` holds for any type a value of `ty` holds: its type arguments, which are /// what std's types hold, and the fields of the crate's own types, normalized in `env`. + /// Fields nested past the crate's recursion limit, as a type whose fields hold it at + /// ever deeper arguments nests them, may hold anything. pub(crate) fn reaches(&self, env: TypingEnv<'tcx>, ty: Ty<'tcx>, mut f: impl FnMut(Ty<'tcx>) -> bool) -> bool { + let limit = self.tcx.recursion_limit().0; let mut seen = FxHashSet::default(); - let mut stack = vec![ty]; - while let Some(ty) = stack.pop() { + let mut stack = vec![(ty, 0)]; + while let Some((ty, depth)) = stack.pop() { for t in ty.walk().filter_map(|arg| arg.as_type()) { if f(t) { return true; @@ -3425,7 +3428,13 @@ impl<'tcx> Translator<'_, 'tcx> { && self.own(adt.did()) && seen.insert(t) { - stack.extend(adt.all_fields().map(|field| field_ty(self.tcx, env, field.did, args))); + if depth >= limit { + return true; + } + stack.extend( + adt.all_fields() + .map(|field| (field_ty(self.tcx, env, field.did, args), depth + 1)), + ); } } } diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index f1a20be0d..294a2be72 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1102,9 +1102,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // A named type, so an instance its fields hold again ends there. let instancing = std::mem::take(&mut self.defining_state.instancing); let instanced = std::mem::take(&mut self.defining_state.instanced); + let too_deep = std::mem::take(&mut self.defining_state.too_deep); let info = self.define(adt, span); (self.held, self.glue, self.defining, self.defining_state.field) = (held, glue, defining, field); (self.defining_state.instancing, self.defining_state.instanced) = (instancing, instanced); + self.defining_state.too_deep = too_deep; info } @@ -1927,6 +1929,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.err(span, msg); } tys.extend(self.projection_args(adt.did(), args, span).into_iter().map(|(_, t)| t)); + // Past the recursion limit, refused already: what it typed is not kept, as each + // projection may double it. + if self.defining_state.too_deep { + self.leave_instance(instance, None); + return Type::Builtin("unknown"); + } let ty = Type::Sym(info.sym, tys); self.leave_instance(instance, Some(&ty)); ty From a09b8391d3749fa6432242aa86870e735a9785d2 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:14:39 -0800 Subject: [PATCH 09/21] feat(ts): name instances a projection argument holds with an alias An instance without type parameters that a projection argument holds is named by an exported alias of its own, typed once, so a type whose two projections each name the next instance grows with the instances rather than the paths through them, and one whose projection holds it again (`Source::Item = Held`) names its alias instead of being refused. An enum's alias spells out its union, which TypeScript needs for an alias that names itself. Drop-glue classification keeps each answer once it no longer rests on an instance still being asked about. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 10 +- .../lang/shapes/expected/core/option.ts | 59 ++++++ .../lang/shapes/expected/generics.Carried.ts | 28 +++ .../lang/shapes/expected/generics.Chain.ts | 23 ++ .../lang/shapes/expected/generics.Depth.ts | 7 + .../lang/shapes/expected/generics.Forked.ts | 99 +++++++++ .../lang/shapes/expected/generics.Linked.ts | 7 + .../lang/shapes/expected/generics.Looped.ts | 7 + .../lang/shapes/expected/generics.Ping.ts | 30 +++ .../lang/shapes/expected/generics.Pong.ts | 13 ++ .../lang/shapes/expected/generics.Rally.ts | 7 + ts/fixtures/lang/shapes/expected/generics.ts | 96 ++++++++- ts/fixtures/lang/shapes/expected/lib.ts | 4 +- ts/fixtures/lang/shapes/src/generics.rs | 199 ++++++++++++++++++ ts/fixtures/lang/shapes/src/lib.rs | 3 + ts/fixtures/reject/census.txt | 11 +- ts/fixtures/reject/expected.txt | 11 +- ts/fixtures/reject/src/values.rs | 79 +++---- ts/fixtures/reject/ts.toml | 3 +- ts/src/lower/defined.rs | 114 +++++++++- ts/src/lower/drop.rs | 75 +++++-- ts/src/lower/mod.rs | 37 +++- ts/src/lower/names.rs | 2 +- 23 files changed, 817 insertions(+), 107 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Carried.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Chain.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Depth.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Forked.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Linked.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Looped.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Ping.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Pong.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Rally.ts diff --git a/ts/README.md b/ts/README.md index 5c1b89071..645ff14be 100644 --- a/ts/README.md +++ b/ts/README.md @@ -484,8 +484,12 @@ but not their const ones, which type nothing in TS: arrayvec's `ArrayString<4>` through a generic type of the crate's they hold, or as a std iterator's item, is a type parameter of its own after them, named for its associated type and numbered past any it would shadow, which each instance gives what the projection is there: -`Drain<[u8; 4]>` is a `Drain`. Types typed with each other, as a tree -and its branches are, each take the projections either names. So a function of a generic type is one instance's, declaring the instance it +`Drain<[u8; 4]>` is a `Drain`. An instance a projection argument +holds is named by an exported alias of its own, `type Held$Src = Held`, so +one that holds itself again ends, and the output grows with the instances rather than +the paths through them; an enum's alias spells out its union at the instance, as +TypeScript refuses an alias naming itself through a generic one. Types typed with each +other, as a tree and its branches are, each take the projections either names. So a function of a generic type is one instance's, declaring the instance it takes as `self`: `fmt$fmt_Debug_fmt_Pair(self: Wrap, f: rs.Formatter)`. A reference or pointer in a generic type to what only an instance decides the form of, such as `*mut T` or @@ -1144,7 +1148,7 @@ function without a mapping of its own that makes or reads an `Option` whose `Non `Option`, an instance of a generic type whose field is an `Option` of a type parameter, or of a projection from one, that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, an instance of a generic type -whose projection is a type holding that instance again (`Held` where `Src::Item` is a `Held`), a generic type that +over type parameters whose projection is a type holding that instance again, in a generic type's definition (`Held>` where `Wrap::Item` is a `Held>`), a generic type that holds itself at other arguments than its own parameters whose projections there are new ones each time, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS copies that std makes, other than a map's, or hands a closure, as `iter_mut` and diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index df445ca7b..67ad47f7f 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -1,6 +1,10 @@ // Generated by rust2-ts. Do not edit. import * as rs from "../rs.ts"; +import type * as Carried from "../generics.Carried.ts"; +import type * as Depth from "../generics.Depth.ts"; +import type * as Forked from "../generics.Forked.ts"; +import type * as Looped from "../generics.Looped.ts"; import type * as Ticked from "../generics.Ticked.ts"; import { from$convert_From_shapes_Dir_u8 } from "../shapes.ts"; import type * as Dir from "../shapes.Dir.ts"; @@ -43,6 +47,41 @@ export function unwrapOrDefault$03apme8(self: rs.u8 | undefined): rs.u8 { return self; } +export function map$00jdj9f(self: Carried.Carried | undefined, _: (a0: Carried.Carried) => Carried.Carried): Carried.Carried | undefined { + if (self === undefined) { + return undefined; + } + return self; +} + +export function map$1ug09xc(self: Forked.Forked | undefined, f: (a0: Forked.Forked) => rs.u8): rs.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$1747o3c(self: Forked.Forked | undefined, f: (a0: Forked.Forked) => rs.u8): rs.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$1mvacy3(self: Forked.Forked | undefined, f: (a0: Forked.Forked) => [rs.u8, rs.u8 | undefined, rs.u8 | undefined]): [rs.u8, rs.u8 | undefined, rs.u8 | undefined] | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$1ub01y7(self: Ticked.Ticked | undefined, f: (a0: Ticked.Ticked) => rs.u8): rs.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0ggarad(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.Vec): rs.Vec | undefined { if (self === undefined) { return undefined; @@ -57,6 +96,20 @@ export function map$132wgz4(self: rs.u8 | undefined, f: (a0: rs.u8) => boolean): return f(self); } +export function map$1olalbb(self: rs.u8 | undefined, f: (a0: rs.u8) => Forked.Forked): Forked.Forked | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + +export function map$0xpm57l(self: rs.u8 | undefined, f: (a0: rs.u8) => Ticked.Ticked): Ticked.Ticked | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0ydco3f(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u16): rs.u16 | undefined { if (self === undefined) { return undefined; @@ -142,6 +195,12 @@ export function okOr$1j8owgb(self: rs.u8 | undefined, err: rs.u8): rs.Result | undefined): Carried.Carried | undefined { + return map$00jdj9f(self, (a0: Carried.Carried): Carried.Carried => { + return a0; + }); +} + export function andThen$0jc4o0i(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u8 | undefined | rs.None): rs.u8 | undefined | rs.None { if (self === undefined) { return rs.None; diff --git a/ts/fixtures/lang/shapes/expected/generics.Carried.ts b/ts/fixtures/lang/shapes/expected/generics.Carried.ts new file mode 100644 index 000000000..7af6402ca --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Carried.ts @@ -0,0 +1,28 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Looped from "./generics.Looped.ts"; +import type * as Ticked from "./generics.Ticked.ts"; + +/** + * What a source holds next, which TypeScript names through an alias where the item is it + * again. + */ +export class Carried<_S, Item> { + tick: Ticked.Ticked; + next: rs.OptionOf; + + constructor(tick: Ticked.Ticked, next: rs.OptionOf) { + this.tick = tick; + this.next = next; + } + + [Symbol.dispose](): void { + this.tick[Symbol.dispose](); + rs.drop(this.next); + } +} + +export type Carried$generics_Looped = Carried; + +//# sourceMappingURL=generics.Carried.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Chain.ts b/ts/fixtures/lang/shapes/expected/generics.Chain.ts new file mode 100644 index 000000000..b0b41cbd0 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Chain.ts @@ -0,0 +1,23 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Linked from "./generics.Linked.ts"; + +/** + * An enum of what a source holds next, whose alias at an instance whose items are it again + * spells out its union. + */ +export type Chain<_S, Item> = { tag: "End" } | { tag: "Link"; _0: rs.u8; _1: Item }; + +export type Chain$generics_Linked = + | { tag: "End" } + | { tag: "Link"; _0: rs.u8; _1: Chain$generics_Linked }; + +export function ids$generics_Linked(self: Chain, out: rs.ByteVec): void { + if (self.tag === "Link") { + rs.byteVecPush(out, self._0); + ids$generics_Linked(self._1, out); + } +} + +//# sourceMappingURL=generics.Chain.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Depth.ts b/ts/fixtures/lang/shapes/expected/generics.Depth.ts new file mode 100644 index 000000000..7327949b2 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Depth.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The source at a depth. */ +export interface Depth { +} + +//# sourceMappingURL=generics.Depth.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Forked.ts b/ts/fixtures/lang/shapes/expected/generics.Forked.ts new file mode 100644 index 000000000..e5bea62d5 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Forked.ts @@ -0,0 +1,99 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Depth from "./generics.Depth.ts"; +import type * as Ticked from "./generics.Ticked.ts"; + +/** A fork of what its source's projections both are. */ +export class Forked<_F, Left, Right> { + id: rs.u8; + left: rs.OptionOf; + right: rs.OptionOf; + + constructor(id: rs.u8, left: rs.OptionOf, right: rs.OptionOf) { + this.id = id; + this.left = left; + this.right = right; + } + + [Symbol.dispose](): void { + rs.drop(this.left); + rs.drop(this.right); + } +} + +export type Forked$generics_Depth_0 = + | Forked; + +export type Forked$generics_Depth_1 = + | Forked; + +export type Forked$generics_Depth_10 = + | Forked; + +export type Forked$generics_Depth_11 = + | Forked; + +export type Forked$generics_Depth_12 = + | Forked; + +export type Forked$generics_Depth_13 = + | Forked; + +export type Forked$generics_Depth_14 = + | Forked; + +export type Forked$generics_Depth_15 = + | Forked; + +export type Forked$generics_Depth_16 = + | Forked; + +export type Forked$generics_Depth_17 = + | Forked; + +export type Forked$generics_Depth_18 = + | Forked; + +export type Forked$generics_Depth_19 = + | Forked; + +export type Forked$generics_Depth_2 = + | Forked; + +export type Forked$generics_Depth_20 = + | Forked; + +export type Forked$generics_Depth_21 = + | Forked; + +export type Forked$generics_Depth_22 = + | Forked; + +export type Forked$generics_Depth_23 = + | Forked; + +export type Forked$generics_Depth_24 = Forked; + +export type Forked$generics_Depth_3 = + | Forked; + +export type Forked$generics_Depth_4 = + | Forked; + +export type Forked$generics_Depth_5 = + | Forked; + +export type Forked$generics_Depth_6 = + | Forked; + +export type Forked$generics_Depth_7 = + | Forked; + +export type Forked$generics_Depth_8 = + | Forked; + +export type Forked$generics_Depth_9 = + | Forked; + +//# sourceMappingURL=generics.Forked.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Linked.ts b/ts/fixtures/lang/shapes/expected/generics.Linked.ts new file mode 100644 index 000000000..1afc6804b --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Linked.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A source whose items chain it again. */ +export interface Linked { +} + +//# sourceMappingURL=generics.Linked.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Looped.ts b/ts/fixtures/lang/shapes/expected/generics.Looped.ts new file mode 100644 index 000000000..76ccd257b --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Looped.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A source whose items hold it again. */ +export interface Looped { +} + +//# sourceMappingURL=generics.Looped.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Ping.ts b/ts/fixtures/lang/shapes/expected/generics.Ping.ts new file mode 100644 index 000000000..23c1e3406 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Ping.ts @@ -0,0 +1,30 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Pong from "./generics.Pong.ts"; +import type * as Rally from "./generics.Rally.ts"; + +/** What holds a [`Duo`]'s pong. */ +export class Ping<_D, Pong> { + id: rs.u8; + pong: rs.OptionOf; + + constructor(id: rs.u8, pong: rs.OptionOf) { + this.id = id; + this.pong = pong; + } +} + +export type Ping$generics_Rally = Ping; + +export function ids$generics_Rally(self: Ping, out: rs.ByteVec): void { + rs.byteVecPush(out, self.id); + if (self.pong !== undefined) { + if (self.pong.tag === "Hit") { + rs.byteVecPush(out, self.pong._0); + ids$generics_Rally(self.pong._1, out); + } + } +} + +//# sourceMappingURL=generics.Ping.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Pong.ts b/ts/fixtures/lang/shapes/expected/generics.Pong.ts new file mode 100644 index 000000000..ca3cf134b --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Pong.ts @@ -0,0 +1,13 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Ping from "./generics.Ping.ts"; + +/** What holds a [`Duo`]'s ping, or misses. */ +export type Pong<_D, Ping> = { tag: "Miss" } | { tag: "Hit"; _0: rs.u8; _1: Ping }; + +export type Pong$generics_Rally = + | { tag: "Miss" } + | { tag: "Hit"; _0: rs.u8; _1: Ping.Ping$generics_Rally }; + +//# sourceMappingURL=generics.Pong.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Rally.ts b/ts/fixtures/lang/shapes/expected/generics.Rally.ts new file mode 100644 index 000000000..86ee5b8df --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Rally.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** A duo of its own ping and pong. */ +export interface Rally { +} + +//# sourceMappingURL=generics.Rally.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 7baa08738..863b1caee 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -3,27 +3,36 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; import { thenSome$1ctlqen, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e } from "./core/option.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1ub01y7 as option_map$1ub01y7, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; import * as Batch from "./generics.Batch.ts"; import * as Branch from "./generics.Branch.ts"; +import * as Carried from "./generics.Carried.ts"; +import * as Chain from "./generics.Chain.ts"; import * as Deep from "./generics.Deep.ts"; +import type * as Depth from "./generics.Depth.ts"; import * as Drained from "./generics.Drained.ts"; import * as Either from "./generics.Either.ts"; import * as Even from "./generics.Even.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Flags from "./generics.Flags.ts"; +import * as Forked from "./generics.Forked.ts"; import * as Ghost from "./generics.Ghost.ts"; import * as Kept from "./generics.Kept.ts"; import type * as Lends from "./generics.Lends.ts"; import type * as Lent from "./generics.Lent.ts"; +import type * as Linked from "./generics.Linked.ts"; +import type * as Looped from "./generics.Looped.ts"; import type * as Marked from "./generics.Marked.ts"; import * as Odd from "./generics.Odd.ts"; import * as Parked from "./generics.Parked.ts"; import type * as Paths from "./generics.Paths.ts"; import * as Pile from "./generics.Pile.ts"; +import * as Ping from "./generics.Ping.ts"; +import type * as Pong from "./generics.Pong.ts"; +import type * as Rally from "./generics.Rally.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; import * as Swap from "./generics.Swap.ts"; @@ -709,4 +718,89 @@ export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtOption(arg, f, rs.fmtUnit, rs.None)]).v); } +/** + * Types whose projections hold their instances again, directly or through each other, as + * structs and enums, and a type whose two projections each name the next of 25 instances, + * the last holding the first again, which an alias per instance names once rather than + * once per path. + */ +export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { + let _73!: Pong.Pong; + const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); + const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { + return new Ticked.Ticked(id, rs.arcClone(log)); + }; + let held: Carried.Carried | undefined = undefined; + for (const _11 of rs.byteSliceView(data)) { + held = new Carried.Carried(tick(_11), option_map$00jdj9f(held, (a0: Carried.Carried): Carried.Carried => { + return a0; + }) as rs.OptionOf); + } + let depth: rs.u8 = 0; + let at: Carried.Carried | undefined = held; + while (at !== undefined) { + const next: Carried.Carried = at; + depth = rs.u8Add(depth, 1); + at = option_asDeref$10lhnlx(next.next); + } + const _33: rs.RefCell = rs.refCellBorrowMut(log.value); + rs.byteVecPush(_33.value, depth); + rs.refMutRelease(_33); + rs.drop(held); + const chain: Chain.Chain = rs.iterFold(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), { tag: "End" } as Chain.Chain, (next2: Chain.Chain, _32: rs.u8): Chain.Chain => { + return { tag: "Link", _0: _32, _1: next2 } as Chain.Chain; + }); + const ids: rs.ByteVec = new rs.ByteVec(); + Chain.ids$generics_Linked(chain, ids); + put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids), f, rs.u8Debug)]).v); + let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); + const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); + while (true) { + const _65: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_65 === rs.DONE) { + break; + } + const i: rs.usize = _65[0]; + const b2: rs.u8 = _65[1]; + if (rs.usizeRem(i, 3) === 0) { + _73 = { tag: "Miss" } as Pong.Pong; + } else { + _73 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; + } + ping = new Ping.Ping(b2, _73 as rs.OptionOf); + } + const ids2: rs.ByteVec = new rs.ByteVec(); + Ping.ids$generics_Rally(ping, ids2); + put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug)]).v); + const below: Forked.Forked = new Forked.Forked(23, undefined as rs.OptionOf, new Forked.Forked(24, option_map$1olalbb(rs.byteSliceFirst(data), (_2: rs.u8): Forked.Forked => { + return new Forked.Forked(_2, undefined as rs.OptionOf, undefined as rs.OptionOf); + }) as rs.OptionOf, option_map$0xpm57l(rs.byteSliceLast(data), (_22: rs.u8): Ticked.Ticked => { + return tick(~_22 & 0xff); + }) as rs.OptionOf) as rs.OptionOf); + const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); + if (rs.usizeRem(data.length, 2) === 0) { + const _113: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + rs.drop(fork.left); + fork.left = _113; + } + const ids3: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { + return f.id; + }), below.id]; + const right: [rs.u8, rs.u8 | undefined, rs.u8 | undefined] | undefined = option_map$1mvacy3(below.right, (f2: Forked.Forked): [rs.u8, rs.u8 | undefined, rs.u8 | undefined] => { + return [f2.id, option_map$1ug09xc(f2.left, (f3: Forked.Forked): rs.u8 => { + return f3.id; + }), option_map$1ub01y7(f2.right, (t: Ticked.Ticked): rs.u8 => { + return t.id; + })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; + }); + const args9 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids3[0], f), (f: rs.Formatter) => rs.fmtOption(ids3[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids3[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args9[0], " ", args9[1]]).v); + below[Symbol.dispose](); + fork[Symbol.dispose](); + const _149: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_149.value))), undefined, true); + rs.refRelease(_149); + log[Symbol.dispose](); +} + //# sourceMappingURL=generics.ts.map diff --git a/ts/fixtures/lang/shapes/expected/lib.ts b/ts/fixtures/lang/shapes/expected/lib.ts index 1ffea0768..8903a3bd4 100644 --- a/ts/fixtures/lang/shapes/expected/lib.ts +++ b/ts/fixtures/lang/shapes/expected/lib.ts @@ -2,7 +2,7 @@ import * as rs from "./rs.ts"; import { ints, strings, derived, written, counts, rounding, panics, wrapped, collections, enums, iters, adaptors, writes, objects, sources, sets, calls, cells, searches } from "./fmt.ts"; -import { methods, runs, units, projections, projectedCycles } from "./generics.ts"; +import { methods, runs, units, projections, projectedCycles, projectedSelves } from "./generics.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; import type * as Pile from "./generics.Pile.ts"; @@ -237,6 +237,8 @@ export function cases(): rs.Vec<[string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.By projections(data29, out30); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::projected_cycles", (_240: rs.u8, data30: rs.ByteSlice, out31: rs.ByteVec): undefined => { projectedCycles(data30, out31); + }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined], ["generics::projected_selves", (_241: rs.u8, data31: rs.ByteSlice, out32: rs.ByteVec): undefined => { + projectedSelves(data31, out32); }] as [string, (a0: rs.u8, a1: rs.ByteSlice, a2: rs.ByteVec) => undefined]]); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index f6b750bf9..f99b353ba 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -904,3 +904,202 @@ pub fn projected_cycles(data: &[u8], out: &mut Vec) { }; put(out, &format!("{:?}", ghost.item)); } + +/// What a source yields, named by an associated type. +trait Source { + type Item; +} + +/// What a source holds next, which TypeScript names through an alias where the item is it +/// again. +struct Carried { + tick: Ticked, + next: Option>, +} + +/// A source whose items hold it again. +struct Looped; + +impl Source for Looped { + type Item = Carried; +} + +/// An enum of what a source holds next, whose alias at an instance whose items are it again +/// spells out its union. +enum Chain { + End, + Link(u8, Box), +} + +/// A source whose items chain it again. +struct Linked; + +impl Source for Linked { + type Item = Chain; +} + +impl Chain { + fn ids(&self, out: &mut Vec) { + if let Chain::Link(id, next) = self { + out.push(*id); + next.ids(out); + } + } +} + +/// Two types a source names, each holding the other. +trait Duo { + type Ping; + type Pong; +} + +/// What holds a [`Duo`]'s pong. +struct Ping { + id: u8, + pong: Option>, +} + +/// What holds a [`Duo`]'s ping, or misses. +enum Pong { + Miss, + Hit(u8, Box), +} + +/// A duo of its own ping and pong. +struct Rally; + +impl Duo for Rally { + type Ping = Ping; + type Pong = Pong; +} + +impl Ping { + fn ids(&self, out: &mut Vec) { + out.push(self.id); + if let Some(pong) = &self.pong + && let Pong::Hit(id, ping) = &**pong + { + out.push(*id); + ping.ids(out); + } + } +} + +/// Two projections, each naming the next instance down to the last. +trait Fork { + type Left; + type Right; +} + +/// A fork of what its source's projections both are. +struct Forked { + id: u8, + left: Option>, + right: Option>, +} + +/// The source at a depth. +struct Depth; + +/// Each depth's projections, the next depth's [`Forked`]. +macro_rules! forks { + ($($depth:literal => $next:literal),* $(,)?) => {$( + impl Fork for Depth<$depth> { + type Left = Forked>; + type Right = Forked>; + } + )*}; +} + +forks!( + 0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 5, 5 => 6, 6 => 7, 7 => 8, 8 => 9, 9 => 10, 10 => 11, + 11 => 12, 12 => 13, 13 => 14, 14 => 15, 15 => 16, 16 => 17, 17 => 18, 18 => 19, 19 => 20, + 20 => 21, 21 => 22, 22 => 23, 23 => 24, +); + +/// The last depth, whose left is the first's fork again and whose right drops. +impl Fork for Depth<24> { + type Left = Forked>; + type Right = Ticked; +} + +/// Types whose projections hold their instances again, directly or through each other, as +/// structs and enums, and a type whose two projections each name the next of 25 instances, +/// the last holding the first again, which an alias per instance names once rather than +/// once per path. +pub fn projected_selves(data: &[u8], out: &mut Vec) { + let log = Rc::new(RefCell::new(Vec::new())); + let tick = |id: u8| Ticked { id, log: log.clone() }; + let mut held = None::>; + for &b in data { + held = Some(Carried { + tick: tick(b), + next: held.map(Box::new), + }); + } + let mut depth = 0; + let mut at = held.as_ref(); + while let Some(next) = at { + depth += 1; + at = next.next.as_deref(); + } + log.borrow_mut().push(depth); + drop(held); + + let chain = data + .iter() + .rev() + .fold(Chain::::End, |next, &b| Chain::Link(b, Box::new(next))); + let mut ids = Vec::new(); + chain.ids(&mut ids); + put(out, &format!("{ids:?}")); + + let mut ping = Ping:: { id: 0xee, pong: None }; + for (i, &b) in data.iter().enumerate() { + ping = Ping { + id: b, + pong: Some(Box::new(match i % 3 { + 0 => Pong::Miss, + _ => Pong::Hit(!b, Box::new(ping)), + })), + }; + } + let mut ids = Vec::new(); + ping.ids(&mut ids); + put(out, &format!("{ids:?}")); + + let last = Forked::> { + id: 24, + left: data.first().map(|&b| { + Box::new(Forked { + id: b, + left: None, + right: None, + }) + }), + right: data.last().map(|&b| Box::new(tick(!b))), + }; + let below = Forked::> { + id: 23, + left: None, + right: Some(Box::new(last)), + }; + let mut fork = Forked::> { + id: 0, + left: None, + right: None, + }; + if data.len() % 2 == 0 { + fork.left = Some(Box::new(Forked { + id: 1, + left: None, + right: None, + })); + } + let ids = (fork.id, fork.left.as_ref().map(|f| f.id), below.id); + let right = (below.right.as_ref()).map(|f| (f.id, f.left.as_ref().map(|f| f.id), f.right.as_ref().map(|t| t.id))); + put(out, &format!("{ids:?} {right:?}")); + drop(below); + drop(fork); + out.extend(log.borrow().iter()); +} diff --git a/ts/fixtures/lang/shapes/src/lib.rs b/ts/fixtures/lang/shapes/src/lib.rs index 264263e42..d76df0b96 100644 --- a/ts/fixtures/lang/shapes/src/lib.rs +++ b/ts/fixtures/lang/shapes/src/lib.rs @@ -60,5 +60,8 @@ fn cases() -> Vec<(&'static str, Case)> { ("generics::projected_cycles", |_, data, out| { generics::projected_cycles(data, out) }), + ("generics::projected_selves", |_, data, out| { + generics::projected_selves(data, out) + }), ] } diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 98cb6928b..46e044d0b 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -267,6 +267,7 @@ fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not sup fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit fixtures/reject/src/values.rs:Mixed+0:1: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate @@ -294,16 +295,6 @@ fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index b66bbc9b1..70296adc4 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -260,6 +260,7 @@ fixtures/reject/src/strs.rs:str_from_bytes_mut+1:20: `std::str::from_utf8_mut` w fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate @@ -289,16 +290,6 @@ fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_rally+1:6: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+1:26: `values::Ping` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Ping` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:14: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_rally+2:21: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_rally+2:31: `values::Pong` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Pong` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:6: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+1:27: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:14: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end, in `core::bool::::then` -fixtures/reject/src/values.rs:projected_self+2:21: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:projected_self+2:31: `values::Held` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Held` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 173769e82..5abb5e514 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -110,27 +110,6 @@ pub trait Source { type Item; } -/// What a source holds next. -pub struct Held { - pub next: Option>, -} - -/// A source whose items hold it again. -pub struct Looped; - -impl Source for Looped { - type Item = Held; -} - -/// `Held`'s projection argument is `Held` itself, which TypeScript can name -/// only through a type alias of its own. -pub fn projected_self(deep: bool) -> bool { - let held: Held = Held { - next: deep.then(|| Box::new(Held { next: None })), - }; - held.next.is_some() -} - /// What a source lends next, which drops nothing itself. pub struct Lent { pub next: Option<&'static S::Item>, @@ -151,6 +130,26 @@ pub fn projected_inert(deep: bool) -> bool { deep } +/// A source wrapped once, whose items lend it again at any `T`. +pub struct Relent(pub T); + +impl Source for Relent { + type Item = Lent>; +} + +/// A generic type holding an instance over its own parameter whose projection holds that +/// instance again, which no alias names, as an alias names only instances without type +/// parameters. +pub struct Over { + pub held: Option<&'static Lent>>, +} + +/// `Over`'s definition would hold `Lent>` without end. +pub fn projected_over(deep: bool) -> bool { + let over = Over:: { held: None }; + over.held.is_none() && deep +} + /// What a source lends next, beside what it owns, so it drops. pub struct Owning { pub next: Option<&'static S::Item>, @@ -194,6 +193,12 @@ pub fn projected_spread(deep: bool) -> bool { spread.next.is_none() && deep } +/// Two types a source names. +pub trait Duo { + type Ping; + type Pong; +} + /// What a duo's ping and pong both are at a wrapped duo: the same type at one wrapped again. pub struct Fork { pub ping: Option<&'static D::Ping>, @@ -247,38 +252,6 @@ pub fn projected_deep(chain: std::rc::Rc>) { drop(chain); } -/// Two types a source names, each holding the other. -pub trait Duo { - type Ping; - type Pong; -} - -/// What holds a [`Duo`]'s pong. -pub struct Ping { - pub pong: Option>, -} - -/// What holds a [`Duo`]'s ping. -pub struct Pong { - pub ping: Option>, -} - -/// A duo of its own ping and pong. -pub struct Rally; - -impl Duo for Rally { - type Ping = Ping; - type Pong = Pong; -} - -/// `Ping`'s projection argument is `Pong`, whose own is `Ping` again. -pub fn projected_rally(deep: bool) -> bool { - let ping: Ping = Ping { - pong: deep.then(|| Box::new(Pong { ping: None })), - }; - ping.pong.is_some() -} - /// A chain of types, each naming the next. pub trait Chain { type Item; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 87bcdfbb9..e3e113f4b 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -212,8 +212,7 @@ roots = [ "values::projected_fork", "values::projected_inert", "values::projected_nested", - "values::projected_rally", - "values::projected_self", + "values::projected_over", "values::projected_spread", "values::projected_uninit", "values::units_compared", diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index c03193e5f..5929d6783 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -3,7 +3,8 @@ //! //! A projection from a generic type's parameters that typing its fields names, such as //! smallvec's `::Item`, becomes a TypeScript type parameter of its own, which -//! each instance gives what the projection is there. A type typed with itself, directly or +//! each instance gives what the projection is there. An instance without type parameters +//! that a projection argument holds is named by an alias, typed once. A type typed with itself, directly or //! through others, names instances of a definition that is still being typed, whose //! projections are not all known yet: those instances' projection arguments are filled in //! once every definition typed with them is. @@ -12,8 +13,9 @@ use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, GenericArgsRef, Ty, TypeVisitableExt, TypingEnv}; use rustc_span::Span; -use super::Translator; -use crate::ts::{Item, Type}; +use super::{Adt, Translator}; +use crate::program::{SymKind, Symbol}; +use crate::ts::{Item, SymId, Type}; /// The account of one generic type's definition. #[derive(Default)] @@ -69,6 +71,21 @@ pub struct Defining<'tcx> { pub(super) too_deep: bool, /// The instances whose drop glue is being classified, by what it drops through. pub(super) glue_open: Vec>, + /// The shallowest of [`Self::glue_open`] the classification under way rested on. + pub(super) glue_rests: usize, + /// Each classification of an instance's glue, once it rests on none still under way. + pub(super) glue_known: rustc_data_structures::fx::FxHashMap<(Ty<'tcx>, super::drop::Asked), bool>, + /// Each that holds if the one under way it rested on, by its depth there, does. + pub(super) glue_resting: rustc_data_structures::fx::FxHashMap<(Ty<'tcx>, super::drop::Asked), usize>, + /// Those, in the order they were classified. + pub(super) glue_order: Vec<(Ty<'tcx>, super::drop::Asked)>, + /// How deep in projection arguments the type being typed is. + pub(super) projecting: usize, + /// The alias naming each instance without type parameters a projection argument held. + aliases: rustc_data_structures::fx::FxHashMap, SymId>, + /// The aliases of enum instances, by the enum, whose union each spells out at its + /// instance, as TypeScript refuses an alias that names itself through a generic alias. + enum_aliases: Vec<(SymId, SymId)>, } struct Typing { @@ -238,17 +255,98 @@ impl<'tcx> Translator<'_, 'tcx> { Some(Type::Later(state.pending.len() - 1)) } + /// Whether an instance of the crate's generic `adt` may name projections: it names some, + /// or its definition is still being typed. + pub(super) fn may_project(&self, adt: DefId) -> bool { + (self.defining_state.records.get(&adt)).is_some_and(|r| r.open.is_some() || !r.projections.is_empty()) + } + + /// The alias naming `instance`, an instance of the crate's generic type `info` without + /// type parameters, which a projection argument holds: `Held$Looped` for `Held`, + /// whose module exports `type Held$Looped = Held`. Defined the first + /// time, and named while it is, so an instance its own projections hold again names it. + pub(super) fn instance_alias(&mut self, instance: Ty<'tcx>, info: &Adt, span: Span) -> Type { + if let Some(&alias) = self.defining_state.aliases.get(&instance) { + return Type::Sym(alias, Vec::new()); + } + let tcx = self.tcx; + let ty::Adt(adt, args) = *instance.kind() else { + unreachable!("an instance of a generic type") + }; + let did = adt.did(); + let args = tcx.generics_of(did).own_args_no_defaults(tcx, args); + let alias = self.program.sym(Symbol { + module: self.type_module(did), + base: tcx.item_name(did).to_string(), + suffix: super::names::args(tcx, args).collect::>().join("_"), + owner: None, + kind: SymKind::Union, + order: self.order(tcx.def_span(did)), + span: tcx.def_span(did), + instance: String::new(), + name: String::new(), + alias: None, + }); + self.defining_state.aliases.insert(instance, alias); + // Typed as anywhere else, outside the projection argument. + let projecting = std::mem::take(&mut self.defining_state.projecting); + let ty = self.instance_ty(instance, info, span); + self.defining_state.projecting = projecting; + // Refused past the recursion limit, as each use of it is again. + if self.defining_state.too_deep { + self.defining_state.aliases.remove(&instance); + return Type::Builtin("unknown"); + } + if adt.is_enum() { + self.defining_state.enum_aliases.push((alias, info.sym)); + } + self.program.items.push(Item::Union { + sym: alias, + params: Vec::new(), + ty, + doc: None, + }); + Type::Sym(alias, Vec::new()) + } + + /// Spell out each enum instance's alias as the enum's union at the instance, once every + /// definition is complete. + pub(super) fn expand_enum_aliases(&mut self) { + let items: rustc_data_structures::fx::FxHashMap = (self.program.items.iter().enumerate()) + .filter_map(|(i, item)| match item { + Item::Union { sym, .. } => Some((*sym, i)), + _ => None, + }) + .collect(); + for (alias, adt) in std::mem::take(&mut self.defining_state.enum_aliases) { + let Item::Union { params, ty: union, .. } = &self.program.items[items[&adt]] else { + unreachable!("an enum's union") + }; + let Item::Union { + ty: Type::Sym(_, args), .. + } = &self.program.items[items[&alias]] + else { + unreachable!("an alias of an enum instance") + }; + let expanded = union.subst(&|name| params.iter().position(|p| p == name).map(|i| args[i].clone())); + let Item::Union { ty, .. } = &mut self.program.items[items[&alias]] else { + unreachable!("an alias") + }; + *ty = expanded; + } + } + /// Start typing `instance`, an instance of the crate's generic type, unless it is - /// typed already further out in the same type expression: a projection of its - /// parameters is then a type whose arguments hold it again, so its TypeScript type - /// arguments would never end, which is refused. + /// typed already further out in the same type expression over type parameters, which no + /// alias names: a projection of its parameters is then a type whose arguments hold it + /// again, so its TypeScript type arguments would never end, which is refused. pub(super) fn enter_instance(&mut self, instance: Ty<'tcx>, span: Span) -> bool { let instancing = &self.defining_state.instancing; if self.defining_state.too_deep { return false; } let msg = match () { - _ if instancing.contains(&instance) => format!( + _ if instance.has_param() && instancing.contains(&instance) => format!( "`{instance}` is not supported yet: a projection of its parameters is a type whose arguments hold `{instance}` again, so its TypeScript type arguments would never end" ), // Named by the outermost, which the source wrote. @@ -390,6 +488,7 @@ impl<'tcx> Translator<'_, 'tcx> { let before = (named(&self.defining_state), self.defining_state.pending.len()); let outer = std::mem::replace(&mut self.defining, at); let outer_field = std::mem::replace(&mut self.defining_state.field, field); + self.defining_state.projecting += 1; let mut tys: Vec = projections .iter() .map(|&p| { @@ -397,6 +496,7 @@ impl<'tcx> Translator<'_, 'tcx> { self.ty(at, span) }) .collect(); + self.defining_state.projecting -= 1; self.defining = outer; self.defining_state.field = outer_field; let after = (named(&self.defining_state), self.defining_state.pending.len()); diff --git a/ts/src/lower/drop.rs b/ts/src/lower/drop.rs index 2a6d25a95..6c8061636 100644 --- a/ts/src/lower/drop.rs +++ b/ts/src/lower/drop.rs @@ -15,6 +15,15 @@ use super::{Adt, AdtRepr, Translator}; use crate::program::{SymKind, Symbol}; use crate::ts::{self, Expr, Param, Stmt, StmtKind, Type}; +/// What [`Translator::glue_all`] asks of what an instance's glue drops through. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum Asked { + /// That JS sees none of it drop. + Inert, + /// That each drops through its own `[Symbol.dispose]`, giving glue where it needs it if so. + Disposable(bool), +} + impl<'a, 'tcx> Translator<'a, 'tcx> { /// Whether dropping a `ty` does something JS can see. pub fn drops(&mut self, ty: Ty<'tcx>) -> bool { @@ -202,7 +211,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { None | Some(Std::Peekable) if self.std_iter(ty) => self.closures_held(ty), Some(_) => false, None if self.shared_glue(*adt) => { - if !self.glue_all(ty, *adt, args, |t, held| t.disposable(held, glue)) { + if !self.glue_all(ty, *adt, args, Asked::Disposable(glue.is_some()), |t, held| { + t.disposable(held, glue) + }) { return false; } if let Some(span) = glue { @@ -225,20 +236,38 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } } - /// Whether `f` holds of everything [`Self::glue_held`] has `ty`, an instance of `adt` with - /// `args`, drop through. It holds of an instance already being asked about, which only a - /// projection whose value holds the instance again reaches, and which typing the - /// instance refuses. What nests past the crate's recursion limit is refused. + /// Whether `f`, which `asked` names, holds of everything [`Self::glue_held`] has `ty`, an + /// instance of `adt` with `args`, drop through. It holds of an instance already being + /// asked about, which a projection whose value holds the instance again reaches: what + /// the instance drops through there it drops through already. Each answer is kept, once + /// it no longer rests on an instance still being asked about, so an instance is asked + /// about once however many paths reach it. What nests past the crate's recursion limit + /// is refused. fn glue_all( &mut self, ty: Ty<'tcx>, adt: AdtDef<'tcx>, args: ty::GenericArgsRef<'tcx>, + asked: Asked, f: impl Fn(&mut Self, Ty<'tcx>) -> bool, ) -> bool { - if self.defining_state.glue_open.len() > self.tcx.recursion_limit().0 { + let key = (ty, asked); + let state = &mut self.defining_state; + if let Some(&all) = state.glue_known.get(&key) { + return all; + } + // What rests on the instances it rested on. + if let Some(&rests) = state.glue_resting.get(&key) { + state.glue_rests = state.glue_rests.min(rests); + return true; + } + if let Some(open) = state.glue_open.iter().position(|t| *t == ty) { + state.glue_rests = state.glue_rests.min(open); + return true; + } + if state.glue_open.len() > self.tcx.recursion_limit().0 { // At the definition of the outermost, which it names. - let outer = self.defining_state.glue_open[0]; + let outer = state.glue_open[0]; let span = match outer.kind() { ty::Adt(outer, _) => self.tcx.def_span(outer.did()), _ => self.tcx.def_span(adt.did()), @@ -251,12 +280,32 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { ); return false; } - if self.defining_state.glue_open.contains(&ty) { - return true; - } - self.defining_state.glue_open.push(ty); + let depth = state.glue_open.len(); + state.glue_open.push(ty); + let outer_rests = std::mem::replace(&mut state.glue_rests, usize::MAX); + let resting = state.glue_order.len(); let all = self.glue_held(adt, args).into_iter().all(|held| f(self, held)); - self.defining_state.glue_open.pop(); + let state = &mut self.defining_state; + state.glue_open.pop(); + let rests = std::mem::replace(&mut state.glue_rests, outer_rests); + match () { + // Still resting on one further out, whose answer decides it too. + _ if all && rests < depth => { + state.glue_rests = state.glue_rests.min(rests); + state.glue_resting.insert(key, rests); + state.glue_order.push(key); + } + _ => { + // Each answer that rested on it holds if it does. + for rested in state.glue_order.drain(resting..) { + state.glue_resting.remove(&rested); + if all { + state.glue_known.insert(rested, true); + } + } + state.glue_known.insert(key, all); + } + } all } @@ -448,7 +497,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Some(Std::Context) => false, Some(_) => true, // Its glue, if an instance that drops gave it one, drops its type parameters' fields. - None if adt.is_struct() => self.glue_all(ty, *adt, args, |t, held| t.inert(held)), + None if adt.is_struct() => self.glue_all(ty, *adt, args, Asked::Inert, |t, held| t.inert(held)), None => true, }, _ => true, diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index f3ea63242..decb3876c 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -65,6 +65,7 @@ pub fn translate<'tcx>(tcx: TyCtxt<'tcx>, config: &Config) -> Option { if t.failed { return None; } + t.expand_enum_aliases(); t.program.names = t.names(); t.program.mark_unread(); t.program.merge(); @@ -1104,10 +1105,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let instancing = std::mem::take(&mut self.defining_state.instancing); let instanced = std::mem::take(&mut self.defining_state.instanced); let too_deep = std::mem::take(&mut self.defining_state.too_deep); + let projecting = std::mem::take(&mut self.defining_state.projecting); let info = self.define(adt, span); (self.held, self.glue, self.defining, self.defining_state.field) = (held, glue, defining, field); (self.defining_state.instancing, self.defining_state.instanced) = (instancing, instanced); - self.defining_state.too_deep = too_deep; + (self.defining_state.too_deep, self.defining_state.projecting) = (too_deep, projecting); info } @@ -1913,13 +1915,32 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { return Type::Builtin("unknown"); } let info = self.adt(adt, span); + let instance = Ty::new_adt(tcx, adt, args); + // In a projection argument, named by an alias, so a type whose projections hold + // instances is typed once per instance, however many paths through them reach it, + // and one that holds itself again names its alias. + if self.defining_state.projecting > 0 + && !instance.has_param() + && args.types().next().is_some() + && self.may_project(adt.did()) + { + return self.instance_alias(instance, &info, span); + } + self.instance_ty(instance, &info, span) + } + + /// The TypeScript type of `instance`, an instance of the crate's generic type `info`: + /// its type arguments, then what each of its projections is at them. + fn instance_ty(&mut self, instance: Ty<'tcx>, info: &Adt, span: Span) -> Type { + let ty::Adt(adt, args) = *instance.kind() else { + unreachable!("an instance of a generic type") + }; // Of a definition still being typed, whose instances are checked once it is complete. if let Some(later) = self.later_projections(adt.did(), args, span) { let mut tys: Vec = args.types().map(|t| self.ty(t, span)).collect(); tys.push(later); return Type::Sym(info.sym, tys); } - let instance = Ty::new_adt(tcx, adt, args); if let Some(ty) = self.instanced(instance) { return ty; } @@ -1936,8 +1957,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { self.err(span, msg); } tys.extend(self.projection_args(adt.did(), args, span).into_iter().map(|(_, t)| t)); - // Past the recursion limit, refused already: what it typed is not kept, as each - // projection may double it. + // Past the recursion limit, refused already: what it typed is not kept. if self.defining_state.too_deep { self.leave_instance(instance, None); return Type::Builtin("unknown"); @@ -1981,9 +2001,12 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let (projections, names): (Vec>, Vec) = self.defined(adt).projections.iter().cloned().unzip(); let env = self.defining.map_or(self.typing_env(), |(_, env)| env); let tcx = self.tcx; - (projections.into_iter().zip(names)) + self.defining_state.projecting += 1; + let args = (projections.into_iter().zip(names)) .map(|(projection, name)| (name, self.ty(copy::instantiate(tcx, env, projection, args), span))) - .collect() + .collect(); + self.defining_state.projecting -= 1; + args } /// The TypeScript type of `field` in the definition of `adt`, over its own parameters. @@ -2093,7 +2116,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { continue; }; let arg = copy::instantiate(tcx, env, param, args); + self.defining_state.projecting += 1; let decided = self.ty(arg, span); + self.defining_state.projecting -= 1; let (holds, alias) = match option { true => (holds_undefined(&decided) == at.undefined, "OptionOf"), false => (holds_pending(&decided) == at.pending, "PollOf"), diff --git a/ts/src/lower/names.rs b/ts/src/lower/names.rs index 6e4972a87..006dcb1d0 100644 --- a/ts/src/lower/names.rs +++ b/ts/src/lower/names.rs @@ -263,7 +263,7 @@ fn defined<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, def_args: &[GenericArg<'tcx>] } /// The type and const arguments of `args`, as a suffix spells them. -fn args<'tcx>(tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> impl Iterator { +pub(super) fn args<'tcx>(tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> impl Iterator { args.iter().filter_map(move |arg| match arg.kind() { ty::GenericArgKind::Type(t) => Some(ty(tcx, t)), ty::GenericArgKind::Const(c) => Some(konst(c)), From b9380624e99e038085a021625e1c51f74d004859 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:45:24 -0800 Subject: [PATCH 10/21] fix(ts): name aliases by default arguments too, and finish finite concrete chains An alias is named by all its type arguments, so one at a defaulted parameter's default is no longer named as its type is. An instance without type parameters met while its definition is still being typed no longer counts as endless growth when its projections name another: each has an alias, so a finite chain ends, and one nested past the recursion limit is refused at the instance the source wrote. Co-Authored-By: Claude Opus 5.5 --- .../lang/shapes/expected/core/option.ts | 31 ++++++++ .../lang/shapes/expected/generics.Back.ts | 7 ++ .../shapes/expected/generics.Defaulted.ts | 22 ++++++ .../lang/shapes/expected/generics.Head.ts | 7 ++ .../lang/shapes/expected/generics.Node.ts | 23 ++++++ .../lang/shapes/expected/generics.Tail.ts | 7 ++ ts/fixtures/lang/shapes/expected/generics.ts | 79 +++++++++++++------ ts/fixtures/lang/shapes/src/generics.rs | 59 ++++++++++++++ ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/values.rs | 20 +++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/defined.rs | 38 +++++++-- 13 files changed, 265 insertions(+), 31 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Back.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Defaulted.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Head.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Node.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Tail.ts diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index 67ad47f7f..83be8e120 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -1,10 +1,14 @@ // Generated by rust2-ts. Do not edit. import * as rs from "../rs.ts"; +import type * as Back from "../generics.Back.ts"; import type * as Carried from "../generics.Carried.ts"; +import type * as Defaulted from "../generics.Defaulted.ts"; import type * as Depth from "../generics.Depth.ts"; import type * as Forked from "../generics.Forked.ts"; +import type * as Head from "../generics.Head.ts"; import type * as Looped from "../generics.Looped.ts"; +import type * as Node from "../generics.Node.ts"; import type * as Ticked from "../generics.Ticked.ts"; import { from$convert_From_shapes_Dir_u8 } from "../shapes.ts"; import type * as Dir from "../shapes.Dir.ts"; @@ -54,6 +58,13 @@ export function map$00jdj9f(self: Carried.Carried | undefined, _: (a0: Defaulted.Defaulted) => Defaulted.Defaulted): Defaulted.Defaulted | undefined { + if (self === undefined) { + return undefined; + } + return self; +} + export function map$1ug09xc(self: Forked.Forked | undefined, f: (a0: Forked.Forked) => rs.u8): rs.u8 | undefined { if (self === undefined) { return undefined; @@ -75,6 +86,13 @@ export function map$1mvacy3(self: Forked.Forked | undefined, f: (a0: Node.Node) => rs.u8): rs.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$1ub01y7(self: Ticked.Ticked | undefined, f: (a0: Ticked.Ticked) => rs.u8): rs.u8 | undefined { if (self === undefined) { return undefined; @@ -103,6 +121,13 @@ export function map$1olalbb(self: rs.u8 | undefined, f: (a0: rs.u8) => Forked.Fo return f(self); } +export function map$1htvaju(self: rs.u8 | undefined, f: (a0: rs.u8) => Node.Node): Node.Node | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0xpm57l(self: rs.u8 | undefined, f: (a0: rs.u8) => Ticked.Ticked): Ticked.Ticked | undefined { if (self === undefined) { return undefined; @@ -201,6 +226,12 @@ export function asDeref$10lhnlx(self: Carried.Carried | undefined): Defaulted.Defaulted | undefined { + return map$0983ug4(self, (a0: Defaulted.Defaulted): Defaulted.Defaulted => { + return a0; + }); +} + export function andThen$0jc4o0i(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u8 | undefined | rs.None): rs.u8 | undefined | rs.None { if (self === undefined) { return rs.None; diff --git a/ts/fixtures/lang/shapes/expected/generics.Back.ts b/ts/fixtures/lang/shapes/expected/generics.Back.ts new file mode 100644 index 000000000..7c865a8f6 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Back.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The default source of a [`Defaulted`]. */ +export interface Back { +} + +//# sourceMappingURL=generics.Back.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Defaulted.ts b/ts/fixtures/lang/shapes/expected/generics.Defaulted.ts new file mode 100644 index 000000000..abd289f57 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Defaulted.ts @@ -0,0 +1,22 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Back from "./generics.Back.ts"; + +/** + * What a source holds next, whose source defaults to the one whose items are it again, + * so its alias is named by the default too. + */ +export class Defaulted<_S, Item> { + id: rs.u8; + next: rs.OptionOf; + + constructor(id: rs.u8, next: rs.OptionOf) { + this.id = id; + this.next = next; + } +} + +export type Defaulted$generics_Back = Defaulted; + +//# sourceMappingURL=generics.Defaulted.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Head.ts b/ts/fixtures/lang/shapes/expected/generics.Head.ts new file mode 100644 index 000000000..063773c64 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Head.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The source of the node a [`Node`] holds. */ +export interface Head { +} + +//# sourceMappingURL=generics.Head.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Node.ts b/ts/fixtures/lang/shapes/expected/generics.Node.ts new file mode 100644 index 000000000..4c7789c35 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Node.ts @@ -0,0 +1,23 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Head from "./generics.Head.ts"; +import type * as Tail from "./generics.Tail.ts"; + +/** + * A node holding the node at [`Head`], typed while its own definition is, whose item is the + * node at [`Tail`], whose item ends the chain. + */ +export class Node<_S, Item> { + item: Item; + next: Node | undefined; + + constructor(item: Item, next: Node | undefined) { + this.item = item; + this.next = next; + } +} + +export type Node$generics_Tail = Node; + +//# sourceMappingURL=generics.Node.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Tail.ts b/ts/fixtures/lang/shapes/expected/generics.Tail.ts new file mode 100644 index 000000000..ac4f7751e --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Tail.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The last source of a [`Node`] chain. */ +export interface Tail { +} + +//# sourceMappingURL=generics.Tail.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 863b1caee..052e001cc 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -3,15 +3,17 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; import { thenSome$1ctlqen, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1ub01y7 as option_map$1ub01y7, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx } from "./core/option.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0983ug4 as option_map$0983ug4, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; +import type * as Back from "./generics.Back.ts"; import * as Batch from "./generics.Batch.ts"; import * as Branch from "./generics.Branch.ts"; import * as Carried from "./generics.Carried.ts"; import * as Chain from "./generics.Chain.ts"; import * as Deep from "./generics.Deep.ts"; +import * as Defaulted from "./generics.Defaulted.ts"; import type * as Depth from "./generics.Depth.ts"; import * as Drained from "./generics.Drained.ts"; import * as Either from "./generics.Either.ts"; @@ -20,12 +22,14 @@ import * as Fixed from "./generics.Fixed.ts"; import type * as Flags from "./generics.Flags.ts"; import * as Forked from "./generics.Forked.ts"; import * as Ghost from "./generics.Ghost.ts"; +import type * as Head from "./generics.Head.ts"; import * as Kept from "./generics.Kept.ts"; import type * as Lends from "./generics.Lends.ts"; import type * as Lent from "./generics.Lent.ts"; import type * as Linked from "./generics.Linked.ts"; import type * as Looped from "./generics.Looped.ts"; import type * as Marked from "./generics.Marked.ts"; +import * as Node from "./generics.Node.ts"; import * as Odd from "./generics.Odd.ts"; import * as Parked from "./generics.Parked.ts"; import type * as Paths from "./generics.Paths.ts"; @@ -37,6 +41,7 @@ import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; import * as Swap from "./generics.Swap.ts"; import * as Tagged from "./generics.Tagged.ts"; +import type * as Tail from "./generics.Tail.ts"; import * as Ticked from "./generics.Ticked.ts"; import type * as Ticks from "./generics.Ticks.ts"; import * as Tree from "./generics.Tree.ts"; @@ -725,7 +730,7 @@ export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { * once per path. */ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { - let _73!: Pong.Pong; + let _110!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -747,43 +752,65 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { rs.byteVecPush(_33.value, depth); rs.refMutRelease(_33); rs.drop(held); - const chain: Chain.Chain = rs.iterFold(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), { tag: "End" } as Chain.Chain, (next2: Chain.Chain, _32: rs.u8): Chain.Chain => { - return { tag: "Link", _0: _32, _1: next2 } as Chain.Chain; + const chain: Chain.Chain = rs.iterFold(rs.iterRev(rs.iterSlice(rs.byteSliceView(data))), { tag: "End" } as Chain.Chain, (next3: Chain.Chain, _32: rs.u8): Chain.Chain => { + return { tag: "Link", _0: _32, _1: next3 } as Chain.Chain; }); const ids: rs.ByteVec = new rs.ByteVec(); Chain.ids$generics_Linked(chain, ids); put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids), f, rs.u8Debug)]).v); + const defaulted: Defaulted.Defaulted | undefined = rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Defaulted.Defaulted | undefined, (next4: Defaulted.Defaulted | undefined, _35: rs.u8): Defaulted.Defaulted | undefined => { + return new Defaulted.Defaulted(_35, option_map$0983ug4(next4, (a02: Defaulted.Defaulted): Defaulted.Defaulted => { + return a02; + }) as rs.OptionOf); + }); + const ids2: rs.ByteVec = new rs.ByteVec(); + let at2: Defaulted.Defaulted | undefined = defaulted; + while (at2 !== undefined) { + const next2: Defaulted.Defaulted = at2; + rs.byteVecPush(ids2, next2.id); + at2 = option_asDeref$1z13rqi(next2.next); + } + const node: Node.Node = new Node.Node(data.length & 0xff, option_map$1htvaju(rs.byteSliceFirst(data), (_2: rs.u8): Node.Node => { + return new Node.Node(new Node.Node(_2, undefined), undefined); + })); + const held2: rs.u8 | undefined = option_map$1es1113(node.next, (n: Node.Node): rs.u8 => { + return n.item.item; + }); + const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); + const arg = node.item; + const args10 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args10[0], " ", args10[1], " ", args10[2]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _65: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_65 === rs.DONE) { + const _102: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_102 === rs.DONE) { break; } - const i: rs.usize = _65[0]; - const b2: rs.u8 = _65[1]; + const i: rs.usize = _102[0]; + const b2: rs.u8 = _102[1]; if (rs.usizeRem(i, 3) === 0) { - _73 = { tag: "Miss" } as Pong.Pong; + _110 = { tag: "Miss" } as Pong.Pong; } else { - _73 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; + _110 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b2, _73 as rs.OptionOf); + ping = new Ping.Ping(b2, _110 as rs.OptionOf); } - const ids2: rs.ByteVec = new rs.ByteVec(); - Ping.ids$generics_Rally(ping, ids2); - put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug)]).v); - const below: Forked.Forked = new Forked.Forked(23, undefined as rs.OptionOf, new Forked.Forked(24, option_map$1olalbb(rs.byteSliceFirst(data), (_2: rs.u8): Forked.Forked => { - return new Forked.Forked(_2, undefined as rs.OptionOf, undefined as rs.OptionOf); - }) as rs.OptionOf, option_map$0xpm57l(rs.byteSliceLast(data), (_22: rs.u8): Ticked.Ticked => { - return tick(~_22 & 0xff); + const ids3: rs.ByteVec = new rs.ByteVec(); + Ping.ids$generics_Rally(ping, ids3); + put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids3), f, rs.u8Debug)]).v); + const below: Forked.Forked = new Forked.Forked(23, undefined as rs.OptionOf, new Forked.Forked(24, option_map$1olalbb(rs.byteSliceFirst(data), (_22: rs.u8): Forked.Forked => { + return new Forked.Forked(_22, undefined as rs.OptionOf, undefined as rs.OptionOf); + }) as rs.OptionOf, option_map$0xpm57l(rs.byteSliceLast(data), (_23: rs.u8): Ticked.Ticked => { + return tick(~_23 & 0xff); }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _113: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _150: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _113; + fork.left = _150; } - const ids3: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { + const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; }), below.id]; const right: [rs.u8, rs.u8 | undefined, rs.u8 | undefined] | undefined = option_map$1mvacy3(below.right, (f2: Forked.Forked): [rs.u8, rs.u8 | undefined, rs.u8 | undefined] => { @@ -793,13 +820,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return t.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args9 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids3[0], f), (f: rs.Formatter) => rs.fmtOption(ids3[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids3[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args9[0], " ", args9[1]]).v); + const args12 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args12[0], " ", args12[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _149: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_149.value))), undefined, true); - rs.refRelease(_149); + const _186: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_186.value))), undefined, true); + rs.refRelease(_186); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index f99b353ba..90b7d9e5c 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -947,6 +947,41 @@ impl Chain { } } +/// What a source holds next, whose source defaults to the one whose items are it again, +/// so its alias is named by the default too. +struct Defaulted { + id: u8, + next: Option>, +} + +/// The default source of a [`Defaulted`]. +struct Back; + +impl Source for Back { + type Item = Defaulted; +} + +/// A node holding the node at [`Head`], typed while its own definition is, whose item is the +/// node at [`Tail`], whose item ends the chain. +struct Node { + item: S::Item, + next: Option>>, +} + +/// The source of the node a [`Node`] holds. +struct Head; + +impl Source for Head { + type Item = Node; +} + +/// The last source of a [`Node`] chain. +struct Tail; + +impl Source for Tail { + type Item = u8; +} + /// Two types a source names, each holding the other. trait Duo { type Ping; @@ -1054,6 +1089,30 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { chain.ids(&mut ids); put(out, &format!("{ids:?}")); + let defaulted = data.iter().fold(None::, |next, &b| { + Some(Defaulted { + id: b, + next: next.map(Box::new), + }) + }); + let mut ids = Vec::new(); + let mut at = defaulted.as_ref(); + while let Some(next) = at { + ids.push(next.id); + at = next.next.as_deref(); + } + let node = Node:: { + item: data.len() as u8, + next: data.first().map(|&b| { + Box::new(Node:: { + item: Node { item: b, next: None }, + next: None, + }) + }), + }; + let held = node.next.as_ref().map(|n| n.item.item); + put(out, &format!("{ids:?} {} {held:?}", node.item)); + let mut ping = Ping:: { id: 0xee, pong: None }; for (i, &b) in data.iter().enumerate() { ping = Ping { diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 46e044d0b..d80a2b0e8 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -268,6 +268,7 @@ fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:Sprawl+2:2: `values::Sprawl>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 70296adc4..587778d7b 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -261,6 +261,7 @@ fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not sup fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:Sprawl+2:2: `values::Sprawl>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 5abb5e514..bea161228 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -199,6 +199,26 @@ pub trait Duo { type Pong; } +/// A source widened once more. +pub struct Widen(pub T); + +impl Source for Widen { + type Item = Sprawl>>; +} + +/// A type holding an instance of itself without type parameters, typed while its own +/// definition is, whose item is it at a source widened again. +pub struct Sprawl { + pub item: Option<&'static S::Item>, + pub wide: Option<&'static Sprawl>>, +} + +/// `Sprawl`'s definition would name an instance at each widening. +pub fn projected_sprawl(deep: bool) -> bool { + let sprawl: Sprawl> = Sprawl { item: None, wide: None }; + sprawl.wide.is_none() && deep +} + /// What a duo's ping and pong both are at a wrapped duo: the same type at one wrapped again. pub struct Fork { pub ping: Option<&'static D::Ping>, diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index e3e113f4b..dd3282981 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -214,6 +214,7 @@ roots = [ "values::projected_nested", "values::projected_over", "values::projected_spread", + "values::projected_sprawl", "values::projected_uninit", "values::units_compared", ] diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 5929d6783..7893ccf18 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -58,6 +58,9 @@ pub struct Defining<'tcx> { pending: Vec>, /// How many projections every record holds, which grows while instances are filled. recorded: usize, + /// The [`Pending::depth`] and [`Pending::root`] of the instance whose projection + /// arguments are being filled. + filling: Option<(usize, usize)>, /// The field of the definition being typed whose type is. pub field: Option, /// The instances being typed in the type expression being typed, which a definition @@ -115,6 +118,10 @@ struct Pending<'tcx> { filled: Option>, /// Whether it was refused, and so is filled for good. refused: bool, + /// How many instances' projection arguments named it in turn, from one a definition's + /// fields typed, and that one, by its index in [`Defining::pending`]. + depth: usize, + root: usize, } impl<'tcx> Translator<'_, 'tcx> { @@ -251,6 +258,8 @@ impl<'tcx> Translator<'_, 'tcx> { span, filled: None, refused: false, + depth: state.filling.map_or(0, |(depth, _)| depth + 1), + root: state.filling.map_or(state.pending.len(), |(_, root)| root), }); Some(Type::Later(state.pending.len() - 1)) } @@ -274,11 +283,11 @@ impl<'tcx> Translator<'_, 'tcx> { unreachable!("an instance of a generic type") }; let did = adt.did(); - let args = tcx.generics_of(did).own_args_no_defaults(tcx, args); + // Defaults included, so no alias is named as its type is. let alias = self.program.sym(Symbol { module: self.type_module(did), base: tcx.item_name(did).to_string(), - suffix: super::names::args(tcx, args).collect::>().join("_"), + suffix: super::names::args(tcx, args.as_slice()).collect::>().join("_"), owner: None, kind: SymKind::Union, order: self.order(tcx.def_span(did)), @@ -473,7 +482,8 @@ impl<'tcx> Translator<'_, 'tcx> { /// Fill in the projection arguments of the instance `pending[i]` typed, from its /// definition's projections so far, refusing an instance at other than type parameters /// whose projections there name a new projection or another instance still being typed, - /// as a type that holds itself at other arguments would without end. + /// as a type that holds itself at other arguments would without end, and one without + /// type parameters named through more instances than the crate's recursion limit. fn fill_pending(&mut self, i: usize) { let p = &self.defining_state.pending[i]; let projections: Vec> = (self.defining_state.records[&p.adt].projections.iter()) @@ -482,13 +492,29 @@ impl<'tcx> Translator<'_, 'tcx> { if p.refused || p.filled.as_ref().is_some_and(|f| f.len() == projections.len()) { return; } - let (adt, args, at, field, span) = (p.adt, p.args, p.at, p.field, p.span); + let (adt, args, at, field, span, depth, root) = (p.adt, p.args, p.at, p.field, p.span, p.depth, p.root); + if !args.has_param() && depth > self.tcx.recursion_limit().0 { + // Named by the one its definition's field typed, which the source wrote. + let root = &self.defining_state.pending[root]; + let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(root.adt), root.args); + self.err( + root.span, + format!( + "`{instance}` is not supported: its projections' values nest instances past the crate's recursion limit" + ), + ); + let p = &mut self.defining_state.pending[i]; + p.filled = Some(vec![Type::Builtin("unknown"); projections.len()]); + p.refused = true; + return; + } let env = at.map_or(self.typing_env(), |(_, env)| env); let named = |state: &Defining<'tcx>| at.map_or(0, |(did, _)| state.records[&did].projections.len()); let before = (named(&self.defining_state), self.defining_state.pending.len()); let outer = std::mem::replace(&mut self.defining, at); let outer_field = std::mem::replace(&mut self.defining_state.field, field); self.defining_state.projecting += 1; + let filling = self.defining_state.filling.replace((depth, root)); let mut tys: Vec = projections .iter() .map(|&p| { @@ -497,10 +523,12 @@ impl<'tcx> Translator<'_, 'tcx> { }) .collect(); self.defining_state.projecting -= 1; + self.defining_state.filling = filling; self.defining = outer; self.defining_state.field = outer_field; let after = (named(&self.defining_state), self.defining_state.pending.len()); - let refused = after != before && !args.types().all(|t| matches!(t.kind(), ty::Param(_))); + // Instances without type parameters each have an alias, so a finite chain of them ends. + let refused = after != before && args.has_param() && !args.types().all(|t| matches!(t.kind(), ty::Param(_))); if refused { let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); self.err( From 701b907650d9ed417732e70e8d9a1d7e0bb41989 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:05:57 -0800 Subject: [PATCH 11/21] fix(ts): let finite projection growth settle, and alias const-only instances Filling in an instance's projection arguments may name more projections or instances; they are now filled until none is new, refused only past the crate's recursion limit, by how deep instances nest or how many projections the definition names, rather than at the first growth. An instance of a type with only const arguments gets an alias too. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/expected/core/bool.ts | 10 +++ .../lang/shapes/expected/core/option.ts | 32 +++++++++ .../lang/shapes/expected/generics.Counted.ts | 18 +++++ .../lang/shapes/expected/generics.Layer.ts | 20 ++++++ .../lang/shapes/expected/generics.Peel.ts | 12 ++++ .../lang/shapes/expected/generics.Top.ts | 7 ++ ts/fixtures/lang/shapes/expected/generics.ts | 59 +++++++++++------ ts/fixtures/lang/shapes/src/generics.rs | 66 +++++++++++++++++++ ts/fixtures/reject/census.txt | 2 +- ts/fixtures/reject/expected.txt | 2 +- ts/src/lower/defined.rs | 20 +++--- ts/src/lower/mod.rs | 2 +- 12 files changed, 218 insertions(+), 32 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Counted.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Layer.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Peel.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Top.ts diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index 25a236022..ebed49990 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -2,7 +2,10 @@ import * as rs from "../rs.ts"; import type * as Pair from "../fmt.Pair.ts"; +import type * as Layer from "../generics.Layer.ts"; +import type * as Peel from "../generics.Peel.ts"; import type * as Ticked from "../generics.Ticked.ts"; +import type * as Top from "../generics.Top.ts"; import type * as Void from "../shapes.Void.ts"; export function thenSome$1ctlqen(self: boolean, t: rs.u8 | undefined): rs.u8 | undefined | rs.None { @@ -33,6 +36,13 @@ export function thenSome$05ku0zn(self: boolean, t: rs.i32): rs.i32 | undefined { return t; } +export function thenSome$0bd8znh(self: boolean, t: Layer.Layer, boolean, rs.u8>): Layer.Layer, boolean, rs.u8> | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$0tbnivw(self: boolean, t: Ticked.Ticked): Ticked.Ticked | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index 83be8e120..8b108c8b7 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -3,13 +3,17 @@ import * as rs from "../rs.ts"; import type * as Back from "../generics.Back.ts"; import type * as Carried from "../generics.Carried.ts"; +import type * as Counted from "../generics.Counted.ts"; import type * as Defaulted from "../generics.Defaulted.ts"; import type * as Depth from "../generics.Depth.ts"; import type * as Forked from "../generics.Forked.ts"; import type * as Head from "../generics.Head.ts"; +import type * as Layer from "../generics.Layer.ts"; import type * as Looped from "../generics.Looped.ts"; import type * as Node from "../generics.Node.ts"; +import type * as Peel from "../generics.Peel.ts"; import type * as Ticked from "../generics.Ticked.ts"; +import type * as Top from "../generics.Top.ts"; import { from$convert_From_shapes_Dir_u8 } from "../shapes.ts"; import type * as Dir from "../shapes.Dir.ts"; import type * as Meters from "../shapes.Meters.ts"; @@ -58,6 +62,13 @@ export function map$00jdj9f(self: Carried.Carried | undefined, _: (a0: Counted.Counted) => Counted.Counted): Counted.Counted | undefined { + if (self === undefined) { + return undefined; + } + return self; +} + export function map$0983ug4(self: Defaulted.Defaulted | undefined, _: (a0: Defaulted.Defaulted) => Defaulted.Defaulted): Defaulted.Defaulted | undefined { if (self === undefined) { return undefined; @@ -65,6 +76,13 @@ export function map$0983ug4(self: Defaulted.Defaulted | undefined, f: (a0: Counted.Counted) => rs.u8): rs.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$1ug09xc(self: Forked.Forked | undefined, f: (a0: Forked.Forked) => rs.u8): rs.u8 | undefined { if (self === undefined) { return undefined; @@ -100,6 +118,13 @@ export function map$1ub01y7(self: Ticked.Ticked | undefined, f: (a0: Ticked.Tick return f(self); } +export function map$1a3j889(self: Layer.Layer, boolean, rs.u8> | undefined, f: (a0: Layer.Layer, boolean, rs.u8>) => [boolean, boolean]): [boolean, boolean] | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0ggarad(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.Vec): rs.Vec | undefined { if (self === undefined) { return undefined; @@ -232,6 +257,13 @@ export function asDeref$1z13rqi(self: Defaulted.Defaulted | undefined, f: (a0: Counted.Counted) => Counted.Counted | undefined): Counted.Counted | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function andThen$0jc4o0i(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u8 | undefined | rs.None): rs.u8 | undefined | rs.None { if (self === undefined) { return rs.None; diff --git a/ts/fixtures/lang/shapes/expected/generics.Counted.ts b/ts/fixtures/lang/shapes/expected/generics.Counted.ts new file mode 100644 index 000000000..3e511a0ee --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Counted.ts @@ -0,0 +1,18 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** What a source at a depth holds next, an instance only of its const parameter. */ +export class Counted { + id: rs.u8; + next: rs.OptionOf; + + constructor(id: rs.u8, next: rs.OptionOf) { + this.id = id; + this.next = next; + } +} + +export type Counted$0 = Counted; + +//# sourceMappingURL=generics.Counted.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Layer.ts b/ts/fixtures/lang/shapes/expected/generics.Layer.ts new file mode 100644 index 000000000..28ac099e8 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Layer.ts @@ -0,0 +1,20 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Peel from "./generics.Peel.ts"; + +/** + * A layer lending the one under it, peeled, whose item is a projection its own definition + * names only once that instance is filled in. + */ +export class Layer { + item: Item; + under: Layer, Other, rs.u8> | undefined; + + constructor(item: Item, under: Layer, Other, rs.u8> | undefined) { + this.item = item; + this.under = under; + } +} + +//# sourceMappingURL=generics.Layer.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Peel.ts b/ts/fixtures/lang/shapes/expected/generics.Peel.ts new file mode 100644 index 000000000..92709ee3e --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Peel.ts @@ -0,0 +1,12 @@ +// Generated by rust2-ts. Do not edit. + +/** A source with a layer peeled off. */ +export class Peel { + _0: S; + + constructor(_0: S) { + this._0 = _0; + } +} + +//# sourceMappingURL=generics.Peel.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Top.ts b/ts/fixtures/lang/shapes/expected/generics.Top.ts new file mode 100644 index 000000000..cd233a80f --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Top.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The outermost layer. */ +export interface Top { +} + +//# sourceMappingURL=generics.Top.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 052e001cc..dd618bda2 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -2,8 +2,8 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; -import { thenSome$1ctlqen, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0983ug4 as option_map$0983ug4, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi } from "./core/option.ts"; +import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; @@ -12,6 +12,7 @@ import * as Batch from "./generics.Batch.ts"; import * as Branch from "./generics.Branch.ts"; import * as Carried from "./generics.Carried.ts"; import * as Chain from "./generics.Chain.ts"; +import * as Counted from "./generics.Counted.ts"; import * as Deep from "./generics.Deep.ts"; import * as Defaulted from "./generics.Defaulted.ts"; import type * as Depth from "./generics.Depth.ts"; @@ -24,6 +25,7 @@ import * as Forked from "./generics.Forked.ts"; import * as Ghost from "./generics.Ghost.ts"; import type * as Head from "./generics.Head.ts"; import * as Kept from "./generics.Kept.ts"; +import * as Layer from "./generics.Layer.ts"; import type * as Lends from "./generics.Lends.ts"; import type * as Lent from "./generics.Lent.ts"; import type * as Linked from "./generics.Linked.ts"; @@ -33,6 +35,7 @@ import * as Node from "./generics.Node.ts"; import * as Odd from "./generics.Odd.ts"; import * as Parked from "./generics.Parked.ts"; import type * as Paths from "./generics.Paths.ts"; +import type * as Peel from "./generics.Peel.ts"; import * as Pile from "./generics.Pile.ts"; import * as Ping from "./generics.Ping.ts"; import type * as Pong from "./generics.Pong.ts"; @@ -44,6 +47,7 @@ import * as Tagged from "./generics.Tagged.ts"; import type * as Tail from "./generics.Tail.ts"; import * as Ticked from "./generics.Ticked.ts"; import type * as Ticks from "./generics.Ticks.ts"; +import type * as Top from "./generics.Top.ts"; import * as Tree from "./generics.Tree.ts"; import * as Wrap from "./generics.Wrap.ts"; import { put } from "./harness.ts"; @@ -730,7 +734,7 @@ export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { * once per path. */ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { - let _110!: Pong.Pong; + let _149!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -778,23 +782,40 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }); const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); const arg = node.item; - const args10 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args10[0], " ", args10[1], " ", args10[2]]).v); + const args12 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args12[0], " ", args12[1], " ", args12[2]]).v); + const under: Layer.Layer, boolean, rs.u8> = new Layer.Layer(true, undefined); + const layer: Layer.Layer = new Layer.Layer(((option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16) << 4) & 0xffff, thenSome$0bd8znh(rs.usizeRem(data.length, 2) === 1, under)); + const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _39: rs.u8): Counted.Counted | undefined => { + return new Counted.Counted(_39, option_map$0xoixq7(next5, (a03: Counted.Counted): Counted.Counted => { + return a03; + }) as rs.OptionOf); + }), (c: Counted.Counted): Counted.Counted | undefined => { + return c.next; + }), (c2: Counted.Counted): rs.u8 => { + return c2.id; + }); + const _126: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { + return [u.item, u.under !== undefined] as [boolean, boolean]; + }); + const arg2 = layer.item; + const args13 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_126, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args13[0], " ", args13[1], " ", args13[2]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _102: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_102 === rs.DONE) { + const _141: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_141 === rs.DONE) { break; } - const i: rs.usize = _102[0]; - const b2: rs.u8 = _102[1]; + const i: rs.usize = _141[0]; + const b2: rs.u8 = _141[1]; if (rs.usizeRem(i, 3) === 0) { - _110 = { tag: "Miss" } as Pong.Pong; + _149 = { tag: "Miss" } as Pong.Pong; } else { - _110 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; + _149 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b2, _110 as rs.OptionOf); + ping = new Ping.Ping(b2, _149 as rs.OptionOf); } const ids3: rs.ByteVec = new rs.ByteVec(); Ping.ids$generics_Rally(ping, ids3); @@ -806,9 +827,9 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _150: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _189: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _150; + fork.left = _189; } const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; @@ -820,13 +841,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return t.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args12 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args12[0], " ", args12[1]]).v); + const args15 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args15[0], " ", args15[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _186: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_186.value))), undefined, true); - rs.refRelease(_186); + const _225: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_225.value))), undefined, true); + rs.refRelease(_225); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 90b7d9e5c..88b3d9972 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -982,6 +982,48 @@ impl Source for Tail { type Item = u8; } +/// A layered source's item, and the item of the layer peeled off it. +trait Layered { + type Item; + type Other; +} + +/// A layer lending the one under it, peeled, whose item is a projection its own definition +/// names only once that instance is filled in. +struct Layer { + item: S::Item, + under: Option<&'static Layer>>, +} + +/// A source with a layer peeled off. +struct Peel(S); + +impl Layered for Peel { + type Item = S::Other; + type Other = u8; +} + +/// The outermost layer. +struct Top; + +impl Layered for Top { + type Item = u16; + type Other = bool; +} + +/// What a source at a depth holds next, an instance only of its const parameter. +struct Counted +where + Depth: Source, +{ + id: u8, + next: Option as Source>::Item>>, +} + +impl Source for Depth<0> { + type Item = Counted<0>; +} + /// Two types a source names, each holding the other. trait Duo { type Ping; @@ -1113,6 +1155,30 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { let held = node.next.as_ref().map(|n| n.item.item); put(out, &format!("{ids:?} {} {held:?}", node.item)); + let under: &'static Layer> = &Layer { + item: true, + under: None, + }; + let layer = Layer:: { + item: u16::from(data.first().copied().unwrap_or(0)) << 4, + under: (data.len() % 2 == 1).then_some(under), + }; + let counted = data.iter().fold(None::>, |next, &b| { + Some(Counted { + id: b, + next: next.map(Box::new), + }) + }); + let count = counted.as_ref().and_then(|c| c.next.as_ref()).map(|c| c.id); + put( + out, + &format!( + "{} {:?} {count:?}", + layer.item, + layer.under.map(|u| (u.item, u.under.is_some())) + ), + ); + let mut ping = Ping:: { id: 0xee, pong: None }; for (i, &b) in data.iter().enumerate() { ping = Ping { diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index d80a2b0e8..ae4d71d0f 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -269,7 +269,7 @@ fixtures/reject/src/values.rs:Mixed+0:1: union `values::Mixed`, whose fields ove fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:Sprawl+2:2: `values::Sprawl>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 587778d7b..d0f86ba23 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -262,7 +262,7 @@ fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end fixtures/reject/src/values.rs:Sprawl+2:2: `values::Sprawl>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 7893ccf18..495b8dd63 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -480,10 +480,11 @@ impl<'tcx> Translator<'_, 'tcx> { } /// Fill in the projection arguments of the instance `pending[i]` typed, from its - /// definition's projections so far, refusing an instance at other than type parameters - /// whose projections there name a new projection or another instance still being typed, - /// as a type that holds itself at other arguments would without end, and one without - /// type parameters named through more instances than the crate's recursion limit. + /// definition's projections so far. Filling them in may name more projections or + /// instances, which are filled in turn until none is new; a type that holds itself at + /// ever other arguments would name them without end, so an instance named through more + /// instances than the crate's recursion limit is refused, as is filling one in that + /// leaves its definition with more projections than that. fn fill_pending(&mut self, i: usize) { let p = &self.defining_state.pending[i]; let projections: Vec> = (self.defining_state.records[&p.adt].projections.iter()) @@ -493,7 +494,8 @@ impl<'tcx> Translator<'_, 'tcx> { return; } let (adt, args, at, field, span, depth, root) = (p.adt, p.args, p.at, p.field, p.span, p.depth, p.root); - if !args.has_param() && depth > self.tcx.recursion_limit().0 { + let limit = self.tcx.recursion_limit().0; + if depth > limit { // Named by the one its definition's field typed, which the source wrote. let root = &self.defining_state.pending[root]; let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(root.adt), root.args); @@ -510,7 +512,7 @@ impl<'tcx> Translator<'_, 'tcx> { } let env = at.map_or(self.typing_env(), |(_, env)| env); let named = |state: &Defining<'tcx>| at.map_or(0, |(did, _)| state.records[&did].projections.len()); - let before = (named(&self.defining_state), self.defining_state.pending.len()); + let before = self.defining_state.pending.len(); let outer = std::mem::replace(&mut self.defining, at); let outer_field = std::mem::replace(&mut self.defining_state.field, field); self.defining_state.projecting += 1; @@ -526,9 +528,7 @@ impl<'tcx> Translator<'_, 'tcx> { self.defining_state.filling = filling; self.defining = outer; self.defining_state.field = outer_field; - let after = (named(&self.defining_state), self.defining_state.pending.len()); - // Instances without type parameters each have an alias, so a finite chain of them ends. - let refused = after != before && args.has_param() && !args.types().all(|t| matches!(t.kind(), ty::Param(_))); + let refused = named(&self.defining_state) > limit; if refused { let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); self.err( @@ -539,7 +539,7 @@ impl<'tcx> Translator<'_, 'tcx> { ); tys = vec![Type::Builtin("unknown"); projections.len()]; // Nor are the instances it named filled in, which would name more. - for named in &mut self.defining_state.pending[before.1..] { + for named in &mut self.defining_state.pending[before..] { named.filled = Some(Vec::new()); named.refused = true; } diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index decb3876c..92cd9a0ca 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1921,7 +1921,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // and one that holds itself again names its alias. if self.defining_state.projecting > 0 && !instance.has_param() - && args.types().next().is_some() + && args.iter().any(|arg| arg.as_region().is_none()) && self.may_project(adt.did()) { return self.instance_alias(instance, &info, span); From 499a91970aa915af2571650eae0a7e69bed8bc21 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:10:13 -0800 Subject: [PATCH 12/21] fix(ts): bound projection growth by how deep projections nest A definition may name more projections than the recursion limit, each independent; what grows without end is a projection of projections nesting ever deeper, so that is what is refused past the limit. Co-Authored-By: Claude Opus 5.5 --- .../lang/shapes/expected/generics.Wide.ts | 277 ++++++++++++++++++ ts/fixtures/lang/shapes/expected/generics.ts | 46 +-- ts/fixtures/lang/shapes/src/generics.rs | 49 ++++ ts/src/lower/defined.rs | 21 +- 4 files changed, 368 insertions(+), 25 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Wide.ts diff --git a/ts/fixtures/lang/shapes/expected/generics.Wide.ts b/ts/fixtures/lang/shapes/expected/generics.Wide.ts new file mode 100644 index 000000000..5daf8a3b6 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Wide.ts @@ -0,0 +1,277 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +export class Wide { + a0: A0; + a1: A1; + a2: A2; + a3: A3; + a4: A4; + a5: A5; + a6: A6; + a7: A7; + a8: A8; + a9: A9; + a10: A10; + a11: A11; + a12: A12; + a13: A13; + a14: A14; + a15: A15; + a16: A16; + a17: A17; + a18: A18; + a19: A19; + a20: A20; + a21: A21; + a22: A22; + a23: A23; + a24: A24; + a25: A25; + a26: A26; + a27: A27; + a28: A28; + a29: A29; + a30: A30; + a31: A31; + a32: A32; + a33: A33; + a34: A34; + a35: A35; + a36: A36; + a37: A37; + a38: A38; + a39: A39; + a40: A40; + a41: A41; + a42: A42; + a43: A43; + a44: A44; + a45: A45; + a46: A46; + a47: A47; + a48: A48; + a49: A49; + a50: A50; + a51: A51; + a52: A52; + a53: A53; + a54: A54; + a55: A55; + a56: A56; + a57: A57; + a58: A58; + a59: A59; + a60: A60; + a61: A61; + a62: A62; + a63: A63; + a64: A64; + a65: A65; + a66: A66; + a67: A67; + a68: A68; + a69: A69; + a70: A70; + a71: A71; + a72: A72; + a73: A73; + a74: A74; + a75: A75; + a76: A76; + a77: A77; + a78: A78; + a79: A79; + a80: A80; + a81: A81; + a82: A82; + a83: A83; + a84: A84; + a85: A85; + a86: A86; + a87: A87; + a88: A88; + a89: A89; + a90: A90; + a91: A91; + a92: A92; + a93: A93; + a94: A94; + a95: A95; + a96: A96; + a97: A97; + a98: A98; + a99: A99; + a100: A100; + a101: A101; + a102: A102; + a103: A103; + a104: A104; + a105: A105; + a106: A106; + a107: A107; + a108: A108; + a109: A109; + a110: A110; + a111: A111; + a112: A112; + a113: A113; + a114: A114; + a115: A115; + a116: A116; + a117: A117; + a118: A118; + a119: A119; + a120: A120; + a121: A121; + a122: A122; + a123: A123; + a124: A124; + a125: A125; + a126: A126; + a127: A127; + a128: A128; + a129: A129; + next: Wide | undefined; + + constructor(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11, a12: A12, a13: A13, a14: A14, a15: A15, a16: A16, a17: A17, a18: A18, a19: A19, a20: A20, a21: A21, a22: A22, a23: A23, a24: A24, a25: A25, a26: A26, a27: A27, a28: A28, a29: A29, a30: A30, a31: A31, a32: A32, a33: A33, a34: A34, a35: A35, a36: A36, a37: A37, a38: A38, a39: A39, a40: A40, a41: A41, a42: A42, a43: A43, a44: A44, a45: A45, a46: A46, a47: A47, a48: A48, a49: A49, a50: A50, a51: A51, a52: A52, a53: A53, a54: A54, a55: A55, a56: A56, a57: A57, a58: A58, a59: A59, a60: A60, a61: A61, a62: A62, a63: A63, a64: A64, a65: A65, a66: A66, a67: A67, a68: A68, a69: A69, a70: A70, a71: A71, a72: A72, a73: A73, a74: A74, a75: A75, a76: A76, a77: A77, a78: A78, a79: A79, a80: A80, a81: A81, a82: A82, a83: A83, a84: A84, a85: A85, a86: A86, a87: A87, a88: A88, a89: A89, a90: A90, a91: A91, a92: A92, a93: A93, a94: A94, a95: A95, a96: A96, a97: A97, a98: A98, a99: A99, a100: A100, a101: A101, a102: A102, a103: A103, a104: A104, a105: A105, a106: A106, a107: A107, a108: A108, a109: A109, a110: A110, a111: A111, a112: A112, a113: A113, a114: A114, a115: A115, a116: A116, a117: A117, a118: A118, a119: A119, a120: A120, a121: A121, a122: A122, a123: A123, a124: A124, a125: A125, a126: A126, a127: A127, a128: A128, a129: A129, next: Wide | undefined) { + this.a0 = a0; + this.a1 = a1; + this.a2 = a2; + this.a3 = a3; + this.a4 = a4; + this.a5 = a5; + this.a6 = a6; + this.a7 = a7; + this.a8 = a8; + this.a9 = a9; + this.a10 = a10; + this.a11 = a11; + this.a12 = a12; + this.a13 = a13; + this.a14 = a14; + this.a15 = a15; + this.a16 = a16; + this.a17 = a17; + this.a18 = a18; + this.a19 = a19; + this.a20 = a20; + this.a21 = a21; + this.a22 = a22; + this.a23 = a23; + this.a24 = a24; + this.a25 = a25; + this.a26 = a26; + this.a27 = a27; + this.a28 = a28; + this.a29 = a29; + this.a30 = a30; + this.a31 = a31; + this.a32 = a32; + this.a33 = a33; + this.a34 = a34; + this.a35 = a35; + this.a36 = a36; + this.a37 = a37; + this.a38 = a38; + this.a39 = a39; + this.a40 = a40; + this.a41 = a41; + this.a42 = a42; + this.a43 = a43; + this.a44 = a44; + this.a45 = a45; + this.a46 = a46; + this.a47 = a47; + this.a48 = a48; + this.a49 = a49; + this.a50 = a50; + this.a51 = a51; + this.a52 = a52; + this.a53 = a53; + this.a54 = a54; + this.a55 = a55; + this.a56 = a56; + this.a57 = a57; + this.a58 = a58; + this.a59 = a59; + this.a60 = a60; + this.a61 = a61; + this.a62 = a62; + this.a63 = a63; + this.a64 = a64; + this.a65 = a65; + this.a66 = a66; + this.a67 = a67; + this.a68 = a68; + this.a69 = a69; + this.a70 = a70; + this.a71 = a71; + this.a72 = a72; + this.a73 = a73; + this.a74 = a74; + this.a75 = a75; + this.a76 = a76; + this.a77 = a77; + this.a78 = a78; + this.a79 = a79; + this.a80 = a80; + this.a81 = a81; + this.a82 = a82; + this.a83 = a83; + this.a84 = a84; + this.a85 = a85; + this.a86 = a86; + this.a87 = a87; + this.a88 = a88; + this.a89 = a89; + this.a90 = a90; + this.a91 = a91; + this.a92 = a92; + this.a93 = a93; + this.a94 = a94; + this.a95 = a95; + this.a96 = a96; + this.a97 = a97; + this.a98 = a98; + this.a99 = a99; + this.a100 = a100; + this.a101 = a101; + this.a102 = a102; + this.a103 = a103; + this.a104 = a104; + this.a105 = a105; + this.a106 = a106; + this.a107 = a107; + this.a108 = a108; + this.a109 = a109; + this.a110 = a110; + this.a111 = a111; + this.a112 = a112; + this.a113 = a113; + this.a114 = a114; + this.a115 = a115; + this.a116 = a116; + this.a117 = a117; + this.a118 = a118; + this.a119 = a119; + this.a120 = a120; + this.a121 = a121; + this.a122 = a122; + this.a123 = a123; + this.a124 = a124; + this.a125 = a125; + this.a126 = a126; + this.a127 = a127; + this.a128 = a128; + this.a129 = a129; + this.next = next; + } +} + +export function new$u8(n: rs.u8): Wide { + return new Wide(n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, undefined); +} + +//# sourceMappingURL=generics.Wide.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index dd618bda2..bfefe2379 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -49,6 +49,7 @@ import * as Ticked from "./generics.Ticked.ts"; import type * as Ticks from "./generics.Ticks.ts"; import type * as Top from "./generics.Top.ts"; import * as Tree from "./generics.Tree.ts"; +import * as generics_Wide from "./generics.Wide.ts"; import * as Wrap from "./generics.Wrap.ts"; import { put } from "./harness.ts"; import * as shapes_Gauge from "./shapes.Gauge.ts"; @@ -734,7 +735,7 @@ export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { * once per path. */ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { - let _149!: Pong.Pong; + let _170!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -782,8 +783,8 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }); const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); const arg = node.item; - const args12 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args12[0], " ", args12[1], " ", args12[2]]).v); + const args14 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args14[0], " ", args14[1], " ", args14[2]]).v); const under: Layer.Layer, boolean, rs.u8> = new Layer.Layer(true, undefined); const layer: Layer.Layer = new Layer.Layer(((option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16) << 4) & 0xffff, thenSome$0bd8znh(rs.usizeRem(data.length, 2) === 1, under)); const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _39: rs.u8): Counted.Counted | undefined => { @@ -799,23 +800,30 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return [u.item, u.under !== undefined] as [boolean, boolean]; }); const arg2 = layer.item; - const args13 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_126, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args13[0], " ", args13[1], " ", args13[2]]).v); + const args15 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_126, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args15[0], " ", args15[1], " ", args15[2]]).v); + const wide: generics_Wide.Wide = generics_Wide.new$u8(data.length & 0xff); + const _148: boolean = wide.next === undefined; + const arg3 = wide.a0; + const _151: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); + const arg4 = wide.a129; + const args16 = [_151, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_148, f)]; + put(out, rs.fmtFormat([args16[0], " ", args16[1], " ", args16[2]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _141: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_141 === rs.DONE) { + const _162: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_162 === rs.DONE) { break; } - const i: rs.usize = _141[0]; - const b2: rs.u8 = _141[1]; + const i: rs.usize = _162[0]; + const b2: rs.u8 = _162[1]; if (rs.usizeRem(i, 3) === 0) { - _149 = { tag: "Miss" } as Pong.Pong; + _170 = { tag: "Miss" } as Pong.Pong; } else { - _149 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; + _170 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b2, _149 as rs.OptionOf); + ping = new Ping.Ping(b2, _170 as rs.OptionOf); } const ids3: rs.ByteVec = new rs.ByteVec(); Ping.ids$generics_Rally(ping, ids3); @@ -827,9 +835,9 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _189: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _210: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _189; + fork.left = _210; } const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; @@ -841,13 +849,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return t.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args15 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args15[0], " ", args15[1]]).v); + const args18 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args18[0], " ", args18[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _225: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_225.value))), undefined, true); - rs.refRelease(_225); + const _246: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_246.value))), undefined, true); + rs.refRelease(_246); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 88b3d9972..523d3a697 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -1024,6 +1024,52 @@ impl Source for Depth<0> { type Item = Counted<0>; } +/// A trait of more associated types than the crate's recursion limit, a struct with a field +/// of each that lends itself again, and its instance at `u8`, which translate as any +/// projections do, however many. +macro_rules! broad { + ($($assoc:ident $field:ident),* $(,)?) => { + trait Broad { + $(type $assoc;)* + } + + impl Broad for u8 { + $(type $assoc = u8;)* + } + + struct Wide { + $($field: S::$assoc,)* + next: Option<&'static Wide>, + } + + impl Wide { + fn new(n: u8) -> Self { + Self { + $($field: n,)* + next: None, + } + } + } + }; +} + +broad!( + A0 a0, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9, A10 a10, A11 a11, A12 + a12, A13 a13, A14 a14, A15 a15, A16 a16, A17 a17, A18 a18, A19 a19, A20 a20, A21 a21, A22 + a22, A23 a23, A24 a24, A25 a25, A26 a26, A27 a27, A28 a28, A29 a29, A30 a30, A31 a31, A32 + a32, A33 a33, A34 a34, A35 a35, A36 a36, A37 a37, A38 a38, A39 a39, A40 a40, A41 a41, A42 + a42, A43 a43, A44 a44, A45 a45, A46 a46, A47 a47, A48 a48, A49 a49, A50 a50, A51 a51, A52 + a52, A53 a53, A54 a54, A55 a55, A56 a56, A57 a57, A58 a58, A59 a59, A60 a60, A61 a61, A62 + a62, A63 a63, A64 a64, A65 a65, A66 a66, A67 a67, A68 a68, A69 a69, A70 a70, A71 a71, A72 + a72, A73 a73, A74 a74, A75 a75, A76 a76, A77 a77, A78 a78, A79 a79, A80 a80, A81 a81, A82 + a82, A83 a83, A84 a84, A85 a85, A86 a86, A87 a87, A88 a88, A89 a89, A90 a90, A91 a91, A92 + a92, A93 a93, A94 a94, A95 a95, A96 a96, A97 a97, A98 a98, A99 a99, A100 a100, A101 a101, + A102 a102, A103 a103, A104 a104, A105 a105, A106 a106, A107 a107, A108 a108, A109 a109, A110 + a110, A111 a111, A112 a112, A113 a113, A114 a114, A115 a115, A116 a116, A117 a117, A118 + a118, A119 a119, A120 a120, A121 a121, A122 a122, A123 a123, A124 a124, A125 a125, A126 + a126, A127 a127, A128 a128, A129 a129 +); + /// Two types a source names, each holding the other. trait Duo { type Ping; @@ -1179,6 +1225,9 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { ), ); + let wide = Wide::new(data.len() as u8); + put(out, &format!("{} {} {}", wide.a0, wide.a129, wide.next.is_none())); + let mut ping = Ping:: { id: 0xee, pong: None }; for (i, &b) in data.iter().enumerate() { ping = Ping { diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 495b8dd63..d1463cc4b 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -482,9 +482,9 @@ impl<'tcx> Translator<'_, 'tcx> { /// Fill in the projection arguments of the instance `pending[i]` typed, from its /// definition's projections so far. Filling them in may name more projections or /// instances, which are filled in turn until none is new; a type that holds itself at - /// ever other arguments would name them without end, so an instance named through more - /// instances than the crate's recursion limit is refused, as is filling one in that - /// leaves its definition with more projections than that. + /// ever other arguments would name them without end, ever deeper, so an instance named + /// through more instances than the crate's recursion limit is refused, as is one whose + /// filling names a projection of projections nested deeper than that. fn fill_pending(&mut self, i: usize) { let p = &self.defining_state.pending[i]; let projections: Vec> = (self.defining_state.records[&p.adt].projections.iter()) @@ -512,7 +512,7 @@ impl<'tcx> Translator<'_, 'tcx> { } let env = at.map_or(self.typing_env(), |(_, env)| env); let named = |state: &Defining<'tcx>| at.map_or(0, |(did, _)| state.records[&did].projections.len()); - let before = self.defining_state.pending.len(); + let before = (named(&self.defining_state), self.defining_state.pending.len()); let outer = std::mem::replace(&mut self.defining, at); let outer_field = std::mem::replace(&mut self.defining_state.field, field); self.defining_state.projecting += 1; @@ -528,7 +528,16 @@ impl<'tcx> Translator<'_, 'tcx> { self.defining_state.filling = filling; self.defining = outer; self.defining_state.field = outer_field; - let refused = named(&self.defining_state) > limit; + let refused = at.is_some_and(|(did, _)| { + let nested = |p: Ty<'tcx>| { + (p.walk()) + .filter(|arg| matches!(arg.as_type().map(|t| *t.kind()), Some(ty::Alias(..)))) + .count() + }; + (self.defining_state.records[&did].projections[before.0..]) + .iter() + .any(|&(p, _)| nested(p) > limit) + }); if refused { let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); self.err( @@ -539,7 +548,7 @@ impl<'tcx> Translator<'_, 'tcx> { ); tys = vec![Type::Builtin("unknown"); projections.len()]; // Nor are the instances it named filled in, which would name more. - for named in &mut self.defining_state.pending[before..] { + for named in &mut self.defining_state.pending[before.1..] { named.filled = Some(Vec::new()); named.refused = true; } From f1851e9464ee788512891448f182fd1961ee7127 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:15:14 -0800 Subject: [PATCH 13/21] chore(ts): sort the reject fixture's roots Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/ts.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index dd3282981..b5be22d96 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -213,8 +213,8 @@ roots = [ "values::projected_inert", "values::projected_nested", "values::projected_over", - "values::projected_spread", "values::projected_sprawl", + "values::projected_spread", "values::projected_uninit", "values::units_compared", ] From f97d9ecdf0bf6aa5d1594c3c0741998be7d923dc Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:37:29 -0800 Subject: [PATCH 14/21] fix(ts): bound filling an open definition's instances by rounds, at the recursion limit Each round of the fill loop fills what the round before named, so a finite layout settles in as many rounds as its chain is long, and endless growth of any kind never settles. More rounds than the crate's recursion limit are refused at the definition. This replaces the instance-depth and nested-projection-count guards, which missed growth inside ordinary type arguments and refused 130 sibling projections two deep. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/expected/core/bool.ts | 9 ++ .../lang/shapes/expected/core/option.ts | 9 ++ .../lang/shapes/expected/generics.Remade.ts | 15 +++ .../lang/shapes/expected/generics.Sibling.ts | 20 ++++ ts/fixtures/lang/shapes/expected/generics.ts | 56 ++++++---- ts/fixtures/lang/shapes/src/generics.rs | 47 ++++++++ ts/fixtures/reject/expected.txt | 7 +- ts/fixtures/reject/src/values.rs | 33 ++++++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/defined.rs | 102 ++++++------------ 10 files changed, 204 insertions(+), 95 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Remade.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Sibling.ts diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index ebed49990..7695fc1da 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -4,6 +4,8 @@ import * as rs from "../rs.ts"; import type * as Pair from "../fmt.Pair.ts"; import type * as Layer from "../generics.Layer.ts"; import type * as Peel from "../generics.Peel.ts"; +import type * as Remade from "../generics.Remade.ts"; +import type * as Sibling from "../generics.Sibling.ts"; import type * as Ticked from "../generics.Ticked.ts"; import type * as Top from "../generics.Top.ts"; import type * as Void from "../shapes.Void.ts"; @@ -43,6 +45,13 @@ export function thenSome$0bd8znh(self: boolean, t: Layer.Layer, rs.u8, rs.u8>): Sibling.Sibling, rs.u8, rs.u8> | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$0tbnivw(self: boolean, t: Ticked.Ticked): Ticked.Ticked | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index 8b108c8b7..52f50b1dd 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -12,6 +12,8 @@ import type * as Layer from "../generics.Layer.ts"; import type * as Looped from "../generics.Looped.ts"; import type * as Node from "../generics.Node.ts"; import type * as Peel from "../generics.Peel.ts"; +import type * as Remade from "../generics.Remade.ts"; +import type * as Sibling from "../generics.Sibling.ts"; import type * as Ticked from "../generics.Ticked.ts"; import type * as Top from "../generics.Top.ts"; import { from$convert_From_shapes_Dir_u8 } from "../shapes.ts"; @@ -125,6 +127,13 @@ export function map$1a3j889(self: Layer.Layer, boolean, rs.u8 return f(self); } +export function map$0e9v36n(self: Sibling.Sibling, rs.u8, rs.u8> | undefined, f: (a0: Sibling.Sibling, rs.u8, rs.u8>) => [rs.u8, boolean]): [rs.u8, boolean] | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0ggarad(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.Vec): rs.Vec | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Remade.ts b/ts/fixtures/lang/shapes/expected/generics.Remade.ts new file mode 100644 index 000000000..e92a1b9bc --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Remade.ts @@ -0,0 +1,15 @@ +// Generated by rust2-ts. Do not edit. + +/** + * A source made once more, whose item is what its source makes of what it makes of 130 + * types: more projections than the crate's recursion limit, side by side, but only two deep. + */ +export class Remade { + _0: S; + + constructor(_0: S) { + this._0 = _0; + } +} + +//# sourceMappingURL=generics.Remade.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Sibling.ts b/ts/fixtures/lang/shapes/expected/generics.Sibling.ts new file mode 100644 index 000000000..465fcecd7 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Sibling.ts @@ -0,0 +1,20 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Remade from "./generics.Remade.ts"; + +/** + * A source's item, lending the same at a source made once more, whose item is a projection + * of 130 sibling projections, which settles at its second. + */ +export class Sibling { + item: Item; + next: Sibling, Made, rs.u8> | undefined; + + constructor(item: Item, next: Sibling, Made, rs.u8> | undefined) { + this.item = item; + this.next = next; + } +} + +//# sourceMappingURL=generics.Sibling.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index bfefe2379..b3a11ff5a 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -2,8 +2,8 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; -import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; +import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0rdxccx, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$0e9v36n as option_map$0e9v36n, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; @@ -40,8 +40,10 @@ import * as Pile from "./generics.Pile.ts"; import * as Ping from "./generics.Ping.ts"; import type * as Pong from "./generics.Pong.ts"; import type * as Rally from "./generics.Rally.ts"; +import type * as Remade from "./generics.Remade.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; +import * as Sibling from "./generics.Sibling.ts"; import * as Swap from "./generics.Swap.ts"; import * as Tagged from "./generics.Tagged.ts"; import type * as Tail from "./generics.Tail.ts"; @@ -735,7 +737,7 @@ export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { * once per path. */ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { - let _170!: Pong.Pong; + let _200!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -783,8 +785,8 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }); const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); const arg = node.item; - const args14 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args14[0], " ", args14[1], " ", args14[2]]).v); + const args16 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args16[0], " ", args16[1], " ", args16[2]]).v); const under: Layer.Layer, boolean, rs.u8> = new Layer.Layer(true, undefined); const layer: Layer.Layer = new Layer.Layer(((option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16) << 4) & 0xffff, thenSome$0bd8znh(rs.usizeRem(data.length, 2) === 1, under)); const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _39: rs.u8): Counted.Counted | undefined => { @@ -800,30 +802,38 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return [u.item, u.under !== undefined] as [boolean, boolean]; }); const arg2 = layer.item; - const args15 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_126, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args15[0], " ", args15[1], " ", args15[2]]).v); + const args17 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_126, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args17[0], " ", args17[1], " ", args17[2]]).v); const wide: generics_Wide.Wide = generics_Wide.new$u8(data.length & 0xff); const _148: boolean = wide.next === undefined; const arg3 = wide.a0; const _151: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); const arg4 = wide.a129; - const args16 = [_151, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_148, f)]; - put(out, rs.fmtFormat([args16[0], " ", args16[1], " ", args16[2]]).v); + const args18 = [_151, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_148, f)]; + put(out, rs.fmtFormat([args18[0], " ", args18[1], " ", args18[2]]).v); + const made: Sibling.Sibling, rs.u8, rs.u8> = new Sibling.Sibling(7, undefined); + const sibling: Sibling.Sibling = new Sibling.Sibling(((option_unwrapOr$1fr498u(rs.byteSliceLast(data), 0) as rs.u16) << 3) & 0xffff, thenSome$0rdxccx(rs.usizeRem(data.length, 3) === 0, made)); + const _179: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { + return [s.item, s.next === undefined] as [rs.u8, boolean]; + }); + const arg5 = sibling.item; + const args19 = [(f: rs.Formatter) => rs.u16Display(arg5, f), (f: rs.Formatter) => rs.fmtOption(_179, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; + put(out, rs.fmtFormat([args19[0], " ", args19[1]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _162: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_162 === rs.DONE) { + const _192: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_192 === rs.DONE) { break; } - const i: rs.usize = _162[0]; - const b2: rs.u8 = _162[1]; + const i: rs.usize = _192[0]; + const b2: rs.u8 = _192[1]; if (rs.usizeRem(i, 3) === 0) { - _170 = { tag: "Miss" } as Pong.Pong; + _200 = { tag: "Miss" } as Pong.Pong; } else { - _170 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; + _200 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b2, _170 as rs.OptionOf); + ping = new Ping.Ping(b2, _200 as rs.OptionOf); } const ids3: rs.ByteVec = new rs.ByteVec(); Ping.ids$generics_Rally(ping, ids3); @@ -835,9 +845,9 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _210: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _240: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _210; + fork.left = _240; } const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; @@ -849,13 +859,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return t.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args18 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args18[0], " ", args18[1]]).v); + const args21 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args21[0], " ", args21[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _246: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_246.value))), undefined, true); - rs.refRelease(_246); + const _276: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_276.value))), undefined, true); + rs.refRelease(_276); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 523d3a697..6a31c01bb 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -1070,6 +1070,47 @@ broad!( a126, A127 a127, A128 a128, A129 a129 ); +/// A source's item, and what it makes of any type. +trait Makes { + type Item; + type Made; +} + +impl Makes for u8 { + type Item = u16; + type Made = u8; +} + +/// A source made once more, whose item is what its source makes of what it makes of 130 +/// types: more projections than the crate's recursion limit, side by side, but only two deep. +struct Remade(S); + +macro_rules! siblings { + ($($n:literal),* $(,)?) => { + impl Makes for Remade { + type Item = S::Made<($(S::Made<[(); $n]>,)*)>; + type Made = u8; + } + }; +} + +siblings!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, + 126, 127, 128, 129 +); + +/// A source's item, lending the same at a source made once more, whose item is a projection +/// of 130 sibling projections, which settles at its second. +struct Sibling { + item: S::Item, + next: Option<&'static Sibling>>, +} + /// Two types a source names, each holding the other. trait Duo { type Ping; @@ -1227,6 +1268,12 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { let wide = Wide::new(data.len() as u8); put(out, &format!("{} {} {}", wide.a0, wide.a129, wide.next.is_none())); + let made: &'static Sibling> = &Sibling { item: 7, next: None }; + let sibling = Sibling:: { + item: u16::from(data.last().copied().unwrap_or(0)) << 3, + next: (data.len() % 3 == 0).then_some(made), + }; + put(out, &format!("{} {:?}", sibling.item, sibling.next.map(|s| (s.item, s.next.is_none())))); let mut ping = Ping:: { id: 0xee, pong: None }; for (i, &b) in data.iter().enumerate() { diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index d0f86ba23..2b71f080a 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -259,10 +259,7 @@ fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unch 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 fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit -fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:Sprawl+2:2: `values::Sprawl>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` @@ -284,14 +281,18 @@ fixtures/reject/src/values.rs:manual_impls+2:34: a constant of type `std::mem::M fixtures/reject/src/values.rs:mixed_byte+0:19: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_chain+1:27: `values::Nested` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:23: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` +fixtures/reject/src/values.rs:projected_grown+1:25: `values::Grown` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_sprawl+1:34: `values::Sprawl` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_spread+1:36: `values::Spread` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index bea161228..1155fb0f5 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -295,6 +295,39 @@ pub fn projected_chain(item: u8) -> u8 { nested.item } +/// A source's item, and what it makes of any type. +pub trait Gathers { + type Item; + type Many; +} + +impl Gathers for u8 { + type Item = u8; + type Many = T; +} + +/// A source gathered once more, whose item and what it makes are what its source makes, +/// gathered once more. +pub struct Gather(pub S); + +impl Gathers for Gather { + type Item = S::Many; + type Many = S::Many>; +} + +/// A type that holds itself at a gathered source, whose item is a projection at a type +/// argument gathered once more at each, one projection deep. +pub struct Grown { + pub item: S::Item, + pub next: Option<&'static Grown>>, +} + +/// `Grown`'s definition would name a new projection at each gathering. +pub fn projected_grown(item: u8) -> u8 { + let grown: Grown = Grown { item, next: None }; + grown.item +} + /// Parsing an address is not translated yet. pub fn addr_parsed(s: &str) -> Result { s.parse() diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index b5be22d96..e6bf8e2e7 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -210,6 +210,7 @@ roots = [ "values::projected_deep", "values::projected_disposable", "values::projected_fork", + "values::projected_grown", "values::projected_inert", "values::projected_nested", "values::projected_over", diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index d1463cc4b..3184cfa17 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -58,9 +58,6 @@ pub struct Defining<'tcx> { pending: Vec>, /// How many projections every record holds, which grows while instances are filled. recorded: usize, - /// The [`Pending::depth`] and [`Pending::root`] of the instance whose projection - /// arguments are being filled. - filling: Option<(usize, usize)>, /// The field of the definition being typed whose type is. pub field: Option, /// The instances being typed in the type expression being typed, which a definition @@ -116,12 +113,6 @@ struct Pending<'tcx> { span: Span, /// Its projection arguments as last filled in. filled: Option>, - /// Whether it was refused, and so is filled for good. - refused: bool, - /// How many instances' projection arguments named it in turn, from one a definition's - /// fields typed, and that one, by its index in [`Defining::pending`]. - depth: usize, - root: usize, } impl<'tcx> Translator<'_, 'tcx> { @@ -257,9 +248,6 @@ impl<'tcx> Translator<'_, 'tcx> { field: state.field, span, filled: None, - refused: false, - depth: state.filling.map_or(0, |(depth, _)| depth + 1), - root: state.filling.map_or(state.pending.len(), |(_, root)| root), }); Some(Type::Later(state.pending.len() - 1)) } @@ -399,21 +387,47 @@ impl<'tcx> Translator<'_, 'tcx> { /// Complete the definition at `depth` in [`Defining::typing`] and every one typed with /// it: fill in the projection arguments of the instances they typed until no record /// grows, then close their records and settle what reads them. + /// + /// Each round fills in what the round before named, so a finite layout settles in as + /// many rounds as its chain of instances and projections is long. A type that holds + /// itself at ever other arguments never settles, so, as rustc bounds its own recursion, + /// more rounds than the crate's recursion limit are refused at the definition. fn complete(&mut self, depth: usize) { let tcx = self.tcx; let (members, pending, items) = { let typing = &self.defining_state.typing[depth]; (typing.members, typing.pending, typing.items) }; - loop { + let mut rounds = 0; + let settled = loop { let recorded = self.defining_state.recorded; let count = self.defining_state.pending.len(); for i in pending..count { self.fill_pending(i); } if self.defining_state.recorded == recorded && self.defining_state.pending.len() == count { - break; + break true; } + rounds += 1; + if rounds > tcx.recursion_limit().0 { + break false; + } + }; + if !settled { + let state = &mut self.defining_state; + for p in &mut state.pending[pending..] { + p.filled = Some(vec![Type::Builtin("unknown"); state.records[&p.adt].projections.len()]); + } + // The definition completing them all, which finished typing last. + let root = state.members.last().expect("the definition completing"); + let instance = Ty::new_adt(tcx, tcx.adt_def(root.adt), ty::GenericArgs::identity_for_item(tcx, root.adt)); + let span = tcx.def_span(root.adt); + self.err( + span, + format!( + "`{instance}` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit" + ), + ); } let done: Vec = self.defining_state.members.drain(members..).collect(); for member in &done { @@ -481,43 +495,21 @@ impl<'tcx> Translator<'_, 'tcx> { /// Fill in the projection arguments of the instance `pending[i]` typed, from its /// definition's projections so far. Filling them in may name more projections or - /// instances, which are filled in turn until none is new; a type that holds itself at - /// ever other arguments would name them without end, ever deeper, so an instance named - /// through more instances than the crate's recursion limit is refused, as is one whose - /// filling names a projection of projections nested deeper than that. + /// instances, which the next round of [`Self::complete`] fills in turn. fn fill_pending(&mut self, i: usize) { let p = &self.defining_state.pending[i]; let projections: Vec> = (self.defining_state.records[&p.adt].projections.iter()) .map(|(p, _)| *p) .collect(); - if p.refused || p.filled.as_ref().is_some_and(|f| f.len() == projections.len()) { - return; - } - let (adt, args, at, field, span, depth, root) = (p.adt, p.args, p.at, p.field, p.span, p.depth, p.root); - let limit = self.tcx.recursion_limit().0; - if depth > limit { - // Named by the one its definition's field typed, which the source wrote. - let root = &self.defining_state.pending[root]; - let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(root.adt), root.args); - self.err( - root.span, - format!( - "`{instance}` is not supported: its projections' values nest instances past the crate's recursion limit" - ), - ); - let p = &mut self.defining_state.pending[i]; - p.filled = Some(vec![Type::Builtin("unknown"); projections.len()]); - p.refused = true; + if p.filled.as_ref().is_some_and(|f| f.len() == projections.len()) { return; } + let (args, at, field, span) = (p.args, p.at, p.field, p.span); let env = at.map_or(self.typing_env(), |(_, env)| env); - let named = |state: &Defining<'tcx>| at.map_or(0, |(did, _)| state.records[&did].projections.len()); - let before = (named(&self.defining_state), self.defining_state.pending.len()); let outer = std::mem::replace(&mut self.defining, at); let outer_field = std::mem::replace(&mut self.defining_state.field, field); self.defining_state.projecting += 1; - let filling = self.defining_state.filling.replace((depth, root)); - let mut tys: Vec = projections + let tys: Vec = projections .iter() .map(|&p| { let at = super::copy::instantiate(self.tcx, env, p, args); @@ -525,37 +517,9 @@ impl<'tcx> Translator<'_, 'tcx> { }) .collect(); self.defining_state.projecting -= 1; - self.defining_state.filling = filling; self.defining = outer; self.defining_state.field = outer_field; - let refused = at.is_some_and(|(did, _)| { - let nested = |p: Ty<'tcx>| { - (p.walk()) - .filter(|arg| matches!(arg.as_type().map(|t| *t.kind()), Some(ty::Alias(..)))) - .count() - }; - (self.defining_state.records[&did].projections[before.0..]) - .iter() - .any(|&(p, _)| nested(p) > limit) - }); - if refused { - let instance = Ty::new_adt(self.tcx, self.tcx.adt_def(adt), args); - self.err( - span, - format!( - "`{instance}` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end" - ), - ); - tys = vec![Type::Builtin("unknown"); projections.len()]; - // Nor are the instances it named filled in, which would name more. - for named in &mut self.defining_state.pending[before.1..] { - named.filled = Some(Vec::new()); - named.refused = true; - } - } - let p = &mut self.defining_state.pending[i]; - p.filled = Some(tys); - p.refused = refused; + self.defining_state.pending[i].filled = Some(tys); } } From 6a63ba9347cf94002783170a7f7d526e91c34e9b Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:38:13 -0800 Subject: [PATCH 15/21] test(ts): refusals of endless fills at their definitions Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 2 +- ts/fixtures/reject/census.txt | 7 ++++--- ts/fixtures/reject/expected.txt | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ts/README.md b/ts/README.md index 9fe4588b2..f9cae07a6 100644 --- a/ts/README.md +++ b/ts/README.md @@ -1168,7 +1168,7 @@ function without a mapping of its own that makes or reads an `Option` whose `Non or of a projection from one, that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, an instance of a generic type over type parameters whose projection is a type holding that instance again, in a generic type's definition (`Held>` where `Wrap::Item` is a `Held>`), a generic type that -holds itself at other arguments than its own parameters whose projections there are new ones each time, a `String`'s `capacity`, looking up a key by +holds itself at other arguments than its own parameters whose instances name new instances or projections for more rounds than the crate's recursion limit, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS copies that std makes, other than a map's, or hands a closure, as `iter_mut` and `and_modify` do, one into a `Result`'s payload passed on or kept, the adaptors diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index fd21ce9e1..27af42537 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -289,13 +289,14 @@ fixtures/reject/src/strs.rs:str_bytes_mut+2:13: `core::str::::get_unch 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 fixtures/reject/src/values.rs:Beside+0:1: union `values::Beside`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:Bits+0:1: union `values::Bits`, whose fields overlap in `u32` and `f64`, which JS keeps apart +fixtures/reject/src/values.rs:Grown+0:1: `values::Grown` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit fixtures/reject/src/values.rs:Mixed+0:1: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart -fixtures/reject/src/values.rs:Nested+2:2: `values::Nested<::Next>` is not supported: a type that holds itself at other arguments than its own parameters, through a projection of them, which would name new projections without end +fixtures/reject/src/values.rs:Nested+0:1: `values::Nested` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end -fixtures/reject/src/values.rs:Sprawl+2:2: `values::Sprawl>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:Spread+2:2: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:Sprawl+0:1: `values::Sprawl` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit +fixtures/reject/src/values.rs:Spread+0:1: `values::Spread` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 61d31a1c5..f079d72e6 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -283,9 +283,13 @@ fixtures/reject/src/strs.rs:cow_made+2:53: constructing `std::borrow::Cow<'_, st 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 +fixtures/reject/src/values.rs:Grown+0:1: `values::Grown` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:HoldsByte+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:Links+0:1: dropping `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: what its glue drops through, by its projections, nests past the crate's recursion limit +fixtures/reject/src/values.rs:Nested+0:1: `values::Nested` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:Over+1:2: `values::Lent>` is not supported yet: a projection of its parameters is a type whose arguments hold `values::Lent>` again, so its TypeScript type arguments would never end +fixtures/reject/src/values.rs:Sprawl+0:1: `values::Sprawl` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit +fixtures/reject/src/values.rs:Spread+0:1: `values::Spread` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::AddrParseError` from another crate fixtures/reject/src/values.rs:addr_parsed+0:32: unsupported type `std::net::Ipv4Addr` from another crate fixtures/reject/src/values.rs:addr_parsed+1:4: unsupported call to `core::str::::parse::` @@ -307,18 +311,14 @@ fixtures/reject/src/values.rs:manual_impls+2:34: a constant of type `std::mem::M fixtures/reject/src/values.rs:mixed_byte+0:19: union `values::Mixed`, whose fields overlap in `u32` and `f32`, which JS keeps apart fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_chain+1:27: `values::Nested` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:23: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` -fixtures/reject/src/values.rs:projected_grown+1:25: `values::Grown` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_sprawl+1:34: `values::Sprawl` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:projected_spread+1:36: `values::Spread` is not supported: it holds itself at ever other arguments, whose projections name new instances or projections past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own From 904df1534c0d39b41594a4ae04077c046e37d5af Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:39:14 -0800 Subject: [PATCH 16/21] style(ts): format Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/src/generics.rs | 21 +++++++++++++-------- ts/src/lower/defined.rs | 6 +++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 6a31c01bb..59f6b2622 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -1095,13 +1095,11 @@ macro_rules! siblings { } siblings!( - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, - 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, - 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, - 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, - 126, 127, 128, 129 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129 ); /// A source's item, lending the same at a source made once more, whose item is a projection @@ -1273,7 +1271,14 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { item: u16::from(data.last().copied().unwrap_or(0)) << 3, next: (data.len() % 3 == 0).then_some(made), }; - put(out, &format!("{} {:?}", sibling.item, sibling.next.map(|s| (s.item, s.next.is_none())))); + put( + out, + &format!( + "{} {:?}", + sibling.item, + sibling.next.map(|s| (s.item, s.next.is_none())) + ), + ); let mut ping = Ping:: { id: 0xee, pong: None }; for (i, &b) in data.iter().enumerate() { diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 3184cfa17..15f67f1f9 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -420,7 +420,11 @@ impl<'tcx> Translator<'_, 'tcx> { } // The definition completing them all, which finished typing last. let root = state.members.last().expect("the definition completing"); - let instance = Ty::new_adt(tcx, tcx.adt_def(root.adt), ty::GenericArgs::identity_for_item(tcx, root.adt)); + let instance = Ty::new_adt( + tcx, + tcx.adt_def(root.adt), + ty::GenericArgs::identity_for_item(tcx, root.adt), + ); let span = tcx.def_span(root.adt); self.err( span, From b0425d55d58172c278d075b2d8164eb1ad669bc3 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 14:13:21 -0800 Subject: [PATCH 17/21] fix(ts): complete a definition with the one further out its fill names, and check held instances' Options Filling an open definition's instances can name an instance of a definition further out that is still being typed; the inner one now completes with it instead of alone with too few projection arguments. A definition also takes in the `Option` and `Poll` parts each generic instance over its parameters decided, so `Outside<()>` holding `Projected<()>` is checked as `Projected<()>` is. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/expected/core/bool.ts | 20 +++ .../lang/shapes/expected/core/option.ts | 20 +++ .../lang/shapes/expected/generics.Copse.ts | 16 +++ .../lang/shapes/expected/generics.Grove.ts | 18 +++ .../lang/shapes/expected/generics.Seed.ts | 7 ++ .../lang/shapes/expected/generics.Shoot.ts | 17 +++ .../lang/shapes/expected/generics.Sprout.ts | 7 ++ .../lang/shapes/expected/generics.Twig.ts | 17 +++ ts/fixtures/lang/shapes/expected/generics.ts | 118 +++++++++++++----- ts/fixtures/lang/shapes/src/generics.rs | 72 +++++++++++ ts/fixtures/reject/census.txt | 1 + ts/fixtures/reject/expected.txt | 1 + ts/fixtures/reject/src/values.rs | 15 +++ ts/fixtures/reject/ts.toml | 1 + ts/src/lower/defined.rs | 92 ++++++++++++-- ts/src/lower/mod.rs | 1 + 16 files changed, 380 insertions(+), 43 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Copse.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Grove.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Seed.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Shoot.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Sprout.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Twig.ts diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index 7695fc1da..603f7536e 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -2,12 +2,18 @@ import * as rs from "../rs.ts"; import type * as Pair from "../fmt.Pair.ts"; +import type * as Copse from "../generics.Copse.ts"; +import type * as Grove from "../generics.Grove.ts"; import type * as Layer from "../generics.Layer.ts"; import type * as Peel from "../generics.Peel.ts"; import type * as Remade from "../generics.Remade.ts"; +import type * as Seed from "../generics.Seed.ts"; +import type * as Shoot from "../generics.Shoot.ts"; import type * as Sibling from "../generics.Sibling.ts"; +import type * as Sprout from "../generics.Sprout.ts"; import type * as Ticked from "../generics.Ticked.ts"; import type * as Top from "../generics.Top.ts"; +import type * as Twig from "../generics.Twig.ts"; import type * as Void from "../shapes.Void.ts"; export function thenSome$1ctlqen(self: boolean, t: rs.u8 | undefined): rs.u8 | undefined | rs.None { @@ -45,6 +51,13 @@ export function thenSome$0bd8znh(self: boolean, t: Layer.Layer): Shoot.Shoot | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$0rdxccx(self: boolean, t: Sibling.Sibling, rs.u8, rs.u8>): Sibling.Sibling, rs.u8, rs.u8> | undefined { if (!self) { return undefined; @@ -59,6 +72,13 @@ export function thenSome$0tbnivw(self: boolean, t: Ticked.Ticked): Ticked.Ticked return t; } +export function thenSome$0epplxd(self: boolean, t: Twig.Twig): Twig.Twig | undefined { + if (!self) { + return undefined; + } + return t; +} + export function thenSome$19pue01(self: boolean, t: rs.Poll): rs.Poll | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index 52f50b1dd..f7ad654fb 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -3,19 +3,25 @@ import * as rs from "../rs.ts"; import type * as Back from "../generics.Back.ts"; import type * as Carried from "../generics.Carried.ts"; +import type * as Copse from "../generics.Copse.ts"; import type * as Counted from "../generics.Counted.ts"; import type * as Defaulted from "../generics.Defaulted.ts"; import type * as Depth from "../generics.Depth.ts"; import type * as Forked from "../generics.Forked.ts"; +import type * as Grove from "../generics.Grove.ts"; import type * as Head from "../generics.Head.ts"; import type * as Layer from "../generics.Layer.ts"; import type * as Looped from "../generics.Looped.ts"; import type * as Node from "../generics.Node.ts"; import type * as Peel from "../generics.Peel.ts"; import type * as Remade from "../generics.Remade.ts"; +import type * as Seed from "../generics.Seed.ts"; +import type * as Shoot from "../generics.Shoot.ts"; import type * as Sibling from "../generics.Sibling.ts"; +import type * as Sprout from "../generics.Sprout.ts"; import type * as Ticked from "../generics.Ticked.ts"; import type * as Top from "../generics.Top.ts"; +import type * as Twig from "../generics.Twig.ts"; import { from$convert_From_shapes_Dir_u8 } from "../shapes.ts"; import type * as Dir from "../shapes.Dir.ts"; import type * as Meters from "../shapes.Meters.ts"; @@ -127,6 +133,13 @@ export function map$1a3j889(self: Layer.Layer, boolean, rs.u8 return f(self); } +export function map$1avgfgw(self: Shoot.Shoot | undefined, f: (a0: Shoot.Shoot) => boolean): boolean | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0e9v36n(self: Sibling.Sibling, rs.u8, rs.u8> | undefined, f: (a0: Sibling.Sibling, rs.u8, rs.u8>) => [rs.u8, boolean]): [rs.u8, boolean] | undefined { if (self === undefined) { return undefined; @@ -134,6 +147,13 @@ export function map$0e9v36n(self: Sibling.Sibling, rs.u8, r return f(self); } +export function map$1i2swnz(self: Twig.Twig | undefined, f: (a0: Twig.Twig) => boolean): boolean | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0ggarad(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.Vec): rs.Vec | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Copse.ts b/ts/fixtures/lang/shapes/expected/generics.Copse.ts new file mode 100644 index 000000000..668bb0718 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Copse.ts @@ -0,0 +1,16 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Shoot from "./generics.Shoot.ts"; +import type * as Tail from "./generics.Tail.ts"; + +/** A [`Grove`] with its item typed first. */ +export type Copse = + | { tag: "Item"; _0: Item } + | { tag: "Branch"; _0: Shoot.Shoot }; + +export type Copse$generics_Tail = + | { tag: "Item"; _0: rs.u8 } + | { tag: "Branch"; _0: Shoot.Shoot }; + +//# sourceMappingURL=generics.Copse.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Grove.ts b/ts/fixtures/lang/shapes/expected/generics.Grove.ts new file mode 100644 index 000000000..9abb6a551 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Grove.ts @@ -0,0 +1,18 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Tail from "./generics.Tail.ts"; +import type * as Twig from "./generics.Twig.ts"; + +/** + * A grove's branch, or its source's item, its branch typed first: typing [`Twig`] names + * `Grove` while the grove's own definition, still being typed, has yet to name its + * item, so the twig completes with the grove. + */ +export type Grove = { tag: "Branch"; _0: Twig.Twig } | { tag: "Item"; _0: Item }; + +export type Grove$generics_Tail = + | { tag: "Branch"; _0: Twig.Twig } + | { tag: "Item"; _0: rs.u8 }; + +//# sourceMappingURL=generics.Grove.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Seed.ts b/ts/fixtures/lang/shapes/expected/generics.Seed.ts new file mode 100644 index 000000000..0ba851cf9 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Seed.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The source of a [`Shoot`]'s next. */ +export interface Seed { +} + +//# sourceMappingURL=generics.Seed.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Shoot.ts b/ts/fixtures/lang/shapes/expected/generics.Shoot.ts new file mode 100644 index 000000000..1d6cab527 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Shoot.ts @@ -0,0 +1,17 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Copse from "./generics.Copse.ts"; +import type * as Seed from "./generics.Seed.ts"; + +/** A [`Twig`] of a [`Copse`]. */ +export class Shoot<_S, Item> { + next: Shoot | undefined; + item: Item; + + constructor(next: Shoot | undefined, item: Item) { + this.next = next; + this.item = item; + } +} + +//# sourceMappingURL=generics.Shoot.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Sprout.ts b/ts/fixtures/lang/shapes/expected/generics.Sprout.ts new file mode 100644 index 000000000..eedb0cece --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Sprout.ts @@ -0,0 +1,7 @@ +// Generated by rust2-ts. Do not edit. + +/** The source of a [`Twig`]'s next. */ +export interface Sprout { +} + +//# sourceMappingURL=generics.Sprout.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Twig.ts b/ts/fixtures/lang/shapes/expected/generics.Twig.ts new file mode 100644 index 000000000..839126ec5 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Twig.ts @@ -0,0 +1,17 @@ +// Generated by rust2-ts. Do not edit. + +import type * as Grove from "./generics.Grove.ts"; +import type * as Sprout from "./generics.Sprout.ts"; + +/** A twig lending the twig at [`Sprout`], whose item is a grove, and its source's item. */ +export class Twig<_S, Item> { + next: Twig | undefined; + item: Item; + + constructor(next: Twig | undefined, item: Item) { + this.next = next; + this.item = item; + } +} + +//# sourceMappingURL=generics.Twig.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 3853f67c9..793cab115 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -2,8 +2,8 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; -import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0rdxccx, thenSome$0tbnivw, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$0e9v36n as option_map$0e9v36n, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; +import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0czqe00, thenSome$0rdxccx, thenSome$0tbnivw, thenSome$0epplxd, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$1avgfgw as option_map$1avgfgw, map$0e9v36n as option_map$0e9v36n, map$1i2swnz as option_map$1i2swnz, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; @@ -12,6 +12,7 @@ import * as Batch from "./generics.Batch.ts"; import * as Branch from "./generics.Branch.ts"; import * as Carried from "./generics.Carried.ts"; import * as Chain from "./generics.Chain.ts"; +import type * as Copse from "./generics.Copse.ts"; import * as Counted from "./generics.Counted.ts"; import * as Deep from "./generics.Deep.ts"; import * as Defaulted from "./generics.Defaulted.ts"; @@ -23,6 +24,7 @@ import * as Fixed from "./generics.Fixed.ts"; import type * as Flags from "./generics.Flags.ts"; import * as Forked from "./generics.Forked.ts"; import * as Ghost from "./generics.Ghost.ts"; +import type * as Grove from "./generics.Grove.ts"; import type * as Head from "./generics.Head.ts"; import * as Kept from "./generics.Kept.ts"; import * as Layer from "./generics.Layer.ts"; @@ -43,7 +45,10 @@ import type * as Rally from "./generics.Rally.ts"; import type * as Remade from "./generics.Remade.ts"; import * as Run from "./generics.Run.ts"; import * as Runs from "./generics.Runs.ts"; +import type * as Seed from "./generics.Seed.ts"; +import * as Shoot from "./generics.Shoot.ts"; import * as Sibling from "./generics.Sibling.ts"; +import type * as Sprout from "./generics.Sprout.ts"; import * as Swap from "./generics.Swap.ts"; import * as Tagged from "./generics.Tagged.ts"; import type * as Tail from "./generics.Tail.ts"; @@ -51,6 +56,7 @@ import * as Ticked from "./generics.Ticked.ts"; import type * as Ticks from "./generics.Ticks.ts"; import type * as Top from "./generics.Top.ts"; import * as Tree from "./generics.Tree.ts"; +import * as Twig from "./generics.Twig.ts"; import * as generics_Wide from "./generics.Wide.ts"; import * as Wrap from "./generics.Wrap.ts"; import { put } from "./harness.ts"; @@ -728,7 +734,11 @@ export function projectedCycles(data: rs.ByteSlice, out: rs.ByteVec): void { * once per path. */ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { - let _200!: Pong.Pong; + let grove!: Grove.Grove; + let copse!: Copse.Copse; + let grown!: [rs.u8, boolean | undefined]; + let sown!: [rs.u8, boolean | undefined]; + let _257!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -776,8 +786,52 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }); const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); const arg = node.item; - const args16 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args16[0], " ", args16[1], " ", args16[2]]).v); + const args18 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args18[0], " ", args18[1], " ", args18[2]]).v); + const _98: rs.u8 | undefined = rs.byteSliceFirst(data); + const twig: Twig.Twig = new Twig.Twig(undefined, { tag: "Item", _0: 5 } as Grove.Grove); + if (_98 === undefined) { + grove = { tag: "Item", _0: data.length & 0xff } as Grove.Grove; + } else { + const b2: rs.u8 = _98; + grove = { tag: "Branch", _0: new Twig.Twig(thenSome$0epplxd(rs.u8Rem(b2, 2) === 0, twig), b2) } as Grove.Grove; + } + const _110: rs.u8 | undefined = rs.byteSliceLast(data); + const shoot: Shoot.Shoot = new Shoot.Shoot(undefined, { tag: "Item", _0: 6 } as Copse.Copse); + if (_110 === undefined) { + copse = { tag: "Item", _0: 0 } as Copse.Copse; + } else { + const b3: rs.u8 = _110; + copse = { tag: "Branch", _0: new Shoot.Shoot(thenSome$0czqe00(rs.u8Rem(b3, 2) === 1, shoot), ~b3 & 0xff) } as Copse.Copse; + } + const _120: Grove.Grove = grove; + if (_120.tag === "Branch") { + grown = [_120._0.item, option_map$1i2swnz(_120._0.next, (t: Twig.Twig): boolean => { + if (t.item.tag === "Item") { + if (t.item._0 === 5) { + return true; + } + } + return false; + })] as [rs.u8, boolean | undefined]; + } else { + grown = [_120._0, undefined] as [rs.u8, boolean | undefined]; + } + const _130: Copse.Copse = copse; + if (_130.tag === "Item") { + sown = [_130._0, undefined] as [rs.u8, boolean | undefined]; + } else { + sown = [_130._0.item, option_map$1avgfgw(_130._0.next, (s: Shoot.Shoot): boolean => { + if (s.item.tag === "Item") { + if (s.item._0 === 6) { + return true; + } + } + return false; + })] as [rs.u8, boolean | undefined]; + } + const args19 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(grown[0], f), (f: rs.Formatter) => rs.fmtOption(grown[1], f, rs.fmtBool)], f), (f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(sown[0], f), (f: rs.Formatter) => rs.fmtOption(sown[1], f, rs.fmtBool)], f)]; + put(out, rs.fmtFormat([args19[0], " ", args19[1]]).v); const under: Layer.Layer, boolean, rs.u8> = new Layer.Layer(true, undefined); const layer: Layer.Layer = new Layer.Layer(((option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16) << 4) & 0xffff, thenSome$0bd8znh(rs.usizeRem(data.length, 2) === 1, under)); const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _39: rs.u8): Counted.Counted | undefined => { @@ -789,42 +843,42 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }), (c2: Counted.Counted): rs.u8 => { return c2.id; }); - const _126: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { + const _183: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { return [u.item, u.under !== undefined] as [boolean, boolean]; }); const arg2 = layer.item; - const args17 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_126, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args17[0], " ", args17[1], " ", args17[2]]).v); + const args20 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_183, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args20[0], " ", args20[1], " ", args20[2]]).v); const wide: generics_Wide.Wide = generics_Wide.new$u8(data.length & 0xff); - const _148: boolean = wide.next === undefined; + const _205: boolean = wide.next === undefined; const arg3 = wide.a0; - const _151: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); + const _208: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); const arg4 = wide.a129; - const args18 = [_151, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_148, f)]; - put(out, rs.fmtFormat([args18[0], " ", args18[1], " ", args18[2]]).v); + const args21 = [_208, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_205, f)]; + put(out, rs.fmtFormat([args21[0], " ", args21[1], " ", args21[2]]).v); const made: Sibling.Sibling, rs.u8, rs.u8> = new Sibling.Sibling(7, undefined); const sibling: Sibling.Sibling = new Sibling.Sibling(((option_unwrapOr$1fr498u(rs.byteSliceLast(data), 0) as rs.u16) << 3) & 0xffff, thenSome$0rdxccx(rs.usizeRem(data.length, 3) === 0, made)); - const _179: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { - return [s.item, s.next === undefined] as [rs.u8, boolean]; + const _236: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s2: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { + return [s2.item, s2.next === undefined] as [rs.u8, boolean]; }); const arg5 = sibling.item; - const args19 = [(f: rs.Formatter) => rs.u16Display(arg5, f), (f: rs.Formatter) => rs.fmtOption(_179, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; - put(out, rs.fmtFormat([args19[0], " ", args19[1]]).v); + const args22 = [(f: rs.Formatter) => rs.u16Display(arg5, f), (f: rs.Formatter) => rs.fmtOption(_236, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; + put(out, rs.fmtFormat([args22[0], " ", args22[1]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _192: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_192 === rs.DONE) { + const _249: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_249 === rs.DONE) { break; } - const i: rs.usize = _192[0]; - const b2: rs.u8 = _192[1]; + const i: rs.usize = _249[0]; + const b4: rs.u8 = _249[1]; if (rs.usizeRem(i, 3) === 0) { - _200 = { tag: "Miss" } as Pong.Pong; + _257 = { tag: "Miss" } as Pong.Pong; } else { - _200 = { tag: "Hit", _0: ~b2 & 0xff, _1: ping } as Pong.Pong; + _257 = { tag: "Hit", _0: ~b4 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b2, _200 as rs.OptionOf); + ping = new Ping.Ping(b4, _257 as rs.OptionOf); } const ids3: rs.ByteVec = new rs.ByteVec(); Ping.ids$generics_Rally(ping, ids3); @@ -836,9 +890,9 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _240: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _297: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _240; + fork.left = _297; } const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; @@ -846,17 +900,17 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { const right: [rs.u8, rs.u8 | undefined, rs.u8 | undefined] | undefined = option_map$1mvacy3(below.right, (f2: Forked.Forked): [rs.u8, rs.u8 | undefined, rs.u8 | undefined] => { return [f2.id, option_map$1ug09xc(f2.left, (f3: Forked.Forked): rs.u8 => { return f3.id; - }), option_map$1ub01y7(f2.right, (t: Ticked.Ticked): rs.u8 => { - return t.id; + }), option_map$1ub01y7(f2.right, (t2: Ticked.Ticked): rs.u8 => { + return t2.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args21 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args21[0], " ", args21[1]]).v); + const args24 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args24[0], " ", args24[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _276: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_276.value))), undefined, true); - rs.refRelease(_276); + const _333: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_333.value))), undefined, true); + rs.refRelease(_333); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 59f6b2622..40db89e0e 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -982,6 +982,46 @@ impl Source for Tail { type Item = u8; } +/// A grove's branch, or its source's item, its branch typed first: typing [`Twig`] names +/// `Grove` while the grove's own definition, still being typed, has yet to name its +/// item, so the twig completes with the grove. +enum Grove { + Branch(Twig), + Item(S::Item), +} + +/// A twig lending the twig at [`Sprout`], whose item is a grove, and its source's item. +struct Twig { + next: Option<&'static Twig>, + item: S::Item, +} + +/// The source of a [`Twig`]'s next. +struct Sprout; + +impl Source for Sprout { + type Item = Grove; +} + +/// A [`Grove`] with its item typed first. +enum Copse { + Item(S::Item), + Branch(Shoot), +} + +/// A [`Twig`] of a [`Copse`]. +struct Shoot { + next: Option<&'static Shoot>, + item: S::Item, +} + +/// The source of a [`Shoot`]'s next. +struct Seed; + +impl Source for Seed { + type Item = Copse; +} + /// A layered source's item, and the item of the layer peeled off it. trait Layered { type Item; @@ -1240,6 +1280,38 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { let held = node.next.as_ref().map(|n| n.item.item); put(out, &format!("{ids:?} {} {held:?}", node.item)); + let twig: &'static Twig = &Twig { + next: None, + item: Grove::Item(5), + }; + let grove = match data.first() { + Some(&b) => Grove::::Branch(Twig { + next: (b % 2 == 0).then_some(twig), + item: b, + }), + None => Grove::Item(data.len() as u8), + }; + let shoot: &'static Shoot = &Shoot { + next: None, + item: Copse::Item(6), + }; + let copse = match data.last() { + Some(&b) => Copse::::Branch(Shoot { + next: (b % 2 == 1).then_some(shoot), + item: !b, + }), + None => Copse::Item(0), + }; + let grown = match &grove { + Grove::Branch(twig) => (twig.item, twig.next.map(|t| matches!(t.item, Grove::Item(5)))), + Grove::Item(item) => (*item, None), + }; + let sown = match &copse { + Copse::Branch(shoot) => (shoot.item, shoot.next.map(|s| matches!(s.item, Copse::Item(6)))), + Copse::Item(item) => (*item, None), + }; + put(out, &format!("{grown:?} {sown:?}")); + let under: &'static Layer> = &Layer { item: true, under: None, diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index 27af42537..cb1b1d629 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -323,6 +323,7 @@ fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_outside+0:26: `values::Outside<()>` is not supported: its field `inside` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index f079d72e6..652f8740c 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -318,6 +318,7 @@ fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_outside+0:26: `values::Outside<()>` is not supported: its field `inside` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 1155fb0f5..895badcf7 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -105,6 +105,21 @@ pub fn projected_nested(unit: bool) -> bool { unit } +/// A [`Projected`] held by another generic type at its own parameter. +pub struct Outside { + pub inside: Projected, +} + +/// `Outside<()>` holds `Projected<()>`'s `Option<()>`, whose `None` and `Some(None)` would +/// both be `rs.None`, though it never names `Projected<()>`. +pub fn projected_outside(outside: &Outside<()>) -> u8 { + match outside.inside.value { + None => 0, + Some(None) => 1, + Some(Some(())) => 2, + } +} + /// What a source yields, named by an associated type. pub trait Source { type Item; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 095f52095..30be5b070 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -221,6 +221,7 @@ roots = [ "values::projected_inert", "values::projected_nested", "values::projected_over", + "values::projected_outside", "values::projected_sprawl", "values::projected_spread", "values::projected_uninit", diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 15f67f1f9..62106ccbb 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -23,8 +23,12 @@ pub struct Defined<'tcx> { /// Each projection from its type parameters its fields' types name, by the name of the /// TypeScript type parameter it is, after its own. pub projections: Vec<(Ty<'tcx>, String)>, - /// Each `Option`, `Poll`, or `ControlFlow` its fields' types type over a type parameter. + /// Each `Option`, `Poll`, or `ControlFlow` its fields' types type over a type parameter, + /// including those of each instance in [`Self::held`] at its arguments. pub decided: Vec>, + /// Each instance of the crate's generic types over its type parameters its fields' types + /// type, and the field whose type does. + held: Vec<(DefId, Ty<'tcx>)>, /// While it is typed, the shallowest definition still being typed that it was typed /// with, by its depth in [`Defining::typing`]; `None` once it is complete. open: Option, @@ -56,7 +60,8 @@ pub struct Defining<'tcx> { members: Vec, /// Instances of definitions still being typed, by the [`Type::Later`] each typed. pending: Vec>, - /// How many projections every record holds, which grows while instances are filled. + /// How many projections and decided parts every record holds, which grows while + /// instances are filled. recorded: usize, /// The field of the definition being typed whose type is. pub field: Option, @@ -156,14 +161,15 @@ impl<'tcx> Translator<'_, 'tcx> { span, }); let low = state.records[&adt].open.expect("an open definition"); - if low < depth { - state.typing.pop(); - let parent = state.typing.len() - 1; - state.lower_typing(parent, low); + if low == depth && self.complete(depth) { + self.defining_state.typing.pop(); return; } - self.complete(depth); - self.defining_state.typing.pop(); + let state = &mut self.defining_state; + let low = state.records[&adt].open.expect("an open definition"); + state.typing.pop(); + let parent = state.typing.len() - 1; + state.lower_typing(parent, low); } /// The TypeScript parameter a projection `ty` from type parameters is in the definition @@ -220,6 +226,24 @@ impl<'tcx> Translator<'_, 'tcx> { defined: defined.clone(), option, }); + self.defining_state.recorded += 1; + } + } + + /// Note that the definition being typed typed `instance`, an instance of the crate's + /// generic type, so it checks the parts that instance's definition decided too. + pub(super) fn note_held(&mut self, instance: Ty<'tcx>) { + let (Some((adt, _)), Some(field)) = (self.defining, self.defining_state.field) else { + return; + }; + if !instance.has_param() { + return; + } + let Some(record) = self.defining_state.records.get_mut(&adt) else { + return; + }; + if record.open.is_some() && !record.held.contains(&(field, instance)) { + record.held.push((field, instance)); } } @@ -385,14 +409,17 @@ impl<'tcx> Translator<'_, 'tcx> { } /// Complete the definition at `depth` in [`Defining::typing`] and every one typed with - /// it: fill in the projection arguments of the instances they typed until no record - /// grows, then close their records and settle what reads them. + /// it: fill in the projection arguments of the instances they typed, and take in what + /// each instance they hold decided, until no record grows, then close their records and + /// settle what reads them. Filling may name an instance of a definition further out + /// that is still being typed, which then completes them with its own, so this returns + /// whether they completed. /// /// Each round fills in what the round before named, so a finite layout settles in as /// many rounds as its chain of instances and projections is long. A type that holds /// itself at ever other arguments never settles, so, as rustc bounds its own recursion, /// more rounds than the crate's recursion limit are refused at the definition. - fn complete(&mut self, depth: usize) { + fn complete(&mut self, depth: usize) -> bool { let tcx = self.tcx; let (members, pending, items) = { let typing = &self.defining_state.typing[depth]; @@ -405,6 +432,7 @@ impl<'tcx> Translator<'_, 'tcx> { for i in pending..count { self.fill_pending(i); } + self.take_held(members); if self.defining_state.recorded == recorded && self.defining_state.pending.len() == count { break true; } @@ -433,6 +461,10 @@ impl<'tcx> Translator<'_, 'tcx> { ), ); } + let root = self.defining_state.typing[depth].adt; + if self.defining_state.records[&root].open.is_some_and(|low| low < depth) { + return false; + } let done: Vec = self.defining_state.members.drain(members..).collect(); for member in &done { let record = self.defining_state.records.get_mut(&member.adt).expect("a record"); @@ -495,6 +527,44 @@ impl<'tcx> Translator<'_, 'tcx> { let info = self.adts[&member.adt].clone(); self.drop_glue(adt, &info, member.span); } + true + } + + /// Take into each definition from `members` on in [`Defining::members`] the parts each + /// instance it holds decided, at that instance's arguments, so an instance of the + /// definition checks them: `Outer` holding `Inner` checks `Inner`'s + /// `Option` at `Outer`'s instances. + fn take_held(&mut self, members: usize) { + let tcx = self.tcx; + let adts: Vec = self.defining_state.members[members..].iter().map(|m| m.adt).collect(); + for adt in adts { + let env = TypingEnv::post_analysis(tcx, adt); + let held = self.defining_state.records[&adt].held.clone(); + for (field, instance) in held { + let ty::Adt(def, args) = *instance.kind() else { + unreachable!("an instance of a generic type") + }; + let Some(record) = self.defining_state.records.get(&def.did()) else { + continue; + }; + for d in record.decided.clone() { + let part = super::copy::instantiate(tcx, env, d.part, args); + if self.defining_state.records[&adt].decided.iter().any(|d| d.part == part) { + continue; + } + let inner = super::copy::instantiate(tcx, env, d.inner, args); + if !self.erases_to(inner).param { + continue; + } + let outer = self.defining.replace((adt, env)); + let outer_field = self.defining_state.field.replace(field); + let defined = self.ty(inner, tcx.def_span(field)); + self.note_decided(part, inner, &defined, d.option); + self.defining = outer; + self.defining_state.field = outer_field; + } + } + } } /// Fill in the projection arguments of the instance `pending[i]` typed, from its diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index bf6a74e69..2c5a8dbf3 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1973,6 +1973,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { let ty::Adt(adt, args) = *instance.kind() else { unreachable!("an instance of a generic type") }; + self.note_held(instance); // Of a definition still being typed, whose instances are checked once it is complete. if let Some(later) = self.later_projections(adt.did(), args, span) { let mut tys: Vec = args.types().map(|t| self.ty(t, span)).collect(); From 3cf8d518c6c44375ce06dd17d8cb4bfb575110ee Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 14:13:42 -0800 Subject: [PATCH 18/21] chore(ts): sort the reject fixture's roots Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/ts.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 30be5b070..17ec4673f 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -220,8 +220,8 @@ roots = [ "values::projected_grown", "values::projected_inert", "values::projected_nested", - "values::projected_over", "values::projected_outside", + "values::projected_over", "values::projected_sprawl", "values::projected_spread", "values::projected_uninit", From 9ec4633e8fa9d4003071db278af6f5664bf20e5a Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 15:41:38 -0800 Subject: [PATCH 19/21] fix(ts): check what a held instance decides in its own frame, and ControlFlow and future parts A definition's checks keep the definition that typed each part and its arguments, so an instance of a type holding `Slot>` is checked as that slot's instance is. A `ControlFlow` breaking with what an instance decides, and a future of it, are checked at instances too. Only instances outside projection arguments are held, which an instance types at its own arguments, and a refused endless definition checks nothing, so checks no longer multiply with the instances. Co-Authored-By: Claude Opus 5.5 --- ts/README.md | 3 +- .../lang/shapes/expected/core/option.ts | 7 + .../lang/shapes/expected/generics.Slot.ts | 14 ++ .../lang/shapes/expected/generics.Uninit.ts | 13 ++ ts/fixtures/lang/shapes/expected/generics.ts | 103 +++++----- ts/fixtures/lang/shapes/expected/shapes.ts | 4 +- ts/fixtures/lang/shapes/src/generics.rs | 18 ++ ts/fixtures/reject/census.txt | 7 +- ts/fixtures/reject/expected.txt | 7 +- ts/fixtures/reject/src/values.rs | 54 ++++++ ts/fixtures/reject/ts.toml | 3 + ts/src/lower/defined.rs | 152 ++++++++++----- ts/src/lower/mod.rs | 183 ++++++++++-------- 13 files changed, 392 insertions(+), 176 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Slot.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Uninit.ts diff --git a/ts/README.md b/ts/README.md index f9cae07a6..e0805445c 100644 --- a/ts/README.md +++ b/ts/README.md @@ -1166,7 +1166,8 @@ function without a mapping of its own that makes or reads an `Option` whose `Non `rs.None`, an `Option` of an `Option` of `()` or another `Option`, an instance of a generic type whose field is an `Option` of a type parameter, or of a projection from one, that can be `undefined` though TypeScript's type of it can't, as a `MaybeUninit` -can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, an instance of a generic type +can, or an `Option` or a `Poll` of another form of it whose instance can be `undefined` or pending, a `ControlFlow` breaking with one that an instance +makes an `Option` or `Result`, or a future of one whose instance can be pending, each also through a generic type it holds, an instance of a generic type over type parameters whose projection is a type holding that instance again, in a generic type's definition (`Held>` where `Wrap::Item` is a `Held>`), a generic type that holds itself at other arguments than its own parameters whose instances name new instances or projections for more rounds than the crate's recursion limit, a `String`'s `capacity`, looking up a key by another type it borrows as through the crate's own `Borrow`, a `&mut` to a value JS diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index f7ad654fb..016a0e2b5 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -189,6 +189,13 @@ export function map$0xpm57l(self: rs.u8 | undefined, f: (a0: rs.u8) => Ticked.Ti return f(self); } +export function map$1yjmt5m(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u8): rs.u8 | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$0ydco3f(self: rs.u8 | undefined, f: (a0: rs.u8) => rs.u16): rs.u16 | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Slot.ts b/ts/fixtures/lang/shapes/expected/generics.Slot.ts new file mode 100644 index 000000000..89c53d092 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Slot.ts @@ -0,0 +1,14 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; + +/** An `Option` of a type parameter. */ +export class Slot { + value: rs.OptionOf; + + constructor(value: rs.OptionOf) { + this.value = value; + } +} + +//# sourceMappingURL=generics.Slot.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Uninit.ts b/ts/fixtures/lang/shapes/expected/generics.Uninit.ts new file mode 100644 index 000000000..dd54571ed --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Uninit.ts @@ -0,0 +1,13 @@ +// Generated by rust2-ts. Do not edit. + +import type * as generics_Slot from "./generics.Slot.ts"; + +/** + * A [`Slot`] of a `MaybeUninit` of its own parameter, which at a byte is `0` rather than + * `undefined`, as that slot's instance is. + */ +export interface Uninit { + slot: generics_Slot.Slot; +} + +//# sourceMappingURL=generics.Uninit.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 793cab115..346b8af5d 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -3,7 +3,7 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0czqe00, thenSome$0rdxccx, thenSome$0tbnivw, thenSome$0epplxd, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$1avgfgw as option_map$1avgfgw, map$0e9v36n as option_map$0e9v36n, map$1i2swnz as option_map$1i2swnz, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$1avgfgw as option_map$1avgfgw, map$0e9v36n as option_map$0e9v36n, map$1i2swnz as option_map$1i2swnz, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$1yjmt5m as option_map$1yjmt5m, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; @@ -48,6 +48,7 @@ import * as Runs from "./generics.Runs.ts"; import type * as Seed from "./generics.Seed.ts"; import * as Shoot from "./generics.Shoot.ts"; import * as Sibling from "./generics.Sibling.ts"; +import * as generics_Slot from "./generics.Slot.ts"; import type * as Sprout from "./generics.Sprout.ts"; import * as Swap from "./generics.Swap.ts"; import * as Tagged from "./generics.Tagged.ts"; @@ -57,6 +58,7 @@ import type * as Ticks from "./generics.Ticks.ts"; import type * as Top from "./generics.Top.ts"; import * as Tree from "./generics.Tree.ts"; import * as Twig from "./generics.Twig.ts"; +import type * as Uninit from "./generics.Uninit.ts"; import * as generics_Wide from "./generics.Wide.ts"; import * as Wrap from "./generics.Wrap.ts"; import { put } from "./harness.ts"; @@ -738,7 +740,7 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { let copse!: Copse.Copse; let grown!: [rs.u8, boolean | undefined]; let sown!: [rs.u8, boolean | undefined]; - let _257!: Pong.Pong; + let _275!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -786,27 +788,32 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }); const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); const arg = node.item; - const args18 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args18[0], " ", args18[1], " ", args18[2]]).v); - const _98: rs.u8 | undefined = rs.byteSliceFirst(data); + const args20 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args20[0], " ", args20[1], " ", args20[2]]).v); + const uninit: Uninit.Uninit = { slot: new generics_Slot.Slot(option_map$1yjmt5m(rs.byteSliceFirst(data), (_22: rs.u8): rs.u8 => { + return _22; + }) as rs.OptionOf) }; + const part = uninit.slot.value !== undefined; + put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtBool(part, f)]).v); + const _116: rs.u8 | undefined = rs.byteSliceFirst(data); const twig: Twig.Twig = new Twig.Twig(undefined, { tag: "Item", _0: 5 } as Grove.Grove); - if (_98 === undefined) { + if (_116 === undefined) { grove = { tag: "Item", _0: data.length & 0xff } as Grove.Grove; } else { - const b2: rs.u8 = _98; + const b2: rs.u8 = _116; grove = { tag: "Branch", _0: new Twig.Twig(thenSome$0epplxd(rs.u8Rem(b2, 2) === 0, twig), b2) } as Grove.Grove; } - const _110: rs.u8 | undefined = rs.byteSliceLast(data); + const _128: rs.u8 | undefined = rs.byteSliceLast(data); const shoot: Shoot.Shoot = new Shoot.Shoot(undefined, { tag: "Item", _0: 6 } as Copse.Copse); - if (_110 === undefined) { + if (_128 === undefined) { copse = { tag: "Item", _0: 0 } as Copse.Copse; } else { - const b3: rs.u8 = _110; + const b3: rs.u8 = _128; copse = { tag: "Branch", _0: new Shoot.Shoot(thenSome$0czqe00(rs.u8Rem(b3, 2) === 1, shoot), ~b3 & 0xff) } as Copse.Copse; } - const _120: Grove.Grove = grove; - if (_120.tag === "Branch") { - grown = [_120._0.item, option_map$1i2swnz(_120._0.next, (t: Twig.Twig): boolean => { + const _138: Grove.Grove = grove; + if (_138.tag === "Branch") { + grown = [_138._0.item, option_map$1i2swnz(_138._0.next, (t: Twig.Twig): boolean => { if (t.item.tag === "Item") { if (t.item._0 === 5) { return true; @@ -815,13 +822,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return false; })] as [rs.u8, boolean | undefined]; } else { - grown = [_120._0, undefined] as [rs.u8, boolean | undefined]; + grown = [_138._0, undefined] as [rs.u8, boolean | undefined]; } - const _130: Copse.Copse = copse; - if (_130.tag === "Item") { - sown = [_130._0, undefined] as [rs.u8, boolean | undefined]; + const _148: Copse.Copse = copse; + if (_148.tag === "Item") { + sown = [_148._0, undefined] as [rs.u8, boolean | undefined]; } else { - sown = [_130._0.item, option_map$1avgfgw(_130._0.next, (s: Shoot.Shoot): boolean => { + sown = [_148._0.item, option_map$1avgfgw(_148._0.next, (s: Shoot.Shoot): boolean => { if (s.item.tag === "Item") { if (s.item._0 === 6) { return true; @@ -830,8 +837,8 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return false; })] as [rs.u8, boolean | undefined]; } - const args19 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(grown[0], f), (f: rs.Formatter) => rs.fmtOption(grown[1], f, rs.fmtBool)], f), (f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(sown[0], f), (f: rs.Formatter) => rs.fmtOption(sown[1], f, rs.fmtBool)], f)]; - put(out, rs.fmtFormat([args19[0], " ", args19[1]]).v); + const args22 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(grown[0], f), (f: rs.Formatter) => rs.fmtOption(grown[1], f, rs.fmtBool)], f), (f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(sown[0], f), (f: rs.Formatter) => rs.fmtOption(sown[1], f, rs.fmtBool)], f)]; + put(out, rs.fmtFormat([args22[0], " ", args22[1]]).v); const under: Layer.Layer, boolean, rs.u8> = new Layer.Layer(true, undefined); const layer: Layer.Layer = new Layer.Layer(((option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16) << 4) & 0xffff, thenSome$0bd8znh(rs.usizeRem(data.length, 2) === 1, under)); const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _39: rs.u8): Counted.Counted | undefined => { @@ -843,56 +850,56 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }), (c2: Counted.Counted): rs.u8 => { return c2.id; }); - const _183: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { + const _201: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { return [u.item, u.under !== undefined] as [boolean, boolean]; }); const arg2 = layer.item; - const args20 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_183, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args20[0], " ", args20[1], " ", args20[2]]).v); + const args23 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_201, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args23[0], " ", args23[1], " ", args23[2]]).v); const wide: generics_Wide.Wide = generics_Wide.new$u8(data.length & 0xff); - const _205: boolean = wide.next === undefined; + const _223: boolean = wide.next === undefined; const arg3 = wide.a0; - const _208: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); + const _226: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); const arg4 = wide.a129; - const args21 = [_208, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_205, f)]; - put(out, rs.fmtFormat([args21[0], " ", args21[1], " ", args21[2]]).v); + const args24 = [_226, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_223, f)]; + put(out, rs.fmtFormat([args24[0], " ", args24[1], " ", args24[2]]).v); const made: Sibling.Sibling, rs.u8, rs.u8> = new Sibling.Sibling(7, undefined); const sibling: Sibling.Sibling = new Sibling.Sibling(((option_unwrapOr$1fr498u(rs.byteSliceLast(data), 0) as rs.u16) << 3) & 0xffff, thenSome$0rdxccx(rs.usizeRem(data.length, 3) === 0, made)); - const _236: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s2: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { + const _254: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s2: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { return [s2.item, s2.next === undefined] as [rs.u8, boolean]; }); const arg5 = sibling.item; - const args22 = [(f: rs.Formatter) => rs.u16Display(arg5, f), (f: rs.Formatter) => rs.fmtOption(_236, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; - put(out, rs.fmtFormat([args22[0], " ", args22[1]]).v); + const args25 = [(f: rs.Formatter) => rs.u16Display(arg5, f), (f: rs.Formatter) => rs.fmtOption(_254, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; + put(out, rs.fmtFormat([args25[0], " ", args25[1]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _249: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_249 === rs.DONE) { + const _267: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_267 === rs.DONE) { break; } - const i: rs.usize = _249[0]; - const b4: rs.u8 = _249[1]; + const i: rs.usize = _267[0]; + const b4: rs.u8 = _267[1]; if (rs.usizeRem(i, 3) === 0) { - _257 = { tag: "Miss" } as Pong.Pong; + _275 = { tag: "Miss" } as Pong.Pong; } else { - _257 = { tag: "Hit", _0: ~b4 & 0xff, _1: ping } as Pong.Pong; + _275 = { tag: "Hit", _0: ~b4 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b4, _257 as rs.OptionOf); + ping = new Ping.Ping(b4, _275 as rs.OptionOf); } const ids3: rs.ByteVec = new rs.ByteVec(); Ping.ids$generics_Rally(ping, ids3); put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids3), f, rs.u8Debug)]).v); - const below: Forked.Forked = new Forked.Forked(23, undefined as rs.OptionOf, new Forked.Forked(24, option_map$1olalbb(rs.byteSliceFirst(data), (_22: rs.u8): Forked.Forked => { - return new Forked.Forked(_22, undefined as rs.OptionOf, undefined as rs.OptionOf); - }) as rs.OptionOf, option_map$0xpm57l(rs.byteSliceLast(data), (_23: rs.u8): Ticked.Ticked => { - return tick(~_23 & 0xff); + const below: Forked.Forked = new Forked.Forked(23, undefined as rs.OptionOf, new Forked.Forked(24, option_map$1olalbb(rs.byteSliceFirst(data), (_23: rs.u8): Forked.Forked => { + return new Forked.Forked(_23, undefined as rs.OptionOf, undefined as rs.OptionOf); + }) as rs.OptionOf, option_map$0xpm57l(rs.byteSliceLast(data), (_25: rs.u8): Ticked.Ticked => { + return tick(~_25 & 0xff); }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _297: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _315: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _297; + fork.left = _315; } const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; @@ -904,13 +911,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return t2.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args24 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args24[0], " ", args24[1]]).v); + const args27 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args27[0], " ", args27[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _333: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_333.value))), undefined, true); - rs.refRelease(_333); + const _351: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_351.value))), undefined, true); + rs.refRelease(_351); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/expected/shapes.ts b/ts/fixtures/lang/shapes/expected/shapes.ts index 358fe7c80..2ff0e920f 100644 --- a/ts/fixtures/lang/shapes/expected/shapes.ts +++ b/ts/fixtures/lang/shapes/expected/shapes.ts @@ -23,7 +23,7 @@ import type * as Parts from "./shapes.Parts.ts"; import * as Point from "./shapes.Point.ts"; import * as Shape from "./shapes.Shape.ts"; import type * as Signed from "./shapes.Signed.ts"; -import type * as Slot from "./shapes.Slot.ts"; +import type * as shapes_Slot from "./shapes.Slot.ts"; import * as Track from "./shapes.Track.ts"; import * as Void from "./shapes.Void.ts"; import type * as shapes_Wide from "./shapes.Wide.ts"; @@ -1435,7 +1435,7 @@ export function payloads(_dataData: Uint8Array, _dataStart: number, dataLen: num } const part2: rs.u8 | undefined = parity(rs.u8WrappingAdd(n, 1)); const err2: rs.u8 | undefined = part2 === undefined ? out$u8.err as rs.u8 : undefined; - const slot: Slot.Slot = { res: (err2 !== undefined ? rs.Err(err2 as rs.u8) : rs.Ok(part2 as rs.u8)) as rs.Result }; + const slot: shapes_Slot.Slot = { res: (err2 !== undefined ? rs.Err(err2 as rs.u8) : rs.Ok(part2 as rs.u8)) as rs.Result }; if (!slot.res.ok) { const _33: rs.u8 = rs.u8WrappingSub(slot.res.error, 4); slot.res = { ok: false, error: _33 }; diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 40db89e0e..61a7c8b73 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -1022,6 +1022,17 @@ impl Source for Seed { type Item = Copse; } +/// An `Option` of a type parameter. +struct Slot { + value: Option, +} + +/// A [`Slot`] of a `MaybeUninit` of its own parameter, which at a byte is `0` rather than +/// `undefined`, as that slot's instance is. +struct Uninit { + slot: Slot>, +} + /// A layered source's item, and the item of the layer peeled off it. trait Layered { type Item; @@ -1280,6 +1291,13 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { let held = node.next.as_ref().map(|n| n.item.item); put(out, &format!("{ids:?} {} {held:?}", node.item)); + let uninit = Uninit:: { + slot: Slot { + value: data.first().map(|&b| std::mem::MaybeUninit::new(b)), + }, + }; + put(out, &format!("{}", uninit.slot.value.is_some())); + let twig: &'static Twig = &Twig { next: None, item: Grove::Item(5), diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index cb1b1d629..d78966abc 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -310,6 +310,7 @@ fixtures/reject/src/values.rs:generic_nested+1:6: `values::Maybe>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:generic_uninit_byte+1:2: `values::MaybeByte` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` +fixtures/reject/src/values.rs:generic_uninit_byte_open+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:2: unsupported call to ` as std::fmt::Debug>::fmt` fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to ` as std::cmp::PartialEq>::eq` @@ -317,15 +318,19 @@ fixtures/reject/src/values.rs:manual_impls+2:34: a constant of type `std::mem::M fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_flow+0:23: `values::Flowing` is not supported: its field `flow` is `std::ops::ControlFlow<::Break, u8>`, typed as an `rs.Result` breaking with `::Break`, which `std::ops::ControlFlow, u8>` is not, as `?` makes its break fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:23: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` +fixtures/reject/src/values.rs:projected_future+0:25: `values::Awaiting` is not supported: its field `future` holds a future of `std::task::Poll`, which can be `rs.Pending`, as the future's own `Pending` is +fixtures/reject/src/values.rs:projected_future+0:52: `values::Awaiting` is not supported: its field `future` holds a future of `std::task::Poll`, which can be `rs.Pending`, as the future's own `Pending` is fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_outside+0:26: `values::Outside<()>` is not supported: its field `inside` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_outside+0:26: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases +fixtures/reject/src/values.rs:projected_uninit_slot+0:30: `values::Slot>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:70: comparing pointers to `places::Bare`, which has no bytes and so no address of its own diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index 652f8740c..bae2838f9 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -304,6 +304,7 @@ fixtures/reject/src/values.rs:generic_nested+1:6: `values::Maybe>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:generic_uninit+1:2: `values::Maybe>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:generic_uninit_byte+1:2: `values::MaybeByte` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` +fixtures/reject/src/values.rs:generic_uninit_byte_open+1:2: `values::ByteAnd` is not supported: its field `value` is `Option>`, typed `std::mem::MaybeUninit | rs.None`, whose `None` would be `undefined` as `Option>` fixtures/reject/src/values.rs:lists+1:4: `std::collections::LinkedList::::retain` with a mutable reference to a value JS copies, which its mapping would copy fixtures/reject/src/values.rs:manual_impls+2:11: unsupported call to ` as std::fmt::Debug>::fmt` fixtures/reject/src/values.rs:manual_impls+2:25: unsupported call to ` as std::cmp::PartialEq>::eq` @@ -312,15 +313,19 @@ fixtures/reject/src/values.rs:mixed_byte+0:19: union `values::Mixed`, whose fiel fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit +fixtures/reject/src/values.rs:projected_flow+0:23: `values::Flowing` is not supported: its field `flow` is `std::ops::ControlFlow<::Break, u8>`, typed as an `rs.Result` breaking with `::Break`, which `std::ops::ControlFlow, u8>` is not, as `?` makes its break fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:23: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `std::option::Option::>::flatten` +fixtures/reject/src/values.rs:projected_future+0:25: `values::Awaiting` is not supported: its field `future` holds a future of `std::task::Poll`, which can be `rs.Pending`, as the future's own `Pending` is +fixtures/reject/src/values.rs:projected_future+0:52: `values::Awaiting` is not supported: its field `future` holds a future of `std::task::Poll`, which can be `rs.Pending`, as the future's own `Pending` is fixtures/reject/src/values.rs:projected_nested+1:6: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_nested+1:52: `Option>` is not supported: its `None` and `Some` would both be `rs.None` -fixtures/reject/src/values.rs:projected_outside+0:26: `values::Outside<()>` is not supported: its field `inside` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` +fixtures/reject/src/values.rs:projected_outside+0:26: `values::Projected<()>` is not supported: its field `value` is `std::option::Option>`: `Option>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_spread+1:6: `values::Spread>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_uninit+1:2: `values::Projected` is not supported: its field `value` is `std::option::Option<::Held>`, typed `rs.OptionOf<::Held>`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases +fixtures/reject/src/values.rs:projected_uninit_slot+0:30: `values::Slot>` is not supported: its field `value` is `std::option::Option`, typed `rs.OptionOf`, which decides by TypeScript's type of `std::mem::MaybeUninit` otherwise than `std::option::Option>` erases fixtures/reject/src/values.rs:units_compared+2:15: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:29: a raw pointer to a place of `()`, which has no address of its own fixtures/reject/src/values.rs:units_compared+2:70: comparing pointers to `places::Bare`, which has no bytes and so no address of its own diff --git a/ts/fixtures/reject/src/values.rs b/ts/fixtures/reject/src/values.rs index 895badcf7..2612efa76 100644 --- a/ts/fixtures/reject/src/values.rs +++ b/ts/fixtures/reject/src/values.rs @@ -120,6 +120,60 @@ pub fn projected_outside(outside: &Outside<()>) -> u8 { } } +/// An `Option` of a type parameter. +pub struct Slot { + pub value: Option, +} + +/// A [`Slot`] of a `MaybeUninit` of its own parameter, checked as that slot's instance is. +pub struct Uninit { + pub slot: Slot>, +} + +/// `Uninit` holds `Slot>`, whose `rs.OptionOf` would make `None` +/// `undefined`. +pub fn projected_uninit_slot(uninit: &Uninit) -> bool { + uninit.slot.value.is_some() +} + +/// What a flow breaks with, named by an associated type. +pub trait Breaks { + type Break; +} + +impl Breaks for u8 { + type Break = Option; +} + +/// A `ControlFlow` breaking with a projection, typed as an `rs.Result` of it. +pub struct Flowing { + pub flow: std::ops::ControlFlow, +} + +/// `Flowing` breaks with an `Option`, a `ControlFlow` typed as an `Option`. +pub fn projected_flow(flowing: &Flowing) -> bool { + flowing.flow.is_continue() +} + +/// What a future outputs, named by an associated type. +pub trait Outputs { + type Output; +} + +impl Outputs for u8 { + type Output = std::task::Poll; +} + +/// A future of a projection. +pub struct Awaiting { + pub future: std::pin::Pin>>, +} + +/// `Awaiting`'s future outputs a `Poll`, which can be the future's own `rs.Pending`. +pub fn projected_future(awaiting: Awaiting) -> Awaiting { + awaiting +} + /// What a source yields, named by an associated type. pub trait Source { type Item; diff --git a/ts/fixtures/reject/ts.toml b/ts/fixtures/reject/ts.toml index 17ec4673f..1236aad66 100644 --- a/ts/fixtures/reject/ts.toml +++ b/ts/fixtures/reject/ts.toml @@ -216,7 +216,9 @@ roots = [ "values::projected_chain", "values::projected_deep", "values::projected_disposable", + "values::projected_flow", "values::projected_fork", + "values::projected_future", "values::projected_grown", "values::projected_inert", "values::projected_nested", @@ -225,6 +227,7 @@ roots = [ "values::projected_sprawl", "values::projected_spread", "values::projected_uninit", + "values::projected_uninit_slot", "values::units_compared", ] printable = ["config::Meters"] diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 62106ccbb..37f130379 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -23,31 +23,71 @@ pub struct Defined<'tcx> { /// Each projection from its type parameters its fields' types name, by the name of the /// TypeScript type parameter it is, after its own. pub projections: Vec<(Ty<'tcx>, String)>, - /// Each `Option`, `Poll`, or `ControlFlow` its fields' types type over a type parameter, - /// including those of each instance in [`Self::held`] at its arguments. - pub decided: Vec>, + /// Each part its fields' types type that its instances decide, and those of each + /// instance in [`Self::held`], at that instance's arguments. + pub checks: Vec>, + /// What each of those checks: its type, what it decides, and its part and what that + /// holds, at its arguments. + checked: rustc_data_structures::fx::FxHashSet<(DefId, Decides, Ty<'tcx>, Ty<'tcx>, Ty<'tcx>)>, /// Each instance of the crate's generic types over its type parameters its fields' types - /// type, and the field whose type does. - held: Vec<(DefId, Ty<'tcx>)>, + /// type. + held: Vec>, /// While it is typed, the shallowest definition still being typed that it was typed /// with, by its depth in [`Defining::typing`]; `None` once it is complete. open: Option, } -/// An `Option` or a `Poll` over a type parameter, or a projection from one, that a generic -/// type's definition typed. +/// A part of a generic type's definition, over a type parameter or a projection from one, +/// whose representation its instances decide. #[derive(Clone)] pub struct Decided<'tcx> { /// The field whose type holds it. pub field: DefId, /// It, over the definition's own parameters. pub part: Ty<'tcx>, - /// What it holds, or for a `ControlFlow`, continues with. + /// What an `Option` or a `Poll` holds, an `Option`-like `ControlFlow` continues with, any + /// other `ControlFlow` breaks with, or a future outputs. pub inner: Ty<'tcx>, /// TypeScript's type of `inner` in the definition. pub defined: Type, - /// An `Option`, rather than a `Poll`. - pub option: bool, + /// What its instances decide of it. + pub decides: Decides, +} + +/// What an instance decides of a [`Decided`] part. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub enum Decides { + /// An `Option`'s `None`, or that of a `ControlFlow` typed as one. + Option, + /// A `Poll`'s `Pending`. + Poll, + /// Whether a `ControlFlow` typed as an `rs.Result` of its break stays one, which an + /// `Option` or `Result` break makes otherwise. + Flow, + /// Whether a future's output can be `rs.Pending`, as the future's own `Pending` is. + Future, +} + +/// A [`Decided`] part of the definition of the crate's generic `adt`, at `args` over the +/// parameters of the definition that checks it: its own at its own parameters, or an +/// instance's it holds. +#[derive(Clone)] +pub struct Check<'tcx> { + pub adt: DefId, + pub decided: Decided<'tcx>, + pub args: GenericArgsRef<'tcx>, +} + +/// `args`, over a definition's parameters, at that definition's instance `at`. +pub fn instantiate_args<'tcx>( + tcx: ty::TyCtxt<'tcx>, + env: TypingEnv<'tcx>, + args: GenericArgsRef<'tcx>, + at: GenericArgsRef<'tcx>, +) -> GenericArgsRef<'tcx> { + let bound = ty::EarlyBinder::bind(tcx, args); + tcx.try_instantiate_and_normalize_erasing_regions(at, env, bound) + .unwrap_or_else(|_| bound.instantiate(tcx, at).skip_norm_wip()) } /// The state of typing definitions. @@ -206,44 +246,69 @@ impl<'tcx> Translator<'_, 'tcx> { Some(Type::Param(name)) } - /// Note that the definition being typed typed `part`, an `Option`, `Poll`, or - /// `ControlFlow` of `inner`, typing `inner` as `defined`, for the check of its instances. - pub(super) fn note_decided(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, defined: &Type, option: bool) { + /// Note that the definition being typed typed `part`, of `inner`, typed `defined`, for + /// the check of its instances, if they decide it. + pub(super) fn note_decided(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, defined: &Type, decides: Decides) { let (Some((adt, _)), Some(field)) = (self.defining, self.defining_state.field) else { return; }; - if !self.erases_to(inner).param { + let depends = match decides { + Decides::Flow => inner.has_param(), + _ => self.erases_to(inner).param, + }; + if !depends { return; } + let identity = ty::GenericArgs::identity_for_item(self.tcx, adt); + self.add_check( + adt, + Check { + adt, + decided: Decided { + field, + part, + inner, + defined: defined.clone(), + decides, + }, + args: identity, + }, + ); + } + + /// Add `check` to the record of the open definition of `adt` unless one checks the same. + fn add_check(&mut self, adt: DefId, check: Check<'tcx>) { + let tcx = self.tcx; + let env = TypingEnv::post_analysis(tcx, adt); + let d = &check.decided; + let part = super::copy::instantiate(tcx, env, d.part, check.args); + let inner = super::copy::instantiate(tcx, env, d.inner, check.args); + let key = (check.adt, d.decides, d.part, part, inner); let Some(record) = self.defining_state.records.get_mut(&adt) else { return; }; - if record.open.is_some() && !record.decided.iter().any(|d| d.part == part) { - record.decided.push(Decided { - field, - part, - inner, - defined: defined.clone(), - option, - }); + if record.open.is_some() && record.checked.insert(key) { + record.checks.push(check); self.defining_state.recorded += 1; } } /// Note that the definition being typed typed `instance`, an instance of the crate's - /// generic type, so it checks the parts that instance's definition decided too. + /// generic type, so it checks the parts that instance's definition decided too. One in a + /// projection argument is not: an instance of the definition types it at its own + /// arguments, which checks it there. pub(super) fn note_held(&mut self, instance: Ty<'tcx>) { - let (Some((adt, _)), Some(field)) = (self.defining, self.defining_state.field) else { + let (Some((adt, _)), Some(_)) = (self.defining, self.defining_state.field) else { return; }; - if !instance.has_param() { + if !instance.has_param() || self.defining_state.projecting > 0 { return; } let Some(record) = self.defining_state.records.get_mut(&adt) else { return; }; - if record.open.is_some() && !record.held.contains(&(field, instance)) { - record.held.push((field, instance)); + if record.open.is_some() && !record.held.contains(&instance) { + record.held.push(instance); } } @@ -446,6 +511,11 @@ impl<'tcx> Translator<'_, 'tcx> { for p in &mut state.pending[pending..] { p.filled = Some(vec![Type::Builtin("unknown"); state.records[&p.adt].projections.len()]); } + // Refused, so nothing of its instances is checked: the checks it took in, of ever + // deeper instances, would each type one deeper again. + for member in &state.members[members..] { + state.records.get_mut(&member.adt).expect("a record").checks.clear(); + } // The definition completing them all, which finished typing last. let root = state.members.last().expect("the definition completing"); let instance = Ty::new_adt( @@ -530,38 +600,26 @@ impl<'tcx> Translator<'_, 'tcx> { true } - /// Take into each definition from `members` on in [`Defining::members`] the parts each - /// instance it holds decided, at that instance's arguments, so an instance of the - /// definition checks them: `Outer` holding `Inner` checks `Inner`'s - /// `Option` at `Outer`'s instances. + /// Take into each definition from `members` on in [`Defining::members`] the checks of + /// each instance it holds, at that instance's arguments, so an instance of the + /// definition checks them as the instance it holds there would be: `Outer` holding + /// `Inner` checks `Inner`'s `Option` at `Outer`'s instances. fn take_held(&mut self, members: usize) { let tcx = self.tcx; let adts: Vec = self.defining_state.members[members..].iter().map(|m| m.adt).collect(); for adt in adts { let env = TypingEnv::post_analysis(tcx, adt); let held = self.defining_state.records[&adt].held.clone(); - for (field, instance) in held { + for instance in held { let ty::Adt(def, args) = *instance.kind() else { unreachable!("an instance of a generic type") }; let Some(record) = self.defining_state.records.get(&def.did()) else { continue; }; - for d in record.decided.clone() { - let part = super::copy::instantiate(tcx, env, d.part, args); - if self.defining_state.records[&adt].decided.iter().any(|d| d.part == part) { - continue; - } - let inner = super::copy::instantiate(tcx, env, d.inner, args); - if !self.erases_to(inner).param { - continue; - } - let outer = self.defining.replace((adt, env)); - let outer_field = self.defining_state.field.replace(field); - let defined = self.ty(inner, tcx.def_span(field)); - self.note_decided(part, inner, &defined, d.option); - self.defining = outer; - self.defining_state.field = outer_field; + for check in record.checks.clone() { + let args = instantiate_args(tcx, env, check.args, args); + self.add_check(adt, Check { args, ..check }); } } } diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 2c5a8dbf3..c0f3897e1 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1927,7 +1927,11 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { Type::Rs("Result", vec![self.ty(value, span), self.ty(residual.type_at(1), span)]) } library::Flow::Option => self.option_ty(Ty::new_adt(tcx, adt, args), value, span), - library::Flow::Other => Type::Rs("Result", vec![self.ty(value, span), self.ty(residual, span)]), + library::Flow::Other => { + let broken = self.ty(residual, span); + self.note_decided(Ty::new_adt(tcx, adt, args), residual, &broken, defined::Decides::Flow); + Type::Rs("Result", vec![self.ty(value, span), broken]) + } } } }; @@ -2112,89 +2116,114 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { } /// Why an instance of the crate's generic `adt` with `args` can't hold its fields' values, - /// if it can't: an `Option` or a `Poll` over a type parameter whose `None` or `Pending` - /// this instance makes other than its definition types it, as an `rs.OptionOf` or - /// `rs.PollOf` decides by TypeScript's type of `T`, and otherwise by the definition's - /// own parameter, or which can't tell its values apart at all. + /// if it can't: a part its definition, or that of an instance it holds, types that this + /// instance represents otherwise (see [`defined::Decides`]), or that can't tell its values + /// apart at all. fn generic_erasure(&mut self, adt: AdtDef<'tcx>, args: GenericArgsRef<'tcx>, span: Span) -> Option { let tcx = self.tcx; - let ty = Ty::new_adt(tcx, adt, args); let env = self.defining.map_or(self.typing_env(), |(_, env)| env); // Each its definition typed, which leaves out what types nothing, such as a `PhantomData`. - let decided = self.defined(adt.did()).decided.clone(); - for defined::Decided { + let checks = self.defined(adt.did()).checks.clone(); + checks.into_iter().find_map(|check| { + let args = defined::instantiate_args(tcx, env, check.args, args); + self.decided_conflict(tcx.adt_def(check.adt), args, &check.decided, span) + }) + } + + /// Why the instance of the crate's generic `adt` with `args` represents `decided`, a part + /// its definition typed, otherwise than its definition does, if it does. + fn decided_conflict( + &mut self, + adt: AdtDef<'tcx>, + args: GenericArgsRef<'tcx>, + decided: &defined::Decided<'tcx>, + span: Span, + ) -> Option { + let tcx = self.tcx; + let ty = Ty::new_adt(tcx, adt, args); + let env = self.defining.map_or(self.typing_env(), |(_, env)| env); + let defined::Decided { field, part, inner, - defined, - option, - } in decided - { - let erased = self.erases_to(inner); - let field = tcx.item_name(field); - let held = copy::instantiate(tcx, env, part, args); - if let Some(msg) = self.erasure_conflict(held) { - return Some(format!( - "`{ty}` is not supported: its field `{field}` is `{held}`: {msg}" - )); - } - // Over a parameter of the instance's own, which its instances decide. - if held.has_param() { - continue; - } - let instance = copy::instantiate(tcx, env, inner, args); - let at = self.erases_to(instance); - let msg = match defined { - // What TypeScript's type of the parameter decides, which must be what the - // instance's erasure does. - Type::Param(_) if if option { !erased.undefined } else { !erased.pending } => { - // The parameter, or the projection from one, that it types. - let Some(param) = (inner.walk().filter_map(|arg| arg.as_type())) - .find(|t| matches!(t.kind(), ty::Param(_) | ty::Alias(..))) - else { - continue; - }; - let arg = copy::instantiate(tcx, env, param, args); - self.defining_state.projecting += 1; - let decided = self.ty(arg, span); - self.defining_state.projecting -= 1; - let (holds, alias) = match option { - true => (holds_undefined(&decided) == at.undefined, "OptionOf"), - false => (holds_pending(&decided) == at.pending, "PollOf"), - }; - (!holds).then(|| { - format!( - "`{ty}` is not supported: its field `{field}` is `{part}`, typed `rs.{alias}<{inner}>`, which decides by TypeScript's type of `{arg}` otherwise than `{held}` erases" - ) - }) - } - _ if option && !erased.undefined => at.undefined.then(|| { - format!( - "`{ty}` is not supported: its field `{field}` is `Option<{inner}>`, typed `{inner} | undefined`, whose `None` would be `rs.None` as `Option<{instance}>`" - ) - }), - _ if !option && !erased.pending => at.pending.then(|| { - format!( - "`{ty}` is not supported: its field `{field}` is `Poll<{inner}>`, typed `rs.Poll<{inner}>`, whose `Pending` would be `rs.OuterPending` as `Poll<{instance}>`" - ) - }), - // A `MaybeUninit`, which can be `undefined` but for a byte. - _ if option => (!at.undefined).then(|| { - format!( - "`{ty}` is not supported: its field `{field}` is `Option<{inner}>`, typed `{inner} | rs.None`, whose `None` would be `undefined` as `Option<{instance}>`" - ) - }), - _ => (!at.pending).then(|| { + ref defined, + decides, + } = *decided; + let field = tcx.item_name(field); + let held = copy::instantiate(tcx, env, part, args); + let instance = copy::instantiate(tcx, env, inner, args); + if decides == defined::Decides::Future { + return self.erases_to(instance).pending.then(|| { + format!( + "`{ty}` is not supported: its field `{field}` holds a future of `{instance}`, which can be `rs.Pending`, as the future's own `Pending` is" + ) + }); + } + if let Some(msg) = self.erasure_conflict(held) { + return Some(format!( + "`{ty}` is not supported: its field `{field}` is `{held}`: {msg}" + )); + } + if decides == defined::Decides::Flow { + let (ty::Adt(_, at), ty::Adt(_, typed)) = (held.kind(), part.kind()) else { + return None; + }; + return (library::Flow::of(tcx, at) != library::Flow::of(tcx, typed)).then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `{part}`, typed as an `rs.Result` breaking with `{inner}`, which `{held}` is not, as `?` makes its break" + ) + }); + } + // Over a parameter of the instance's own, which its instances decide. + if held.has_param() { + return None; + } + let option = decides == defined::Decides::Option; + let erased = self.erases_to(inner); + let at = self.erases_to(instance); + match defined { + // What TypeScript's type of the parameter decides, which must be what the + // instance's erasure does. + Type::Param(_) if if option { !erased.undefined } else { !erased.pending } => { + // The parameter, or the projection from one, that it types. + let param = (inner.walk().filter_map(|arg| arg.as_type())) + .find(|t| matches!(t.kind(), ty::Param(_) | ty::Alias(..)))?; + let arg = copy::instantiate(tcx, env, param, args); + self.defining_state.projecting += 1; + let decided = self.ty(arg, span); + self.defining_state.projecting -= 1; + let (holds, alias) = match option { + true => (holds_undefined(&decided) == at.undefined, "OptionOf"), + false => (holds_pending(&decided) == at.pending, "PollOf"), + }; + (!holds).then(|| { format!( - "`{ty}` is not supported: its field `{field}` is `Poll<{inner}>`, typed `{inner} | rs.OuterPending`, whose `Pending` would be `rs.Pending` as `Poll<{instance}>`" + "`{ty}` is not supported: its field `{field}` is `{part}`, typed `rs.{alias}<{inner}>`, which decides by TypeScript's type of `{arg}` otherwise than `{held}` erases" ) - }), - }; - if msg.is_some() { - return msg; + }) } + _ if option && !erased.undefined => at.undefined.then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Option<{inner}>`, typed `{inner} | undefined`, whose `None` would be `rs.None` as `Option<{instance}>`" + ) + }), + _ if !option && !erased.pending => at.pending.then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Poll<{inner}>`, typed `rs.Poll<{inner}>`, whose `Pending` would be `rs.OuterPending` as `Poll<{instance}>`" + ) + }), + // A `MaybeUninit`, which can be `undefined` but for a byte. + _ if option => (!at.undefined).then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Option<{inner}>`, typed `{inner} | rs.None`, whose `None` would be `undefined` as `Option<{instance}>`" + ) + }), + _ => (!at.pending).then(|| { + format!( + "`{ty}` is not supported: its field `{field}` is `Poll<{inner}>`, typed `{inner} | rs.OuterPending`, whose `Pending` would be `rs.Pending` as `Poll<{instance}>`" + ) + }), } - None } /// The type of an iterator's items, which every iterator has. @@ -2214,7 +2243,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// `part` is the `Option`, or the `ControlFlow` that is one. fn option_ty(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, span: Span) -> Type { let some = self.ty(inner, span); - self.note_decided(part, inner, &some, true); + self.note_decided(part, inner, &some, defined::Decides::Option); let erased = self.erases_to(inner); if matches!(some, Type::Param(_)) && !erased.undefined { return Type::Rs("OptionOf", vec![some]); @@ -2235,7 +2264,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { format!("a future of `{output}`, which can be `rs.Pending`, as the future's own `Pending` is"), ); } - Type::Rs("Future", vec![self.ty(output, span)]) + let ty = self.ty(output, span); + self.note_decided(output, output, &ty, defined::Decides::Future); + Type::Rs("Future", vec![ty]) } /// The TypeScript type of a `Poll`: `rs.Poll`, or `T | rs.OuterPending` where @@ -2243,7 +2274,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { /// parameter, an `rs.PollOf`, which is either as each instance's `T` decides. fn poll_ty(&mut self, part: Ty<'tcx>, inner: Ty<'tcx>, span: Span) -> Type { let ready = self.ty(inner, span); - self.note_decided(part, inner, &ready, false); + self.note_decided(part, inner, &ready, defined::Decides::Poll); if matches!(ready, Type::Param(_)) && !self.erases_to(inner).pending { return Type::Rs("PollOf", vec![ready]); } From 1e2558a93cae88ef11d21a8f406a5ec4b9c04072 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 15:43:08 -0800 Subject: [PATCH 20/21] fix(ts): word the ControlFlow refusal in census terms Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/reject/census.txt | 2 +- ts/fixtures/reject/expected.txt | 2 +- ts/src/lower/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/fixtures/reject/census.txt b/ts/fixtures/reject/census.txt index d78966abc..5885af8d5 100644 --- a/ts/fixtures/reject/census.txt +++ b/ts/fixtures/reject/census.txt @@ -318,7 +318,7 @@ fixtures/reject/src/values.rs:manual_impls+2:34: a constant of type `std::mem::M fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:projected_flow+0:23: `values::Flowing` is not supported: its field `flow` is `std::ops::ControlFlow<::Break, u8>`, typed as an `rs.Result` breaking with `::Break`, which `std::ops::ControlFlow, u8>` is not, as `?` makes its break +fixtures/reject/src/values.rs:projected_flow+0:23: `values::Flowing` is not supported: its field `flow` is `std::ops::ControlFlow<::Break, u8>`, typed as a result that breaks with `::Break`, which `std::ops::ControlFlow, u8>` is not, as it breaks with what `?` makes fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit diff --git a/ts/fixtures/reject/expected.txt b/ts/fixtures/reject/expected.txt index bae2838f9..6d1d0b575 100644 --- a/ts/fixtures/reject/expected.txt +++ b/ts/fixtures/reject/expected.txt @@ -313,7 +313,7 @@ fixtures/reject/src/values.rs:mixed_byte+0:19: union `values::Mixed`, whose fiel fixtures/reject/src/values.rs:nested+0:15: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:nested+1:2: `Option>>` is not supported: its `None` and `Some` would both be `rs.None` fixtures/reject/src/values.rs:projected_deep+0:23: `values::Links>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>` is not supported: its projections' values nest instances past the crate's recursion limit -fixtures/reject/src/values.rs:projected_flow+0:23: `values::Flowing` is not supported: its field `flow` is `std::ops::ControlFlow<::Break, u8>`, typed as an `rs.Result` breaking with `::Break`, which `std::ops::ControlFlow, u8>` is not, as `?` makes its break +fixtures/reject/src/values.rs:projected_flow+0:23: `values::Flowing` is not supported: its field `flow` is `std::ops::ControlFlow<::Break, u8>`, typed as a result that breaks with `::Break`, which `std::ops::ControlFlow, u8>` is not, as it breaks with what `?` makes fixtures/reject/src/values.rs:projected_fork+0:38: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit fixtures/reject/src/values.rs:projected_fork+1:7: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit, in `core::bool::::then_some` fixtures/reject/src/values.rs:projected_fork+1:17: `values::Fork>` is not supported: its projections' values nest instances past the crate's recursion limit diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index c0f3897e1..e5e5f7ad6 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -2170,7 +2170,7 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { }; return (library::Flow::of(tcx, at) != library::Flow::of(tcx, typed)).then(|| { format!( - "`{ty}` is not supported: its field `{field}` is `{part}`, typed as an `rs.Result` breaking with `{inner}`, which `{held}` is not, as `?` makes its break" + "`{ty}` is not supported: its field `{field}` is `{part}`, typed as a result that breaks with `{inner}`, which `{held}` is not, as it breaks with what `?` makes" ) }); } From 7c9b53ee41ef0010257301af6e48d307c6e4d246 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:17:42 -0800 Subject: [PATCH 21/21] fix(ts): type trait objects outside the definition typing them, and fill what they keep A trait object's methods are typed over the trait's own parameters, so typing them while a definition is open no longer records the trait's `Option` against that definition. Completing a cycle that typed instances of open definitions now fills their projection arguments wherever a type typed meanwhile is kept, including the methods an object holds and items made before the cycle, leaving those of a cycle further out for it. Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/lang/shapes/expected/core/bool.ts | 8 ++ .../lang/shapes/expected/core/option.ts | 8 ++ .../lang/shapes/expected/generics.Get.ts | 12 ++ .../lang/shapes/expected/generics.Getter.ts | 22 ++++ .../lang/shapes/expected/generics.Holder.ts | 29 +++++ ts/fixtures/lang/shapes/expected/generics.ts | 112 ++++++++++-------- ts/fixtures/lang/shapes/expected/lib.ts | 18 +++ ts/fixtures/lang/shapes/src/generics.rs | 40 +++++++ ts/src/lower/defined.rs | 48 ++++++-- ts/src/lower/dynamic.rs | 20 +++- ts/src/lower/mod.rs | 12 +- ts/src/ts.rs | 14 ++- 12 files changed, 264 insertions(+), 79 deletions(-) create mode 100644 ts/fixtures/lang/shapes/expected/generics.Get.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Getter.ts create mode 100644 ts/fixtures/lang/shapes/expected/generics.Holder.ts diff --git a/ts/fixtures/lang/shapes/expected/core/bool.ts b/ts/fixtures/lang/shapes/expected/core/bool.ts index 603f7536e..6fec500c8 100644 --- a/ts/fixtures/lang/shapes/expected/core/bool.ts +++ b/ts/fixtures/lang/shapes/expected/core/bool.ts @@ -4,6 +4,7 @@ import * as rs from "../rs.ts"; import type * as Pair from "../fmt.Pair.ts"; import type * as Copse from "../generics.Copse.ts"; import type * as Grove from "../generics.Grove.ts"; +import type * as Holder from "../generics.Holder.ts"; import type * as Layer from "../generics.Layer.ts"; import type * as Peel from "../generics.Peel.ts"; import type * as Remade from "../generics.Remade.ts"; @@ -114,6 +115,13 @@ export function then$0y7on52(self: boolean, _: () => rs.ByteVec): rs.ByteVec | u return new rs.ByteVec(); } +export function then$1tgze8k(self: boolean, f: () => Holder.Holder): Holder.Holder | undefined { + if (!self) { + return undefined; + } + return f(); +} + export function then$1qflv5x(self: boolean, f: () => Ticked.Ticked): Ticked.Ticked | undefined { if (!self) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/core/option.ts b/ts/fixtures/lang/shapes/expected/core/option.ts index 016a0e2b5..451569538 100644 --- a/ts/fixtures/lang/shapes/expected/core/option.ts +++ b/ts/fixtures/lang/shapes/expected/core/option.ts @@ -10,6 +10,7 @@ import type * as Depth from "../generics.Depth.ts"; import type * as Forked from "../generics.Forked.ts"; import type * as Grove from "../generics.Grove.ts"; import type * as Head from "../generics.Head.ts"; +import type * as Holder from "../generics.Holder.ts"; import type * as Layer from "../generics.Layer.ts"; import type * as Looped from "../generics.Looped.ts"; import type * as Node from "../generics.Node.ts"; @@ -84,6 +85,13 @@ export function map$0983ug4(self: Defaulted.Defaulted [rs.u8, rs.u8 | undefined]): [rs.u8, rs.u8 | undefined] | undefined { + if (self === undefined) { + return undefined; + } + return f(self); +} + export function map$04m4r6n(self: Counted.Counted | undefined, f: (a0: Counted.Counted) => rs.u8): rs.u8 | undefined { if (self === undefined) { return undefined; diff --git a/ts/fixtures/lang/shapes/expected/generics.Get.ts b/ts/fixtures/lang/shapes/expected/generics.Get.ts new file mode 100644 index 000000000..8834fba8a --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Get.ts @@ -0,0 +1,12 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import type * as Holder from "./generics.Holder.ts"; + +/** + * A getter of its parameter, and of the type holding it, which its object's type names + * while that type's definition is still being typed. + */ +export type Get = { get$Get: () => rs.OptionOf; holder$Get: () => Holder.Holder | undefined }; + +//# sourceMappingURL=generics.Get.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Getter.ts b/ts/fixtures/lang/shapes/expected/generics.Getter.ts new file mode 100644 index 000000000..3018c0f05 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Getter.ts @@ -0,0 +1,22 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { thenSome$0quywni, then$1tgze8k } from "./core/bool.ts"; +import * as Holder from "./generics.Holder.ts"; + +/** A getter of a byte, holding half of it again past `0x80`. */ +export interface Getter { + _0: rs.u8; +} + +export function get$generics_Get_u8(self: Getter): rs.u8 | undefined { + return thenSome$0quywni(rs.u8Rem(self._0, 3) !== 0, self._0); +} + +export function holder$generics_Get_u8(self: Getter): Holder.Holder | undefined { + return then$1tgze8k(self._0 > 128, (): Holder.Holder => { + return Holder.new(rs.u8Div(self._0, 2), rs.u8Div(self._0, 2)); + }); +} + +//# sourceMappingURL=generics.Getter.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.Holder.ts b/ts/fixtures/lang/shapes/expected/generics.Holder.ts new file mode 100644 index 000000000..a716fb916 --- /dev/null +++ b/ts/fixtures/lang/shapes/expected/generics.Holder.ts @@ -0,0 +1,29 @@ +// Generated by rust2-ts. Do not edit. + +import * as rs from "./rs.ts"; +import { of$generics_Getter } from "./lib.ts"; +import type * as Get from "./generics.Get.ts"; + +/** A type holding a getter whose methods return it. */ +export class Holder { + id: rs.u8; + get: Get.Get; + + constructor(id: rs.u8, get: Get.Get) { + this.id = id; + this.get = get; + } + + [Symbol.dispose](): void { + rs.drop(this.get); + } +} + +/** A holder of a getter of `b`, whose type is typed before its getter's object is. */ +function new_(id: rs.u8, b: rs.u8): Holder { + return new Holder(id, new of$generics_Getter({ _0: b })); +} + +export { new_ as new }; + +//# sourceMappingURL=generics.Holder.ts.map diff --git a/ts/fixtures/lang/shapes/expected/generics.ts b/ts/fixtures/lang/shapes/expected/generics.ts index 346b8af5d..88a59e69b 100644 --- a/ts/fixtures/lang/shapes/expected/generics.ts +++ b/ts/fixtures/lang/shapes/expected/generics.ts @@ -3,7 +3,7 @@ import * as rs from "./rs.ts"; import { copy$array_generics_Either_u8_shapes_Point, copy$generics_Fixed_u16, copy$generics_Fixed_u8, copy$generics_Wrap_shapes_Point, copy$generics_Wrap_u8, copy$shapes_Gauge, drop$generics_Pile_generics_Ticks, drop$generics_Run_generics_Ticked } from "./lib.ts"; import { thenSome$1ctlqen, thenSome$0bd8znh, thenSome$0czqe00, thenSome$0rdxccx, thenSome$0tbnivw, thenSome$0epplxd, thenSome$19pue01, thenSome$0quywni, thenSome$1n1oy1h, then$01xsbgy, then$0y7on52, then$1qflv5x } from "./core/bool.ts"; -import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$1avgfgw as option_map$1avgfgw, map$0e9v36n as option_map$0e9v36n, map$1i2swnz as option_map$1i2swnz, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$1yjmt5m as option_map$1yjmt5m, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; +import { unwrapOr$1fr498u as option_unwrapOr$1fr498u, unwrapOrDefault$0w7995p as option_unwrapOrDefault$0w7995p, unwrapOrDefault$03apme8 as option_unwrapOrDefault$03apme8, map$00jdj9f as option_map$00jdj9f, map$0xoixq7 as option_map$0xoixq7, map$0983ug4 as option_map$0983ug4, map$14wqhyc as option_map$14wqhyc, map$04m4r6n as option_map$04m4r6n, map$1ug09xc as option_map$1ug09xc, map$1747o3c as option_map$1747o3c, map$1mvacy3 as option_map$1mvacy3, map$1es1113 as option_map$1es1113, map$1ub01y7 as option_map$1ub01y7, map$1a3j889 as option_map$1a3j889, map$1avgfgw as option_map$1avgfgw, map$0e9v36n as option_map$0e9v36n, map$1i2swnz as option_map$1i2swnz, map$0ggarad as option_map$0ggarad, map$132wgz4 as option_map$132wgz4, map$1olalbb as option_map$1olalbb, map$1htvaju as option_map$1htvaju, map$0xpm57l as option_map$0xpm57l, map$1yjmt5m as option_map$1yjmt5m, map$0ydco3f as option_map$0ydco3f, map$057a36e as option_map$057a36e, asDeref$10lhnlx as option_asDeref$10lhnlx, asDeref$1z13rqi as option_asDeref$1z13rqi, andThen$0jij6xn as option_andThen$0jij6xn } from "./core/option.ts"; import { eq$1qrb1lw as poll_eq$1qrb1lw, isPending$07fnw8g, isPending$1imonrp, isPending$189w6zf, isPending$1yqrsts } from "./core/task/poll.ts"; import type * as Pair from "./fmt.Pair.ts"; import * as Ahead from "./generics.Ahead.ts"; @@ -26,6 +26,7 @@ import * as Forked from "./generics.Forked.ts"; import * as Ghost from "./generics.Ghost.ts"; import type * as Grove from "./generics.Grove.ts"; import type * as Head from "./generics.Head.ts"; +import * as Holder from "./generics.Holder.ts"; import * as Kept from "./generics.Kept.ts"; import * as Layer from "./generics.Layer.ts"; import type * as Lends from "./generics.Lends.ts"; @@ -740,7 +741,7 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { let copse!: Copse.Copse; let grown!: [rs.u8, boolean | undefined]; let sown!: [rs.u8, boolean | undefined]; - let _275!: Pong.Pong; + let _302!: Pong.Pong; const log: rs.Arc> = new rs.Arc(new rs.RefCell(new rs.ByteVec())); const tick: (a0: rs.u8) => Ticked.Ticked = (id: rs.u8): Ticked.Ticked => { return new Ticked.Ticked(id, rs.arcClone(log)); @@ -788,32 +789,42 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }); const _91: rs.Fmt = (f: rs.Formatter) => rs.fmtList(rs.byteVecView(ids2), f, rs.u8Debug); const arg = node.item; - const args20 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args20[0], " ", args20[1], " ", args20[2]]).v); + const args22 = [_91, (f: rs.Formatter) => rs.u8Display(arg, f), (f: rs.Formatter) => rs.fmtOption(held2, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args22[0], " ", args22[1], " ", args22[2]]).v); const uninit: Uninit.Uninit = { slot: new generics_Slot.Slot(option_map$1yjmt5m(rs.byteSliceFirst(data), (_22: rs.u8): rs.u8 => { return _22; }) as rs.OptionOf) }; const part = uninit.slot.value !== undefined; put(out, rs.fmtFormat([(f: rs.Formatter) => rs.fmtBool(part, f)]).v); - const _116: rs.u8 | undefined = rs.byteSliceFirst(data); + const holder: Holder.Holder = Holder.new(data.length & 0xff, option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0)); + const held3: [rs.u8, rs.u8 | undefined] | undefined = option_map$14wqhyc(holder.get.holder$Get(), (h: Holder.Holder): [rs.u8, rs.u8 | undefined] => { + const _09: [rs.u8, rs.u8 | undefined] = [h.id, h.get.get$Get() as rs.u8 | undefined]; + h[Symbol.dispose](); + return _09; + }); + const _132: rs.u8 | undefined = holder.get.get$Get() as rs.u8 | undefined; + const arg2 = holder.id; + const args24 = [(f: rs.Formatter) => rs.u8Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_132, f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(held3, f, (value: [rs.u8, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args24[0], " ", args24[1], " ", args24[2]]).v); + const _143: rs.u8 | undefined = rs.byteSliceFirst(data); const twig: Twig.Twig = new Twig.Twig(undefined, { tag: "Item", _0: 5 } as Grove.Grove); - if (_116 === undefined) { + if (_143 === undefined) { grove = { tag: "Item", _0: data.length & 0xff } as Grove.Grove; } else { - const b2: rs.u8 = _116; + const b2: rs.u8 = _143; grove = { tag: "Branch", _0: new Twig.Twig(thenSome$0epplxd(rs.u8Rem(b2, 2) === 0, twig), b2) } as Grove.Grove; } - const _128: rs.u8 | undefined = rs.byteSliceLast(data); + const _155: rs.u8 | undefined = rs.byteSliceLast(data); const shoot: Shoot.Shoot = new Shoot.Shoot(undefined, { tag: "Item", _0: 6 } as Copse.Copse); - if (_128 === undefined) { + if (_155 === undefined) { copse = { tag: "Item", _0: 0 } as Copse.Copse; } else { - const b3: rs.u8 = _128; + const b3: rs.u8 = _155; copse = { tag: "Branch", _0: new Shoot.Shoot(thenSome$0czqe00(rs.u8Rem(b3, 2) === 1, shoot), ~b3 & 0xff) } as Copse.Copse; } - const _138: Grove.Grove = grove; - if (_138.tag === "Branch") { - grown = [_138._0.item, option_map$1i2swnz(_138._0.next, (t: Twig.Twig): boolean => { + const _165: Grove.Grove = grove; + if (_165.tag === "Branch") { + grown = [_165._0.item, option_map$1i2swnz(_165._0.next, (t: Twig.Twig): boolean => { if (t.item.tag === "Item") { if (t.item._0 === 5) { return true; @@ -822,13 +833,13 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return false; })] as [rs.u8, boolean | undefined]; } else { - grown = [_138._0, undefined] as [rs.u8, boolean | undefined]; + grown = [_165._0, undefined] as [rs.u8, boolean | undefined]; } - const _148: Copse.Copse = copse; - if (_148.tag === "Item") { - sown = [_148._0, undefined] as [rs.u8, boolean | undefined]; + const _175: Copse.Copse = copse; + if (_175.tag === "Item") { + sown = [_175._0, undefined] as [rs.u8, boolean | undefined]; } else { - sown = [_148._0.item, option_map$1avgfgw(_148._0.next, (s: Shoot.Shoot): boolean => { + sown = [_175._0.item, option_map$1avgfgw(_175._0.next, (s: Shoot.Shoot): boolean => { if (s.item.tag === "Item") { if (s.item._0 === 6) { return true; @@ -837,12 +848,12 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return false; })] as [rs.u8, boolean | undefined]; } - const args22 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(grown[0], f), (f: rs.Formatter) => rs.fmtOption(grown[1], f, rs.fmtBool)], f), (f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(sown[0], f), (f: rs.Formatter) => rs.fmtOption(sown[1], f, rs.fmtBool)], f)]; - put(out, rs.fmtFormat([args22[0], " ", args22[1]]).v); + const args25 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(grown[0], f), (f: rs.Formatter) => rs.fmtOption(grown[1], f, rs.fmtBool)], f), (f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(sown[0], f), (f: rs.Formatter) => rs.fmtOption(sown[1], f, rs.fmtBool)], f)]; + put(out, rs.fmtFormat([args25[0], " ", args25[1]]).v); const under: Layer.Layer, boolean, rs.u8> = new Layer.Layer(true, undefined); const layer: Layer.Layer = new Layer.Layer(((option_unwrapOr$1fr498u(rs.byteSliceFirst(data), 0) as rs.u16) << 4) & 0xffff, thenSome$0bd8znh(rs.usizeRem(data.length, 2) === 1, under)); - const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _39: rs.u8): Counted.Counted | undefined => { - return new Counted.Counted(_39, option_map$0xoixq7(next5, (a03: Counted.Counted): Counted.Counted => { + const count: rs.u8 | undefined = option_map$04m4r6n(option_andThen$0jij6xn(rs.iterFold(rs.iterSlice(rs.byteSliceView(data)), undefined as Counted.Counted | undefined, (next5: Counted.Counted | undefined, _311: rs.u8): Counted.Counted | undefined => { + return new Counted.Counted(_311, option_map$0xoixq7(next5, (a03: Counted.Counted): Counted.Counted => { return a03; }) as rs.OptionOf); }), (c: Counted.Counted): Counted.Counted | undefined => { @@ -850,42 +861,42 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }), (c2: Counted.Counted): rs.u8 => { return c2.id; }); - const _201: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { + const _228: [boolean, boolean] | undefined = option_map$1a3j889(layer.under, (u: Layer.Layer, boolean, rs.u8>): [boolean, boolean] => { return [u.item, u.under !== undefined] as [boolean, boolean]; }); - const arg2 = layer.item; - const args23 = [(f: rs.Formatter) => rs.u16Display(arg2, f), (f: rs.Formatter) => rs.fmtOption(_201, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; - put(out, rs.fmtFormat([args23[0], " ", args23[1], " ", args23[2]]).v); + const arg3 = layer.item; + const args26 = [(f: rs.Formatter) => rs.u16Display(arg3, f), (f: rs.Formatter) => rs.fmtOption(_228, f, (value: [boolean, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.fmtBool(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f)), (f: rs.Formatter) => rs.fmtOption(count, f, rs.u8Debug)]; + put(out, rs.fmtFormat([args26[0], " ", args26[1], " ", args26[2]]).v); const wide: generics_Wide.Wide = generics_Wide.new$u8(data.length & 0xff); - const _223: boolean = wide.next === undefined; - const arg3 = wide.a0; - const _226: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg3, f); - const arg4 = wide.a129; - const args24 = [_226, (f: rs.Formatter) => rs.u8Display(arg4, f), (f: rs.Formatter) => rs.fmtBool(_223, f)]; - put(out, rs.fmtFormat([args24[0], " ", args24[1], " ", args24[2]]).v); + const _250: boolean = wide.next === undefined; + const arg4 = wide.a0; + const _253: rs.Fmt = (f: rs.Formatter) => rs.u8Display(arg4, f); + const arg5 = wide.a129; + const args27 = [_253, (f: rs.Formatter) => rs.u8Display(arg5, f), (f: rs.Formatter) => rs.fmtBool(_250, f)]; + put(out, rs.fmtFormat([args27[0], " ", args27[1], " ", args27[2]]).v); const made: Sibling.Sibling, rs.u8, rs.u8> = new Sibling.Sibling(7, undefined); const sibling: Sibling.Sibling = new Sibling.Sibling(((option_unwrapOr$1fr498u(rs.byteSliceLast(data), 0) as rs.u16) << 3) & 0xffff, thenSome$0rdxccx(rs.usizeRem(data.length, 3) === 0, made)); - const _254: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s2: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { + const _281: [rs.u8, boolean] | undefined = option_map$0e9v36n(sibling.next, (s2: Sibling.Sibling, rs.u8, rs.u8>): [rs.u8, boolean] => { return [s2.item, s2.next === undefined] as [rs.u8, boolean]; }); - const arg5 = sibling.item; - const args25 = [(f: rs.Formatter) => rs.u16Display(arg5, f), (f: rs.Formatter) => rs.fmtOption(_254, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; - put(out, rs.fmtFormat([args25[0], " ", args25[1]]).v); + const arg6 = sibling.item; + const args28 = [(f: rs.Formatter) => rs.u16Display(arg6, f), (f: rs.Formatter) => rs.fmtOption(_281, f, (value: [rs.u8, boolean], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtBool(value[1], f)], f))]; + put(out, rs.fmtFormat([args28[0], " ", args28[1]]).v); let ping: Ping.Ping = new Ping.Ping(238, undefined as rs.OptionOf); const iter2: rs.Iter<[rs.usize, rs.u8]> = rs.iterEnumerate(rs.iterSlice(rs.byteSliceView(data))); while (true) { - const _267: [rs.usize, rs.u8] | rs.Done = iter2.next(); - if (_267 === rs.DONE) { + const _294: [rs.usize, rs.u8] | rs.Done = iter2.next(); + if (_294 === rs.DONE) { break; } - const i: rs.usize = _267[0]; - const b4: rs.u8 = _267[1]; + const i: rs.usize = _294[0]; + const b4: rs.u8 = _294[1]; if (rs.usizeRem(i, 3) === 0) { - _275 = { tag: "Miss" } as Pong.Pong; + _302 = { tag: "Miss" } as Pong.Pong; } else { - _275 = { tag: "Hit", _0: ~b4 & 0xff, _1: ping } as Pong.Pong; + _302 = { tag: "Hit", _0: ~b4 & 0xff, _1: ping } as Pong.Pong; } - ping = new Ping.Ping(b4, _275 as rs.OptionOf); + ping = new Ping.Ping(b4, _302 as rs.OptionOf); } const ids3: rs.ByteVec = new rs.ByteVec(); Ping.ids$generics_Rally(ping, ids3); @@ -897,9 +908,9 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { }) as rs.OptionOf) as rs.OptionOf); const fork: Forked.Forked = new Forked.Forked(0, undefined as rs.OptionOf, undefined as rs.OptionOf); if (rs.usizeRem(data.length, 2) === 0) { - const _315: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); + const _342: Forked.Forked | undefined = new Forked.Forked(1, undefined as rs.OptionOf, undefined as rs.OptionOf); rs.drop(fork.left); - fork.left = _315; + fork.left = _342; } const ids4: [rs.u8, rs.u8 | undefined, rs.u8] = [fork.id, option_map$1747o3c(fork.left, (f: Forked.Forked): rs.u8 => { return f.id; @@ -911,13 +922,14 @@ export function projectedSelves(data: rs.ByteSlice, out: rs.ByteVec): void { return t2.id; })] as [rs.u8, rs.u8 | undefined, rs.u8 | undefined]; }); - const args27 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; - put(out, rs.fmtFormat([args27[0], " ", args27[1]]).v); + const args30 = [(f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(ids4[0], f), (f: rs.Formatter) => rs.fmtOption(ids4[1], f, rs.u8Debug), (f: rs.Formatter) => rs.u8Debug(ids4[2], f)], f), (f: rs.Formatter) => rs.fmtOption(right, f, (value: [rs.u8, rs.u8 | undefined, rs.u8 | undefined], f: rs.Formatter) => rs.fmtTuple([(f: rs.Formatter) => rs.u8Debug(value[0], f), (f: rs.Formatter) => rs.fmtOption(value[1], f, rs.u8Debug), (f: rs.Formatter) => rs.fmtOption(value[2], f, rs.u8Debug)], f))]; + put(out, rs.fmtFormat([args30[0], " ", args30[1]]).v); below[Symbol.dispose](); fork[Symbol.dispose](); - const _351: rs.RefCell = rs.refCellBorrow(log.value); - rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_351.value))), undefined, true); - rs.refRelease(_351); + const _378: rs.RefCell = rs.refCellBorrow(log.value); + rs.iterExtendBytes(out, rs.iterSlice(rs.byteSliceView(rs.byteVecSlice(_378.value))), undefined, true); + rs.refRelease(_378); + holder[Symbol.dispose](); log[Symbol.dispose](); } diff --git a/ts/fixtures/lang/shapes/expected/lib.ts b/ts/fixtures/lang/shapes/expected/lib.ts index 8903a3bd4..d32cdc7dc 100644 --- a/ts/fixtures/lang/shapes/expected/lib.ts +++ b/ts/fixtures/lang/shapes/expected/lib.ts @@ -5,6 +5,8 @@ import { ints, strings, derived, written, counts, rounding, panics, wrapped, col import { methods, runs, units, projections, projectedCycles, projectedSelves } from "./generics.ts"; import type * as Either from "./generics.Either.ts"; import * as Fixed from "./generics.Fixed.ts"; +import * as Getter from "./generics.Getter.ts"; +import type * as Holder from "./generics.Holder.ts"; import type * as Pile from "./generics.Pile.ts"; import type * as Run from "./generics.Run.ts"; import type * as Ticked from "./generics.Ticked.ts"; @@ -158,6 +160,22 @@ export function drop$generics_Run_generics_Ticked(_1: Run.Run): v } } +export class of$generics_Getter { + v: Getter.Getter; + + constructor(v: Getter.Getter) { + this.v = v; + } + + get$Get(): rs.OptionOf { + return Getter.get$generics_Get_u8(this.v) as rs.OptionOf; + } + + holder$Get(): Holder.Holder | undefined { + return Getter.holder$generics_Get_u8(this.v); + } +} + export class of$u8 { v: rs.u8; diff --git a/ts/fixtures/lang/shapes/src/generics.rs b/ts/fixtures/lang/shapes/src/generics.rs index 61a7c8b73..0b2214719 100644 --- a/ts/fixtures/lang/shapes/src/generics.rs +++ b/ts/fixtures/lang/shapes/src/generics.rs @@ -1033,6 +1033,42 @@ struct Uninit { slot: Slot>, } +/// A getter of its parameter, and of the type holding it, which its object's type names +/// while that type's definition is still being typed. +trait Get { + fn get(&self) -> Option; + fn holder(&self) -> Option; +} + +/// A type holding a getter whose methods return it. +struct Holder { + id: u8, + get: Box>, +} + +impl Holder { + /// A holder of a getter of `b`, whose type is typed before its getter's object is. + fn new(id: u8, b: u8) -> Self { + Self { + id, + get: Box::new(Getter(b)), + } + } +} + +/// A getter of a byte, holding half of it again past `0x80`. +struct Getter(u8); + +impl Get for Getter { + fn get(&self) -> Option { + (self.0 % 3 != 0).then_some(self.0) + } + + fn holder(&self) -> Option { + (self.0 > 0x80).then(|| Holder::new(self.0 / 2, self.0 / 2)) + } +} + /// A layered source's item, and the item of the layer peeled off it. trait Layered { type Item; @@ -1298,6 +1334,10 @@ pub fn projected_selves(data: &[u8], out: &mut Vec) { }; put(out, &format!("{}", uninit.slot.value.is_some())); + let holder = Holder::new(data.len() as u8, data.first().copied().unwrap_or(0)); + let held = holder.get.holder().map(|h| (h.id, h.get.get())); + put(out, &format!("{} {:?} {held:?}", holder.id, holder.get.get())); + let twig: &'static Twig = &Twig { next: None, item: Grove::Item(5), diff --git a/ts/src/lower/defined.rs b/ts/src/lower/defined.rs index 37f130379..dad3123c4 100644 --- a/ts/src/lower/defined.rs +++ b/ts/src/lower/defined.rs @@ -137,7 +137,6 @@ struct Typing { adt: DefId, members: usize, pending: usize, - items: usize, } /// A definition typed, and the type of each of its fields there, which may hold @@ -170,6 +169,24 @@ impl<'tcx> Translator<'_, 'tcx> { &self.defining_state.records[&adt] } + /// Run `f` outside the definition being typed and the type expression in it, as for + /// another definition, or a trait object's, whose types are over its own parameters. + pub(super) fn outside_definition(&mut self, f: impl FnOnce(&mut Self) -> R) -> R { + let defining = self.defining.take(); + let state = &mut self.defining_state; + let field = state.field.take(); + let instancing = std::mem::take(&mut state.instancing); + let instanced = std::mem::take(&mut state.instanced); + let too_deep = std::mem::take(&mut state.too_deep); + let projecting = std::mem::take(&mut state.projecting); + let r = f(self); + self.defining = defining; + let state = &mut self.defining_state; + (state.field, state.instancing, state.instanced) = (field, instancing, instanced); + (state.too_deep, state.projecting) = (too_deep, projecting); + r + } + /// Start typing the definition of `adt`. pub(super) fn start_definition(&mut self, adt: DefId) { let state = &mut self.defining_state; @@ -184,7 +201,6 @@ impl<'tcx> Translator<'_, 'tcx> { adt, members: state.members.len(), pending: state.pending.len(), - items: self.program.items.len(), }); } @@ -486,9 +502,9 @@ impl<'tcx> Translator<'_, 'tcx> { /// more rounds than the crate's recursion limit are refused at the definition. fn complete(&mut self, depth: usize) -> bool { let tcx = self.tcx; - let (members, pending, items) = { + let (members, pending) = { let typing = &self.defining_state.typing[depth]; - (typing.members, typing.pending, typing.items) + (typing.members, typing.pending) }; let mut rounds = 0; let settled = loop { @@ -544,17 +560,23 @@ impl<'tcx> Translator<'_, 'tcx> { let filled: Vec> = (self.defining_state.pending[pending..].iter()) .map(|p| p.filled.clone().unwrap_or_default()) .collect(); - let later = |id: usize| filled[id.checked_sub(pending).expect("an instance this cycle typed")].clone(); + // One of a cycle further out, still being typed, is left for it. + let later = |id: usize| id.checked_sub(pending).map(|i| filled[i].clone()); let mut endless = false; - for item in &mut self.program.items[items..] { - let tys: Vec<&mut Type> = match item { - Item::Struct { fields, .. } => fields.iter_mut().map(|f| &mut f.ty).collect(), - Item::Union { ty, .. } => vec![ty], - _ => continue, - }; - for ty in tys { - endless |= ty.fill(&later).is_none(); + // Wherever a type typed while they were open is kept: an item, such as an alias or a + // trait object's type, made then or before, and the methods objects hold. + if !filled.is_empty() { + for item in &mut self.program.items { + let tys: Vec<&mut Type> = match item { + Item::Struct { fields, .. } => fields.iter_mut().map(|f| &mut f.ty).collect(), + Item::Union { ty, .. } | Item::Value { ty, .. } => vec![ty], + Item::Function(_) => continue, + }; + for ty in tys { + endless |= ty.fill(&later).is_none(); + } } + endless |= self.fill_objects(&later); } let mut members_done = Vec::with_capacity(done.len()); for mut member in done { diff --git a/ts/src/lower/dynamic.rs b/ts/src/lower/dynamic.rs index 5138b7171..da2352f28 100644 --- a/ts/src/lower/dynamic.rs +++ b/ts/src/lower/dynamic.rs @@ -160,7 +160,8 @@ impl<'tcx> Translator<'_, 'tcx> { return None; }; if !self.objects.contains_key(&principal) { - self.define_object(principal, ty, span)?; + // Over the trait's own parameters, whatever definition is typing it. + self.outside_definition(|t| t.define_object(principal, ty, span))?; } let args = self.object_args(principal, ty, span); let sym = self.objects[&principal].sym; @@ -352,6 +353,11 @@ impl<'tcx> Translator<'_, 'tcx> { /// The object of `principal` holding its method `index` from now on: in its type, and in /// each class its objects wrap a value in. fn hold(&mut self, principal: DefId, index: usize, span: Span) { + self.outside_definition(|t| t.hold_method(principal, index, span)); + } + + /// [`Self::hold`], outside any definition being typed. + fn hold_method(&mut self, principal: DefId, index: usize, span: Span) { let tcx = self.tcx; let object = &self.objects[&principal]; let method = &object.methods[index]; @@ -402,6 +408,18 @@ impl<'tcx> Translator<'_, 'tcx> { } } + /// Fill in the [`Type::Later`]s of the methods objects hold, as [`Type::fill`] does, + /// returning whether one reaches itself. + pub(super) fn fill_objects(&mut self, later: &impl Fn(usize) -> Option>) -> bool { + let mut endless = false; + for object in self.objects.values_mut() { + for ty in object.methods.iter_mut().filter_map(|m| m.ty.as_mut()) { + endless |= ty.fill(later).is_none(); + } + } + endless + } + /// Whether an upcast from the trait object `from` to `to`, a made object, is cast to /// `to`'s type: a generic trait's, whose methods have a form only its arguments decide /// where `from`'s have another. diff --git a/ts/src/lower/mod.rs b/ts/src/lower/mod.rs index 092d39295..91b8e4e42 100644 --- a/ts/src/lower/mod.rs +++ b/ts/src/lower/mod.rs @@ -1113,17 +1113,9 @@ impl<'a, 'tcx> Translator<'a, 'tcx> { // Its fields' types are refused where they are written, whatever reached it. let held = std::mem::replace(&mut self.held, false); let glue = std::mem::replace(&mut self.glue, false); - let defining = self.defining.take(); - let field = self.defining_state.field.take(); // A named type, so an instance its fields hold again ends there. - let instancing = std::mem::take(&mut self.defining_state.instancing); - let instanced = std::mem::take(&mut self.defining_state.instanced); - let too_deep = std::mem::take(&mut self.defining_state.too_deep); - let projecting = std::mem::take(&mut self.defining_state.projecting); - let info = self.define(adt, span); - (self.held, self.glue, self.defining, self.defining_state.field) = (held, glue, defining, field); - (self.defining_state.instancing, self.defining_state.instanced) = (instancing, instanced); - (self.defining_state.too_deep, self.defining_state.projecting) = (too_deep, projecting); + let info = self.outside_definition(|t| t.define(adt, span)); + (self.held, self.glue) = (held, glue); info } diff --git a/ts/src/ts.rs b/ts/src/ts.rs index e650dedc5..37cb73843 100644 --- a/ts/src/ts.rs +++ b/ts/src/ts.rs @@ -746,13 +746,13 @@ impl Type { } } - /// It with each [`Type::Later`] replaced by the arguments `later` gives it, or `None` - /// if one reaches itself. - pub fn fill(&mut self, later: &impl Fn(usize) -> Vec) -> Option<()> { + /// It with each [`Type::Later`] replaced by the arguments `later` gives it, if it gives + /// any, or `None` if one reaches itself. + pub fn fill(&mut self, later: &impl Fn(usize) -> Option>) -> Option<()> { self.fill_from(later, &mut Vec::new()) } - fn fill_from(&mut self, later: &impl Fn(usize) -> Vec, open: &mut Vec) -> Option<()> { + fn fill_from(&mut self, later: &impl Fn(usize) -> Option>, open: &mut Vec) -> Option<()> { match self { Type::Param(_) | Type::Builtin(_) | Type::StrLit(_) => Some(()), Type::Later(_) => unreachable!("projection arguments outside a generic type's arguments"), @@ -767,11 +767,15 @@ impl Type { filled.push(t); continue; }; + let Some(args) = later(id) else { + filled.push(t); + continue; + }; if open.contains(&id) { return None; } open.push(id); - for mut t in later(id) { + for mut t in args { t.fill_from(later, open)?; filled.push(t); }