#![no_std]
#![warn(clippy::pedantic)]
use core::{
future::Future,
hash::{Hash, Hasher},
mem::Discriminant,
};
struct DiscriminantExtractor {
discriminant: Result<Option<u32>, ()>,
}
impl Hasher for DiscriminantExtractor {
fn finish(&self) -> u64 {
unreachable!()
}
fn write(&mut self, _bytes: &[u8]) {
self.discriminant = Err(());
}
fn write_u32(&mut self, i: u32) {
if self.discriminant == Ok(None) {
self.discriminant = Ok(Some(i));
} else {
self.discriminant = Err(());
}
}
}
fn discriminant_value<T>(discriminant: Discriminant<T>) -> Option<u32> {
let mut hasher = DiscriminantExtractor {
discriminant: Ok(None),
};
discriminant.hash(&mut hasher);
hasher.discriminant.ok()?
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoroutineState {
Unresumed,
Returned,
Panicked,
Suspend(u32),
}
pub fn coroutine_state<Fut: Future>(fut: &Fut) -> CoroutineState {
let Some(value) = discriminant_value(core::mem::discriminant(fut)) else {
panic!(
"couln't get coroutine state of Future with type {}",
core::any::type_name::<Fut>()
);
};
match value {
0 => CoroutineState::Unresumed,
1 => CoroutineState::Returned,
2 => CoroutineState::Panicked,
_ => CoroutineState::Suspend(value - 3),
}
}
#[cfg(test)]
mod test {
use core::{
panic::AssertUnwindSafe,
pin::{pin, Pin},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};
use super::*;
extern crate std;
use std::{prelude::rust_2021::*, vec};
struct PendingOnce {
is_ready: bool,
}
impl Future for PendingOnce {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.is_ready {
Poll::Ready(())
} else {
self.is_ready = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
fn async_yield() -> impl Future<Output = ()> {
PendingOnce { is_ready: false }
}
const NOOP_RAW_WAKER: RawWaker = {
const VTABLE: RawWakerVTable =
RawWakerVTable::new(|_| NOOP_RAW_WAKER, |_| {}, |_| {}, |_| {});
RawWaker::new(core::ptr::null(), &VTABLE)
};
static NOOP_WAKER: Waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) };
const NOOP_CONTEXT: Context = Context::from_waker(&NOOP_WAKER);
fn coroutine_states_inner<Fut: Future>(
mut fut: Pin<&mut Fut>,
states: &mut Vec<CoroutineState>,
) {
states.push(coroutine_state(&*fut));
let mut cx = NOOP_CONTEXT;
loop {
let is_ready = fut.as_mut().poll(&mut cx).is_ready();
states.push(coroutine_state(&*fut));
if is_ready {
break;
}
}
}
fn coroutine_states<Fut: Future>(fut: Fut) -> Vec<CoroutineState> {
let mut states = vec![];
let fut = pin!(fut);
coroutine_states_inner(fut, &mut states);
states
}
#[test]
fn coroutine_state_empty() {
#[allow(clippy::unused_async)]
async fn f() {}
assert_eq!(
coroutine_states(f()),
vec![CoroutineState::Unresumed, CoroutineState::Returned]
);
}
#[test]
fn coroutine_state_one_yield() {
async fn f() {
async_yield().await;
}
assert_eq!(
coroutine_states(f()),
vec![
CoroutineState::Unresumed,
CoroutineState::Suspend(0),
CoroutineState::Returned,
]
);
}
#[test]
fn coroutine_state_two_yields() {
async fn f() {
async_yield().await;
async_yield().await;
}
assert_eq!(
coroutine_states(f()),
vec![
CoroutineState::Unresumed,
CoroutineState::Suspend(0),
CoroutineState::Suspend(1),
CoroutineState::Returned,
]
);
}
#[test]
fn coroutine_state_panic() {
#[allow(clippy::unused_async)]
async fn f() {
panic!();
}
let mut states = vec![];
let fut = f();
let mut fut = pin!(fut);
let r = {
let fut = fut.as_mut();
std::panic::catch_unwind(AssertUnwindSafe(|| {
coroutine_states_inner(fut, &mut states);
}))
};
assert!(r.is_err());
assert_eq!(states, vec![CoroutineState::Unresumed]);
assert_eq!(coroutine_state(&*fut), CoroutineState::Panicked);
}
}