future_form_ffi 0.1.0

FFI support for future_form: host-driven polling, opaque handles, and effect slots
Documentation
#![allow(missing_docs, clippy::missing_const_for_fn, clippy::future_not_send)]

use core::task::Poll;

use future_form::{FutureForm, Local, Sendable, future_form};
use future_form_ffi::poll_once::PollOnce;
use futures::future::{BoxFuture, LocalBoxFuture};

// -- Direct usage (no macro) -------------------------------------------------

#[test]
fn test_sendable_poll_once_from_future() {
    let mut fut = Sendable::from_future(async { 42u32 });
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

#[test]
fn test_local_poll_once_from_future() {
    let mut fut = Local::from_future(async { 42u32 });
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

#[test]
fn test_sendable_poll_once_ready() {
    let mut fut: BoxFuture<'_, u32> = Sendable::ready(42);
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

#[test]
fn test_local_poll_once_ready() {
    let mut fut: LocalBoxFuture<'_, u32> = Local::ready(42);
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

#[test]
fn test_sendable_poll_once_multi_step() {
    use core::sync::atomic::{AtomicU32, Ordering};
    use std::sync::Arc;

    let shared = Arc::new(AtomicU32::new(0));
    let shared2 = Arc::clone(&shared);

    let mut fut = Sendable::from_future(core::future::poll_fn(move |_cx| {
        let val = shared2.load(Ordering::Acquire);
        if val > 0 {
            Poll::Ready(val)
        } else {
            Poll::Pending
        }
    }));

    // First poll: value is 0, future is pending
    assert_eq!(fut.poll_once(), Poll::Pending);

    // Host fulfills the effect
    shared.store(99, Ordering::Release);

    // Second poll: value available
    assert_eq!(fut.poll_once(), Poll::Ready(99));
}

#[test]
fn test_local_poll_once_with_non_send() {
    use std::rc::Rc;

    let data = Rc::new(42u32);
    let mut fut = Local::from_future(async move { *data });
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

// -- BoxFuture/LocalBoxFuture still implement Future -------------------------

#[tokio::test]
async fn test_sendable_future_await() {
    let fut = Sendable::from_future(async { 42u32 });
    assert_eq!(fut.await, 42);
}

#[tokio::test]
async fn test_local_future_await() {
    let fut = Local::from_future(async { 42u32 });
    assert_eq!(fut.await, 42);
}

// -- Macro-generated impls with poll_once ------------------------------------

trait Stepper<K: FutureForm> {
    fn step(&self) -> K::Future<'_, u32>;
}

struct StepperImpl {
    val: u32,
}

#[future_form(Sendable, Local)]
impl<K: FutureForm> Stepper<K> for StepperImpl {
    fn step(&self) -> K::Future<'_, u32> {
        let val = self.val;
        K::from_future(async move { val + 1 })
    }
}

#[test]
fn test_macro_sendable_poll_once() {
    let s = StepperImpl { val: 41 };
    let mut fut = <StepperImpl as Stepper<Sendable>>::step(&s);
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

#[test]
fn test_macro_local_poll_once() {
    let s = StepperImpl { val: 41 };
    let mut fut = <StepperImpl as Stepper<Local>>::step(&s);
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

// -- Conditional bounds with poll_once ---------------------------------------

trait HostWithBounds<K: FutureForm, T: Clone> {
    fn wrap(&self, val: T) -> K::Future<'_, T>;
}

struct BoundsImpl;

#[future_form(Sendable where T: Send, Local)]
impl<K: FutureForm, T: Clone + 'static> HostWithBounds<K, T> for BoundsImpl {
    fn wrap(&self, val: T) -> K::Future<'_, T> {
        K::from_future(async move { val })
    }
}

#[test]
fn test_sendable_conditional_bounds_poll_once() {
    let b = BoundsImpl;
    let mut fut = <BoundsImpl as HostWithBounds<Sendable, String>>::wrap(&b, "hi".into());
    assert_eq!(fut.poll_once(), Poll::Ready("hi".to_string()));
}

#[test]
fn test_local_conditional_bounds_poll_once() {
    use std::rc::Rc;

    let b = BoundsImpl;
    let val = Rc::new(42u32);
    let mut fut = <BoundsImpl as HostWithBounds<Local, Rc<u32>>>::wrap(&b, val);
    assert_eq!(fut.poll_once(), Poll::Ready(Rc::new(42)));
}

// -- Generic consumer with poll_once -----------------------------------------

async fn generic_step<K: FutureForm>(s: &StepperImpl) -> u32
where
    StepperImpl: Stepper<K>,
{
    Stepper::<K>::step(s).await
}

#[tokio::test]
async fn test_generic_consumer_sendable() {
    let s = StepperImpl { val: 41 };
    assert_eq!(generic_step::<Sendable>(&s).await, 42);
}

#[tokio::test]
async fn test_generic_consumer_local() {
    let s = StepperImpl { val: 41 };
    assert_eq!(generic_step::<Local>(&s).await, 42);
}

// -- K::ready with poll_once -------------------------------------------------

trait ReadyStepper<K: FutureForm> {
    fn cached(&self) -> K::Future<'_, u32>;
}

struct ReadyImpl {
    val: u32,
}

#[future_form(Sendable, Local)]
impl<K: FutureForm> ReadyStepper<K> for ReadyImpl {
    fn cached(&self) -> K::Future<'_, u32> {
        K::ready(self.val)
    }
}

#[test]
fn test_macro_sendable_ready_poll_once() {
    let r = ReadyImpl { val: 42 };
    let mut fut = <ReadyImpl as ReadyStepper<Sendable>>::cached(&r);
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}

#[test]
fn test_macro_local_ready_poll_once() {
    let r = ReadyImpl { val: 42 };
    let mut fut = <ReadyImpl as ReadyStepper<Local>>::cached(&r);
    assert_eq!(fut.poll_once(), Poll::Ready(42));
}