code_product_lib/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro2::TokenStream;
4
5#[cfg(test)]
6use quote::quote;
7
8mod product;
9use product::Product;
10mod token;
11
12/// The scope mode for the product expansion. This defines how scopes are handled at the top
13/// level.
14pub enum ScopeMode {
15    /// Expand products in all scopes, including the root scope.
16    Expand,
17    /// Expand products in sub-scopes but not in the root scope. Thus one has to insert at
18    /// least one manual scope with `${  }` to expand products.
19    SubExpand,
20    /// Insert scopes automatically and close/expand them after a ending brace or a
21    /// semicolon. Then start a new scope This is convenient when a lot structs or impls shall
22    /// be independently expanded.
23    ///
24    /// # Example
25    ///
26    /// ```text
27    /// impl Foo for $((First)(Second)) { }
28    /// impl Bar for $((Third)(Fourth)) { }
29    /// ```
30    ///
31    /// will treat each impl item as it is surrounded by it own scope and expand to:
32    ///
33    /// ```text
34    /// impl Foo for First { }
35    /// impl Foo for Second { }
36    /// impl Bar for Third { }
37    /// impl Bar for Fourth { }
38    /// ```
39    AutoBraceSemi,
40    // when you read this, you prolly want to add another mode. Just do and send a PR.
41}
42
43/// Expands a product syntax into a `TokenStream`.
44///
45/// # Examples
46///
47/// ## Complete expansion
48///
49/// ```
50/// # use quote::quote;
51/// # use code_product_lib::*;
52///
53/// let input = quote! {
54///     impl Foo for $((This)(That)) { }
55/// };
56///
57/// let output = expand_products(input, ScopeMode::Expand);
58/// assert_eq!(
59///     output.to_string(),
60///     "impl Foo for This { } \
61///      impl Foo for That { }");
62/// ```
63///
64/// ## Sub-expand
65///
66/// ```
67/// # use quote::quote;
68/// # use code_product_lib::*;
69///
70/// let input = quote! {
71///     let toplevel = thing ;
72///     ${ impl Foo for $((This)(That)) { } }
73///     ${ impl Bar for $((This)(That)) { } }
74/// };
75///
76/// let output = expand_products(input, ScopeMode::SubExpand);
77/// assert_eq!(
78///     output.to_string(),
79///     "let toplevel = thing ; \
80///      impl Foo for This { } \
81///      impl Foo for That { } \
82///      impl Bar for This { } \
83///      impl Bar for That { }"
84/// );
85/// ```
86///
87/// ## Auto-brace-semi
88///
89/// ```
90/// # use quote::quote;
91/// # use code_product_lib::*;
92///
93/// let input = quote! {
94///     impl Foo for $((This)(That)) { }
95///     impl Bar for $((This)(That)) { }
96/// };
97///
98/// let output = expand_products(input, ScopeMode::AutoBraceSemi);
99/// assert_eq!(
100///     output.to_string(),
101///     "impl Foo for This { } \
102///      impl Foo for That { } \
103///      impl Bar for This { } \
104///      impl Bar for That { }"
105/// );
106/// ```
107pub fn expand_products(input: TokenStream, mode: ScopeMode) -> TokenStream {
108    Product::new(input).parse(mode).expand()
109}
110
111#[cfg(test)]
112macro_rules! assert_expansion {
113    ($expand_mode:ident {$($in:tt)*} == {$($out:tt)*}) => {
114        let input = quote! { $($in)* };
115        let output = expand_products(input, ScopeMode::$expand_mode);
116        let expected = quote! { $($out)* };
117        assert_eq!(output.to_string(), expected.to_string());
118    };
119    ($($in:tt)*) => {
120        assert_expansion!(Expand {$($in)*} == {$($in)*});
121    };
122    (SubExpand $($in:tt)*) => {
123        assert_expansion!(SubExpand {$($in)*} == {$($in)*});
124    };
125}
126
127#[test]
128fn smoke() {
129    assert_expansion! {
130        #[newtype]
131        pub struct MyNewtype<T>(T);
132    };
133}
134
135#[test]
136fn smoke_sub() {
137    assert_expansion! {
138        noexpand
139        #[newtype]
140        pub struct MyNewtype<T>(T);
141    };
142}
143
144#[test]
145fn test_tt_groups_parsed_recursive() {
146    let input = quote! {
147        {{$$}}
148    };
149
150    let output = expand_products(input, ScopeMode::Expand);
151    assert_eq!(output.to_string(), "{ { $ } }");
152}
153
154#[test]
155fn test_scopes() {
156    assert_expansion! {
157        Expand {
158            foo ${bar ${baz}}
159        } == {
160            foo bar baz
161        }
162    };
163}
164
165#[test]
166fn test_sub_scopes() {
167    assert_expansion! {
168        SubExpand {
169            foo ${bar ${baz}}
170        } == {
171            foo bar baz
172        }
173    };
174}
175
176#[test]
177fn expand_at_escaping() {
178    assert_expansion! {
179        Expand {
180            $$ ${$$ foo}
181        } == {
182            $ $ foo
183        }
184    };
185}
186
187#[test]
188#[should_panic]
189fn single_at_is_invalid() {
190    assert_expansion! {
191        Expand {
192            $
193        } == {
194            $
195        }
196    };
197}
198
199#[test]
200fn noexpand_leaves_at_verbatim() {
201    assert_expansion! {
202        SubExpand {
203            $ ${$$}
204        } == {
205            $ $
206        }
207    };
208}
209
210#[test]
211fn simple_expand_unnamed() {
212    assert_expansion! {
213        Expand {
214            $((foo))
215        } == {
216            foo
217        }
218    };
219}
220
221#[test]
222fn simple_expand_named() {
223    assert_expansion! {
224        Expand {
225            $(bar: (foo))
226            $bar
227        } == {
228            foo
229        }
230    };
231}
232
233#[test]
234fn simple_expand_named_visible() {
235    assert_expansion! {
236        Expand {
237            $($bar: (foo))
238        } == {
239            foo
240        }
241    };
242}
243
244#[test]
245fn empty_defintions_expand() {
246    assert_expansion! {
247        Expand {
248            $(()()())
249            $((foo)(bar)) ;
250        } == {
251            foo ;
252            foo ;
253            foo ;
254            bar ;
255            bar ;
256            bar ;
257        }
258    };
259}