Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions quest/m2/ts-async.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ moq-net still needs, by its census from MIR at the pinned revision:

- Async closures, which only moq-net's tests write today.
- kio's own API, reached across crates (`Pending`, `Consumer::poll`,
`Waiter::poll_future`, `Tasks`), translates through
[safe dependencies](/quest/m2/ts-model/crates.md), and kio's `RawWaker` vtable and
`Waiter::poll_future`, `Tasks`), translates from source as a
[dependency](/ts/README.md), and kio's `RawWaker` vtable and
`Box<dyn FnMut>` tasks are [bindings](/ts/README.md#bindings).
- The edge: `rt.spawn`'s shape for callers, and cancelling a spawned task.

Expand Down
1 change: 0 additions & 1 deletion quest/m2/ts-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ Public API: the translator's output for moq-net's model.
## Required

- [Shared ownership](/quest/m2/ts-shared.md) - kio's waiters are `Arc`/`Weak` with observable counts, and its atomics and locks are plain values
- [Translate safe dependencies with the package](/quest/m2/ts-model/crates.md) - kio, moq-clock, moq-pattern, and rangemap translate as one program with moq-net
- [Float formatting](/quest/m2/ts-model/floats.md) - `f64` formats and parses as std does
- [The rest of std the model reaches](/quest/m2/ts-model/std-rest.md) - the std calls the census still refuses
- [Well-known crates without config](/quest/m2/ts-model/stock.md) - rust2-ts maps `bytes`, `rand`, and `tracing` itself, so `model.toml` stops binding and ignoring them
Expand Down
33 changes: 0 additions & 33 deletions quest/m2/ts-model/crates.md

This file was deleted.

11 changes: 11 additions & 0 deletions ts/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions ts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/kixelated/rust2"

[workspace.lints.rust]
# What rust2-ts sets where it translates, for a safe twin of `unsafe`.
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(rust2_ts)"] }

[workspace.lints.clippy]
# Every suppression says why, so the next lint audit can judge it.
allow_attributes_without_reason = "warn"
Expand Down
25 changes: 22 additions & 3 deletions ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ It runs `cargo check` on the package with itself as `RUSTC_WRAPPER`, for
`wasm32-unknown-unknown`, with std built from source for it, unwinding and with every
function's MIR, which needs the toolchain's `rust-src`. Every crate compiles as rustc
would; the configured library is also translated, after type and borrow checking, starting from its roots and following
what they reach. A construct outside the subset is a compile error with its span, and
what they reach. Every crate it depends on but std's translates with it as one program,
as far as the roots reach: each compiles with every function's MIR, and its types and
calls translate as the package's own do. They compile with `--cfg rust2_ts`, so a crate
can give translation a safe twin of its `unsafe`, and otherwise with the panic strategy
cargo gives them, so a panic drops nothing in a dependency's frames. Their `unsafe` is
refused where it is, as the package's is, unless a binding stands in for the item holding
it. Only the writes of the dependencies' functions translation reaches decide what it
copies, so it runs again when it reaches one it didn't read. A construct outside the subset is a compile error with its span, and
then nothing is written. A type without a JS value is refused where a value of it is made
or received, such as a parameter, a field, or a call, rather than again at each variable
holding one; any variable or temporary that holds a type whose JS value is ambiguous, such
Expand Down Expand Up @@ -51,7 +58,8 @@ translation still fails on any refusal.

`just ts census` runs it at the pinned moq revision, into `target/census`, on moq-net
whole (`moq/net.toml`), on its model's public types with their boundaries bound
(`moq/model.toml`), and on kio, and the nightly job publishes their tables.
(`moq/model.toml`), and on kio, and the nightly job
publishes their tables.

## Output

Expand All @@ -63,7 +71,10 @@ runtime: what std does that JS does not, with iterators in `rt/iter.ts`, `str`'s
in `rt/str.ts`, collections in `rt/collections.ts`, ownership, atomics, and async in
`rt/task.ts`, `f64`'s methods in `rt/float.ts`, and `Duration` in `rt/time.ts`. What translates from std's own MIR,
`Option`'s, `Result`'s, and `Poll`'s methods, goes in a module under its crate's name, as
`core/option.ts` holds `map$Option_u64_u64_rescale_closure0`.
`core/option.ts` holds `map$Option_u64_u64_rescale_closure0`. A dependency's modules go
under its crate's name too, `kio.ts` and `kio/lock.ts`, with the instances of its generic
functions that the package's types make, so a module at the package's root named for a
crate translation reaches is refused, and so are two versions of one crate it reaches.

| Rust | TypeScript |
| --- | --- |
Expand Down Expand Up @@ -549,6 +560,14 @@ built natively for wasm32 and run beside its translation on the same inputs by
differential test runs them against native Rust. `wrong.toml` binds TS that doesn't match
the Rust signatures, which `tsc` refuses at each item, with the errors in `wrong.txt`.
`refused.toml` holds bindings the config can't make, with the errors in `refused.txt`.
- `crates` translates with `crates/dep`, which writes into a `Copy` type only the package
copies, ranks the package's type in a generic function, drops, iterates, and reads
through a raw pointer behind a safe twin under `rust2_ts` and in a function
`bind/last.ts` stands in for. `bind/slots.ts` stands in for a type whose method alone
writes into an array the package copies. Its differential test runs them against native Rust.
`refused.toml` drops the binding, its `rev` feature makes the dependency's iterator run
from either end, and its `shared` feature adds a module of the dependency's name, with
the errors in `refused.txt`, `rev.txt`, and `shared.txt`.
- `reject` holds constructs outside the subset; its expected errors are `expected.txt`,
and its expected census is `census.md`.

Expand Down
5 changes: 3 additions & 2 deletions ts/fixtures/codec/expected/coding/decode.DecodeError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import * as rt from "../rt.ts";
import type * as BoundsExceeded from "../coding/varint.BoundsExceeded.ts";
import * as SessionError from "../lib.SessionError.ts";
import { asDisplay$AsDisplay_usize } from "../thiserror/display.ts";

export function fmt$Display(self: DecodeError, __formatter: rt.Formatter): rt.Result<undefined, rt.FmtError> {
if (self.tag === "Short") {
Expand Down Expand Up @@ -35,8 +36,8 @@ export function fmt$Display(self: DecodeError, __formatter: rt.Formatter): rt.Re
return rt.fmtWriteStr(__formatter, "expected end");
}
if (self.tag === "MessageTooLarge") {
const _26: rt.usize = self.size;
const _28: rt.usize = self.max;
const _26: rt.usize = asDisplay$AsDisplay_usize(self.size);
const _28: rt.usize = asDisplay$AsDisplay_usize(self.max);
const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.usizeDisplay(_26, f), (f: rt.Formatter) => rt.usizeDisplay(_28, f)];
return rt.fmtWriteFmt(__formatter, ["message too large: ", args4[0], " bytes exceeds ", args4[1], " byte limit"]);
}
Expand Down
4 changes: 3 additions & 1 deletion ts/fixtures/codec/expected/lib.SessionError.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "./rt.ts";
import { asDisplay$AsDisplay_u16 } from "./thiserror/display.ts";

export function fmt$Display(self: SessionError, __formatter: rt.Formatter): rt.Result<undefined, rt.FmtError> {
if (self.tag === "ProtocolViolation") {
return rt.fmtWriteStr(__formatter, "protocol violation");
}
const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(self._0, f)];
const arg = asDisplay$AsDisplay_u16(self._0);
const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.u16Display(arg, f)];
return rt.fmtWriteFmt(__formatter, ["app code=", args2[0]]);
}

