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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ re-exports its items instead, which export. A type no path reaches is refused.
| a type whose only generic parameters are lifetimes, such as `Path<'a>` | an object holding its `'static` form |
| a generic type, at each instance it infers | that instance as any other type, with each method whose impl and own bounds it meets; any other instance is refused |
| a `pub` field of an object | a getter returning a copy, and a setter assigning in place, as `x.field = value` does |
| a `pub` trait | a callback interface: a table of C functions and their data |
| a `pub` trait | a callback interface: a table of C functions and their data; a trait C cannot implement, such as one with a blanket impl, an associated type, or a supertrait but a marker, is left out, since C never passes one, and a signature taking one from C is refused |
| a type parameter or `impl` bounded by `Fn`, `FnMut`, or `FnOnce`, which no lifetime bound or return lets outlive the call | a C function and its `void *` data, which Rust calls only during the call; any other closure is refused |
| `fn` and inherent `pub` methods | functions named through their type |
| `Result<T, E>` | `bool f(..., T *out, E *err)`; an `E` of another crate that implements `Display`, such as `anyhow::Error` or `io::Error`, is `String`, its message |
Expand All @@ -90,8 +90,9 @@ re-exports its items instead, which export. A type no path reaches is refused.
| a `pub const` | a `#define` if a `bool` or integer, else a function returning it |
| `impl Into<X>`, `impl AsRef<X>`, or a bound implying one, as a supertrait does | `X`, or `&X` and then `String` or `Vec<T>` where `X` is `str` or `[T]` |
| `impl Into<X>`, where `X` does not map | what a `From` implementation into `X` takes: a source parameter by its bounds as above, so `impl<B: Into<Bytes>> From<B> for Timed<Bytes>` takes `Bytes`, and by each `Into<Y>` whose `Y` does not map, what converts into `Y` in turn, each `Y` once and refusing a chain longer than the crate's recursion limit; any other source as it is, so `impl<'a, V> From<&'a V> for Timed<&'a V>` takes `&V`. Two sources C passes as one buffer, such as `String` and `Vec<u8>`, are refused, unless they are forms of one value, as `&str` and `String` are |
| `impl IntoIterator<Item = X>`, where `X` is no type parameter | `Vec<X>` |
| another type parameter | `serde_json::Value` for a serde `Serialize` or `Deserialize` bound, else the first of `String`, `Bytes`, and `Vec<u8>` meeting its bounds, or a foreign implementation of its one exported trait; else an instance of the function at each type the exports implement a trait bounding it for, as `&X` where that meets its bounds too and no bound, its own or one a supertrait implies, makes it outlive the call, so C keeps its handle, and, as an argument of a generic type, at each argument of an instance that meets its bounds and makes each generic type the signature names an instance |
| `impl IntoIterator<Item = X>`, where `X` is no type parameter | `Vec<X>`, or `&[Y]` where `X` is `&Y` |
| `impl io::Read` | `&[u8]` |
| another type parameter | `serde_json::Value` for a serde `Serialize` or `Deserialize` bound, else the first of `String`, `Bytes`, and `Vec<u8>` meeting its bounds, or a foreign implementation of its one exported trait; else an instance of the function at each type the exports implement a trait bounding it for, through a blanket impl at each type implementing what the impl requires, as `&X` where that meets its bounds too and no bound, its own or one a supertrait implies, makes it outlive the call, so C keeps its handle, and, as an argument of a generic type, at each argument of an instance that meets its bounds and makes each generic type the signature names an instance |

A generic type infers the instances it exports at, with no config: the one at its
defaults, if each type parameter has one; the one each inherent impl is of, such as
Expand Down Expand Up @@ -619,7 +620,8 @@ runtime package and each crate's include [`runtime/rt.go`](runtime/rt.go) and

`--census dir` exports nothing. It writes `<package>.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, then the `exclude` list that exports the rest, and
what else it left out, such as what C can never hold. It ignores the config's `exclude`. It still fails on an `instance not inferred`
what else it left out, such as what C can never hold and the traits C cannot implement, and why.
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.

