proc_macro_assertions/
store.rs

1use proc_macro2::TokenStream;
2use quote::{quote, ToTokens};
3
4use crate::{raw_assert::r#trait, token_store::TokenStore};
5
6use super::{
7    context::Context,
8    generatable_set::GeneratableSet,
9    ident_generator::{self, CountingIdentGenerator},
10};
11
12/// A store for assertions with some custom [`IdentGenerator`](ident_generator::IdentGenerator) to generate
13/// unique identifiers to be used within asserts. [`DefaultStore`] is recommended for most
14/// purposes, which uses a [`CountingIdentGenerator`].
15///
16///
17/// This is the central type of this crate. It provides a target to generate assertions into.
18/// All asserts are stored here before beeing turned into tokens (using [`quote::ToTokens`]).
19///
20/// Usually you want to use the [`assert_into!`](macro@crate::prelude::assert_into) macro to actually add the tokens.
21///
22/// # Example
23/// ```
24/// # use proc_macro_assertions::prelude::DefaultStore
25/// let store = DefaultStore::new();
26/// let token_to_assert_something_on = todo!();
27/// assert_into!(store | token_to_assert_something_on impl std::default::Default);
28/// let tokens = quote!{ #store };
29/// ```
30pub struct Store<'a, IdentGenerator = CountingIdentGenerator>
31where
32    IdentGenerator: ident_generator::IdentGenerator,
33{
34    pub(crate) extra_items: TokenStream,
35    pub(crate) generatables: GeneratableSet<'a>,
36    pub(crate) ident_gen: IdentGenerator,
37}
38
39impl<'a, IdentGenerator> Store<'a, IdentGenerator>
40where
41    IdentGenerator: ident_generator::IdentGenerator,
42{
43    pub fn add_extra_items(&mut self, item: impl ToTokens) {
44        item.to_tokens(&mut self.extra_items);
45    }
46}
47
48#[allow(clippy::module_name_repetitions)]
49pub type DefaultStore<'a> = Store<'a, CountingIdentGenerator>;
50
51impl<'a, IdentGenerator> Store<'a, IdentGenerator>
52where
53    IdentGenerator: ident_generator::IdentGenerator,
54{
55    pub fn assert(&mut self, assert: impl r#trait::RawAssertable<'a>) {
56        assert.do_raw_assert(self);
57    }
58
59    #[must_use]
60    pub fn new() -> Self
61    where
62        IdentGenerator: Default,
63    {
64        Self::default()
65    }
66}
67
68impl<'a, IdentGenerator> Default for Store<'a, IdentGenerator>
69where
70    IdentGenerator: ident_generator::IdentGenerator + Default,
71{
72    fn default() -> Self {
73        Self {
74            extra_items: TokenStream::new(),
75            generatables: GeneratableSet::new(),
76            ident_gen: IdentGenerator::default(),
77        }
78    }
79}
80
81impl<'a, IdentGenerator> ToTokens for Store<'a, IdentGenerator>
82where
83    IdentGenerator: ident_generator::IdentGenerator + Clone,
84{
85    fn to_tokens(&self, tokens: &mut TokenStream) {
86        let mut ident_gen = self.ident_gen.clone();
87        let mut context = Context::new(&mut ident_gen);
88
89        let mut token_store = TokenStore::new();
90
91        for generatable in self.generatables.iter() {
92            generatable.generate_into(&mut context, &mut token_store);
93        }
94        let asserted_tokens = token_store.into_tokens(&mut context);
95        let extra_items = &self.extra_items;
96
97        let closure_contents = quote! {
98                #extra_items
99                #asserted_tokens
100        };
101
102        let closure = if context.requires_nonconstant_code() {
103            quote! {
104                let _ = || {
105                    #closure_contents
106                };
107            }
108        } else {
109            quote! {
110                const _: fn() = || {
111                    #closure_contents
112                };
113            }
114        };
115
116        tokens.extend(quote! {
117            // const fn to ensure that this is only called at compile time, that is then ignored
118            // This ensures that this is a zero cost abstraction and that the assertions are only
119            // checked at compile time
120            #[doc(hidden)]
121            #[allow(warnings)]
122            #closure
123        });
124    }
125}
126
127#[cfg(test)]
128mod test {
129    use quote::ToTokens;
130    use syn::parse_quote;
131
132    use crate::assert_into;
133
134    use super::DefaultStore;
135
136    #[test]
137    fn test() {
138        let generics = syn::Generics::default();
139        let test_ident: syn::Type = parse_quote!(Test1);
140        let test_type: syn::Type = syn::Type::Path(parse_quote!(Test2));
141        let mut store = DefaultStore::new();
142        assert_into!(store | &test_type with &generics == Test);
143        assert_into!(store | &test_ident with &generics impl Debug);
144        println!("{}", store.to_token_stream());
145    }
146}