floop 0.1.2

A more convenient and less error prone replacement for loop `{ select! { .. }}`
Documentation
use unsynn::*;

use crate::code_generation::hygenic_ident;

pub(super) struct FootgunDetector {
    // all the declerations that need to be in scope for `detector` to work.
    pub declerations: TokenStream,
    /// evualates to `None`, panics at compile time if `T` (the conents of the option) is a reference or mutable reference.
    /// usage: `variable = #detector;`.
    /// the detector may be extremely sensitive to anything and everything, requiring its precise placement in the output code to follow some arcane rules i don't understand,
    /// but i can't get `const { panic! }` to *not* compile in one of my tests so the detector may not be sensitive to anything.
    /// due to https://github.com/rust-lang/rust/issues/140655 the detector should only be used inside a non-async closure, as that makes it sync again, bypassing the issue (in my testing).
    pub detector: TokenStream,
}

/// generates code to detect references to futures at compile time and panic (at compile time) if any where detected.
/// based on https://goldstein.lol/posts/const-deref-specialization/src/lib/
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 {
        // FIXME: this mess is generated for every `floop` invocation, which may increase compile times.
        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;
                }
            }            
        },
    }
}