## Tests
Expand Down
27 changes: 27 additions & 0 deletions ffi/fixtures/api/examples/transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ fn main() {
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 five = Point { x: 5, y: 2 };
println!(
"kept or {} {} keeper {}",
api::kept_or(five),
api::kept_or::<Point>(None),
api::keeper(five)
);
let sides = Pair { left: 2u32, right: 5 };
println!("apply {}", api::apply(sides, |side| side * 3));
println!("report {}", api::report(sides, Print));
Expand Down Expand Up @@ -484,6 +491,26 @@ fn main() {
api::longest(["a".to_string(), "abc".to_string()]),
api::longest([])
);
let words = ["a".to_string(), "b".to_string()];
println!("first of {:?} {:?}", api::first_of(&words), api::first_of(&[]));
let limits = api::byte_limits(Bytes::from_static(b"a"), Bytes::from_static(b"bc"));
let waits = api::Limits {
low: std::time::Duration::from_millis(5),
high: std::time::Duration::from_secs(2),
};
println!(
"limits {} {} breadth {}",
String::from_utf8_lossy(&limits.low),
String::from_utf8_lossy(&limits.high),
api::breadth(waits).as_millis()
);
let parts = [Bytes::from_static(b"ab"), Bytes::new(), Bytes::from_static(b"c")];
println!(
"joined {} {} read {}",
String::from_utf8_lossy(&api::joined(&parts)),
api::joined(&[]).len(),
api::read_len(&b"hello"[..])
);
let doc: serde_json::Value = r#"{"b": [1, 2.5, null], "a": "\u00e9"}"#.parse().expect("a JSON document");
println!("json {}", api::encode(&doc));
println!("timed text {}", api::timed_text(&doc));
Expand Down
10 changes: 3 additions & 7 deletions ffi/fixtures/api/ffi.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
# 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`,
# `Tick`, `Tock`, `Scoped`, `Rooted`, `Copied`, `Stored`, and `Layer` only convert, so
# their parameters take what converts into them, and
# out a method the others keep, `Timed`, `Sample`, `Queued`, `Tick`, `Tock`, `Scoped`,
# `Rooted`, `Copied`, `Stored`, and `Layer` only convert, so their parameters take what
# converts into them, and
# `field@` leaves out a field alone where a method takes its path, as `Retry`'s and
# `Slot<u8>`'s do. `Slot<u32>` has no such method, so its path alone names its field. A
# function instance is left out by its path, as `depth_past` at `polar::Point` is.
exclude = [
"Copied",
"Kept",
"Layer",
"Node::next",
"Pair<u8, api::Size>::swap",
"Payload",
"Queued",
"Rooted",
"Sample",
"Scoped",
"Seed",
"Slot<u32>::value",
"field@Retry::delay",
"field@Slot<u8>::value",
Expand Down
89 changes: 89 additions & 0 deletions ffi/fixtures/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,24 @@ pub fn keep(kept: impl Kept) -> i32 {
kept.kept()
}

/// `kept`'s, or 0 without one: `K` exports at each type implementing `Kept`, and the
/// parameter naming it at the option of each.
pub fn kept_or<K: Kept>(kept: impl Into<Option<K>>) -> i32 {
kept.into().map_or(0, Kept::kept)
}

/// Implemented for what `Kept` is, as moq's `Boxable` is for each `Session`: a blanket impl,
/// which C cannot implement apart, so a parameter it bounds exports at each type implementing
/// what the impl requires.
pub trait Keeper: Kept + Copy {}

impl<K: Kept + Copy> Keeper for K {}

/// As `keep`, through the blanket impl.
pub fn keeper(keeper: impl Keeper) -> i32 {
keeper.kept()
}

/// A generic record only a constant names, which infers its instance.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Boxed<T> {
Expand Down Expand Up @@ -1438,6 +1456,44 @@ pub fn report<T: Copy + fmt::Debug, L: Log>(pair: Pair<T>, log: L) -> u32 {
.count() as u32
}

/// What a log kept, made from any log.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LogCount {
pub kept: u32,
}

/// A count, which C cannot implement as it needs `Copy`: a parameter it bounds exports at
/// each type implementing it.
pub trait Tallied: Copy {
fn tally(self) -> u32;
}

impl Tallied for LogCount {
fn tally(self) -> u32 {
self.kept
}
}

impl Tallied for Point {
fn tally(self) -> u32 {
self.x.unsigned_abs()
}
}

impl<L: Log> From<L> for LogCount {
fn from(log: L) -> Self {
Self {
kept: u32::from(log.log(3, "tally")),
}
}
}

/// What `log` kept, as each tally that converts from a log: `LogCount`, and not `Point`,
/// which is `Tallied` too but converts from no log.
pub fn tally<T: Tallied + From<L>, L: Log>(log: L) -> u32 {
T::from(log).tally()
}

/// A wrapped value, which only its impl of `Seed<u64>` at `Wrap<u32>` names: each
/// function taking a seed infers that instance.
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -1683,6 +1739,39 @@ pub fn summed(points: impl IntoIterator<Item = Point>) -> Point {
points.into_iter().fold(Point::default(), |sum, p| sum.offset(p.x, p.y))
}

/// Bounds on a measure, as moq's `health::Limits<Duration>` is: a generic record whose
/// instances are of types that map by role.
#[derive(Clone, Debug, PartialEq)]
pub struct Limits<T> {
pub low: T,
pub high: T,
}

/// How far apart `limits` are: an instance at `Duration`, passed in.
pub fn breadth(limits: Limits<Duration>) -> Duration {
limits.high.saturating_sub(limits.low)
}

/// `low` and `high` as limits: an instance at `Bytes`, returned.
pub fn byte_limits(low: Bytes, high: Bytes) -> Limits<Bytes> {
Limits { low, high }
}

/// The parts joined, from anything that lends them for the call: a slice C lends.
pub fn joined<'a, I: IntoIterator<Item = &'a Bytes>>(parts: I) -> Bytes {
parts.into_iter().flat_map(|part| part.iter().copied()).collect()
}

/// The first of `items`, a borrow of what C lends that is copied out before the call ends.
pub fn first_of<'a, I: IntoIterator<Item = &'a String>>(items: I) -> Option<&'a String> {
items.into_iter().next()
}

