proc_macro_assertions/generatable/
trait.rs

1use better_any::{Tid, TidAble};
2use proc_macro2::TokenStream;
3use quote::{quote, ToTokens};
4use syn::{Ident, TraitBound, Type};
5
6use crate::{
7    context::Context,
8    into_template::IntoTemplate,
9    maybe_borrowed::{FromMaybeBorrowed, MaybeBorrowed},
10    passed_data::{PassedData, WithData},
11    token_cmp_wrapper::TokenCmpWrapper,
12};
13
14use super::{Generatable, StaticTid};
15
16/// A trait Generatable / Template. This asserts that some type implements some trait.
17#[derive(Tid)]
18pub struct Trait<'a> {
19    /// The trait bound to check the type implements
20    trait_bound: MaybeBorrowed<'a, TraitBound>,
21}
22
23impl<'a> Trait<'a> {
24    /// Creates a new trait template from some trait bound.
25    /// Takes any T that can be turned into a `MaybeBorrowed` type, to support
26    /// references as well as owned types
27    pub fn new<T>(trait_bound: T) -> Self
28    where
29        T: Into<MaybeBorrowed<'a, TraitBound>>,
30    {
31        Self {
32            trait_bound: trait_bound.into(),
33        }
34    }
35}
36
37impl<'a> FromMaybeBorrowed<'a, TraitBound> for Trait<'a> {
38    fn from_maybe_borrowed(from: MaybeBorrowed<'a, TraitBound>) -> Self {
39        Self::new(from)
40    }
41}
42
43impl<'a> IntoTemplate<'a> for TraitBound {
44    type Template = Trait<'a>;
45
46    fn into_template<T: crate::into_template::TypeEq<This = Self::Template>>(
47        self,
48    ) -> Self::Template {
49        Trait::new(self)
50    }
51}
52
53impl<'a> IntoTemplate<'a> for &'a TraitBound {
54    type Template = Trait<'a>;
55
56    fn into_template<T: crate::into_template::TypeEq<This = Self::Template>>(
57        self,
58    ) -> Self::Template {
59        Trait::new(self)
60    }
61}
62
63impl<'a> PartialOrd for Trait<'a> {
64    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
65        Some(Ord::cmp(self, other))
66    }
67}
68
69impl<'a> Ord for Trait<'a> {
70    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
71        Ord::cmp(
72            &self.trait_bound.to_token_stream().to_string(),
73            &other.trait_bound.to_token_stream().to_string(),
74        )
75    }
76}
77
78impl<'a> PartialEq for Trait<'a> {
79    fn eq(&self, other: &Self) -> bool {
80        PartialEq::eq(
81            &self.trait_bound.to_token_stream().to_string(),
82            &other.trait_bound.to_token_stream().to_string(),
83        )
84    }
85}
86
87impl<'a> Eq for Trait<'a> {}
88
89impl<'a> Generatable<'a, TokenCmpWrapper<Type>> for Trait<'a> {
90    type GeneratableData = ();
91    type TemplateData = Ident;
92
93    const EMITS_NON_CONSTANT_CODE: bool = false;
94
95    fn template(
96        &self,
97        Context {
98            ident_generator, ..
99        }: &mut Context,
100        _passed: &Self::GeneratableData,
101    ) -> PassedData<Self::TemplateData> {
102        let fn_ident = ident_generator.prefixed("assert_trait_bound");
103        let trait_bound = &*self.trait_bound;
104
105        quote! {
106            fn #fn_ident<T: #trait_bound>() {}
107        }
108        .with_data(fn_ident)
109    }
110
111    fn assert(
112        &self,
113        _context: &mut Context,
114        (_, assert_trait_bound): (&Self::GeneratableData, &Self::TemplateData),
115        to_assert: &TokenCmpWrapper<syn::Type>,
116    ) -> Option<TokenStream> {
117        Some(quote! {
118            #assert_trait_bound::<#to_assert>();
119        })
120    }
121
122    fn generatable(_context: &mut Context) -> PassedData<Self::GeneratableData>
123    where
124        Self: Sized,
125    {
126        PassedData::default()
127    }
128}
129
130impl<'a> Generatable<'a, StaticTid<Ident>> for Trait<'a> {
131    type GeneratableData = ();
132    type TemplateData = Ident;
133
134    const EMITS_NON_CONSTANT_CODE: bool = true;
135
136    fn template(
137        &self,
138        Context {
139            ident_generator, ..
140        }: &mut Context,
141        _passed: &Self::GeneratableData,
142    ) -> PassedData<Self::TemplateData> {
143        let fn_ident = ident_generator.prefixed("assert_trait_bound");
144        let trait_bound = &*self.trait_bound;
145
146        quote! {
147            fn #fn_ident<T: #trait_bound>(_v: T) {}
148        }
149        .with_data(fn_ident)
150    }
151
152    fn assert(
153        &self,
154        _context: &mut Context,
155        (_, assert_trait_bound): (&Self::GeneratableData, &Self::TemplateData),
156        to_assert: &StaticTid<proc_macro2::Ident>,
157    ) -> Option<TokenStream> {
158        Some(quote! {
159            #assert_trait_bound(#to_assert);
160        })
161    }
162
163    fn generatable(_context: &mut Context) -> PassedData<Self::GeneratableData>
164    where
165        Self: Sized,
166    {
167        PassedData::default()
168    }
169}
170
171#[cfg(test)]
172mod test {
173    use quote::quote;
174    use syn::parse_quote;
175
176    use crate::{
177        context::Context,
178        generatable::Generatable,
179        ident_generator::MockIdentGenerator,
180        maybe_borrowed::MaybeBorrowed,
181        passed_data::{PassedData, WithData},
182        token_cmp_wrapper::TokenCmpWrapper,
183    };
184
185    use super::Trait;
186
187    #[test]
188    fn generate_generatable() {
189        let mut mock_ident_gen = MockIdentGenerator::new();
190        let mut context = Context::new(&mut mock_ident_gen);
191
192        assert_eq!(
193            <Trait as Generatable<TokenCmpWrapper<syn::Type>>>::generatable(&mut context),
194            PassedData::default()
195        );
196        assert!(mock_ident_gen.was_not_called());
197    }
198
199    #[test]
200    fn generate_template() {
201        let mut mock_ident_gen = MockIdentGenerator::new();
202
203        mock_ident_gen.push_ident("mock_1");
204
205        let mut context = Context::new(&mut mock_ident_gen);
206        assert_eq!(
207            <Trait as Generatable<TokenCmpWrapper<syn::Type>>>::template(
208                &Trait {
209                    trait_bound: MaybeBorrowed::Owned(parse_quote!(::test_mod::Test))
210                },
211                &mut context,
212                &()
213            ),
214            quote! {
215                fn assert_trait_bound_mock_1<T: ::test_mod::Test>() {}
216            }
217            .with_data(parse_quote!(assert_trait_bound_mock_1))
218        );
219        assert!(mock_ident_gen.has_no_idents_remaining());
220    }
221
222    #[test]
223    fn generate_assert() {
224        let mut mock_ident_gen = MockIdentGenerator::new();
225        let mut context = Context::new(&mut mock_ident_gen);
226
227        let test_type: syn::Type = parse_quote!(TestType);
228
229        assert_eq!(
230            <Trait as Generatable<TokenCmpWrapper<syn::Type>>>::assert(
231                &Trait {
232                    trait_bound: MaybeBorrowed::Owned(parse_quote!(::test_mod::Test))
233                },
234                &mut context,
235                (&(), &parse_quote!(assert_trait_bound_mock_1)),
236                &test_type.into()
237            )
238            .as_ref()
239            .map(ToString::to_string),
240            Some(quote! {
241                assert_trait_bound_mock_1::<TestType>();
242            })
243            .as_ref()
244            .map(ToString::to_string)
245        );
246        assert!(mock_ident_gen.was_not_called());
247    }
248}