Expand Down
13 changes: 13 additions & 0 deletions ts/fixtures/codec/expected/thiserror/display.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Generated by rust2-ts. Do not edit.

import * as rt from "../rt.ts";

export function asDisplay$AsDisplay_u16(self: rt.u16): rt.u16 {
return self;
}

export function asDisplay$AsDisplay_usize(self: rt.usize): rt.usize {
return self;
}

//# sourceMappingURL=display.ts.map
23 changes: 23 additions & 0 deletions ts/fixtures/crates/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "crates-fixture"
description = "A package that translates with a dependency as one program"
version = "0.0.0"
edition.workspace = true
license.workspace = true
publish = false

[lib]
# The cdylib is the native side of the differential test, built for wasm32.
crate-type = ["lib", "cdylib"]

[features]
# A module of the dependency's name, whose files would be the dependency's.
shared = []
# The dependency's iterator runs from either end, which translation refuses.
rev = ["crates-dep/rev"]

[dependencies]
crates-dep = { path = "dep" }

[lints]
workspace = true
8 changes: 8 additions & 0 deletions ts/fixtures/crates/bind/last.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// `crates_dep::last`, which reads through a raw pointer.

import * as rt from "../rt.ts";

export function last(bytes: rt.ByteSlice): rt.u8 {
if (bytes.length === 0) rt.panic("no last byte");
return bytes.data[bytes.start + bytes.length - 1] as rt.u8;
}
14 changes: 14 additions & 0 deletions ts/fixtures/crates/bind/slots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// `crates_dep::Slots`, which writes into the array it is given.

