proc-macro-assertions 0.1.5

Easily create asserts on proc macro inputs
Documentation
use std::iter;

use better_any::{Tid, TidAble};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Ident;

use crate::{
    context::Context,
    maybe_borrowed::MaybeBorrowed,
    passed_data::{PassedData, WithData},
    token_cmp_wrapper::TokenCmpWrapper,
};

use super::{Generatable, StaticTid};
/// A trait Generatable / Template. This asserts that some type implements some trait.
#[derive(Tid, PartialEq, Eq, PartialOrd, Ord)]
pub struct Type<'a> {
    #[doc = r"The type to check the asserted type equals"]
    pub r#type: MaybeBorrowed<'a, TokenCmpWrapper<syn::Type>>,
    /// The amount of generics the type should have
    pub generics_count: usize,
}

impl<'a> Type<'a> {
    /// Creates a new type template from some type to be checked against.
    /// Takes any T that can be turned into a `MaybeBorrowed` type, to support
    /// references as well as owned types
    pub fn new<T>(r#type: T, generics_count: usize) -> Self
    where
        T: Into<MaybeBorrowed<'a, TokenCmpWrapper<syn::Type>>>,
    {
        Self {
            r#type: r#type.into(),
            generics_count,
        }
    }

    #[must_use]
    pub fn from_owned(r#type: syn::Type, generics_count: usize) -> Self {
        Self::new(r#type, generics_count)
    }

    #[must_use]
    pub fn from_borrowed(r#type: &'a syn::Type, generics_count: usize) -> Self {
        Self::new(r#type, generics_count)
    }
}

impl<'a> Generatable<'a, TokenCmpWrapper<syn::Type>> for Type<'a> {
    type GeneratableData = Ident;
    type TemplateData = ();

    const EMITS_NON_CONSTANT_CODE: bool = false;

    fn generatable(
        Context {
            ident_generator, ..
        }: &mut Context,
    ) -> PassedData<Self::GeneratableData> {
        let ident = ident_generator.prefixed("assert_type_eq");
        let type_eq_ident = ident_generator.prefixed("TypeEq");

        quote! {
            trait #type_eq_ident {
                type This: ?Sized;
            }
            impl<T: ?Sized> #type_eq_ident for T {
                type This = Self;
            }

            fn #ident<T, U>()
            where
                T: ?Sized + TypeEq<This = U>,
                U: ?Sized,
            {
            }
        }
        .with_data(ident)
    }

    fn assert(
        &self,
        _context: &mut Context,
        (assert_type_eq, _): (&Self::GeneratableData, &Self::TemplateData),
        to_assert: &TokenCmpWrapper<syn::Type>,
    ) -> Option<TokenStream> {
        let type_bound = &*self.r#type;

        let generics = (self.generics_count > 0).then(|| {
            let generic_ident = Ident::new("_", Span::call_site());
            let generic_ident_iter = iter::repeat(generic_ident).take(self.generics_count);

            quote! {
                <#(#generic_ident_iter),*>
            }
        });

        Some(quote! {
            #assert_type_eq::<#to_assert,#type_bound #generics>();
        })
    }

    fn template(
        &self,
        _context: &mut Context,
        _passed: &Self::GeneratableData,
    ) -> PassedData<Self::TemplateData>
    where
        Self::TemplateData: Default,
    {
        PassedData::default()
    }
}

impl<'a> Generatable<'a, TokenCmpWrapper<StaticTid<syn::Ident>>> for Type<'a> {
    type GeneratableData = Ident;
    type TemplateData = ();

    const EMITS_NON_CONSTANT_CODE: bool = true;

    fn generatable(
        Context {
            ident_generator, ..
        }: &mut Context,
    ) -> PassedData<Self::GeneratableData> {
        let ident = ident_generator.prefixed("assert_type_eq");
        let type_eq_ident = ident_generator.prefixed("TypeEq");

        quote! {
            trait #type_eq_ident {
                type This: ?Sized;
            }
            impl<T: ?Sized> #type_eq_ident for T {
                type This = Self;
            }

            fn #ident<T, U>(_v: U)
            where
                U: ?Sized + TypeEq<This = U>,
                T: ?Sized,
            {
            }
        }
        .with_data(ident)
    }

    fn assert(
        &self,
        _context: &mut Context,
        (assert_type_eq, _): (&Self::GeneratableData, &Self::TemplateData),
        to_assert: &TokenCmpWrapper<StaticTid<syn::Ident>>,
    ) -> Option<TokenStream> {
        let type_bound = &*self.r#type;

        let generics = (self.generics_count > 0).then(|| {
            let generic_ident = Ident::new("_", Span::call_site());
            let generic_ident_iter = iter::repeat(generic_ident).take(self.generics_count);

            quote! {
                <#(#generic_ident_iter),*>
            }
        });

        Some(quote! {
            #assert_type_eq::<#type_bound #generics,_>(#to_assert);
        })
    }

    fn template(
        &self,
        _context: &mut Context,
        _passed: &Self::GeneratableData,
    ) -> PassedData<Self::TemplateData>
    where
        Self::TemplateData: Default,
    {
        PassedData::default()
    }
}

#[cfg(test)]
mod test {
    use crate::ident_generator::MockIdentGenerator;
    use quote::quote;
    use syn::parse_quote;

    use super::*;

    #[test]
    fn generate_generatable() {
        let mut mock_ident_gen = MockIdentGenerator::new();

        mock_ident_gen.push_ident("mock_1");

        let mut context = Context::new(&mut mock_ident_gen);

        assert_eq!(
            <Type as Generatable<TokenCmpWrapper<syn::Type>>>::generatable(&mut context),
            quote! {
                trait TypeEq {
                    type This: ?Sized;
                }
                impl<T: ?Sized> TypeEq for T {
                    type This = Self;
                }

                fn assert_type_eq_mock_1<T, U>()
                where
                    T: ?Sized + TypeEq<This = U>,
                    U: ?Sized,
                {
                }
            }
            .with_data(parse_quote!(assert_type_eq_mock_1)),
        );

        assert!(mock_ident_gen.has_no_idents_remaining());
    }

    #[test]
    fn generate_template() {
        let mut mock_ident_gen = MockIdentGenerator::new();
        let mut context = Context::new(&mut mock_ident_gen);

        assert_eq!(
            <Type as Generatable<TokenCmpWrapper<syn::Type>>>::template(
                &Type {
                    r#type: (&syn::Type::Path(syn::parse_quote!(Type))).into(),
                    generics_count: 2,
                },
                &mut context,
                &parse_quote!(assert_type_eq_mock_1)
            ),
            PassedData::<()>::default(),
        );
        assert!(mock_ident_gen.was_not_called());
    }

    #[test]
    fn generate_assert() {
        let mut mock_ident_gen = MockIdentGenerator::new();
        let mut context = Context::new(&mut mock_ident_gen);

        assert_eq!(
            <Type as Generatable<TokenCmpWrapper<syn::Type>>>::assert(
                &Type {
                    r#type: (&syn::Type::Path(syn::parse_quote!(Type))).into(),
                    generics_count: 2,
                },
                &mut context,
                (&parse_quote!(assert_type_eq_mock_1), &()),
                &syn::Type::Verbatim("Test".parse().unwrap()).into()
            )
            .map(|x| x.to_string()),
            Some(quote! {
                assert_type_eq_mock_1::<Test,Type<_,_>>();
            })
            .map(|x| x.to_string())
        );
        assert!(mock_ident_gen.was_not_called());
    }
}