proc-macro-assertions 0.1.5

Easily create asserts on proc macro inputs
Documentation
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};

use crate::{raw_assert::r#trait, token_store::TokenStore};

use super::{
    context::Context,
    generatable_set::GeneratableSet,
    ident_generator::{self, CountingIdentGenerator},
};

/// A store for assertions with some custom [`IdentGenerator`](ident_generator::IdentGenerator) to generate
/// unique identifiers to be used within asserts. [`DefaultStore`] is recommended for most
/// purposes, which uses a [`CountingIdentGenerator`].
///
///
/// This is the central type of this crate. It provides a target to generate assertions into.
/// All asserts are stored here before beeing turned into tokens (using [`quote::ToTokens`]).
///
/// Usually you want to use the [`assert_into!`](macro@crate::prelude::assert_into) macro to actually add the tokens.
///
/// # Example
/// ```
/// # use proc_macro_assertions::prelude::DefaultStore
/// let store = DefaultStore::new();
/// let token_to_assert_something_on = todo!();
/// assert_into!(store | token_to_assert_something_on impl std::default::Default);
/// let tokens = quote!{ #store };
/// ```
pub struct Store<'a, IdentGenerator = CountingIdentGenerator>
where
    IdentGenerator: ident_generator::IdentGenerator,
{
    pub(crate) extra_items: TokenStream,
    pub(crate) generatables: GeneratableSet<'a>,
    pub(crate) ident_gen: IdentGenerator,
}

impl<'a, IdentGenerator> Store<'a, IdentGenerator>
where
    IdentGenerator: ident_generator::IdentGenerator,
{
    pub fn add_extra_items(&mut self, item: impl ToTokens) {
        item.to_tokens(&mut self.extra_items);
    }
}

#[allow(clippy::module_name_repetitions)]
pub type DefaultStore<'a> = Store<'a, CountingIdentGenerator>;

impl<'a, IdentGenerator> Store<'a, IdentGenerator>
where
    IdentGenerator: ident_generator::IdentGenerator,
{
    pub fn assert(&mut self, assert: impl r#trait::RawAssertable<'a>) {
        assert.do_raw_assert(self);
    }

    #[must_use]
    pub fn new() -> Self
    where
        IdentGenerator: Default,
    {
        Self::default()
    }
}

impl<'a, IdentGenerator> Default for Store<'a, IdentGenerator>
where
    IdentGenerator: ident_generator::IdentGenerator + Default,
{
    fn default() -> Self {
        Self {
            extra_items: TokenStream::new(),
            generatables: GeneratableSet::new(),
            ident_gen: IdentGenerator::default(),
        }
    }
}

impl<'a, IdentGenerator> ToTokens for Store<'a, IdentGenerator>
where
    IdentGenerator: ident_generator::IdentGenerator + Clone,
{
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let mut ident_gen = self.ident_gen.clone();
        let mut context = Context::new(&mut ident_gen);

        let mut token_store = TokenStore::new();

        for generatable in self.generatables.iter() {
            generatable.generate_into(&mut context, &mut token_store);
        }
        let asserted_tokens = token_store.into_tokens(&mut context);
        let extra_items = &self.extra_items;

        let closure_contents = quote! {
                #extra_items
                #asserted_tokens
        };

        let closure = if context.requires_nonconstant_code() {
            quote! {
                let _ = || {
                    #closure_contents
                };
            }
        } else {
            quote! {
                const _: fn() = || {
                    #closure_contents
                };
            }
        };

        tokens.extend(quote! {
            // const fn to ensure that this is only called at compile time, that is then ignored
            // This ensures that this is a zero cost abstraction and that the assertions are only
            // checked at compile time
            #[doc(hidden)]
            #[allow(warnings)]
            #closure
        });
    }
}

#[cfg(test)]
mod test {
    use quote::ToTokens;
    use syn::parse_quote;

    use crate::assert_into;

    use super::DefaultStore;

    #[test]
    fn test() {
        let generics = syn::Generics::default();
        let test_ident: syn::Type = parse_quote!(Test1);
        let test_type: syn::Type = syn::Type::Path(parse_quote!(Test2));
        let mut store = DefaultStore::new();
        assert_into!(store | &test_type with &generics == Test);
        assert_into!(store | &test_ident with &generics impl Debug);
        println!("{}", store.to_token_stream());
    }
}