import * as rt from "../rt.ts";

export type Slots = { base: rt.i16 };

export function withBase(base: rt.i16): Slots {
return { base };
}

export function fill(self: Slots, out: rt.i16[]): undefined {
out[0] = self.base;
out[1] = rt.i16Sub(self.base, 1 as rt.i16);
}
14 changes: 14 additions & 0 deletions ts/fixtures/crates/dep/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "crates-dep"
description = "The dependency crates-fixture translates with"
version = "0.0.0"
edition.workspace = true
license.workspace = true
publish = false

[features]
# `Countdown` runs from either end.
rev = []

[lints]
workspace = true
130 changes: 130 additions & 0 deletions ts/fixtures/crates/dep/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! The dependency `crates-fixture` translates with: a type the package copies, which only
//! this crate writes into, a generic function over the package's type, drops, an
//! iterator, and `unsafe` with a safe twin where it translates and without one.

use std::sync::{Arc, Mutex};

#[derive(Clone, Copy)]
pub struct Point {
pub x: i32,
pub y: i32,
}

impl Point {
/// Move it by `(dx, dy)`: the only write into a `Point` in either crate.
pub fn shift(&mut self, dx: i32, dy: i32) {
self.x += dx;
self.y += dy;
}
}

/// What [`heaviest`] ranks by.
pub trait Weigh {
fn weight(&self) -> u32;
}

/// The heaviest of `items`, the first of equals.
pub fn heaviest<T: Weigh>(items: &[T]) -> Option<&T> {
let mut best: Option<&T> = None;
for item in items {
match best {
Some(b) if b.weight() >= item.weight() => {}
_ => best = Some(item),
}
}
best
}

/// Notes its id in a log when it drops.
pub struct Guard {
id: u8,
log: Arc<Mutex<Vec<u8>>>,
}

impl Guard {
pub fn new(id: u8, log: &Arc<Mutex<Vec<u8>>>) -> Self {
Self { id, log: log.clone() }
}
}

impl Drop for Guard {
fn drop(&mut self) {
self.log.lock().unwrap().push(self.id);
}
}

/// Counts down from where it starts to one.
pub struct Countdown {
next: u8,
last: u8,
}

impl Countdown {
pub fn new(from: u8) -> Self {
Self { next: from, last: 0 }
}
}

impl Iterator for Countdown {
type Item = u8;

fn next(&mut self) -> Option<u8> {
if self.next == self.last {
return None;
}
self.next -= 1;
Some(self.next + 1)
}
}

#[cfg(feature = "rev")]
impl DoubleEndedIterator for Countdown {
fn next_back(&mut self) -> Option<u8> {
if self.next == self.last {
return None;
}
self.last += 1;
Some(self.last)
}
}

/// Writes into the arrays it is given, where a binding stands in for it.
pub struct Slots {
base: i16,
}

impl Slots {
pub fn with_base(base: i16) -> Self {
Self { base }
}

/// Fill `out` from the base: the only write into a `[i16; 2]`.
pub fn fill(&self, out: &mut [i16; 2]) {
*out = [self.base, self.base - 1];
}
}

/// The first byte of `bytes`.
pub fn first(bytes: &[u8]) -> u8 {
assert!(!bytes.is_empty(), "no first byte");
read(bytes)
}

#[cfg(not(rust2_ts))]
fn read(bytes: &[u8]) -> u8 {
// SAFETY: `first` checked it holds a byte.
unsafe { *bytes.as_ptr() }
}

/// `read`'s safe twin, which translates.
#[cfg(rust2_ts)]
fn read(bytes: &[u8]) -> u8 {
bytes[0]
}

/// The last byte of `bytes`, which only a binding translates.
pub fn last(bytes: &[u8]) -> u8 {
assert!(!bytes.is_empty(), "no last byte");
// SAFETY: it holds a byte.
unsafe { *bytes.as_ptr().add(bytes.len() - 1) }
}
9 changes: 9 additions & 0 deletions ts/fixtures/crates/expected/bind/last.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Generated by rust2-ts. Do not edit.
// `crates_dep::last`, which reads through a raw pointer.

import * as rt from "../rt.ts";

export function last(bytes: rt.ByteSlice): rt.u8 {
if (bytes.length === 0) rt.panic("no last byte");
return bytes.data[bytes.start + bytes.length - 1] as rt.u8;
}
Loading