/// How many bytes `reader` holds: bytes C lends for the call.
pub fn read_len(mut reader: impl std::io::Read) -> u64 {
std::io::copy(&mut reader, &mut std::io::sink()).expect("a slice reads")
}

/// The length of the longest word, naming its iterator.
pub fn longest<I>(words: I) -> usize
where
Expand Down
45 changes: 27 additions & 18 deletions ffi/fixtures/reject/expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,38 @@ fixtures/reject/src/lib.rs:475:1: rust2-ffi: cannot export fn `naturals`: foreig
fixtures/reject/src/lib.rs:480: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:485: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:490: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:495:1: rust2-ffi: cannot export trait `Source`: callback signature `items`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:500: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:505: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:522: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:539: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:516:1: rust2-ffi: cannot export fn `tallied`: unmet bound `u32: std::convert::From<C>`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:521:1: rust2-ffi: cannot export fn `drain`: trait object `dyn Source`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:495:1: note: C cannot implement `Source`: callback signature `items`
fixtures/reject/src/lib.rs:526:1: rust2-ffi: cannot export fn `summed_forever`: 'static borrow `&'static [u32]`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:531:1: rust2-ffi: cannot export fn `retext_last`: 'static borrow `&'a [std::string::String]`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:531:20: note: `'a` is `'static`, as a handle outlives every call
fixtures/reject/src/lib.rs:538:1: rust2-ffi: cannot export fn `outlast`: 'static borrow `&'b [std::string::String]`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:538:20: note: `'b` is `'static`, as a handle outlives every call
fixtures/reject/src/lib.rs:545:1: rust2-ffi: cannot export fn `retext_into`: 'static borrow `&'a [std::string::String]`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:545:20: note: `'a` is `'static`, as a handle outlives every call
fixtures/reject/src/lib.rs:54:1: rust2-ffi: cannot export fn `excluded`: excluded type `Excluded`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:562: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:576: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:581: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:588: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:594:1: rust2-ffi: cannot export fn `floats`: collection not built from its items `std::collections::BTreeSet<f64>`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:599: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:604: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:624: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:631: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:655: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:554: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:559: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:576: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:593: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:616: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:630: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:635: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:642: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:648:1: rust2-ffi: cannot export fn `floats`: collection not built from its items `std::collections::BTreeSet<f64>`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:653: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:658: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:65: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:60:2: note: fn `reject::audio_encoder::finish` is here
fixtures/reject/src/lib.rs:673: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:684:9: rust2-ffi: cannot export use `dep`: re-export of a whole crate `dep`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:687:9: rust2-ffi: cannot export use `renamed`: re-export of a whole crate `dep`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:678: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:685: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:69:1: rust2-ffi: cannot export fn `letters`: Range `char`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:6:1: rust2-ffi: cannot export fn `pair`: tuple of references `(&str, u8)`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:709: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:727: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:738:9: rust2-ffi: cannot export use `dep`: re-export of a whole crate `dep`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:741:9: rust2-ffi: cannot export use `renamed`: re-export of a whole crate `dep`; exclude it in the config to leave it out
fixtures/reject/src/lib.rs:78: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:76:6: note: `'a` is `'static`, as a handle outlives every call
fixtures/reject/src/lib.rs:83:2: rust2-ffi: cannot export method `Text::view`: returned object that borrows `Text<'_>`; exclude it in the config to leave it out
Expand Down
Loading