use unsynn::*;
use crate::code_generation::hygenic_ident;
pub(super) struct FootgunDetector {
pub declerations: TokenStream,
pub detector: TokenStream,
}
pub(super) fn footgun_detector() -> FootgunDetector {
let mod_name = hygenic_ident("__floop_foodgun_detector_mod");
let is_ref_struct = hygenic_ident("__FloopIsRef");
let is_ref_false_struct = hygenic_ident("__FloopIsRefFalse");
let extract_bool = hygenic_ident("__floop_extract_bool");
let constrain = hygenic_ident("__floop_constrain");
let is_ref = hygenic_ident("__floop_is_ref");
let panic_message = r#""
detected a future of type `&mut T` or `&T`, this would result in the future being polled after it was finished¹ the second time the arm runs.
unlike `loop {{ select!{{}} }}`, `floop!{{}}` does not cancel the other futures when a arm finishes,
futures are only reconstructed **after** they finished, you can just pass the future by value.
(this also applies to non-cancel-safe futures, `floop` doesn't cancel futures)
if polling the future after it finished is intended behaviour add the keyword `footgun` before it to silence this error
(e.g. `foo in footgun &mut my_future => {{ ... }}`).
¹ polling a future after it finished is unspecified behaviour, std docs say:
Once a future has completed (returned Ready from poll), calling its poll method again may panic, block forever, or cause other kinds of problems;
the Future trait places no requirements on the effects of such a call.
[safety part ommitted, polling a future is always safe]
""#;
FootgunDetector {
declerations: quote! {
#[allow(unused)]
mod #mod_name {
pub(super) struct True;
pub(super) struct False;
trait ToBool {
const BOOL: bool;
}
impl ToBool for True {
const BOOL: bool = true;
}
impl ToBool for False {
const BOOL: bool = false;
}
pub(super) struct #is_ref_struct<T: ?Sized>(pub(super) ::core::marker::PhantomData<T>);
impl<T: ?::core::marker::Sized> #is_ref_struct<&mut T> {
pub(super) fn #is_ref(&self) -> True {
True
}
}
impl<T: ?::core::marker::Sized> #is_ref_struct<&T> {
pub(super) fn #is_ref(&self) -> True {
True
}
}
impl<T> #is_ref_struct<T> {
pub(super) const fn #constrain(&self) -> ::core::option::Option<T> {
None
}
}
impl<T: ?::core::marker::Sized> ::core::ops::Deref for #is_ref_struct<T> {
type Target = #is_ref_false_struct;
fn deref(&self) -> &Self::Target {
&#is_ref_false_struct
}
}
pub(super) struct #is_ref_false_struct;
impl #is_ref_false_struct {
pub(super) fn #is_ref(&self) -> False {
False
}
}
pub(super) const fn #extract_bool<T: ToBool, F: FnOnce() -> T>(function: &F) -> bool {
T::BOOL
}
}
use #mod_name::{#is_ref_struct, #is_ref_false_struct, #extract_bool};
},
detector: quote! {
const {
#[allow(clippy::never_loop)]
loop {
let mut output = None;
if false {
break output;
}
let is_ref = #is_ref_struct(::core::marker::PhantomData);
output = is_ref.#constrain();
let function = || is_ref.#is_ref();
let should_panic = #extract_bool(&function);
if should_panic {
::core::panic!(#panic_message);
}
break output;
}
}
},
}
}