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};
#[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>>,
pub generics_count: usize,
}
impl<'a> Type<'a> {
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 = ();
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 = ();
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 {
ident_generator: &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 {
ident_generator: &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 {
ident_generator: &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());
}
}