use better_any::{Tid, TidAble};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{Ident, TraitBound, Type};
use crate::{
context::Context,
into_template::IntoTemplate,
maybe_borrowed::{FromMaybeBorrowed, MaybeBorrowed},
passed_data::{PassedData, WithData},
token_cmp_wrapper::TokenCmpWrapper,
};
use super::{Generatable, StaticTid};
#[derive(Tid)]
pub struct Trait<'a> {
trait_bound: MaybeBorrowed<'a, TraitBound>,
}
impl<'a> Trait<'a> {
pub fn new<T>(trait_bound: T) -> Self
where
T: Into<MaybeBorrowed<'a, TraitBound>>,
{
Self {
trait_bound: trait_bound.into(),
}
}
}
impl<'a> FromMaybeBorrowed<'a, TraitBound> for Trait<'a> {
fn from_maybe_borrowed(from: MaybeBorrowed<'a, TraitBound>) -> Self {
Self::new(from)
}
}
impl<'a> IntoTemplate<'a> for TraitBound {
type Template = Trait<'a>;
fn into_template<T: crate::into_template::TypeEq<This = Self::Template>>(
self,
) -> Self::Template {
Trait::new(self)
}
}
impl<'a> IntoTemplate<'a> for &'a TraitBound {
type Template = Trait<'a>;
fn into_template<T: crate::into_template::TypeEq<This = Self::Template>>(
self,
) -> Self::Template {
Trait::new(self)
}
}
impl<'a> PartialOrd for Trait<'a> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(Ord::cmp(self, other))
}
}
impl<'a> Ord for Trait<'a> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
Ord::cmp(
&self.trait_bound.to_token_stream().to_string(),
&other.trait_bound.to_token_stream().to_string(),
)
}
}
impl<'a> PartialEq for Trait<'a> {
fn eq(&self, other: &Self) -> bool {
PartialEq::eq(
&self.trait_bound.to_token_stream().to_string(),
&other.trait_bound.to_token_stream().to_string(),
)
}
}
impl<'a> Eq for Trait<'a> {}
impl<'a> Generatable<'a, TokenCmpWrapper<Type>> for Trait<'a> {
type GeneratableData = ();
type TemplateData = Ident;
const EMITS_NON_CONSTANT_CODE: bool = false;
fn template(
&self,
Context {
ident_generator, ..
}: &mut Context,
_passed: &Self::GeneratableData,
) -> PassedData<Self::TemplateData> {
let fn_ident = ident_generator.prefixed("assert_trait_bound");
let trait_bound = &*self.trait_bound;
quote! {
fn #fn_ident<T: #trait_bound>() {}
}
.with_data(fn_ident)
}
fn assert(
&self,
_context: &mut Context,
(_, assert_trait_bound): (&Self::GeneratableData, &Self::TemplateData),
to_assert: &TokenCmpWrapper<syn::Type>,
) -> Option<TokenStream> {
Some(quote! {
#assert_trait_bound::<#to_assert>();
})
}
fn generatable(_context: &mut Context) -> PassedData<Self::GeneratableData>
where
Self: Sized,
{
PassedData::default()
}
}
impl<'a> Generatable<'a, StaticTid<Ident>> for Trait<'a> {
type GeneratableData = ();
type TemplateData = Ident;
const EMITS_NON_CONSTANT_CODE: bool = true;
fn template(
&self,
Context {
ident_generator, ..
}: &mut Context,
_passed: &Self::GeneratableData,
) -> PassedData<Self::TemplateData> {
let fn_ident = ident_generator.prefixed("assert_trait_bound");
let trait_bound = &*self.trait_bound;
quote! {
fn #fn_ident<T: #trait_bound>(_v: T) {}
}
.with_data(fn_ident)
}
fn assert(
&self,
_context: &mut Context,
(_, assert_trait_bound): (&Self::GeneratableData, &Self::TemplateData),
to_assert: &StaticTid<proc_macro2::Ident>,
) -> Option<TokenStream> {
Some(quote! {
#assert_trait_bound(#to_assert);
})
}
fn generatable(_context: &mut Context) -> PassedData<Self::GeneratableData>
where
Self: Sized,
{
PassedData::default()
}
}
#[cfg(test)]
mod test {
use quote::quote;
use syn::parse_quote;
use crate::{
context::Context,
generatable::Generatable,
ident_generator::MockIdentGenerator,
maybe_borrowed::MaybeBorrowed,
passed_data::{PassedData, WithData},
token_cmp_wrapper::TokenCmpWrapper,
};
use super::Trait;
#[test]
fn generate_generatable() {
let mut mock_ident_gen = MockIdentGenerator::new();
let mut context = Context::new(&mut mock_ident_gen);
assert_eq!(
<Trait as Generatable<TokenCmpWrapper<syn::Type>>>::generatable(&mut context),
PassedData::default()
);
assert!(mock_ident_gen.was_not_called());
}
#[test]
fn generate_template() {
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!(
<Trait as Generatable<TokenCmpWrapper<syn::Type>>>::template(
&Trait {
trait_bound: MaybeBorrowed::Owned(parse_quote!(::test_mod::Test))
},
&mut context,
&()
),
quote! {
fn assert_trait_bound_mock_1<T: ::test_mod::Test>() {}
}
.with_data(parse_quote!(assert_trait_bound_mock_1))
);
assert!(mock_ident_gen.has_no_idents_remaining());
}
#[test]
fn generate_assert() {
let mut mock_ident_gen = MockIdentGenerator::new();
let mut context = Context::new(&mut mock_ident_gen);
let test_type: syn::Type = parse_quote!(TestType);
assert_eq!(
<Trait as Generatable<TokenCmpWrapper<syn::Type>>>::assert(
&Trait {
trait_bound: MaybeBorrowed::Owned(parse_quote!(::test_mod::Test))
},
&mut context,
(&(), &parse_quote!(assert_trait_bound_mock_1)),
&test_type.into()
)
.as_ref()
.map(ToString::to_string),
Some(quote! {
assert_trait_bound_mock_1::<TestType>();
})
.as_ref()
.map(ToString::to_string)
);
assert!(mock_ident_gen.was_not_called());
}
}