diff --git a/ffi/README.md b/ffi/README.md index c765b8cfe..9b170f0f0 100644 --- a/ffi/README.md +++ b/ffi/README.md @@ -9,17 +9,19 @@ rust2-ffi --config ffi.toml (--out dir | --census dir) [--manifest-path Cargo.to ``` It runs `cargo check` on the package with itself as `RUSTC_WRAPPER`, on the host, through -the front end it shares with [rust2-ts](../ts/README.md) in [`driver/`](../driver). Once -rustc has checked the configured library, it walks every `pub` item reachable from the -crate root and exports it by fixed rules, or refuses it with a compile error at its span. -It walks each other package the config lists the same way, from the metadata of the -crate the library uses through its dependency on that package, so one library holds them -all and an export names another's types across crates. `--out` writes a crate of `extern "C"` functions over the -libraries, its runtime beside it, and the header in `include/.h`; `cargo build` -on that crate makes a static and a shared library. That crate enables each export's -features as the check did, including `--features /`; such a feature on a -dependency that is no export is an error. It resolves them as the check did, wherever it -is built: its manifest carries, as a `[patch]`, each patch or `[replace]` cargo applied +the front end it shares with [rust2-ts](../ts/README.md) in [`driver/`](../driver). The +package is the facade: once rustc has checked its library, it walks every `pub` item +reachable from the crate root, following `pub use` re-exports of other crates' items and +modules, and exports it by fixed rules, or refuses it with a compile error at its span. +So a facade such as moq-ffi exports several crates into one library by re-exporting what +it ships, and a type no path from its root reaches is refused. The prefix of every C name +is the library's crate name, Cargo's `[lib] name`, which defaults to the package's name in +snake case. `--out` writes a crate of `extern "C"` functions over the facade, its runtime +beside it, and the header in `include/.h`; `cargo build` on that crate makes a +static and a shared library. That crate enables the facade's features as the check did; +a feature named in a dependency, `--features /`, is an error, since +the facade enables what its dependencies build with. It resolves them as the check did, +wherever it is built: its manifest carries, as a `[patch]`, each patch or `[replace]` cargo applied to what the library builds with on any platform, read off cargo's resolution, wherever it was declared (the manifest, cargo's config, `--config`, or the environment), keyed by its source's URL. @@ -35,30 +37,18 @@ ABI through cgo, and `dart/.dart` holds it in Dart, over the C ABI throu ```toml # ffi.toml, beside the package's Cargo.toml unless --manifest-path says otherwise. -package = "moq-net" -# The prefix of every C name, the package's name in snake case by default. -prefix = "moq" -# Paths from the crate root left out: a module, an item, or one method or field. Each +# Paths from the facade's root left out: a module, an item, or one method or field. Each # must name something exported. Nothing else leaves an item out. As in rustdoc, a path a # method or constant shares with a field names the method, and `field@` the field. exclude = ["track::Producer::poll_closed"] - -# C names, without the prefix, for paths whose names the naming rule makes clash. -[rename] -"session::Stats" = "session_stats_snapshot" - -# Another package exported into the same library, which the package builds with: its -# own `exclude` and `rename`, by paths from its crate root. -[crates.moq-pattern] -exclude = ["Pattern::new"] ``` ## Rules -Every `pub` item reachable from an exported crate's root, by its shortest path, is -exported or refused. `#[doc(hidden)]` items are no part of the API and are left out; type -aliases export nothing of their own. A re-export of another export's item names that -export's, and adds nothing; another crate's type is refused. +Every `pub` item reachable from the facade's root, by its shortest path, is exported or +refused. `#[doc(hidden)]` items are no part of the API and are left out; type aliases +export nothing of their own. A re-export of the standard library's item is refused, since +its types map by role; a type no path reaches is refused. | Rust | C | | --- | --- | @@ -158,43 +148,44 @@ refused, and the census counts why. built with the header. A binding checks the two match before any other call, so a header and a library from different builds refuse to mix. - **Panics** abort, as an `extern "C"` function's do. -- **Names** are the prefix, then the item's path from its crate's root, each segment in - snake case, joined by `_`: `track::Producer::append_group` is - `moq_track_producer_append_group`, and moq-pattern's `Patterns` is `moq_patterns`. - Joining loses where segments split, and every export shares the prefix, so every C - name is registered, and two items claiming one is a compile error at both spans, fixed - by a `rename`: moq-net's `session::Stats` and `Session::stats` are both - `moq_session_stats`. So are a field and the method of its name, as moq-tokio's - `Backoff::initial`; `field@Backoff::initial` names the field. A field's setter is - named as its getter, with `set_` after its type's name: `moq_connect_config_set_tls`, - and `moq_backoff_set_initial_setting` once renamed. +- **Names** are the prefix, then the item's path from the facade's root, each segment in + Rust's case, joined by `_`: `track::Producer::append_group` is + `moq_track_Producer_append_group`, and moq-net's `session::Stats` and `Session::stats` + are `moq_session_Stats` and `moq_Session_stats`. Items of other crates are named by + where the facade re-exports them, so two of one name need two paths. A field's getter + and setter add `_get` and `_set` to its path, as SWIG's do, so they stay apart from a + method of the field's name and from a `set_` method, which Rust names verb first: + moq-tokio's `Backoff::initial` field, which moq's facade re-exports in `tokio`, is + `moq_tokio_Backoff_initial_get` and `moq_tokio_Backoff_initial_set` beside the method + `moq_tokio_Backoff_initial`. An enum variant's + payload is `_`, and its tag and a constant are the name in upper case. + Joining still loses where segments split, and the runtime shares the prefix, so every C + name is registered, and two items claiming one, which only names Rust's conventions + leave apart can, is a compile error at both spans: `audio_encoder::finish` and + `audio::encoder_finish` are both `moq_audio_encoder_finish`. An instance is named by its generic item's name, then each argument but those at their defaults after the last that is not, so the instance at its defaults is named as the - item. Each argument takes the last segment of its C name, such as `extra` for moq-mux's - `catalog::hang::Extra`, so `catalog::Producer` is - `moq_catalog_producer_extra`. Where two instances of one item would still share a name, + item. Each argument takes the last segment of its C name, such as `Extra` for + `mux::catalog::hang::Extra`, so `mux::catalog::Producer` is + `moq_mux_catalog_Producer_Extra`. Where two instances of one item would still share a name, each argument that tells them apart takes one more segment at a time, until they - differ: the api fixture's `Counter::seeded` is `api_counter_seeded_point` at its - `Point`, and `api_counter_seeded_polar_point` at `polar::Point`. The names depend on + differ: the api fixture's `Counter::seeded` is `api_Counter_seeded_Point` at its + `Point`, and `api_Counter_seeded_polar_Point` at `polar::Point`. The names depend on every instance the item exports at, so one an upstream change adds can lengthen another's name. A type instance's methods are named through it, and a function instance appends its arguments' names, a reference's by what it borrows. An `exclude` of a type or method by the generic type's path leaves it out of every instance, and by the instance's path - (`"catalog::Producer::stats"`) out of that one. A - `rename` matches paths as `exclude` does: by the generic item's path it renames each of - its instances, and by an instance's path (`"catalog::Producer"`) - that one alone, whose arguments then add nothing to its name, while the others keep - theirs. The longer path wins, and at one length, the instance's. + (`"mux::catalog::Producer::stats"`) out of that one. ## C++ `.hpp` is header-only C++ over the C header, for C++17, 20, and 23, with exceptions and RTTI on or off: it throws nothing and reads no type information. -- **Names** are the C names without the prefix, in a namespace of it: `moq_track_producer` - is `moq::track_producer`, and its method `moq_track_producer_append_group` is - `moq::track_producer::append_group`. A keyword takes a trailing `_` (`counter::new_`), +- **Names** are the C names without the prefix, in a namespace of it: `moq_track_Producer` + is `moq::track_Producer`, and its method `moq_track_Producer_append_group` is + `moq::track_Producer::append_group`. A keyword takes a trailing `_` (`counter::new_`), as does a member named as its class or one of its fields, and a parameter named as a member or as anything in the namespace, which it would hide. - **Objects** are classes owning their handle through a `std::unique_ptr` whose deleter @@ -267,8 +258,9 @@ in as a `ByteArray`, bytes returned as their owner, and anything else encoded in no call holds it; a call after it throws. One never closed is closed once the GC collects it, on a daemon thread a phantom reference queue feeds. A `self` method, or passing the object by value, closes it once the call returns. `Clone` is `clone()`, and `PartialEq` is `equals`. - A field is a `val`, and its setter a function, `setFirst(value)`, since setting an object - moves it in and closes its handle, which an assignment would hide. + A field is a `val`, `firstGet`, and its setter a function, `firstSet(value)`, named as + the C ones, since setting an object moves it in and closes its handle, which an + assignment would hide. - **Records** are data classes, compared by their fields, and **enums** an `enum class`, or for one with data a sealed class of a data class per variant. `Display` is `toString()`, `Debug` `debug()`, and `Default` a companion's `default()`. @@ -328,8 +320,8 @@ docstrings and signatures. which a call holds until it returns, so there is no `close()`. A `self` method, or passing the object by value, moves its value out and closes it once the call returns, after which a call raises `ValueError`. `Clone` is `clone()`, and `PartialEq` is `==`, unhashable. A field is a - property, and its setter a method, `set_first(value)`, since setting an object moves it - in and closes it, which an assignment would hide. + property, `first_get`, and its setter a method, `first_set(value)`, named as the C ones, + since setting an object moves it in and closes it, which an assignment would hide. - **Records** are slotted dataclasses, compared by their fields, and **enums** an `Enum` of upper-case members, or for one with data a class of a dataclass per variant, `Event.Moved(p)`. `Display` is `str()`, `Debug` `repr()`, and `Default` a static @@ -384,9 +376,9 @@ in either language mode. holds off until it returns, so there is no `close()`. A `self` method, or passing the object by value, moves its value out and closes it once the call returns, after which a call traps. `Clone` is `clone()`, and `PartialEq` - is `==`. A field is a computed property, and its setter a function, `setFirst(value)`, - since setting an object moves it in and closes its handle, which an assignment would - hide. An object is `Sendable`, since Rust checks each borrow at run time. + is `==`. A field is a computed property, `firstGet`, and its setter a function, + `firstSet(value)`, named as the C ones, since setting an object moves it in and closes + its handle, which an assignment would hide. An object is `Sendable`, since Rust checks each borrow at run time. - **Records** are structs and **enums** Swift enums, with a variant's data as associated values, all `Sendable`. `PartialEq` is `==`, through Rust's; `Display` is `description`, `Debug` `debugDescription`, and `Default` a static `default()`. @@ -418,13 +410,14 @@ in either language mode. later. It includes `.h` from `../include`, as the export lays them out, or from its own directory, and links the library through `CGO_LDFLAGS`. -- **Names** are the C names without the prefix, in Pascal case: `moq_track_producer` is - `TrackProducer`, and its method `moq_track_producer_append_group` is +- **Names** are the C names without the prefix, in Pascal case: `moq_track_Producer` is + `TrackProducer`, and its method `moq_track_Producer_append_group` is `TrackProducer.AppendGroup`. A function of a type that takes no receiver is a function of the package named through its type (`CounterSeededPoint`), but `new`, which is `NewCounter`. A constant keeps its upper case (`LIMIT`), and one that is not a `bool` or - integer is a function of that name (`GREETINGS()`). A method named `Close` or `Error` - takes a trailing `_`, and a parameter named as a Go keyword, a predeclared name, or a + integer is a function of that name (`GREETINGS()`). A method named `Close` or `Error`, + and a function of the package named as a type, such as moq's `clock::wall` beside + `clock::Wall`, takes a trailing `_`, and a parameter named as a Go keyword, a predeclared name, or a package the code uses takes one too. - **Objects** are pointers to structs owning their handle. `Close()` frees it once no call holds it, and a second `Close()` does nothing; one never closed is closed once the GC @@ -432,8 +425,8 @@ own directory, and links the library through `CGO_LDFLAGS`. receiver or anywhere in its arguments, returns an error matching `ErrClosed`, even when `Close` races it on another goroutine, so every such call returns an `error`; `String()` and `GoString()`, which return none, return its text. A `self` method, or passing the object by value, closes - it once the call returns. `Clone` is `Clone()`, and a `pub` field a getter, `First()`, - and a setter, `SetFirst(v)`. + it once the call returns. `Clone` is `Clone()`, and a `pub` field a getter, `FirstGet()`, + and a setter, `FirstSet(v)`. - **Records** are structs, and **plain enums** an `int32` type with a constant per variant (`ShapeSquare`). An enum with data is an interface each variant's struct implements (`EventMoved{V0: p}`), sealed so only the package's do; its methods are the @@ -490,13 +483,14 @@ the shared library the crate builds with `--features dart` as the package's nati the library's own URI, mapped to the library by a build hook or by `--native-assets`. - **Names** are the C names without the prefix: a type in Pascal case and a member in - camel case, so `moq_track_producer` is `TrackProducer`, and its method - `moq_track_producer_append_group` is `TrackProducer.appendGroup`. A function of a type + camel case, so `moq_track_Producer` is `TrackProducer`, and its method + `moq_track_Producer_append_group` is `TrackProducer.appendGroup`, and a constant in + upper case (`LIMIT`). A function of a type that returns it is a constructor, which may share its name with a field: an object's `new` is its unnamed one (`Counter('c', 1)`), and any other a named one (`Position.group`). A type's other functions are static members. A reserved word takes a trailing `_` (`Point.default_()`). An item whose Dart name clashes with another's, or - with a name the runtime declares, fails the export: give it a `rename`. + with a name the runtime declares, fails the export: rename one in the source or exclude it. - **Objects** are final classes owning their handle. `close()` frees it once no call holds it, and a second does nothing; one never closed is freed once the garbage collector finds it unreachable, through a finalizable handle of Dart's C API. Any call that meets a @@ -578,8 +572,7 @@ Python and its extension include [`runtime/rt.py`](runtime/rt.py) and ## Census -`--census dir` exports nothing. It writes `.md` for each exported package, -which counts what exports and ranks every refusal by construct and by the type it names, +`--census dir` exports nothing. It writes `.md` for the facade, which counts what exports and ranks every refusal by construct and by the type it names, lists each refused item at its span, and ends with the `exclude` list that exports the rest. It ignores the config's `exclude`. It still fails on an `instance not inferred` refusal, which is a bug in the exporter's inference rather than in the crate. @@ -596,7 +589,7 @@ nix develop .#android --command just ffi android # the Kotlin test, shrunk by R nix develop .#bench --command just ffi bench # frame writes and reads, through the export from C, C++, Kotlin, Python, Swift, Go, and Dart, and moq's rs/moq-c ``` -The `api` fixture covers each rule once, and names the `dep` fixture's types, which +The `api` fixture covers each rule once, and re-exports the `dep` fixture's types, which export into the same library. `tests/api.c` runs the same script as the fixture's `examples/transcript.rs`, and the two transcripts must match line for line; then it breaks each runtime check, which must abort with its message. `tests/api.cpp` diff --git a/ffi/fixtures/api/Cargo.toml b/ffi/fixtures/api/Cargo.toml index 12f3de2bb..ce5cc4b5a 100644 --- a/ffi/fixtures/api/Cargo.toml +++ b/ffi/fixtures/api/Cargo.toml @@ -6,9 +6,13 @@ edition = "2024" license = "MIT OR Apache-2.0" publish = false -# Named apart from the package, which the export must follow. +# Named apart from the package: the export's prefix, which the export must follow. [lib] -name = "api_lib" +name = "api" + +[features] +# Doubles each area's size in dep-fixture, which the export must build as the check did. +doubled = ["dep-fixture/doubled"] [dependencies] bytes = "1.11.1" diff --git a/ffi/fixtures/api/examples/transcript.rs b/ffi/fixtures/api/examples/transcript.rs index 7fb065d96..56b36d1f7 100644 --- a/ffi/fixtures/api/examples/transcript.rs +++ b/ffi/fixtures/api/examples/transcript.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use std::task::{Context, Poll, Wake, Waker}; -use api_lib::{ +use api::{ Channel, Counter, Event, LIMIT, Label, Log, Logger, Message, Name, Node, Pair, Point, Registry, Shape, Size, Stack, Stamp, Tally, Window, }; @@ -72,14 +72,10 @@ fn main() { println!("debug {c:?}"); let label = c.into_label(Some(p)); println!("label {label:?}"); - let empty = api_lib::Note { + let empty = api::Note { text: Some(String::new()), }; - println!( - "note {} {}", - api_lib::has_text(empty), - api_lib::has_text(api_lib::Note::default()) - ); + println!("note {} {}", api::has_text(empty), api::has_text(api::Note::default())); let (below, above) = Counter::new("s", 7).split(5); println!( @@ -89,34 +85,34 @@ fn main() { above.name(), above.get() ); - let (only,) = api_lib::only(Counter::new("o", 3)); + let (only,) = api::only(Counter::new("o", 3)); println!("only {} {}", only.name(), only.get()); - let (a, b, c, d) = api_lib::widths(98305); + let (a, b, c, d) = api::widths(98305); println!("widths {a} {b} {c} {d}"); for b in [2, 0] { - match api_lib::divide(7, b) { + match api::divide(7, b) { Ok((q, r)) => println!("divide {q} {r}"), Err(e) => println!("divide err {e}"), } } for n in [98305, 0] { - match api_lib::spread(n) { + match api::spread(n) { Some((a, b, c, d)) => println!("spread {a} {b} {c} {d}"), None => println!("spread none"), } } for needle in [&b"lo"[..], b"x"] { - match api_lib::find(b"hello", needle) { + match api::find(b"hello", needle) { Some((at, len)) => println!("find {at} {len}"), None => println!("find none"), } } - let rows = api_lib::rows(&[Point { x: 1, y: 2 }, Point { x: 3, y: 3 }]); + let rows = api::rows(&[Point { x: 1, y: 2 }, Point { x: 3, y: 3 }]); let rows: Vec = (rows.iter()) .map(|(name, shape, p)| format!("{name} {shape:?} {} {}", p.x, p.y)) .collect(); println!("rows {}", rows.join(", ")); - let board = api_lib::Board::new(vec![ + let board = api::Board::new(vec![ ("low".to_string(), Counter::new("l", 1)), ("high".to_string(), Counter::new("h", 9)), ]); @@ -124,26 +120,26 @@ fn main() { .map(|(name, counter)| format!("{name} {} {}", counter.name(), counter.get())) .collect(); println!("ranked {}", ranked.join(", ")); - let span = api_lib::Span { + let span = api::Span { bounds: (2, 10), named: Some(("s".to_string(), Shape::Square)), }; - let ((a, b), (c, d)) = api_lib::halves(&span); + let ((a, b), (c, d)) = api::halves(&span); println!("halves {a} {b} {c} {d} {span:?}"); - let (n, text) = api_lib::swapped(("t".to_string(), 4)); + let (n, text) = api::swapped(("t".to_string(), 4)); println!("swapped {n} {text}"); for value in [None, Some((None,)), Some((Some(3),))] { - println!("doubled {:?}", api_lib::doubled(value)); + println!("doubled {:?}", api::doubled(value)); } let lent = (vec![1, 2],); - let (lent,) = api_lib::lent_back(&lent); - let (boxed,) = *api_lib::boxed_tuple(vec![3]); + let (lent,) = api::lent_back(&lent); + let (boxed,) = *api::boxed_tuple(vec![3]); println!("lent back {lent:?} boxed {boxed:?}"); println!( "title {:?} {:?} named {:?}", - api_lib::title(true), - api_lib::title(false), - api_lib::named("x") + api::title(true), + api::title(false), + api::named("x") ); // Each lent counter is a new handle to a clone. @@ -165,73 +161,73 @@ fn main() { // A setter moves an object in, as assignment does. registry.first = Counter::new("second", 3); println!("set first {} {}", registry.first.get(), registry.first.name()); - println!("walk {}", api_lib::walk(3, |cursor| cursor.at() * 2)); - println!("each {}", api_lib::each(2, |at| println!("visit {at}"))); - println!("offset {}", api_lib::offset(|| 7, 3)); - println!("shift {}", api_lib::shift(|| 7, 4)); + println!("walk {}", api::walk(3, |cursor| cursor.at() * 2)); + println!("each {}", api::each(2, |at| println!("visit {at}"))); + println!("offset {}", api::offset(|| 7, 3)); + println!("shift {}", api::shift(|| 7, 4)); let name = Name::owned("a".to_string()); let mut joined = name.join(&Name::owned("b".to_string())); println!("joined {} {joined:?} {}", joined.as_str(), joined == name); joined.rename("c".to_string()); println!("renamed {}", joined.as_str()); - let long = api_lib::longer(joined, &Name::owned("dd".to_string())); + let long = api::longer(joined, &Name::owned("dd".to_string())); println!("longer {}", long.as_str()); - println!("unnamed [{}]", api_lib::unnamed().as_str()); + println!("unnamed [{}]", api::unnamed().as_str()); - println!("total {}", api_lib::total(2..5)); - let wide = api_lib::widen(2..5, 1); + println!("total {}", api::total(2..5)); + let wide = api::widen(2..5, 1); println!("widen {} {}", wide.start, wide.end); println!( "count {} {} {} {} {}", - api_lib::count(2..5), - api_lib::count(2..=5), - api_lib::count(3..), - api_lib::count(..), - api_lib::count((Bound::Excluded(2), Bound::Included(4))) + api::count(2..5), + api::count(2..=5), + api::count(3..), + api::count(..), + api::count((Bound::Excluded(2), Bound::Included(4))) ); - println!("end {:?} {:?}", api_lib::end(3), api_lib::end(0)); - let (start, end) = api_lib::ends(3); + println!("end {:?} {:?}", api::end(3), api::end(0)); + let (start, end) = api::ends(3); println!("ends {start:?} {end:?}"); for n in [2, 5] { - match api_lib::below(n, 3) { + match api::below(n, 3) { Ok(n) => println!("below {n}"), Err((start, end)) => println!("below err {start:?} {end:?}"), } } let window = Window { range: 2..8, - end: api_lib::end(5), + end: api::end(5), }; println!("window {} {} {window:?}", window.contains(4), window.contains(6)); - println!("{}", api_lib::greet("x", Shape::Square)); - println!("len {}", api_lib::len(b"abc")); - println!("hex {}", api_lib::hex(Bytes::from_static(b"\xff\x00a"))); - println!("timed hex {}", api_lib::timed_hex(Bytes::from_static(b"\xff\x00a"))); - println!("sample hex {}", api_lib::sample_hex(&b"\xff\x00a"[..])); - println!("queued hex {}", api_lib::queued_hex(b"\xff\x00a".to_vec())); + println!("{}", api::greet("x", Shape::Square)); + println!("len {}", api::len(b"abc")); + println!("hex {}", api::hex(Bytes::from_static(b"\xff\x00a"))); + println!("timed hex {}", api::timed_hex(Bytes::from_static(b"\xff\x00a"))); + println!("sample hex {}", api::sample_hex(&b"\xff\x00a"[..])); + println!("queued hex {}", api::queued_hex(b"\xff\x00a".to_vec())); let tick = Label { name: "tick".to_string(), at: None, ttl: None, }; - println!("tick {}", api_lib::tick_name(tick)); + println!("tick {}", api::tick_name(tick)); let rooted = Label { name: "rooted".to_string(), at: None, ttl: None, }; - println!("rooted {}", api_lib::rooted_name(rooted)); - println!("stored {}", api_lib::stored_text("stored")); + println!("rooted {}", api::rooted_name(rooted)); + println!("stored {}", api::stored_text("stored")); let layer = Label { name: "layer".to_string(), at: None, ttl: None, }; - println!("layer {}", api_lib::layer_name(layer)); + println!("layer {}", api::layer_name(layer)); for event in [Event::Moved(p), Event::Named { name: "n".to_string() }, Event::Done] { - println!("{}", api_lib::describe(&event)); + println!("{}", api::describe(&event)); } let stamp = Stamp::from_millis(2500); @@ -252,12 +248,12 @@ fn main() { message.stamp = None; println!("stamp {}", message.stamp.is_some()); - println!("kept {}", api_lib::emit(Print, vec![1, 2, 3, 4])); + println!("kept {}", api::emit(Print, vec![1, 2, 3, 4])); let logger = Logger::new(Box::new(Print)); println!("write {}", logger.write("hi")); drop(logger); println!("dropped"); - let kept = api_lib::filter(vec![1, 2, 3, 4], |level, line| { + let kept = api::filter(vec![1, 2, 3, 4], |level, line| { println!("filter {level} {line}"); level > 2 }); @@ -309,10 +305,10 @@ fn main() { let tally = Tally::new(); tally.bump(); println!("tally {} {}", tally.bump(), tally == tally); - println!("both {}", api_lib::bump_both(&tally, &tally)); - println!("peek {}", api_lib::peek(&tally, |tally| tally.bump())); + println!("both {}", api::bump_both(&tally, &tally)); + println!("peek {}", api::peek(&tally, |tally| tally.bump())); - let area = api_lib::grow(&dep::Area::new(Size { width: 2, height: 3 }), 1); + let area = api::grow(&dep::Area::new(Size { width: 2, height: 3 }), 1); let size = area.size(); println!("area {} {} {}", size.width, size.height, area.pixels()); @@ -326,7 +322,7 @@ fn main() { pair == swapped ); println!("even {} {}", pair.even(), Pair { left: 7u32, right: 7 }.even()); - let sized = api_lib::tag(Size { width: 1, height: 2 }, 3); + let sized = api::tag(Size { width: 1, height: 2 }, 3); let flipped = sized.swap(); println!("sized {} {} {flipped:?}", flipped.left, flipped.right.width); let beside = Pair { left: 258u32, right: 9 }.beside(sized.left); @@ -345,7 +341,7 @@ fn main() { let from_point = Counter::seeded("p", Point { x: -3, y: 4 }); let from_size = Counter::seeded("s", Size { width: 5, height: 6 }); let from_counter = Counter::seeded("c", &from_point); - let from_polar = Counter::seeded("q", api_lib::polar::Point { turns: 1, distance: 2 }); + let from_polar = Counter::seeded("q", api::polar::Point { turns: 1, distance: 2 }); println!( "seeded {} {} {} {} past {} seeds {}", from_point.get(), @@ -353,39 +349,31 @@ fn main() { from_counter.get(), from_polar.get(), stack.depth_past(&Point { x: 1, y: 2 }), - api_lib::seeds::(&Size { width: 2, height: 3 }) + api::seeds::(&Size { width: 2, height: 3 }) ); - println!("weigh {}", api_lib::weigh(Point { x: -1, y: 2 })); - let left = api_lib::Left { - value: api_lib::Right { value: 1u8 }, + println!("weigh {}", api::weigh(Point { x: -1, y: 2 })); + let left = api::Left { + value: api::Right { value: 1u8 }, }; - let right = api_lib::Right { - value: api_lib::Left { value: 2u8 }, + let right = api::Right { + value: api::Left { value: 2u8 }, }; - println!( - "cross {} outer {}", - api_lib::cross(left, right), - api_lib::Outer::new(4).get() - ); - println!( - "kept {} boxed {}", - api_lib::kept(Point { x: 1, y: -2 }), - api_lib::BOXED.value - ); - println!("keep {}", api_lib::keep(Point { x: 1, y: -2 })); + println!("cross {} outer {}", api::cross(left, right), api::Outer::new(4).get()); + println!("kept {} boxed {}", api::kept(Point { x: 1, y: -2 }), api::BOXED.value); + println!("keep {}", api::keep(Point { x: 1, y: -2 })); let sides = Pair { left: 2u32, right: 5 }; - println!("apply {}", api_lib::apply(sides, |side| side * 3)); - println!("report {}", api_lib::report(sides, Print)); - println!("wrapped {}", api_lib::kept(api_lib::Wrap { value: 4u32 })); - println!("keeps {}", api_lib::keeps(Print).value); + println!("apply {}", api::apply(sides, |side| side * 3)); + println!("report {}", api::report(sides, Print)); + println!("wrapped {}", api::kept(api::Wrap { value: 4u32 })); + println!("keeps {}", api::keeps(Print).value); - let token = api_lib::Token::new(7); - println!("token {} dropped {}", token.id, api_lib::dropped()); + let token = api::Token::new(7); + println!("token {} dropped {}", token.id, api::dropped()); drop(token); - println!("dropped {}", api_lib::dropped()); + println!("dropped {}", api::dropped()); - let mut retry = api_lib::Retry::new(Some(5)); + let mut retry = api::Retry::new(Some(5)); let limit = retry.limit; println!( "retry {} {} {} {}", @@ -397,12 +385,12 @@ fn main() { // The renamed field's setter, named as its getter. retry.limit = None; println!("retry {}", retry.limit()); - println!("slot {}", api_lib::Slot::::new(200).value()); + println!("slot {}", api::Slot::::new(200).value()); // Foreign types: a URL parsed from text, socket addresses, OS paths, and `Vec`s. let base = url::Url::parse("https://example.com/a/b").unwrap(); - println!("url {base} host {:?}", api_lib::host(&base)); - match api_lib::resolve(base.clone(), "c?d") { + println!("url {base} host {:?}", api::host(&base)); + match api::resolve(base.clone(), "c?d") { Ok(url) => println!("resolve {url} {}", url == base), Err(err) => println!("resolve err {err}"), } @@ -412,16 +400,16 @@ fn main() { } let v4: std::net::SocketAddr = "127.0.0.1:8080".parse().unwrap(); let v6: std::net::SocketAddr = "[::1%3]:443".parse().unwrap(); - println!("ports {:?}", api_lib::ports(vec![v4, v6])); - let peer = api_lib::Peer { + println!("ports {:?}", api::ports(vec![v4, v6])); + let peer = api::Peer { addr: v6, names: vec!["a".to_string(), "b".to_string()], } .next("c"); println!("peer {peer:?} {:?}", peer.aliases()); - let file = api_lib::file(std::path::Path::new("dir"), "f\\'\"\t"); + let file = api::file(std::path::Path::new("dir"), "f\\'\"\t"); println!("file {}", file.as_os_str().as_encoded_bytes().escape_ascii()); - let parents = api_lib::parents(vec![file, std::path::PathBuf::from("/")]); + let parents = api::parents(vec![file, std::path::PathBuf::from("/")]); let parents: Vec = (parents.iter()) .map(|p| match p { Some(p) => p.as_os_str().as_encoded_bytes().escape_ascii().to_string(), @@ -429,61 +417,57 @@ fn main() { }) .collect(); println!("parents {}", parents.join(" ")); - let words = api_lib::words(" one two three "); + let words = api::words(" one two three "); println!("words {} {words:?}", words.len()); - println!( - "sentence {} {}", - api_lib::sentence(Some(words)), - api_lib::sentence(None) - ); - println!("sentence {}|", api_lib::sentence(Some(Vec::new()))); - println!("chunks {:?}", api_lib::chunks(b"abcde", 2)); - let mut counters = api_lib::counters(vec!["x".to_string(), "y".to_string()]); + println!("sentence {} {}", api::sentence(Some(words)), api::sentence(None)); + println!("sentence {}|", api::sentence(Some(Vec::new()))); + println!("chunks {:?}", api::chunks(b"abcde", 2)); + let mut counters = api::counters(vec!["x".to_string(), "y".to_string()]); let names: Vec = counters.iter().map(|c| format!("{} {}", c.name(), c.get())).collect(); println!("counters {}", names.join(", ")); let boxed = counters.split_off(1); println!( "worth {} {}", - api_lib::worth(Some(counters), Box::new(boxed)), - api_lib::worth(None, Box::default()) + api::worth(Some(counters), Box::new(boxed)), + api::worth(None, Box::default()) ); println!( "held {:?} {:?}", - api_lib::held_count(Some((Counter::new("h", 4),))), - api_lib::held_count(None) + api::held_count(Some((Counter::new("h", 4),))), + api::held_count(None) ); - let boxed = api_lib::boxed_counter(Box::new(Some("b".to_string()))); + let boxed = api::boxed_counter(Box::new(Some("b".to_string()))); println!( "boxed {:?} {:?} {}", - api_lib::boxed_name(boxed), - api_lib::boxed_name(Box::new(None)), - api_lib::boxed_counter(Box::new(None)).is_none() + api::boxed_name(boxed), + api::boxed_name(Box::new(None)), + api::boxed_counter(Box::new(None)).is_none() ); - println!("less {}", api_lib::less(5, 2)); - println!("sums {:?}", api_lib::sums(vec![vec![1, 2], Vec::new(), vec![3]])); - let summed = api_lib::summed([Point { x: 1, y: 2 }, Point { x: -4, y: 6 }]); - let none = api_lib::summed([]); + println!("less {}", api::less(5, 2)); + println!("sums {:?}", api::sums(vec![vec![1, 2], Vec::new(), vec![3]])); + let summed = api::summed([Point { x: 1, y: 2 }, Point { x: -4, y: 6 }]); + let none = api::summed([]); println!("summed {} {} {} {}", summed.x, summed.y, none.x, none.y); println!( "longest {} {}", - api_lib::longest(["a".to_string(), "abc".to_string()]), - api_lib::longest([]) + api::longest(["a".to_string(), "abc".to_string()]), + api::longest([]) ); let doc: serde_json::Value = r#"{"b": [1, 2.5, null], "a": "\u00e9"}"#.parse().expect("a JSON document"); - println!("json {}", api_lib::encode(&doc)); - println!("timed text {}", api_lib::timed_text(&doc)); - println!("tagged {}", api_lib::tagged(doc, "t")); + println!("json {}", api::encode(&doc)); + println!("timed text {}", api::timed_text(&doc)); + println!("tagged {}", api::tagged(doc, "t")); match "{\"a\":".parse::() { Ok(doc) => println!("json parse {doc}"), Err(err) => println!("json parse err {err}"), } - let site = api_lib::Site::new(base); + let site = api::Site::new(base); println!("site {} {}", site.home, site.home); // Slices, passed in and returned as clones, and the socket address of each family. - println!("shapes {:?} greetings {:?}", api_lib::Shape::ALL, api_lib::GREETINGS); - let trail = api_lib::Trail::new( - &[api_lib::Point { x: 1, y: 2 }, api_lib::Point { x: 3, y: 4 }], + println!("shapes {:?} greetings {:?}", api::Shape::ALL, api::GREETINGS); + let trail = api::Trail::new( + &[api::Point { x: 1, y: 2 }, api::Point { x: 3, y: 4 }], &["a".to_string(), "b".to_string()], ); println!("trail {:?} {:?}", trail.points(), trail.labels()); @@ -500,10 +484,10 @@ fn main() { trail.drain().collect::>(), trail.labels() ); - let numbered: Vec = api_lib::numbered(vec!["p".to_string(), "q".to_string()]) + let numbered: Vec = api::numbered(vec!["p".to_string(), "q".to_string()]) .map(|c| format!("{} {}", c.name(), c.get())) .collect(); println!("numbered {}", numbered.join(", ")); - let (v4, v6) = api_lib::nexts("127.0.0.1:8080".parse().unwrap(), "[::1%3]:443".parse().unwrap()); + let (v4, v6) = api::nexts("127.0.0.1:8080".parse().unwrap(), "[::1%3]:443".parse().unwrap()); println!("nexts {} {} {} {}", v4.ip(), v4.port(), v6.port(), v6.scope_id()); } diff --git a/ffi/fixtures/api/ffi.toml b/ffi/fixtures/api/ffi.toml index 3fb66cc53..187ab2526 100644 --- a/ffi/fixtures/api/ffi.toml +++ b/ffi/fixtures/api/ffi.toml @@ -1,5 +1,3 @@ -package = "api-fixture" -prefix = "api" # The list's tail is not `Clone`, so its getter has no copy to return, one instance leaves # out a method the others keep, `Seed` is generic, so no callback implements it, `Payload` # and `Kept` consume themselves, which no callback can, `Timed`, `Sample`, `Queued`, @@ -13,7 +11,7 @@ exclude = [ "Kept", "Layer", "Node::next", - "Pair::swap", + "Pair::swap", "Payload", "Queued", "Rooted", @@ -23,20 +21,9 @@ exclude = [ "Slot::value", "field@Retry::delay", "field@Slot::value", - "Stack::depth_past", + "Stack::depth_past", "Stored", "Tick", "Timed", "Tock", ] - -# The field `Retry::limit`, whose name its method takes, and one instance each of `Pair` -# and `Counter::seeded`, whose other instances keep their names. -[rename] -"Counter::seeded" = "counter_from_size" -"field@Retry::limit" = "retry_limit_setting" -"Pair" = "size_tag" - -# Exported into the same library: `grow` names its types. -[crates.dep-fixture] -exclude = ["HashSet"] diff --git a/ffi/fixtures/api/src/lib.rs b/ffi/fixtures/api/src/lib.rs index 49498c93e..5e51c8726 100644 --- a/ffi/fixtures/api/src/lib.rs +++ b/ffi/fixtures/api/src/lib.rs @@ -8,8 +8,8 @@ use std::task::{Poll, Waker}; use std::time::Duration; use bytes::Bytes; -// Another export's type, which this names rather than exports again. -pub use dep::Size; +// Another crate's types, which the export follows this re-export to. +pub use dep::{Area, Size}; /// The largest value a [`Counter`] reaches. pub const LIMIT: u64 = 1000; @@ -878,7 +878,8 @@ pub fn next_packet(packet: Packet) -> Packet { } /// Retry settings: each field is unset until C sets it, and the method of its name -/// resolves it, as Rust's accessors do. Not exhaustive, so an object with getters. +/// resolves it, as Rust's accessors do, and `set_limit` sets it, as Rust's setters do: C +/// names each apart from the field's accessors. Not exhaustive, so an object with getters. #[non_exhaustive] pub struct Retry { pub limit: Option, @@ -897,6 +898,10 @@ impl Retry { pub fn delay(&self) -> u32 { self.delay.unwrap_or(100) } + + pub fn set_limit(&mut self, limit: u32) { + self.limit = Some(limit); + } } /// How many tokens have dropped. @@ -1191,7 +1196,7 @@ impl Pair { } /// The left value as a byte, beside `right`: the one instance that names the result - /// fills the type parameter, with `dep_fixture::Size`, and names this instance. + /// fills the type parameter, with `Size`, and names this instance. pub fn beside(&self, right: T) -> Pair { Pair { left: self.left as u8, @@ -1426,9 +1431,9 @@ pub fn seeds + Default>(from: impl Seed) -> u64 { } /// An area grown by `by` on each side: another export's types, across crates. -pub fn grow(area: &dep::Area, by: u32) -> dep::Area { +pub fn grow(area: &Area, by: u32) -> Area { let size = area.size(); - dep::Area::new(Size { + Area::new(Size { width: size.width + by, height: size.height + by, }) diff --git a/ffi/fixtures/dep/Cargo.toml b/ffi/fixtures/dep/Cargo.toml index 5730d145d..edc47ab36 100644 --- a/ffi/fixtures/dep/Cargo.toml +++ b/ffi/fixtures/dep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dep-fixture" -description = "Another export, whose types the api fixture names across crates" +description = "Another crate, whose types the api fixture re-exports and names" version = "0.0.0" edition = "2024" license = "MIT OR Apache-2.0" @@ -12,8 +12,7 @@ name = "dep" crate-type = ["rlib"] [features] -# Doubles each area's size: the tests enable it on this crate directly, which the export -# must build as the check did. +# Doubles each area's size: the api fixture's own feature enables it. doubled = [] [lints] diff --git a/ffi/fixtures/dep/src/lib.rs b/ffi/fixtures/dep/src/lib.rs index 4e52e5e22..4887122a3 100644 --- a/ffi/fixtures/dep/src/lib.rs +++ b/ffi/fixtures/dep/src/lib.rs @@ -1,13 +1,13 @@ -//! Another export, exported into the api fixture's library, which names its types. +//! Another crate, whose types the api fixture re-exports and names. -/// A size: a record of this export, which another's signatures name. +/// A size: a record of this crate, which the api fixture's signatures name. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Size { pub width: u32, pub height: u32, } -/// An area: an object of this export, which another's signatures take and return. +/// An area: an object of this crate, which the api fixture's signatures take and return. #[derive(Clone, Debug)] pub struct Area { size: Size, @@ -33,7 +33,3 @@ impl Area { u64::from(self.size.width) * u64::from(self.size.height) } } - -/// Another crate's item that is no export, which each export re-exporting it refuses or -/// excludes on its own. -pub use std::collections::HashSet; diff --git a/ffi/fixtures/reject/Cargo.toml b/ffi/fixtures/reject/Cargo.toml index 4a0f71933..9a922c57d 100644 --- a/ffi/fixtures/reject/Cargo.toml +++ b/ffi/fixtures/reject/Cargo.toml @@ -6,6 +6,10 @@ edition = "2024" license = "MIT OR Apache-2.0" publish = false +# The export's prefix. +[lib] +name = "reject" + [dependencies] dep-fixture = { path = "../dep" } diff --git a/ffi/fixtures/reject/expected.txt b/ffi/fixtures/reject/expected.txt index fb9844303..1dfe156be 100644 --- a/ffi/fixtures/reject/expected.txt +++ b/ffi/fixtures/reject/expected.txt @@ -1,8 +1,3 @@ -fixtures/dep/src/lib.rs:39:9: rust2-ffi: cannot export use `HashSet`: re-export of another crate's item `std`; exclude it in the config's [crates.dep-fixture] to leave it out -fixtures/dep/src/lib.rs:5:1: rust2-ffi: struct `dep_fixture::Size` and struct `reject_fixture::Size` are both `reject_size_free` in C - fixtures/reject/src/lib.rs:139:1: note: struct `reject_fixture::Size` is here -fixtures/dep/src/lib.rs:5:1: rust2-ffi: struct `dep_fixture::Size` and struct `reject_fixture::Size` are both `reject_size` in C - fixtures/reject/src/lib.rs:139:1: note: struct `reject_fixture::Size` is here fixtures/reject/src/lib.rs:113:2: rust2-ffi: cannot export method `Text::replace`: 'static borrow `&'b str`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:113:17: note: `'b` is `'static`, as a handle outlives every call fixtures/reject/src/lib.rs:118:2: rust2-ffi: cannot export method `Text::later`: returned object that borrows `impl std::future::Future>`; exclude it in the config to leave it out @@ -10,82 +5,79 @@ fixtures/reject/src/lib.rs:125:1: rust2-ffi: cannot export fn `either`: referenc fixtures/reject/src/lib.rs:130:1: rust2-ffi: cannot export struct `Lease`: object no export returns; exclude it in the config to leave it out fixtures/reject/src/lib.rs:139:1: rust2-ffi: tuple `((u8, u16), u32)` and tuple `((u8, u16, u32),)` are both `reject_tuple_tuple_u8_u16_u32` in C fixtures/reject/src/lib.rs:139:1: note: tuple `((u8, u16, u32),)` is here +fixtures/reject/src/lib.rs:144:1: rust2-ffi: cannot export fn `measure`: foreign type `dep::Size`; foreign type `dep::Area`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:149:1: rust2-ffi: cannot export instance `Cell>`: type argument `std::vec::Vec`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:14:1: rust2-ffi: cannot export fn `map`: foreign type `std::collections::HashMap`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:159:2: rust2-ffi: cannot export method `Cell::nest`: instance holding an instance of a type inside it `reject_fixture::Cell>`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:178:2: rust2-ffi: cannot export method `Pong>::ping`: instance holding an instance of a type inside it `reject_fixture::Ping>>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:159:2: rust2-ffi: cannot export method `Cell::nest`: instance holding an instance of a type inside it `reject::Cell>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:178:2: rust2-ffi: cannot export method `Pong>::ping`: instance holding an instance of a type inside it `reject::Ping>>`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:189:1: rust2-ffi: cannot export instance `Grow<(u32,)>`: type argument `(u32,)`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:192:2: rust2-ffi: cannot export method `Grow::grow`: refused type `reject_fixture::Grow<(u32,)>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:192:2: rust2-ffi: cannot export method `Grow::grow`: refused type `reject::Grow<(u32,)>`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:19:1: rust2-ffi: cannot export fn `generic`: generic parameter `std::marker::Copy`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:203:1: rust2-ffi: cannot export instance `Shell<(u32,)>`: type argument `(u32,)`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:208:1: rust2-ffi: cannot export struct `Core`: generic type; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:212:1: rust2-ffi: cannot export fn `shell`: refused type `reject_fixture::Shell<(u32,)>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:212:1: rust2-ffi: cannot export fn `shell`: refused type `reject::Shell<(u32,)>`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:221:1: rust2-ffi: cannot export struct `Probe`: generic type; exclude it in the config to leave it out fixtures/reject/src/lib.rs:223:1: rust2-ffi: cannot export fn `probe`: const generic; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:228:1: rust2-ffi: cannot export fn `excluded_cell`: excluded type `reject_fixture::Cell`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:233:1: rust2-ffi: cannot export fn `listed`: refused type `reject_fixture::Cell>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:228:1: rust2-ffi: cannot export fn `excluded_cell`: excluded type `reject::Cell`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:233:1: rust2-ffi: cannot export fn `listed`: refused type `reject::Cell>`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:238:1: rust2-ffi: cannot export struct `Fixed`: generic type; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:241:1: rust2-ffi: cannot export fn `fixed`: const generic `reject_fixture::Fixed`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:246:9: rust2-ffi: cannot export use `HashSet`: re-export of another crate's item `std`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:241:1: rust2-ffi: cannot export fn `fixed`: const generic `reject::Fixed`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:246:9: rust2-ffi: cannot export use `HashSet`: re-export of the standard library's item `std`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:249:1: rust2-ffi: cannot export fn `lend`: Waker outside a parameter taken by value; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:264:2: rust2-ffi: fn `reject_fixture::Limit::max` and field `reject_fixture::Limit::max` are both `reject_limit_max` in C - fixtures/reject/src/lib.rs:256:2: note: field `reject_fixture::Limit::max` is here -fixtures/reject/src/lib.rs:269:2: rust2-ffi: fn `reject_fixture::Limit::set_max` and setter of field `reject_fixture::Limit::max` are both `reject_limit_set_max` in C - fixtures/reject/src/lib.rs:256:2: note: setter of field `reject_fixture::Limit::max` is here +fixtures/reject/src/lib.rs:257:2: rust2-ffi: cannot export field `Tag::label`: 'static borrow `&'static str`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:267:1: rust2-ffi: cannot export fn `spawn`: closure kept past the call `impl FnOnce() + Send + 'static`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:26:1: rust2-ffi: cannot export fn `borrowing`: borrowing future from a function that is not async; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:278:2: rust2-ffi: cannot export field `Tag::label`: 'static borrow `&'static str`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:288:1: rust2-ffi: cannot export fn `spawn`: closure kept past the call `impl FnOnce() + Send + 'static`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:293:1: rust2-ffi: cannot export fn `later`: closure kept past the call `impl FnOnce()`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:298:1: rust2-ffi: cannot export fn `twice`: closure bound `std::clone::Clone`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:304:1: rust2-ffi: cannot export fn `both`: closure type taken twice `F`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:310:1: rust2-ffi: cannot export fn `read`: lent object that borrows `&Text<'_>`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:315:1: rust2-ffi: cannot export fn `count_words`: Vec of references `&str`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:272:1: rust2-ffi: cannot export fn `later`: closure kept past the call `impl FnOnce()`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:277:1: rust2-ffi: cannot export fn `twice`: closure bound `std::clone::Clone`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:283:1: rust2-ffi: cannot export fn `both`: closure type taken twice `F`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:289:1: rust2-ffi: cannot export fn `read`: lent object that borrows `&Text<'_>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:294:1: rust2-ffi: cannot export fn `count_words`: Vec of references `&str`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:299:1: rust2-ffi: cannot export fn `count_some`: Vec of references `std::option::Option<&str>`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:304:1: rust2-ffi: cannot export fn `count_slice`: slice of references `&str`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:309:1: rust2-ffi: cannot export fn `peek_all`: reference to a handle inside a value `[Opaque]`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:314:1: rust2-ffi: cannot export fn `none_opaque`: borrowed return that is not Clone `[Opaque]`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:31:1: rust2-ffi: cannot export fn `nested`: nested Option `std::option::Option>>`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:320:1: rust2-ffi: cannot export fn `count_some`: Vec of references `std::option::Option<&str>`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:325:1: rust2-ffi: cannot export fn `count_slice`: slice of references `&str`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:330:1: rust2-ffi: cannot export fn `peek_all`: reference to a handle inside a value `[Opaque]`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:335:1: rust2-ffi: cannot export fn `none_opaque`: borrowed return that is not Clone `[Opaque]`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:347:1: rust2-ffi: cannot export fn `stash`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:362:1: rust2-ffi: cannot export fn `hold`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:367:1: rust2-ffi: cannot export fn `bound`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:372:1: rust2-ffi: cannot export fn `kept`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:386:1: rust2-ffi: cannot export fn `holder`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:400:1: rust2-ffi: cannot export fn `lender`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:326:1: rust2-ffi: cannot export fn `stash`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:341:1: rust2-ffi: cannot export fn `hold`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:346:1: rust2-ffi: cannot export fn `bound`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:351:1: rust2-ffi: cannot export fn `kept`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:365:1: rust2-ffi: cannot export fn `holder`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:379:1: rust2-ffi: cannot export fn `lender`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:394:1: rust2-ffi: cannot export fn `outlived`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:409:1: rust2-ffi: cannot export fn `paired`: generic parameter `std::convert::Into`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:40:1: rust2-ffi: cannot export fn `peek`: reference to a handle inside a value `std::boxed::Box`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:415:1: rust2-ffi: cannot export fn `outlived`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:430:1: rust2-ffi: cannot export fn `paired`: generic parameter `std::convert::Into`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:453:1: rust2-ffi: cannot export fn `pinner`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:432:1: rust2-ffi: cannot export fn `pinner`: generic parameter `std::convert::Into`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:447:1: rust2-ffi: cannot export fn `grows`: growing conversion `Grows`; exclude it in the config to leave it out + fixtures/reject/src/lib.rs:439:1: note: applies past the recursion limit fixtures/reject/src/lib.rs:45:1: rust2-ffi: cannot export static `COUNT`: static; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:468:1: rust2-ffi: cannot export fn `grows`: growing conversion `Grows`; exclude it in the config to leave it out - fixtures/reject/src/lib.rs:460:1: note: applies past the recursion limit -fixtures/reject/src/lib.rs:498:1: rust2-ffi: cannot export fn `call`: ambiguous conversion `std::vec::Vec`; exclude it in the config to leave it out - fixtures/reject/src/lib.rs:475:1: note: `std::string::String` converts too, which C passes alike +fixtures/reject/src/lib.rs:477:1: rust2-ffi: cannot export fn `call`: ambiguous conversion `std::vec::Vec`; exclude it in the config to leave it out + fixtures/reject/src/lib.rs:454:1: note: `std::string::String` converts too, which C passes alike +fixtures/reject/src/lib.rs:482:1: rust2-ffi: cannot export fn `alphabet`: Range `char`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:487:1: rust2-ffi: cannot export fn `naturals`: foreign type `std::ops::RangeFrom`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:492:1: rust2-ffi: cannot export fn `opaques`: borrowed return that is not Clone `Opaque`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:497:1: rust2-ffi: cannot export fn `tried`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:49:1: rust2-ffi: cannot export macro `noop`: macro; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:503:1: rust2-ffi: cannot export fn `alphabet`: Range `char`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:508:1: rust2-ffi: cannot export fn `naturals`: foreign type `std::ops::RangeFrom`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:513:1: rust2-ffi: cannot export fn `opaques`: borrowed return that is not Clone `Opaque`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:518:1: rust2-ffi: cannot export fn `tried`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:523:1: rust2-ffi: cannot export fn `eventually`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:528:1: rust2-ffi: cannot export trait `Source`: callback signature `items`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:533:1: rust2-ffi: cannot export fn `ones`: foreign type `std::iter::Repeat`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:538:1: rust2-ffi: cannot export fn `words`: foreign type `std::str::Split`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:555:1: rust2-ffi: cannot export fn `launched`: foreign type `std::iter::Chain`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:572:1: rust2-ffi: cannot export fn `launches`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:502:1: rust2-ffi: cannot export fn `eventually`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:507:1: rust2-ffi: cannot export trait `Source`: callback signature `items`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:512:1: rust2-ffi: cannot export fn `ones`: foreign type `std::iter::Repeat`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:517:1: rust2-ffi: cannot export fn `words`: foreign type `std::str::Split`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:534:1: rust2-ffi: cannot export fn `launched`: foreign type `std::iter::Chain`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:551:1: rust2-ffi: cannot export fn `launches`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:574:1: rust2-ffi: cannot export fn `pumped`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:579:1: rust2-ffi: cannot export struct `Token`: object no export returns; exclude it in the config to leave it out fixtures/reject/src/lib.rs:57:1: rust2-ffi: cannot export fn `excluded`: excluded type `Excluded`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:595:1: rust2-ffi: cannot export fn `pumped`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:600:1: rust2-ffi: cannot export struct `Token`: object no export returns; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:609:1: rust2-ffi: cannot export fn `skipped`: foreign type `std::iter::Skip`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:614:1: rust2-ffi: cannot export fn `merged`: foreign type `std::iter::Copied`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:621:1: rust2-ffi: cannot export fn `values`: foreign type `std::collections::btree_map::IntoValues`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:626:1: rust2-ffi: cannot export fn `zeros`: foreign type `std::iter::RepeatN`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:631:1: rust2-ffi: cannot export fn `successes`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:651:1: rust2-ffi: cannot export fn `allocated`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:658:1: rust2-ffi: cannot export fn `flattened`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:682:1: rust2-ffi: cannot export fn `lent`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:68:2: rust2-ffi: fn `reject_fixture::audio::encoder_finish` and fn `reject_fixture::audio_encoder::finish` are both `reject_audio_encoder_finish` in C - fixtures/reject/src/lib.rs:63:2: note: fn `reject_fixture::audio_encoder::finish` is here +fixtures/reject/src/lib.rs:588:1: rust2-ffi: cannot export fn `skipped`: foreign type `std::iter::Skip`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:593:1: rust2-ffi: cannot export fn `merged`: foreign type `std::iter::Copied`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:600:1: rust2-ffi: cannot export fn `values`: foreign type `std::collections::btree_map::IntoValues`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:605:1: rust2-ffi: cannot export fn `zeros`: foreign type `std::iter::RepeatN`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:610:1: rust2-ffi: cannot export fn `successes`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:630:1: rust2-ffi: cannot export fn `allocated`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:637:1: rust2-ffi: cannot export fn `flattened`: foreign type `std::iter::Flatten`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:661:1: rust2-ffi: cannot export fn `lent`: foreign type `std::vec::IntoIter`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:679:1: rust2-ffi: cannot export fn `halves`: foreign type `std::str::Split`; exclude it in the config to leave it out +fixtures/reject/src/lib.rs:68:2: rust2-ffi: fn `reject::audio::encoder_finish` and fn `reject::audio_encoder::finish` are both `reject_audio_encoder_finish` in C + fixtures/reject/src/lib.rs:63:2: note: fn `reject::audio_encoder::finish` is here fixtures/reject/src/lib.rs:6:1: rust2-ffi: cannot export struct `Wrap`: generic type; exclude it in the config to leave it out -fixtures/reject/src/lib.rs:700:1: rust2-ffi: cannot export fn `halves`: foreign type `std::str::Split`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:72:1: rust2-ffi: cannot export fn `letters`: Range `char`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:81:2: rust2-ffi: cannot export method `Text::new`: 'static borrow `&'a str`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:79:6: note: `'a` is `'static`, as a handle outlives every call @@ -94,5 +86,3 @@ fixtures/reject/src/lib.rs:92:1: rust2-ffi: cannot export fn `retext`: 'static b fixtures/reject/src/lib.rs:92:15: note: `'a` is `'static`, as a handle outlives every call fixtures/reject/src/lib.rs:97:1: rust2-ffi: cannot export fn `first`: borrowed return that is not Clone `Opaque`; exclude it in the config to leave it out fixtures/reject/src/lib.rs:9:1: rust2-ffi: cannot export fn `pair`: tuple of references `(&str, u8)`; exclude it in the config to leave it out -no span: rust2-ffi: rename `Cell` in the config names nothing exported -no span: rust2-ffi: rename `field@Limit::min` in the config names nothing exported diff --git a/ffi/fixtures/reject/ffi.toml b/ffi/fixtures/reject/ffi.toml index 058e1f498..978aba4f6 100644 --- a/ffi/fixtures/reject/ffi.toml +++ b/ffi/fixtures/reject/ffi.toml @@ -1,12 +1,2 @@ -package = "reject-fixture" -prefix = "reject" # An exclusion leaves an item or an instance out, so nothing refuses it. exclude = ["Called", "Caller", "Cell", "Excluded", "Grows", "Held", "Holder", "Lender", "Outlived", "Paired", "Pinned", "Pinner"] - -# A field that is none: `min` names nothing of `Limit`. An instance the config leaves out. -[rename] -"Cell" = "short_cell" -"field@Limit::min" = "limit_min" - -# Another export, whose `Size` claims the same C name as this one's. -[crates.dep-fixture] diff --git a/ffi/fixtures/reject/src/lib.rs b/ffi/fixtures/reject/src/lib.rs index c448b2ad1..5b4c72a44 100644 --- a/ffi/fixtures/reject/src/lib.rs +++ b/ffi/fixtures/reject/src/lib.rs @@ -135,12 +135,12 @@ impl Lease { } } -/// `reject_size` in C, as another export's `Size` is: one prefix serves every export. +/// A record, a type of the facade's own. pub struct Size { pub bytes: u64, } -/// Another export's type, which this one names. +/// Another crate's types, which the facade does not re-export. pub fn measure(size: dep::Size) -> dep::Area { dep::Area::new(size) } @@ -242,7 +242,7 @@ pub fn fixed() -> Fixed<2> { Fixed(2) } -/// Another crate's item that is no export, so nothing names it. +/// The standard library's item, whose types the export maps by role instead. pub use std::collections::HashSet; /// A borrowed waker: one crosses only into Rust, which keeps it. @@ -250,27 +250,6 @@ pub fn lend(waker: &std::task::Waker) { waker.wake_by_ref(); } -/// A field and a method of one name, which claim one C name until `field@` renames one. -#[non_exhaustive] -pub struct Limit { - pub max: u32, -} - -impl Limit { - pub fn new(max: u32) -> Self { - Self { max } - } - - pub fn max(&self) -> u32 { - self.max.max(1) - } - - /// The name of `max`'s setter too. - pub fn set_max(&mut self, max: u32) { - self.max = max; - } -} - /// A field holding a `'static` borrow, which a setter cannot take from C's borrow for /// the call. #[non_exhaustive] diff --git a/ffi/justfile b/ffi/justfile index bd5c7936b..915d3694c 100644 --- a/ffi/justfile +++ b/ffi/justfile @@ -40,7 +40,7 @@ fix: run *args: cargo run --locked --quiet -- {{ args }} -# Census each package moq/ffi.toml exports into target/census/.md, from the pinned moq revision or a checkout. +# Census the stand-in facade moq/ffi.toml exports into target/census/moq-ffi.md, from the pinned moq revision or a checkout. census checkout="": (standin checkout) cargo run --locked --quiet -- --config moq/ffi.toml --manifest-path target/moq-ffi/Cargo.toml --census target/census @@ -221,7 +221,7 @@ moq-export checkout="": (standin checkout) cargo build --quiet --release --manifest-path "$out/Cargo.toml" --features kotlin,dart 2>&1 | tee target/moq-build.log sed -n 's/^note: native-static-libs: //p' target/moq-build.log > target/moq-native -# Write target/moq-ffi, an empty library on every crate moq/ffi.toml exports, from the pinned moq revision or a checkout: the check builds with all of them, as moq-ffi's remainder will. +# Write target/moq-ffi, a facade re-exporting what moq-ffi ships from moq's crates, from the pinned moq revision or a checkout: the export follows its re-exports, as it will moq-ffi's remainder's. [private] standin checkout="": #!/usr/bin/env bash @@ -237,22 +237,41 @@ standin checkout="": src=$(cygpath -m "$src") fi dir=target/moq-ffi - # moq-ffi's dependencies among the exports, moq-tokio with its default features as - # moq-ffi has them, and moq-pattern and kio, which moq-ffi reaches through moq-net. - exports=(hang kio moq-mux moq-net moq-pattern moq-tokio) + # moq-ffi's dependencies, moq-tokio with its default features as moq-ffi has them. + deps=(hang kio moq-mux moq-net moq-pattern moq-tokio) mkdir -p "$dir/src" { printf '# Generated by just ffi standin, from %s.\n' "$src" - printf '[package]\nname = "moq-ffi"\nversion = "0.0.0"\nedition = "2024"\npublish = false\n\n[dependencies]\n' - for name in "${exports[@]}"; do + printf '[package]\nname = "moq-ffi"\nversion = "0.0.0"\nedition = "2024"\npublish = false\n\n' + # The prefix of every C name. + printf '[lib]\nname = "moq"\n\n[dependencies]\n' + for name in "${deps[@]}"; do printf '%s = { path = "%s/rs/%s" }\n' "$name" "$src" "$name" done printf '\n[workspace]\n' } > "$dir/Cargo.toml" - # rustc loads a dependency only once the crate names it. - for name in "${exports[@]}"; do - printf 'use %s as _;\n' "${name//-/_}" - done > "$dir/src/lib.rs" + # What moq-ffi ships, re-exported: moq-net and moq-pattern at the root, beside the + # waiter a driver's poll registers and the clock it polls at, and each other crate in a + # module of its own, since several name an item alike, such as `Error`. + cat > "$dir/src/lib.rs" <<'EOF' + pub use kio::{Park, Waiter, WaiterList, clock, coop, log}; + pub use moq_net::*; + pub use moq_pattern::*; + + pub mod hang { + pub use ::hang::*; + } + + pub mod mux { + pub use ::moq_mux::*; + } + + pub mod tokio { + pub use ::moq_tokio::*; + // Apart from `ConnectError`, which languages that flatten modules name alike. + pub use ::moq_tokio::connect::Error as ConnectTargetError; + } + EOF # moq's lockfile pins what moq's own builds use. cp "$src/Cargo.lock" "$dir/Cargo.lock" diff --git a/ffi/moq/bench/export.c b/ffi/moq/bench/export.c index 40d7cd29f..e0f530971 100644 --- a/ffi/moq/bench/export.c +++ b/ffi/moq/bench/export.c @@ -25,20 +25,20 @@ static moq_buf str(const char *s) { } int main(void) { - moq_broadcast_info *binfo = moq_broadcast_info_new(); - moq_broadcast_producer *broadcast = moq_broadcast_info_produce(binfo); - moq_broadcast_info_free(binfo); - moq_timescale *micro = moq_timescale_micro(); + moq_broadcast_Info *binfo = moq_broadcast_Info_new(); + moq_broadcast_Producer *broadcast = moq_broadcast_Info_produce(binfo); + moq_broadcast_Info_free(binfo); + moq_Timescale *micro = moq_Timescale_MICRO(); /* Builders take the handle's value, leaving the handle to free. */ - moq_track_info *defaults = moq_track_info_default(); - moq_track_info *info = moq_track_info_with_timescale(defaults, micro); - moq_track_info_free(defaults); - moq_track_producer *track = NULL; - moq_error *err = NULL; - if (!moq_broadcast_producer_create_track(broadcast, str("bench"), info, &track, &err)) { + moq_track_Info *defaults = moq_track_Info_default(); + moq_track_Info *info = moq_track_Info_with_timescale(defaults, micro); + moq_track_Info_free(defaults); + moq_track_Producer *track = NULL; + moq_Error *err = NULL; + if (!moq_broadcast_Producer_create_track(broadcast, str("bench"), info, &track, &err)) { return 1; } - moq_track_info_free(info); + moq_track_Info_free(info); uint8_t payload[100] = {0}; moq_buf frame = {.ptr = payload, .len = sizeof payload}; @@ -47,37 +47,37 @@ int main(void) { /* Each frame stamped through a fresh timestamp handle, as a caller holding a u64 must. */ double start = now(); for (int g = 0; g < GROUPS; g++) { - moq_group_producer *group = NULL; - moq_track_producer_append_group(track, &group, &err); + moq_group_Producer *group = NULL; + moq_track_Producer_append_group(track, &group, &err); for (int f = 0; f < FRAMES; f++) { - moq_timestamp *at = NULL; - moq_time_overflow *overflow = NULL; - moq_timestamp_from_micros(micros++, &at, &overflow); - if (!moq_group_producer_write_frame(group, at, frame, &err)) { + moq_Timestamp *at = NULL; + moq_TimeOverflow *overflow = NULL; + moq_Timestamp_from_micros(micros++, &at, &overflow); + if (!moq_group_Producer_write_frame(group, at, frame, &err)) { return 2; } - moq_timestamp_free(at); + moq_Timestamp_free(at); } - moq_group_producer_finish(group, &err); - moq_group_producer_free(group); + moq_group_Producer_finish(group, &err); + moq_group_Producer_free(group); } double stamped = (now() - start) / (GROUPS * FRAMES); /* The same, reusing one timestamp handle: what the handle costs. */ - moq_timestamp *at = NULL; - moq_time_overflow *overflow = NULL; - moq_timestamp_from_micros(micros, &at, &overflow); + moq_Timestamp *at = NULL; + moq_TimeOverflow *overflow = NULL; + moq_Timestamp_from_micros(micros, &at, &overflow); start = now(); for (int g = 0; g < GROUPS; g++) { - moq_group_producer *group = NULL; - moq_track_producer_append_group(track, &group, &err); + moq_group_Producer *group = NULL; + moq_track_Producer_append_group(track, &group, &err); for (int f = 0; f < FRAMES; f++) { - if (!moq_group_producer_write_frame(group, at, frame, &err)) { + if (!moq_group_Producer_write_frame(group, at, frame, &err)) { return 3; } } - moq_group_producer_finish(group, &err); - moq_group_producer_free(group); + moq_group_Producer_finish(group, &err); + moq_group_Producer_free(group); } double reused = (now() - start) / (GROUPS * FRAMES); @@ -85,70 +85,70 @@ int main(void) { start = now(); int closed = 0; for (int i = 0; i < GROUPS * FRAMES; i++) { - closed += moq_track_producer_is_closed(track); + closed += moq_track_Producer_is_closed(track); } double call = (now() - start) / (GROUPS * FRAMES); /* Reads: each frame written, then read through a new future polled once, ready. */ moq_waker waker = {0}; - moq_track_consumer *consumer = moq_track_producer_consume(track); - moq_track_consumer_subscribe_future *subscribing = moq_track_consumer_subscribe(consumer, NULL); - moq_track_subscriber *subscriber = NULL; - if (moq_track_consumer_subscribe_poll(subscribing, waker, &subscriber, &err) != MOQ_POLL_READY) { + moq_track_Consumer *consumer = moq_track_Producer_consume(track); + moq_track_Consumer_subscribe_future *subscribing = moq_track_Consumer_subscribe(consumer, NULL); + moq_track_Subscriber *subscriber = NULL; + if (moq_track_Consumer_subscribe_poll(subscribing, waker, &subscriber, &err) != MOQ_POLL_READY) { return 4; } - moq_track_consumer_subscribe_future_free(subscribing); - moq_frame_frame *last = NULL; + moq_track_Consumer_subscribe_future_free(subscribing); + moq_frame_Frame *last = NULL; start = now(); for (int g = 0; g < GROUPS; g++) { - moq_group_producer *group = NULL; - moq_track_producer_append_group(track, &group, &err); - moq_track_subscriber_recv_group_future *receiving = moq_track_subscriber_recv_group(subscriber); - moq_group_consumer *reader = NULL; - if (moq_track_subscriber_recv_group_poll(receiving, waker, &reader, &err) != MOQ_POLL_READY || reader == NULL) { + moq_group_Producer *group = NULL; + moq_track_Producer_append_group(track, &group, &err); + moq_track_Subscriber_recv_group_future *receiving = moq_track_Subscriber_recv_group(subscriber); + moq_group_Consumer *reader = NULL; + if (moq_track_Subscriber_recv_group_poll(receiving, waker, &reader, &err) != MOQ_POLL_READY || reader == NULL) { return 5; } - moq_track_subscriber_recv_group_future_free(receiving); + moq_track_Subscriber_recv_group_future_free(receiving); for (int f = 0; f < FRAMES; f++) { - if (!moq_group_producer_write_frame(group, at, frame, &err)) { + if (!moq_group_Producer_write_frame(group, at, frame, &err)) { return 6; } - moq_group_consumer_read_frame_future *reading = moq_group_consumer_read_frame(reader); - moq_frame_frame *read = NULL; - if (moq_group_consumer_read_frame_poll(reading, waker, &read, &err) != MOQ_POLL_READY || read == NULL) { + moq_group_Consumer_read_frame_future *reading = moq_group_Consumer_read_frame(reader); + moq_frame_Frame *read = NULL; + if (moq_group_Consumer_read_frame_poll(reading, waker, &read, &err) != MOQ_POLL_READY || read == NULL) { return 7; } - moq_group_consumer_read_frame_future_free(reading); + moq_group_Consumer_read_frame_future_free(reading); if (g == GROUPS - 1 && f == FRAMES - 1) { last = read; } else { - moq_frame_frame_free(read); + moq_frame_Frame_free(read); } } - moq_group_producer_finish(group, &err); - moq_group_producer_free(group); - moq_group_consumer_free(reader); + moq_group_Producer_finish(group, &err); + moq_group_Producer_free(group); + moq_group_Consumer_free(reader); } double read = (now() - start) / (GROUPS * FRAMES); /* Payload reads: the last frame's 100 bytes, owned without a copy, then freed. */ start = now(); for (int i = 0; i < GROUPS * FRAMES; i++) { - moq_buf bytes = moq_frame_frame_payload(last); + moq_buf bytes = moq_frame_Frame_payload_get(last); closed += bytes.ptr[0]; moq_buf_free(bytes); } double lent = (now() - start) / (GROUPS * FRAMES); - moq_frame_frame_free(last); - moq_timestamp_free(at); - moq_track_subscriber_free(subscriber); - moq_track_consumer_free(consumer); + moq_frame_Frame_free(last); + moq_Timestamp_free(at); + moq_track_Subscriber_free(subscriber); + moq_track_Consumer_free(consumer); printf("export: %.0f ns per stamped 100 B frame, %.0f reusing the timestamp, %.1f ns per handle call, %.0f ns per frame " "written and read, %.0f ns per payload read%s\n", stamped, reused, call, read, lent, closed ? "!" : ""); - moq_track_producer_free(track); - moq_timescale_free(micro); - moq_broadcast_producer_free(broadcast); + moq_track_Producer_free(track); + moq_Timescale_free(micro); + moq_broadcast_Producer_free(broadcast); return 0; } diff --git a/ffi/moq/bench/export.cpp b/ffi/moq/bench/export.cpp index d3b6177a9..da1fbb84f 100644 --- a/ffi/moq/bench/export.cpp +++ b/ffi/moq/bench/export.cpp @@ -22,9 +22,9 @@ double per_frame(std::chrono::steady_clock::time_point start) { } // namespace int main() { - moq::broadcast_producer broadcast = moq::broadcast_info::new_().produce(); - moq::timescale micro = moq::timescale::micro(); - auto track = broadcast.create_track("bench", moq::track_info::default_().with_timescale(µ)); + moq::broadcast_Producer broadcast = moq::broadcast_Info::new_().produce(); + moq::Timescale micro = moq::Timescale::MICRO(); + auto track = broadcast.create_track("bench", moq::track_Info::default_().with_timescale(µ)); if (!track) { return 1; } @@ -36,9 +36,9 @@ int main() { // Each frame stamped through a fresh timestamp handle, as a caller holding a u64 must. auto start = std::chrono::steady_clock::now(); for (int g = 0; g < groups; g++) { - moq::group_producer group = std::move(*track->append_group()); + moq::group_Producer group = std::move(*track->append_group()); for (int f = 0; f < frames; f++) { - moq::timestamp at = std::move(*moq::timestamp::from_micros(micros++)); + moq::Timestamp at = std::move(*moq::Timestamp::from_micros(micros++)); if (!group.write_frame(&at, frame)) { return 2; } @@ -48,10 +48,10 @@ int main() { double stamped = per_frame(start); // The same, reusing one timestamp handle: what the handle costs. - moq::timestamp at = std::move(*moq::timestamp::from_micros(micros)); + moq::Timestamp at = std::move(*moq::Timestamp::from_micros(micros)); start = std::chrono::steady_clock::now(); for (int g = 0; g < groups; g++) { - moq::group_producer group = std::move(*track->append_group()); + moq::group_Producer group = std::move(*track->append_group()); for (int f = 0; f < frames; f++) { if (!group.write_frame(&at, frame)) { return 3; @@ -71,21 +71,21 @@ int main() { // Reads: each frame written, then read through a new future polled once, ready. moq::waker waker([]() {}); - moq::track_consumer consumer = track->consume(); + moq::track_Consumer consumer = track->consume(); auto subscribed = consumer.subscribe(std::nullopt).poll(waker); if (!subscribed || !*subscribed) { return 4; } - moq::track_subscriber subscriber = std::move(**subscribed); - std::optional last; + moq::track_Subscriber subscriber = std::move(**subscribed); + std::optional last; start = std::chrono::steady_clock::now(); for (int g = 0; g < groups; g++) { - moq::group_producer group = std::move(*track->append_group()); + moq::group_Producer group = std::move(*track->append_group()); auto received = subscriber.recv_group().poll(waker); if (!received || !*received || !received->value()) { return 5; } - moq::group_consumer reader = std::move(*received->value()); + moq::group_Consumer reader = std::move(*received->value()); for (int f = 0; f < frames; f++) { if (!group.write_frame(&at, frame)) { return 6; @@ -105,7 +105,7 @@ int main() { // Payload reads: the last frame's 100 bytes, owned without a copy, then freed. start = std::chrono::steady_clock::now(); for (int i = 0; i < groups * frames; i++) { - closed += last->payload().data()[0]; + closed += last->payload_get().data()[0]; } double lent = per_frame(start); diff --git a/ffi/moq/bench/export.dart b/ffi/moq/bench/export.dart index bcc1db038..1e664c471 100644 --- a/ffi/moq/bench/export.dart +++ b/ffi/moq/bench/export.dart @@ -14,7 +14,7 @@ double perFrame(Stopwatch watch) => watch.elapsedTicks * 1e9 / watch.frequency / Future round() async { final broadcast = moq.BroadcastInfo().produce(); - final micro = moq.Timescale.micro; + final micro = moq.Timescale.MICRO; final track = broadcast.createTrack('bench', moq.TrackInfo.default_().withTimescale(micro)); micro.close(); @@ -112,7 +112,7 @@ Future round() async { double payload(moq.FrameFrame frame) { final watch = Stopwatch()..start(); for (var i = 0; i < groups * frames; i++) { - closed += frame.payload[0]; + closed += frame.payloadGet[0]; } return perFrame(watch); } diff --git a/ffi/moq/bench/export.go b/ffi/moq/bench/export.go index 411f23483..58c104013 100644 --- a/ffi/moq/bench/export.go +++ b/ffi/moq/bench/export.go @@ -125,14 +125,14 @@ func round() string { lent := func(frame *moq.FrameFrame) float64 { start := time.Now() for range groups * frames { - closed += int(must(frame.Payload()).At(0)) + closed += int(must(frame.PayloadGet()).At(0)) } return perFrame(start) } copied := func(frame *moq.FrameFrame) float64 { start := time.Now() for range groups * frames { - closed += int(must(frame.Payload()).Copy()[0]) + closed += int(must(frame.PayloadGet()).Copy()[0]) } return perFrame(start) } diff --git a/ffi/moq/bench/export.kt b/ffi/moq/bench/export.kt index e4241b116..057d3d44a 100644 --- a/ffi/moq/bench/export.kt +++ b/ffi/moq/bench/export.kt @@ -87,7 +87,7 @@ fun round(): String { start = System.nanoTime() last!!.use { read -> repeat(GROUPS * FRAMES) { - closed += read.payload[0] + closed += read.payloadGet[0] } } val payload = perFrame(start) @@ -97,7 +97,7 @@ fun round(): String { start = System.nanoTime() large!!.use { read -> repeat(GROUPS * FRAMES) { - closed += read.payload.toByteArray()[0] + closed += read.payloadGet.toByteArray()[0] } } val lentPayload = perFrame(start) diff --git a/ffi/moq/bench/export.py b/ffi/moq/bench/export.py index 9c3d72987..1281d4e6b 100644 --- a/ffi/moq/bench/export.py +++ b/ffi/moq/bench/export.py @@ -114,7 +114,7 @@ async def looped(): # Payload reads: the last frame's 100 bytes, lent without a copy, then released. start = time.perf_counter_ns() for _ in range(GROUPS * FRAMES): - with last.payload as payload: + with last.payload_get as payload: closed += payload[0] lent = per_frame(start) diff --git a/ffi/moq/bench/export.swift b/ffi/moq/bench/export.swift index af09f839f..8f19c386b 100644 --- a/ffi/moq/bench/export.swift +++ b/ffi/moq/bench/export.swift @@ -92,7 +92,7 @@ func round() throws -> String { // Payload reads: the last frame's 100 bytes, lent without a copy, then freed. start = DispatchTime.now().uptimeNanoseconds for _ in 0..`. -"field@Backoff::initial" = "backoff_initial_setting" -"field@Backoff::max" = "backoff_max_setting" -"field@Backoff::timeout" = "backoff_timeout_setting" - -[crates.hang] -exclude = [ - "Catalog::contributions", - "Catalog::from_reader", - "Catalog::subscribe", - "catalog::Audio::enabled", - "catalog::Audio::renditions", - "catalog::Binary::tracks", - "catalog::BinaryConfig::extra", - "catalog::H265", - "catalog::Json::tracks", - "catalog::JsonConfig::extra", - "catalog::Reference", - "catalog::Text::renditions", - "catalog::UnknownContainer", - "catalog::UnknownProjection", - "catalog::Video::enabled", - "catalog::Video::renditions", - "container::Frame::encode", - "echo::Snapshot", - "health::Health", - "health::Limits", - "health::PublisherThresholds::skipped", - "health::Report", - "health::Sample", - "health::TransportThresholds::headroom", - "health::TransportThresholds::loss", - "health::TransportThresholds::rtt", - "health::ViewerThresholds::errors", - "health::ViewerThresholds::late", - "health::ViewerThresholds::stalled", - "health::publisher", - "health::viewer", - "stats::Snapshot", -] - -[crates.hang.rename] -# The rule makes its accessors, such as `audio`, the `catalog` module's types: -# `moq_catalog_audio`. -"Catalog" = "hang_catalog" -# The rule names this and moq-net's `Error` alike. -"Error" = "hang_error" -# The rule makes both these enums and the `kind` methods of `AudioCodec` and `VideoCodec` -# `moq_catalog__codec_kind`. -"catalog::AudioCodecKind" = "audio_codec_kind" -"catalog::VideoCodecKind" = "video_codec_kind" -# The rule makes both this method and the field `media` `moq_hang_catalog_media`. A rename -# by the generic path would name every instance's alike. -"Catalog::media" = "hang_catalog_to_media" -"Catalog::media" = "hang_catalog_extra_to_media" -"Catalog::media" = "hang_catalog_ext_to_media" - -[crates.moq-mux] -exclude = [ - "Linger", - "Source::bind", - "Source::follow", - "Source::resolve", - "Source::resolve_reference", - "Source::stats", - "Source::subscribe_track", - "catalog::Entry", - "catalog::Guard", - "catalog::IntoRendition", - "catalog::Producer::audio", - "catalog::Producer::modify", - "catalog::Producer::stats", - "catalog::Producer::text", - "catalog::Producer::track", - "catalog::Producer::video", - "catalog::RenditionConfig", - "catalog::Reserved::audio", - "catalog::Reserved::text", - "catalog::Reserved::track", - "catalog::Reserved::video", - "catalog::Select", - "catalog::Stream", - "catalog::hang::CatalogExt", - "catalog::hang::Extra::iter", - "codec::aac::Config::parse", - "codec::annexb::NalIterator", - "codec::annexb::build_prefix", - "codec::annexb::from_length_prefixed", - "codec::annexb::length_prefixed_to_annexb", - "codec::av1::Error", - "codec::flac::Config", - "codec::h264::Export", - "codec::h265::Export", - "codec::opus::Config::parse", - "codec::opus::Mapping::new", - "codec::opus::mapping::Config", - "container::Container", - "container::InvalidEnd", - "container::MissingKeyframe", - "container::Producer", - "container::flv::Export", - "container::fmp4::Export", - "container::fmp4::Wire::new", - "container::fmp4::Wire::trak", - "container::fmp4::mp4_atom", - "container::mkv::Error", - "container::mkv::Export", - "container::mp4::Export", - "container::mp4::repair", - "container::ts::Catalog", - "container::ts::Export", - "container::ts::Follower", - "container::ts::Import", - "container::ts::Mpegts::si", - "container::ts::Mpegts::tracks", - "container::ts::MultipleProgramsError", - "container::ts::Programs::decode", - "container::ts::Programs::finish", - "container::ts::Restart", - "container::ts::stats::Export::streams", - "container::ts::stats::Snapshot::streams", - "follow::Event", - "follow::Instances", - "json::Consumer", - "select::Selected::audio", - "select::Selected::video", - "timeline::Mapping", - "timeline::Resume", - "timeline::Track::apply", - "timeline::Track::resume", -] - -[crates.moq-mux.rename] -# The rule names these and moq-net's `Error`, and hang's `container::Frame` and -# `catalog::Consumer`, alike. -"Error" = "mux_error" -"container::Frame" = "mux_container_frame" -"catalog::Consumer" = "mux_catalog_consumer" -# The rule makes both this constant and `CatalogFormat`'s `Default` `moq_catalog_catalog_format_default`. -"catalog::CatalogFormat::DEFAULT" = "catalog_catalog_format_fallback" -# The rule makes this media clock's methods, such as `now`, kio's clock's: `moq_clock_now`. -"Clock" = "mux_clock" -# The rule makes both this constant and the method `Clock::timescale` `moq_mux_clock_timescale`. -"Clock::TIMESCALE" = "clock_fixed_timescale" -# The rule makes each of these functions and its module's `Config` `moq_codec__config`. -"codec::aac::config" = "codec_aac_audio_config" -"codec::mp3::config" = "codec_mp3_audio_config" -"codec::opus::config" = "codec_opus_audio_config" -# The rule makes these methods and the variants' tags `moq_container_fmp4_chunk_`. -"container::fmp4::Chunk::init" = "container_fmp4_chunk_into_init" -"container::fmp4::Chunk::fragment" = "container_fmp4_chunk_into_fragment" - -# The waiter a driver's poll registers, which C makes from its waker and holds across polls, -# and the clock it polls at. -[crates.kio] -exclude = [ - "Bridge", - "Closed", - "Consumer", - "ConsumerWeak", - "Lock", - "LockGuard", - "Mut", - "Park::hold", - "Pending", - "Producer", - "ProducerWeak", - "PushError", - "Queue", - "Ref", - "Shared", - "Task", - "TaskKey", - "Tasks", - "Unwanted", - "Waiter::batch", - "Waiter::context", - "Waiter::poll_future", - "Waiter::waker", - "Watch", - "Weak", - "clock::Deadline::poll", - "clock::Deadlines::enter", - "clock::Elapsed", - "clock::Entered", - "clock::Instant::from_std", - "clock::Instant::into_std", - "clock::OutOfRange", - "clock::Rng", - "clock::Sleep", - "clock::Timeout", - "clock::drive", - "clock::rng", - "clock::timeout", - "clock::timeout_at", - "coop::Budget::poll_yield", - "log::Evicted", - "log::Read", - "log::Reader", - "log::Writer", - "machine", - "wait", - "wrapper", -] - -[crates.kio.rename] -# The rule makes both this function and the type `clock::Wall` `moq_clock_wall`. -"clock::wall" = "clock_now_wall" -# The rule makes both this function and the type `clock::Interval` `moq_clock_interval`. -"clock::interval" = "clock_interval_every" diff --git a/ffi/moq/moq.c b/ffi/moq/moq.c index 65046e576..05f700cf9 100644 --- a/ffi/moq/moq.c +++ b/ffi/moq/moq.c @@ -44,19 +44,19 @@ static int is(moq_buf buf, const char *s) { } /* Moves the stats track in data into the lent catalog, in place. */ -static void sets_stats(void *data, moq_hang_catalog_extra *catalog) { - moq_hang_catalog_extra_set_stats(catalog, data); +static void sets_stats(void *data, moq_hang_Catalog_Extra *catalog) { + moq_hang_Catalog_Extra_stats_set(catalog, data); } /* Whether the lent catalog lists its stats track as `stats`: counts it in data. */ -static void lists_stats(void *data, moq_hang_catalog_extra *catalog) { - moq_catalog_stats *stats = moq_hang_catalog_extra_stats(catalog); - moq_catalog_stats *want = moq_catalog_stats_new(str("stats")); - if (stats != NULL && moq_catalog_stats_eq(stats, want)) { +static void lists_stats(void *data, moq_hang_Catalog_Extra *catalog) { + moq_hang_catalog_Stats *stats = moq_hang_Catalog_Extra_stats_get(catalog); + moq_hang_catalog_Stats *want = moq_hang_catalog_Stats_new(str("stats")); + if (stats != NULL && moq_hang_catalog_Stats_eq(stats, want)) { (*(int *)data)++; } - moq_catalog_stats_free(want); - moq_catalog_stats_free(stats); + moq_hang_catalog_Stats_free(want); + moq_hang_catalog_Stats_free(stats); } /* Whether `buf` holds `s` anywhere. */ @@ -71,12 +71,12 @@ static int contains(moq_buf buf, const char *s) { } /* Sets the lent catalog's `app` section to the JSON document in data, in place. */ -static void sets_app(void *data, moq_hang_catalog_extra *catalog) { - moq_catalog_hang_extra *sections = moq_hang_catalog_extra_ext(catalog); - moq_mux_error *err = NULL; - CHECK(moq_catalog_hang_extra_set(sections, str("app"), data, &err)); - moq_hang_catalog_extra_set_ext(catalog, sections); - moq_catalog_hang_extra_free(sections); +static void sets_app(void *data, moq_hang_Catalog_Extra *catalog) { + moq_mux_catalog_hang_Extra *sections = moq_hang_Catalog_Extra_ext_get(catalog); + moq_mux_Error *err = NULL; + CHECK(moq_mux_catalog_hang_Extra_set(sections, str("app"), data, &err)); + moq_hang_Catalog_Extra_ext_set(catalog, sections); + moq_mux_catalog_hang_Extra_free(sections); } /* What C reads back from the lent catalog: its JSON, and its `app` section as text. */ @@ -85,17 +85,17 @@ struct readback { moq_buf app; }; -static void reads_back(void *data, moq_hang_catalog_extra *catalog) { +static void reads_back(void *data, moq_hang_Catalog_Extra *catalog) { struct readback *back = data; - moq_hang_error *hang_err = NULL; - CHECK(moq_hang_catalog_extra_to_json(catalog, &back->json, &hang_err)); - moq_catalog_hang_extra *sections = moq_hang_catalog_extra_ext(catalog); - moq_json *app = moq_catalog_hang_extra_get(sections, str("app")); + moq_hang_Error *hang_err = NULL; + CHECK(moq_hang_Catalog_Extra_to_json(catalog, &back->json, &hang_err)); + moq_mux_catalog_hang_Extra *sections = moq_hang_Catalog_Extra_ext_get(catalog); + moq_Json *app = moq_mux_catalog_hang_Extra_get(sections, str("app")); if (app != NULL) { - back->app = moq_json_to_text(app); - moq_json_free(app); + back->app = moq_Json_to_text(app); + moq_Json_free(app); } - moq_catalog_hang_extra_free(sections); + moq_mux_catalog_hang_Extra_free(sections); } static int wakes = 0; @@ -144,14 +144,14 @@ static void wait_wake(unsigned long seen) { } /* The server's accept loop, which must keep running while the client waits on it. */ -static moq_tokio_server_serve_publish_future *serving = NULL; +static moq_tokio_Server_serve_publish_future *serving = NULL; /* Polls `poll` until it is ready, and the server's accept loop alongside it. */ #define AWAIT(poll) \ for (;;) { \ unsigned long seen = wakes_so_far(); \ - moq_tokio_error *serve_err = NULL; \ - if (moq_tokio_server_serve_publish_poll(serving, session_waker, &serve_err) == MOQ_POLL_READY) { \ + moq_tokio_Error *serve_err = NULL; \ + if (moq_tokio_Server_serve_publish_poll(serving, session_waker, &serve_err) == MOQ_POLL_READY) { \ fprintf(stderr, "%s:%d: the server stopped\n", __FILE__, __LINE__); \ abort(); \ } \ @@ -163,544 +163,544 @@ static moq_tokio_server_serve_publish_future *serving = NULL; int main(void) { moq_waker waker = {.wake = wake}; - moq_error *err = NULL; + moq_Error *err = NULL; CHECK(moq_contract() == MOQ_CONTRACT); - moq_broadcast_info *info = moq_broadcast_info_new(); - moq_broadcast_producer *broadcast = moq_broadcast_info_produce(info); - moq_broadcast_info_free(info); + moq_broadcast_Info *info = moq_broadcast_Info_new(); + moq_broadcast_Producer *broadcast = moq_broadcast_Info_produce(info); + moq_broadcast_Info_free(info); - moq_track_producer *track = NULL; - CHECK(moq_broadcast_producer_create_track(broadcast, str("video"), NULL, &track, &err)); - CHECK(is(moq_track_producer_name(track), "video")); + moq_track_Producer *track = NULL; + CHECK(moq_broadcast_Producer_create_track(broadcast, str("video"), NULL, &track, &err)); + CHECK(is(moq_track_Producer_name(track), "video")); /* A second track of the same name is an error, printed as Rust prints it. */ - moq_track_producer *duplicate = NULL; - CHECK(!moq_broadcast_producer_create_track(broadcast, str("video"), NULL, &duplicate, &err)); - CHECK(err != NULL && is(moq_error_to_string(err), "duplicate")); - moq_error_free(err); + moq_track_Producer *duplicate = NULL; + CHECK(!moq_broadcast_Producer_create_track(broadcast, str("video"), NULL, &duplicate, &err)); + CHECK(err != NULL && is(moq_Error_to_string(err), "duplicate")); + moq_Error_free(err); - moq_track_consumer *consumer = moq_track_producer_consume(track); - moq_track_consumer_subscribe_future *subscribing = moq_track_consumer_subscribe(consumer, NULL); - moq_track_subscriber *subscriber = NULL; - CHECK(moq_track_consumer_subscribe_poll(subscribing, waker, &subscriber, &err) == MOQ_POLL_READY); - moq_track_consumer_subscribe_future_free(subscribing); + moq_track_Consumer *consumer = moq_track_Producer_consume(track); + moq_track_Consumer_subscribe_future *subscribing = moq_track_Consumer_subscribe(consumer, NULL); + moq_track_Subscriber *subscriber = NULL; + CHECK(moq_track_Consumer_subscribe_poll(subscribing, waker, &subscriber, &err) == MOQ_POLL_READY); + moq_track_Consumer_subscribe_future_free(subscribing); /* A group's arrival wakes the subscriber's pending receive. */ - moq_track_subscriber_recv_group_future *receiving = moq_track_subscriber_recv_group(subscriber); - moq_group_consumer *group = NULL; - CHECK(moq_track_subscriber_recv_group_poll(receiving, waker, &group, &err) == MOQ_POLL_PENDING); - moq_group_producer *writer = NULL; - CHECK(moq_track_producer_append_group(track, &writer, &err)); + moq_track_Subscriber_recv_group_future *receiving = moq_track_Subscriber_recv_group(subscriber); + moq_group_Consumer *group = NULL; + CHECK(moq_track_Subscriber_recv_group_poll(receiving, waker, &group, &err) == MOQ_POLL_PENDING); + moq_group_Producer *writer = NULL; + CHECK(moq_track_Producer_append_group(track, &writer, &err)); CHECK(wakes == 1); - CHECK(moq_track_subscriber_recv_group_poll(receiving, waker, &group, &err) == MOQ_POLL_READY); - moq_track_subscriber_recv_group_future_free(receiving); - CHECK(group != NULL && moq_group_consumer_index(group) == 0); + CHECK(moq_track_Subscriber_recv_group_poll(receiving, waker, &group, &err) == MOQ_POLL_READY); + moq_track_Subscriber_recv_group_future_free(receiving); + CHECK(group != NULL && moq_group_Consumer_index(group) == 0); /* So does a frame's. */ - moq_group_consumer_read_frame_future *reading = moq_group_consumer_read_frame(group); - moq_frame_frame *frame = NULL; - CHECK(moq_group_consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_PENDING); - CHECK(moq_group_producer_write_frame(writer, NULL, str("hello"), &err)); + moq_group_Consumer_read_frame_future *reading = moq_group_Consumer_read_frame(group); + moq_frame_Frame *frame = NULL; + CHECK(moq_group_Consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_PENDING); + CHECK(moq_group_Producer_write_frame(writer, NULL, str("hello"), &err)); CHECK(wakes == 2); - CHECK(moq_group_consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_READY); - moq_group_consumer_read_frame_future_free(reading); - CHECK(frame != NULL && is(moq_frame_frame_payload(frame), "hello")); - CHECK(moq_frame_frame_timestamp(frame) == NULL); + CHECK(moq_group_Consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_READY); + moq_group_Consumer_read_frame_future_free(reading); + CHECK(frame != NULL && is(moq_frame_Frame_payload_get(frame), "hello")); + CHECK(moq_frame_Frame_timestamp_get(frame) == NULL); /* When it arrived: kio's clock's `Instant`, from kio's own export. */ - moq_clock_instant *arrived = moq_frame_frame_arrived(frame); - moq_clock_instant *now = moq_clock_now(); - CHECK(moq_clock_instant_checked_duration_since(now, arrived).some); - moq_clock_instant_free(now); - moq_clock_instant_free(arrived); - moq_frame_frame_free(frame); + moq_clock_Instant *arrived = moq_frame_Frame_arrived(frame); + moq_clock_Instant *now = moq_clock_now(); + CHECK(moq_clock_Instant_checked_duration_since(now, arrived).some); + moq_clock_Instant_free(now); + moq_clock_Instant_free(arrived); + moq_frame_Frame_free(frame); /* A timestamp is a `Copy` value behind a handle: passing it copies it out. The track * is untimed, so a timestamped frame is refused. */ - moq_timestamp *at = NULL; - moq_time_overflow *overflow = NULL; - CHECK(moq_timestamp_from_millis(1500, &at, &overflow)); - CHECK(moq_timestamp_as_secs(at) == 1); - CHECK(!moq_group_producer_write_frame(writer, at, str("late"), &err)); - CHECK(err != NULL && is(moq_error_to_string(err), "frame timestamp doesn't fit its track")); - moq_error_free(err); - CHECK(moq_timestamp_as_secs(at) == 1); + moq_Timestamp *at = NULL; + moq_TimeOverflow *overflow = NULL; + CHECK(moq_Timestamp_from_millis(1500, &at, &overflow)); + CHECK(moq_Timestamp_as_secs(at) == 1); + CHECK(!moq_group_Producer_write_frame(writer, at, str("late"), &err)); + CHECK(err != NULL && is(moq_Error_to_string(err), "frame timestamp doesn't fit its track")); + moq_Error_free(err); + CHECK(moq_Timestamp_as_secs(at) == 1); /* The last frame, bytes that are not UTF-8, then the group's end, which reads as None. */ - CHECK(moq_group_producer_write_frame(writer, NULL, str("\xffworld"), &err)); - CHECK(moq_group_producer_finish(writer, &err)); - reading = moq_group_consumer_read_frame(group); - CHECK(moq_group_consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_READY); - moq_group_consumer_read_frame_future_free(reading); - CHECK(frame != NULL && is(moq_frame_frame_payload(frame), "\xffworld")); - moq_frame_frame_free(frame); - reading = moq_group_consumer_read_frame(group); - frame = (moq_frame_frame *)1; - CHECK(moq_group_consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_READY); + CHECK(moq_group_Producer_write_frame(writer, NULL, str("\xffworld"), &err)); + CHECK(moq_group_Producer_finish(writer, &err)); + reading = moq_group_Consumer_read_frame(group); + CHECK(moq_group_Consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_READY); + moq_group_Consumer_read_frame_future_free(reading); + CHECK(frame != NULL && is(moq_frame_Frame_payload_get(frame), "\xffworld")); + moq_frame_Frame_free(frame); + reading = moq_group_Consumer_read_frame(group); + frame = (moq_frame_Frame *)1; + CHECK(moq_group_Consumer_read_frame_poll(reading, waker, &frame, &err) == MOQ_POLL_READY); CHECK(frame == NULL); - moq_group_consumer_read_frame_future_free(reading); + moq_group_Consumer_read_frame_future_free(reading); - moq_timestamp_free(at); - moq_group_consumer_free(group); - moq_group_producer_free(writer); - moq_track_subscriber_free(subscriber); - moq_track_consumer_free(consumer); - moq_track_producer_free(track); - moq_broadcast_producer_free(broadcast); + moq_Timestamp_free(at); + moq_group_Consumer_free(group); + moq_group_Producer_free(writer); + moq_track_Subscriber_free(subscriber); + moq_track_Consumer_free(consumer); + moq_track_Producer_free(track); + moq_broadcast_Producer_free(broadcast); /* An origin and the driver that runs it, a tuple returned through two out pointers. * Its root, which it lends, is returned as a new handle to a clone. */ - moq_origin_config *config = moq_origin_config_default(); - moq_origin_producer *origin = NULL; - moq_origin_driver *driver = NULL; - moq_origin_producer_new(config, &origin, &driver); - moq_origin_config_free(config); + moq_origin_Config *config = moq_origin_Config_default(); + moq_origin_Producer *origin = NULL; + moq_origin_Driver *driver = NULL; + moq_origin_Producer_new(config, &origin, &driver); + moq_origin_Config_free(config); CHECK(origin != NULL && driver != NULL); - moq_path *root = moq_origin_producer_root(origin); - CHECK(is(moq_path_as_str(root), "")); - moq_path_free(root); + moq_Path *root = moq_origin_Producer_root(origin); + CHECK(is(moq_Path_as_str(root), "")); + moq_Path_free(root); /* Scoped beneath a root to patterns, which moq-pattern's own export builds. */ - moq_pattern *alice = NULL; - moq_invalid_pattern invalid; - CHECK(moq_pattern_subtree(str("alice"), &alice, &invalid)); - moq_patterns *patterns = moq_patterns_new(); - CHECK(moq_patterns_insert(patterns, alice)); - moq_pattern_free(alice); - moq_origin_producer *scoped = NULL; - CHECK(moq_origin_producer_scope(origin, str("room"), patterns, &scoped, &err)); - moq_patterns_free(patterns); - root = moq_origin_producer_root(scoped); - CHECK(is(moq_path_as_str(root), "room")); - moq_path_free(root); - moq_path *absolute = NULL; - CHECK(moq_origin_producer_absolute(scoped, str("alice"), &absolute, &err)); - CHECK(is(moq_path_as_str(absolute), "room/alice")); - moq_path_free(absolute); - moq_patterns *allowed = moq_origin_producer_allowed(scoped); - CHECK(moq_patterns_matches(allowed, str("alice/cam"))); - CHECK(!moq_patterns_matches(allowed, str("bob"))); - moq_patterns_free(allowed); - moq_origin_consumer *reader = moq_origin_producer_consume(scoped); - CHECK(moq_origin_consumer_absolute(reader, str("bob"), &absolute, &err)); - CHECK(is(moq_path_as_str(absolute), "room/bob")); - moq_path_free(absolute); - moq_origin_consumer_free(reader); - moq_origin_producer_free(scoped); + moq_Pattern *alice = NULL; + moq_InvalidPattern invalid; + CHECK(moq_Pattern_subtree(str("alice"), &alice, &invalid)); + moq_Patterns *patterns = moq_Patterns_new(); + CHECK(moq_Patterns_insert(patterns, alice)); + moq_Pattern_free(alice); + moq_origin_Producer *scoped = NULL; + CHECK(moq_origin_Producer_scope(origin, str("room"), patterns, &scoped, &err)); + moq_Patterns_free(patterns); + root = moq_origin_Producer_root(scoped); + CHECK(is(moq_Path_as_str(root), "room")); + moq_Path_free(root); + moq_Path *absolute = NULL; + CHECK(moq_origin_Producer_absolute(scoped, str("alice"), &absolute, &err)); + CHECK(is(moq_Path_as_str(absolute), "room/alice")); + moq_Path_free(absolute); + moq_Patterns *allowed = moq_origin_Producer_allowed(scoped); + CHECK(moq_Patterns_matches(allowed, str("alice/cam"))); + CHECK(!moq_Patterns_matches(allowed, str("bob"))); + moq_Patterns_free(allowed); + moq_origin_Consumer *reader = moq_origin_Producer_consume(scoped); + CHECK(moq_origin_Consumer_absolute(reader, str("bob"), &absolute, &err)); + CHECK(is(moq_Path_as_str(absolute), "room/bob")); + moq_Path_free(absolute); + moq_origin_Consumer_free(reader); + moq_origin_Producer_free(scoped); /* A broadcast the origin creates is stamped with its path, which its info lends. */ - moq_origin_publication *publication = NULL; - CHECK(moq_origin_producer_create_broadcast(origin, str("room/alice"), &publication, &err)); - moq_broadcast_producer *created = moq_origin_publication_broadcast(publication); - moq_origin_publication_free(publication); - moq_broadcast_info *stamped = moq_broadcast_producer_info(created); - moq_path *path = moq_broadcast_info_path(stamped); - CHECK(is(moq_path_as_str(path), "room/alice")); + moq_origin_Publication *publication = NULL; + CHECK(moq_origin_Producer_create_broadcast(origin, str("room/alice"), &publication, &err)); + moq_broadcast_Producer *created = moq_origin_Publication_broadcast(publication); + moq_origin_Publication_free(publication); + moq_broadcast_Info *stamped = moq_broadcast_Producer_info(created); + moq_Path *path = moq_broadcast_Info_path_get(stamped); + CHECK(is(moq_Path_as_str(path), "room/alice")); /* Paths own their segments, so C builds them, and relative ones, from a string. */ - moq_path *cam = NULL; - moq_path_error path_error; - CHECK(!moq_path_new(str("/cam/"), &cam, &path_error) && path_error == MOQ_PATH_ERROR_EMPTY_SEGMENT); - CHECK(moq_path_new(str("cam"), &cam, &path_error)); - CHECK(is(moq_path_as_str(cam), "cam")); - moq_path *joined = NULL; - CHECK(moq_path_join(path, cam, &joined, &path_error)); - CHECK(is(moq_path_as_str(joined), "room/alice/cam")); - moq_path *stripped = moq_path_strip_prefix(joined, path); - CHECK(stripped != NULL && moq_path_eq(stripped, cam)); - moq_path_free(stripped); - CHECK(moq_path_strip_prefix(path, joined) == NULL); - moq_relative *up = NULL; - CHECK(moq_relative_new(str("../bob"), &up, &path_error)); - moq_path *resolved = NULL; - CHECK(moq_path_resolve(joined, up, &resolved, &path_error)); - CHECK(is(moq_path_as_str(resolved), "room/bob")); - moq_path_free(resolved); - moq_relative_free(up); - moq_path_free(joined); - moq_path_free(cam); - moq_path_free(path); - moq_broadcast_info_free(stamped); - moq_broadcast_producer_free(created); + moq_Path *cam = NULL; + moq_path_Error path_error; + CHECK(!moq_Path_new(str("/cam/"), &cam, &path_error) && path_error == MOQ_PATH_ERROR_EMPTY_SEGMENT); + CHECK(moq_Path_new(str("cam"), &cam, &path_error)); + CHECK(is(moq_Path_as_str(cam), "cam")); + moq_Path *joined = NULL; + CHECK(moq_Path_join(path, cam, &joined, &path_error)); + CHECK(is(moq_Path_as_str(joined), "room/alice/cam")); + moq_Path *stripped = moq_Path_strip_prefix(joined, path); + CHECK(stripped != NULL && moq_Path_eq(stripped, cam)); + moq_Path_free(stripped); + CHECK(moq_Path_strip_prefix(path, joined) == NULL); + moq_path_Relative *up = NULL; + CHECK(moq_path_Relative_new(str("../bob"), &up, &path_error)); + moq_Path *resolved = NULL; + CHECK(moq_Path_resolve(joined, up, &resolved, &path_error)); + CHECK(is(moq_Path_as_str(resolved), "room/bob")); + moq_Path_free(resolved); + moq_path_Relative_free(up); + moq_Path_free(joined); + moq_Path_free(cam); + moq_Path_free(path); + moq_broadcast_Info_free(stamped); + moq_broadcast_Producer_free(created); /* The driver, polled as a language's event loop would: at the time, and with a waiter * made from the waker and held across polls, since what a poll registers lives only * as long as its waiter. It runs until every owner of the origin is gone. */ - moq_waiter *waiter = moq_waiter_new(waker); + moq_Waiter *waiter = moq_Waiter_new(waker); now = moq_clock_now(); - moq_clock_instant *deadline = NULL; - CHECK(moq_origin_driver_poll(driver, now, waiter, &deadline, &err)); - moq_clock_instant_free(deadline); - moq_clock_instant_free(now); + moq_clock_Instant *deadline = NULL; + CHECK(moq_origin_Driver_poll(driver, now, waiter, &deadline, &err)); + moq_clock_Instant_free(deadline); + moq_clock_Instant_free(now); /* A client publishes the origin through moq-net's generic `with_publisher`, at each * type that implements its bound, by reference, so C keeps its handle. */ - moq_client *client = moq_client_new(); - moq_client *publishing = moq_client_with_publisher_producer(client, origin); + moq_Client *client = moq_Client_new(); + moq_Client *publishing = moq_Client_with_publisher_Producer(client, origin); CHECK(publishing != NULL); - moq_client_free(publishing); - moq_client_free(client); + moq_Client_free(publishing); + moq_Client_free(client); int before = wakes; - moq_origin_producer_free(origin); + moq_origin_Producer_free(origin); /* The last owner's drop wakes the driver, which then finishes, cleanly. */ CHECK(wakes > before); now = moq_clock_now(); deadline = NULL; err = NULL; - CHECK(!moq_origin_driver_poll(driver, now, waiter, &deadline, &err)); - CHECK(deadline == NULL && err != NULL && is(moq_error_to_string(err), "closed")); - moq_error_free(err); + CHECK(!moq_origin_Driver_poll(driver, now, waiter, &deadline, &err)); + CHECK(deadline == NULL && err != NULL && is(moq_Error_to_string(err), "closed")); + moq_Error_free(err); err = NULL; - moq_clock_instant_free(now); - moq_waiter_free(waiter); - moq_origin_driver_free(driver); + moq_clock_Instant_free(now); + moq_Waiter_free(waiter); + moq_origin_Driver_free(driver); /* moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which * the catalog lists at the decoder's 48 kHz, keeping the head as its description. */ static const uint8_t head[] = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd', 1, 2, 0x38, 0x01, 0x44, 0xac, 0, 0, 0, 0, 0}; - moq_catalog_audio_config *audio = NULL; - moq_mux_error *mux_err = NULL; - CHECK(moq_codec_opus_audio_config((moq_buf){.ptr = head, .len = sizeof head}, &audio, &mux_err)); - CHECK(moq_catalog_audio_config_sample_rate(audio) == 48000); - CHECK(moq_catalog_audio_config_channel_count(audio) == 2); - moq_catalog_audio_codec codec = moq_catalog_audio_config_codec(audio); - CHECK(codec.tag == MOQ_CATALOG_AUDIO_CODEC_OPUS); - moq_catalog_audio_codec_free(&codec); - moq_buf description = moq_catalog_audio_config_description(audio); + moq_hang_catalog_AudioConfig *audio = NULL; + moq_mux_Error *mux_err = NULL; + CHECK(moq_mux_codec_opus_config((moq_buf){.ptr = head, .len = sizeof head}, &audio, &mux_err)); + CHECK(moq_hang_catalog_AudioConfig_sample_rate_get(audio) == 48000); + CHECK(moq_hang_catalog_AudioConfig_channel_count_get(audio) == 2); + moq_hang_catalog_AudioCodec codec = moq_hang_catalog_AudioConfig_codec_get(audio); + CHECK(codec.tag == MOQ_HANG_CATALOG_AUDIOCODEC_OPUS); + moq_hang_catalog_AudioCodec_free(&codec); + moq_buf description = moq_hang_catalog_AudioConfig_description_get(audio); CHECK(description.len == sizeof head && memcmp(description.ptr, head, sizeof head) == 0); moq_buf_free(description); - moq_catalog_audio_config_free(audio); + moq_hang_catalog_AudioConfig_free(audio); /* A truncated head is moq-mux's error. */ - CHECK(!moq_codec_opus_audio_config((moq_buf){.ptr = head, .len = 8}, &audio, &mux_err)); + CHECK(!moq_mux_codec_opus_config((moq_buf){.ptr = head, .len = 8}, &audio, &mux_err)); CHECK(mux_err != NULL); - moq_buf message = moq_mux_error_to_string(mux_err); + moq_buf message = moq_mux_Error_to_string(mux_err); CHECK(message.len > 0); moq_buf_free(message); - moq_mux_error_free(mux_err); + moq_mux_Error_free(mux_err); mux_err = NULL; /* A subscription's ranges, from any iterable of them: an array, copied in. */ - moq_track_range ranges[] = { - moq_track_range_live(), - moq_track_range_groups((moq_bounds_u64){.start = {.kind = MOQ_BOUND_INCLUDED, .value = 2}, + moq_track_Range ranges[] = { + moq_track_Range_live(), + moq_track_Range_groups((moq_bounds_u64){.start = {.kind = MOQ_BOUND_INCLUDED, .value = 2}, .end = {.kind = MOQ_BOUND_EXCLUDED, .value = 6}}), }; - moq_track_subscription *unranged = moq_track_subscription_default(); - moq_track_subscription *ranged = - moq_track_subscription_with_ranges(unranged, (moq_vec_track_range){.ptr = ranges, .len = 2}); - moq_track_subscription_free(unranged); - moq_vec_track_range got = moq_track_subscription_ranges(ranged); - CHECK(got.len == 2 && moq_track_range_eq(&got.ptr[0], &ranges[0])); + moq_track_Subscription *unranged = moq_track_Subscription_default(); + moq_track_Subscription *ranged = + moq_track_Subscription_with_ranges(unranged, (moq_vec_track_Range){.ptr = ranges, .len = 2}); + moq_track_Subscription_free(unranged); + moq_vec_track_Range got = moq_track_Subscription_ranges_get(ranged); + CHECK(got.len == 2 && moq_track_Range_eq(&got.ptr[0], &ranges[0])); CHECK(got.len == 2 && got.ptr[1].start.tag == MOQ_TRACK_ANCHOR_ABSOLUTE && got.ptr[1].start.absolute._0 == 2); CHECK(got.len == 2 && got.ptr[1].end.some && got.ptr[1].end.value.absolute._0 == 6); - moq_vec_track_range_free(got); - moq_track_range_free(&ranges[0]); - moq_track_range_free(&ranges[1]); - moq_track_subscription_free(ranged); + moq_vec_track_Range_free(got); + moq_track_Range_free(&ranges[0]); + moq_track_Range_free(&ranges[1]); + moq_track_Subscription_free(ranged); /* A catalog at moq-mux's untyped extension, from the config C builds, whose generic * `is_claimed` is named by the last segment of its argument, and whose `mutate` lends * the catalog to C, which sets its fields in place. */ - moq_broadcast_info *listed = moq_broadcast_info_new(); - moq_broadcast_producer *publisher = moq_broadcast_info_produce(listed); - moq_broadcast_info_free(listed); - moq_catalog_config *catalog_config = moq_catalog_config_default(); - moq_hang_catalog_extra *hang = moq_hang_catalog_extra_default(); - moq_catalog_config_extra *extra = moq_catalog_config_with_catalog_extra(catalog_config, hang); - moq_catalog_config_free(catalog_config); - moq_hang_catalog_extra_free(hang); - moq_catalog_producer_extra *catalog = NULL; - CHECK(moq_catalog_producer_extra_new(publisher, extra, &catalog, &mux_err)); - moq_catalog_config_extra_free(extra); - CHECK(!moq_catalog_producer_extra_is_claimed_binary_config(catalog, str("state"))); + moq_broadcast_Info *listed = moq_broadcast_Info_new(); + moq_broadcast_Producer *publisher = moq_broadcast_Info_produce(listed); + moq_broadcast_Info_free(listed); + moq_mux_catalog_Config *catalog_config = moq_mux_catalog_Config_default(); + moq_hang_Catalog_Extra *hang = moq_hang_Catalog_Extra_default(); + moq_mux_catalog_Config_Extra *extra = moq_mux_catalog_Config_with_catalog_Extra(catalog_config, hang); + moq_mux_catalog_Config_free(catalog_config); + moq_hang_Catalog_Extra_free(hang); + moq_mux_catalog_Producer_Extra *catalog = NULL; + CHECK(moq_mux_catalog_Producer_Extra_new(publisher, extra, &catalog, &mux_err)); + moq_mux_catalog_Config_Extra_free(extra); + CHECK(!moq_mux_catalog_Producer_Extra_is_claimed_BinaryConfig(catalog, str("state"))); /* A field of the lent catalog, set in place: the next call reads it. */ - moq_catalog_stats *stats = moq_catalog_stats_new(str("stats")); - CHECK(moq_catalog_producer_extra_mutate(catalog, sets_stats, stats, &mux_err)); - moq_catalog_stats_free(stats); + moq_hang_catalog_Stats *stats = moq_hang_catalog_Stats_new(str("stats")); + CHECK(moq_mux_catalog_Producer_Extra_mutate(catalog, sets_stats, stats, &mux_err)); + moq_hang_catalog_Stats_free(stats); int listed_stats = 0; - CHECK(moq_catalog_producer_extra_mutate(catalog, lists_stats, &listed_stats, &mux_err)); + CHECK(moq_mux_catalog_Producer_Extra_mutate(catalog, lists_stats, &listed_stats, &mux_err)); CHECK(listed_stats == 1); /* A JSON track the catalog lists, published through moq-mux's generic `json_snapshot` * at a JSON document, and a section C parses from text, set in the lent catalog and * read back as text. Its track has a timescale, since every write is stamped. */ - moq_timescale *milli = moq_timescale_milli(); - moq_track_info *status_info = moq_track_info_with_timescale(moq_track_info_default(), milli); - moq_timescale_free(milli); - moq_track_producer *status = NULL; - CHECK(moq_broadcast_producer_create_track(publisher, str("status"), status_info, &status, &err)); - moq_json_config *json_config = moq_json_config_default(); - moq_json_options *json_options = moq_json_options_default(); - moq_json_snapshot_json *status_snapshot = NULL; - CHECK(moq_catalog_producer_extra_json_snapshot_config(catalog, status, json_config, json_options, &status_snapshot, + moq_Timescale *milli = moq_Timescale_MILLI(); + moq_track_Info *status_info = moq_track_Info_with_timescale(moq_track_Info_default(), milli); + moq_Timescale_free(milli); + moq_track_Producer *status = NULL; + CHECK(moq_broadcast_Producer_create_track(publisher, str("status"), status_info, &status, &err)); + moq_mux_json_Config *json_config = moq_mux_json_Config_default(); + moq_mux_json_Options *json_options = moq_mux_json_Options_default(); + moq_mux_json_Snapshot_Json *status_snapshot = NULL; + CHECK(moq_mux_catalog_Producer_Extra_json_snapshot_Config(catalog, status, json_config, json_options, &status_snapshot, &mux_err)); - moq_json_config_free(json_config); - moq_json_options_free(json_options); - moq_track_producer_free(status); - CHECK(is(moq_json_snapshot_json_name(status_snapshot), "status")); - CHECK(moq_catalog_producer_extra_is_claimed_json_config(catalog, str("status"))); + moq_mux_json_Config_free(json_config); + moq_mux_json_Options_free(json_options); + moq_track_Producer_free(status); + CHECK(is(moq_mux_json_Snapshot_Json_name(status_snapshot), "status")); + CHECK(moq_mux_catalog_Producer_Extra_is_claimed_JsonConfig(catalog, str("status"))); /* A value C parses is published as the track's latest, which a subscriber reads back * as JSON text. */ - moq_json *latest = NULL; + moq_Json *latest = NULL; moq_buf json_err; - CHECK(moq_json_parse(str("{\"on\": true}"), &latest, &json_err)); - moq_timestamp *stamp = moq_timestamp_now(); - CHECK(moq_json_snapshot_json_update(status_snapshot, latest, stamp, &mux_err)); - moq_timestamp_free(stamp); - moq_json_free(latest); - moq_track_subscriber *status_subscriber = moq_json_snapshot_json_consume(status_snapshot); - moq_track_subscriber_recv_group_future *status_receiving = moq_track_subscriber_recv_group(status_subscriber); - moq_group_consumer *status_group = NULL; - CHECK(moq_track_subscriber_recv_group_poll(status_receiving, waker, &status_group, &err) == MOQ_POLL_READY); - moq_track_subscriber_recv_group_future_free(status_receiving); - moq_frame_frame *status_frame = NULL; + CHECK(moq_Json_parse(str("{\"on\": true}"), &latest, &json_err)); + moq_Timestamp *stamp = moq_Timestamp_now(); + CHECK(moq_mux_json_Snapshot_Json_update(status_snapshot, latest, stamp, &mux_err)); + moq_Timestamp_free(stamp); + moq_Json_free(latest); + moq_track_Subscriber *status_subscriber = moq_mux_json_Snapshot_Json_consume(status_snapshot); + moq_track_Subscriber_recv_group_future *status_receiving = moq_track_Subscriber_recv_group(status_subscriber); + moq_group_Consumer *status_group = NULL; + CHECK(moq_track_Subscriber_recv_group_poll(status_receiving, waker, &status_group, &err) == MOQ_POLL_READY); + moq_track_Subscriber_recv_group_future_free(status_receiving); + moq_frame_Frame *status_frame = NULL; if (status_group != NULL) { - moq_group_consumer_read_frame_future *status_reading = moq_group_consumer_read_frame(status_group); - CHECK(moq_group_consumer_read_frame_poll(status_reading, waker, &status_frame, &err) == MOQ_POLL_READY); - moq_group_consumer_read_frame_future_free(status_reading); - moq_group_consumer_free(status_group); + moq_group_Consumer_read_frame_future *status_reading = moq_group_Consumer_read_frame(status_group); + CHECK(moq_group_Consumer_read_frame_poll(status_reading, waker, &status_frame, &err) == MOQ_POLL_READY); + moq_group_Consumer_read_frame_future_free(status_reading); + moq_group_Consumer_free(status_group); } - CHECK(status_frame != NULL && is(moq_frame_frame_payload(status_frame), "{\"on\":true}")); - moq_frame_frame_free(status_frame); - moq_track_subscriber_free(status_subscriber); - moq_json *app = NULL; - CHECK(moq_json_parse(str("{\"v\": [1, true]}"), &app, &json_err)); - CHECK(moq_catalog_producer_extra_mutate(catalog, sets_app, app, &mux_err)); - moq_json_free(app); + CHECK(status_frame != NULL && is(moq_frame_Frame_payload_get(status_frame), "{\"on\":true}")); + moq_frame_Frame_free(status_frame); + moq_track_Subscriber_free(status_subscriber); + moq_Json *app = NULL; + CHECK(moq_Json_parse(str("{\"v\": [1, true]}"), &app, &json_err)); + CHECK(moq_mux_catalog_Producer_Extra_mutate(catalog, sets_app, app, &mux_err)); + moq_Json_free(app); struct readback back = {0}; - CHECK(moq_catalog_producer_extra_mutate(catalog, reads_back, &back, &mux_err)); + CHECK(moq_mux_catalog_Producer_Extra_mutate(catalog, reads_back, &back, &mux_err)); CHECK(contains(back.json, "\"status\"")); moq_buf_free(back.json); CHECK(is(back.app, "{\"v\":[1,true]}")); /* Malformed text fails to parse, and a reserved section is moq-mux's error. */ - CHECK(!moq_json_parse(str("{\"v\":"), &app, &json_err)); + CHECK(!moq_Json_parse(str("{\"v\":"), &app, &json_err)); CHECK(is(json_err, "EOF while parsing a value at line 1 column 5")); - moq_catalog_hang_extra *sections = moq_catalog_hang_extra_default(); - CHECK(moq_json_parse(str("1"), &app, &json_err)); - CHECK(!moq_catalog_hang_extra_set(sections, str("json"), app, &mux_err)); - CHECK(mux_err != NULL && is(moq_mux_error_to_string(mux_err), "reserved catalog section: json")); - moq_mux_error_free(mux_err); + moq_mux_catalog_hang_Extra *sections = moq_mux_catalog_hang_Extra_default(); + CHECK(moq_Json_parse(str("1"), &app, &json_err)); + CHECK(!moq_mux_catalog_hang_Extra_set(sections, str("json"), app, &mux_err)); + CHECK(mux_err != NULL && is(moq_mux_Error_to_string(mux_err), "reserved catalog section: json")); + moq_mux_Error_free(mux_err); mux_err = NULL; - moq_json_free(app); - moq_catalog_hang_extra_free(sections); - moq_json_snapshot_json_free(status_snapshot); - moq_catalog_producer_extra_free(catalog); - moq_broadcast_producer_free(publisher); + moq_Json_free(app); + moq_mux_catalog_hang_Extra_free(sections); + moq_mux_json_Snapshot_Json_free(status_snapshot); + moq_mux_catalog_Producer_Extra_free(catalog); + moq_broadcast_Producer_free(publisher); /* Outside any tokio runtime, as C is, moq-tokio runs on the fallback it starts. */ - moq_origin_producer *spawned = moq_origin_spawn(); - moq_origin_producer_free(spawned); - moq_connect_config *client_config = moq_connect_config_default(); - moq_quic_config *quic = moq_quic_config_default(); - moq_tokio_client *tokio_client = NULL; - moq_tokio_error *tokio_err = NULL; - CHECK(moq_connect_config_init(client_config, quic, &tokio_client, &tokio_err)); - moq_connect_config_free(client_config); - moq_quic_config_free(quic); - moq_tokio_client_free(tokio_client); + moq_origin_Producer *spawned = moq_tokio_origin_spawn(); + moq_origin_Producer_free(spawned); + moq_tokio_connect_Config *client_config = moq_tokio_connect_Config_default(); + moq_tokio_quic_Config *quic = moq_tokio_quic_Config_default(); + moq_tokio_Client *tokio_client = NULL; + moq_tokio_Error *tokio_err = NULL; + CHECK(moq_tokio_connect_Config_init(client_config, quic, &tokio_client, &tokio_err)); + moq_tokio_connect_Config_free(client_config); + moq_tokio_quic_Config_free(quic); + moq_tokio_Client_free(tokio_client); /* moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. */ - moq_connect_error unauthorized = MOQ_CONNECT_ERROR_UNAUTHORIZED; - CHECK(moq_connect_error_is_auth(&unauthorized)); - CHECK(is(moq_connect_error_to_string(&unauthorized), "unauthorized")); + moq_tokio_ConnectError unauthorized = MOQ_TOKIO_CONNECTERROR_UNAUTHORIZED; + CHECK(moq_tokio_ConnectError_is_auth(&unauthorized)); + CHECK(is(moq_tokio_ConnectError_to_string(&unauthorized), "unauthorized")); /* A session's config, filled in as Rust assigns it, and a nested field by getting a copy, * editing it, and setting it back. */ - moq_connect_config *connect = moq_connect_config_default(); - moq_tls_connect *tls = moq_connect_config_tls(connect); - moq_tls_connect_set_insecure(tls, (moq_option_bool){.some = true, .value = true}); - moq_tls_connect_set_host_name(tls, str("relay.example")); + moq_tokio_connect_Config *connect = moq_tokio_connect_Config_default(); + moq_tokio_tls_Connect *tls = moq_tokio_connect_Config_tls_get(connect); + moq_tokio_tls_Connect_insecure_set(tls, (moq_option_bool){.some = true, .value = true}); + moq_tokio_tls_Connect_host_name_set(tls, str("relay.example")); /* A `Vec`, an array of buffers C keeps. */ moq_buf pins[] = {str("ab:cd")}; - moq_tls_connect_set_fingerprint(tls, (moq_vec_string){.ptr = pins, .len = 1}); - moq_connect_config_set_tls(connect, tls); - moq_tls_connect_free(tls); - moq_connect_config_set_implementation(connect, str("moq.c")); + moq_tokio_tls_Connect_fingerprint_set(tls, (moq_vec_string){.ptr = pins, .len = 1}); + moq_tokio_connect_Config_tls_set(connect, tls); + moq_tokio_tls_Connect_free(tls); + moq_tokio_connect_Config_implementation_set(connect, str("moq.c")); /* A URL, parsed from text, failing with why, and moved in. */ - moq_url *url = NULL; + moq_Url *url = NULL; moq_buf parse_err; - CHECK(!moq_url_parse(str("relay.example"), &url, &parse_err)); + CHECK(!moq_Url_parse(str("relay.example"), &url, &parse_err)); CHECK(is(parse_err, "relative URL without a base")); - CHECK(moq_url_parse(str("https://relay.example:4443/demo"), &url, &parse_err)); - moq_connect_config_set_url(connect, url); - moq_url_free(url); - tls = moq_connect_config_tls(connect); - moq_option_bool insecure = moq_tls_connect_insecure(tls); + CHECK(moq_Url_parse(str("https://relay.example:4443/demo"), &url, &parse_err)); + moq_tokio_connect_Config_url_set(connect, url); + moq_Url_free(url); + tls = moq_tokio_connect_Config_tls_get(connect); + moq_option_bool insecure = moq_tokio_tls_Connect_insecure_get(tls); CHECK(insecure.some && insecure.value); - CHECK(is(moq_tls_connect_host_name(tls), "relay.example")); + CHECK(is(moq_tokio_tls_Connect_host_name_get(tls), "relay.example")); /* TLS files, as OS paths: none by default. */ - moq_vec_path roots = moq_tls_connect_root(tls); - moq_vec_string fingerprints = moq_tls_connect_fingerprint(tls); + moq_vec_path roots = moq_tokio_tls_Connect_root_get(tls); + moq_vec_string fingerprints = moq_tokio_tls_Connect_fingerprint_get(tls); CHECK(roots.ptr != NULL && roots.len == 0); CHECK(fingerprints.len == 1 && fingerprints.ptr[0].len == 5 && memcmp(fingerprints.ptr[0].ptr, "ab:cd", 5) == 0); moq_vec_path_free(roots); moq_vec_string_free(fingerprints); - moq_tls_connect_free(tls); - CHECK(is(moq_connect_config_implementation(connect), "moq.c")); - url = moq_connect_config_url(connect); - CHECK(url != NULL && is(moq_url_to_string(url), "https://relay.example:4443/demo")); + moq_tokio_tls_Connect_free(tls); + CHECK(is(moq_tokio_connect_Config_implementation_get(connect), "moq.c")); + url = moq_tokio_connect_Config_url_get(connect); + CHECK(url != NULL && is(moq_Url_to_string(url), "https://relay.example:4443/demo")); /* The config's URL, dialed with no tokens. */ - moq_auth_tokens *tokens = moq_auth_tokens_new((moq_vec_auth_token){0}); - moq_versions *versions = moq_versions_default(); - moq_auth_dial *dial = moq_auth_tokens_dial(tokens, url, versions); - moq_url *dialed = moq_auth_dial_url(dial); - CHECK(moq_url_eq(dialed, url)); - moq_url_free(dialed); - moq_auth_dial_free(dial); - moq_auth_tokens_free(tokens); - moq_url_free(url); - moq_connect_config_free(connect); + moq_auth_Tokens *tokens = moq_auth_Tokens_new((moq_vec_auth_Token){0}); + moq_Versions *versions = moq_Versions_default(); + moq_auth_Dial *dial = moq_auth_Tokens_dial(tokens, url, versions); + moq_Url *dialed = moq_auth_Dial_url_get(dial); + CHECK(moq_Url_eq(dialed, url)); + moq_Url_free(dialed); + moq_auth_Dial_free(dial); + moq_auth_Tokens_free(tokens); + moq_Url_free(url); + moq_tokio_connect_Config_free(connect); /* A backoff's fields are settings left unset, and the methods of their names resolve them. */ - moq_backoff *backoff = moq_backoff_default(); - CHECK(moq_backoff_initial_setting(backoff) == NULL); - moq_duration initial = moq_backoff_initial(backoff); + moq_tokio_Backoff *backoff = moq_tokio_Backoff_default(); + CHECK(moq_tokio_Backoff_initial_get(backoff) == NULL); + moq_duration initial = moq_tokio_Backoff_initial(backoff); CHECK(initial.secs == 1 && initial.nanos == 0); - moq_backoff_free(backoff); + moq_tokio_Backoff_free(backoff); /* A `Vec`, each element a buffer the array frees. */ - moq_vec_string alpns = moq_versions_alpns(versions); + moq_vec_string alpns = moq_Versions_alpns(versions); CHECK(alpns.len > 0); for (size_t i = 0; i < alpns.len; i++) { CHECK(alpns.ptr[i].len > 0); } moq_vec_string_free(alpns); - moq_versions_free(versions); + moq_Versions_free(versions); /* A socket address, crossing in a listener's bind, set on its config and read back. */ - moq_listen_bind bind = {.tag = MOQ_LISTEN_BIND_ADDR, .addr = {._0 = {.ip = {[15] = 1}, .port = 4443, .v6 = true}}}; - CHECK(is(moq_listen_bind_to_string(&bind), "[::1]:4443")); - moq_listen_bind v4 = {.tag = MOQ_LISTEN_BIND_ADDR, .addr = {._0 = {.ip = {127, 0, 0, 1}, .port = 0}}}; - CHECK(is(moq_listen_bind_to_string(&v4), "127.0.0.1:0")); - CHECK(!moq_listen_bind_eq(&bind, &v4)); - moq_listen_config *listen = moq_listen_config_default(); - moq_listen_config_set_bind(listen, (moq_option_listen_bind){.some = true, .value = bind}); - moq_option_listen_bind bound = moq_listen_config_bind(listen); - CHECK(bound.some && moq_listen_bind_eq(&bound.value, &bind)); - moq_listen_bind_free(&bound.value); + moq_tokio_listen_Bind bind = {.tag = MOQ_TOKIO_LISTEN_BIND_ADDR, .addr = {._0 = {.ip = {[15] = 1}, .port = 4443, .v6 = true}}}; + CHECK(is(moq_tokio_listen_Bind_to_string(&bind), "[::1]:4443")); + moq_tokio_listen_Bind v4 = {.tag = MOQ_TOKIO_LISTEN_BIND_ADDR, .addr = {._0 = {.ip = {127, 0, 0, 1}, .port = 0}}}; + CHECK(is(moq_tokio_listen_Bind_to_string(&v4), "127.0.0.1:0")); + CHECK(!moq_tokio_listen_Bind_eq(&bind, &v4)); + moq_tokio_listen_Config *listen = moq_tokio_listen_Config_default(); + moq_tokio_listen_Config_bind_set(listen, (moq_option_tokio_listen_Bind){.some = true, .value = bind}); + moq_option_tokio_listen_Bind bound = moq_tokio_listen_Config_bind_get(listen); + CHECK(bound.some && moq_tokio_listen_Bind_eq(&bound.value, &bind)); + moq_tokio_listen_Bind_free(&bound.value); /* The preferred address of each family, which takes only that family. */ moq_socket_addr preferred = {.ip = {10, 0, 0, 1}, .port = 4443}; - moq_listen_config_set_preferred_v4(listen, (moq_option_socket_addr){.some = true, .value = preferred}); - moq_option_socket_addr preferred_v4 = moq_listen_config_preferred_v4(listen); + moq_tokio_listen_Config_preferred_v4_set(listen, (moq_option_socket_addr){.some = true, .value = preferred}); + moq_option_socket_addr preferred_v4 = moq_tokio_listen_Config_preferred_v4_get(listen); CHECK(preferred_v4.some && !preferred_v4.value.v6 && preferred_v4.value.ip[0] == 10 && preferred_v4.value.port == 4443); - CHECK(!moq_listen_config_preferred_v6(listen).some); - moq_listen_config_free(listen); + CHECK(!moq_tokio_listen_Config_preferred_v6_get(listen).some); + moq_tokio_listen_Config_free(listen); /* A constant slice, as an array of clones. */ - moq_vec_string alpns_all = moq_alpns(); + moq_vec_string alpns_all = moq_ALPNS(); CHECK(alpns_all.len > 0); moq_vec_string_free(alpns_all); /* A session from C: a server binds QUIC on port 0, reads back the port it got, and * serves an origin with one call, and a client dials it with one call and reads a frame * back. Neither has a runtime of C's, so moq-tokio's fallback runs both. */ - moq_origin_producer *served = moq_origin_spawn(); - moq_origin_publication *live = NULL; - CHECK(moq_origin_producer_create_broadcast(served, str("live"), &live, &err)); - moq_origin_route *route = moq_origin_route_default(); - CHECK(moq_origin_publication_announce(live, route, &err)); - moq_origin_route_free(route); - moq_broadcast_producer *live_broadcast = moq_origin_publication_broadcast(live); - moq_track_producer *live_track = NULL; - CHECK(moq_broadcast_producer_create_track(live_broadcast, str("video"), NULL, &live_track, &err)); - moq_group_producer *live_group = NULL; - CHECK(moq_track_producer_append_group(live_track, &live_group, &err)); - CHECK(moq_group_producer_write_frame(live_group, NULL, str("hello"), &err)); - CHECK(moq_group_producer_finish(live_group, &err)); - moq_group_producer_free(live_group); - - moq_server_config *server_config = moq_server_config_default(); - moq_listen_config *listen_config = moq_server_config_listen(server_config); - moq_listen_bind any = {.tag = MOQ_LISTEN_BIND_ADDR, .addr = {._0 = {.port = 0, .v6 = true}}}; - moq_listen_config_set_bind(listen_config, (moq_option_listen_bind){.some = true, .value = any}); - moq_tls_listen *tls_listen = moq_tls_listen_default(); + moq_origin_Producer *served = moq_tokio_origin_spawn(); + moq_origin_Publication *live = NULL; + CHECK(moq_origin_Producer_create_broadcast(served, str("live"), &live, &err)); + moq_origin_Route *route = moq_origin_Route_default(); + CHECK(moq_origin_Publication_announce(live, route, &err)); + moq_origin_Route_free(route); + moq_broadcast_Producer *live_broadcast = moq_origin_Publication_broadcast(live); + moq_track_Producer *live_track = NULL; + CHECK(moq_broadcast_Producer_create_track(live_broadcast, str("video"), NULL, &live_track, &err)); + moq_group_Producer *live_group = NULL; + CHECK(moq_track_Producer_append_group(live_track, &live_group, &err)); + CHECK(moq_group_Producer_write_frame(live_group, NULL, str("hello"), &err)); + CHECK(moq_group_Producer_finish(live_group, &err)); + moq_group_Producer_free(live_group); + + moq_tokio_server_Config *server_config = moq_tokio_server_Config_default(); + moq_tokio_listen_Config *listen_config = moq_tokio_server_Config_listen_get(server_config); + moq_tokio_listen_Bind any = {.tag = MOQ_TOKIO_LISTEN_BIND_ADDR, .addr = {._0 = {.port = 0, .v6 = true}}}; + moq_tokio_listen_Config_bind_set(listen_config, (moq_option_tokio_listen_Bind){.some = true, .value = any}); + moq_tokio_tls_Listen *tls_listen = moq_tokio_tls_Listen_default(); moq_buf names[] = {str("localhost")}; - moq_tls_listen_set_generate(tls_listen, (moq_vec_string){.ptr = names, .len = 1}); - moq_listen_config_set_tls(listen_config, tls_listen); - moq_tls_listen_free(tls_listen); - moq_server_config_set_listen(server_config, listen_config); - moq_listen_config_free(listen_config); - moq_tokio_server *server = NULL; - CHECK(moq_server_config_init(server_config, &server, &tokio_err)); - moq_server_config_free(server_config); + moq_tokio_tls_Listen_generate_set(tls_listen, (moq_vec_string){.ptr = names, .len = 1}); + moq_tokio_listen_Config_tls_set(listen_config, tls_listen); + moq_tokio_tls_Listen_free(tls_listen); + moq_tokio_server_Config_listen_set(server_config, listen_config); + moq_tokio_listen_Config_free(listen_config); + moq_tokio_Server *server = NULL; + CHECK(moq_tokio_server_Config_init(server_config, &server, &tokio_err)); + moq_tokio_server_Config_free(server_config); moq_socket_addr local; - CHECK(moq_tokio_server_local_addr(server, &local, &tokio_err)); + CHECK(moq_tokio_Server_local_addr(server, &local, &tokio_err)); CHECK(local.v6 && local.port != 0); - moq_origin_consumer *published = moq_origin_producer_consume(served); - serving = moq_tokio_server_serve_publish(server, published); - moq_tokio_server_free(server); - moq_origin_consumer_free(published); + moq_origin_Consumer *published = moq_origin_Producer_consume(served); + serving = moq_tokio_Server_serve_publish(server, published); + moq_tokio_Server_free(server); + moq_origin_Consumer_free(published); char address[32]; snprintf(address, sizeof address, "moqt://localhost:%u", (unsigned)local.port); - moq_connect_config *dialing = moq_connect_config_default(); - CHECK(moq_url_parse(str(address), &url, &parse_err)); - moq_connect_config_set_url(dialing, url); - moq_url_free(url); - tls = moq_connect_config_tls(dialing); - moq_tls_connect_set_insecure(tls, (moq_option_bool){.some = true, .value = true}); - moq_connect_config_set_tls(dialing, tls); - moq_tls_connect_free(tls); - moq_client_config *session_client = moq_client_config_default(); - moq_client_config_set_connect(session_client, dialing); - moq_connect_config_free(dialing); - moq_tokio_client *dialer = NULL; - CHECK(moq_client_config_init(session_client, &dialer, &tokio_err)); - moq_client_config_free(session_client); - moq_tokio_client *dialer_copy = moq_tokio_client_clone(dialer); - moq_tokio_client *once = moq_tokio_client_with_reconnect(dialer_copy, false); - moq_tokio_client_free(dialer_copy); - moq_origin_producer *received = moq_origin_spawn(); - moq_origin_consumer *remote = moq_origin_producer_consume(received); - moq_connection *connection = moq_tokio_client_consume(once, received); - moq_tokio_client_free(once); - moq_origin_producer_free(received); + moq_tokio_connect_Config *dialing = moq_tokio_connect_Config_default(); + CHECK(moq_Url_parse(str(address), &url, &parse_err)); + moq_tokio_connect_Config_url_set(dialing, url); + moq_Url_free(url); + tls = moq_tokio_connect_Config_tls_get(dialing); + moq_tokio_tls_Connect_insecure_set(tls, (moq_option_bool){.some = true, .value = true}); + moq_tokio_connect_Config_tls_set(dialing, tls); + moq_tokio_tls_Connect_free(tls); + moq_tokio_client_Config *session_client = moq_tokio_client_Config_default(); + moq_tokio_client_Config_connect_set(session_client, dialing); + moq_tokio_connect_Config_free(dialing); + moq_tokio_Client *dialer = NULL; + CHECK(moq_tokio_client_Config_init(session_client, &dialer, &tokio_err)); + moq_tokio_client_Config_free(session_client); + moq_tokio_Client *dialer_copy = moq_tokio_Client_clone(dialer); + moq_tokio_Client *once = moq_tokio_Client_with_reconnect(dialer_copy, false); + moq_tokio_Client_free(dialer_copy); + moq_origin_Producer *received = moq_tokio_origin_spawn(); + moq_origin_Consumer *remote = moq_origin_Producer_consume(received); + moq_tokio_Connection *connection = moq_tokio_Client_consume(once, received); + moq_tokio_Client_free(once); + moq_origin_Producer_free(received); CHECK(connection != NULL); - moq_connection_established_future *establishing = moq_connection_established(connection); - moq_connection_free(connection); - moq_connection *established = NULL; - AWAIT(moq_connection_established_poll(establishing, session_waker, &established, &tokio_err)); - moq_connection_established_future_free(establishing); + moq_tokio_Connection_established_future *establishing = moq_tokio_Connection_established(connection); + moq_tokio_Connection_free(connection); + moq_tokio_Connection *established = NULL; + AWAIT(moq_tokio_Connection_established_poll(establishing, session_waker, &established, &tokio_err)); + moq_tokio_Connection_established_future_free(establishing); CHECK(established != NULL); - moq_origin_consumer_request_future *requesting = moq_origin_consumer_request(remote, str("live"), NULL); - moq_broadcast_consumer *remote_broadcast = NULL; - AWAIT(moq_origin_consumer_request_poll(requesting, session_waker, &remote_broadcast, &err)); - moq_origin_consumer_request_future_free(requesting); + moq_origin_Consumer_request_future *requesting = moq_origin_Consumer_request(remote, str("live"), NULL); + moq_broadcast_Consumer *remote_broadcast = NULL; + AWAIT(moq_origin_Consumer_request_poll(requesting, session_waker, &remote_broadcast, &err)); + moq_origin_Consumer_request_future_free(requesting); CHECK(remote_broadcast != NULL); - moq_track_consumer *remote_track = NULL; - CHECK(moq_broadcast_consumer_track(remote_broadcast, str("video"), &remote_track, &err)); - subscribing = moq_track_consumer_subscribe(remote_track, NULL); - AWAIT(moq_track_consumer_subscribe_poll(subscribing, session_waker, &subscriber, &err)); - moq_track_consumer_subscribe_future_free(subscribing); + moq_track_Consumer *remote_track = NULL; + CHECK(moq_broadcast_Consumer_track(remote_broadcast, str("video"), &remote_track, &err)); + subscribing = moq_track_Consumer_subscribe(remote_track, NULL); + AWAIT(moq_track_Consumer_subscribe_poll(subscribing, session_waker, &subscriber, &err)); + moq_track_Consumer_subscribe_future_free(subscribing); CHECK(subscriber != NULL); - receiving = moq_track_subscriber_recv_group(subscriber); + receiving = moq_track_Subscriber_recv_group(subscriber); group = NULL; - AWAIT(moq_track_subscriber_recv_group_poll(receiving, session_waker, &group, &err)); - moq_track_subscriber_recv_group_future_free(receiving); + AWAIT(moq_track_Subscriber_recv_group_poll(receiving, session_waker, &group, &err)); + moq_track_Subscriber_recv_group_future_free(receiving); CHECK(group != NULL); - reading = moq_group_consumer_read_frame(group); + reading = moq_group_Consumer_read_frame(group); frame = NULL; - AWAIT(moq_group_consumer_read_frame_poll(reading, session_waker, &frame, &err)); - moq_group_consumer_read_frame_future_free(reading); - CHECK(frame != NULL && is(moq_frame_frame_payload(frame), "hello")); - moq_frame_frame_free(frame); - moq_group_consumer_free(group); - moq_track_subscriber_free(subscriber); - moq_track_consumer_free(remote_track); - moq_broadcast_consumer_free(remote_broadcast); - - moq_connection_close_future *closing = moq_connection_close(established); - moq_connection_free(established); - AWAIT(moq_connection_close_poll(closing, session_waker, &tokio_err)); - moq_connection_close_future_free(closing); - moq_tokio_client_close_future *client_closing = moq_tokio_client_close(dialer); - moq_tokio_client_free(dialer); - AWAIT(moq_tokio_client_close_poll(client_closing, session_waker)); - moq_tokio_client_close_future_free(client_closing); - moq_tokio_server_serve_publish_future_free(serving); - moq_origin_consumer_free(remote); - moq_track_producer_free(live_track); - moq_broadcast_producer_free(live_broadcast); - moq_origin_publication_free(live); - moq_origin_producer_free(served); + AWAIT(moq_group_Consumer_read_frame_poll(reading, session_waker, &frame, &err)); + moq_group_Consumer_read_frame_future_free(reading); + CHECK(frame != NULL && is(moq_frame_Frame_payload_get(frame), "hello")); + moq_frame_Frame_free(frame); + moq_group_Consumer_free(group); + moq_track_Subscriber_free(subscriber); + moq_track_Consumer_free(remote_track); + moq_broadcast_Consumer_free(remote_broadcast); + + moq_tokio_Connection_close_future *closing = moq_tokio_Connection_close(established); + moq_tokio_Connection_free(established); + AWAIT(moq_tokio_Connection_close_poll(closing, session_waker, &tokio_err)); + moq_tokio_Connection_close_future_free(closing); + moq_tokio_Client_close_future *client_closing = moq_tokio_Client_close(dialer); + moq_tokio_Client_free(dialer); + AWAIT(moq_tokio_Client_close_poll(client_closing, session_waker)); + moq_tokio_Client_close_future_free(client_closing); + moq_tokio_Server_serve_publish_future_free(serving); + moq_origin_Consumer_free(remote); + moq_track_Producer_free(live_track); + moq_broadcast_Producer_free(live_broadcast); + moq_origin_Publication_free(live); + moq_origin_Producer_free(served); CHECK(tokio_err == NULL); if (failures == 0) { diff --git a/ffi/moq/moq.cpp b/ffi/moq/moq.cpp index 15be5ab3a..75cc993d8 100644 --- a/ffi/moq/moq.cpp +++ b/ffi/moq/moq.cpp @@ -122,7 +122,7 @@ class watchdog { int main() { moq::waker waker([]() { wakes++; }); - moq::broadcast_producer broadcast = moq::broadcast_info::new_().produce(); + moq::broadcast_Producer broadcast = moq::broadcast_Info::new_().produce(); auto track = broadcast.create_track("video", std::nullopt); CHECK(track && is(track->name(), "video")); @@ -130,10 +130,10 @@ int main() { auto duplicate = broadcast.create_track("video", std::nullopt); CHECK(!duplicate && is(duplicate.error().to_string(), "duplicate")); - moq::track_consumer consumer = track->consume(); + moq::track_Consumer consumer = track->consume(); auto subscribed = consumer.subscribe(std::nullopt).poll(waker); CHECK(subscribed && *subscribed); - moq::track_subscriber subscriber = std::move(**subscribed); + moq::track_Subscriber subscriber = std::move(**subscribed); // A group's arrival wakes the subscriber's pending receive. auto receiving = subscriber.recv_group(); @@ -142,7 +142,7 @@ int main() { CHECK(writer && wakes == 1); auto received = receiving.poll(waker); CHECK(received && *received && received->value()); - moq::group_consumer group = std::move(*received->value()); + moq::group_Consumer group = std::move(*received->value()); CHECK(group.index() == 0); // So does a frame's. @@ -153,16 +153,16 @@ int main() { CHECK(wakes == 2); auto frame = reading.poll(waker); CHECK(frame && *frame && frame->value()); - const moq::frame_frame &hello = *frame->value(); - CHECK(is(hello.payload(), "hello")); - CHECK(!hello.timestamp()); + const moq::frame_Frame &hello = *frame->value(); + CHECK(is(hello.payload_get(), "hello")); + CHECK(!hello.timestamp_get()); // When it arrived: kio's clock's `Instant`, from kio's own export. CHECK(moq::clock_now().checked_duration_since(hello.arrived()).has_value()); } // A timestamp is a `Copy` value behind a handle: passing it copies it out. The track is // untimed, so a timestamped frame is refused. - auto at = moq::timestamp::from_millis(1500); + auto at = moq::Timestamp::from_millis(1500); CHECK(at && at->as_secs() == 1); auto late = writer->write_frame(&*at, "late"); CHECK(!late && is(late.error().to_string(), "frame timestamp doesn't fit its track")); @@ -173,27 +173,27 @@ int main() { CHECK(writer->write_frame(nullptr, "\xffworld")); CHECK(std::move(*writer).finish()); auto world = group.read_frame().get(); - CHECK(world && world.value() && is(world.value()->payload(), "\xffworld")); + CHECK(world && world.value() && is(world.value()->payload_get(), "\xffworld")); auto end = group.read_frame().get(); CHECK(end && !end.value()); // An origin and the driver that runs it, a tuple. Its root, which it lends, is returned // as a new handle to a clone. - auto [origin, driver] = moq::origin_producer::new_(moq::origin_config::default_()); + auto [origin, driver] = moq::origin_Producer::new_(moq::origin_Config::default_()); CHECK(is(origin.root().as_str(), "")); // Scoped beneath a root to patterns, which moq-pattern's own export builds. { - auto alice = moq::pattern::subtree("alice"); + auto alice = moq::Pattern::subtree("alice"); CHECK(alice); - moq::patterns patterns = moq::patterns::new_(); + moq::Patterns patterns = moq::Patterns::new_(); CHECK(patterns.insert(*alice)); auto scoped = origin.scope("room", patterns); CHECK(scoped); CHECK(is(scoped->root().as_str(), "room")); auto absolute = scoped->absolute("alice"); CHECK(absolute && is(absolute->as_str(), "room/alice")); - moq::patterns allowed = scoped->allowed(); + moq::Patterns allowed = scoped->allowed(); CHECK(allowed.matches("alice/cam")); CHECK(!allowed.matches("bob")); absolute = scoped->consume().absolute("bob"); @@ -204,27 +204,27 @@ int main() { { auto publication = origin.create_broadcast("room/alice"); CHECK(publication); - moq::broadcast_producer producer = publication->broadcast(); - moq::path path = producer.info().path(); + moq::broadcast_Producer producer = publication->broadcast(); + moq::Path path = producer.info().path_get(); CHECK(is(path.as_str(), "room/alice")); // A path's clone is a value no call tells apart, so its copies share one handle, // while a producer's clone counts producers, so it crosses into Rust. - moq::path shared = path; + moq::Path shared = path; CHECK(shared == path); - moq::broadcast_producer cloned = producer.clone(); - CHECK(is(cloned.info().path().as_str(), "room/alice")); + moq::broadcast_Producer cloned = producer.clone(); + CHECK(is(cloned.info().path_get().as_str(), "room/alice")); // Paths own their segments, so C++ builds them, and relative ones, from a string. - auto empty = moq::path::new_("/cam/"); - CHECK(!empty && empty.error() == moq::path_error::empty_segment); - auto cam = moq::path::new_("cam"); + auto empty = moq::Path::new_("/cam/"); + CHECK(!empty && empty.error() == moq::path_Error::empty_segment); + auto cam = moq::Path::new_("cam"); CHECK(cam && is(cam->as_str(), "cam")); auto joined = path.join(*cam); CHECK(joined && is(joined->as_str(), "room/alice/cam")); - std::optional stripped = joined->strip_prefix(path); + std::optional stripped = joined->strip_prefix(path); CHECK(stripped && *stripped == *cam); CHECK(!path.strip_prefix(*joined)); - auto up = moq::relative::new_("../bob"); + auto up = moq::path_Relative::new_("../bob"); CHECK(up); auto resolved = joined->resolve(*up); CHECK(resolved && is(resolved->as_str(), "room/bob")); @@ -233,17 +233,17 @@ int main() { // The driver, polled as a language's event loop would: at the time, and with a waiter // made from the waker and held across polls, since what a poll registers lives only as // long as its waiter. It runs until every owner of the origin is gone. - moq::waiter waiter = moq::waiter::new_(waker); + moq::Waiter waiter = moq::Waiter::new_(waker); CHECK(driver.poll(moq::clock_now(), waiter)); { // A client publishes the origin through moq-net's generic `with_publisher`, at each // type that implements its bound, by reference, so C++ keeps its handle. - moq::client publishing = moq::client::new_().with_publisher_producer(origin); + moq::Client publishing = moq::Client::new_().with_publisher_Producer(origin); (void)publishing; } int before = wakes; { - moq::origin_producer last = std::move(origin); + moq::origin_Producer last = std::move(origin); } // The last owner's drop wakes the driver, which then finishes, cleanly. CHECK(wakes > before); @@ -253,40 +253,40 @@ int main() { // moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the // catalog lists at the decoder's 48 kHz, keeping the head as its description. static const std::uint8_t head[] = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd', 1, 2, 0x38, 0x01, 0x44, 0xac, 0, 0, 0, 0, 0}; - auto audio = moq::codec_opus_audio_config(moq::view(head, sizeof head)); + auto audio = moq::mux_codec_opus_config(moq::view(head, sizeof head)); CHECK(audio); - CHECK(audio->sample_rate() == 48000); - CHECK(audio->channel_count() == 2); - CHECK(std::holds_alternative(audio->codec())); - auto description = audio->description(); + CHECK(audio->sample_rate_get() == 48000); + CHECK(audio->channel_count_get() == 2); + CHECK(std::holds_alternative(audio->codec_get())); + auto description = audio->description_get(); CHECK(description && description->size() == sizeof head && std::memcmp(description->data(), head, sizeof head) == 0); // A truncated head is moq-mux's error. - auto truncated = moq::codec_opus_audio_config(moq::view(head, 8)); + auto truncated = moq::mux_codec_opus_config(moq::view(head, 8)); CHECK(!truncated && !truncated.error().to_string().empty()); // A subscription's ranges, from any iterable of them: a vector. { - moq::track_range live = moq::track_range::live(); - moq::track_subscription ranged = moq::track_subscription::default_().with_ranges( - {live, moq::track_range::groups({{MOQ_BOUND_INCLUDED, 2}, {MOQ_BOUND_EXCLUDED, 6}})}); - std::vector got = ranged.ranges(); + moq::track_Range live = moq::track_Range::live(); + moq::track_Subscription ranged = moq::track_Subscription::default_().with_ranges( + {live, moq::track_Range::groups({{MOQ_BOUND_INCLUDED, 2}, {MOQ_BOUND_EXCLUDED, 6}})}); + std::vector got = ranged.ranges_get(); CHECK(got.size() == 2 && got[0] == live); - CHECK(got.size() == 2 && std::get(got[1].start)._0 == 2); - CHECK(got.size() == 2 && got[1].end && std::get(*got[1].end)._0 == 6); + CHECK(got.size() == 2 && std::get(got[1].start)._0 == 2); + CHECK(got.size() == 2 && got[1].end && std::get(*got[1].end)._0 == 6); } // A catalog at moq-mux's untyped extension, from the config C++ builds, whose generic // `is_claimed` is named by the last segment of its argument, and whose `mutate` lends // the catalog to C++. { - moq::broadcast_producer publisher = moq::broadcast_info::new_().produce(); - moq::catalog_config_extra extra = - moq::catalog_config::default_().with_catalog_extra(moq::hang_catalog_extra::default_()); - auto catalog = moq::catalog_producer_extra::new_(publisher, std::move(extra)); + moq::broadcast_Producer publisher = moq::broadcast_Info::new_().produce(); + moq::mux_catalog_Config_Extra extra = + moq::mux_catalog_Config::default_().with_catalog_Extra(moq::hang_Catalog_Extra::default_()); + auto catalog = moq::mux_catalog_Producer_Extra::new_(publisher, std::move(extra)); CHECK(catalog); - CHECK(!catalog->is_claimed_binary_config("state")); + CHECK(!catalog->is_claimed_BinaryConfig("state")); int read_catalog = 0; - CHECK(catalog->mutate([&](moq::hang_catalog_extra &lent) { + CHECK(catalog->mutate([&](moq::hang_Catalog_Extra &lent) { auto json = lent.to_json(); CHECK(json); if (json && json.value().str().substr(0, 1) == "{") { @@ -299,82 +299,82 @@ int main() { // `json_snapshot` at a JSON document, and a section C++ parses from text, set in the // lent catalog and read back as text. // Its track has a timescale, since every write is stamped. - auto milli = moq::timescale::milli(); - auto status = publisher.create_track("status", moq::track_info::default_().with_timescale(&milli)); + auto milli = moq::Timescale::MILLI(); + auto status = publisher.create_track("status", moq::track_Info::default_().with_timescale(&milli)); CHECK(status); - auto status_snapshot = catalog->json_snapshot_config(std::move(*status), moq::json_config::default_(), - moq::json_options::default_()); + auto status_snapshot = catalog->json_snapshot_Config(std::move(*status), moq::mux_json_Config::default_(), + moq::mux_json_Options::default_()); CHECK(status_snapshot && is(status_snapshot->name(), "status")); - CHECK(catalog->is_claimed_json_config("status")); + CHECK(catalog->is_claimed_JsonConfig("status")); // A value C++ parses is published as the track's latest, which a subscriber reads // back as JSON text. - CHECK(status_snapshot->update(moq::json::parse(R"({"on": true})").value(), moq::timestamp::now())); - moq::track_subscriber status_subscriber = status_snapshot->consume(); + CHECK(status_snapshot->update(moq::Json::parse(R"({"on": true})").value(), moq::Timestamp::now())); + moq::track_Subscriber status_subscriber = status_snapshot->consume(); auto status_group = status_subscriber.recv_group().get(); CHECK(status_group && status_group.value()); if (status_group && status_group.value()) { auto status_frame = status_group.value()->read_frame().get(); - CHECK(status_frame && status_frame.value() && is(status_frame.value()->payload(), R"({"on":true})")); + CHECK(status_frame && status_frame.value() && is(status_frame.value()->payload_get(), R"({"on":true})")); } - auto app = moq::json::parse(R"({"v": [1, true]})"); + auto app = moq::Json::parse(R"({"v": [1, true]})"); CHECK(app); - CHECK(catalog->mutate([&](moq::hang_catalog_extra &lent) { - moq::catalog_hang_extra sections = lent.ext(); + CHECK(catalog->mutate([&](moq::hang_Catalog_Extra &lent) { + moq::mux_catalog_hang_Extra sections = lent.ext_get(); CHECK(sections.set("app", std::move(*app))); - lent.set_ext(std::move(sections)); + lent.ext_set(std::move(sections)); })); - CHECK(catalog->mutate([&](moq::hang_catalog_extra &lent) { + CHECK(catalog->mutate([&](moq::hang_Catalog_Extra &lent) { auto json = lent.to_json(); CHECK(json && json.value().str().find("\"status\"") != std::string_view::npos); - auto section = lent.ext().get("app"); + auto section = lent.ext_get().get("app"); CHECK(section && is(section->to_text(), R"({"v":[1,true]})")); })); // Malformed text fails to parse, and a reserved section is moq-mux's error. - auto broken = moq::json::parse(R"({"v":)"); + auto broken = moq::Json::parse(R"({"v":)"); CHECK(!broken && is(broken.error(), "EOF while parsing a value at line 1 column 5")); - moq::catalog_hang_extra sections = moq::catalog_hang_extra::default_(); - auto reserved = sections.set("json", moq::json::parse("1").value()); + moq::mux_catalog_hang_Extra sections = moq::mux_catalog_hang_Extra::default_(); + auto reserved = sections.set("json", moq::Json::parse("1").value()); CHECK(!reserved && is(reserved.error().to_string(), "reserved catalog section: json")); } // A session's config, filled in as Rust assigns it, and a nested field by getting a copy, // editing it, and setting it back. { - moq::connect_config connect = moq::connect_config::default_(); - moq::tls_connect tls = connect.tls(); - tls.set_insecure(true); - tls.set_host_name("relay.example"); - tls.set_fingerprint({"ab:cd"}); - connect.set_tls(std::move(tls)); - connect.set_implementation("moq.cpp"); - connect.set_url(moq::url::parse("https://relay.example:4443/demo").value()); - CHECK(connect.tls().insecure() == std::optional(true)); - CHECK(is(*connect.tls().host_name(), "relay.example")); - auto fingerprints = connect.tls().fingerprint(); + moq::tokio_connect_Config connect = moq::tokio_connect_Config::default_(); + moq::tokio_tls_Connect tls = connect.tls_get(); + tls.insecure_set(true); + tls.host_name_set("relay.example"); + tls.fingerprint_set({"ab:cd"}); + connect.tls_set(std::move(tls)); + connect.implementation_set("moq.cpp"); + connect.url_set(moq::Url::parse("https://relay.example:4443/demo").value()); + CHECK(connect.tls_get().insecure_get() == std::optional(true)); + CHECK(is(*connect.tls_get().host_name_get(), "relay.example")); + auto fingerprints = connect.tls_get().fingerprint_get(); CHECK(fingerprints.size() == 1 && is(fingerprints[0], "ab:cd")); - CHECK(is(*connect.implementation(), "moq.cpp")); - CHECK(is(connect.url()->to_string(), "https://relay.example:4443/demo")); + CHECK(is(*connect.implementation_get(), "moq.cpp")); + CHECK(is(connect.url_get()->to_string(), "https://relay.example:4443/demo")); // A listener's bind, a socket address. moq::socket_addr addr{}; addr.ip[15] = 1; addr.port = 4443; addr.v6 = true; - moq::listen_config listen = moq::listen_config::default_(); - listen.set_bind(moq::listen_bind(moq::listen_bind_addr{addr})); - auto bind = listen.bind(); - CHECK(bind && is(moq::listen_bind_to_string(*bind), "[::1]:4443")); + moq::tokio_listen_Config listen = moq::tokio_listen_Config::default_(); + listen.bind_set(moq::tokio_listen_Bind(moq::tokio_listen_Bind_Addr{addr})); + auto bind = listen.bind_get(); + CHECK(bind && is(moq::tokio_listen_Bind_to_string(*bind), "[::1]:4443")); // The preferred address of each family, which takes only that family. - listen.set_preferred_v6(addr); - auto preferred = listen.preferred_v6(); - CHECK(preferred && preferred->v6 && preferred->port == 4443 && !listen.preferred_v4()); + listen.preferred_v6_set(addr); + auto preferred = listen.preferred_v6_get(); + CHECK(preferred && preferred->v6 && preferred->port == 4443 && !listen.preferred_v4_get()); // A constant slice, as a vector of clones. - CHECK(!moq::alpns().empty()); + CHECK(!moq::ALPNS().empty()); } // moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. - CHECK(moq::connect_error_is_auth(moq::connect_error::unauthorized)); - CHECK(is(moq::connect_error_to_string(moq::connect_error::unauthorized), "unauthorized")); + CHECK(moq::tokio_ConnectError_is_auth(moq::tokio_ConnectError::unauthorized)); + CHECK(is(moq::tokio_ConnectError_to_string(moq::tokio_ConnectError::unauthorized), "unauthorized")); // A session, as moq.c runs it: a server bound to port 0 reads back the port it got and // serves with one call, on an executor of its own, and a client dials it with one call @@ -382,25 +382,25 @@ int main() { // it from its threads. { watchdog limit(std::chrono::seconds(30)); - moq::origin_producer served = moq::origin_spawn(); + moq::origin_Producer served = moq::tokio_origin_spawn(); auto live = served.create_broadcast("live"); - CHECK(live && live->announce(moq::origin_route::default_())); - moq::broadcast_producer live_broadcast = live->broadcast(); + CHECK(live && live->announce(moq::origin_Route::default_())); + moq::broadcast_Producer live_broadcast = live->broadcast(); auto live_track = live_broadcast.create_track("video", std::nullopt); CHECK(live_track); auto live_group = live_track->append_group(); CHECK(live_group && live_group->write_frame(nullptr, "hello")); CHECK(std::move(*live_group).finish()); - moq::server_config server_config = moq::server_config::default_(); - moq::listen_config listen = server_config.listen(); + moq::tokio_server_Config server_config = moq::tokio_server_Config::default_(); + moq::tokio_listen_Config listen = server_config.listen_get(); moq::socket_addr any{}; any.v6 = true; - listen.set_bind(moq::listen_bind(moq::listen_bind_addr{any})); - moq::tls_listen tls = listen.tls(); - tls.set_generate({"localhost"}); - listen.set_tls(std::move(tls)); - server_config.set_listen(std::move(listen)); + listen.bind_set(moq::tokio_listen_Bind(moq::tokio_listen_Bind_Addr{any})); + moq::tokio_tls_Listen tls = listen.tls_get(); + tls.generate_set({"localhost"}); + listen.tls_set(std::move(tls)); + server_config.listen_set(std::move(listen)); auto server = std::move(server_config).init(); CHECK(server); auto local = server->local_addr(); @@ -410,17 +410,17 @@ int main() { moq::task serving = std::move(*server).serve_publish(served.consume()).then(serving_thread.executor(), [&](auto &&) { stopped = true; }); - moq::connect_config connect = moq::connect_config::default_(); - connect.set_url(moq::url::parse("moqt://localhost:" + std::to_string(local->port)).value()); - moq::tls_connect insecure = connect.tls(); - insecure.set_insecure(true); - connect.set_tls(std::move(insecure)); - moq::client_config client_config = moq::client_config::default_(); - client_config.set_connect(std::move(connect)); + moq::tokio_connect_Config connect = moq::tokio_connect_Config::default_(); + connect.url_set(moq::Url::parse("moqt://localhost:" + std::to_string(local->port)).value()); + moq::tokio_tls_Connect insecure = connect.tls_get(); + insecure.insecure_set(true); + connect.tls_set(std::move(insecure)); + moq::tokio_client_Config client_config = moq::tokio_client_Config::default_(); + client_config.connect_set(std::move(connect)); auto client = std::move(client_config).init(); CHECK(client); - moq::origin_producer incoming = moq::origin_spawn(); - moq::origin_consumer remote = incoming.consume(); + moq::origin_Producer incoming = moq::tokio_origin_spawn(); + moq::origin_Consumer remote = incoming.consume(); auto connection = client->clone().with_reconnect(false).consume(std::move(incoming)); CHECK(connection); auto established = std::move(*connection).established().get(); @@ -435,7 +435,7 @@ int main() { auto remote_group = remote_subscriber->recv_group().get(); CHECK(remote_group && remote_group.value()); auto remote_frame = remote_group.value()->read_frame().get(); - CHECK(remote_frame && remote_frame.value() && is(remote_frame.value()->payload(), "hello")); + CHECK(remote_frame && remote_frame.value() && is(remote_frame.value()->payload_get(), "hello")); CHECK(std::move(*established).close().get()); std::move(*client).close().get(); diff --git a/ffi/moq/moq.dart b/ffi/moq/moq.dart index 745067e15..cd00edf57 100644 --- a/ffi/moq/moq.dart +++ b/ffi/moq/moq.dart @@ -111,8 +111,8 @@ Future local() async { expect(!ready, 'a read waits for a frame'); writer.writeFrame(null, utf8.encode('hello')); final hello = (await read.timeout(const Duration(seconds: 5)))!; - expect(utf8.decode(hello.payload) == 'hello', 'a frame has its payload'); - expect(hello.timestamp == null, 'an untimed frame has no timestamp'); + expect(utf8.decode(hello.payloadGet) == 'hello', 'a frame has its payload'); + expect(hello.timestampGet == null, 'an untimed frame has no timestamp'); // When it arrived: kio's clock's `Instant`, from kio's own export. expect(moq.clockNow().checkedDurationSince(hello.arrived()) != null, 'a frame arrived before now'); @@ -132,7 +132,7 @@ Future local() async { writer.writeFrame(null, Uint8List.fromList(world)); writer.finish(); final last = await first.readFrame(); - expect(same(last?.payload, world), 'the last frame reads'); + expect(same(last?.payloadGet, world), 'the last frame reads'); expect(await first.readFrame() == null, 'a finished group reads null'); } @@ -161,7 +161,7 @@ Future origin() async { // A broadcast the origin creates is stamped with its path, which its info lends. final publication = origin.createBroadcast('room/alice'); final created = publication.broadcast(); - final path = created.info().path; + final path = created.info().pathGet; created.close(); publication.close(); expect(path.asStr() == 'room/alice', 'a broadcast has its path'); @@ -178,7 +178,7 @@ Future origin() async { expect(joined.asStr() == 'room/alice/cam', 'paths join'); expect(joined.stripPrefix(path) == cam, 'a path strips its prefix'); expect(path.stripPrefix(joined) == null, 'a path strips no longer prefix'); - expect(joined.resolve(moq.Relative('../bob')).asStr() == 'room/bob', 'a relative path resolves'); + expect(joined.resolve(moq.PathRelative('../bob')).asStr() == 'room/bob', 'a relative path resolves'); // The driver, polled from the event loop until every owner of the origin is gone. final driving = drive(driver); @@ -199,14 +199,14 @@ Future catalog() async { // moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the // catalog lists at the decoder's 48 kHz, keeping the head as its description. final head = Uint8List.fromList([...utf8.encode('OpusHead'), 1, 2, 0x38, 0x01, 0x44, 0xac, 0, 0, 0, 0, 0]); - final audio = moq.codecOpusAudioConfig(head); - expect(audio.sampleRate == 48000, 'Opus decodes at 48 kHz'); - expect(audio.channelCount == 2, 'the head is stereo'); - expect(audio.codec == const moq.CatalogAudioCodecOpus(), 'the codec is Opus'); - expect(same(audio.description, head), 'the head is the description'); + final audio = moq.muxCodecOpusConfig(head); + expect(audio.sampleRateGet == 48000, 'Opus decodes at 48 kHz'); + expect(audio.channelCountGet == 2, 'the head is stereo'); + expect(audio.codecGet == const moq.HangCatalogAudioCodecOpus(), 'the codec is Opus'); + expect(same(audio.descriptionGet, head), 'the head is the description'); // A truncated head is moq-mux's error. throws( - () => moq.codecOpusAudioConfig(Uint8List.sublistView(head, 0, 8)), + () => moq.muxCodecOpusConfig(Uint8List.sublistView(head, 0, 8)), (e) => '$e'.isNotEmpty, "a truncated head's error has a message", ); @@ -214,7 +214,7 @@ Future catalog() async { // A subscription's ranges, from any iterable of them: a list. final groups = moq.TrackRange.groups(const moq.BoundsU64(moq.BoundU64Included(2), moq.BoundU64Excluded(6))); final ranges = [moq.TrackRange.live(), groups]; - final ranged = moq.TrackSubscription.default_().withRanges(ranges).ranges; + final ranged = moq.TrackSubscription.default_().withRanges(ranges).rangesGet; expect(ranged.length == 2 && ranged[0] == ranges[0] && ranged[1] == ranges[1], 'a subscription keeps its ranges'); expect(groups.start == const moq.TrackAnchorAbsolute(2), 'groups start at their first'); expect(groups.end == const moq.TrackAnchorAbsolute(6), 'groups end before their end'); @@ -223,8 +223,8 @@ Future catalog() async { // `is_claimed` is named by the last segment of its argument, and whose `mutate` lends // the catalog to Dart. final publisher = moq.BroadcastInfo().produce(); - final extra = moq.CatalogConfig.default_().withCatalogExtra(moq.HangCatalogExtra.default_()); - final producer = moq.CatalogProducerExtra(publisher, extra); + final extra = moq.MuxCatalogConfig.default_().withCatalogExtra(moq.HangCatalogExtra.default_()); + final producer = moq.MuxCatalogProducerExtra(publisher, extra); expect(!producer.isClaimedBinaryConfig('state'), 'an unclaimed name is not claimed'); var read = 0; producer.mutate((lent) { @@ -236,9 +236,9 @@ Future catalog() async { // a JSON document, and a section Dart parses from text, set in the lent catalog and read // back as text. final status = producer.jsonSnapshotConfig( - publisher.createTrack('status', moq.TrackInfo.default_().withTimescale(moq.Timescale.milli)), - moq.JsonConfig.default_(), - moq.JsonOptions.default_(), + publisher.createTrack('status', moq.TrackInfo.default_().withTimescale(moq.Timescale.MILLI)), + moq.MuxJsonConfig.default_(), + moq.MuxJsonOptions.default_(), ); expect(status.name() == 'status', "a JSON snapshot has its track's name"); expect(producer.isClaimedJsonConfig('status'), 'the JSON snapshot claims its name'); @@ -248,16 +248,16 @@ Future catalog() async { status.update(on, moq.Timestamp.now()); on.close(); final latest = await (await status.consume().recvGroup())?.readFrame(); - expect(latest != null && utf8.decode(latest.payload) == '{"on":true}', 'a subscriber reads the latest value'); + expect(latest != null && utf8.decode(latest.payloadGet) == '{"on":true}', 'a subscriber reads the latest value'); producer.mutate((lent) { - final sections = lent.ext; + final sections = lent.extGet; sections.set('app', moq.Json.parse('{"v": [1, true]}')); - lent.setExt(sections); + lent.extSet(sections); }); String? app; producer.mutate((lent) { expect(lent.toJson().contains('"status"'), 'the catalog lists the JSON track'); - app = lent.ext.get('app')?.toText(); + app = lent.extGet.get('app')?.toText(); }); expect(app == '{"v":[1,true]}', 'the catalog reads its section back as text'); // Malformed text fails to parse, and a reserved section is moq-mux's error. @@ -267,7 +267,7 @@ Future catalog() async { "a parse error is serde_json's", ); throws( - () => moq.CatalogHangExtra.default_().set('json', moq.Json.parse('1')), + () => moq.MuxCatalogHangExtra.default_().set('json', moq.Json.parse('1')), (e) => '$e' == 'reserved catalog section: json', "a reserved section is moq-mux's error", ); @@ -275,8 +275,8 @@ Future catalog() async { void config() { // moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. - expect(moq.ConnectError.unauthorized.isAuth(), 'unauthorized is an auth error'); - expect('${moq.ConnectError.unauthorized}' == 'unauthorized', "a connect error prints as Rust's"); + expect(moq.TokioConnectError.unauthorized.isAuth(), 'unauthorized is an auth error'); + expect('${moq.TokioConnectError.unauthorized}' == 'unauthorized', "a connect error prints as Rust's"); // Session config's foreign types. A URL is parsed from text, failing with why. throws( @@ -285,20 +285,20 @@ void config() { "a URL's parse error is Rust's", ); // A session's config, its URL and pinned fingerprints set as Rust assigns them. - final connect = moq.ConnectConfig.default_(); - final tls = connect.tls; - tls.setFingerprint(['ab:cd']); - connect.setTls(tls); - connect.setUrl(moq.Url.parse('https://relay.example:4443/demo')); - final kept = connect.tls; - expect(kept.fingerprint.length == 1 && kept.fingerprint[0] == 'ab:cd', 'a TLS config keeps its fingerprints'); + final connect = moq.TokioConnectConfig.default_(); + final tls = connect.tlsGet; + tls.fingerprintSet(['ab:cd']); + connect.tlsSet(tls); + connect.urlSet(moq.Url.parse('https://relay.example:4443/demo')); + final kept = connect.tlsGet; + expect(kept.fingerprintGet.length == 1 && kept.fingerprintGet[0] == 'ab:cd', 'a TLS config keeps its fingerprints'); kept.close(); - final url = connect.url!; + final url = connect.urlGet!; connect.close(); final versions = moq.Versions.default_(); final tokens = moq.AuthTokens([]); final dial = tokens.dial(url, versions); - final dialed = dial.url; + final dialed = dial.urlGet; expect(dialed == url, 'a dial keeps its URL'); expect('$dialed' == 'https://relay.example:4443/demo', "a URL prints as Rust's"); dialed.close(); @@ -306,27 +306,27 @@ void config() { tokens.close(); url.close(); // A `Vec`, as a list. - expect(moq.alpns.isNotEmpty, 'a constant slice crosses as a list'); + expect(moq.ALPNS.isNotEmpty, 'a constant slice crosses as a list'); final alpns = versions.alpns(); expect(alpns.isNotEmpty && alpns.every((a) => a.isNotEmpty), 'each version has an ALPN'); versions.close(); // A socket address, crossing in a listener's bind. - final bind = moq.ListenBindAddr(moq.SocketAddr(InternetAddress('::1'), 4443)); + final bind = moq.TokioListenBindAddr(moq.SocketAddr(InternetAddress('::1'), 4443)); expect('$bind' == '[::1]:4443', "an IPv6 bind prints as Rust's"); final v4 = moq.SocketAddr(InternetAddress('127.0.0.1'), 0); - expect('${moq.ListenBindAddr(v4)}' == '127.0.0.1:0', "an IPv4 bind prints as Rust's"); - final listen = moq.ListenConfig.default_(); - listen.setBind(bind); + expect('${moq.TokioListenBindAddr(v4)}' == '127.0.0.1:0', "an IPv4 bind prints as Rust's"); + final listen = moq.TokioListenConfig.default_(); + listen.bindSet(bind); // The preferred address of each family, which takes only that family. - listen.setPreferredV4(v4); - expect(listen.preferredV4 == v4 && listen.preferredV6 == null, 'a listener keeps its preferred IPv4 address'); + listen.preferredV4Set(v4); + expect(listen.preferredV4Get == v4 && listen.preferredV6Get == null, 'a listener keeps its preferred IPv4 address'); // A variant without Rust's `PartialEq` compares by identity, so by its field. - final listening = listen.bind; - expect(listening is moq.ListenBindAddr && listening.$0 == bind.$0, "a listener's config keeps its bind"); + final listening = listen.bindGet; + expect(listening is moq.TokioListenBindAddr && listening.$0 == bind.$0, "a listener's config keeps its bind"); listen.close(); // TLS files, as OS paths, and fingerprints, as strings: none by default. - final defaults = moq.TlsConnect.default_(); - expect(defaults.root.isEmpty && defaults.fingerprint.isEmpty, 'a default TLS config lists none'); + final defaults = moq.TokioTlsConnect.default_(); + expect(defaults.rootGet.isEmpty && defaults.fingerprintGet.isEmpty, 'a default TLS config lists none'); defaults.close(); } @@ -335,7 +335,7 @@ void config() { /// reads a frame back, each await completing once moq-tokio's fallback runtime wakes it /// from its threads. Future session() async { - final served = moq.originSpawn(); + final served = moq.tokioOriginSpawn(); final live = served.createBroadcast('live'); live.announce(moq.OriginRoute.default_()); final liveTrack = live.broadcast().createTrack('video', null); @@ -343,27 +343,27 @@ Future session() async { liveGroup.writeFrame(null, utf8.encode('hello')); liveGroup.finish(); - final serverConfig = moq.ServerConfig.default_(); - final listen = serverConfig.listen; - listen.setBind(moq.ListenBindAddr(moq.SocketAddr(InternetAddress('::'), 0))); - final tls = listen.tls; - tls.setGenerate(['localhost']); - listen.setTls(tls); - serverConfig.setListen(listen); + final serverConfig = moq.TokioServerConfig.default_(); + final listen = serverConfig.listenGet; + listen.bindSet(moq.TokioListenBindAddr(moq.SocketAddr(InternetAddress('::'), 0))); + final tls = listen.tlsGet; + tls.generateSet(['localhost']); + listen.tlsSet(tls); + serverConfig.listenSet(listen); final server = serverConfig.init(); final local = server.localAddr(); expect(local.port != 0, 'a server bound to port 0 reads back the port it got'); final serving = server.servePublish(served.consume()); - final connect = moq.ConnectConfig.default_(); - connect.setUrl(moq.Url.parse('moqt://localhost:${local.port}')); - final insecure = connect.tls; - insecure.setInsecure(true); - connect.setTls(insecure); - final clientConfig = moq.ClientConfig.default_(); - clientConfig.setConnect(connect); + final connect = moq.TokioConnectConfig.default_(); + connect.urlSet(moq.Url.parse('moqt://localhost:${local.port}')); + final insecure = connect.tlsGet; + insecure.insecureSet(true); + connect.tlsSet(insecure); + final clientConfig = moq.TokioClientConfig.default_(); + clientConfig.connectSet(connect); final client = clientConfig.init(); - final received = moq.originSpawn(); + final received = moq.tokioOriginSpawn(); final remote = received.consume(); final connection = client.clone().withReconnect(false).consume(received); expect(connection != null, 'a client with a URL dials it'); @@ -373,7 +373,7 @@ Future session() async { final subscriber = await broadcast.track('video').subscribe(null); final group = await subscriber.recvGroup(); final frame = await group?.readFrame(); - expect(frame != null && utf8.decode(frame.payload) == 'hello', 'a session reads the frame the server serves'); + expect(frame != null && utf8.decode(frame.payloadGet) == 'hello', 'a session reads the frame the server serves'); await established.close_(); await client.close_(); diff --git a/ffi/moq/moq.go b/ffi/moq/moq.go index 40bd7959f..9dec82f88 100644 --- a/ffi/moq/moq.go +++ b/ffi/moq/moq.go @@ -146,8 +146,8 @@ func script() { <-waitFrame.waiting expect(writer.WriteFrame(nil, []byte("hello")) == nil, "a frame writes") hello := within(frame, "a frame's arrival wakes the read") - expect(string(must(hello.Payload()).Copy()) == "hello", "a frame has its payload") - expect(must(hello.Timestamp()) == nil, "an untimed frame has no timestamp") + expect(string(must(hello.PayloadGet()).Copy()) == "hello", "a frame has its payload") + expect(must(hello.TimestampGet()) == nil, "an untimed frame has no timestamp") // When it arrived: kio's clock's `Instant`, from kio's own export. expect(must(moq.ClockNow().CheckedDurationSince(must(hello.Arrived()))) != nil, "a frame arrived before now") @@ -164,7 +164,7 @@ func script() { expect(writer.WriteFrame(nil, world) == nil, "the last frame writes") expect(writer.Finish() == nil, "a group finishes") last := must(first.ReadFrame(ctx)) - expect(last != nil && bytes.Equal(must(last.Payload()).Copy(), world), "the last frame reads") + expect(last != nil && bytes.Equal(must(last.PayloadGet()).Copy(), world), "the last frame reads") expect(must(first.ReadFrame(ctx)) == nil, "a finished group reads nil") // An origin and the driver that runs it, a pair. Its root, which it lends, is returned @@ -193,7 +193,7 @@ func script() { // handle to it is closed, since an open one keeps it, and so the origin, alive. publication := must(origin.CreateBroadcast("room/alice")) created := must(publication.Broadcast()) - path := must(must(created.Info()).Path()) + path := must(must(created.Info()).PathGet()) created.Close() publication.Close() expect(must(path.AsStr()) == "room/alice", "a broadcast has its path") @@ -208,7 +208,7 @@ func script() { expect(must(joined.AsStr()) == "room/alice/cam", "paths join") expect(must(must(joined.StripPrefix(path)).Equal(cam)), "a path strips its prefix") expect(must(path.StripPrefix(joined)) == nil, "a path strips no longer prefix") - expect(must(must(joined.Resolve(must(moq.NewRelative("../bob")))).AsStr()) == "room/bob", "a relative path resolves") + expect(must(must(joined.Resolve(must(moq.NewPathRelative("../bob")))).AsStr()) == "room/bob", "a relative path resolves") // A client publishes the origin through moq-net's generic `with_publisher`, at each // type that implements its bound, by reference, so Go keeps its handle. @@ -226,14 +226,14 @@ func script() { // moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the // catalog lists at the decoder's 48 kHz, keeping the head as its description. head := append([]byte("OpusHead"), 1, 2, 0x38, 0x01, 0x44, 0xac, 0, 0, 0, 0, 0) - audio := must(moq.CodecOpusAudioConfig(head)) - expect(must(audio.SampleRate()) == 48000, "Opus decodes at 48 kHz") - expect(must(audio.ChannelCount()) == 2, "the head is stereo") - expect(must(audio.Codec()) == moq.CatalogAudioCodecOpus{}, "the codec is Opus") - description := must(audio.Description()) + audio := must(moq.MuxCodecOpusConfig_(head)) + expect(must(audio.SampleRateGet()) == 48000, "Opus decodes at 48 kHz") + expect(must(audio.ChannelCountGet()) == 2, "the head is stereo") + expect(must(audio.CodecGet()) == moq.HangCatalogAudioCodecOpus{}, "the codec is Opus") + description := must(audio.DescriptionGet()) expect(description != nil && bytes.Equal(description.Copy(), head), "the head is the description") // A truncated head is moq-mux's error. - _, err = moq.CodecOpusAudioConfig(head[:8]) + _, err = moq.MuxCodecOpusConfig_(head[:8]) var mux *moq.MuxError expect(errors.As(err, &mux) && err.Error() != "", "a truncated head's error has a message") @@ -243,7 +243,7 @@ func script() { End: moq.BoundU64{Kind: moq.BoundExcluded, Value: 6}, }) ranges := []moq.TrackRange{moq.TrackRangeLive(), groups} - ranged := must(must(moq.TrackSubscriptionDefault().WithRanges(ranges)).Ranges()) + ranged := must(must(moq.TrackSubscriptionDefault().WithRanges(ranges)).RangesGet()) expect(slices.EqualFunc(ranged, ranges, func(a, b moq.TrackRange) bool { return must(a.Equal(b)) }), "a subscription keeps its ranges") expect(groups.Start == moq.TrackAnchorAbsolute{V0: 2}, "groups start at their first") expect(groups.End != nil && *groups.End == moq.TrackAnchorAbsolute{V0: 6}, "groups end before their end") @@ -252,8 +252,8 @@ func script() { // `is_claimed` is named by the last segment of its argument, and whose `mutate` lends // the catalog to Go. publisher := must(moq.NewBroadcastInfo().Produce()) - extra := must(moq.CatalogConfigDefault().WithCatalogExtra(moq.HangCatalogExtraDefault())) - catalog := must(moq.NewCatalogProducerExtra(publisher, extra)) + extra := must(moq.MuxCatalogConfigDefault().WithCatalogExtra(moq.HangCatalogExtraDefault())) + catalog := must(moq.NewMuxCatalogProducerExtra(publisher, extra)) expect(!must(catalog.IsClaimedBinaryConfig("state")), "an unclaimed name is not claimed") read := 0 expect(catalog.Mutate(func(lent *moq.HangCatalogExtra) { @@ -266,7 +266,7 @@ func script() { // A JSON track the catalog lists, published through moq-mux's generic `json_snapshot` at // a JSON document, and a section Go parses from text, set in the lent catalog and read // back as text. - status := must(catalog.JsonSnapshotConfig(must(publisher.CreateTrack("status", must(moq.TrackInfoDefault().WithTimescale(moq.TimescaleMILLI())))), moq.JsonConfigDefault(), moq.JsonOptionsDefault())) + status := must(catalog.JsonSnapshotConfig(must(publisher.CreateTrack("status", must(moq.TrackInfoDefault().WithTimescale(moq.TimescaleMILLI())))), moq.MuxJsonConfigDefault(), moq.MuxJsonOptionsDefault())) expect(must(status.Name()) == "status", "a JSON snapshot has its track's name") expect(must(catalog.IsClaimedJsonConfig("status")), "the JSON snapshot claims its name") // A value Go parses is published as the track's latest, which a subscriber reads back @@ -275,16 +275,16 @@ func script() { expect(status.Update(on, moq.TimestampNow()) == nil, "a JSON snapshot updates") on.Close() latest := must(must(must(status.Consume()).RecvGroup(ctx)).ReadFrame(ctx)) - expect(latest != nil && string(must(latest.Payload()).Copy()) == `{"on":true}`, "a subscriber reads the latest value") + expect(latest != nil && string(must(latest.PayloadGet()).Copy()) == `{"on":true}`, "a subscriber reads the latest value") expect(catalog.Mutate(func(lent *moq.HangCatalogExtra) { - sections := must(lent.Ext()) + sections := must(lent.ExtGet()) expect(sections.Set("app", must(moq.JsonParse(`{"v": [1, true]}`))) == nil, "a section sets") - check(lent.SetExt(sections)) + check(lent.ExtSet(sections)) }) == nil, "mutate sets a section") var app *string expect(catalog.Mutate(func(lent *moq.HangCatalogExtra) { expect(strings.Contains(must(lent.ToJson()), `"status"`), "the catalog lists the JSON track") - if section := must(must(lent.Ext()).Get("app")); section != nil { + if section := must(must(lent.ExtGet()).Get("app")); section != nil { text := must(section.ToText()) app = &text } @@ -294,30 +294,30 @@ func script() { _, err = moq.JsonParse(`{"v":`) var parse moq.StringError expect(errors.As(err, &parse) && parse.Value == "EOF while parsing a value at line 1 column 5", "a parse error is serde_json's") - err = moq.CatalogHangExtraDefault().Set("json", must(moq.JsonParse("1"))) + err = moq.MuxCatalogHangExtraDefault().Set("json", must(moq.JsonParse("1"))) expect(errors.As(err, &mux) && err.Error() == "reserved catalog section: json", "a reserved section is moq-mux's error") // moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. - expect(moq.ConnectErrorUnauthorized.IsAuth(), "unauthorized is an auth error") - expect(moq.ConnectErrorUnauthorized.String() == "unauthorized", "a connect error prints as Rust's") + expect(moq.TokioConnectErrorUnauthorized.IsAuth(), "unauthorized is an auth error") + expect(moq.TokioConnectErrorUnauthorized.String() == "unauthorized", "a connect error prints as Rust's") // Session config's foreign types. A URL is parsed from text, failing with why. _, err = moq.UrlParse("relay.example") expect(errors.As(err, &parse) && parse.Value == "relative URL without a base", "a URL's parse error is Rust's") // A session's config, its URL and pinned fingerprints set as Rust assigns them. - connect := moq.ConnectConfigDefault() - tls := must(connect.Tls()) - check(tls.SetFingerprint([]string{"ab:cd"})) - check(connect.SetTls(tls)) + connect := moq.TokioConnectConfigDefault() + tls := must(connect.TlsGet()) + check(tls.FingerprintSet([]string{"ab:cd"})) + check(connect.TlsSet(tls)) tls.Close() - check(connect.SetUrl(must(moq.UrlParse("https://relay.example:4443/demo")))) - expect(slices.Equal(must(must(connect.Tls()).Fingerprint()), []string{"ab:cd"}), "a TLS config keeps its fingerprints") - url := must(connect.Url()) + check(connect.UrlSet(must(moq.UrlParse("https://relay.example:4443/demo")))) + expect(slices.Equal(must(must(connect.TlsGet()).FingerprintGet()), []string{"ab:cd"}), "a TLS config keeps its fingerprints") + url := must(connect.UrlGet()) connect.Close() versions := moq.VersionsDefault() tokens := must(moq.NewAuthTokens(nil)) dial := must(tokens.Dial(url, versions)) - dialed := must(dial.Url()) + dialed := must(dial.UrlGet()) expect(must(dialed.Equal(url)), "a dial keeps its URL") expect(dialed.String() == "https://relay.example:4443/demo", "a URL prints as Rust's") dialed.Close() @@ -330,22 +330,22 @@ func script() { expect(len(alpns) > 0 && !slices.Contains(alpns, ""), "each version has an ALPN") versions.Close() // A socket address, crossing in a listener's bind. - bind := moq.ListenBind(moq.ListenBindAddr{V0: netip.MustParseAddrPort("[::1]:4443")}) + bind := moq.TokioListenBind(moq.TokioListenBindAddr{V0: netip.MustParseAddrPort("[::1]:4443")}) expect(fmt.Sprint(bind) == "[::1]:4443", "an IPv6 bind prints as Rust's") v4 := netip.MustParseAddrPort("127.0.0.1:0") - expect(moq.ListenBindAddr{V0: v4}.String() == "127.0.0.1:0", "an IPv4 bind prints as Rust's") - listen := moq.ListenConfigDefault() - check(listen.SetBind(&bind)) + expect(moq.TokioListenBindAddr{V0: v4}.String() == "127.0.0.1:0", "an IPv4 bind prints as Rust's") + listen := moq.TokioListenConfigDefault() + check(listen.BindSet(&bind)) // The preferred address of each family, which takes only that family. - check(listen.SetPreferredV4(&v4)) - preferred := must(listen.PreferredV4()) - expect(preferred != nil && *preferred == v4 && must(listen.PreferredV6()) == nil, "a listener keeps its preferred IPv4 address") - kept := must(listen.Bind()) + check(listen.PreferredV4Set(&v4)) + preferred := must(listen.PreferredV4Get()) + expect(preferred != nil && *preferred == v4 && must(listen.PreferredV6Get()) == nil, "a listener keeps its preferred IPv4 address") + kept := must(listen.BindGet()) expect(kept != nil && *kept == bind, "a listener's config keeps its bind") listen.Close() // TLS files, as OS paths, and fingerprints, as strings: none by default. - defaults := moq.TlsConnectDefault() - expect(len(must(defaults.Root())) == 0 && len(must(defaults.Fingerprint())) == 0, "a default TLS config lists none") + defaults := moq.TokioTlsConnectDefault() + expect(len(must(defaults.RootGet())) == 0 && len(must(defaults.FingerprintGet())) == 0, "a default TLS config lists none") defaults.Close() session() @@ -359,7 +359,7 @@ func session() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - served := moq.OriginSpawn() + served := moq.TokioOriginSpawn() live := must(served.CreateBroadcast("live")) expect(live.Announce(moq.OriginRouteDefault()) == nil, "a broadcast announces") liveTrack := must(must(live.Broadcast()).CreateTrack("video", nil)) @@ -367,14 +367,14 @@ func session() { expect(liveGroup.WriteFrame(nil, []byte("hello")) == nil, "a served frame writes") expect(liveGroup.Finish() == nil, "a served group finishes") - serverConfig := moq.ServerConfigDefault() - listen := must(serverConfig.Listen()) - var bind moq.ListenBind = moq.ListenBindAddr{V0: netip.MustParseAddrPort("[::]:0")} - check(listen.SetBind(&bind)) - tls := must(listen.Tls()) - check(tls.SetGenerate([]string{"localhost"})) - check(listen.SetTls(tls)) - check(serverConfig.SetListen(listen)) + serverConfig := moq.TokioServerConfigDefault() + listen := must(serverConfig.ListenGet()) + var bind moq.TokioListenBind = moq.TokioListenBindAddr{V0: netip.MustParseAddrPort("[::]:0")} + check(listen.BindSet(&bind)) + tls := must(listen.TlsGet()) + check(tls.GenerateSet([]string{"localhost"})) + check(listen.TlsSet(tls)) + check(serverConfig.ListenSet(listen)) server := must(serverConfig.Init()) local := must(server.LocalAddr()) expect(local.Port() != 0, "a server bound to port 0 reads back the port it got") @@ -382,16 +382,16 @@ func session() { defer stop() go server.ServePublish(serving, must(served.Consume())) - connect := moq.ConnectConfigDefault() - check(connect.SetUrl(must(moq.UrlParse(fmt.Sprintf("moqt://localhost:%d", local.Port()))))) - insecure := must(connect.Tls()) + connect := moq.TokioConnectConfigDefault() + check(connect.UrlSet(must(moq.UrlParse(fmt.Sprintf("moqt://localhost:%d", local.Port()))))) + insecure := must(connect.TlsGet()) yes := true - check(insecure.SetInsecure(&yes)) - check(connect.SetTls(insecure)) - clientConfig := moq.ClientConfigDefault() - check(clientConfig.SetConnect(connect)) + check(insecure.InsecureSet(&yes)) + check(connect.TlsSet(insecure)) + clientConfig := moq.TokioClientConfigDefault() + check(clientConfig.ConnectSet(connect)) client := must(clientConfig.Init()) - received := moq.OriginSpawn() + received := moq.TokioOriginSpawn() remote := must(received.Consume()) connection := must(must(must(client.Clone()).WithReconnect(false)).Consume(received)) expect(connection != nil, "a client with a URL dials it") @@ -399,7 +399,7 @@ func session() { subscriber := must(must(must(remote.Request(ctx, "live", nil)).Track("video")).Subscribe(ctx, nil)) frame := must(must(subscriber.RecvGroup(ctx)).ReadFrame(ctx)) - expect(frame != nil && string(must(frame.Payload()).Copy()) == "hello", "a session reads the frame the server serves") + expect(frame != nil && string(must(frame.PayloadGet()).Copy()) == "hello", "a session reads the frame the server serves") expect(established.Close_(ctx) == nil, "a connection closes") expect(client.Close_(ctx) == nil, "a client closes") diff --git a/ffi/moq/moq.kt b/ffi/moq/moq.kt index d211e2a31..8cac2b723 100644 --- a/ffi/moq/moq.kt +++ b/ffi/moq/moq.kt @@ -84,8 +84,8 @@ fun main() { expect(q.size == 1, "a frame's arrival wakes the read") q.drain() val hello = frame!! - expect(hello.payload.toByteArray().decodeToString() == "hello", "a frame has its payload") - expect(hello.timestamp == null, "an untimed frame has no timestamp") + expect(hello.payloadGet.toByteArray().decodeToString() == "hello", "a frame has its payload") + expect(hello.timestampGet == null, "an untimed frame has no timestamp") // When it arrived: kio's clock's `Instant`, from kio's own export. expect(clockNow().checkedDurationSince(hello.arrived()) != null, "a frame arrived before now") @@ -106,7 +106,7 @@ fun main() { writer.writeFrame(null, world) writer.finish() runBlocking { - expect(first.readFrame()?.payload?.toByteArray()?.contentEquals(world) == true, "the last frame reads") + expect(first.readFrame()?.payloadGet?.toByteArray()?.contentEquals(world) == true, "the last frame reads") expect(first.readFrame() == null, "a finished group reads null") } @@ -133,7 +133,7 @@ fun main() { // A broadcast the origin creates is stamped with its path, which its info lends. Each // handle to it is closed, since an open one keeps it, and so the origin, alive. origin.createBroadcast("room/alice").use { publication -> - val path = publication.broadcast().use { it.info().path } + val path = publication.broadcast().use { it.info().pathGet } expect(path.asStr() == "room/alice", "a broadcast has its path") // Paths own their segments, so Kotlin builds them, and relative ones, from a string. @@ -149,7 +149,7 @@ fun main() { expect(joined.asStr() == "room/alice/cam", "paths join") expect(joined.stripPrefix(path) == cam, "a path strips its prefix") expect(path.stripPrefix(joined) == null, "a path strips no longer prefix") - expect(joined.resolve(Relative.new("../bob")).asStr() == "room/bob", "a relative path resolves") + expect(joined.resolve(PathRelative.new("../bob")).asStr() == "room/bob", "a relative path resolves") } // The driver, polled as a language's event loop would: at the time, and with a waiter @@ -175,14 +175,14 @@ fun main() { // moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the // catalog lists at the decoder's 48 kHz, keeping the head as its description. val head = "OpusHead".encodeToByteArray() + byteArrayOf(1, 2, 0x38, 0x01, 0x44, 0xac.toByte(), 0, 0, 0, 0, 0) - val audio = codecOpusAudioConfig(head) - expect(audio.sampleRate == 48000u, "Opus decodes at 48 kHz") - expect(audio.channelCount == 2u, "the head is stereo") - expect(audio.codec == CatalogAudioCodec.Opus, "the codec is Opus") - expect(audio.description?.toByteArray()?.contentEquals(head) == true, "the head is the description") + val audio = muxCodecOpusConfig(head) + expect(audio.sampleRateGet == 48000u, "Opus decodes at 48 kHz") + expect(audio.channelCountGet == 2u, "the head is stereo") + expect(audio.codecGet == HangCatalogAudioCodec.Opus, "the codec is Opus") + expect(audio.descriptionGet?.toByteArray()?.contentEquals(head) == true, "the head is the description") // A truncated head is moq-mux's error. try { - codecOpusAudioConfig(head.copyOf(8)) + muxCodecOpusConfig(head.copyOf(8)) expect(false, "a truncated head is an error") } catch (e: MuxErrorException) { expect(e.message.isNotEmpty(), "a truncated head's error has a message") @@ -190,7 +190,7 @@ fun main() { // A subscription's ranges, from any iterable of them: a list. val groups = TrackRange.groups(BoundsU64(BoundU64(Bound.INCLUDED, 2u), BoundU64(Bound.EXCLUDED, 6u))) - val ranged = TrackSubscription.default().withRanges(listOf(TrackRange.live(), groups)).ranges + val ranged = TrackSubscription.default().withRanges(listOf(TrackRange.live(), groups)).rangesGet expect(ranged == listOf(TrackRange.live(), groups), "a subscription keeps its ranges") expect(groups.start == TrackAnchor.Absolute(2u), "groups start at their first") expect(groups.end == TrackAnchor.Absolute(6u), "groups end before their end") @@ -199,8 +199,8 @@ fun main() { // `is_claimed` is named by the last segment of its argument, and whose `mutate` lends // the catalog to Kotlin. val publisher = BroadcastInfo.new().produce() - val extra = CatalogConfig.default().withCatalogExtra(HangCatalogExtra.default()) - val catalog = CatalogProducerExtra.new(publisher, extra) + val extra = MuxCatalogConfig.default().withCatalogExtra(HangCatalogExtra.default()) + val catalog = MuxCatalogProducerExtra.new(publisher, extra) expect(!catalog.isClaimedBinaryConfig("state"), "an unclaimed name is not claimed") var read = 0 catalog.mutate { lent -> @@ -211,7 +211,7 @@ fun main() { // A JSON track the catalog lists, published through moq-mux's generic `json_snapshot` at // a JSON document, and a section Kotlin parses from text, set in the lent catalog and // read back as text. - val status = catalog.jsonSnapshotConfig(publisher.createTrack("status", TrackInfo.default().withTimescale(Timescale.MILLI)), JsonConfig.default(), JsonOptions.default()) + val status = catalog.jsonSnapshotConfig(publisher.createTrack("status", TrackInfo.default().withTimescale(Timescale.MILLI)), MuxJsonConfig.default(), MuxJsonOptions.default()) expect(status.name() == "status", "a JSON snapshot has its track's name") expect(catalog.isClaimedJsonConfig("status"), "the JSON snapshot claims its name") // A value Kotlin parses is published as the track's latest, which a subscriber reads @@ -219,17 +219,17 @@ fun main() { Json.parse("""{"on": true}""").use { status.update(it, Timestamp.now()) } runBlocking { val frame = status.consume().recvGroup()?.readFrame() - expect(frame?.payload?.toByteArray()?.decodeToString() == """{"on":true}""", "a subscriber reads the latest value") + expect(frame?.payloadGet?.toByteArray()?.decodeToString() == """{"on":true}""", "a subscriber reads the latest value") } catalog.mutate { lent -> - val sections = lent.ext + val sections = lent.extGet sections.set("app", Json.parse("""{"v": [1, true]}""")) - lent.setExt(sections) + lent.extSet(sections) } var app: String? = null catalog.mutate { lent -> expect(lent.toJson().contains("\"status\""), "the catalog lists the JSON track") - app = lent.ext.get("app")?.use { it.toText() } + app = lent.extGet.get("app")?.use { it.toText() } } expect(app == """{"v":[1,true]}""", "the catalog reads its section back as text") // Malformed text fails to parse, and a reserved section is moq-mux's error. @@ -240,15 +240,15 @@ fun main() { expect(e.error == "EOF while parsing a value at line 1 column 5", "a parse error is serde_json's") } try { - CatalogHangExtra.default().use { it.set("json", Json.parse("1")) } + MuxCatalogHangExtra.default().use { it.set("json", Json.parse("1")) } expect(false, "a reserved section is an error") } catch (e: MuxErrorException) { expect(e.message == "reserved catalog section: json", "a reserved section is moq-mux's error") } // moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. - expect(ConnectError.UNAUTHORIZED.isAuth(), "unauthorized is an auth error") - expect(ConnectError.UNAUTHORIZED.toString() == "unauthorized", "a connect error prints as Rust's") + expect(TokioConnectError.UNAUTHORIZED.isAuth(), "unauthorized is an auth error") + expect(TokioConnectError.UNAUTHORIZED.toString() == "unauthorized", "a connect error prints as Rust's") // Session config's foreign types. A URL is parsed from text, failing with why. try { @@ -258,19 +258,19 @@ fun main() { expect(e.message == "relative URL without a base", "a URL's parse error is Rust's") } // A session's config, its URL and pinned fingerprints set as Rust assigns them. - val connect = ConnectConfig.default() - connect.tls.use { tls -> - tls.setFingerprint(listOf("ab:cd")) - connect.setTls(tls) + val connect = TokioConnectConfig.default() + connect.tlsGet.use { tls -> + tls.fingerprintSet(listOf("ab:cd")) + connect.tlsSet(tls) } - connect.setUrl(Url.parse("https://relay.example:4443/demo")) - connect.tls.use { tls -> expect(tls.fingerprint == listOf("ab:cd"), "a TLS config keeps its fingerprints") } - val url = connect.url!! + connect.urlSet(Url.parse("https://relay.example:4443/demo")) + connect.tlsGet.use { tls -> expect(tls.fingerprintGet == listOf("ab:cd"), "a TLS config keeps its fingerprints") } + val url = connect.urlGet!! connect.close() val versions = Versions.default() AuthTokens.new(emptyList()).use { tokens -> tokens.dial(url, versions).use { dial -> - dial.url.use { dialed -> + dial.urlGet.use { dialed -> expect(dialed == url, "a dial keeps its URL") expect(dialed.toString() == "https://relay.example:4443/demo", "a URL prints as Rust's") } @@ -284,20 +284,20 @@ fun main() { versions.close() // A socket address, crossing in a listener's bind. val loopback = ByteArray(16).also { it[15] = 1 } - val bind = ListenBind.Addr(java.net.InetSocketAddress(java.net.Inet6Address.getByAddress(null, loopback, null as java.net.NetworkInterface?), 4443)) + val bind = TokioListenBind.Addr(java.net.InetSocketAddress(java.net.Inet6Address.getByAddress(null, loopback, null as java.net.NetworkInterface?), 4443)) expect(bind.toString() == "[::1]:4443", "an IPv6 bind prints as Rust's") - val v4 = ListenBind.Addr(java.net.InetSocketAddress(java.net.InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)), 0)) + val v4 = TokioListenBind.Addr(java.net.InetSocketAddress(java.net.InetAddress.getByAddress(byteArrayOf(127, 0, 0, 1)), 0)) expect(v4.toString() == "127.0.0.1:0", "an IPv4 bind prints as Rust's") - ListenConfig.default().use { listen -> - listen.setBind(bind) + TokioListenConfig.default().use { listen -> + listen.bindSet(bind) // The preferred address of each family, which takes only that family. - listen.setPreferredV4(v4._0) - expect(listen.preferredV4 == v4._0 && listen.preferredV6 == null, "a listener keeps its preferred IPv4 address") - expect(listen.bind == bind, "a listener's config keeps its bind") + listen.preferredV4Set(v4._0) + expect(listen.preferredV4Get == v4._0 && listen.preferredV6Get == null, "a listener keeps its preferred IPv4 address") + expect(listen.bindGet == bind, "a listener's config keeps its bind") } // TLS files, as OS paths, and fingerprints, as strings: none by default. - TlsConnect.default().use { tls -> - expect(tls.root.isEmpty() && tls.fingerprint.isEmpty(), "a default TLS config lists none") + TokioTlsConnect.default().use { tls -> + expect(tls.rootGet.isEmpty() && tls.fingerprintGet.isEmpty(), "a default TLS config lists none") } // A session, as moq.c runs it: a server bound to port 0 reads back the port it got and @@ -306,7 +306,7 @@ fun main() { // wakes it from its threads. A stall fails with the timeout's exception. runBlocking { withTimeout(30.seconds) { - val served = originSpawn() + val served = tokioOriginSpawn() val live = served.createBroadcast("live") live.announce(OriginRoute.default()) val liveTrack = live.broadcast().createTrack("video", null) @@ -314,27 +314,27 @@ fun main() { liveGroup.writeFrame(null, "hello".encodeToByteArray()) liveGroup.finish() - val serverConfig = ServerConfig.default() - val listen = serverConfig.listen - listen.setBind(ListenBind.Addr(InetSocketAddress(InetAddress.getByName("::"), 0))) - val tls = listen.tls - tls.setGenerate(listOf("localhost")) - listen.setTls(tls) - serverConfig.setListen(listen) + val serverConfig = TokioServerConfig.default() + val listen = serverConfig.listenGet + listen.bindSet(TokioListenBind.Addr(InetSocketAddress(InetAddress.getByName("::"), 0))) + val tls = listen.tlsGet + tls.generateSet(listOf("localhost")) + listen.tlsSet(tls) + serverConfig.listenSet(listen) val server = serverConfig.init() val local = server.localAddr() expect(local.port != 0, "a server bound to port 0 reads back the port it got") val serving = launch { server.servePublish(served.consume()) } - val connect = ConnectConfig.default() - connect.setUrl(Url.parse("moqt://localhost:${local.port}")) - val insecure = connect.tls - insecure.setInsecure(true) - connect.setTls(insecure) - val clientConfig = ClientConfig.default() - clientConfig.setConnect(connect) + val connect = TokioConnectConfig.default() + connect.urlSet(Url.parse("moqt://localhost:${local.port}")) + val insecure = connect.tlsGet + insecure.insecureSet(true) + connect.tlsSet(insecure) + val clientConfig = TokioClientConfig.default() + clientConfig.connectSet(connect) val client = clientConfig.init() - val received = originSpawn() + val received = tokioOriginSpawn() val remote = received.consume() val connection = client.clone().withReconnect(false).consume(received) expect(connection != null, "a client with a URL dials it") @@ -342,7 +342,7 @@ fun main() { val subscriber = remote.request("live", null).track("video").subscribe(null) val frame = subscriber.recvGroup()?.readFrame() - expect(frame?.payload?.toByteArray()?.decodeToString() == "hello", "a session reads the frame the server serves") + expect(frame?.payloadGet?.toByteArray()?.decodeToString() == "hello", "a session reads the frame the server serves") established.close_() client.close_() diff --git a/ffi/moq/moq.py b/ffi/moq/moq.py index c561b5872..d23540460 100644 --- a/ffi/moq/moq.py +++ b/ffi/moq/moq.py @@ -78,8 +78,8 @@ def local(loop): expect(not read.done(), "a read waits for a frame") writer.write_frame(None, b"hello") hello = loop.run_until_complete(asyncio.wait_for(read, 5)) - expect(text(hello.payload) == "hello", "a frame has its payload") - expect(hello.timestamp is None, "an untimed frame has no timestamp") + expect(text(hello.payload_get) == "hello", "a frame has its payload") + expect(hello.timestamp_get is None, "an untimed frame has no timestamp") # When it arrived: kio's clock's `Instant`, from kio's own export. expect(moq.clock_now().checked_duration_since(hello.arrived()) is not None, "a frame arrived before now") @@ -99,7 +99,7 @@ def local(loop): writer.write_frame(None, world) writer.finish() last = loop.run_until_complete(first.read_frame()) - expect(last is not None and bytes(last.payload) == world, "the last frame reads") + expect(last is not None and bytes(last.payload_get) == world, "the last frame reads") expect(loop.run_until_complete(first.read_frame()) is None, "a finished group reads None") @@ -123,7 +123,7 @@ def origin(loop): # A broadcast the origin creates is stamped with its path, which its info lends. publication = origin.create_broadcast("room/alice") - path = publication.broadcast().info().path + path = publication.broadcast().info().path_get expect(path.as_str() == "room/alice", "a broadcast has its path") # Paths own their segments, so Python builds them, and relative ones, from a string. @@ -138,7 +138,7 @@ def origin(loop): expect(joined.as_str() == "room/alice/cam", "paths join") expect(joined.strip_prefix(path) == cam, "a path strips its prefix") expect(path.strip_prefix(joined) is None, "a path strips no longer prefix") - expect(joined.resolve(moq.Relative.new("../bob")).as_str() == "room/bob", "a relative path resolves") + expect(joined.resolve(moq.PathRelative.new("../bob")).as_str() == "room/bob", "a relative path resolves") # Each object of the origin is released, since a live one keeps it alive. del scoped, consumer, publication @@ -162,14 +162,14 @@ def catalog(loop): # moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the # catalog lists at the decoder's 48 kHz, keeping the head as its description. head = b"OpusHead" + bytes([1, 2, 0x38, 0x01, 0x44, 0xAC, 0, 0, 0, 0, 0]) - audio = moq.codec_opus_audio_config(head) - expect(audio.sample_rate == 48000, "Opus decodes at 48 kHz") - expect(audio.channel_count == 2, "the head is stereo") - expect(audio.codec == moq.CatalogAudioCodec.Opus(), "the codec is Opus") - expect(audio.description is not None and bytes(audio.description) == head, "the head is the description") + audio = moq.mux_codec_opus_config(head) + expect(audio.sample_rate_get == 48000, "Opus decodes at 48 kHz") + expect(audio.channel_count_get == 2, "the head is stereo") + expect(audio.codec_get == moq.HangCatalogAudioCodec.Opus(), "the codec is Opus") + expect(audio.description_get is not None and bytes(audio.description_get) == head, "the head is the description") # A truncated head is moq-mux's error. try: - moq.codec_opus_audio_config(head[:8]) + moq.mux_codec_opus_config(head[:8]) expect(False, "a truncated head is an error") except moq.MuxErrorException as e: expect(str(e) != "", "a truncated head's error has a message") @@ -177,7 +177,7 @@ def catalog(loop): # A subscription's ranges, from any iterable of them: a list. b = moq.Bound groups = moq.TrackRange.groups(moq.BoundsU64(moq.BoundU64(b.INCLUDED, 2), moq.BoundU64(b.EXCLUDED, 6))) - ranged = moq.TrackSubscription.default().with_ranges([moq.TrackRange.live(), groups]).ranges + ranged = moq.TrackSubscription.default().with_ranges([moq.TrackRange.live(), groups]).ranges_get expect(ranged == [moq.TrackRange.live(), groups], "a subscription keeps its ranges") expect(groups.start == moq.TrackAnchor.Absolute(2), "groups start at their first") expect(groups.end == moq.TrackAnchor.Absolute(6), "groups end before their end") @@ -186,8 +186,8 @@ def catalog(loop): # `is_claimed` is named by the last segment of its argument, and whose `mutate` lends # the catalog to Python. publisher = moq.BroadcastInfo.new().produce() - extra = moq.CatalogConfig.default().with_catalog_extra(moq.HangCatalogExtra.default()) - producer = moq.CatalogProducerExtra.new(publisher, extra) + extra = moq.MuxCatalogConfig.default().with_catalog_extra(moq.HangCatalogExtra.default()) + producer = moq.MuxCatalogProducerExtra.new(publisher, extra) expect(not producer.is_claimed_binary_config("state"), "an unclaimed name is not claimed") read = 0 @@ -203,7 +203,7 @@ def count(lent): # a JSON document, and a section Python parses from text, set in the lent catalog and # read back as text. status = producer.json_snapshot_config( - publisher.create_track("status", moq.TrackInfo.default().with_timescale(moq.Timescale.MILLI)), moq.JsonConfig.default(), moq.JsonOptions.default() + publisher.create_track("status", moq.TrackInfo.default().with_timescale(moq.Timescale.MILLI)), moq.MuxJsonConfig.default(), moq.MuxJsonOptions.default() ) expect(status.name() == "status", "a JSON snapshot has its track's name") expect(producer.is_claimed_json_config("status"), "the JSON snapshot claims its name") @@ -216,12 +216,12 @@ async def latest(): return None if group is None else await group.read_frame() frame = loop.run_until_complete(latest()) - expect(frame is not None and text(frame.payload) == '{"on":true}', "a subscriber reads the latest value") + expect(frame is not None and text(frame.payload_get) == '{"on":true}', "a subscriber reads the latest value") def section(lent): - sections = lent.ext + sections = lent.ext_get sections.set("app", moq.Json.parse('{"v": [1, true]}')) - lent.set_ext(sections) + lent.ext_set(sections) producer.mutate(section) app = None @@ -229,7 +229,7 @@ def section(lent): def back(lent): nonlocal app expect('"status"' in lent.to_json(), "the catalog lists the JSON track") - value = lent.ext.get("app") + value = lent.ext_get.get("app") app = None if value is None else value.to_text() producer.mutate(back) @@ -241,7 +241,7 @@ def back(lent): except moq.StringException as e: expect(e.error == "EOF while parsing a value at line 1 column 5", "a parse error is serde_json's") try: - moq.CatalogHangExtra.default().set("json", moq.Json.parse("1")) + moq.MuxCatalogHangExtra.default().set("json", moq.Json.parse("1")) expect(False, "a reserved section is an error") except moq.MuxErrorException as e: expect(str(e) == "reserved catalog section: json", "a reserved section is moq-mux's error") @@ -249,8 +249,8 @@ def back(lent): def config(): # moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. - expect(moq.ConnectError.UNAUTHORIZED.is_auth(), "unauthorized is an auth error") - expect(str(moq.ConnectError.UNAUTHORIZED) == "unauthorized", "a connect error prints as Rust's") + expect(moq.TokioConnectError.UNAUTHORIZED.is_auth(), "unauthorized is an auth error") + expect(str(moq.TokioConnectError.UNAUTHORIZED) == "unauthorized", "a connect error prints as Rust's") # Session config's foreign types. A URL is parsed from text, failing with why. try: @@ -259,15 +259,15 @@ def config(): except moq.StringException as e: expect(str(e) == "relative URL without a base", "a URL's parse error is Rust's") # A session's config, its URL and pinned fingerprints set as Rust assigns them. - connect = moq.ConnectConfig.default() - tls = connect.tls - tls.set_fingerprint(["ab:cd"]) - connect.set_tls(tls) - connect.set_url(moq.Url.parse("https://relay.example:4443/demo")) - expect(connect.tls.fingerprint == ["ab:cd"], "a TLS config keeps its fingerprints") - url = connect.url + connect = moq.TokioConnectConfig.default() + tls = connect.tls_get + tls.fingerprint_set(["ab:cd"]) + connect.tls_set(tls) + connect.url_set(moq.Url.parse("https://relay.example:4443/demo")) + expect(connect.tls_get.fingerprint_get == ["ab:cd"], "a TLS config keeps its fingerprints") + url = connect.url_get versions = moq.Versions.default() - dialed = moq.AuthTokens.new([]).dial(url, versions).url + dialed = moq.AuthTokens.new([]).dial(url, versions).url_get expect(dialed == url, "a dial keeps its URL") expect(str(dialed) == "https://relay.example:4443/demo", "a URL prints as Rust's") # A `Vec`, as a list. @@ -275,19 +275,19 @@ def config(): alpns = versions.alpns() expect(len(alpns) > 0 and all(alpns), "each version has an ALPN") # A socket address, crossing in a listener's bind. - bind = moq.ListenBind.Addr(("::1", 4443, 0, 0)) + bind = moq.TokioListenBind.Addr(("::1", 4443, 0, 0)) expect(str(bind) == "[::1]:4443", "an IPv6 bind prints as Rust's") - v4 = moq.ListenBind.Addr(("127.0.0.1", 0)) + v4 = moq.TokioListenBind.Addr(("127.0.0.1", 0)) expect(str(v4) == "127.0.0.1:0", "an IPv4 bind prints as Rust's") - listen = moq.ListenConfig.default() - listen.set_bind(bind) + listen = moq.TokioListenConfig.default() + listen.bind_set(bind) # The preferred address of each family, which takes only that family. - listen.set_preferred_v4(v4._0) - expect(listen.preferred_v4 == v4._0 and listen.preferred_v6 is None, "a listener keeps its preferred IPv4 address") - expect(listen.bind == bind, "a listener's config keeps its bind") + listen.preferred_v4_set(v4._0) + expect(listen.preferred_v4_get == v4._0 and listen.preferred_v6_get is None, "a listener keeps its preferred IPv4 address") + expect(listen.bind_get == bind, "a listener's config keeps its bind") # TLS files, as OS paths, and fingerprints, as strings: none by default. - tls = moq.TlsConnect.default() - expect(tls.root == [] and tls.fingerprint == [], "a default TLS config lists none") + tls = moq.TokioTlsConnect.default() + expect(tls.root_get == [] and tls.fingerprint_get == [], "a default TLS config lists none") async def session(): @@ -295,7 +295,7 @@ async def session(): and serves with one call, in a task of its own, and a client dials it with one call and reads a frame back, each await resuming once moq-tokio's fallback runtime wakes it from its threads.""" - served = moq.origin_spawn() + served = moq.tokio_origin_spawn() live = served.create_broadcast("live") live.announce(moq.OriginRoute.default()) live_track = live.broadcast().create_track("video", None) @@ -303,27 +303,27 @@ async def session(): live_group.write_frame(None, b"hello") live_group.finish() - server_config = moq.ServerConfig.default() - listen = server_config.listen - listen.set_bind(moq.ListenBind.Addr(("::", 0, 0, 0))) - tls = listen.tls - tls.set_generate(["localhost"]) - listen.set_tls(tls) - server_config.set_listen(listen) + server_config = moq.TokioServerConfig.default() + listen = server_config.listen_get + listen.bind_set(moq.TokioListenBind.Addr(("::", 0, 0, 0))) + tls = listen.tls_get + tls.generate_set(["localhost"]) + listen.tls_set(tls) + server_config.listen_set(listen) server = server_config.init() local = server.local_addr() expect(local[1] != 0, "a server bound to port 0 reads back the port it got") serving = asyncio.create_task(server.serve_publish(served.consume())) - connect = moq.ConnectConfig.default() - connect.set_url(moq.Url.parse(f"moqt://localhost:{local[1]}")) - insecure = connect.tls - insecure.set_insecure(True) - connect.set_tls(insecure) - client_config = moq.ClientConfig.default() - client_config.set_connect(connect) + connect = moq.TokioConnectConfig.default() + connect.url_set(moq.Url.parse(f"moqt://localhost:{local[1]}")) + insecure = connect.tls_get + insecure.insecure_set(True) + connect.tls_set(insecure) + client_config = moq.TokioClientConfig.default() + client_config.connect_set(connect) client = client_config.init() - received = moq.origin_spawn() + received = moq.tokio_origin_spawn() remote = received.consume() connection = client.clone().with_reconnect(False).consume(received) expect(connection is not None, "a client with a URL dials it") @@ -333,7 +333,7 @@ async def session(): subscriber = await broadcast.track("video").subscribe(None) group = await subscriber.recv_group() frame = None if group is None else await group.read_frame() - expect(frame is not None and text(frame.payload) == "hello", "a session reads the frame the server serves") + expect(frame is not None and text(frame.payload_get) == "hello", "a session reads the frame the server serves") await established.close() await client.close() diff --git a/ffi/moq/moq.swift b/ffi/moq/moq.swift index 06a787a33..5220fcf11 100644 --- a/ffi/moq/moq.swift +++ b/ffi/moq/moq.swift @@ -117,7 +117,7 @@ func origin(_ waiter: Waiter, _ wakes: Cell) throws -> (OriginDriver, Int) // A broadcast the origin creates is stamped with its path, which its info lends. do { let publication = try origin.createBroadcast("room/alice") - let path = publication.broadcast().info().path + let path = publication.broadcast().info().pathGet expect(path.asStr() == "room/alice", "a broadcast has its path") // Paths own their segments, so Swift builds them, and relative ones, from a string. @@ -133,7 +133,7 @@ func origin(_ waiter: Waiter, _ wakes: Cell) throws -> (OriginDriver, Int) expect(joined.asStr() == "room/alice/cam", "paths join") expect(joined.stripPrefix(path) == cam, "a path strips its prefix") expect(path.stripPrefix(joined) == nil, "a path strips no longer prefix") - expect(try joined.resolve(Relative.new("../bob")).asStr() == "room/bob", "a relative path resolves") + expect(try joined.resolve(PathRelative.new("../bob")).asStr() == "room/bob", "a relative path resolves") } _ = try driver.poll(clockNow(), waiter) @@ -180,8 +180,8 @@ func script() throws { expect(q.size == 1, "a frame's arrival wakes the read") q.drain() let hello = frame.value! - expect(text(hello.payload) == "hello", "a frame has its payload") - expect(hello.timestamp == nil, "an untimed frame has no timestamp") + expect(text(hello.payloadGet) == "hello", "a frame has its payload") + expect(hello.timestampGet == nil, "an untimed frame has no timestamp") // When it arrived: kio's clock's `Instant`, from kio's own export. expect(clockNow().checkedDurationSince(hello.arrived()) != nil, "a frame arrived before now") @@ -202,7 +202,7 @@ func script() throws { try writer.writeFrame(nil, world) try writer.finish() try block { - expect(try await first.readFrame()?.payload == world, "the last frame reads") + expect(try await first.readFrame()?.payloadGet == world, "the last frame reads") expect(try await first.readFrame() == nil, "a finished group reads nil") } @@ -224,14 +224,14 @@ func script() throws { // moq-mux reads an OpusHead into hang's catalog config: 44.1 kHz in, stereo, which the // catalog lists at the decoder's 48 kHz, keeping the head as its description. let head = Data("OpusHead".utf8) + Data([1, 2, 0x38, 0x01, 0x44, 0xac, 0, 0, 0, 0, 0]) - let audio = try codecOpusAudioConfig(head) - expect(audio.sampleRate == 48000, "Opus decodes at 48 kHz") - expect(audio.channelCount == 2, "the head is stereo") - expect(audio.codec == .opus, "the codec is Opus") - expect(audio.description == head, "the head is the description") + let audio = try muxCodecOpusConfig(head) + expect(audio.sampleRateGet == 48000, "Opus decodes at 48 kHz") + expect(audio.channelCountGet == 2, "the head is stereo") + expect(audio.codecGet == .opus, "the codec is Opus") + expect(audio.descriptionGet == head, "the head is the description") // A truncated head is moq-mux's error. do { - _ = try codecOpusAudioConfig(head.prefix(8)) + _ = try muxCodecOpusConfig(head.prefix(8)) expect(false, "a truncated head is an error") } catch let e as MuxError { expect(!"\(e)".isEmpty, "a truncated head's error has a message") @@ -239,7 +239,7 @@ func script() throws { // A subscription's ranges, from any iterable of them: an array. let groups = TrackRange.groups(BoundsU64(start: .included(2), end: .excluded(6))) - let ranged = TrackSubscription.default().withRanges([TrackRange.live(), groups]).ranges + let ranged = TrackSubscription.default().withRanges([TrackRange.live(), groups]).rangesGet expect(ranged == [TrackRange.live(), groups], "a subscription keeps its ranges") expect(groups.start == .absolute(2), "groups start at their first") expect(groups.end == .absolute(6), "groups end before their end") @@ -248,8 +248,8 @@ func script() throws { // `is_claimed` is named by the last segment of its argument, and whose `mutate` lends // the catalog to Swift. let publisher = BroadcastInfo.new().produce() - let extra = CatalogConfig.default().withCatalogExtra(HangCatalogExtra.default()) - let catalog = try CatalogProducerExtra.new(publisher, extra) + let extra = MuxCatalogConfig.default().withCatalogExtra(HangCatalogExtra.default()) + let catalog = try MuxCatalogProducerExtra.new(publisher, extra) expect(!catalog.isClaimedBinaryConfig("state"), "an unclaimed name is not claimed") let read = Cell(0) try catalog.mutate { lent in @@ -263,7 +263,7 @@ func script() throws { // a JSON document, and a section Swift parses from text, set in the lent catalog and // read back as text. let status = try catalog.jsonSnapshotConfig( - try publisher.createTrack("status", TrackInfo.default().withTimescale(Timescale.MILLI)), JsonConfig.default(), JsonOptions.default()) + try publisher.createTrack("status", TrackInfo.default().withTimescale(Timescale.MILLI)), MuxJsonConfig.default(), MuxJsonOptions.default()) expect(status.name() == "status", "a JSON snapshot has its track's name") expect(catalog.isClaimedJsonConfig("status"), "the JSON snapshot claims its name") // A value Swift parses is published as the track's latest, which a subscriber reads @@ -272,17 +272,17 @@ func script() throws { try status.update(on, Timestamp.now()) try block { let frame = try await status.consume().recvGroup()?.readFrame() - expect(text(frame?.payload) == #"{"on":true}"#, "a subscriber reads the latest value") + expect(text(frame?.payloadGet) == #"{"on":true}"#, "a subscriber reads the latest value") } try catalog.mutate { lent in - let sections = lent.ext + let sections = lent.extGet try! sections.set("app", Json.parse(#"{"v": [1, true]}"#)) - lent.setExt(sections) + lent.extSet(sections) } let app = Cell(nil) try catalog.mutate { lent in expect(try! lent.toJson().contains("\"status\""), "the catalog lists the JSON track") - app.value = lent.ext.get("app")?.toText() + app.value = lent.extGet.get("app")?.toText() } expect(app.value == #"{"v":[1,true]}"#, "the catalog reads its section back as text") // Malformed text fails to parse, and a reserved section is moq-mux's error. @@ -293,15 +293,15 @@ func script() throws { expect(e.error == "EOF while parsing a value at line 1 column 5", "a parse error is serde_json's") } do { - try CatalogHangExtra.default().set("json", Json.parse("1")) + try MuxCatalogHangExtra.default().set("json", Json.parse("1")) expect(false, "a reserved section is an error") } catch let e as MuxError { expect("\(e)" == "reserved catalog section: json", "a reserved section is moq-mux's error") } // moq-tokio's terminal connect errors, which moq-net's `Error` shares no names with. - expect(ConnectError.unauthorized.isAuth(), "unauthorized is an auth error") - expect("\(ConnectError.unauthorized)" == "unauthorized", "a connect error prints as Rust's") + expect(TokioConnectError.unauthorized.isAuth(), "unauthorized is an auth error") + expect("\(TokioConnectError.unauthorized)" == "unauthorized", "a connect error prints as Rust's") // Session config's foreign types. A URL is parsed from text, failing with why. do { @@ -311,15 +311,15 @@ func script() throws { expect(e.error == "relative URL without a base", "a URL's parse error is Rust's") } // A session's config, its URL and pinned fingerprints set as Rust assigns them. - let connect = ConnectConfig.default() - let tls = connect.tls - tls.setFingerprint(["ab:cd"]) - connect.setTls(tls) - connect.setUrl(try Url.parse("https://relay.example:4443/demo")) - expect(connect.tls.fingerprint == ["ab:cd"], "a TLS config keeps its fingerprints") - let url = connect.url! + let connect = TokioConnectConfig.default() + let tls = connect.tlsGet + tls.fingerprintSet(["ab:cd"]) + connect.tlsSet(tls) + connect.urlSet(try Url.parse("https://relay.example:4443/demo")) + expect(connect.tlsGet.fingerprintGet == ["ab:cd"], "a TLS config keeps its fingerprints") + let url = connect.urlGet! let versions = Versions.default() - let dialed = AuthTokens.new([]).dial(url, versions).url + let dialed = AuthTokens.new([]).dial(url, versions).urlGet expect(dialed == url, "a dial keeps its URL") expect("\(dialed)" == "https://relay.example:4443/demo", "a URL prints as Rust's") // A `Vec`, as an array. @@ -328,19 +328,19 @@ func script() throws { expect(!alpns.isEmpty && alpns.allSatisfy { !$0.isEmpty }, "each version has an ALPN") // A socket address, crossing in a listener's bind. let loopback: [UInt8] = Array(repeating: 0, count: 15) + [1] - let bind = ListenBind.addr(SocketAddr(ip: loopback, port: 4443)) + let bind = TokioListenBind.addr(SocketAddr(ip: loopback, port: 4443)) expect("\(bind)" == "[::1]:4443", "an IPv6 bind prints as Rust's") let v4 = SocketAddr(ip: [127, 0, 0, 1], port: 0) - expect("\(ListenBind.addr(v4))" == "127.0.0.1:0", "an IPv4 bind prints as Rust's") - let listen = ListenConfig.default() - listen.setBind(bind) + expect("\(TokioListenBind.addr(v4))" == "127.0.0.1:0", "an IPv4 bind prints as Rust's") + let listen = TokioListenConfig.default() + listen.bindSet(bind) // The preferred address of each family, which takes only that family. - listen.setPreferredV4(v4) - expect(listen.preferredV4 == v4 && listen.preferredV6 == nil, "a listener keeps its preferred IPv4 address") - expect(listen.bind == bind, "a listener's config keeps its bind") + listen.preferredV4Set(v4) + expect(listen.preferredV4Get == v4 && listen.preferredV6Get == nil, "a listener keeps its preferred IPv4 address") + expect(listen.bindGet == bind, "a listener's config keeps its bind") // TLS files, as OS paths, and fingerprints, as strings: none by default. - let defaults = TlsConnect.default() - expect(defaults.root.isEmpty && defaults.fingerprint.isEmpty, "a default TLS config lists none") + let defaults = TokioTlsConnect.default() + expect(defaults.rootGet.isEmpty && defaults.fingerprintGet.isEmpty, "a default TLS config lists none") // A session, as moq.c runs it: a server bound to port 0 reads back the port it got and // serves with one call, in a task of its own, and a client dials it with one call and @@ -351,7 +351,7 @@ func script() throws { exit(1) } try block { - let served = originSpawn() + let served = tokioOriginSpawn() let live = try served.createBroadcast("live") try live.announce(OriginRoute.default()) let liveTrack = try live.broadcast().createTrack("video", nil) @@ -359,27 +359,27 @@ func script() throws { try liveGroup.writeFrame(nil, Data("hello".utf8)) try liveGroup.finish() - let serverConfig = ServerConfig.default() - let listen = serverConfig.listen - listen.setBind(.addr(SocketAddr(ip: Array(repeating: 0, count: 16), port: 0))) - let tls = listen.tls - tls.setGenerate(["localhost"]) - listen.setTls(tls) - serverConfig.setListen(listen) + let serverConfig = TokioServerConfig.default() + let listen = serverConfig.listenGet + listen.bindSet(.addr(SocketAddr(ip: Array(repeating: 0, count: 16), port: 0))) + let tls = listen.tlsGet + tls.generateSet(["localhost"]) + listen.tlsSet(tls) + serverConfig.listenSet(listen) let server = try serverConfig.init_() let local = try server.localAddr() expect(local.port != 0, "a server bound to port 0 reads back the port it got") let serving = Task { try await server.servePublish(served.consume()) } - let connect = ConnectConfig.default() - connect.setUrl(try Url.parse("moqt://localhost:\(local.port)")) - let insecure = connect.tls - insecure.setInsecure(true) - connect.setTls(insecure) - let clientConfig = ClientConfig.default() - clientConfig.setConnect(connect) + let connect = TokioConnectConfig.default() + connect.urlSet(try Url.parse("moqt://localhost:\(local.port)")) + let insecure = connect.tlsGet + insecure.insecureSet(true) + connect.tlsSet(insecure) + let clientConfig = TokioClientConfig.default() + clientConfig.connectSet(connect) let client = try clientConfig.init_() - let received = originSpawn() + let received = tokioOriginSpawn() let remote = received.consume() let connection = client.clone().withReconnect(false).consume(received) expect(connection != nil, "a client with a URL dials it") @@ -387,7 +387,7 @@ func script() throws { let subscriber = try await remote.request("live", nil).track("video").subscribe(nil) let frame = try await subscriber.recvGroup()?.readFrame() - expect(text(frame?.payload) == "hello", "a session reads the frame the server serves") + expect(text(frame?.payloadGet) == "hello", "a session reads the frame the server serves") try await established.close() try await client.close() diff --git a/ffi/src/abi.rs b/ffi/src/abi.rs index 10391f988..c1b4eb9c9 100644 --- a/ffi/src/abi.rs +++ b/ffi/src/abi.rs @@ -1,10 +1,12 @@ //! The C ABI the IR becomes: every C name, and every function's C signature, decided //! once so the header and the Rust side agree. //! -//! Names follow one rule: the prefix, then the item's path from its crate's root, each -//! segment in snake case, joined by `_`; a method's path goes through its type's. Joining -//! loses where segments split (`audio_encoder::finish` and `audio::encoder_finish` are both -//! `moq_audio_encoder_finish`), and every export shares the prefix, so every name is +//! Names follow one rule: the prefix, then the item's path from the facade's root, each +//! segment in Rust's case, joined by `_`; a method's path goes through its type's, and a +//! field's getter and setter add `_get` and `_set` to it. Case keeps a type apart from a +//! function or module of its name, and Rust's verb-first names keep a method apart from a +//! field's accessors. Joining still loses where segments split (`audio_encoder::finish` +//! and `audio::encoder_finish` are both `moq_audio_encoder_finish`), so every name is //! registered, and a second item claiming one is a compile error at both spans rather //! than a silent clash. @@ -12,7 +14,6 @@ use std::collections::{BTreeMap, HashMap}; use rustc_span::Span; -use crate::config; use crate::ir::{Access, Api, Dyn, Function, Int, Receiver, Type}; /// A C name claimed twice: the name, then what claimed it first and second. @@ -92,11 +93,6 @@ type Suffixes = HashMap, String>; pub struct Abi<'a> { pub api: &'a Api, pub prefix: String, - /// C names for Rust paths, each starting with its crate's name, in place of the - /// rule's: an item's, and its methods' through it, or an instance's alone. - rename: &'a BTreeMap, - /// The renamed paths some name went through. - renamed: std::cell::RefCell>, /// Each generic item's instances, by whether it is a function and its C name, with /// the suffix that names each by its arguments. suffixes: std::cell::RefCell>, @@ -115,12 +111,10 @@ pub struct Abi<'a> { } impl<'a> Abi<'a> { - pub fn new(api: &'a Api, prefix: &str, rename: &'a BTreeMap) -> Self { + pub fn new(api: &'a Api, prefix: &str) -> Self { let mut abi = Self { api, prefix: prefix.to_string(), - rename, - renamed: Default::default(), suffixes: Default::default(), options: BTreeMap::new(), ranges: BTreeMap::new(), @@ -133,63 +127,23 @@ impl<'a> Abi<'a> { abi } - /// The C name for a Rust path, which starts with its crate's name. + /// The C name for a Rust path, which starts with the facade's root. pub fn name(&self, path: &[String]) -> String { - self.named(path).0 + std::iter::once(self.prefix.clone()) + .chain(self.segments(path)) + .collect::>() + .join("_") } - /// The C name for the item at `shown`, its path with its instances' arguments, and - /// whether a rename named the instance it is alone, which no suffix then names. - fn named(&self, shown: &[String]) -> (String, bool) { - let (segments, alone) = self.segments(shown); - let name = std::iter::once(self.prefix.clone()).chain(segments).collect::>(); - (name.join("_"), alone) - } - - /// The segments of the C name of the item at `shown` after the prefix: the longest - /// renamed part, as `exclude` matches it, as one, then each segment past it in snake - /// case; and whether the rename named the instance it is alone. - fn segments(&self, shown: &[String]) -> (Vec, bool) { - let mut segments = Vec::new(); - let mut rest = &shown[1..]; - let mut alone = false; - for (len, key) in config::keys(shown).take_while(|&(len, _)| len >= 2) { - if let Some(renamed) = self.rename.get(&key) { - alone = len == shown.len() && key.ends_with('>'); - self.renamed.borrow_mut().insert(key); - segments.push(renamed.clone()); - rest = &shown[len..]; - break; - } - // No config names a foreign type, which is named by its role instead. - if key == "serde_json::Value" { - segments.push("json".to_string()); - rest = &shown[len..]; - break; - } + /// The segments of the C name of the item at `shown` after the prefix: each of its + /// path's after the root, without an instance's arguments. + fn segments(&self, shown: &[String]) -> Vec { + // A foreign type, which no facade path names, is named by its role. + if shown == ["serde_json", "Value"] { + return vec!["Json".to_string()]; } - segments.extend( - rest.iter() - .map(|segment| snake(segment.split('<').next().unwrap_or(segment))), - ); - (segments, alone) - } - - /// The key, with `prefix` before it, of the rename naming the item at `shown` itself - /// rather than a module or type it is in. - fn renames(&self, shown: &[String], prefix: &str) -> Option { - (config::keys(shown).take_while(|&(len, _)| len == shown.len())) - .map(|(_, key)| format!("{prefix}{key}")) - .find(|key| self.rename.contains_key(key)) - } - - /// The config's renames that name nothing exported. - pub fn unused_renames(&self) -> Vec<&str> { - let renamed = self.renamed.borrow(); - self.rename - .keys() - .filter(|key| !renamed.contains(*key)) - .map(String::as_str) + (shown[1..].iter()) + .map(|segment| segment.split('<').next().unwrap_or(segment).to_string()) .collect() } @@ -217,11 +171,7 @@ impl<'a> Abi<'a> { /// the type's every instance, whichever kind each is. fn type_instance(&self, path: &[String], shown: &[String], args: &[Type]) -> String { let api = self.api; - let (name, alone) = self.named(shown); - if alone { - return name; - } - self.instance(false, name, args, || { + self.instance(false, self.name(shown), args, || { let records = api.records.iter().map(|r| (&r.path, &r.args)); let enums = api.enums.iter().map(|e| (&e.path, &e.args)); let objects = api.objects.iter().map(|o| (&o.path, &o.args)); @@ -265,10 +215,7 @@ impl<'a> Abi<'a> { Type::Ref(_, inner) | Type::Box(inner) => return self.arg_forms(inner), ty => return vec![self.key(ty)], }; - let (segments, alone) = self.segments(shown); - if alone { - return segments; - } + let segments = self.segments(shown); let nested: Vec> = args.iter().map(|arg| self.arg_forms(arg)).collect(); let steps = nested.iter().map(Vec::len).max().unwrap_or(1); let form = |len: usize, step: usize| { @@ -283,47 +230,23 @@ impl<'a> Abi<'a> { forms } - /// A function's C name: a method's through its type's, unless renamed itself, then - /// each argument of the instance it is. A field is renamed by `field@` and its path, - /// or by its path unless a method or constant takes it, and its setter is named as it - /// is, with `set_` after its type's name. + /// A function's C name: a method's through its type's, then each argument of the + /// instance it is. A field's getter adds `_get`, and its setter `_set`. fn function_name(&self, function: &Function, owner: Option<&Ty>) -> String { - let owner_name = match owner { + let owner = match owner { Some(Ty::Struct(owner) | Ty::Enum(owner) | Ty::Handle { name: owner, .. }) => Some(owner.as_str()), _ => None, }; - let Access::Field { shared, set } = function.access else { - return self.method_name(function, owner_name, false); - }; - let name = match self.renames(&function.shown, "field@") { - Some(key) => { - let renamed = format!("{}_{}", self.prefix, self.rename[&key]); - self.renamed.borrow_mut().insert(key); - renamed - } - None => self.method_name(function, owner_name, shared), + let name = match owner { + Some(owner) => format!("{owner}_{}", function.name()), + None => self.name(&function.shown), }; - if !set { - return name; + match function.access { + Access::Field { set: false, .. } => return format!("{name}_get"), + Access::Field { set: true, .. } => return format!("{name}_set"), + _ => {} } - // A rename need not start with the type's name, but always with the prefix. - let at = owner_name - .filter(|owner| name.starts_with(&format!("{owner}_"))) - .unwrap_or(&self.prefix); - format!("{at}_set_{}", &name[at.len() + 1..]) - } - - /// A function's C name through its type's, unless renamed by its path and no method - /// or constant `shared` its name, then each argument of the instance it is, unless - /// renamed alone. - fn method_name(&self, function: &Function, owner: Option<&str>, shared: bool) -> String { - let (name, alone) = match owner { - Some(owner) if shared || self.renames(&function.shown, "").is_none() => { - (format!("{owner}_{}", snake(function.name())), false) - } - _ => self.named(&function.shown), - }; - let Some(instance) = function.instance.as_ref().filter(|_| !alone) else { + let Some(instance) = function.instance.as_ref() else { return name; }; self.instance(true, name, &instance.named, || { @@ -658,7 +581,7 @@ impl<'a> Abi<'a> { self.claim(format!("{name}_tag"), what.clone(), e.span); // Each variant's payload, which C++ names even when it has no fields. for variant in &e.variants { - self.claim(format!("{name}_{}", snake(&variant.name)), what.clone(), e.span); + self.claim(payload(&name, &variant.name), what.clone(), e.span); } } self.claim_traits(&name, &what, e.span, e.traits, !plain); @@ -865,6 +788,28 @@ fn shortest(group: &[Vec>]) -> Vec { } } +/// A name in Pascal case, word by word as [`snake`] splits it: `track_Producer` and +/// `TrackProducer` are `TrackProducer`, and `BOXED` is `Boxed`. +pub fn pascal(name: &str) -> String { + snake(name) + .split('_') + .filter(|s| !s.is_empty()) + .map(|s| { + let mut chars = s.chars(); + match chars.next() { + Some(first) => first.to_uppercase().chain(chars).collect(), + None => String::new(), + } + }) + .collect() +} + +/// The C name of the struct of an enum's variant's fields: its own in Rust's case, after +/// the enum's. +pub fn payload(name: &str, variant: &str) -> String { + format!("{name}_{variant}") +} + /// A Rust identifier in snake case: `TrackProducer` is `track_producer`, and a name /// already in snake case stays as it is. pub fn snake(name: &str) -> String { diff --git a/ffi/src/c.rs b/ffi/src/c.rs index 104559e20..b7ef05793 100644 --- a/ffi/src/c.rs +++ b/ffi/src/c.rs @@ -335,7 +335,7 @@ impl Header<'_> { )); let mut members = Vec::new(); for variant in e.variants.iter().filter(|v| !v.fields.is_empty()) { - let payload = format!("{name}_{}", abi::snake(&variant.name)); + let payload = abi::payload(name, &variant.name); let fields: Vec = variant.fields.iter().map(|f| abi.ty(&f.ty)).collect(); for field in &fields { self.value(field); diff --git a/ffi/src/census.rs b/ffi/src/census.rs index fca7ffedf..8ad5e7f28 100644 --- a/ffi/src/census.rs +++ b/ffi/src/census.rs @@ -1,4 +1,4 @@ -//! The census: what an exported crate's `pub` API exports, and every item the export +//! The census: what the facade's `pub` API exports, and every item the export //! refuses, ranked by why, so the next mapping to build is picked from numbers. use std::collections::BTreeMap; @@ -10,10 +10,10 @@ use crate::abi::Clash; use crate::collect::{Refusal, Report}; use crate::ir::{Access, Api, Function}; -/// The census of one export as Markdown, `[crates.]` in the config unless it is -/// the configured package; `at` locates a span. -pub fn render(api: &Api, collected: &Report, top: bool, clashes: &[Clash], at: &dyn Fn(Span) -> String) -> String { - let ours = |path: &[String]| path[0] == collected.krate; +/// The census of the facade as Markdown; `at` locates a span. +pub fn render(api: &Api, collected: &Report, clashes: &[Clash], at: &dyn Fn(Span) -> String) -> String { + // Not a foreign type's. + let ours = |path: &[String]| path[0] == collected.root; let records: Vec<_> = api.records.iter().filter(|r| ours(&r.path)).collect(); let enums: Vec<_> = api.enums.iter().filter(|e| ours(&e.path)).collect(); let objects: Vec<_> = api.objects.iter().filter(|o| ours(&o.path)).collect(); @@ -140,11 +140,7 @@ pub fn render(api: &Api, collected: &Report, top: bool, clashes: &[Clash], at: & if !collected.refusals.is_empty() { let _ = writeln!(out, "\n## The config that exports what maps\n"); - let table = match top { - true => String::new(), - false => format!("[crates.{}]\n", collected.package), - }; - let _ = writeln!(out, "```toml\n{table}exclude = ["); + let _ = writeln!(out, "```toml\nexclude = ["); let mut paths: Vec<&str> = collected.refusals.iter().map(|r| r.path.as_str()).collect(); paths.sort(); paths.dedup(); @@ -153,11 +149,6 @@ pub fn render(api: &Api, collected: &Report, top: bool, clashes: &[Clash], at: & } let _ = writeln!(out, "]\n```"); } - // A clash names each claimant by its Rust path, which starts with its crate's name. - let claims = |what: &str| what.contains(&format!("`{}::", collected.krate)); - let clashes: Vec<_> = (clashes.iter()) - .filter(|(_, (first, _), (second, _))| claims(first) || claims(second)) - .collect(); if !clashes.is_empty() { let _ = writeln!(out, "\n## C names claimed twice\n"); for (name, (first, _), (second, span)) in clashes { @@ -197,15 +188,6 @@ pub fn render(api: &Api, collected: &Report, top: bool, clashes: &[Clash], at: & let _ = writeln!(out, "- `{path}`"); } } - if !collected.reexports.is_empty() { - let _ = writeln!( - out, - "\n## Re-exports of another export's items, which name that export's\n" - ); - for path in &collected.reexports { - let _ = writeln!(out, "- `{path}`"); - } - } out } diff --git a/ffi/src/cli.rs b/ffi/src/cli.rs index 7e1ea66b6..ce24f8682 100644 --- a/ffi/src/cli.rs +++ b/ffi/src/cli.rs @@ -10,14 +10,14 @@ use crate::config::{self, Config}; const USAGE: &str = "usage: rust2-ffi --config (--out | --census ) [--manifest-path ] [-- ] -Exports the pub API of the library of the package that ffi.toml names, and of the other -packages it lists, into one library: a crate in that builds the C ABI, with its C -header in /include. The package's Cargo.toml is beside ffi.toml unless ---manifest-path says where. Cargo args, such as --features, are passed to `cargo check`, -on the host. +Exports the pub API of the package's library, following its `pub use` re-exports into +other crates, as one library: a crate in that builds the C ABI, with its C header +in /include, every C name prefixed by the library's crate name. ffi.toml says what +to leave out. The package's Cargo.toml is beside ffi.toml unless --manifest-path says +where. Cargo args, such as --features, are passed to `cargo check`, on the host. ---census writes a Markdown report per package, /.md, of what exports and -of every construct the export refuses, instead of exporting."; +--census writes a Markdown report, /.md, of what exports and of every +construct the export refuses, instead of exporting."; pub fn run(args: &[String]) -> ExitCode { match try_run(args) { @@ -33,16 +33,18 @@ fn try_run(args: &[String]) -> Result { let Some(args) = Args::parse(args, USAGE)? else { return Ok(ExitCode::SUCCESS); }; - let config = Config::load(&args.config)?; - let (crates, patch) = crates(&args.manifest, &config, &args.cargo)?; + // Read now, so a malformed config fails before the check. + Config::load(&args.config)?; + let (facade, patch) = facade(&args.manifest, &args.cargo)?; let output = match &args.output { Output::Dir(out) => (config::env::OUT, out.as_os_str()), Output::Census(census) => (config::env::CENSUS, census.as_os_str()), }; + let line = facade.line(); Check { tool: "rust2-ffi", manifest: &args.manifest, - package: &config.package, + package: &facade.name, root: None, // The host's, whatever cargo's config names: a C ABI is the platform's, and the // exports are found on it. @@ -50,7 +52,7 @@ fn try_run(args: &[String]) -> Result { args: &args.cargo, env: &[ (config::env::CONFIG, args.config.as_os_str()), - (config::env::CRATES, crates.as_ref()), + (config::env::PACKAGE, line.as_ref()), (config::env::PATCH, patch.as_ref()), output, ], @@ -58,9 +60,9 @@ fn try_run(args: &[String]) -> Result { .run() } -/// Each export's package, as [`config::env::CRATES`] lists them: the configured package -/// first. Then the patches it resolved with, as [`config::env::PATCH`] has them. -fn crates(manifest: &Path, config: &Config, cargo: &[String]) -> Result<(String, String), String> { +/// The facade, the package of `manifest`, then the patches it resolved with, as +/// [`config::env::PATCH`] has them. +fn facade(manifest: &Path, cargo: &[String]) -> Result<(config::Package, String), String> { let picks = Picks::parse(cargo)?; // On the host, so the resolved dependencies are the ones the check passes rustc. let host = rustc_session::config::host_tuple(); @@ -74,96 +76,25 @@ fn crates(manifest: &Path, config: &Config, cargo: &[String]) -> Result<(String, std::fs::canonicalize(manifest).map_err(|err| format!("resolving {}: {err}", manifest.display()))?; // Cargo keeps a symlink's spelling of the path. let top = (metadata.packages.iter()) - .find(|p| p.name == config.package && std::fs::canonicalize(&p.manifest_path).is_ok_and(|p| p == canonical)); + .find(|p| p.source.is_none() && std::fs::canonicalize(&p.manifest_path).is_ok_and(|p| p == canonical)); let Some(top) = top else { - return Err(format!( - "{} is not the package of {}", - config.package, - manifest.display() - )); + return Err(format!("{} is no package's manifest", manifest.display())); + }; + let lib = (top.targets.iter()).find(|t| t.kind.iter().any(|k| k.contains("lib"))); + let Some(lib) = lib else { + return Err(format!("{} has no library", top.name)); + }; + picks.of_facade(top)?; + let facade = config::Package { + name: top.name.clone(), + lib: lib.name.replace('-', "_"), + dir: manifest.parent().expect("a manifest path has a parent").into(), + features: Vec::new(), }; - // What the library builds with on the host, each package as ` v`, then - // its directory in parentheses for a path package. Metadata's graph would also hold - // what dev-dependencies' features add, and a workspace may hold another version of a - // package beside it. - let tree = [ - "tree", - // The check's package, not the workspace's default members. - "--package", - &config.package, - "--edges", - "normal", - "--target", - rustc_session::config::host_tuple(), - "--prefix", - "none", - "--format", - "{p}", - // Plain, whatever cargo's config says, so a repeat's `(*)` reads as one. - "--color", - "never", - ]; - let output = run_cargo(manifest, &tree, &picks)?; - let output = String::from_utf8(output).map_err(|err| format!("reading cargo tree: {err}"))?; - let reached: BTreeSet<&str> = output.lines().map(|line| line.trim_end_matches(" (*)")).collect(); - let features = picks.of_exports(top, config)?; - let mut lines = String::new(); - for name in std::iter::once(&config.package).chain(config.crates.keys()) { - let mut found = reached.iter().filter(|p| p.split(' ').next() == Some(name.as_str())); - let (Some(&found), None) = (found.next(), found.next()) else { - return Err(format!( - "{name} is not exactly one package that {} builds with", - config.package - )); - }; - // The glue depends on each by path, so it builds the crate the export checked. - let package = (metadata.packages.iter()).find(|p| { - let dir = p.manifest_path.parent().expect("a manifest path has a parent"); - p.source.is_none() && found == format!("{} v{} ({})", p.name, p.version, dir.display()) - }); - let Some(package) = package else { - return Err(format!("{name} is not a path package, which the export can depend on")); - }; - let lib = (package.targets.iter()).find(|t| t.kind.iter().any(|k| matches!(k.as_str(), "lib" | "rlib"))); - let Some(lib) = lib else { - return Err(format!("{name} has no library")); - }; - let dir = package.manifest_path.parent().expect("a manifest path has a parent"); - // Another export by the name the configured package depends on it by, unique among - // its dependencies: crates of two packages may share a name. - let node = metadata.resolve.nodes.iter().find(|n| n.id == top.id); - let krate = match package.id == top.id { - true => Some(lib.name.clone()), - false => (node.into_iter().flat_map(|n| &n.deps)) - .filter(|d| d.pkg == package.id && d.dep_kinds.iter().any(|k| k.kind.is_none())) - .map(|d| d.name.clone()) - .min(), - }; - let Some(krate) = krate else { - return Err(format!( - "{name} is not a dependency of {}, which names each export", - config.package - )); - }; - let package = config::Package { - name: name.clone(), - krate: krate.replace('-', "_"), - dir: dir.into(), - features: features.get(name.as_str()).cloned().unwrap_or_default(), - }; - if lines - .lines() - .filter_map(|l| l.split('\t').next()) - .any(|n| n.replace('-', "_") == package.root()) - { - return Err(format!("{name} is another export's name in snake case")); - } - lines.push_str(&package.line()); - } // Every platform's dependencies, which cargo resolves wherever the glue builds. let output = run_cargo(manifest, &["metadata", "--format-version", "1"], &picks)?; let resolved: Metadata = serde_json::from_slice(&output).map_err(|err| format!("reading cargo metadata: {err}"))?; - Ok((lines, overrides(&resolved, top)?)) + Ok((facade, overrides(&resolved, top)?)) } /// The `[patch]` the glue's workspace resolves the exports by as the check did, wherever @@ -346,30 +277,21 @@ impl<'a> Picks<'a> { args } - /// The features named in another export directly, as `/`, by - /// package. Cargo unifies in the rest through the configured package's own. - fn of_exports(&self, top: &'a Package, config: &Config) -> Result>, String> { - let mut features: BTreeMap<&str, Vec> = BTreeMap::new(); + /// Refuse a feature named in a dependency, `/`: the generated + /// crate depends on the facade alone, which enables what its dependencies build with. + fn of_facade(&self, top: &Package) -> Result<(), String> { for feature in &self.named { - let Some((key, name)) = feature.split_once('/') else { - continue; - }; - let key = key.strip_suffix('?').unwrap_or(key); - let dep = - (top.dependencies.iter()).find(|d| d.kind.is_none() && d.rename.as_deref().unwrap_or(&d.name) == key); - // Cargo refuses a feature of no dependency. - let Some(dep) = dep else { + let Some((key, _)) = feature.split_once('/') else { continue; }; - if !config.crates.contains_key(&dep.name) { + if key.strip_suffix('?').unwrap_or(key) != top.name { return Err(format!( - "--features {feature}: the export builds {} with only the features {} enables in it, unless it is an export too", - dep.name, config.package + "--features {feature}: the export builds {}'s dependencies with only the features it enables in them", + top.name )); } - features.entry(&dep.name).or_default().push(name.to_string()); } - Ok(features) + Ok(()) } } diff --git a/ffi/src/collect.rs b/ffi/src/collect.rs index e10fec2a9..0d8ed45da 100644 --- a/ffi/src/collect.rs +++ b/ffi/src/collect.rs @@ -1,8 +1,8 @@ -//! The `pub` API of each exported crate, as rustc checked it, to the [IR](crate::ir). +//! The `pub` API of the facade, as rustc checked it, to the [IR](crate::ir). //! -//! Every `pub` item reachable from an exported crate's root is exported by fixed rules, or -//! refused with its span. An export names another's types across crates; another crate's -//! type is refused. Nothing is opted in by attribute and nothing is skipped silently: the +//! Every `pub` item reachable from the facade's root, through its re-exports into other +//! crates too, is exported by fixed rules, or refused with its span. A type it does not +//! reach is refused. Nothing is opted in by attribute and nothing is skipped silently: the //! config's `exclude` is the only way to leave an item out, and `#[doc(hidden)]` items, //! which are no part of the documented API, are listed in the census. A generic type //! exports at the instances it infers, before any type is mapped: its defaults, each @@ -12,7 +12,7 @@ use std::collections::VecDeque; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{CrateNum, DefId}; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::util::elaborate; use rustc_infer::traits::{Obligation, ObligationCause}; @@ -69,13 +69,11 @@ pub struct Refusal { /// What collection found. pub struct Collected { pub api: Api, - /// Each export's, in the order given. - pub reports: Vec, + pub report: Report, } -/// An exported crate. -pub struct Crate<'a> { - pub krate: CrateNum, +/// The facade, the local crate. +pub struct Facade<'a> { /// The first segment of its items' paths in the IR. pub root: String, pub package: &'a str, @@ -83,11 +81,11 @@ pub struct Crate<'a> { pub exclude: &'a [String], } -/// What collection found in one exported crate, by paths from its root. +/// What collection found in the facade, by paths from its root. pub struct Report { pub package: String, - /// Its crate's root, the first segment of its items' paths in the IR. - pub krate: String, + /// The facade's root, the first segment of its items' paths in the IR. + pub root: String, pub refusals: Vec, /// Records and enums exported as objects, since a field does not map: the first. pub opaque: Vec<(String, Why)>, @@ -99,8 +97,6 @@ pub struct Report { pub excluded: Vec<(String, usize)>, /// Type aliases, which export nothing of their own. pub aliases: Vec, - /// Re-exports of another export's items, which name that export's. - pub reexports: Vec, } /// How an exported crate's type is exported, once decided. @@ -159,9 +155,9 @@ struct Collector<'tcx> { /// Whether instances are still being inferred, before any type is declared: each type /// then counts as mapping, so inference takes every choice typing could. inferring: bool, - /// The exported crates, the configured one first, and what collection found in each. + /// The crates of the items the walk reached, the facade's first. crates: Vec, - reports: Vec, + report: Report, /// Items the walk left out, and why: `excluded` or `doc(hidden)`. left_out: rustc_data_structures::fx::FxHashMap, /// The type parameters of the function being mapped that take a foreign @@ -179,8 +175,8 @@ struct Collector<'tcx> { api: Api, } -/// Collect the exported crates' API, leaving out what each one's `exclude` names. -pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, crates: &[Crate<'_>]) -> Collected { +/// Collect the facade's API, leaving out what its `exclude` names. +pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, facade: &Facade<'_>) -> Collected { // A type at a crate's root, by the crate's name and its own. let root = |krate: &str, name: &str| { let krate = *tcx.crates(()).iter().find(|&&k| tcx.crate_name(k).as_str() == krate)?; @@ -191,7 +187,6 @@ pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, crates: &[Crate<'_>]) -> Collected { let bytes = root("bytes", "Bytes"); let foreign: Vec = (FOREIGN.iter()) .filter_map(|&(krate, name)| root(krate, name)) - .filter(|ty| !crates.iter().any(|c| c.krate == ty.krate)) .collect(); let range_bounds = tcx.get_diagnostic_item(rustc_span::Symbol::intern("RangeBounds")); // `RangeBounds::start_bound` returns a `Bound`, which has no diagnostic item of its own. @@ -210,28 +205,24 @@ pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, crates: &[Crate<'_>]) -> Collected { tcx, items: Vec::new(), decls: Default::default(), - crates: crates.iter().map(|c| c.krate).collect(), - reports: crates - .iter() - .map(|c| Report { - package: c.package.to_string(), - krate: c.root.clone(), - refusals: Vec::new(), - opaque: Vec::new(), - twins: Vec::new(), - hidden: Vec::new(), - excluded: c.exclude.iter().map(|path| (path.clone(), 0)).collect(), - aliases: Vec::new(), - reexports: Vec::new(), - }) - .collect(), + crates: vec![LOCAL_CRATE], + report: Report { + package: facade.package.to_string(), + root: facade.root.clone(), + refusals: Vec::new(), + opaque: Vec::new(), + twins: Vec::new(), + hidden: Vec::new(), + excluded: facade.exclude.iter().map(|path| (path.clone(), 0)).collect(), + aliases: Vec::new(), + }, instances: Vec::new(), rejected: Default::default(), inferring: true, left_out: Default::default(), impls: Default::default(), bytes, - foreign: foreign.iter().map(|&ty| (ty, 0)).collect(), + foreign: Vec::new(), bound, range_bounds, orphans: orphans.clone(), @@ -245,6 +236,12 @@ pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, crates: &[Crate<'_>]) -> Collected { }, }; c.walk(); + // A foreign type the facade re-exports is its own export, whatever else of its + // crate the facade reaches. + c.foreign = (foreign.iter()) + .filter(|&&ty| !c.items.iter().any(|(item, _)| *item == ty)) + .map(|&ty| (ty, 0)) + .collect(); c.infer(); c.declare(); c.define(); @@ -261,7 +258,7 @@ pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, crates: &[Crate<'_>]) -> Collected { if more.is_empty() { return Collected { api: c.api, - reports: c.reports, + report: c.report, }; } orphans.extend(more); @@ -269,15 +266,12 @@ pub fn collect<'tcx>(tcx: TyCtxt<'tcx>, crates: &[Crate<'_>]) -> Collected { } impl<'tcx> Collector<'tcx> { - /// What collection found in the crate `path` starts with, and the rest of `path`. + /// What collection found, and `path` without the facade's root. fn report(&mut self, path: &[String]) -> (&mut Report, String) { - let report = (self.reports.iter_mut()) - .find(|r| r.krate == path[0]) - .expect("a path starts with its exported crate"); - (report, path[1..].join("::")) + (&mut self.report, path[1..].join("::")) } - /// Whether `krate` is exported. + /// Whether the walk reached an item of `krate`. fn exported(&self, krate: CrateNum) -> bool { self.crates.contains(&krate) } @@ -285,7 +279,7 @@ impl<'tcx> Collector<'tcx> { /// Whether the config excludes `path`, counting the exclusion. A member of an instance /// is left out by its instance's path or its type's, which leaves it out of each. fn exclude(&mut self, path: &[String]) -> bool { - let (report, _) = self.report(path); + let report = &mut self.report; for (_, key) in config::keys(&path[1..]) { if let Some((_, count)) = report.excluded.iter_mut().find(|(excluded, _)| *excluded == key) { *count += 1; @@ -696,82 +690,76 @@ impl<'tcx> Collector<'tcx> { report.refusals.push(Refusal { path, kind, span, why }); } - /// Every `pub` item reachable from each exported crate's root, breadth first so each - /// keeps its shortest path, which starts with its crate's root. + /// Every `pub` item reachable from the facade's root, through re-exports of other + /// crates' items and modules too, breadth first so each keeps its shortest path, which + /// starts with the facade's root. fn walk(&mut self) { let tcx = self.tcx; - for (i, krate) in self.crates.clone().into_iter().enumerate() { - // Per export: two may re-export one item, and each refuses or excludes it. - let mut seen = rustc_data_structures::fx::FxHashSet::default(); - let mut queue = VecDeque::from([(krate.as_def_id(), vec![self.reports[i].krate.clone()])]); - while let Some((module, path)) = queue.pop_front() { - let children = match module.as_local() { - Some(local) => tcx.module_children_local(local), - None => tcx.module_children(module), + let mut seen = rustc_data_structures::fx::FxHashSet::default(); + let mut queue = VecDeque::from([(LOCAL_CRATE.as_def_id(), vec![self.report.root.clone()])]); + while let Some((module, path)) = queue.pop_front() { + let children = match module.as_local() { + Some(local) => tcx.module_children_local(local), + None => tcx.module_children(module), + }; + for child in children { + if !child.vis.is_public() || child.ident.name == rustc_span::kw::Underscore { + continue; + } + let Res::Def(kind, def_id) = child.res else { + continue; }; - for child in children { - if !child.vis.is_public() || child.ident.name == rustc_span::kw::Underscore { - continue; - } - let Res::Def(kind, def_id) = child.res else { - continue; - }; - // Variants and constructors come along with their type. - if matches!(kind, DefKind::Ctor(..) | DefKind::Variant) { - continue; - } - let mut path = path.clone(); - path.push(child.ident.to_string()); - // Another export's item, which that export walks from its own root. - if def_id.krate != krate && self.exported(def_id.krate) { - let (report, rest) = self.report(&path); - report.reexports.push(rest); - continue; - } - if !seen.insert(def_id) { - continue; - } - if self.exclude(&path) { - self.left_out.insert(def_id, "excluded type"); - continue; - } - if tcx.is_doc_hidden(def_id) { + // Variants and constructors come along with their type. + if matches!(kind, DefKind::Ctor(..) | DefKind::Variant) { + continue; + } + if !seen.insert(def_id) { + continue; + } + let mut path = path.clone(); + path.push(child.ident.to_string()); + if self.exclude(&path) { + self.left_out.insert(def_id, "excluded type"); + continue; + } + if tcx.is_doc_hidden(def_id) { + let (report, rest) = self.report(&path); + report.hidden.push(rest); + self.left_out.insert(def_id, "doc(hidden) type"); + continue; + } + let span = child + .reexport_chain + .first() + .and_then(|reexport| reexport.id()) + .map_or_else(|| tcx.def_span(def_id), |id| tcx.def_span(id)); + // Its types map by role, not as items of the facade's. + let krate = tcx.crate_name(def_id.krate); + if matches!(krate.as_str(), "std" | "core" | "alloc") { + let why = why("re-export of the standard library's item", krate.to_string()); + self.refuse(&path, "use", span, vec![why]); + continue; + } + match kind { + DefKind::Mod => queue.push_back((def_id, path)), + DefKind::TyAlias => { let (report, rest) = self.report(&path); - report.hidden.push(rest); - self.left_out.insert(def_id, "doc(hidden) type"); - continue; - } - let span = child - .reexport_chain - .first() - .and_then(|reexport| reexport.id()) - .map_or_else(|| tcx.def_span(def_id), |id| tcx.def_span(id)); - if def_id.krate != krate { - let other = tcx.crate_name(def_id.krate); - self.refuse( - &path, - "use", - span, - vec![why("re-export of another crate's item", other.to_string())], - ); - continue; + report.aliases.push(rest); } - match kind { - DefKind::Mod => queue.push_back((def_id, path)), - DefKind::TyAlias => { - let (report, rest) = self.report(&path); - report.aliases.push(rest); + DefKind::Macro(_) => self.refuse(&path, "macro", span, vec![why("macro", "")]), + DefKind::Static { .. } => self.refuse(&path, "static", span, vec![why("static", "")]), + DefKind::Struct + | DefKind::Enum + | DefKind::Union + | DefKind::Trait + | DefKind::Fn + | DefKind::Const => { + if !self.crates.contains(&def_id.krate) { + self.crates.push(def_id.krate); } - DefKind::Macro(_) => self.refuse(&path, "macro", span, vec![why("macro", "")]), - DefKind::Static { .. } => self.refuse(&path, "static", span, vec![why("static", "")]), - DefKind::Struct - | DefKind::Enum - | DefKind::Union - | DefKind::Trait - | DefKind::Fn - | DefKind::Const => self.items.push((def_id, path)), - _ => self.refuse(&path, "item", span, vec![why("item kind", format!("{kind:?}"))]), + self.items.push((def_id, path)) } + _ => self.refuse(&path, "item", span, vec![why("item kind", format!("{kind:?}"))]), } } } diff --git a/ffi/src/config.rs b/ffi/src/config.rs index 318381cdd..f9536a010 100644 --- a/ffi/src/config.rs +++ b/ffi/src/config.rs @@ -1,51 +1,24 @@ -//! The `ffi.toml` that says what to export. +//! The `ffi.toml` that says what to leave out, and the facade package the CLI resolves. -use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use serde::Deserialize; -/// What to export: one package's crate, and other packages' it depends on, into one -/// library. +/// What the export leaves out of the facade's API. #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct Config { - /// The cargo package whose library is checked, and exported first. - pub package: String, - /// The prefix of every C name, the package's name in snake case by default. - pub prefix: Option, - /// Paths from the crate root left out of the export: a module, an item, or one + /// Paths from the facade's root left out of the export: a module, an item, or one /// method or field (`track::Producer::poll_closed`). Each must name something /// exported. As in rustdoc, a path a method or constant shares with a field names the /// method, and `field@` before it names the field. #[serde(default)] pub exclude: Vec, - /// C names, without the prefix, for paths from the crate root whose names the rule - /// makes clash: `"session::Stats" = "session_stats_snapshot"`. A type's methods are - /// named through its new name. Paths match as `exclude`'s do, so an instance's renames - /// it alone, and a field is named by its path as `exclude` names it. - #[serde(default)] - pub rename: BTreeMap, - /// Other packages exported into the same library, each a dependency of `package`, by - /// name: an export names another's types across crates. - #[serde(default)] - pub crates: BTreeMap, -} - -/// What leaves another export's items out and renames them, as `exclude` and `rename` do -/// the package's. -#[derive(Debug, Default, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "kebab-case")] -pub struct Items { - #[serde(default)] - pub exclude: Vec, - #[serde(default)] - pub rename: BTreeMap, } -/// The keys `exclude` and `rename` name the item at `shown` by, its path with its -/// instances' arguments, longest first: each prefix of it as is, then without the -/// arguments, which names the generic item's every instance. Each comes with its length. +/// The keys `exclude` names the item at `shown` by, its path with its instances' +/// arguments, longest first: each prefix of it as is, then without the arguments, which +/// names the generic item's every instance. Each comes with its length. pub fn keys(shown: &[String]) -> impl Iterator + '_ { (1..=shown.len()).rev().flat_map(move |len| { let exact = shown[..len].join("::"); @@ -58,66 +31,44 @@ pub fn keys(shown: &[String]) -> impl Iterator + '_ { impl Config { pub fn load(path: &Path) -> Result { let text = std::fs::read_to_string(path).map_err(|err| format!("reading {}: {err}", path.display()))?; - let config: Self = toml::from_str(&text).map_err(|err| format!("parsing {}: {err}", path.display()))?; - if config.crates.contains_key(&config.package) { - return Err(format!( - "{}: [crates.{}] names the package itself, whose `exclude` and `rename` are top-level", - path.display(), - config.package - )); - } - Ok(config) - } - - /// The prefix of every C name. - pub fn prefix(&self) -> String { - self.prefix.clone().unwrap_or_else(|| self.package.replace('-', "_")) + toml::from_str(&text).map_err(|err| format!("parsing {}: {err}", path.display())) } } -/// An export's package, as the CLI resolves it for the wrapper. +/// The facade: the package whose library's `pub` API is exported, as the CLI resolves it +/// for the wrapper. pub struct Package { pub name: String, - /// The name the configured package calls its library by, which binds another export - /// to the crate rustc loaded: the configured package's own crate name. - pub krate: String, + /// Its library's crate name, Cargo's `[lib] name`: the prefix of every C name. + pub lib: String, pub dir: PathBuf, - /// The features the generated crate enables in it, beyond what the configured - /// package's enable. + /// The features rustc compiles it with, which the generated crate enables in it. pub features: Vec, } impl Package { /// The first segment of its items' paths in the IR, which the glue depends on it by: - /// its name in snake case, unique among the exports, where the names the configured - /// package calls them by may coincide with each other and its own. + /// its crate's name, as Rust code spells its paths. pub fn root(&self) -> String { - self.name.replace('-', "_") + self.lib.clone() } - /// One line of [`env::CRATES`]. + /// [`env::PACKAGE`]'s value. pub fn line(&self) -> String { - let dir = self.dir.display(); - format!("{}\t{}\t{dir}\t{}\n", self.name, self.krate, self.features.join(",")) + format!("{}\t{}\t{}", self.name, self.lib, self.dir.display()) } - /// The packages [`env::CRATES`] lists, a line each. - pub fn parse(lines: &str) -> Result, String> { - (lines.lines()) - .map(|line| match line.split('\t').collect::>()[..] { - [name, krate, dir, features] => Ok(Self { - name: name.to_string(), - krate: krate.to_string(), - dir: PathBuf::from(dir), - features: features - .split(',') - .filter(|f| !f.is_empty()) - .map(str::to_string) - .collect(), - }), - _ => Err(format!("{} holds a malformed line: {line}", env::CRATES)), - }) - .collect() + /// The package [`env::PACKAGE`] holds, without its features. + pub fn parse(line: &str) -> Result { + match line.split('\t').collect::>()[..] { + [name, lib, dir] => Ok(Self { + name: name.to_string(), + lib: lib.to_string(), + dir: PathBuf::from(dir), + features: Vec::new(), + }), + _ => Err(format!("{} is malformed: {line}", env::PACKAGE)), + } } } @@ -127,11 +78,10 @@ pub mod env { pub const CONFIG: &str = "RUST2_FFI_CONFIG"; /// The output directory, absolute. pub const OUT: &str = "RUST2_FFI_OUT"; - /// The directory to write each export's census into instead of exporting, absolute. + /// The directory to write the census into instead of exporting, absolute. pub const CENSUS: &str = "RUST2_FFI_CENSUS"; - /// Each export's [`Package`](super::Package), a line each. - pub const CRATES: &str = "RUST2_FFI_CRATES"; - /// The `[patch]` resolving the configured package as cargo did, as TOML with each - /// path absolute. + /// The facade's [`Package`](super::Package). + pub const PACKAGE: &str = "RUST2_FFI_PACKAGE"; + /// The `[patch]` resolving the facade as cargo did, as TOML with each path absolute. pub const PATCH: &str = "RUST2_FFI_PATCH"; } diff --git a/ffi/src/cpp.rs b/ffi/src/cpp.rs index cdc1fa723..ca64c45d7 100644 --- a/ffi/src/cpp.rs +++ b/ffi/src/cpp.rs @@ -466,7 +466,7 @@ impl<'a> Header<'a> { let payloads: Vec = e .variants .iter() - .map(|v| self.global(&self.name(&format!("{name}_{}", abi::snake(&v.name))))) + .map(|v| self.global(&self.name(&abi::payload(&name, &v.name)))) .collect(); for payload in &payloads { self.line(format!("struct {payload};")); @@ -586,7 +586,7 @@ impl<'a> Header<'a> { for member in members { if !seen.insert(member) { self.errors.push(format!( - "two members of `{rust}` are both `{}::{class}::{member}` in C++: rename one in the config", + "two members of `{rust}` are both `{}::{class}::{member}` in C++: rename one in the source or exclude it in the config", self.ns )); } @@ -857,7 +857,7 @@ impl<'a> Header<'a> { } } for variant in &e.variants { - let payload = self.name(&format!("{c}_{}", abi::snake(&variant.name))); + let payload = self.name(&abi::payload(&c, &variant.name)); let fields: Vec = variant .fields .iter() @@ -973,7 +973,7 @@ impl<'a> Header<'a> { let mut lift = Vec::new(); for (n, variant) in e.variants.iter().enumerate() { let tag = format!("{upper}_{}", abi::snake(&variant.name).to_uppercase()); - let payload = self.name(&format!("{c}_{}", abi::snake(&variant.name))); + let payload = self.name(&abi::payload(&c, &variant.name)); let member = abi::escape(&abi::snake(&variant.name)); let mut set = vec![format!("c.tag = {tag};")]; let mut fields = Vec::new(); diff --git a/ffi/src/dart.rs b/ffi/src/dart.rs index a5d6337ec..80515947f 100644 --- a/ffi/src/dart.rs +++ b/ffi/src/dart.rs @@ -25,7 +25,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; -use crate::abi::{self, Abi, Sig, Ty}; +use crate::abi::{self, Abi, Sig, Ty, pascal}; use crate::glue::rs; use crate::ir::{Access, Family, Function, Int, Literal, Receiver, Type}; use crate::kotlin::strip_refs; @@ -363,7 +363,7 @@ impl<'a> Gen<'a> { for (name, what) in names { if name.starts_with(&space) || name == abi.rt("dart") && what != "the runtime" { self.errors.push(format!( - "{what} is `{name}` in C, which Dart's glue names: rename it in the config" + "{what} is `{name}` in C, which Dart's glue names: rename it in the source or exclude it in the config" )); } } @@ -373,11 +373,11 @@ impl<'a> Gen<'a> { fn claim(&mut self, name: &str, what: String) { if TAKEN.contains(&name) { self.errors.push(format!( - "{what} is `{name}` in Dart, which its runtime names: rename it in the config" + "{what} is `{name}` in Dart, which its runtime names: rename it in the source or exclude it in the config" )); } else if let Some(first) = self.names.insert(name.to_string(), what.clone()) { self.errors.push(format!( - "{first} and {what} are both `{name}` in Dart: rename one in the config" + "{first} and {what} are both `{name}` in Dart: rename one in the source or exclude it in the config" )); } } @@ -402,7 +402,7 @@ impl<'a> Gen<'a> { } for name in clashes { self.errors.push(format!( - "two members of `{rust}` are both `{name}` in Dart: rename one in the config" + "two members of `{rust}` are both `{name}` in Dart: rename one in the source or exclude it in the config" )); } } @@ -551,7 +551,7 @@ impl<'a> Gen<'a> { let union = format!("_{name}_u"); let mut members = Vec::new(); for variant in e.variants.iter().filter(|v| !v.fields.is_empty()) { - let payload = format!("{name}_{}", abi::snake(&variant.name)); + let payload = abi::payload(&name, &variant.name); let mut decls = Vec::new(); for field in &variant.fields { let ty = abi.ty(&field.ty); @@ -1076,7 +1076,7 @@ impl<'a> Gen<'a> { self.range(c, ty); } for constant in &api.constants { - let name = member(&self.bare(&abi.name(&constant.path)).to_lowercase(), &[]); + let name = abi::snake(self.bare(&abi.name(&constant.path))).to_uppercase(); self.claim(&name, format!("const `{}`", constant.path.join("::"))); let value = match constant.value { Literal::Bool(b) => b.to_string(), @@ -1670,7 +1670,11 @@ impl<'a> Gen<'a> { reserved.push("close"); } let snake = own.unwrap_or(&bare); - let member = member(snake, &reserved); + // A constant in upper case, as Rust names it, apart from a method of its name. + let member = match function.access { + Access::Const => abi::snake(snake).to_uppercase(), + _ => member(snake, &reserved), + }; let receiver = function.receiver.filter(|_| owner.is_some()); let mut pre = Vec::new(); @@ -2023,20 +2027,6 @@ fn int_literal(v: i128) -> String { } } -/// A snake-case name in Pascal case: `size_pair` is `SizePair`. -fn pascal(name: &str) -> String { - name.split('_') - .filter(|s| !s.is_empty()) - .map(|s| { - let mut chars = s.chars(); - match chars.next() { - Some(first) => first.to_uppercase().chain(chars).collect(), - None => String::new(), - } - }) - .collect() -} - /// A snake-case name in camel case: `into_label` is `intoLabel`, and `0` is `$0`. fn camel(name: &str) -> String { if name.starts_with(|c: char| c.is_ascii_digit()) { diff --git a/ffi/src/driver.rs b/ffi/src/driver.rs index 07cb633f6..f7fdf9e4d 100644 --- a/ffi/src/driver.rs +++ b/ffi/src/driver.rs @@ -1,12 +1,10 @@ -//! The binary as rustc: plain for every crate but the configured one, whose API it also -//! collects once rustc has checked it, with the API of each other export it depends on. +//! The binary as rustc: plain for every crate but the facade, whose API it also collects +//! once rustc has checked it, following its re-exports into the crates it depends on. -use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::process::ExitCode; use rustc_driver::{Callbacks, Compilation}; -use rustc_hir::def_id::{CrateNum, LOCAL_CRATE}; use rustc_interface::interface::Compiler; use rustc_middle::ty::TyCtxt; use rustc_span::Span; @@ -22,14 +20,13 @@ pub fn run(args: Vec) -> ExitCode { }) } -/// Compiles the configured crate, exporting its API after analysis. +/// Compiles the facade, exporting its API after analysis. struct Export { config: Config, output: Output, - /// Each export's package: the configured package first, with the features rustc - /// compiles it with. - crates: Vec, - /// The configured workspace's patches, for the glue's workspace. + /// The facade's package, with the features rustc compiles it with. + facade: config::Package, + /// The facade's workspace's patches, for the glue's workspace. patch: String, } @@ -37,20 +34,22 @@ struct Export { enum Output { /// The export, into this directory. Dir(PathBuf), - /// A census of each export, into this directory, instead of an export. + /// A census, into this directory, instead of an export. Census(PathBuf), } impl Export { - /// The job for this rustc invocation, if it compiles the configured library. + /// The job for this rustc invocation, if it compiles the facade's library. fn for_args(args: &[String]) -> Result, String> { let Some(path) = std::env::var_os(config::env::CONFIG) else { return Ok(None); }; - let config = Config::load(path.as_ref())?; - if !rust2_driver::rustc::compiles_lib(args, &config.package) { + let line = std::env::var(config::env::PACKAGE).map_err(|_| format!("{} is unset", config::env::PACKAGE))?; + let mut facade = config::Package::parse(&line)?; + if !rust2_driver::rustc::compiles_lib(args, &facade.name) { return Ok(None); } + let config = Config::load(path.as_ref())?; let output = match ( std::env::var_os(config::env::OUT), std::env::var_os(config::env::CENSUS), @@ -65,12 +64,7 @@ impl Export { )); } }; - let crates = std::env::var(config::env::CRATES).map_err(|_| format!("{} is unset", config::env::CRATES))?; - let mut crates = config::Package::parse(&crates)?; - let Some(top) = crates.first_mut() else { - return Err(format!("{} lists no package", config::env::CRATES)); - }; - top.features = args + facade.features = args .windows(2) .filter(|w| w[0] == "--cfg") .filter_map(|w| w[1].strip_prefix("feature=\"")) @@ -81,7 +75,7 @@ impl Export { Ok(Some(Self { config, output, - crates, + facade, patch, })) } @@ -92,53 +86,38 @@ impl Callbacks for Export { if tcx.dcx().has_errors().is_some() { return Compilation::Stop; } - let Some(crates) = self.crates(tcx) else { - return Compilation::Stop; + let facade = collect::Facade { + root: self.facade.root(), + package: &self.facade.name, + // A census counts the whole API, whatever the config leaves out. + exclude: match self.output { + Output::Census(_) => &[], + Output::Dir(_) => &self.config.exclude, + }, }; - let collected = collect::collect(tcx, &crates); - // Each rename, by the Rust path it names, which starts with its crate's name, after - // a field's `field@`. - let mut renames = BTreeMap::new(); - for (report, package) in collected.reports.iter().zip(&self.crates) { - for (path, name) in self.items(&package.name).1 { - let (field, path) = field(path); - renames.insert(format!("{field}{}::{path}", report.krate), name.clone()); - } - } - let abi = Abi::new(&collected.api, &self.config.prefix(), &renames); - for key in abi.unused_renames() { - let (field, key) = field(key); - let (krate, path) = key.split_once("::").expect("a rename starts with its crate"); - tcx.dcx().err(format!( - "rust2-ffi: rename `{field}{path}` in {} names nothing exported", - self.table(&collected, krate) - )); - } + let collected = collect::collect(tcx, &facade); + let report = &collected.report; + let abi = Abi::new(&collected.api, &self.facade.lib); let written = match &self.output { Output::Census(dir) => { - let written = collected.reports.iter().enumerate().try_for_each(|(i, report)| { - let text = census::render(&collected.api, report, i == 0, &abi.clashes, &|span| at(tcx, span)); - write(&dir.join(format!("{}.md", report.package)), &text) - }); + let text = census::render(&collected.api, report, &abi.clashes, &|span| at(tcx, span)); // A census reports what a crate uses, but a missed instance is the exporter's bug. - for refusal in collected.reports.iter().flat_map(|report| &report.refusals) { + for refusal in &report.refusals { if refusal.why.iter().any(|why| why.construct == collect::NOT_INFERRED) { refuse(tcx, refusal, "inference missed an instance, a bug in rust2-ffi"); } } - written + write(&dir.join(format!("{}.md", report.package)), &text) } Output::Dir(out) => { - for report in &collected.reports { - let table = self.table(&collected, &report.krate); - for refusal in &report.refusals { - refuse(tcx, refusal, &format!("exclude it in {table} to leave it out")); - } - for (path, count) in &report.excluded { - if *count == 0 { - tcx.dcx() - .err(format!("rust2-ffi: exclude `{path}` in {table} names nothing exported")); - } + for refusal in &report.refusals { + refuse(tcx, refusal, "exclude it in the config to leave it out"); + } + for (path, count) in &report.excluded { + if *count == 0 { + tcx.dcx().err(format!( + "rust2-ffi: exclude `{path}` in the config names nothing exported" + )); } } for (name, (first, first_span), (second, second_span)) in &abi.clashes { @@ -148,10 +127,6 @@ impl Callbacks for Export { format!("rust2-ffi: {second} and {first} are both `{name}` in C"), ) .with_span_note(*first_span, format!("{first} is here")) - .with_help(match first.starts_with("field ") || second.starts_with("field ") { - true => "rename or exclude one in the config, a field by `field@` and its path", - false => "rename or exclude one in the config", - }) .emit(); } if tcx.dcx().has_errors().is_some() { @@ -159,7 +134,7 @@ impl Callbacks for Export { } let glue = glue::Crate { abi: &abi, - exports: &self.crates, + facade: &self.facade, patch: &self.patch, }; glue.write(out) @@ -175,91 +150,6 @@ impl Callbacks for Export { } } -impl Export { - /// Each export's crate, or `None` after an error naming one that is not a dependency. - fn crates<'a>(&'a self, tcx: TyCtxt<'_>) -> Option>> { - let mut crates = Vec::new(); - for ( - i, - config::Package { - name: package, krate, .. - }, - ) in self.crates.iter().enumerate() - { - let found: Vec = match i { - 0 => vec![LOCAL_CRATE], - _ => { - // The artifact cargo passed for the dependency, which is the package's - // own: another package's crate may share its name and source. - let files = extern_files(tcx, krate); - (tcx.crates(()).iter().copied()) - .filter(|&cnum| { - let source = tcx.used_crate_source(cnum); - let paths = [&source.rlib, &source.rmeta, &source.dylib]; - (paths.into_iter().flatten()) - .any(|path| canonical(path).is_some_and(|p| files.contains(&p))) - }) - .collect() - } - }; - // Rust loads only the crates a crate's code names. - let [cnum] = found[..] else { - tcx.dcx().err(format!( - "rust2-ffi: [crates.{package}] needs `{}` to use its dependency `{krate}`, and it uses {} crates of it", - self.config.package, - found.len() - )); - return None; - }; - crates.push(collect::Crate { - krate: cnum, - root: self.crates[i].root(), - package, - // A census counts the whole API, whatever the config leaves out. - exclude: match self.output { - Output::Census(_) => &[], - Output::Dir(_) => self.items(package).0, - }, - }); - } - Some(crates) - } - - /// The config's `exclude` and `rename` for `package`. - fn items(&self, package: &str) -> (&[String], &BTreeMap) { - match self.config.crates.get(package) { - Some(items) => (&items.exclude, &items.rename), - None => (&self.config.exclude, &self.config.rename), - } - } - - /// Where the config says what to export from `krate`. - fn table(&self, collected: &collect::Collected, krate: &str) -> String { - match collected.reports.iter().position(|r| r.krate == krate) { - Some(0) => "the config".to_string(), - Some(i) => format!("the config's [crates.{}]", collected.reports[i].package), - None => unreachable!("every path starts with an exported crate"), - } - } -} - -/// The files `--extern =` passes, canonical. -fn extern_files(tcx: TyCtxt<'_>, name: &str) -> Vec { - let Some(entry) = tcx.sess.opts.externs.get(name) else { - return Vec::new(); - }; - match &entry.location { - rustc_session::config::ExternLocation::ExactPaths(paths) => { - paths.iter().filter_map(|p| canonical(p.canonicalized())).collect() - } - rustc_session::config::ExternLocation::FoundInLibrarySearchDirectories => Vec::new(), - } -} - -fn canonical(path: &Path) -> Option { - std::fs::canonicalize(path).ok() -} - /// Emits `refusal` as an error at its span, with every reason and its notes, then `advice`. fn refuse(tcx: TyCtxt<'_>, refusal: &collect::Refusal, advice: &str) { let reasons: Vec = refusal @@ -310,11 +200,3 @@ pub fn write(path: &Path, text: &str) -> Result<(), String> { } std::fs::write(path, text).map_err(|err| format!("writing {}: {err}", path.display())) } - -/// A config path's `field@`, if it names a field by it, and the path after it. -fn field(path: &str) -> (&str, &str) { - match path.strip_prefix("field@") { - Some(path) => ("field@", path), - None => ("", path), - } -} diff --git a/ffi/src/glue.rs b/ffi/src/glue.rs index d8548c218..d4f7a9d67 100644 --- a/ffi/src/glue.rs +++ b/ffi/src/glue.rs @@ -1,5 +1,4 @@ -//! The Rust side of the C ABI: a crate of `extern "C"` functions over the exported -//! crates, with the runtime beside it and the header in `include/`. +//! The Rust side of the C ABI: a crate of `extern "C"` functions over the facade, with the runtime beside it and the header in `include/`. //! //! Each function lifts its C arguments to Rust values, calls the Rust item by its path, //! and lowers the result. A value is copied in from the caller's memory, which Rust @@ -23,8 +22,8 @@ const BUILD: &str = "// Generated by rust2-ffi. Do not edit.\n\n\ \tpyo3_build_config::add_extension_module_link_args();\n\ }\n"; -/// The name the crate depends on an export by, its root suffixed: an export may be -/// named `bytes` or `std`, which the crate names too. +/// The name the crate depends on the facade or a foreign crate by, its root suffixed: the +/// facade may be named `bytes` or `std`, or as the crate itself, which the crate names too. fn dependency(krate: &str) -> String { format!("{krate}_export") } @@ -62,18 +61,17 @@ pub fn rs(ty: &Ty) -> String { /// The crate that builds the C ABI. pub struct Crate<'a> { pub abi: &'a Abi<'a>, - /// The exported packages, the configured one first, each a dependency of the crate. - pub exports: &'a [Package], + /// The facade, a dependency of the crate. + pub facade: &'a Package, /// The `[patch]` table the crate's own workspace resolves the exports with, as the /// check did. pub patch: &'a str, } impl Crate<'_> { - /// The exported packages, comma-separated. + /// The facade's package name. fn packages(&self) -> String { - let names: Vec<&str> = self.exports.iter().map(|p| p.name.as_str()).collect(); - names.join(", ") + self.facade.name.clone() } /// Write the crate into `out`: `Cargo.toml`, `src/`, `include/.h` and `.hpp`, @@ -82,27 +80,24 @@ impl Crate<'_> { /// `swift/.swift` with the clang module it imports the header through, and /// `go/.go`, and `dart/.dart`, whose glue its `dart` feature builds. pub fn write(&self, out: &Path) -> Result<(), String> { - // Cargo unifies in what the configured package enables in each other export. - let dependencies: String = (self.exports.iter()) - .map(|export| { - let mut features: Vec = export.features.iter().map(|f| format!("{f:?}")).collect(); - features.sort(); - format!( - "{} = {{ package = {:?}, path = {:?}, default-features = false, features = [{}] }}\n", - dependency(&export.root()), - export.name, - export.dir.display().to_string(), - features.join(", ") - ) - }) - .collect(); + // Cargo unifies in what the facade enables in its dependencies. + let facade = self.facade; + let mut features: Vec = facade.features.iter().map(|f| format!("{f:?}")).collect(); + features.sort(); + let dependencies = format!( + "{} = {{ package = {:?}, path = {:?}, default-features = false, features = [{}] }}\n", + dependency(&facade.root()), + facade.name, + facade.dir.display().to_string(), + features.join(", ") + ); // A foreign type, exported as an object when no export is its crate, resolves to the // version the exports build with, as `bytes` does: one per major version per // resolution. let foreign: String = [("url", "2"), ("serde_json", "1")] .into_iter() .filter(|(krate, _)| self.abi.api.objects.iter().any(|o| o.path[0] == *krate)) - .filter(|(krate, _)| !self.exports.iter().any(|export| export.root() == *krate)) + .filter(|(krate, _)| facade.root() != *krate) .map(|(krate, version)| { format!( "{} = {{ package = \"{krate}\", version = \"{version}\" }}\n", @@ -144,7 +139,7 @@ impl Crate<'_> { [workspace]\n\ {patch}", patch = self.patch, - package = self.exports[0].name, + package = facade.name, packages = self.packages(), lib = self.abi.prefix, ); @@ -159,7 +154,7 @@ impl Crate<'_> { abi: self.abi, packages: self.packages(), contract, - dependencies: self.exports.iter().map(|export| dependency(&export.root())).collect(), + dependency: dependency(&facade.root()), out: String::new(), }; generator.lib(); @@ -205,8 +200,8 @@ struct Gen<'a> { packages: String, /// The header's contract, which `_contract` returns. contract: u64, - /// The name the crate depends on each export by. - dependencies: Vec, + /// The name the crate depends on the facade by. + dependency: String, out: String, } @@ -490,7 +485,7 @@ impl Gen<'_> { "//! Generated by rust2-ffi: the C ABI of the pub API of {}. Do not edit.\n", self.packages )); - self.line("#![allow(non_camel_case_types, reason = \"the C names\")]"); + self.line("#![allow(non_camel_case_types, non_snake_case, reason = \"the C names\")]"); self.line("#![allow(unused_unsafe, reason = \"every conversion is in its own block\")]"); self.line("#![allow(unused_parens, reason = \"each element is lifted from `(*c)`\")]"); self.line("#![allow(clippy::all, reason = \"generated\")]"); @@ -499,12 +494,8 @@ impl Gen<'_> { self.line("#[cfg(feature = \"kotlin\")]\nmod jni;\n"); self.line("#[cfg(feature = \"python\")]\nmod python;\n"); self.line("#[cfg(feature = \"dart\")]\nmod dart;\n"); - // An export may export nothing and only enable features in the others, as a - // package standing in for a binding crate does, which cargo would call unused. - for dependency in self.dependencies.clone() { - self.line(format!("use {dependency} as _;")); - } - self.line(""); + // The facade may export nothing, which cargo would call unused. + self.line(format!("use {} as _;\n", self.dependency)); let p = self.abi.prefix.clone(); self.line(format!( @@ -759,7 +750,7 @@ impl Gen<'_> { } else { let mut members = String::new(); for (n, variant) in e.variants.iter().enumerate().filter(|(_, v)| !v.fields.is_empty()) { - let payload = format!("{name}_{}", abi::snake(&variant.name)); + let payload = abi::payload(&name, &variant.name); let mut fields = String::new(); for (k, field) in variant.fields.iter().enumerate() { let _ = writeln!(fields, "\tpub f{k}: {},", rs(&self.abi.ty(&field.ty))); @@ -801,7 +792,7 @@ impl Gen<'_> { ); continue; } - let payload = format!("{name}_{}", abi::snake(&variant.name)); + let payload = abi::payload(&name, &variant.name); let lifted: Vec = variant .fields .iter() diff --git a/ffi/src/go.rs b/ffi/src/go.rs index 028c4c01e..fc2d84ccf 100644 --- a/ffi/src/go.rs +++ b/ffi/src/go.rs @@ -25,7 +25,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; -use crate::abi::{self, Abi, Ty}; +use crate::abi::{self, Abi, Ty, pascal}; use crate::ir::{Access, Family, Function, Int, Literal, Param, Receiver, Type}; const RUNTIME: &str = include_str!("../runtime/rt.go"); @@ -43,9 +43,16 @@ pub fn generate(abi: &Abi<'_>, packages: &str, contract: u64) -> Result { tuples: BTreeSet, /// Each package-level Go name, with what claimed it. names: BTreeMap, + /// Each exported type's Go name. + classes: BTreeSet, errors: Vec, /// The struct each error type that is no exported type returns as, by name. thrown: BTreeMap, @@ -190,11 +199,11 @@ impl<'a> Gen<'a> { .is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit())); if tuple || TAKEN.contains(&name) { self.errors.push(format!( - "{what} is `{name}` in Go, which its runtime names: rename it in the config" + "{what} is `{name}` in Go, which its runtime names: rename it in the source or exclude it in the config" )); } else if let Some(first) = self.names.insert(name.to_string(), what.clone()) { self.errors.push(format!( - "{first} and {what} are both `{name}` in Go: rename one in the config" + "{first} and {what} are both `{name}` in Go: rename one in the source or exclude it in the config" )); } } @@ -205,7 +214,7 @@ impl<'a> Gen<'a> { for member in members { if !seen.insert(member) { self.errors.push(format!( - "two members of `{rust}` are both `{member}` in Go: rename one in the config" + "two members of `{rust}` are both `{member}` in Go: rename one in the source or exclude it in the config" )); } } @@ -965,7 +974,7 @@ impl<'a> Gen<'a> { if e.plain() { let consts: String = (e.variants.iter()) .map(|v| { - let variant = format!("{name}{}", pascal(&abi::snake(&v.name))); + let variant = format!("{name}{}", pascal(&v.name)); self.claim(&variant, format!("variant `{rust}::{}`", v.name)); format!("\t{variant} {name} = {}\n", v.discriminant) }) @@ -1005,8 +1014,7 @@ impl<'a> Gen<'a> { ); let mut variants = Vec::new(); for variant in &e.variants { - let snake = abi::snake(&variant.name); - let vname = format!("{name}{}", pascal(&snake)); + let vname = format!("{name}{}", pascal(&variant.name)); self.claim(&vname, format!("variant `{rust}::{}`", variant.name)); let fields: Vec<(String, String)> = (variant.fields.iter()) .map(|f| (field_name(&f.name), self.go(&f.ty))) @@ -1037,7 +1045,7 @@ impl<'a> Gen<'a> { }; self.types.push_str(&forward.write()); } - variants.push((variant, snake, vname, fields)); + variants.push((variant, vname, fields)); } for function in statics { self.types.push_str(&function.write()); @@ -1045,8 +1053,8 @@ impl<'a> Gen<'a> { // Its conversions. let mut lifts = Vec::new(); let mut lowers = Vec::new(); - for (n, (variant, snake, vname, fields)) in variants.iter().enumerate() { - let payload = format!("{c}_{snake}"); + for (n, (variant, vname, fields)) in variants.iter().enumerate() { + let payload = abi::payload(&c, &variant.name); if variant.fields.is_empty() { lifts.push(format!("\tcase {n}:\n\t\treturn {vname}{{}}\n")); lowers.push(format!("\tcase {vname}:\n\t\tc.tag = {n}\n")); @@ -1130,7 +1138,7 @@ impl<'a> Gen<'a> { let mut members = Vec::new(); let mut table = vec!["\tv.free = goRelease\n".to_string()]; for method in &callback.methods { - let member = pascal(&abi::snake(method.name())); + let member = pascal(method.name()); members.push(member.clone()); let names = locals(&method.params); let params: Vec = (method.params.iter().zip(&names)) @@ -1322,6 +1330,8 @@ impl<'a> Gen<'a> { let member = match (receiver.is_some(), member.as_str()) { // What every object or value of the type already has. (true, "Close" | "Error") => format!("{member}_"), + // A type's name, which Go's case cannot keep apart from a function's as C's does. + (false, name) if self.classes.contains(name) => format!("{member}_"), _ => member, }; @@ -1686,20 +1696,6 @@ fn c_int(int: Int) -> &'static str { } } -/// A snake-case name in Pascal case: `size_pair` is `SizePair`. -fn pascal(name: &str) -> String { - name.split('_') - .filter(|s| !s.is_empty()) - .map(|s| { - let mut chars = s.chars(); - match chars.next() { - Some(first) => first.to_uppercase().chain(chars).collect(), - None => String::new(), - } - }) - .collect() -} - /// A field's Go name: in Pascal case, and `V0`, `V1`, ... for a positional one. fn field_name(name: &str) -> String { match name.starts_with(|c: char| c.is_ascii_digit()) { diff --git a/ffi/src/kotlin.rs b/ffi/src/kotlin.rs index 75a7a3868..90e809aa7 100644 --- a/ffi/src/kotlin.rs +++ b/ffi/src/kotlin.rs @@ -23,7 +23,7 @@ use std::hash::Hasher; use rustc_data_structures::stable_hash::StableHasher; -use crate::abi::{self, Abi, Ty}; +use crate::abi::{self, Abi, Ty, pascal}; use crate::ir::{Access, Family, Function, Int, Literal, Param, Receiver, Type}; const RUNTIME: &str = include_str!("../runtime/rt.kt"); @@ -314,12 +314,12 @@ impl<'a> Gen<'a> { fn claim(&mut self, name: &str, what: String) { if TAKEN.contains(&name) { self.errors.push(format!( - "{what} is `{}.{name}` in Kotlin, which its runtime names: rename it in the config", + "{what} is `{}.{name}` in Kotlin, which its runtime names: rename it in the source or exclude it in the config", self.abi.prefix )); } else if let Some(first) = self.names.insert(name.to_string(), what.clone()) { self.errors.push(format!( - "{first} and {what} are both `{}.{name}` in Kotlin: rename one in the config", + "{first} and {what} are both `{}.{name}` in Kotlin: rename one in the source or exclude it in the config", self.abi.prefix )); } @@ -1073,7 +1073,7 @@ impl<'a> Gen<'a> { let mut glue_reads = Vec::new(); let mut glue_writes = Vec::new(); for (n, variant) in e.variants.iter().enumerate() { - let vname = pascal(&abi::snake(&variant.name)); + let vname = pascal(&variant.name); members.push(format!("class {vname}")); let fields: Vec = variant .fields @@ -1102,7 +1102,7 @@ impl<'a> Gen<'a> { write.join("\n\t\t\t") )); reads.push(format!("\t\t{n} -> {name}.{vname}({})", read.join(", "))); - let payload = format!("{c}_{}", abi::snake(&variant.name)); + let payload = abi::payload(&c, &variant.name); let fields: Vec = (0..variant.fields.len()).map(|k| format!("f{k}: r.get()")).collect(); glue_reads.push(format!( "\t\t\t{n} => Self {{ tag, u: crate::{c}_union {{ v{n}: crate::{payload} {{ {} }} }} }},", @@ -1405,7 +1405,7 @@ impl<'a> Gen<'a> { if !seen.insert(member) { let name = member.split(' ').next_back().unwrap_or(member); self.errors.push(format!( - "two members of `{rust}` are both `{name}` in Kotlin: rename one in the config" + "two members of `{rust}` are both `{name}` in Kotlin: rename one in the source or exclude it in the config" )); } } @@ -2057,20 +2057,6 @@ fn ordinal(k: usize) -> String { .map_or_else(|| format!("item{}", k + 1), |o| o.to_string()) } -/// A snake-case name in Pascal case: `size_pair` is `SizePair`. -pub(crate) fn pascal(name: &str) -> String { - name.split('_') - .filter(|s| !s.is_empty()) - .map(|s| { - let mut chars = s.chars(); - match chars.next() { - Some(first) => first.to_uppercase().chain(chars).collect(), - None => String::new(), - } - }) - .collect() -} - /// A snake-case name in camel case: `into_label` is `intoLabel`, and `0` is `_0`. fn camel(name: &str) -> String { if name.starts_with(|c: char| c.is_ascii_digit()) { diff --git a/ffi/src/python.rs b/ffi/src/python.rs index 195caa1e2..24d558bd8 100644 --- a/ffi/src/python.rs +++ b/ffi/src/python.rs @@ -25,9 +25,9 @@ use std::hash::Hasher; use rustc_data_structures::stable_hash::StableHasher; -use crate::abi::{self, Abi, Ty}; +use crate::abi::{self, Abi, Ty, pascal}; use crate::ir::{Access, Family, Function, Int, Literal, Receiver, Traits, Type}; -use crate::kotlin::{indent_by, pascal, rust_c, strip_refs}; +use crate::kotlin::{indent_by, rust_c, strip_refs}; const RUNTIME: &str = include_str!("../runtime/rt.py"); pub const GLUE_RUNTIME: &str = include_str!("../runtime/python.rs"); @@ -298,11 +298,11 @@ impl<'a> Gen<'a> { if let Some(first) = self.names.insert(name.to_string(), what.clone()) { let clash = match first.as_str() { "the runtime" => format!( - "{what} is `{}.{name}` in Python, which its runtime names: rename it in the config", + "{what} is `{}.{name}` in Python, which its runtime names: rename it in the source or exclude it in the config", self.abi.prefix ), _ => format!( - "{first} and {what} are both `{}.{name}` in Python: rename one in the config", + "{first} and {what} are both `{}.{name}` in Python: rename one in the source or exclude it in the config", self.abi.prefix ), }; @@ -815,7 +815,7 @@ impl<'a> Gen<'a> { for name in data.iter().cloned().chain(codes.filter_map(|code| member_name(code))) { if !seen.insert(name.clone()) { self.errors.push(format!( - "two members of `{rust}` are both `{name}` in Python: rename one in the config" + "two members of `{rust}` are both `{name}` in Python: rename one in the source or exclude it in the config" )); } } @@ -913,7 +913,7 @@ impl<'a> Gen<'a> { return; } // Each variant's class, and its fields, which on an instance hide the enum's members. - let mut data: Vec = e.variants.iter().map(|v| pascal(&abi::snake(&v.name))).collect(); + let mut data: Vec = e.variants.iter().map(|v| pascal(&v.name)).collect(); let fields: BTreeSet = e .variants .iter() @@ -935,9 +935,9 @@ impl<'a> Gen<'a> { let mut reads = Vec::new(); let mut writes = Vec::new(); for (n, variant) in e.variants.iter().enumerate() { - let vname = pascal(&abi::snake(&variant.name)); + let vname = pascal(&variant.name); let class = format!("_{name}_{vname}"); - let key = format!("{c}_{}", abi::snake(&variant.name)); + let key = abi::payload(&c, &variant.name); let tv = self.register(&key, class.clone()); let fields: String = variant .fields @@ -960,7 +960,7 @@ impl<'a> Gen<'a> { writes.push(format!("\t\t{n} => rt::record({tv}, vec![]),")); } false => { - let payload = format!("{c}_{}", abi::snake(&variant.name)); + let payload = abi::payload(&c, &variant.name); let mut binds = Vec::new(); let mut fields = Vec::new(); let mut lowers = Vec::new(); @@ -1223,7 +1223,7 @@ impl<'a> Gen<'a> { }; let reserved = owner_ty.map_or(&[][..], |(_, r)| r); let snake = own.unwrap_or(&bare); - let member = member(snake, reserved); + let member = member(&abi::snake(snake), reserved); // The glue: each argument read, the call, and its value lowered. let mut stmts = Vec::new(); diff --git a/ffi/src/swift.rs b/ffi/src/swift.rs index 4ad693b64..0f9f1cf69 100644 --- a/ffi/src/swift.rs +++ b/ffi/src/swift.rs @@ -18,7 +18,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; -use crate::abi::{self, Abi, Ty}; +use crate::abi::{self, Abi, Ty, pascal}; use crate::ir::{Access, Function, Int, Literal, Param, Receiver, Type}; const RUNTIME: &str = include_str!("../runtime/rt.swift"); @@ -199,11 +199,11 @@ impl<'a> Gen<'a> { fn claim(&mut self, name: &str, what: String) { if TAKEN.contains(&name.split('(').next().unwrap_or(name)) { self.errors.push(format!( - "{what} is `{name}` in Swift, which its runtime names: rename it in the config" + "{what} is `{name}` in Swift, which its runtime names: rename it in the source or exclude it in the config" )); } else if let Some(first) = self.names.insert(name.to_string(), what.clone()) { self.errors.push(format!( - "{first} and {what} are both `{name}` in Swift: rename one in the config" + "{first} and {what} are both `{name}` in Swift: rename one in the source or exclude it in the config" )); } } @@ -215,7 +215,7 @@ impl<'a> Gen<'a> { for member in members { if !seen.insert(member) { self.errors.push(format!( - "two members of `{rust}` are both `{member}` in Swift: rename one in the config" + "two members of `{rust}` are both `{member}` in Swift: rename one in the source or exclude it in the config" )); } } @@ -755,7 +755,7 @@ impl<'a> Gen<'a> { }) .collect(); let binds: Vec = (0..variant.fields.len()).map(|k| format!("let p{k}_")).collect(); - let payload = format!("{c}_{snake}"); + let payload = abi::payload(&c, &variant.name); let lowered: Vec = (variant.fields.iter().enumerate()) .map(|(k, f)| format!("{}: {}", abi::escape(&f.name), self.lower(&f.ty, &format!("p{k}_")))) .collect(); @@ -1369,20 +1369,6 @@ fn lift_int(int: Int, e: &str) -> String { } } -/// A snake-case name in Pascal case: `size_pair` is `SizePair`. -fn pascal(name: &str) -> String { - name.split('_') - .filter(|s| !s.is_empty()) - .map(|s| { - let mut chars = s.chars(); - match chars.next() { - Some(first) => first.to_uppercase().chain(chars).collect(), - None => String::new(), - } - }) - .collect() -} - /// The Swift names of parameters, in camel case, [`abi::distinct`], and quoted if a /// keyword. fn locals(params: &[Param]) -> Vec { diff --git a/ffi/tests/api.c b/ffi/tests/api.c index 50f343752..6d02122bd 100644 --- a/ffi/tests/api.c +++ b/ffi/tests/api.c @@ -44,9 +44,9 @@ static void log_free(void *data) { } /* A lent closure: adds data's amount to the counter lent for the call. */ -static bool edit(void *data, api_counter *counter) { - api_counter_add(counter, *(const uint64_t *)data, NULL, NULL); - return api_counter_edited(counter) && api_counter_get(counter) == 6; +static bool edit(void *data, api_Counter *counter) { + api_Counter_add(counter, *(const uint64_t *)data, NULL, NULL); + return api_Counter_edited(counter) && api_Counter_get(counter) == 6; } /* Lent closures borrowing their object shared: one `Sync`, one not. */ @@ -61,9 +61,9 @@ static uint32_t triple(void *data, uint32_t side) { return side * 3; } -static uint64_t visit(void *data, const api_cursor *cursor) { +static uint64_t visit(void *data, const api_Cursor *cursor) { (void)data; - return api_cursor_at(cursor) * 2; + return api_Cursor_at(cursor) * 2; } static void print_visit(void *data, uint32_t at) { @@ -71,9 +71,9 @@ static void print_visit(void *data, uint32_t at) { printf("visit %u\n", at); } -static uint32_t peek_tally(void *data, const api_tally *tally) { +static uint32_t peek_tally(void *data, const api_Tally *tally) { (void)data; - return api_tally_bump(tally); + return api_Tally_bump(tally); } /* A lent closure, which Rust calls on another thread: keeps levels above data's. */ @@ -83,16 +83,16 @@ static bool keep_line(void *data, uint8_t level, api_buf line) { } /* Breaks a lent handle's contract: frees it, or moves its value out. */ -static bool free_lent(void *data, api_counter *counter) { +static bool free_lent(void *data, api_Counter *counter) { (void)data; - api_counter_free(counter); + api_Counter_free(counter); return false; } -static bool move_lent(void *data, api_counter *counter) { +static bool move_lent(void *data, api_Counter *counter) { (void)data; - api_label label = api_counter_into_label(counter, (api_option_point){0}); - api_label_free(&label); + api_Label label = api_Counter_into_label(counter, (api_option_Point){0}); + api_Label_free(&label); return false; } @@ -156,68 +156,68 @@ static void print_bound(api_bound_u64 bound) { static int script(void) { printf("limit %llu\n", (unsigned long long)API_LIMIT); - api_point start = {.x = 1, .y = 2}; - api_point p = api_point_offset(&start, 2, 3); + api_Point start = {.x = 1, .y = 2}; + api_Point p = api_Point_offset(&start, 2, 3); printf("offset %d %d\n", p.x, p.y); - print("debug ", api_point_debug(&p), "\n"); - api_point expected = {.x = 3, .y = 5}; - printf("eq %s\n", api_point_eq(&p, &expected) ? "true" : "false"); - api_point d = api_point_default(); + print("debug ", api_Point_debug(&p), "\n"); + api_Point expected = {.x = 3, .y = 5}; + printf("eq %s\n", api_Point_eq(&p, &expected) ? "true" : "false"); + api_Point d = api_Point_default(); printf("default %d %d\n", d.x, d.y); - api_counter *c = api_counter_new(str("c"), 5); - printf("get %llu\n", (unsigned long long)api_counter_get(c)); - print("name ", api_counter_name(c), "\n"); + api_Counter *c = api_Counter_new(str("c"), 5); + printf("get %llu\n", (unsigned long long)api_Counter_get(c)); + print("name ", api_Counter_name(c), "\n"); uint64_t v; - api_error err; - if (api_counter_add(c, 10, &v, &err)) { + api_Error err; + if (api_Counter_add(c, 10, &v, &err)) { printf("add ok %llu\n", (unsigned long long)v); } - if (!api_counter_add(c, 2000, &v, &err)) { - print("add err ", api_error_to_string(&err), " "); - print("", api_error_debug(&err), "\n"); - api_error_free(&err); + if (!api_Counter_add(c, 2000, &v, &err)) { + print("add err ", api_Error_to_string(&err), " "); + print("", api_Error_debug(&err), "\n"); + api_Error_free(&err); } - if (!api_counter_sub(c, 20, &v, &err)) { + if (!api_Counter_sub(c, 20, &v, &err)) { printf("sub err underflow by %llu ", (unsigned long long)err.underflow.by); - print("", api_error_debug(&err), "\n"); - api_error_free(&err); - } - api_counter_rename(c, str("d")); - print("name ", api_counter_name(c), "\n"); - printf("is d %s\n", api_counter_is(c, str("d")) ? "true" : "false"); - api_counter *copy = api_counter_clone(c); - api_counter_add(copy, 1, NULL, NULL); - printf("clone %llu original %llu\n", (unsigned long long)api_counter_get(copy), - (unsigned long long)api_counter_get(c)); - api_counter_free(copy); - print("debug ", api_counter_debug(c), "\n"); - api_label label = api_counter_into_label(c, (api_option_point){.some = true, .value = p}); - api_counter_free(c); - print("label ", api_label_debug(&label), "\n"); - api_label_free(&label); + print("", api_Error_debug(&err), "\n"); + api_Error_free(&err); + } + api_Counter_rename(c, str("d")); + print("name ", api_Counter_name(c), "\n"); + printf("is d %s\n", api_Counter_is(c, str("d")) ? "true" : "false"); + api_Counter *copy = api_Counter_clone(c); + api_Counter_add(copy, 1, NULL, NULL); + printf("clone %llu original %llu\n", (unsigned long long)api_Counter_get(copy), + (unsigned long long)api_Counter_get(c)); + api_Counter_free(copy); + print("debug ", api_Counter_debug(c), "\n"); + api_Label label = api_Counter_into_label(c, (api_option_Point){.some = true, .value = p}); + api_Counter_free(c); + print("label ", api_Label_debug(&label), "\n"); + api_Label_free(&label); /* Empty text is not none: only a null pointer is. */ - api_note empty = {.text = {.ptr = (const uint8_t *)"", .len = 0}}; - printf("note %s %s\n", api_has_text(empty) ? "true" : "false", api_has_text((api_note){0}) ? "true" : "false"); + api_Note empty = {.text = {.ptr = (const uint8_t *)"", .len = 0}}; + printf("note %s %s\n", api_has_text(empty) ? "true" : "false", api_has_text((api_Note){0}) ? "true" : "false"); /* A tuple returns through one out pointer per element. */ - api_counter *whole = api_counter_new(str("s"), 7); - api_counter *below, *above; - api_counter_split(whole, 5, &below, &above); - api_counter_free(whole); - print("split ", api_counter_name(below), " "); - printf("%llu ", (unsigned long long)api_counter_get(below)); - print("", api_counter_name(above), " "); - printf("%llu\n", (unsigned long long)api_counter_get(above)); - api_counter_free(below); - api_counter_free(above); - api_counter *one = api_counter_new(str("o"), 3); - api_counter *only; + api_Counter *whole = api_Counter_new(str("s"), 7); + api_Counter *below, *above; + api_Counter_split(whole, 5, &below, &above); + api_Counter_free(whole); + print("split ", api_Counter_name(below), " "); + printf("%llu ", (unsigned long long)api_Counter_get(below)); + print("", api_Counter_name(above), " "); + printf("%llu\n", (unsigned long long)api_Counter_get(above)); + api_Counter_free(below); + api_Counter_free(above); + api_Counter *one = api_Counter_new(str("o"), 3); + api_Counter *only; api_only(one, &only); - api_counter_free(one); - print("only ", api_counter_name(only), " "); - printf("%llu\n", (unsigned long long)api_counter_get(only)); - api_counter_free(only); + api_Counter_free(one); + print("only ", api_Counter_name(only), " "); + printf("%llu\n", (unsigned long long)api_Counter_get(only)); + api_Counter_free(only); uint8_t w8; int16_t w16; uint32_t w32; @@ -230,8 +230,8 @@ static int script(void) { if (api_divide(7, divisors[i], &q, &r, &err)) { printf("divide %llu %llu\n", (unsigned long long)q, (unsigned long long)r); } else { - print("divide err ", api_error_to_string(&err), "\n"); - api_error_free(&err); + print("divide err ", api_Error_to_string(&err), "\n"); + api_Error_free(&err); } } /* A tuple inside another type is a struct, its fields _0, _1, and so on. */ @@ -254,43 +254,43 @@ static int script(void) { printf("find none\n"); } } - api_point row_points[] = {{.x = 1, .y = 2}, {.x = 3, .y = 3}}; - api_vec_tuple_string_shape_point stat_rows = api_rows((api_vec_point){.ptr = row_points, .len = 2}); + api_Point row_points[] = {{.x = 1, .y = 2}, {.x = 3, .y = 3}}; + api_vec_tuple_string_Shape_Point stat_rows = api_rows((api_vec_Point){.ptr = row_points, .len = 2}); printf("rows"); for (size_t i = 0; i < stat_rows.len; i++) { /* The array's free frees each element's buffer. */ printf("%s%.*s ", i == 0 ? " " : ", ", (int)stat_rows.ptr[i]._0.len, (const char *)stat_rows.ptr[i]._0.ptr); - print("", api_shape_debug(&stat_rows.ptr[i]._1), " "); + print("", api_Shape_debug(&stat_rows.ptr[i]._1), " "); printf("%d %d", stat_rows.ptr[i]._2.x, stat_rows.ptr[i]._2.y); } printf("\n"); - api_vec_tuple_string_shape_point_free(stat_rows); + api_vec_tuple_string_Shape_Point_free(stat_rows); /* Each handle in a tuple moves in, as one taken by value does. */ - api_tuple_string_counter tallies[] = { - {._0 = str("low"), ._1 = api_counter_new(str("l"), 1)}, - {._0 = str("high"), ._1 = api_counter_new(str("h"), 9)}, + api_tuple_string_Counter tallies[] = { + {._0 = str("low"), ._1 = api_Counter_new(str("l"), 1)}, + {._0 = str("high"), ._1 = api_Counter_new(str("h"), 9)}, }; - api_board *board = api_board_new((api_vec_tuple_string_counter){.ptr = tallies, .len = 2}); - api_counter_free(tallies[0]._1); - api_counter_free(tallies[1]._1); - api_vec_tuple_string_counter ranked = api_board_ranked(board); + api_Board *board = api_Board_new((api_vec_tuple_string_Counter){.ptr = tallies, .len = 2}); + api_Counter_free(tallies[0]._1); + api_Counter_free(tallies[1]._1); + api_vec_tuple_string_Counter ranked = api_Board_ranked(board); printf("ranked"); for (size_t i = 0; i < ranked.len; i++) { printf("%s%.*s ", i == 0 ? " " : ", ", (int)ranked.ptr[i]._0.len, (const char *)ranked.ptr[i]._0.ptr); - print("", api_counter_name(ranked.ptr[i]._1), " "); - printf("%llu", (unsigned long long)api_counter_get(ranked.ptr[i]._1)); + print("", api_Counter_name(ranked.ptr[i]._1), " "); + printf("%llu", (unsigned long long)api_Counter_get(ranked.ptr[i]._1)); } printf("\n"); - api_vec_tuple_string_counter_free(ranked); - api_board_free(board); - api_span span = { + api_vec_tuple_string_Counter_free(ranked); + api_Board_free(board); + api_Span span = { .bounds = {._0 = 2, ._1 = 10}, .named = {.some = true, .value = {._0 = str("s"), ._1 = API_SHAPE_SQUARE}}, }; api_tuple_u32_u32 lower_half, upper_half; api_halves(span, &lower_half, &upper_half); printf("halves %u %u %u %u ", lower_half._0, lower_half._1, upper_half._0, upper_half._1); - print("", api_span_debug(&span), "\n"); + print("", api_Span_debug(&span), "\n"); uint32_t swapped_n; api_buf swapped_text; api_swapped((api_tuple_string_u32){._0 = str("t"), ._1 = 4}, &swapped_n, &swapped_text); @@ -335,65 +335,65 @@ static int script(void) { print("named Some((\"", named.value._0, "\",))\n"); /* Each lent counter is a new handle to a clone. */ - api_registry *registry = api_registry_new(api_counter_new(str("first"), 1)); - printf("last %s\n", api_registry_last(registry) != NULL ? "true" : "false"); - api_registry_push(registry, api_counter_new(str("last"), 2)); - api_counter *first = api_registry_first(registry); - api_counter_add(first, 10, NULL, NULL); - api_counter *lent = api_registry_first(registry); - printf("first %llu lent %llu\n", (unsigned long long)api_counter_get(lent), - (unsigned long long)api_counter_get(first)); - api_counter_free(lent); - api_counter_free(first); - api_counter *last = api_registry_last(registry); - api_counter_add(last, 10, NULL, NULL); - lent = api_registry_last(registry); - printf("last %llu lent %llu\n", (unsigned long long)api_counter_get(lent), - (unsigned long long)api_counter_get(last)); - api_counter_free(lent); - api_counter_free(last); + api_Registry *registry = api_Registry_new(api_Counter_new(str("first"), 1)); + printf("last %s\n", api_Registry_last(registry) != NULL ? "true" : "false"); + api_Registry_push(registry, api_Counter_new(str("last"), 2)); + api_Counter *first = api_Registry_first_get(registry); + api_Counter_add(first, 10, NULL, NULL); + api_Counter *lent = api_Registry_first_get(registry); + printf("first %llu lent %llu\n", (unsigned long long)api_Counter_get(lent), + (unsigned long long)api_Counter_get(first)); + api_Counter_free(lent); + api_Counter_free(first); + api_Counter *last = api_Registry_last(registry); + api_Counter_add(last, 10, NULL, NULL); + lent = api_Registry_last(registry); + printf("last %llu lent %llu\n", (unsigned long long)api_Counter_get(lent), + (unsigned long long)api_Counter_get(last)); + api_Counter_free(lent); + api_Counter_free(last); uint64_t by = 5; - bool edited = api_registry_edit(registry, edit, &by); - first = api_registry_first(registry); - printf("edit %s %llu\n", edited ? "true" : "false", (unsigned long long)api_counter_get(first)); - api_counter_free(first); + bool edited = api_Registry_edit(registry, edit, &by); + first = api_Registry_first_get(registry); + printf("edit %s %llu\n", edited ? "true" : "false", (unsigned long long)api_Counter_get(first)); + api_Counter_free(first); /* A setter moves an object in, as assignment does, leaving the handle to be freed. */ - api_counter *second = api_counter_new(str("second"), 3); - api_registry_set_first(registry, second); - api_counter_free(second); - first = api_registry_first(registry); - printf("set first %llu ", (unsigned long long)api_counter_get(first)); - print("", api_counter_name(first), "\n"); - api_counter_free(first); + api_Counter *second = api_Counter_new(str("second"), 3); + api_Registry_first_set(registry, second); + api_Counter_free(second); + first = api_Registry_first_get(registry); + printf("set first %llu ", (unsigned long long)api_Counter_get(first)); + print("", api_Counter_name(first), "\n"); + api_Counter_free(first); printf("walk %llu\n", (unsigned long long)api_walk(3, visit, NULL)); printf("each %u\n", api_each(2, print_visit, NULL)); printf("offset %u\n", api_offset(seven, NULL, 3)); printf("shift %u\n", api_shift(seven, NULL, 4)); - api_registry_free(registry); + api_Registry_free(registry); /* A type generic only over lifetimes is a handle to its 'static form. */ - api_name *name = api_name_owned(str("a")); - api_name *b = api_name_owned(str("b")); - api_name *joined = api_name_join(name, b); - api_name_free(b); - print("joined ", api_name_as_str(joined), " "); - print("", api_name_debug(joined), " "); - printf("%s\n", api_name_eq(joined, name) ? "true" : "false"); - api_name_free(name); + api_Name *name = api_Name_owned(str("a")); + api_Name *b = api_Name_owned(str("b")); + api_Name *joined = api_Name_join(name, b); + api_Name_free(b); + print("joined ", api_Name_as_str(joined), " "); + print("", api_Name_debug(joined), " "); + printf("%s\n", api_Name_eq(joined, name) ? "true" : "false"); + api_Name_free(name); char text[] = "c"; - api_name_rename(joined, str(text)); + api_Name_rename(joined, str(text)); /* The name took a copy: the caller's buffer is its own again. */ text[0] = 'x'; - print("renamed ", api_name_as_str(joined), "\n"); - api_name *dd = api_name_owned(str("dd")); - api_name *longest = api_longer(joined, dd); - print("longer ", api_name_as_str(longest), "\n"); - api_name_free(longest); - api_name_free(dd); - api_name_free(joined); - api_name *unnamed = api_unnamed(); - print("unnamed [", api_name_as_str(unnamed), "]\n"); - api_name_free(unnamed); + print("renamed ", api_Name_as_str(joined), "\n"); + api_Name *dd = api_Name_owned(str("dd")); + api_Name *longest = api_longer(joined, dd); + print("longer ", api_Name_as_str(longest), "\n"); + api_Name_free(longest); + api_Name_free(dd); + api_Name_free(joined); + api_Name *unnamed = api_unnamed(); + print("unnamed [", api_Name_as_str(unnamed), "]\n"); + api_Name_free(unnamed); printf("total %llu\n", (unsigned long long)api_total((api_range_u64){.start = 2, .end = 5})); api_range_u64 wide = api_widen((api_range_u64){.start = 2, .end = 5}, 1); @@ -436,10 +436,10 @@ static int script(void) { printf("\n"); } } - api_window window = {.range = {.start = 2, .end = 8}, .end = api_end(5)}; - printf("window %s %s ", api_window_contains(&window, 4) ? "true" : "false", - api_window_contains(&window, 6) ? "true" : "false"); - print("", api_window_debug(&window), "\n"); + api_Window window = {.range = {.start = 2, .end = 8}, .end = api_end(5)}; + printf("window %s %s ", api_Window_contains(&window, 4) ? "true" : "false", + api_Window_contains(&window, 6) ? "true" : "false"); + print("", api_Window_debug(&window), "\n"); print("", api_greet(str("x"), API_SHAPE_SQUARE), "\n"); printf("len %zu\n", api_len(str("abc"))); @@ -448,11 +448,11 @@ static int script(void) { print("timed hex ", api_timed_hex(binary), "\n"); print("sample hex ", api_sample_hex(binary), "\n"); print("queued hex ", api_queued_hex(binary), "\n"); - print("tick ", api_tick_name((api_label){.name = str("tick")}), "\n"); - print("rooted ", api_rooted_name((api_label){.name = str("rooted")}), "\n"); + print("tick ", api_tick_name((api_Label){.name = str("tick")}), "\n"); + print("rooted ", api_rooted_name((api_Label){.name = str("rooted")}), "\n"); print("stored ", api_stored_text(str("stored")), "\n"); - print("layer ", api_layer_name((api_label){.name = str("layer")}), "\n"); - api_event events[3] = { + print("layer ", api_layer_name((api_Label){.name = str("layer")}), "\n"); + api_Event events[3] = { {.tag = API_EVENT_MOVED, .moved = {._0 = p}}, {.tag = API_EVENT_NAMED, .named = {.name = str("n")}}, {.tag = API_EVENT_DONE}, @@ -462,230 +462,230 @@ static int script(void) { } /* A `Copy` handle passed by value is copied, so it stays usable. */ - api_stamp *stamp = api_stamp_from_millis(2500); - printf("secs %llu\n", (unsigned long long)api_stamp_secs(stamp)); - api_stamp *later = api_stamp_from_millis(3000); - printf("later %s\n", api_stamp_later(later, stamp) ? "true" : "false"); - api_stamp_free(later); - printf("secs %llu\n", (unsigned long long)api_stamp_secs(stamp)); - api_message *message = api_message_new(stamp, str("body")); - api_stamp_free(stamp); - print("payload ", api_message_payload(message), "\n"); - api_stamp *read = api_message_stamp(message); - print("stamp ", api_stamp_debug(read), "\n"); - api_stamp_free(read); - printf("sent %s\n", api_message_sent(message) ? "true" : "false"); + api_Stamp *stamp = api_Stamp_from_millis(2500); + printf("secs %llu\n", (unsigned long long)api_Stamp_secs(stamp)); + api_Stamp *later = api_Stamp_from_millis(3000); + printf("later %s\n", api_Stamp_later(later, stamp) ? "true" : "false"); + api_Stamp_free(later); + printf("secs %llu\n", (unsigned long long)api_Stamp_secs(stamp)); + api_Message *message = api_Message_new(stamp, str("body")); + api_Stamp_free(stamp); + print("payload ", api_Message_payload_get(message), "\n"); + api_Stamp *read = api_Message_stamp_get(message); + print("stamp ", api_Stamp_debug(read), "\n"); + api_Stamp_free(read); + printf("sent %s\n", api_Message_sent(message) ? "true" : "false"); /* Setters assign in place: the bytes are copied in, and a `Copy` handle copied. */ - api_stamp *reset = api_stamp_from_millis(4000); - api_message_set_stamp(message, reset); - api_message_set_payload(message, str("edited")); - print("payload ", api_message_payload(message), "\n"); - read = api_message_stamp(message); - print("stamp ", api_stamp_debug(read), "\n"); - api_stamp_free(read); - printf("secs %llu\n", (unsigned long long)api_stamp_secs(reset)); - api_stamp_free(reset); - api_message_set_stamp(message, NULL); - printf("stamp %s\n", api_message_stamp(message) != NULL ? "true" : "false"); - api_message_free(message); - - static const api_log_vtable print_log = {.free = log_free, .log = log_line}; + api_Stamp *reset = api_Stamp_from_millis(4000); + api_Message_stamp_set(message, reset); + api_Message_payload_set(message, str("edited")); + print("payload ", api_Message_payload_get(message), "\n"); + read = api_Message_stamp_get(message); + print("stamp ", api_Stamp_debug(read), "\n"); + api_Stamp_free(read); + printf("secs %llu\n", (unsigned long long)api_Stamp_secs(reset)); + api_Stamp_free(reset); + api_Message_stamp_set(message, NULL); + printf("stamp %s\n", api_Message_stamp_get(message) != NULL ? "true" : "false"); + api_Message_free(message); + + static const api_Log_vtable print_log = {.free = log_free, .log = log_line}; uint8_t levels[] = {1, 2, 3, 4}; api_buf lines = {.ptr = levels, .len = sizeof levels}; - printf("kept %u\n", api_emit((api_log){.vtable = &print_log}, lines)); - api_logger *logger = api_logger_new((api_log){.vtable = &print_log}); - printf("write %s\n", api_logger_write(logger, str("hi")) ? "true" : "false"); - api_logger_free(logger); + printf("kept %u\n", api_emit((api_Log){.vtable = &print_log}, lines)); + api_Logger *logger = api_Logger_new((api_Log){.vtable = &print_log}); + printf("write %s\n", api_Logger_write(logger, str("hi")) ? "true" : "false"); + api_Logger_free(logger); printf("dropped\n"); uint8_t threshold = 2; printf("filtered %u\n", api_filter(lines, keep_line, &threshold)); - api_channel *channel = api_channel_new(); + api_Channel *channel = api_Channel_new(); api_waker waker = {.wake = wake, .drop = drop, .data = NULL}; - api_channel_recv_future *recv = api_channel_recv(channel); + api_Channel_recv_future *recv = api_Channel_recv(channel); api_buf payload; - printf("recv pending %s\n", api_channel_recv_poll(recv, waker, &payload) == API_POLL_PENDING ? "true" : "false"); - api_channel_send(channel, str("hi")); + printf("recv pending %s\n", api_Channel_recv_poll(recv, waker, &payload) == API_POLL_PENDING ? "true" : "false"); + api_Channel_send(channel, str("hi")); printf("woken %d\n", wakes); - if (api_channel_recv_poll(recv, waker, &payload) == API_POLL_READY) { + if (api_Channel_recv_poll(recv, waker, &payload) == API_POLL_READY) { print("recv ", payload, "\n"); } - api_channel_recv_future_free(recv); - api_channel_send(channel, str("")); - api_channel_recv_nonempty_future *nonempty = api_channel_recv_nonempty(channel); - switch (api_channel_recv_nonempty_poll(nonempty, waker, &payload, &err)) { + api_Channel_recv_future_free(recv); + api_Channel_send(channel, str("")); + api_Channel_recv_nonempty_future *nonempty = api_Channel_recv_nonempty(channel); + switch (api_Channel_recv_nonempty_poll(nonempty, waker, &payload, &err)) { case API_POLL_READY: printf("nonempty %zu\n", payload.len); api_buf_free(payload); break; case API_POLL_ERROR: - print("nonempty err ", api_error_to_string(&err), "\n"); - api_error_free(&err); + print("nonempty err ", api_Error_to_string(&err), "\n"); + api_Error_free(&err); break; default: printf("nonempty pending\n"); } - api_channel_recv_nonempty_future_free(nonempty); + api_Channel_recv_nonempty_future_free(nonempty); /* The send happens at the call: the future is dropped unpolled. */ - api_channel_send_later_future_free(api_channel_send_later(channel, str("eager"))); - recv = api_channel_recv(channel); - if (api_channel_recv_poll(recv, waker, &payload) == API_POLL_READY) { + api_Channel_send_later_future_free(api_Channel_send_later(channel, str("eager"))); + recv = api_Channel_recv(channel); + if (api_Channel_recv_poll(recv, waker, &payload) == API_POLL_READY) { print("recv ", payload, "\n"); } - api_channel_recv_future_free(recv); - api_channel_send(channel, str("sized")); - api_channel_recv_sized_future *sized = api_channel_recv_sized(channel); + api_Channel_recv_future_free(recv); + api_Channel_send(channel, str("sized")); + api_Channel_recv_sized_future *sized = api_Channel_recv_sized(channel); size_t len; - if (api_channel_recv_sized_poll(sized, waker, &payload, &len) == API_POLL_READY) { + if (api_Channel_recv_sized_poll(sized, waker, &payload, &len) == API_POLL_READY) { print("sized ", payload, " "); printf("%zu\n", len); } - api_channel_recv_sized_future_free(sized); + api_Channel_recv_sized_future_free(sized); /* The waker moves into Rust, which drops it once it wakes. */ - api_channel_notify(channel, waker); - api_channel_send(channel, str("rung")); + api_Channel_notify(channel, waker); + api_Channel_send(channel, str("rung")); printf("woken %d\n", wakes); - api_channel_free(channel); + api_Channel_free(channel); /* A value that holds itself is an object. */ - api_node *list = api_node_new(1, NULL); - api_node_push(list, 2); - printf("node %llu\n", (unsigned long long)api_node_sum(list)); - api_node_free(list); - - api_tally *tally = api_tally_new(); - api_tally_bump(tally); - uint32_t count = api_tally_bump(tally); - printf("tally %u %s\n", count, api_tally_eq(tally, tally) ? "true" : "false"); + api_Node *list = api_Node_new(1, NULL); + api_Node_push(list, 2); + printf("node %llu\n", (unsigned long long)api_Node_sum(list)); + api_Node_free(list); + + api_Tally *tally = api_Tally_new(); + api_Tally_bump(tally); + uint32_t count = api_Tally_bump(tally); + printf("tally %u %s\n", count, api_Tally_eq(tally, tally) ? "true" : "false"); printf("both %u\n", api_bump_both(tally, tally)); printf("peek %u\n", api_peek(tally, peek_tally, NULL)); - api_tally_free(tally); + api_Tally_free(tally); /* Another export's types, which this export's function takes and returns. */ - api_area *small = api_area_new((api_size){.width = 2, .height = 3}); - api_area *area = api_grow(small, 1); - api_area_free(small); - api_size size = api_area_size(area); - printf("area %u %u %llu\n", size.width, size.height, (unsigned long long)api_area_pixels(area)); - api_area_free(area); + api_Area *small = api_Area_new((api_Size){.width = 2, .height = 3}); + api_Area *area = api_grow(small, 1); + api_Area_free(small); + api_Size size = api_Area_size(area); + printf("area %u %u %llu\n", size.width, size.height, (unsigned long long)api_Area_pixels(area)); + api_Area_free(area); /* Generic types, each at the instances it infers, named by their arguments. */ - api_pair_u32 pair = {.left = 2, .right = 5}; - api_pair_u32 swapped = api_pair_u32_swap(&pair); - printf("pair %u %u sum %u ", swapped.left, swapped.right, api_pair_u32_sum(&pair)); - print("", api_pair_u32_debug(&pair), " "); - printf("%s\n", api_pair_u32_eq(&pair, &swapped) ? "true" : "false"); - api_pair_u32 twins = {.left = 7, .right = 7}; - printf("even %s %s\n", api_pair_u32_even(&pair) ? "true" : "false", - api_pair_u32_even(&twins) ? "true" : "false"); + api_Pair_u32 pair = {.left = 2, .right = 5}; + api_Pair_u32 swapped = api_Pair_u32_swap(&pair); + printf("pair %u %u sum %u ", swapped.left, swapped.right, api_Pair_u32_sum(&pair)); + print("", api_Pair_u32_debug(&pair), " "); + printf("%s\n", api_Pair_u32_eq(&pair, &swapped) ? "true" : "false"); + api_Pair_u32 twins = {.left = 7, .right = 7}; + printf("even %s %s\n", api_Pair_u32_even(&pair) ? "true" : "false", + api_Pair_u32_even(&twins) ? "true" : "false"); /* An instance renamed alone, beside another of `Pair` that keeps its name. */ - api_size_tag sizes = api_tag((api_size){.width = 1, .height = 2}, 3); - api_pair_u8_size flipped = api_size_tag_swap(&sizes); + api_Pair_Size_u8 sizes = api_tag((api_Size){.width = 1, .height = 2}, 3); + api_Pair_u8_Size flipped = api_Pair_Size_u8_swap(&sizes); printf("sized %u %u ", flipped.left, flipped.right.width); - print("", api_pair_u8_size_debug(&flipped), "\n"); - api_pair_u32 byte = {.left = 258, .right = 9}; - api_pair_u8_size beside = api_pair_u32_beside_size(&byte, sizes.left); + print("", api_Pair_u8_Size_debug(&flipped), "\n"); + api_Pair_u32 byte = {.left = 258, .right = 9}; + api_Pair_u8_Size beside = api_Pair_u32_beside_Size(&byte, sizes.left); printf("beside %u ", beside.left); - print("", api_pair_u8_size_debug(&beside), "\n"); + print("", api_Pair_u8_Size_debug(&beside), "\n"); /* At its default, named as the type itself. */ - api_stack *stack = api_stack_new(); + api_Stack *stack = api_Stack_new(); const char *names[] = {"a", "b"}; for (int i = 0; i < 2; i++) { - api_counter *pushed = api_counter_new(str(names[i]), (uint64_t)i + 1); - api_stack_push(stack, pushed); - api_counter_free(pushed); - } - api_counter *top = api_stack_pop(stack); - printf("stack %zu Some(%llu)", api_stack_depth(stack), (unsigned long long)api_counter_get(top)); - api_counter_free(top); - api_counter *next = api_stack_pop(stack); + api_Counter *pushed = api_Counter_new(str(names[i]), (uint64_t)i + 1); + api_Stack_push(stack, pushed); + api_Counter_free(pushed); + } + api_Counter *top = api_Stack_pop(stack); + printf("stack %zu Some(%llu)", api_Stack_depth(stack), (unsigned long long)api_Counter_get(top)); + api_Counter_free(top); + api_Counter *next = api_Stack_pop(stack); printf(" %s", next != NULL ? "true" : "false"); - api_counter_free(next); - printf(" %s\n", api_stack_pop(stack) == NULL ? "true" : "false"); + api_Counter_free(next); + printf(" %s\n", api_Stack_pop(stack) == NULL ? "true" : "false"); /* Generic functions and methods, at each implementation of the trait bounding them, by reference, which leaves C its handle. */ - api_counter *from_point = api_counter_seeded_point(str("p"), (api_point){.x = -3, .y = 4}); - api_counter *from_size = api_counter_from_size(str("s"), (api_size){.width = 5, .height = 6}); - api_counter *from_counter = api_counter_seeded_counter(str("c"), from_point); + api_Counter *from_point = api_Counter_seeded_Point(str("p"), (api_Point){.x = -3, .y = 4}); + api_Counter *from_size = api_Counter_seeded_Size(str("s"), (api_Size){.width = 5, .height = 6}); + api_Counter *from_counter = api_Counter_seeded_Counter(str("c"), from_point); /* Two instances whose arguments share a last segment: one takes its module too. */ - api_counter *from_polar = api_counter_seeded_polar_point(str("q"), (api_polar_point){.turns = 1, .distance = 2}); - printf("seeded %llu %llu %llu %llu past %llu seeds %llu\n", (unsigned long long)api_counter_get(from_point), - (unsigned long long)api_counter_get(from_size), (unsigned long long)api_counter_get(from_counter), - (unsigned long long)api_counter_get(from_polar), - (unsigned long long)api_stack_depth_past_point(stack, (api_point){.x = 1, .y = 2}), - (unsigned long long)api_seeds_point_size((api_size){.width = 2, .height = 3})); - api_counter_free(from_point); - api_counter_free(from_size); - api_counter_free(from_counter); - api_counter_free(from_polar); - api_stack_free(stack); + api_Counter *from_polar = api_Counter_seeded_polar_Point(str("q"), (api_polar_Point){.turns = 1, .distance = 2}); + printf("seeded %llu %llu %llu %llu past %llu seeds %llu\n", (unsigned long long)api_Counter_get(from_point), + (unsigned long long)api_Counter_get(from_size), (unsigned long long)api_Counter_get(from_counter), + (unsigned long long)api_Counter_get(from_polar), + (unsigned long long)api_Stack_depth_past_Point(stack, (api_Point){.x = 1, .y = 2}), + (unsigned long long)api_seeds_Point_Size((api_Size){.width = 2, .height = 3})); + api_Counter_free(from_point); + api_Counter_free(from_size); + api_Counter_free(from_counter); + api_Counter_free(from_polar); + api_Stack_free(stack); /* A trait implemented for `Point` and `&Point` apart: one instance. */ - printf("weigh %u\n", api_weigh_point((api_point){.x = -1, .y = 2})); + printf("weigh %u\n", api_weigh_Point((api_Point){.x = -1, .y = 2})); /* Instances that name each other, and one made only by an impl of it. */ - api_outer_inner_u8 *outer = api_outer_inner_u8_new(4); + api_Outer_Inner_u8 *outer = api_Outer_Inner_u8_new(4); printf("cross %u outer %u\n", - api_cross((api_left_right_u8){.value = {.value = 1}}, (api_right_left_u8){.value = {.value = 2}}), - api_outer_inner_u8_get(outer)); - api_outer_inner_u8_free(outer); + api_cross((api_Left_Right_u8){.value = {.value = 1}}, (api_Right_Left_u8){.value = {.value = 2}}), + api_Outer_Inner_u8_get(outer)); + api_Outer_Inner_u8_free(outer); /* A bound that outlives the call takes a value, and an instance only a constant names. */ - printf("kept %llu boxed %u\n", (unsigned long long)api_kept_point((api_point){.x = 1, .y = -2}), api_boxed().value); - printf("keep %d\n", api_keep_point((api_point){.x = 1, .y = -2})); + printf("kept %llu boxed %u\n", (unsigned long long)api_kept_Point((api_Point){.x = 1, .y = -2}), api_BOXED().value); + printf("keep %d\n", api_keep_Point((api_Point){.x = 1, .y = -2})); /* Inferred instances taking a lent closure and a foreign implementation, and one only * its impl of a trait names. */ - api_pair_u32 sides = {.left = 2, .right = 5}; + api_Pair_u32 sides = {.left = 2, .right = 5}; printf("apply %u\n", api_apply_u32(sides, triple, NULL)); - printf("report %u\n", api_report_u32(sides, (api_log){.vtable = &print_log})); - printf("wrapped %llu\n", (unsigned long long)api_kept_wrap_u32((api_wrap_u32){.value = 4})); - printf("keeps %u\n", api_keeps((api_log){.vtable = &print_log}).value); + printf("report %u\n", api_report_u32(sides, (api_Log){.vtable = &print_log})); + printf("wrapped %llu\n", (unsigned long long)api_kept_Wrap_u32((api_Wrap_u32){.value = 4})); + printf("keeps %u\n", api_keeps((api_Log){.vtable = &print_log}).value); /* Its Drop runs when C frees the handle, not before. */ - api_token *token = api_token_new(7); - printf("token %u dropped %u\n", api_token_id(token), api_dropped()); - api_token_free(token); + api_Token *token = api_Token_new(7); + printf("token %u dropped %u\n", api_Token_id_get(token), api_dropped()); + api_Token_free(token); printf("dropped %u\n", api_dropped()); /* A field and the method of its name, each its own function. */ - api_retry *retry = api_retry_new((api_option_u32){.some = true, .value = 5}); - api_option_u32 limit = api_retry_limit_setting(retry); - printf("retry %d %u %u %u\n", limit.some, limit.value, api_retry_limit(retry), api_retry_delay(retry)); + api_Retry *retry = api_Retry_new((api_option_u32){.some = true, .value = 5}); + api_option_u32 limit = api_Retry_limit_get(retry); + printf("retry %d %u %u %u\n", limit.some, limit.value, api_Retry_limit(retry), api_Retry_delay(retry)); /* The renamed field's setter, named as its getter. */ - api_retry_set_limit_setting(retry, (api_option_u32){0}); - printf("retry %u\n", api_retry_limit(retry)); - api_retry_free(retry); - api_slot_u8 *slot = api_slot_u8_new(200); - printf("slot %u\n", api_slot_u8_value(slot)); - api_slot_u8_free(slot); + api_Retry_limit_set(retry, (api_option_u32){0}); + printf("retry %u\n", api_Retry_limit(retry)); + api_Retry_free(retry); + api_Slot_u8 *slot = api_Slot_u8_new(200); + printf("slot %u\n", api_Slot_u8_value(slot)); + api_Slot_u8_free(slot); /* Foreign types: a URL parsed from text, socket addresses, OS paths, and `Vec`s. */ - api_url *base; + api_Url *base; api_buf parse_err; - if (!api_url_parse(str("https://example.com/a/b"), &base, &parse_err)) { + if (!api_Url_parse(str("https://example.com/a/b"), &base, &parse_err)) { return 1; } - print("url ", api_url_to_string(base), " host "); + print("url ", api_Url_to_string(base), " host "); api_buf host = api_host(base); if (host.ptr != NULL) { print("Some(\"", host, "\")\n"); } else { printf("None\n"); } - api_url *resolved; + api_Url *resolved; api_buf resolve_err; - api_url *base_clone = api_url_clone(base); + api_Url *base_clone = api_Url_clone(base); if (api_resolve(base_clone, str("c?d"), &resolved, &resolve_err)) { - print("resolve ", api_url_to_string(resolved), " "); - printf("%s\n", api_url_eq(resolved, base) ? "true" : "false"); - api_url_free(resolved); + print("resolve ", api_Url_to_string(resolved), " "); + printf("%s\n", api_Url_eq(resolved, base) ? "true" : "false"); + api_Url_free(resolved); } else { print("resolve err ", resolve_err, "\n"); } - api_url_free(base_clone); - api_url_free(base); - api_url *bad; - if (api_url_parse(str("no scheme"), &bad, &parse_err)) { - print("parse ", api_url_to_string(bad), "\n"); - api_url_free(bad); + api_Url_free(base_clone); + api_Url_free(base); + api_Url *bad; + if (api_Url_parse(str("no scheme"), &bad, &parse_err)) { + print("parse ", api_Url_to_string(bad), "\n"); + api_Url_free(bad); } else { print("parse err ", parse_err, "\n"); } @@ -698,8 +698,8 @@ static int script(void) { printf("ports [%u, %u]\n", ports.ptr[0], ports.ptr[1]); api_vec_u16_free(ports); api_buf peer_names[] = {str("a"), str("b")}; - api_peer peer_start = {.addr = addrs[1], .names = {.ptr = peer_names, .len = 2}}; - api_peer peer = api_peer_next(&peer_start, str("c")); + api_Peer peer_start = {.addr = addrs[1], .names = {.ptr = peer_names, .len = 2}}; + api_Peer peer = api_Peer_next(&peer_start, str("c")); printf("peer Peer { addr: "); print_addr(peer.addr); printf(", names: ["); @@ -707,14 +707,14 @@ static int script(void) { printf("%s\"%.*s\"", i == 0 ? "" : ", ", (int)peer.names.ptr[i].len, (const char *)peer.names.ptr[i].ptr); } printf("] } ["); - api_vec_string aliases = api_peer_aliases(&peer); + api_vec_string aliases = api_Peer_aliases(&peer); for (size_t i = 0; i < aliases.len; i++) { printf("%s\"%.*s\"", i == 0 ? "" : ", ", (int)aliases.ptr[i].len, (const char *)aliases.ptr[i].ptr); } printf("]\n"); api_vec_string_free(aliases); - api_buf debug = api_peer_debug(&peer); - api_peer_free(&peer); + api_buf debug = api_Peer_debug(&peer); + api_Peer_free(&peer); api_buf_free(debug); /* A path's bytes as the OS holds them, which need not be UTF-8. */ @@ -768,24 +768,24 @@ static int script(void) { api_vec_bytes_free(chunks); api_buf counter_names[] = {str("x"), str("y")}; - api_vec_counter counters = api_counters((api_vec_string){.ptr = counter_names, .len = 2}); + api_vec_Counter counters = api_counters((api_vec_string){.ptr = counter_names, .len = 2}); printf("counters"); for (size_t i = 0; i < counters.len; i++) { - print(i == 0 ? " " : ", ", api_counter_name(counters.ptr[i]), " "); - printf("%llu", (unsigned long long)api_counter_get(counters.ptr[i])); + print(i == 0 ? " " : ", ", api_Counter_name(counters.ptr[i]), " "); + printf("%llu", (unsigned long long)api_Counter_get(counters.ptr[i])); } printf("\n"); /* Each counter moves in, leaving its handle only to free. */ - uint64_t worth = api_worth((api_vec_counter){.ptr = counters.ptr, .len = 1}, (api_vec_counter){.ptr = counters.ptr + 1, .len = 1}); - uint64_t none = api_worth((api_vec_counter){0}, (api_vec_counter){.ptr = counters.ptr, .len = 0}); + uint64_t worth = api_worth((api_vec_Counter){.ptr = counters.ptr, .len = 1}, (api_vec_Counter){.ptr = counters.ptr + 1, .len = 1}); + uint64_t none = api_worth((api_vec_Counter){0}, (api_vec_Counter){.ptr = counters.ptr, .len = 0}); printf("worth %llu %llu\n", (unsigned long long)worth, (unsigned long long)none); - api_vec_counter_free(counters); - api_counter *held = api_counter_new(str("h"), 4); + api_vec_Counter_free(counters); + api_Counter *held = api_Counter_new(str("h"), 4); api_option_u64 counts[] = { - api_held_count((api_option_tuple_counter){.some = true, .value = {._0 = held}}), - api_held_count((api_option_tuple_counter){0}), + api_held_count((api_option_tuple_Counter){.some = true, .value = {._0 = held}}), + api_held_count((api_option_tuple_Counter){0}), }; - api_counter_free(held); + api_Counter_free(held); printf("held"); for (size_t i = 0; i < 2; i++) { if (counts[i].some) { @@ -795,9 +795,9 @@ static int script(void) { } } printf("\n"); - api_counter *boxed = api_boxed_counter(str("b")); + api_Counter *boxed = api_boxed_counter(str("b")); print("boxed Some(\"", api_boxed_name(boxed), "\") "); - api_counter_free(boxed); + api_Counter_free(boxed); printf("%s %s\n", api_boxed_name(NULL).ptr ? "Some" : "None", api_boxed_counter((api_buf){0}) ? "false" : "true"); printf("less %u\n", api_less(5, 2)); @@ -807,9 +807,9 @@ static int script(void) { api_vec_u32 sums = api_sums((api_vec_vec_u32){.ptr = rows, .len = 3}); printf("sums [%u, %u, %u]\n", sums.ptr[0], sums.ptr[1], sums.ptr[2]); api_vec_u32_free(sums); - api_point summands[] = {{.x = 1, .y = 2}, {.x = -4, .y = 6}}; - api_point summed = api_summed((api_vec_point){.ptr = summands, .len = 2}); - api_point none_summed = api_summed((api_vec_point){.ptr = summands, .len = 0}); + api_Point summands[] = {{.x = 1, .y = 2}, {.x = -4, .y = 6}}; + api_Point summed = api_summed((api_vec_Point){.ptr = summands, .len = 2}); + api_Point none_summed = api_summed((api_vec_Point){.ptr = summands, .len = 0}); printf("summed %d %d %d %d\n", summed.x, summed.y, none_summed.x, none_summed.y); api_buf longest_words[] = {str("a"), str("abc")}; size_t most = api_longest((api_vec_string){.ptr = longest_words, .len = 2}); @@ -817,112 +817,112 @@ static int script(void) { printf("longest %zu %zu\n", most, no_longest); /* JSON crosses as a document, made from text and read back as text. */ - api_json *doc; - if (!api_json_parse(str("{\"b\": [1, 2.5, null], \"a\": \"\\u00e9\"}"), &doc, &parse_err)) { + api_Json *doc; + if (!api_Json_parse(str("{\"b\": [1, 2.5, null], \"a\": \"\\u00e9\"}"), &doc, &parse_err)) { return 1; } print("json ", api_encode(doc), "\n"); print("timed text ", api_timed_text(doc), "\n"); - api_json *tagged = api_tagged(doc, str("t")); - api_json_free(doc); - print("tagged ", api_json_to_text(tagged), "\n"); - api_json_free(tagged); - api_json *broken; - if (api_json_parse(str("{\"a\":"), &broken, &parse_err)) { - print("json parse ", api_json_to_text(broken), "\n"); - api_json_free(broken); + api_Json *tagged = api_tagged(doc, str("t")); + api_Json_free(doc); + print("tagged ", api_Json_to_text(tagged), "\n"); + api_Json_free(tagged); + api_Json *broken; + if (api_Json_parse(str("{\"a\":"), &broken, &parse_err)) { + print("json parse ", api_Json_to_text(broken), "\n"); + api_Json_free(broken); } else { print("json parse err ", parse_err, "\n"); } - api_url *home; - if (!api_url_parse(str("https://example.com/a/b"), &home, &parse_err)) { + api_Url *home; + if (!api_Url_parse(str("https://example.com/a/b"), &home, &parse_err)) { return 1; } - api_site *site = api_site_new(home); - api_url *home0 = api_site_home(site); - api_url *home1 = api_site_home(site); - print("site ", api_url_to_string(home0), " "); - print("", api_url_to_string(home1), "\n"); - api_url_free(home0); - api_url_free(home1); - api_site_free(site); - api_url_free(home); + api_Site *site = api_Site_new(home); + api_Url *home0 = api_Site_home_get(site); + api_Url *home1 = api_Site_home_get(site); + print("site ", api_Url_to_string(home0), " "); + print("", api_Url_to_string(home1), "\n"); + api_Url_free(home0); + api_Url_free(home1); + api_Site_free(site); + api_Url_free(home); /* Slices, passed in and returned as clones, and the socket address of each family. */ - api_vec_shape shapes = api_shape_all(); + api_vec_Shape shapes = api_Shape_ALL(); printf("shapes ["); for (size_t i = 0; i < shapes.len; i++) { - print(i == 0 ? "" : ", ", api_shape_debug(&shapes.ptr[i]), ""); + print(i == 0 ? "" : ", ", api_Shape_debug(&shapes.ptr[i]), ""); } - api_vec_shape_free(shapes); - api_vec_string greetings = api_greetings(); + api_vec_Shape_free(shapes); + api_vec_string greetings = api_GREETINGS(); printf("] greetings ["); for (size_t i = 0; i < greetings.len; i++) { printf("%s\"%.*s\"", i == 0 ? "" : ", ", (int)greetings.ptr[i].len, (const char *)greetings.ptr[i].ptr); } printf("]\n"); api_vec_string_free(greetings); - api_point trail_points[] = {{.x = 1, .y = 2}, {.x = 3, .y = 4}}; + api_Point trail_points[] = {{.x = 1, .y = 2}, {.x = 3, .y = 4}}; api_buf trail_names[] = {str("a"), str("b")}; - api_trail *trail = - api_trail_new((api_vec_point){.ptr = trail_points, .len = 2}, (api_vec_string){.ptr = trail_names, .len = 2}); - api_vec_point points = api_trail_points(trail); + api_Trail *trail = + api_Trail_new((api_vec_Point){.ptr = trail_points, .len = 2}, (api_vec_string){.ptr = trail_names, .len = 2}); + api_vec_Point points = api_Trail_points(trail); printf("trail ["); for (size_t i = 0; i < points.len; i++) { - print(i == 0 ? "" : ", ", api_point_debug(&points.ptr[i]), ""); + print(i == 0 ? "" : ", ", api_Point_debug(&points.ptr[i]), ""); } - api_vec_point_free(points); - api_vec_label labels = api_trail_labels(trail); + api_vec_Point_free(points); + api_vec_Label labels = api_Trail_labels(trail); printf("] ["); for (size_t i = 0; i < labels.len; i++) { - print(i == 0 ? "" : ", ", api_label_debug(&labels.ptr[i]), ""); + print(i == 0 ? "" : ", ", api_Label_debug(&labels.ptr[i]), ""); } printf("]\n"); - api_vec_label_free(labels); + api_vec_Label_free(labels); /* Returned iterators, each collected at the call: drained labels are gone after. */ - api_vec_point iter_points = api_trail_iter(trail); + api_vec_Point iter_points = api_Trail_iter(trail); printf("iterated ["); for (size_t i = 0; i < iter_points.len; i++) { - print(i == 0 ? "" : ", ", api_point_debug(&iter_points.ptr[i]), ""); + print(i == 0 ? "" : ", ", api_Point_debug(&iter_points.ptr[i]), ""); } - api_vec_point_free(iter_points); - api_vec_string iter_names = api_trail_names(trail); + api_vec_Point_free(iter_points); + api_vec_string iter_names = api_Trail_names(trail); printf("] ["); for (size_t i = 0; i < iter_names.len; i++) { printf("%s\"%.*s\"", i == 0 ? "" : ", ", (int)iter_names.ptr[i].len, (const char *)iter_names.ptr[i].ptr); } api_vec_string_free(iter_names); - api_buf iter_bytes = api_trail_bytes(trail); + api_buf iter_bytes = api_Trail_bytes(trail); printf("] ["); for (size_t i = 0; i < iter_bytes.len; i++) { printf("%s%u", i == 0 ? "" : ", ", iter_bytes.ptr[i]); } printf("]\n"); api_buf_free(iter_bytes); - api_vec_label drained = api_trail_drain(trail); + api_vec_Label drained = api_Trail_drain(trail); printf("drained ["); for (size_t i = 0; i < drained.len; i++) { - print(i == 0 ? "" : ", ", api_label_debug(&drained.ptr[i]), ""); + print(i == 0 ? "" : ", ", api_Label_debug(&drained.ptr[i]), ""); } - api_vec_label_free(drained); - api_vec_label left = api_trail_labels(trail); + api_vec_Label_free(drained); + api_vec_Label left = api_Trail_labels(trail); printf("] then ["); for (size_t i = 0; i < left.len; i++) { - print(i == 0 ? "" : ", ", api_label_debug(&left.ptr[i]), ""); + print(i == 0 ? "" : ", ", api_Label_debug(&left.ptr[i]), ""); } printf("]\n"); - api_vec_label_free(left); - api_trail_free(trail); + api_vec_Label_free(left); + api_Trail_free(trail); api_buf numbered_names[] = {str("p"), str("q")}; - api_vec_counter numbered = api_numbered((api_vec_string){.ptr = numbered_names, .len = 2}); + api_vec_Counter numbered = api_numbered((api_vec_string){.ptr = numbered_names, .len = 2}); printf("numbered"); for (size_t i = 0; i < numbered.len; i++) { - print(i == 0 ? " " : ", ", api_counter_name(numbered.ptr[i]), " "); - printf("%llu", (unsigned long long)api_counter_get(numbered.ptr[i])); + print(i == 0 ? " " : ", ", api_Counter_name(numbered.ptr[i]), " "); + printf("%llu", (unsigned long long)api_Counter_get(numbered.ptr[i])); } printf("\n"); - api_vec_counter_free(numbered); + api_vec_Counter_free(numbered); api_socket_addr at4 = {.ip = {127, 0, 0, 1}, .port = 8080}; api_socket_addr at6 = {.ip = {[15] = 1}, .port = 443, .v6 = true, .scope_id = 3}; api_socket_addr next4, next6; @@ -931,17 +931,17 @@ static int script(void) { next6.scope_id); /* A returned `Vec<&Token>` holds clones, which its free drops. */ - api_token *twice = api_token_new(9); + api_Token *twice = api_Token_new(9); uint32_t before = api_dropped(); - api_vec_token tokens = api_token_twice(twice); - if (tokens.len != 2 || api_token_id(tokens.ptr[1]) != 9) { + api_vec_Token tokens = api_Token_twice(twice); + if (tokens.len != 2 || api_Token_id_get(tokens.ptr[1]) != 9) { return 1; } - api_vec_token_free(tokens); + api_vec_Token_free(tokens); if (api_dropped() != before + 2) { return 1; } - api_token_free(twice); + api_Token_free(twice); /* Each future dropped its waker once it finished, and Rust the one it was given. */ return drops == 5 ? 0 : 1; @@ -950,39 +950,39 @@ static int script(void) { /* Each breaks a contract, which must abort with a message naming the function. */ static int broken(const char *which) { if (strcmp(which, "moved") == 0) { - api_counter *c = api_counter_new(str("c"), 0); - api_label label = api_counter_into_label(c, (api_option_point){0}); - api_label_free(&label); - api_counter_get(c); + api_Counter *c = api_Counter_new(str("c"), 0); + api_Label label = api_Counter_into_label(c, (api_option_Point){0}); + api_Label_free(&label); + api_Counter_get(c); } else if (strcmp(which, "borrowed") == 0) { - api_channel *channel = api_channel_new(); - api_channel_recv_future *recv = api_channel_recv(channel); + api_Channel *channel = api_Channel_new(); + api_Channel_recv_future *recv = api_Channel_recv(channel); (void)recv; - api_channel_clear(channel); + api_Channel_clear(channel); } else if (strcmp(which, "utf8") == 0) { api_buf bad = {.ptr = (const uint8_t *)"\xff", .len = 1}; api_greet(bad, API_SHAPE_CIRCLE); } else if (strcmp(which, "finished") == 0) { - api_channel *channel = api_channel_new(); - api_channel_send(channel, str("x")); - api_channel_recv_future *recv = api_channel_recv(channel); + api_Channel *channel = api_Channel_new(); + api_Channel_send(channel, str("x")); + api_Channel_recv_future *recv = api_Channel_recv(channel); api_waker waker = {0}; api_buf payload; - api_channel_recv_poll(recv, waker, &payload); + api_Channel_recv_poll(recv, waker, &payload); api_buf_free(payload); - api_channel_recv_poll(recv, waker, &payload); + api_Channel_recv_poll(recv, waker, &payload); } else if (strcmp(which, "family") == 0) { api_socket_addr v6 = {.ip = {[15] = 1}, .port = 443, .v6 = true}; api_socket_addr next4, next6; api_nexts(v6, v6, &next4, &next6); } else if (strcmp(which, "enum") == 0) { - api_greet(str("x"), (api_shape)2); + api_greet(str("x"), (api_Shape)2); } else if (strcmp(which, "lent") == 0 || strcmp(which, "lent moved") == 0) { - api_registry *registry = api_registry_new(api_counter_new(str("c"), 0)); - api_registry_edit(registry, strcmp(which, "lent") == 0 ? free_lent : move_lent, NULL); + api_Registry *registry = api_Registry_new(api_Counter_new(str("c"), 0)); + api_Registry_edit(registry, strcmp(which, "lent") == 0 ? free_lent : move_lent, NULL); } else if (strcmp(which, "null") == 0) { - api_registry *registry = api_registry_new(api_counter_new(str("c"), 0)); - api_registry_edit(registry, NULL, NULL); + api_Registry *registry = api_Registry_new(api_Counter_new(str("c"), 0)); + api_Registry_edit(registry, NULL, NULL); } else if (strcmp(which, "bound") == 0) { api_count((api_bounds_u64){.start = {.kind = (api_bound)3}}); } else if (strcmp(which, "socket") == 0) { diff --git a/ffi/tests/api.cpp b/ffi/tests/api.cpp index a01aa9932..67fb07357 100644 --- a/ffi/tests/api.cpp +++ b/ffi/tests/api.cpp @@ -77,9 +77,9 @@ void print_strings(const std::vector &strings) { int wakes = 0; // The Log callback: keeps even levels. -class print_log final : public api::log { +class print_log final : public api::Log { public: - bool log_(std::uint8_t level, api::view line) const override { + bool log(std::uint8_t level, api::view line) const override { std::printf("log %u %.*s\n", level, static_cast(line.size()), reinterpret_cast(line.data())); return level % 2 == 0; } @@ -141,51 +141,51 @@ struct queue { }; int script() { - std::printf("limit %llu\n", ull(api::limit)); + std::printf("limit %llu\n", ull(api::LIMIT)); - api::point start{1, 2}; - api::point p = start.offset(2, 3); + api::Point start{1, 2}; + api::Point p = start.offset(2, 3); std::printf("offset %d %d\n", p.x, p.y); print("debug ", p.debug(), "\n"); - std::printf("eq %s\n", yes(p == api::point{3, 5})); - api::point d = api::point::default_(); + std::printf("eq %s\n", yes(p == api::Point{3, 5})); + api::Point d = api::Point::default_(); std::printf("default %d %d\n", d.x, d.y); - api::counter c = api::counter::new_("c", 5); + api::Counter c = api::Counter::new_("c", 5); std::printf("get %llu\n", ull(c.get())); print("name ", c.name(), "\n"); if (auto v = c.add(10)) { std::printf("add ok %llu\n", ull(*v)); } if (auto v = c.add(2000); !v) { - print("add err ", api::error_to_string(v.error()), " "); - print("", api::error_debug(v.error()), "\n"); + print("add err ", api::Error_to_string(v.error()), " "); + print("", api::Error_debug(v.error()), "\n"); } if (auto v = c.sub(20); !v) { - std::printf("sub err underflow by %llu ", ull(std::get(v.error()).by)); - print("", api::error_debug(v.error()), "\n"); + std::printf("sub err underflow by %llu ", ull(std::get(v.error()).by)); + print("", api::Error_debug(v.error()), "\n"); } c.rename("d"); print("name ", c.name(), "\n"); std::printf("is d %s\n", yes(c.is("d"))); { - api::counter copy = c.clone(); + api::Counter copy = c.clone(); (void)copy.add(1); std::printf("clone %llu original %llu\n", ull(copy.get()), ull(c.get())); } print("debug ", c.debug(), "\n"); - api::label label = std::move(c).into_label(p); + api::Label label = std::move(c).into_label(p); print("label ", label.debug(), "\n"); // An empty buffer C++ made is not none. - std::printf("note %s %s\n", yes(api::has_text(api::note{api::buf()})), yes(api::has_text(api::note{}))); + std::printf("note %s %s\n", yes(api::has_text(api::Note{api::buf()})), yes(api::has_text(api::Note{}))); // A tuple returns through one out pointer per element. - auto [below, above] = api::counter::new_("s", 7).split(5); + auto [below, above] = api::Counter::new_("s", 7).split(5); print("split ", below.name(), " "); std::printf("%llu ", ull(below.get())); print("", above.name(), " "); std::printf("%llu\n", ull(above.get())); - auto [only] = api::only(api::counter::new_("o", 3)); + auto [only] = api::only(api::Counter::new_("o", 3)); print("only ", only.name(), " "); std::printf("%llu\n", ull(only.get())); auto [w8, w16, w32, w64] = api::widths(98305); @@ -194,7 +194,7 @@ int script() { if (auto r = api::divide(7, divisor)) { std::printf("divide %llu %llu\n", ull(std::get<0>(*r)), ull(std::get<1>(*r))); } else { - print("divide err ", api::error_to_string(r.error()), "\n"); + print("divide err ", api::Error_to_string(r.error()), "\n"); } } // A tuple inside another type is a std::tuple. @@ -214,20 +214,20 @@ int script() { } } std::printf("rows"); - auto stat_rows = api::rows({api::point{1, 2}, api::point{3, 3}}); + auto stat_rows = api::rows({api::Point{1, 2}, api::Point{3, 3}}); for (std::size_t i = 0; i < stat_rows.size(); i++) { auto &[name, shape, at] = stat_rows[i]; print(i == 0 ? " " : ", ", name, " "); - print("", api::shape_debug(shape), " "); + print("", api::Shape_debug(shape), " "); std::printf("%d %d", at.x, at.y); } std::printf("\n"); { // Each handle in a tuple moves in, as one taken by value does. - std::vector> tallies; - tallies.emplace_back("low", api::counter::new_("l", 1)); - tallies.emplace_back("high", api::counter::new_("h", 9)); - auto board = api::board::new_(std::move(tallies)); + std::vector> tallies; + tallies.emplace_back("low", api::Counter::new_("l", 1)); + tallies.emplace_back("high", api::Counter::new_("h", 9)); + auto board = api::Board::new_(std::move(tallies)); std::printf("ranked"); auto ranked = board.ranked(); for (std::size_t i = 0; i < ranked.size(); i++) { @@ -238,7 +238,7 @@ int script() { } std::printf("\n"); } - api::span span{{2, 10}, std::make_tuple(api::buf("s"), api::shape::square)}; + api::Span span{{2, 10}, std::make_tuple(api::buf("s"), api::Shape::square)}; auto [lower_half, upper_half] = api::halves(span); std::printf("halves %u %u %u %u ", std::get<0>(lower_half), std::get<1>(lower_half), std::get<0>(upper_half), std::get<1>(upper_half)); @@ -279,37 +279,37 @@ int script() { print("named Some((\"", std::get<0>(*api::named("x")), "\",))\n"); // Each lent counter is a new handle to a clone. - api::registry registry = api::registry::new_(api::counter::new_("first", 1)); + api::Registry registry = api::Registry::new_(api::Counter::new_("first", 1)); std::printf("last %s\n", yes(registry.last().has_value())); - registry.push(api::counter::new_("last", 2)); + registry.push(api::Counter::new_("last", 2)); { - api::counter first = registry.first(); + api::Counter first = registry.first_get(); (void)first.add(10); - std::printf("first %llu lent %llu\n", ull(registry.first().get()), ull(first.get())); - api::counter last = *registry.last(); + std::printf("first %llu lent %llu\n", ull(registry.first_get().get()), ull(first.get())); + api::Counter last = *registry.last(); (void)last.add(10); std::printf("last %llu lent %llu\n", ull(registry.last()->get()), ull(last.get())); } // A closure lent for the call edits the first counter through its class. std::uint64_t by = 5; - bool edited = registry.edit([&](api::counter &counter) { + bool edited = registry.edit([&](api::Counter &counter) { (void)counter.add(by); return counter.edited() && counter.get() == 6; }); - std::printf("edit %s %llu\n", yes(edited), ull(registry.first().get())); + std::printf("edit %s %llu\n", yes(edited), ull(registry.first_get().get())); // A setter moves an object in, as assignment does. - registry.set_first(api::counter::new_("second", 3)); - std::printf("set first %llu ", ull(registry.first().get())); - print("", registry.first().name(), "\n"); - std::printf("walk %llu\n", ull(api::walk(3, [](const api::cursor &cursor) { return cursor.at() * 2; }))); + registry.first_set(api::Counter::new_("second", 3)); + std::printf("set first %llu ", ull(registry.first_get().get())); + print("", registry.first_get().name(), "\n"); + std::printf("walk %llu\n", ull(api::walk(3, [](const api::Cursor &cursor) { return cursor.at() * 2; }))); std::printf("each %u\n", api::each(2, [](std::uint32_t at) { std::printf("visit %u\n", at); })); std::printf("offset %u\n", api::offset([] { return 7u; }, 3)); std::printf("shift %u\n", api::shift([] { return 7u; }, 4)); // A type generic only over lifetimes is a handle to its 'static form. { - api::name name = api::name::owned("a"); - api::name joined = name.join(api::name::owned("b")); + api::Name name = api::Name::owned("a"); + api::Name joined = name.join(api::Name::owned("b")); print("joined ", joined.as_str(), " "); print("", joined.debug(), " "); std::printf("%s\n", yes(joined == name)); @@ -318,7 +318,7 @@ int script() { // The name took a copy: the caller's buffer is its own again. text[0] = 'x'; print("renamed ", joined.as_str(), "\n"); - api::name longest = api::longer(std::move(joined), api::name::owned("dd")); + api::Name longest = api::longer(std::move(joined), api::Name::owned("dd")); print("longer ", longest.as_str(), "\n"); print("unnamed [", api::unnamed().as_str(), "]\n"); } @@ -358,51 +358,51 @@ int script() { std::printf("\n"); } } - api::window window{{2, 8}, api::end(5)}; + api::Window window{{2, 8}, api::end(5)}; std::printf("window %s %s ", yes(window.contains(4)), yes(window.contains(6))); print("", window.debug(), "\n"); - print("", api::greet("x", api::shape::square), "\n"); + print("", api::greet("x", api::Shape::square), "\n"); std::printf("len %zu\n", api::len("abc")); const std::uint8_t binary[] = {0xff, 0x00, 'a'}; print("hex ", api::hex(api::view(binary, sizeof binary)), "\n"); print("timed hex ", api::timed_hex(api::view(binary, sizeof binary)), "\n"); print("sample hex ", api::sample_hex(api::view(binary, sizeof binary)), "\n"); print("queued hex ", api::queued_hex(api::view(binary, sizeof binary)), "\n"); - print("tick ", api::tick_name(api::label{api::buf("tick"), std::nullopt, std::nullopt}), "\n"); - print("rooted ", api::rooted_name(api::label{api::buf("rooted"), std::nullopt, std::nullopt}), "\n"); + print("tick ", api::tick_name(api::Label{api::buf("tick"), std::nullopt, std::nullopt}), "\n"); + print("rooted ", api::rooted_name(api::Label{api::buf("rooted"), std::nullopt, std::nullopt}), "\n"); print("stored ", api::stored_text("stored"), "\n"); - print("layer ", api::layer_name(api::label{api::buf("layer"), std::nullopt, std::nullopt}), "\n"); - api::event events[] = {api::event_moved{p}, api::event_named{api::buf("n")}, api::event_done{}}; + print("layer ", api::layer_name(api::Label{api::buf("layer"), std::nullopt, std::nullopt}), "\n"); + api::Event events[] = {api::Event_Moved{p}, api::Event_Named{api::buf("n")}, api::Event_Done{}}; for (const auto &event : events) { print("", api::describe(event), "\n"); } // A `Copy` handle passed by value is copied, so it stays usable. { - api::stamp stamp = api::stamp::from_millis(2500); + api::Stamp stamp = api::Stamp::from_millis(2500); std::printf("secs %llu\n", ull(stamp.secs())); - std::printf("later %s\n", yes(api::stamp::from_millis(3000).later(stamp))); + std::printf("later %s\n", yes(api::Stamp::from_millis(3000).later(stamp))); std::printf("secs %llu\n", ull(stamp.secs())); - api::message message = api::message::new_(&stamp, "body"); - print("payload ", message.payload(), "\n"); - print("stamp ", message.stamp()->debug(), "\n"); + api::Message message = api::Message::new_(&stamp, "body"); + print("payload ", message.payload_get(), "\n"); + print("stamp ", message.stamp_get()->debug(), "\n"); std::printf("sent %s\n", yes(message.sent())); // Setters assign in place: the bytes are copied in, and a `Copy` handle copied. - api::stamp reset = api::stamp::from_millis(4000); - message.set_stamp(&reset); - message.set_payload("edited"); - print("payload ", message.payload(), "\n"); - print("stamp ", message.stamp()->debug(), "\n"); + api::Stamp reset = api::Stamp::from_millis(4000); + message.stamp_set(&reset); + message.payload_set("edited"); + print("payload ", message.payload_get(), "\n"); + print("stamp ", message.stamp_get()->debug(), "\n"); std::printf("secs %llu\n", ull(reset.secs())); - message.set_stamp(nullptr); - std::printf("stamp %s\n", yes(message.stamp().has_value())); + message.stamp_set(nullptr); + std::printf("stamp %s\n", yes(message.stamp_get().has_value())); } std::uint8_t levels[] = {1, 2, 3, 4}; std::printf("kept %u\n", api::emit(std::make_unique(), api::view(levels, sizeof levels))); { - api::logger logger = api::logger::new_(std::make_unique()); + api::Logger logger = api::Logger::new_(std::make_unique()); std::printf("write %s\n", yes(logger.write("hi"))); } std::printf("dropped\n"); @@ -414,10 +414,10 @@ int script() { })); { - api::channel channel = api::channel::new_(); + api::Channel channel = api::Channel::new_(); api::waker waker([]() { wakes++; }); { - api::channel_recv_future recv = channel.recv(); + api::Channel_recv_future recv = channel.recv(); std::printf("recv pending %s\n", yes(!recv.poll(waker).has_value())); channel.send("hi"); std::printf("woken %d\n", wakes); @@ -427,12 +427,12 @@ int script() { } channel.send(""); { - api::channel_recv_nonempty_future nonempty = channel.recv_nonempty(); + api::Channel_recv_nonempty_future nonempty = channel.recv_nonempty(); if (auto r = nonempty.poll(waker)) { if (*r) { std::printf("nonempty %zu\n", r->value().size()); } else { - print("nonempty err ", api::error_to_string(r->error()), "\n"); + print("nonempty err ", api::Error_to_string(r->error()), "\n"); } } else { std::printf("nonempty pending\n"); @@ -442,14 +442,14 @@ int script() { // The send happens at the call: the future is dropped unpolled. channel.send_later("eager"); { - api::channel_recv_future recv = channel.recv(); + api::Channel_recv_future recv = channel.recv(); if (auto payload = recv.poll(waker)) { print("recv ", *payload, "\n"); } } channel.send("sized"); { - api::channel_recv_sized_future sized = channel.recv_sized(); + api::Channel_recv_sized_future sized = channel.recv_sized(); if (auto r = sized.poll(waker)) { print("sized ", std::get<0>(*r), " "); std::printf("%zu\n", std::get<1>(*r)); @@ -463,99 +463,99 @@ int script() { // A value that holds itself is an object. { - api::node list = api::node::new_(1, std::nullopt); + api::Node list = api::Node::new_(1, std::nullopt); list.push(2); std::printf("node %llu\n", ull(list.sum())); } { - api::tally tally = api::tally::new_(); + api::Tally tally = api::Tally::new_(); tally.bump(); std::uint32_t count = tally.bump(); std::printf("tally %u %s\n", count, yes(tally == tally)); std::printf("both %u\n", api::bump_both(tally, tally)); - std::printf("peek %u\n", api::peek(tally, [](const api::tally &lent) { return lent.bump(); })); + std::printf("peek %u\n", api::peek(tally, [](const api::Tally &lent) { return lent.bump(); })); } // Another export's types, which this export's function takes and returns. { - api::area area = api::grow(api::area::new_({2, 3}), 1); - api::size size = area.size(); + api::Area area = api::grow(api::Area::new_({2, 3}), 1); + api::Size size = area.size(); std::printf("area %u %u %llu\n", size.width, size.height, ull(area.pixels())); } // Generic types, each at the instances it infers, named by their arguments. - api::pair_u32 pair{2, 5}; - api::pair_u32 swapped = pair.swap(); + api::Pair_u32 pair{2, 5}; + api::Pair_u32 swapped = pair.swap(); std::printf("pair %u %u sum %u ", swapped.left, swapped.right, pair.sum()); print("", pair.debug(), " "); std::printf("%s\n", yes(pair == swapped)); - std::printf("even %s %s\n", yes(pair.even()), yes(api::pair_u32{7, 7}.even())); + std::printf("even %s %s\n", yes(pair.even()), yes(api::Pair_u32{7, 7}.even())); // An instance renamed alone, beside another of `Pair` that keeps its name. - api::size_tag sizes = api::tag({1, 2}, 3); - api::pair_u8_size flipped = sizes.swap(); + api::Pair_Size_u8 sizes = api::tag({1, 2}, 3); + api::Pair_u8_Size flipped = sizes.swap(); std::printf("sized %u %u ", flipped.left, flipped.right.width); print("", flipped.debug(), "\n"); - api::pair_u8_size beside = api::pair_u32{258, 9}.beside_size(sizes.left); + api::Pair_u8_Size beside = api::Pair_u32{258, 9}.beside_Size(sizes.left); std::printf("beside %u ", beside.left); print("", beside.debug(), "\n"); { // At its default, named as the type itself. - api::stack stack = api::stack::new_(); + api::Stack stack = api::Stack::new_(); const char *names[] = {"a", "b"}; for (int i = 0; i < 2; i++) { - stack.push(api::counter::new_(names[i], static_cast(i) + 1)); + stack.push(api::Counter::new_(names[i], static_cast(i) + 1)); } - std::optional top = stack.pop(); + std::optional top = stack.pop(); std::printf("stack %zu Some(%llu)", stack.depth(), ull(top->get())); std::printf(" %s", yes(stack.pop().has_value())); std::printf(" %s\n", yes(!stack.pop().has_value())); // Generic functions and methods, at each implementation of the trait bounding them, // by reference, which leaves C++ its handle. Two arguments sharing a last segment // name their instances apart by the module of one. - api::counter from_point = api::counter::seeded_point("p", {-3, 4}); - api::counter from_size = api::counter::from_size("s", {5, 6}); - api::counter from_counter = api::counter::seeded_counter("c", from_point); - api::counter from_polar = api::counter::seeded_polar_point("q", {1, 2}); - std::printf("seeded %llu %llu %llu %llu past %llu seeds %llu\n", ull(from_point.get()), ull(from_size.get()), - ull(from_counter.get()), ull(from_polar.get()), ull(stack.depth_past_point({1, 2})), - ull(api::seeds_point_size({2, 3}))); + api::Counter from_point = api::Counter::seeded_Point("p", {-3, 4}); + api::Counter seeded_Size = api::Counter::seeded_Size("s", {5, 6}); + api::Counter from_counter = api::Counter::seeded_Counter("c", from_point); + api::Counter from_polar = api::Counter::seeded_polar_Point("q", {1, 2}); + std::printf("seeded %llu %llu %llu %llu past %llu seeds %llu\n", ull(from_point.get()), ull(seeded_Size.get()), + ull(from_counter.get()), ull(from_polar.get()), ull(stack.depth_past_Point({1, 2})), + ull(api::seeds_Point_Size({2, 3}))); } // A trait implemented for `Point` and `&Point` apart: one instance. - std::printf("weigh %u\n", api::weigh_point({-1, 2})); + std::printf("weigh %u\n", api::weigh_Point({-1, 2})); // Instances that name each other, and one made only by an impl of it. - std::printf("cross %u outer %u\n", api::cross(api::left_right_u8{api::right_u8{1}}, api::right_left_u8{api::left_u8{2}}), - api::outer_inner_u8::new_(4).get()); + std::printf("cross %u outer %u\n", api::cross(api::Left_Right_u8{api::Right_u8{1}}, api::Right_Left_u8{api::Left_u8{2}}), + api::Outer_Inner_u8::new_(4).get()); // A bound that outlives the call takes a value, and an instance only a constant names. - std::printf("kept %llu boxed %u\n", ull(api::kept_point({1, -2})), api::boxed().value); - std::printf("keep %d\n", api::keep_point({1, -2})); + std::printf("kept %llu boxed %u\n", ull(api::kept_Point({1, -2})), api::BOXED().value); + std::printf("keep %d\n", api::keep_Point({1, -2})); // Inferred instances taking a lent closure and a foreign implementation, and one only // its impl of a trait names. - api::pair_u32 sides{2, 5}; + api::Pair_u32 sides{2, 5}; std::printf("apply %u\n", api::apply_u32(sides, [](std::uint32_t side) { return side * 3; })); std::printf("report %u\n", api::report_u32(sides, std::make_unique())); - std::printf("wrapped %llu\n", ull(api::kept_wrap_u32({4}))); + std::printf("wrapped %llu\n", ull(api::kept_Wrap_u32({4}))); std::printf("keeps %u\n", api::keeps(std::make_unique()).value); // Its Drop runs when C++ destroys the handle, not before. { - api::token token = api::token::new_(7); - std::printf("token %u dropped %u\n", token.id(), api::dropped()); + api::Token token = api::Token::new_(7); + std::printf("token %u dropped %u\n", token.id_get(), api::dropped()); } std::printf("dropped %u\n", api::dropped()); // A field and the method of its name, each its own member. - api::retry retry = api::retry::new_(5u); - std::optional limit = retry.limit_setting(); + api::Retry retry = api::Retry::new_(5u); + std::optional limit = retry.limit_get(); std::printf("retry %d %u %u %u\n", limit.has_value(), limit.value_or(0), retry.limit(), retry.delay()); // The renamed field's setter, named as its getter. - retry.set_limit_setting(std::nullopt); + retry.limit_set(std::nullopt); std::printf("retry %u\n", retry.limit()); - std::printf("slot %u\n", api::slot_u8::new_(200).value()); + std::printf("slot %u\n", api::Slot_u8::new_(200).value()); // Foreign types: a URL parsed from text, socket addresses, OS paths, and `Vec`s. - api::url base = api::url::parse("https://example.com/a/b").value(); + api::Url base = api::Url::parse("https://example.com/a/b").value(); print("url ", base.to_string(), " host "); if (auto host = api::host(base)) { print("Some(\"", *host, "\")\n"); @@ -568,7 +568,7 @@ int script() { } else { print("resolve err ", resolved.error(), "\n"); } - if (auto parsed = api::url::parse("no scheme")) { + if (auto parsed = api::Url::parse("no scheme")) { print("parse ", parsed->to_string(), "\n"); } else { print("parse err ", parsed.error(), "\n"); @@ -588,7 +588,7 @@ int script() { std::vector names; names.emplace_back(api::view("a")); names.emplace_back(api::view("b")); - api::peer peer = api::peer{v6, names}.next("c"); + api::Peer peer = api::Peer{v6, names}.next("c"); std::printf("peer Peer { addr: [::%u%%%u]:%u, names: ", peer.addr.ip[15], peer.addr.scope_id, peer.addr.port); print_strings(peer.names); std::printf(" } "); @@ -635,13 +635,13 @@ int script() { std::printf("%llu", static_cast(counters[i].get())); } std::printf("\n"); - std::vector some, boxed; + std::vector some, boxed; some.push_back(std::move(counters[0])); boxed.push_back(std::move(counters[1])); auto worth = api::worth(std::move(some), std::move(boxed)); std::printf("worth %llu %llu\n", ull(worth), ull(api::worth(std::nullopt, {}))); std::optional counts[] = { - api::held_count(std::tuple{api::counter::new_("h", 4)}), + api::held_count(std::tuple{api::Counter::new_("h", 4)}), api::held_count(std::nullopt), }; std::printf("held"); @@ -664,31 +664,31 @@ int script() { std::printf("summed %d %d %d %d\n", summed.x, summed.y, none_summed.x, none_summed.y); std::printf("longest %zu %zu\n", api::longest({"a", "abc"}), api::longest({})); - api::json doc = api::json::parse(R"({"b": [1, 2.5, null], "a": "\u00e9"})").value(); + api::Json doc = api::Json::parse(R"({"b": [1, 2.5, null], "a": "\u00e9"})").value(); print("json ", api::encode(doc), "\n"); print("timed text ", api::timed_text(doc), "\n"); - api::json tagged = api::tagged(std::move(doc), "t"); + api::Json tagged = api::tagged(std::move(doc), "t"); print("tagged ", tagged.to_text(), "\n"); - if (auto parsed = api::json::parse("{\"a\":")) { + if (auto parsed = api::Json::parse("{\"a\":")) { print("json parse ", parsed->to_text(), "\n"); } else { print("json parse err ", parsed.error(), "\n"); } - api::site site = api::site::new_(base); - print("site ", site.home().to_string(), " "); - print("", site.home().to_string(), "\n"); + api::Site site = api::Site::new_(base); + print("site ", site.home_get().to_string(), " "); + print("", site.home_get().to_string(), "\n"); // Slices, passed in and returned as clones, and the socket address of each family. - auto shapes = api::shape_all(); + auto shapes = api::Shape_ALL(); std::printf("shapes ["); for (std::size_t i = 0; i < shapes.size(); i++) { - print(i == 0 ? "" : ", ", api::shape_debug(shapes[i]), ""); + print(i == 0 ? "" : ", ", api::Shape_debug(shapes[i]), ""); } std::printf("] greetings "); - print_strings(api::greetings()); + print_strings(api::GREETINGS()); std::printf("\n"); - api::trail trail = api::trail::new_({api::point{1, 2}, api::point{3, 4}}, {"a", "b"}); + api::Trail trail = api::Trail::new_({api::Point{1, 2}, api::Point{3, 4}}, {"a", "b"}); auto points = trail.points(); std::printf("trail ["); for (std::size_t i = 0; i < points.size(); i++) { @@ -746,11 +746,11 @@ int script() { next6.scope_id); // A returned `Vec<&Token>` holds clones, which the vector drops. - api::token twice = api::token::new_(9); + api::Token twice = api::Token::new_(9); std::uint32_t before = api::dropped(); { auto tokens = twice.twice(); - if (tokens.size() != 2 || tokens[1].id() != 9) { + if (tokens.size() != 2 || tokens[1].id_get() != 9) { return 1; } } @@ -770,7 +770,7 @@ int check(bool ok, const char *what) { } int futures() { - api::channel channel = api::channel::new_(); + api::Channel channel = api::Channel::new_(); // Blocking: the send lands from another thread while it waits. { @@ -909,7 +909,7 @@ int futures() { std::coroutine_handle handle; }; std::string got; - auto read = [&](api::channel &ch) -> detached { + auto read = [&](api::Channel &ch) -> detached { api::buf payload = co_await ch.recv().via(q.executor()); got = payload.str(); std::uint32_t value = co_await api::yielded(3).via(q.executor()); @@ -943,8 +943,8 @@ int lifetimes() { // one shows in the other, as it does between clones of an `Arc`. { std::uint32_t before = api::dropped(); - api::gauge a = api::gauge::new_(1); - api::gauge b = a; + api::Gauge a = api::Gauge::new_(1); + api::Gauge b = a; b.set(2); check(a.level() == 2, "copies of a shared type see each other's writes"); // Moving out of one copy moves out a clone, as Rust moves out of a clone, and @@ -952,7 +952,7 @@ int lifetimes() { check(std::move(b).into_level() == 2, "a shared copy moves out its value"); check(a.level() == 2 && api::dropped() == before, "moving out of a shared copy leaves the others"); { - api::gauge c = a; + api::Gauge c = a; (void)c; } check(api::dropped() == before, "a released copy frees nothing while another holds it"); @@ -965,9 +965,9 @@ int lifetimes() { // while a future borrows another. { std::uint32_t before = api::dropped(); - api::gauge a = api::gauge::new_(4); - std::optional made(a); - api::gauge_later_future later = made->later(); + api::Gauge a = api::Gauge::new_(4); + std::optional made(a); + api::Gauge_later_future later = made->later(); api::waker waker([]() {}); check(!later.poll(waker), "a future pends"); made.reset(); @@ -979,8 +979,8 @@ int lifetimes() { // keep apart: copying is `clone()`, which crosses into Rust, and a write through one // clone never shows in the other. { - api::latch a = api::latch::default_(); - api::latch b = a.clone(); + api::Latch a = api::Latch::default_(); + api::Latch b = a.clone(); check(b.set(1) && !a.get(), "a clone's write stays its own"); check(a.set(2) && a.get() == 2u && b.get() == 1u, "each clone holds its own value"); } @@ -989,7 +989,7 @@ int lifetimes() { // the handle, and the last copy frees it, once, on whichever thread drops it. { std::uint32_t before = api::dropped(); - std::optional mine(api::gauge::new_(7)); + std::optional mine(api::Gauge::new_(7)); std::mutex mutex; std::condition_variable cv; bool started = false; @@ -1029,9 +1029,9 @@ int lifetimes() { // A copy of a lent object lasts only its call, as the lent object does. { - api::gauge gauge = api::gauge::new_(3); - check(api::watch(gauge, [](const api::gauge &lent) { - api::gauge copy = lent; + api::Gauge gauge = api::Gauge::new_(3); + check(api::watch(gauge, [](const api::Gauge &lent) { + api::Gauge copy = lent; return copy.level(); }) == 3, "a copy of a lent object reads it during the call"); } @@ -1041,8 +1041,8 @@ int lifetimes() { { api::buf payload; { - api::message message = api::message::new_(nullptr, "kept"); - payload = message.payload(); + api::Message message = api::Message::new_(nullptr, "kept"); + payload = message.payload_get(); } check(payload.str() == "kept", "bytes outlive the object they came from"); std::uint32_t before = api::dropped(); @@ -1066,49 +1066,49 @@ int lifetimes() { // Each breaks a contract, which must abort with a message naming the function. int broken(const char *which) { if (std::strcmp(which, "moved") == 0) { - api::counter c = api::counter::new_("c", 0); - api::label label = std::move(c).into_label(std::nullopt); + api::Counter c = api::Counter::new_("c", 0); + api::Label label = std::move(c).into_label(std::nullopt); c.get(); } else if (std::strcmp(which, "moved from") == 0) { - api::counter c = api::counter::new_("c", 0); - api::counter d = std::move(c); + api::Counter c = api::Counter::new_("c", 0); + api::Counter d = std::move(c); c.get(); } else if (std::strcmp(which, "shared moved from") == 0) { - api::gauge g = api::gauge::new_(0); - api::gauge h = std::move(g); + api::Gauge g = api::Gauge::new_(0); + api::Gauge h = std::move(g); std::move(g).into_level(); } else if (std::strcmp(which, "shared moved out") == 0) { - api::gauge g = api::gauge::new_(0); + api::Gauge g = api::Gauge::new_(0); std::move(g).into_level(); std::move(g).into_level(); } else if (std::strcmp(which, "lent copied") == 0) { - static std::optional kept; - api::watch(api::gauge::new_(0), [](const api::gauge &lent) { + static std::optional kept; + api::watch(api::Gauge::new_(0), [](const api::Gauge &lent) { kept.emplace(lent); return lent.level(); }); } else if (std::strcmp(which, "value") == 0) { - api::counter c = api::counter::new_("c", 0); + api::Counter c = api::Counter::new_("c", 0); c.sub(1).value(); } else if (std::strcmp(which, "finished") == 0) { - api::channel channel = api::channel::new_(); + api::Channel channel = api::Channel::new_(); channel.send("x"); - api::channel_recv_future recv = channel.recv(); + api::Channel_recv_future recv = channel.recv(); api::waker waker([]() {}); recv.poll(waker); recv.poll(waker); } else if (std::strcmp(which, "null view") == 0) { api::len(api::view(nullptr, 1)); } else if (std::strcmp(which, "lent moved") == 0) { - api::registry registry = api::registry::new_(api::counter::new_("c", 0)); - registry.edit([](api::counter &counter) { - api::label label = std::move(counter).into_label(std::nullopt); + api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0)); + registry.edit([](api::Counter &counter) { + api::Label label = std::move(counter).into_label(std::nullopt); return false; }); } else if (std::strcmp(which, "lent kept") == 0) { - static std::optional kept; - api::registry registry = api::registry::new_(api::counter::new_("c", 0)); - registry.edit([](api::counter &counter) { + static std::optional kept; + api::Registry registry = api::Registry::new_(api::Counter::new_("c", 0)); + registry.edit([](api::Counter &counter) { kept.emplace(std::move(counter)); return false; }); diff --git a/ffi/tests/api.dart b/ffi/tests/api.dart index 9a5d1f3d8..638b14cee 100644 --- a/ffi/tests/api.dart +++ b/ffi/tests/api.dart @@ -100,7 +100,7 @@ Future until(bool Function() done, String what) async { } Future script() async { - print('limit ${api.limit}'); + print('limit ${api.LIMIT}'); final p = const api.Point(x: 1, y: 2).offset(2, 3); print('offset ${p.x} ${p.y}'); @@ -191,9 +191,9 @@ Future script() async { final registry = api.Registry(api.Counter('first', 1)); print('last ${registry.last() != null}'); registry.push(api.Counter('last', 2)); - final first = registry.first; + final first = registry.firstGet; first.add(10); - print('first ${registry.first.get()} lent ${first.get()}'); + print('first ${registry.firstGet.get()} lent ${first.get()}'); final last = registry.last()!; last.add(10); print('last ${registry.last()!.get()} lent ${last.get()}'); @@ -203,10 +203,10 @@ Future script() async { counter.add(by); return counter.edited() && counter.get() == 6; }); - print('edit $edited ${registry.first.get()}'); + print('edit $edited ${registry.firstGet.get()}'); // A setter moves an object in, as assignment does, closing it. - registry.setFirst(api.Counter('second', 3)); - print('set first ${registry.first.get()} ${registry.first.name()}'); + registry.firstSet(api.Counter('second', 3)); + print('set first ${registry.firstGet.get()} ${registry.firstGet.name()}'); print('walk ${api.walk(3, (cursor) => cursor.at() * 2)}'); print('each ${api.each(2, (at) => print('visit $at'))}'); print('offset ${api.offset(() => 7, 3)}'); @@ -266,18 +266,18 @@ Future script() async { print('later ${api.Stamp.fromMillis(3000).later(stamp)}'); print('secs ${stamp.secs()}'); final message = api.Message(stamp, utf8.encode('body')); - print('payload ${text(message.payload)}'); - print('stamp ${message.stamp!.debug()}'); + print('payload ${text(message.payloadGet)}'); + print('stamp ${message.stampGet!.debug()}'); print('sent ${message.sent()}'); // Setters assign in place: the bytes are copied in, and a `Copy` handle copied. final reset = api.Stamp.fromMillis(4000); - message.setStamp(reset); - message.setPayload(utf8.encode('edited')); - print('payload ${text(message.payload)}'); - print('stamp ${message.stamp!.debug()}'); + message.stampSet(reset); + message.payloadSet(utf8.encode('edited')); + print('payload ${text(message.payloadGet)}'); + print('stamp ${message.stampGet!.debug()}'); print('secs ${reset.secs()}'); - message.setStamp(null); - print('stamp ${message.stamp != null}'); + message.stampSet(null); + print('stamp ${message.stampGet != null}'); print('kept ${api.emit(PrintLog(), bytes([1, 2, 3, 4]))}'); final logger = api.Logger(PrintLog()); @@ -358,7 +358,7 @@ Future script() async { // Generic functions and methods, at each implementation of the trait bounding them, by // reference, which leaves Dart its object. final fromPoint = api.Counter.seededPoint('p', const api.Point(x: -3, y: 4)); - final fromSize = api.Counter.fromSize('s', const api.Size(width: 5, height: 6)); + final fromSize = api.Counter.seededSize('s', const api.Size(width: 5, height: 6)); final fromCounter = api.Counter.seededCounter('c', fromPoint); final fromPolar = api.Counter.seededPolarPoint('q', const api.PolarPoint(turns: 1, distance: 2)); print( @@ -376,7 +376,7 @@ Future script() async { ); print('cross $cross outer ${api.OuterInnerU8(4).get()}'); // A bound that outlives the call takes a value, and an instance only a constant names. - print('kept ${api.keptPoint(const api.Point(x: 1, y: -2))} boxed ${api.boxed.value}'); + print('kept ${api.keptPoint(const api.Point(x: 1, y: -2))} boxed ${api.BOXED.value}'); print('keep ${api.keepPoint(const api.Point(x: 1, y: -2))}'); // Inferred instances taking a lent closure and a foreign implementation, and one only // its impl of a trait names. @@ -388,16 +388,16 @@ Future script() async { // Its Drop runs when Dart closes it, not before. final token = api.Token(7); - print('token ${token.id} dropped ${api.dropped()}'); + print('token ${token.idGet} dropped ${api.dropped()}'); token.close(); print('dropped ${api.dropped()}'); // A field and the method of its name, each its own member. final retry = api.Retry(5); - final limit = retry.limitSetting; + final limit = retry.limitGet; print('retry ${limit == null ? 0 : 1} ${limit ?? 0} ${retry.limit()} ${retry.delay()}'); // The renamed field's setter, named as its getter. - retry.setLimitSetting(null); + retry.limitSet(null); print('retry ${retry.limit()}'); print('slot ${api.SlotU8(200).value()}'); @@ -463,7 +463,7 @@ Future script() async { print('json parse err ${e.error}'); } final site = api.Site(base); - print('site ${site.home} ${site.home}'); + print('site ${site.homeGet} ${site.homeGet}'); // Slices, passed in and returned as clones, and the socket address of each family. String debugs(List values) => @@ -473,7 +473,7 @@ Future script() async { api.Label l => l.debug(), _ => throw ArgumentError(v), }).join(', ')}]'; - print('shapes ${debugs(api.Shape.all)} greetings ${quoted(api.greetings)}'); + print('shapes ${debugs(api.Shape.ALL)} greetings ${quoted(api.GREETINGS)}'); final trail = api.Trail([const api.Point(x: 1, y: 2), const api.Point(x: 3, y: 4)], ['a', 'b']); print('trail ${debugs(trail.points())} ${debugs(trail.labels())}'); // Returned iterators, each collected at the call: drained labels are gone after. @@ -674,7 +674,7 @@ Future owned() async { check(api.dropped() == before + 1, 'closing frees at once'); token.close(); check(api.dropped() == before + 1, 'closing again frees nothing'); - closed(() => token.id, 'Token', 'a closed token refuses a read'); + closed(() => token.idGet, 'Token', 'a closed token refuses a read'); unclosed(); await collect(() => api.dropped() >= before + 2, 'the garbage collector frees what nobody closed'); @@ -717,9 +717,9 @@ void closedObjects() { closed(() => api.Tally() == tally, 'Tally', '== with a closed object'); final registry = api.Registry(api.Counter('first', 1)); registry.close(); - closed(() => registry.first, 'Registry', "a field's getter of a closed object"); + closed(() => registry.firstGet, 'Registry', "a field's getter of a closed object"); final second = api.Counter('second', 2); - closed(() => registry.setFirst(second), 'Registry', "a field's setter of a closed object"); + closed(() => registry.firstSet(second), 'Registry', "a field's setter of a closed object"); check(second.get() == 2, 'a call that throws moves nothing in'); // An argument, alone, or in a list, beside others the call then moves none of. @@ -767,11 +767,11 @@ void closedObjects() { final editing = api.Registry(api.Counter('e', 4)); final edited = editing.edit((counter) { editing.close(); - closed(() => editing.first, 'Registry', 'an object closed in a closure refuses calls'); + closed(() => editing.firstGet, 'Registry', 'an object closed in a closure refuses calls'); return counter.get() == 4; }); check(edited, 'the call holding a closed object still reaches it'); - closed(() => editing.first, 'Registry', 'an object closed in a closure stays closed'); + closed(() => editing.firstGet, 'Registry', 'an object closed in a closure stays closed'); // As the drop of what it holds shows: once, after the call. late api.Logger logger; late CountLog log; diff --git a/ffi/tests/api.go b/ffi/tests/api.go index e5eebe654..30c098d41 100644 --- a/ffi/tests/api.go +++ b/ffi/tests/api.go @@ -242,9 +242,9 @@ func script() { registry := must(api.NewRegistry(api.NewCounter("first", 1))) fmt.Printf("last %t\n", must(registry.Last()) != nil) ok(registry.Push(api.NewCounter("last", 2))) - first := must(registry.First()) + first := must(registry.FirstGet()) must(first.Add(10)) - fmt.Printf("first %d lent %d\n", must(must(registry.First()).Get()), must(first.Get())) + fmt.Printf("first %d lent %d\n", must(must(registry.FirstGet()).Get()), must(first.Get())) last := must(registry.Last()) must(last.Add(10)) fmt.Printf("last %d lent %d\n", must(must(registry.Last()).Get()), must(last.Get())) @@ -254,10 +254,10 @@ func script() { must(counter.Add(by)) return must(counter.Edited()) && must(counter.Get()) == 6 })) - fmt.Printf("edit %t %d\n", edited, must(must(registry.First()).Get())) + fmt.Printf("edit %t %d\n", edited, must(must(registry.FirstGet()).Get())) // A setter moves an object in, as assignment does, closing its handle. - ok(registry.SetFirst(api.NewCounter("second", 3))) - fmt.Printf("set first %d %s\n", must(must(registry.First()).Get()), must(must(registry.First()).Name())) + ok(registry.FirstSet(api.NewCounter("second", 3))) + fmt.Printf("set first %d %s\n", must(must(registry.FirstGet()).Get()), must(must(registry.FirstGet()).Name())) fmt.Printf("walk %d\n", api.Walk(3, func(cursor *api.Cursor) uint64 { return must(cursor.At()) * 2 })) fmt.Printf("each %d\n", api.Each(2, func(at uint32) { fmt.Printf("visit %d\n", at) })) fmt.Printf("offset %d\n", api.Offset(func() uint32 { return 7 }, 3)) @@ -325,18 +325,18 @@ func script() { fmt.Printf("later %t\n", must(api.StampFromMillis(3000).Later(stamp))) fmt.Printf("secs %d\n", must(stamp.Secs())) message := must(api.NewMessage(stamp, []byte("body"))) - fmt.Printf("payload %s\n", must(message.Payload()).Copy()) - fmt.Printf("stamp %s\n", must(message.Stamp()).GoString()) + fmt.Printf("payload %s\n", must(message.PayloadGet()).Copy()) + fmt.Printf("stamp %s\n", must(message.StampGet()).GoString()) fmt.Printf("sent %t\n", must(message.Sent())) // Setters assign in place: the bytes are copied in, and a `Copy` handle copied. reset := api.StampFromMillis(4000) - ok(message.SetStamp(reset)) - ok(message.SetPayload([]byte("edited"))) - fmt.Printf("payload %s\n", must(message.Payload()).Copy()) - fmt.Printf("stamp %s\n", must(message.Stamp()).GoString()) + ok(message.StampSet(reset)) + ok(message.PayloadSet([]byte("edited"))) + fmt.Printf("payload %s\n", must(message.PayloadGet()).Copy()) + fmt.Printf("stamp %s\n", must(message.StampGet()).GoString()) fmt.Printf("secs %d\n", must(reset.Secs())) - ok(message.SetStamp(nil)) - fmt.Printf("stamp %t\n", must(message.Stamp()) != nil) + ok(message.StampSet(nil)) + fmt.Printf("stamp %t\n", must(message.StampGet()) != nil) // Rust frees the implementation once it drops it, releasing Go's. fmt.Printf("kept %d\n", api.Emit(printLog{}, []byte{1, 2, 3, 4})) @@ -417,7 +417,7 @@ func script() { // Generic functions and methods, at each implementation of the trait bounding them, by // reference, which leaves Go its handle. fromPoint := api.CounterSeededPoint("p", api.Point{X: -3, Y: 4}) - fromSize := api.CounterFromSize("s", api.Size{Width: 5, Height: 6}) + fromSize := api.CounterSeededSize("s", api.Size{Width: 5, Height: 6}) fromCounter := must(api.CounterSeededCounter("c", fromPoint)) fromPolar := api.CounterSeededPolarPoint("q", api.PolarPoint{Turns: 1, Distance: 2}) fmt.Printf("seeded %d %d %d %d past %d seeds %d\n", must(fromPoint.Get()), must(fromSize.Get()), must(fromCounter.Get()), must(fromPolar.Get()), @@ -441,20 +441,20 @@ func script() { // Its Drop runs when Go closes the handle, not before. token := api.NewToken(7) - fmt.Printf("token %d dropped %d\n", must(token.Id()), api.Dropped()) + fmt.Printf("token %d dropped %d\n", must(token.IdGet()), api.Dropped()) token.Close() fmt.Printf("dropped %d\n", api.Dropped()) // A field and the method of its name, each its own member. retry := api.NewRetry(ptr[uint32](5)) - limit := must(retry.LimitSetting()) + limit := must(retry.LimitGet()) isSome := 0 if limit != nil { isSome = 1 } fmt.Printf("retry %d %d %d %d\n", isSome, *limit, must(retry.Limit()), must(retry.Delay())) // The renamed field's setter, named as its getter. - ok(retry.SetLimitSetting(nil)) + ok(retry.LimitSet(nil)) fmt.Printf("retry %d\n", must(retry.Limit())) fmt.Printf("slot %d\n", must(api.NewSlotU8(200).Value())) @@ -533,7 +533,7 @@ func script() { check(errors.As(err, &parse), "parse returns its message") fmt.Printf("json parse err %s\n", err) site := must(api.NewSite(base)) - fmt.Printf("site %s %s\n", must(site.Home()), must(site.Home())) + fmt.Printf("site %s %s\n", must(site.HomeGet()), must(site.HomeGet())) // Slices, passed in and returned as clones, and the socket address of each family. fmt.Printf("shapes %s greetings %s\n", list(api.ShapeALL(), func(s api.Shape) string { return shapes[s] }), list(api.GREETINGS(), quoted)) @@ -700,11 +700,11 @@ func closed() { isClosed(err, "Tally", "Equal with a closed object") registry := must(api.NewRegistry(api.NewCounter("first", 1))) registry.Close() - _, err = registry.First() + _, err = registry.FirstGet() isClosed(err, "Registry", "a field's getter of a closed object") // A call that fails moves nothing in. second := api.NewCounter("second", 2) - isClosed(registry.SetFirst(second), "Registry", "a field's setter of a closed object") + isClosed(registry.FirstSet(second), "Registry", "a field's setter of a closed object") check(must(second.Get()) == 2, "a failed call leaves what it would move") // A record's method, whose receiver holds an object. @@ -758,7 +758,7 @@ func closed() { go func() { defer wg.Done() for { - id, err := token.Id() + id, err := token.IdGet() if err != nil { isClosed(err, "Token", "a call racing Close") failed.Add(1) @@ -838,7 +838,7 @@ func bytes() { // Bytes an object returns outlive its Close: no Bytes depends on an object. body := strings.Repeat("b", 2048) message := must(api.NewMessage(api.StampFromMillis(1), []byte(body))) - payload := must(message.Payload()) + payload := must(message.PayloadGet()) message.Close() runtime.GC() check(string(payload.Copy()) == body, "bytes an object returned outlive its Close") diff --git a/ffi/tests/api.kt b/ffi/tests/api.kt index 57844780a..5bf6dec1b 100644 --- a/ffi/tests/api.kt +++ b/ffi/tests/api.kt @@ -120,9 +120,9 @@ fun script() { val registry = Registry.new(Counter.new("first", 1u)) println("last ${registry.last() != null}") registry.push(Counter.new("last", 2u)) - val first = registry.first + val first = registry.firstGet first.add(10u) - println("first ${registry.first.get()} lent ${first.get()}") + println("first ${registry.firstGet.get()} lent ${first.get()}") val last = registry.last()!! last.add(10u) println("last ${registry.last()!!.get()} lent ${last.get()}") @@ -132,10 +132,10 @@ fun script() { counter.add(by) counter.edited() && counter.get() == 6uL } - println("edit $edited ${registry.first.get()}") + println("edit $edited ${registry.firstGet.get()}") // A setter moves an object in, as assignment does, closing its handle. - registry.setFirst(Counter.new("second", 3u)) - println("set first ${registry.first.get()} ${registry.first.name()}") + registry.firstSet(Counter.new("second", 3u)) + println("set first ${registry.firstGet.get()} ${registry.firstGet.name()}") println("walk ${walk(3u) { cursor -> cursor.at() * 2u }}") println("each ${each(2u) { at -> println("visit $at") }}") println("offset ${offset({ 7u }, 3u)}") @@ -195,18 +195,18 @@ fun script() { println("later ${Stamp.fromMillis(3000u).later(stamp)}") println("secs ${stamp.secs()}") val message = Message.new(stamp, "body".encodeToByteArray()) - println("payload ${message.payload.text()}") - println("stamp ${message.stamp!!.debug()}") + println("payload ${message.payloadGet.text()}") + println("stamp ${message.stampGet!!.debug()}") println("sent ${message.sent()}") // Setters assign in place: the bytes are copied in, and a `Copy` handle copied. val reset = Stamp.fromMillis(4000u) - message.setStamp(reset) - message.setPayload("edited".encodeToByteArray()) - println("payload ${message.payload.text()}") - println("stamp ${message.stamp!!.debug()}") + message.stampSet(reset) + message.payloadSet("edited".encodeToByteArray()) + println("payload ${message.payloadGet.text()}") + println("stamp ${message.stampGet!!.debug()}") println("secs ${reset.secs()}") - message.setStamp(null) - println("stamp ${message.stamp != null}") + message.stampSet(null) + println("stamp ${message.stampGet != null}") println("kept ${emit(PrintLog(), byteArrayOf(1, 2, 3, 4))}") Logger.new(PrintLog()).use { logger -> println("write ${logger.write("hi")}") } @@ -273,7 +273,7 @@ fun script() { println("pair ${swapped.left} ${swapped.right} sum ${pair.sum()} ${pair.debug()} ${pair == swapped}") println("even ${pair.even()} ${PairU32(7u, 7u).even()}") // An instance renamed alone, beside another of `Pair` that keeps its name. - val sized: SizeTag = tag(Size(1u, 2u), 3u) + val sized: PairSizeU8 = tag(Size(1u, 2u), 3u) val flipped: PairU8Size = sized.swap() println("sized ${flipped.left} ${flipped.right.width} ${flipped.debug()}") val beside = PairU32(258u, 9u).besideSize(sized.left) @@ -288,7 +288,7 @@ fun script() { // Generic functions and methods, at each implementation of the trait bounding them, by // reference, which leaves Kotlin its handle. val fromPoint = Counter.seededPoint("p", Point(-3, 4)) - val fromSize = Counter.fromSize("s", Size(5u, 6u)) + val fromSize = Counter.seededSize("s", Size(5u, 6u)) val fromCounter = Counter.seededCounter("c", fromPoint) val fromPolar = Counter.seededPolarPoint("q", PolarPoint(1u, 2u)) println( @@ -313,16 +313,16 @@ fun script() { // Its Drop runs when Kotlin closes the handle, not before. val token = Token.new(7u) - println("token ${token.id} dropped ${dropped()}") + println("token ${token.idGet} dropped ${dropped()}") token.close() println("dropped ${dropped()}") // A field and the method of its name, each its own member. val retry = Retry.new(5u) - val limit = retry.limitSetting + val limit = retry.limitGet println("retry ${if (limit != null) 1 else 0} ${limit ?: 0u} ${retry.limit()} ${retry.delay()}") // The renamed field's setter, named as its getter. - retry.setLimitSetting(null) + retry.limitSet(null) println("retry ${retry.limit()}") println("slot ${SlotU8.new(200u).value()}") @@ -374,7 +374,7 @@ fun script() { println("json parse err ${e.error}") } val site = Site.new(base) - site.home.use { a -> site.home.use { b -> println("site $a $b") } } + site.homeGet.use { a -> site.homeGet.use { b -> println("site $a $b") } } // Slices, passed in and returned as clones, and the socket address of each family. println("shapes ${Shape.ALL.joinToString(", ", "[", "]") { it.debug() }} greetings ${GREETINGS.joinToString(", ", "[", "]") { "\"$it\"" }}") @@ -582,12 +582,12 @@ fun owned() { check(dropped() == before + 1u, "closing frees at once") token.close() check(dropped() == before + 1u, "closing again frees nothing") - check(runCatching { token.id }.exceptionOrNull()?.message == "Token is closed", "a call after close throws") + check(runCatching { token.idGet }.exceptionOrNull()?.message == "Token is closed", "a call after close throws") // Bytes outlive the object that returned them and the record that held them. val body = ByteArray(2048) { 'b'.code.toByte() } val message = Message.new(Stamp.fromMillis(1u), body) - val payload = message.payload + val payload = message.payloadGet message.close() val carried = nextPacket(Packet(1u, Bytes(body))).payload uncollected(before + 1u, "the GC frees nothing still reachable") @@ -660,7 +660,7 @@ fun raced() { thread { while (true) { val id = try { - token.id + token.idGet } catch (e: IllegalStateException) { check(e.message == "Token is closed", "a call racing close throws") failed.incrementAndGet() @@ -693,7 +693,7 @@ fun raced() { val id = rounds % 100u + 1u check(tokenBytes(id, 2048u)[2047] == id.toByte(), "a read by index races the GC") check(tokenBytes(id, 2048u).slice(1024).toByteArray().all { it == id.toByte() }, "a slice's copy races the GC") - check(Token.new(id).id == id, "a call on an object races the GC") + check(Token.new(id).idGet == id, "a call on an object races the GC") rounds++ } collecting.set(false) diff --git a/ffi/tests/api.py b/ffi/tests/api.py index e6bc1c64f..c7e11ca52 100644 --- a/ffi/tests/api.py +++ b/ffi/tests/api.py @@ -188,9 +188,9 @@ def script(): registry = api.Registry.new(api.Counter.new("first", 1)) print(f"last {flag(registry.last() is not None)}") registry.push(api.Counter.new("last", 2)) - first = registry.first + first = registry.first_get first.add(10) - print(f"first {registry.first.get()} lent {first.get()}") + print(f"first {registry.first_get.get()} lent {first.get()}") last = registry.last() last.add(10) print(f"last {registry.last().get()} lent {last.get()}") @@ -201,10 +201,10 @@ def edit(counter): counter.add(by) return counter.edited() and counter.get() == 6 - print(f"edit {flag(registry.edit(edit))} {registry.first.get()}") + print(f"edit {flag(registry.edit(edit))} {registry.first_get.get()}") # A setter moves an object in, as assignment does, closing it. - registry.set_first(api.Counter.new("second", 3)) - print(f"set first {registry.first.get()} {registry.first.name()}") + registry.first_set(api.Counter.new("second", 3)) + print(f"set first {registry.first_get.get()} {registry.first_get.name()}") print(f"walk {api.walk(3, lambda cursor: cursor.at() * 2)}") print(f"each {api.each(2, lambda at: print(f'visit {at}'))}") print(f"offset {api.offset(lambda: 7, 3)}") @@ -265,18 +265,18 @@ def edit(counter): print(f"later {flag(api.Stamp.from_millis(3000).later(stamp))}") print(f"secs {stamp.secs()}") message = api.Message.new(stamp, b"body") - print(f"payload {text(message.payload)}") - print(f"stamp {message.stamp!r}") + print(f"payload {text(message.payload_get)}") + print(f"stamp {message.stamp_get!r}") print(f"sent {flag(message.sent())}") # Setters assign in place: the bytes are copied in, and a `Copy` handle copied. reset = api.Stamp.from_millis(4000) - message.set_stamp(reset) - message.set_payload(b"edited") - print(f"payload {text(message.payload)}") - print(f"stamp {message.stamp!r}") + message.stamp_set(reset) + message.payload_set(b"edited") + print(f"payload {text(message.payload_get)}") + print(f"stamp {message.stamp_get!r}") print(f"secs {reset.secs()}") - message.set_stamp(None) - print(f"stamp {flag(message.stamp is not None)}") + message.stamp_set(None) + print(f"stamp {flag(message.stamp_get is not None)}") print(f"kept {api.emit(PrintLog(), bytes([1, 2, 3, 4]))}") logger = api.Logger.new(PrintLog()) @@ -374,7 +374,7 @@ async def block(): # Generic functions and methods, at each implementation of the trait bounding them, by # reference, which leaves Python its object. from_point = api.Counter.seeded_point("p", api.Point(-3, 4)) - from_size = api.Counter.from_size("s", api.Size(5, 6)) + from_size = api.Counter.seeded_size("s", api.Size(5, 6)) from_counter = api.Counter.seeded_counter("c", from_point) from_polar = api.Counter.seeded_polar_point("q", api.PolarPoint(1, 2)) print( @@ -400,16 +400,16 @@ async def block(): # Its Drop runs when its last reference goes, not before. token = api.Token.new(7) - print(f"token {token.id} dropped {api.dropped()}") + print(f"token {token.id_get} dropped {api.dropped()}") del token print(f"dropped {api.dropped()}") # A field and the method of its name, each its own member. retry = api.Retry.new(5) - limit = retry.limit_setting + limit = retry.limit_get print(f"retry {0 if limit is None else 1} {limit or 0} {retry.limit()} {retry.delay()}") # The renamed field's setter, named as its getter. - retry.set_limit_setting(None) + retry.limit_set(None) print(f"retry {retry.limit()}") print(f"slot {api.SlotU8.new(200).value()}") @@ -466,7 +466,7 @@ async def block(): except api.StringException as e: print(f"json parse err {e.error}") site = api.Site.new(base) - print(f"site {site.home} {site.home}") + print(f"site {site.home_get} {site.home_get}") # Slices, passed in and returned as clones, and the socket address of each family. print(f"shapes {debugs(api.Shape.ALL)} greetings {quoted(api.GREETINGS)}") @@ -772,7 +772,7 @@ def raises(f, message): (api.Counter, "get"): "method_descriptor", (api.Counter, "new"): "staticmethod", (api.Counter, "__repr__"): "method_descriptor", - (api.Registry, "first"): "getset_descriptor", + (api.Registry, "first_get"): "getset_descriptor", (api.Point, "offset"): "method_descriptor", (api.Channel, "send_later"): "method_descriptor", # An `async def` stays a coroutine function. @@ -815,7 +815,7 @@ def raises(f, message): check(api.has_text.__doc__ == "Whether `note` has text, even empty.", "a function's docstring") check(api.Gauge.set.__doc__ == "Set the level each clone reads.", "a method's docstring") check(api.Counter.get.__doc__ is None, "a method without docs has no docstring") - check(api.Registry.first.__doc__ is None, "a property without docs has no docstring") + check(api.Registry.first_get.__doc__ is None, "a property without docs has no docstring") print("calls ok") diff --git a/ffi/tests/api.swift b/ffi/tests/api.swift index dd6083139..ead631d03 100644 --- a/ffi/tests/api.swift +++ b/ffi/tests/api.swift @@ -232,9 +232,9 @@ func script() { let registry = Registry.new(Counter.new("first", 1)) print("last \(registry.last() != nil)") registry.push(Counter.new("last", 2)) - let first = registry.first + let first = registry.firstGet _ = try! first.add(10) - print("first \(registry.first.get()) lent \(first.get())") + print("first \(registry.firstGet.get()) lent \(first.get())") let last = registry.last()! _ = try! last.add(10) print("last \(registry.last()!.get()) lent \(last.get())") @@ -244,10 +244,10 @@ func script() { _ = try! counter.add(by) return counter.edited() && counter.get() == 6 } - print("edit \(edited) \(registry.first.get())") + print("edit \(edited) \(registry.firstGet.get())") // A setter moves an object in, as assignment does, closing its handle. - registry.setFirst(Counter.new("second", 3)) - print("set first \(registry.first.get()) \(registry.first.name())") + registry.firstSet(Counter.new("second", 3)) + print("set first \(registry.firstGet.get()) \(registry.firstGet.name())") print("walk \(walk(3) { cursor in cursor.at() * 2 })") print("each \(each(2) { at in print("visit \(at)") })") print("offset \(offset({ 7 }, 3))") @@ -316,18 +316,18 @@ func script() { print("later \(Stamp.fromMillis(3000).later(stamp))") print("secs \(stamp.secs())") let message = Message.new(stamp, Data("body".utf8)) - print("payload \(text(message.payload))") - print("stamp \(debug(message.stamp!))") + print("payload \(text(message.payloadGet))") + print("stamp \(debug(message.stampGet!))") print("sent \(message.sent())") // Setters assign in place: the bytes are copied in, and a `Copy` handle copied. let reset = Stamp.fromMillis(4000) - message.setStamp(reset) - message.setPayload(Data("edited".utf8)) - print("payload \(text(message.payload))") - print("stamp \(debug(message.stamp!))") + message.stampSet(reset) + message.payloadSet(Data("edited".utf8)) + print("payload \(text(message.payloadGet))") + print("stamp \(debug(message.stampGet!))") print("secs \(reset.secs())") - message.setStamp(nil) - print("stamp \(message.stamp != nil)") + message.stampSet(nil) + print("stamp \(message.stampGet != nil)") // Bound first, so Swift's reference is gone before the print. let kept = emit(PrintLog(), Data([1, 2, 3, 4])) @@ -398,7 +398,7 @@ func script() { print("pair \(swappedPair.left) \(swappedPair.right) sum \(pair.sum()) \(debug(pair)) \(pair == swappedPair)") print("even \(pair.even()) \(PairU32(left: 7, right: 7).even())") // An instance renamed alone, beside another of `Pair` that keeps its name. - let sized: SizeTag = tag(Size(width: 1, height: 2), 3) + let sized: PairSizeU8 = tag(Size(width: 1, height: 2), 3) let flipped: PairU8Size = sized.swap() print("sized \(flipped.left) \(flipped.right.width) \(debug(flipped))") let beside = PairU32(left: 258, right: 9).besideSize(sized.left) @@ -413,7 +413,7 @@ func script() { // Generic functions and methods, at each implementation of the trait bounding them, by // reference, which leaves Swift its handle. let fromPoint = Counter.seededPoint("p", Point(x: -3, y: 4)) - let fromSize = Counter.fromSize("s", Size(width: 5, height: 6)) + let fromSize = Counter.seededSize("s", Size(width: 5, height: 6)) let fromCounter = Counter.seededCounter("c", fromPoint) let fromPolar = Counter.seededPolarPoint("q", PolarPoint(turns: 1, distance: 2)) print( @@ -441,16 +441,16 @@ func script() { // Its Drop runs when Swift releases the last reference, not before. do { let token = Token.new(7) - withExtendedLifetime(token) { print("token \(token.id) dropped \(dropped())") } + withExtendedLifetime(token) { print("token \(token.idGet) dropped \(dropped())") } } print("dropped \(dropped())") // A field and the method of its name, each its own member. let retry = Retry.new(5) - let limit = retry.limitSetting + let limit = retry.limitGet print("retry \(limit != nil ? 1 : 0) \(limit ?? 0) \(retry.limit()) \(retry.delay())") // The renamed field's setter, named as its getter. - retry.setLimitSetting(nil) + retry.limitSet(nil) print("retry \(retry.limit())") print("slot \(SlotU8.new(200).value())") @@ -505,7 +505,7 @@ func script() { check(false, "parse throws its message") } let site = Site.new(base) - print("site \(site.home) \(site.home)") + print("site \(site.homeGet) \(site.homeGet)") // Slices, passed in and returned as clones, and the socket address of each family. print("shapes \(Shape.ALL) greetings \(GREETINGS)") @@ -653,7 +653,7 @@ func owned() { check(dropped() == before + 1, "releasing returned bytes frees Rust's") do { let token = Token.new(2) - check(token.id == 2, "a token reads back") + check(token.idGet == 2, "a token reads back") } check(dropped() == before + 2, "releasing an object frees its handle") do { diff --git a/ffi/tests/fixtures.rs b/ffi/tests/fixtures.rs index b1887cb4f..f1bbab6fb 100644 --- a/ffi/tests/fixtures.rs +++ b/ffi/tests/fixtures.rs @@ -75,8 +75,9 @@ fn api() -> &'static Api { static API: OnceLock = OnceLock::new(); API.get_or_init(|| { let out = scratch().join("api").join("out"); - // A feature enabled on another export directly, which the C library must build with. - let features = ["--features", "dep-fixture/doubled"]; + // A feature of the facade's that enables one in a crate it re-exports, which the C + // library must build with. + let features = ["--features", "doubled"]; let output = export("api", &out, &features); assert!( output.status.success(), @@ -158,24 +159,24 @@ fn api_c() { // Each broken contract aborts, naming the function and what broke. for (case, message) in [ - ("moved", "api_counter_get: a handle's value was moved out"), - ("borrowed", "api_channel_clear: a handle is already borrowed"), + ("moved", "api_Counter_get: a handle's value was moved out"), + ("borrowed", "api_Channel_clear: a handle is already borrowed"), ("utf8", "api_greet: a string is not UTF-8"), ( "finished", - "api_channel_recv_poll: a future is polled after it finished", + "api_Channel_recv_poll: a future is polled after it finished", ), ("enum", "api_greet: an enum value is invalid"), ("bound", "api_count: a bound's kind is invalid"), ("socket", "api_ports: an IPv4 socket address has IPv6 bytes or fields"), ("family", "api_nexts: an IPv6 socket address where IPv4 is taken"), ("array", "api_vec_u16_free: an array is not as Rust returned it"), - ("lent", "api_registry_edit: a lent handle was freed"), + ("lent", "api_Registry_edit: a lent handle was freed"), ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), - ("null", "api_registry_edit: a function is null"), + ("null", "api_Registry_edit: a function is null"), ] { let output = Command::new(&exe).arg(case).output().unwrap(); assert!(!output.status.success(), "{case} did not abort"); @@ -207,22 +208,22 @@ fn api_cpp() { run(Command::new(&exe).arg("futures")); run(Command::new(&exe).arg("lifetimes")); for (case, message) in [ - ("moved", "api_counter_get: a handle's value was moved out"), - ("moved from", "api_counter_get: a handle is null"), - ("shared moved from", "api_gauge_into_level: a handle is null"), + ("moved", "api_Counter_get: a handle's value was moved out"), + ("moved from", "api_Counter_get: a handle is null"), + ("shared moved from", "api_Gauge_into_level: a handle is null"), ( "shared moved out", - "api_gauge_into_level: a handle's value was moved out", + "api_Gauge_into_level: a handle's value was moved out", ), ("lent copied", "a lent object was copied past its call"), ("value", "value() of a result that holds an error"), ( "finished", - "api_channel_recv_poll: a future is polled after it finished", + "api_Channel_recv_poll: a future is polled after it finished", ), ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), ("lent kept", "a lent object was moved out of"), ("null view", "a view of null bytes has a length"), @@ -482,9 +483,9 @@ fn kotlin(native: &str, vm: &dyn Fn(&[&str]) -> Command) { ("lent kept", "Counter is closed"), ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), - ("callback threw", "api_fixture::Log::log: a call into Kotlin threw"), + ("callback threw", "api::Log::log: a call into Kotlin threw"), ("stray", "MalformedInputException"), ("family", "an IPv6 address has no Rust SocketAddrV4"), ] { @@ -535,9 +536,9 @@ fn api_python() { ("lent held", "Counter: a call still holds it when its lend ends"), ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), - ("callback threw", "api_fixture::Log::log: a call into Python raised"), + ("callback threw", "api::Log::log: a call into Python raised"), ("family", "ValueError: an IPv6 address has no Rust SocketAddrV4"), ] { let output = python(&dir, &[case]).output().unwrap(); @@ -633,7 +634,7 @@ fn api_swift() { ("lent kept", "Counter is closed"), ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), ("family", "api_nexts: an IPv6 socket address where IPv4 is taken"), ("stray", "a path from Rust is not UTF-8"), @@ -705,9 +706,9 @@ fn api_dart() { ("lent kept", "Bad state: Counter is closed"), ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), - ("callback threw", "api_fixture::Log::log: a call into Dart threw"), + ("callback threw", "api::Log::log: a call into Dart threw"), ("family", "an IPv6 address has no Rust SocketAddrV4"), ("stray", "a path from Rust is not UTF-8"), ("lent stray", "a path from Rust is not UTF-8"), @@ -875,7 +876,7 @@ fn api_go() { for (case, message) in [ ( "lent moved", - "api_counter_into_label: a lent handle's value cannot move out", + "api_Counter_into_label: a lent handle's value cannot move out", ), ("family", "an address that is not IPv4 has no Rust SocketAddrV4"), ("text", "api_greet: a string is not UTF-8"), @@ -952,8 +953,7 @@ fn relative_target() { "the census failed:\n{}", String::from_utf8_lossy(&output.stderr) ); - // One census per export. - assert!(census.join("api-fixture.md").exists() && census.join("dep-fixture.md").exists()); + assert!(census.join("api-fixture.md").exists()); assert!(dir.join("build").exists()); } @@ -1000,17 +1000,18 @@ fn reject() { assert_eq!(errors.join("\n") + "\n", expected); } -/// The C library builds a dependency with what the check enabled in it, so a feature -/// enabled directly in one that is no export fails loud. +/// The C library builds the facade's dependencies with what the facade enables in them, +/// so a feature enabled directly in one fails loud. #[test] -fn feature_of_non_export() { +fn feature_of_dependency() { let out = scratch().join("feature").join("out"); let output = export("api", &out, &["--features", "bytes/std"]); assert!(!output.status.success(), "exporting with bytes/std succeeded"); let stderr = String::from_utf8_lossy(&output.stderr); assert!( - stderr - .contains("--features bytes/std: the export builds bytes with only the features api-fixture enables in it"), + stderr.contains( + "--features bytes/std: the export builds api-fixture's dependencies with only the features it enables in them" + ), "{stderr}" ); } @@ -1022,7 +1023,7 @@ fn package(name: &str) -> PathBuf { let manifest = format!("[package]\nname = \"{name}\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[workspace]\n"); std::fs::write(package.join("Cargo.toml"), manifest).unwrap(); std::fs::write(package.join("src/lib.rs"), "pub fn one() -> u32 {\n\t1\n}\n").unwrap(); - std::fs::write(package.join("ffi.toml"), format!("package = \"{name}\"\n")).unwrap(); + std::fs::write(package.join("ffi.toml"), "").unwrap(); package } @@ -1069,155 +1070,8 @@ fn symlinked() { ); } -/// Another export is found among what the check builds on the host, whatever target -/// cargo's config names: a version only another platform depends on is no rival. -#[test] -fn other_platform() { - let package = package("platform"); - for (dir, version) in [("unix", "1.0.0"), ("windows", "2.0.0")] { - let dep = package.join(dir); - std::fs::create_dir_all(dep.join("src")).unwrap(); - let manifest = format!("[package]\nname = \"platform-dep\"\nversion = \"{version}\"\nedition = \"2024\"\n"); - std::fs::write(dep.join("Cargo.toml"), manifest).unwrap(); - std::fs::write(dep.join("src/lib.rs"), "pub struct Size {\n\tpub width: u32,\n}\n").unwrap(); - } - let manifest = "[package]\nname = \"platform\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\ - [target.'cfg(unix)'.dependencies]\nunix = { package = \"platform-dep\", path = \"unix\" }\n\n\ - [target.'cfg(windows)'.dependencies]\nwindows = { package = \"platform-dep\", path = \"windows\" }\n\n\ - [workspace]\nexclude = [\"unix\", \"windows\"]\n"; - std::fs::write(package.join("Cargo.toml"), manifest).unwrap(); - std::fs::write( - package.join("src/lib.rs"), - "#[cfg(unix)]\nuse unix as dep;\n#[cfg(windows)]\nuse windows as dep;\n\npub fn width(size: dep::Size) -> u32 {\n\tsize.width\n}\n", - ) - .unwrap(); - std::fs::write( - package.join("ffi.toml"), - "package = \"platform\"\n\n[crates.platform-dep]\n", - ) - .unwrap(); - let output = census(&package.join("ffi.toml"), &[]) - .env("CARGO_BUILD_TARGET", "x86_64-pc-windows-msvc") - .output() - .unwrap(); - assert!( - output.status.success(), - "the census failed:\n{}", - String::from_utf8_lossy(&output.stderr) - ); -} - -/// Another export is found among what the library builds with: a version only a -/// dev-dependency's features pull in is no rival. -#[test] -fn dev_features() { - let package = package("dev"); - let write = |path: &str, text: &str| { - let path = package.join(path); - std::fs::create_dir_all(path.parent().unwrap()).unwrap(); - std::fs::write(path, text).unwrap(); - }; - for (dir, version) in [("exp1", "1.0.0"), ("exp2", "2.0.0")] { - write( - &format!("{dir}/Cargo.toml"), - &format!("[package]\nname = \"dev-exp\"\nversion = \"{version}\"\nedition = \"2024\"\n"), - ); - write( - &format!("{dir}/src/lib.rs"), - "pub struct Size {\n\tpub width: u32,\n}\n", - ); - } - write( - "route/Cargo.toml", - "[package]\nname = \"dev-route\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\ - [dependencies]\nexp2 = { package = \"dev-exp\", path = \"../exp2\", optional = true }\n\n\ - [features]\nextra = [\"dep:exp2\"]\n", - ); - write("route/src/lib.rs", ""); - write( - "Cargo.toml", - "[package]\nname = \"dev\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\ - [dependencies]\nexp = { package = \"dev-exp\", path = \"exp1\" }\nroute = { package = \"dev-route\", path = \"route\" }\n\n\ - [dev-dependencies]\nroute = { package = \"dev-route\", path = \"route\", features = [\"extra\"] }\n\n\ - [workspace]\nexclude = [\"exp1\", \"exp2\", \"route\"]\n", - ); - write( - "src/lib.rs", - "pub fn width(size: exp::Size) -> u32 {\n\tsize.width\n}\n", - ); - write("ffi.toml", "package = \"dev\"\n\n[crates.dev-exp]\n"); - let output = census(&package.join("ffi.toml"), &[]).output().unwrap(); - assert!( - output.status.success(), - "the census failed:\n{}", - String::from_utf8_lossy(&output.stderr) - ); -} - -/// Another export is found in the checked package's own graph, read plainly: a root -/// package that the workspace's default members leave out, under forced color, reaching -/// an export twice. -#[test] -fn diamond() { - let package = package("diamond"); - let write = |path: &str, text: &str| { - let path = package.join(path); - std::fs::create_dir_all(path.parent().unwrap()).unwrap(); - std::fs::write(path, text).unwrap(); - }; - // `left` and `right` both depend on the export `shared`, which depends on `leaf`. - for (name, deps) in [ - ("app", ""), - ( - "left", - "shared = { package = \"diamond-shared\", path = \"../shared\" }\n", - ), - ( - "right", - "shared = { package = \"diamond-shared\", path = \"../shared\" }\n", - ), - ("shared", "leaf = { package = \"diamond-leaf\", path = \"../leaf\" }\n"), - ("leaf", ""), - ] { - write( - &format!("{name}/Cargo.toml"), - &format!( - "[package]\nname = \"diamond-{name}\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[dependencies]\n{deps}" - ), - ); - write( - &format!("{name}/src/lib.rs"), - "pub struct Size {\n\tpub width: u32,\n}\n", - ); - } - write( - "Cargo.toml", - "[package]\nname = \"diamond\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n\ - [dependencies]\nleft = { package = \"diamond-left\", path = \"left\" }\n\ - right = { package = \"diamond-right\", path = \"right\" }\n\ - shared = { package = \"diamond-shared\", path = \"shared\" }\n\n\ - [workspace]\nmembers = [\"app\", \"left\", \"right\", \"shared\", \"leaf\"]\ndefault-members = [\"app\"]\n", - ); - write( - "src/lib.rs", - "pub fn width(size: shared::Size) -> u32 {\n\tsize.width\n}\n", - ); - write("ffi.toml", "package = \"diamond\"\n\n[crates.diamond-shared]\n"); - let output = census(&package.join("ffi.toml"), &[]) - .env("CARGO_TERM_COLOR", "always") - .output() - .unwrap(); - assert!( - output.status.success(), - "the census failed:\n{}", - String::from_utf8_lossy(&output.stderr) - ); -} - -/// Another export is bound by the configured package's dependency on it, not its crate -/// name or source: a used dependency whose crate shares an unused export's name stands -/// in for none, and two exports of one crate name export, by names the configured package -/// calls them that are its own crate name and `bytes`, which the glue names too, with +/// Two crates of one crate name export through the facade, which shares their name too, +/// by names it calls them that the glue names too, `bytes` and its own crate name, with /// their sources remapped. #[test] fn lookalike() { @@ -1249,27 +1103,8 @@ fn lookalike() { ); write( "src/lib.rs", - "pub fn width(size: bytes::Size) -> u32 {\n\tsize.width\n}\n", - ); - write("ffi.toml", "package = \"lookalike\"\n\n[crates.lookalike-exp]\n"); - let output = census(&package.join("ffi.toml"), &[]).output().unwrap(); - assert!(!output.status.success(), "a lookalike stood in for the export"); - let stderr = String::from_utf8_lossy(&output.stderr); - assert!( - stderr.contains( - "[crates.lookalike-exp] needs `lookalike` to use its dependency `shared`, and it uses 0 crates of it" - ), - "{stderr}" - ); - - write( - "src/lib.rs", - "pub fn total(a: shared::Size, b: bytes::Size) -> u32 {\n\ta.width + b.width\n}\n", - ); - write( - "ffi.toml", - "package = \"lookalike\"\n\n[crates.lookalike-exp]\n\n\ - [crates.lookalike-exp.rename]\n\"Size\" = \"exp_size\"\n\n[crates.lookalike-other]\n", + "pub mod exp {\n\tpub use shared::Size;\n}\n\npub mod other {\n\tpub use bytes::Size;\n}\n\n\ + pub fn total(a: shared::Size, b: bytes::Size) -> u32 {\n\ta.width + b.width\n}\n", ); let out = package.join("out"); let remap = format!("--remap-path-prefix={}=/src", package.display()); @@ -1363,10 +1198,9 @@ fn plain() { .env("RUSTFLAGS", "-D warnings")); } -/// Another export patched in for a registry dependency is bound by its resolved package, -/// though the dependency names no path, and the glue builds it patched in too, though -/// the dependency goes by its library's crate name. So is a dependency of no platform's, -/// which cargo resolves all the same. +/// A dependency the facade re-exports from, patched in for a registry dependency, is +/// patched into the glue's build too, though the dependency goes by its library's crate +/// name. So is a dependency of no platform's, which cargo resolves all the same. #[test] fn patched() { let package = package("patched"); @@ -1395,9 +1229,8 @@ fn patched() { ), ( "src/lib.rs", - "pub fn width(size: rust2_ffi_patched_core::Size) -> u32 {\n\tsize.width\n}\n", + "pub use rust2_ffi_patched_core::Size;\n\npub fn width(size: Size) -> u32 {\n\tsize.width\n}\n", ), - ("ffi.toml", "package = \"patched\"\n\n[crates.rust2-ffi-patched-dep]\n"), ], ); export_and_build(&package, &["--offline"]); @@ -1459,7 +1292,8 @@ fn git_patched() { export_and_build(&package, &[]); } -/// Another export the workspace `[replace]`s, by a spec naming no package, is carried as +/// A dependency the facade re-exports from, which the workspace `[replace]`s by a spec +/// naming no package, is carried as /// a `[patch]` of it, which resolves alike since a replacement keeps its version. A /// replacement from the source it replaces is an error, since no patch can carry it. #[test] @@ -1498,23 +1332,15 @@ fn replaced() { ), ( "src/lib.rs", - "pub fn width(size: rust2_ffi_replaced_dep::Size) -> u32 {\n\tsize.width\n}\n", - ), - ( - "ffi.toml", - "package = \"replaced\"\n\n[crates.rust2-ffi-replaced-dep]\n", + "pub use rust2_ffi_replaced_dep::Size;\n\npub fn width(size: Size) -> u32 {\n\tsize.width\n}\n", ), + ("ffi.toml", ""), ], ); }; files("{ path = \"dep\" }"); export_and_build(&package, &[]); - // Not an export, which is a path package, but what the package builds with. files(&format!("{{ git = \"{url}\", branch = \"fixed\" }}")); - write_all( - &package, - &[("ffi.toml", "package = \"replaced\"\nexclude = [\"width\"]\n")], - ); let output = census(&package.join("ffi.toml"), &[]).output().unwrap(); assert!(!output.status.success(), "a replacement from its own source passed"); let stderr = String::from_utf8_lossy(&output.stderr); @@ -1580,13 +1406,10 @@ fn config_patched() { ); write( "pkg/src/lib.rs", - "pub fn width(size: rust2_ffi_config_dep::Size, cli: rust2_ffi_config_cli::Margin) -> u32 {\n\ - \tsize.width + cli.width\n}\n", - ); - write( - "pkg/ffi.toml", - "package = \"config-patched\"\n\n[crates.rust2-ffi-config-cli]\n\n[crates.rust2-ffi-config-dep]\n", + "pub use rust2_ffi_config_cli::Margin;\npub use rust2_ffi_config_dep::Size;\n\n\ + pub fn width(size: Size, cli: Margin) -> u32 {\n\tsize.width + cli.width\n}\n", ); + write("pkg/ffi.toml", ""); let out = root.join("out"); let output = Command::new(env!("CARGO_BIN_EXE_rust2-ffi")) .arg("--config") diff --git a/quest/m1/ffi-export/README.md b/quest/m1/ffi-export/README.md index 6afb3de85..0031d91f8 100644 --- a/quest/m1/ffi-export/README.md +++ b/quest/m1/ffi-export/README.md @@ -61,7 +61,7 @@ names and defaults, and fails the export on a public item it cannot export. ## Required -- [C names and crates with no config](/quest/m1/ffi-export/names.md) - a collision-free naming rule, a facade crate, and the prefix from Cargo, leaving only `exclude` in `ffi.toml` +- [Language names keep module paths apart](/quest/m1/ffi-export/modules.md) - no generator flattens two facade paths into one name, so the stand-in needs no alias - [Sessions on sans-IO QUIC](/quest/m1/ffi-export/session-quic.md) - bindings move UDP datagrams and drive the session's poll themselves, over moq's runtime-free QUIC driver once it lands - [C++ bindings on MSVC](/quest/m1/ffi-export/cpp.md) - the nightly's MSVC job passes the C++ checks - [Generated Python type-checks](/quest/m1/ffi-export/python-types.md) - mypy and pyright pass on the generated Python diff --git a/quest/m1/ffi-export/modules.md b/quest/m1/ffi-export/modules.md new file mode 100644 index 000000000..167ccc54d --- /dev/null +++ b/quest/m1/ffi-export/modules.md @@ -0,0 +1,21 @@ +# [M] Language names keep module paths apart + +## Goal + +Every [rust2-ffi](/ffi/README.md) generator gives two items with distinct C names distinct +names of its own, so a facade never re-exports an item under a second name to dodge a +clash, and `just ffi standin` drops `ConnectTargetError`. + +## Plan + +C keeps each path segment's case, but Kotlin, Swift, Python, Go, and Dart join a type's +segments in Pascal case, which loses where they split: moq-tokio's `ConnectError` and +`connect::Error` are both `TokioConnectError`, so the stand-in re-exports the second as +`ConnectTargetError`. Go's package functions share the namespace with types, so +`clock::wall` takes a trailing `_` beside `clock::Wall`. Each generator either nests a +facade module as its language's namespace (Kotlin and Swift nested objects, Python +submodules, Dart prefixed imports) or fails the export at both spans, as C does on a +residual clash. Measure which moq would hit before choosing. + +Public API: the generated names of items in a facade module, in every language but C and +C++. diff --git a/quest/m1/ffi-export/names.md b/quest/m1/ffi-export/names.md deleted file mode 100644 index b8afcfdcc..000000000 --- a/quest/m1/ffi-export/names.md +++ /dev/null @@ -1,39 +0,0 @@ -# [L] C names and crates with no config - -## Goal - -[rust2-ffi](/ffi/README.md) names every exported item by a collision-free rule and takes -its crate set and prefix from Cargo alone, so `ffi.toml` loses `package`, `prefix`, -`[crates.*]`, and every `rename`, leaving only `exclude` for -[the refusals](/quest/m1/ffi-export/refusals.md) to remove. - -## Plan - -Decided with the maintainer on 2026-11-04, after -[#390](https://github.com/moq-dev/rust2/pull/390) added moq-specific renames: the exporter -takes an arbitrary crate with no config, and a vanity rename, if ever supported, lives -inline in source next to the item. - -- Naming rule: keep Rust's case. ✅ `moq_session_Stats` for the type and - `moq_Session_stats` for the method, so a crate that follows Rust's naming conventions - never collides. A residual clash, possible only with unconventional names, fails the - export with both spans. Rejected: snake case with `__` before members; mangled - (`moq_7session5Stats`), which is provably free but unreadable in C. - Case alone does not separate a field's getter and setter from a method of the same - name, which conventional Rust allows: moq's `Backoff::initial` field and method, and - the reject fixture's `Limit::max` setter beside `set_max`. The rule must name field - accessors apart from methods too, with no rename. -- Items of different crates are named by their path in the facade, so moq-tokio's - `Client` and moq-net's need the facade to re-export them under distinct paths. -- Crate set: one facade crate. ✅ The exporter exports one crate's `pub` API, following - its `pub use` re-exports, so moq-ffi re-exports what it ships, in moq's tree. Rejected: - crates listed on the command line, which only moves the list into a script. -- Prefix: the facade's Cargo lib name. ✅ `[lib] name` defaults to the package name; - moq-ffi sets it to `moq` in its own Cargo.toml for `moq_`. Rejected: the package name, - which would give `moq_ffi_`. - -Every generator derives its names from the C ones, so each changes with them; regenerate -moq's export and the stand-in (`just ffi standin` becomes the facade's `pub use` list) and -update `ffi/README.md`'s naming and config sections. - -Public API: every C name the exporter emits, and the `ffi.toml` keys it reads. diff --git a/quest/m1/ffi-export/refusals.md b/quest/m1/ffi-export/refusals.md index 67c088c83..198776685 100644 --- a/quest/m1/ffi-export/refusals.md +++ b/quest/m1/ffi-export/refusals.md @@ -9,7 +9,7 @@ item it cannot export fails the export with a span. ## Plan Decided with the maintainer on 2026-11-04: configless, and an unsupported public item -fails the export rather than being excluded. ✅ Split: [the naming rule](/quest/m1/ffi-export/names.md) +fails the export rather than being excluded. ✅ Split: the naming rule first, then this. Rejected: one quest that waits for the census to reach zero. Today `ffi/moq/ffi.toml` excludes about 300 refused items across moq-net, moq-pattern, @@ -19,7 +19,3 @@ moq-ffi's hand-written wrapper still covers. This quest's own remaining work, de `ffi.toml` and the config reader, stays in that epic's README. Public API: the exporter's command line and the C API of every item that exports. - -## Required - -- [C names and crates with no config](/quest/m1/ffi-export/names.md) - the collision-free naming rule and a facade crate, leaving only `exclude` in `ffi.toml`