Skip to main content

conflicting/
lib.rs

1use proc_macro::TokenStream;
2
3mod input;
4
5/// The conflicting macro lets you pass a set of feature gated expressions that transitively conflict with
6/// each other. (ie. a feature conflicts with the powerset of all other features in the block)
7///
8/// You may have expressions gated by features a, b, and c, but theyre all mutually
9/// exclusive. To handle this macro will throw an error at compile time. 
10///
11/// # Example
12///
13/// ```ignore
14///     fn new() -> &'static str {
15///         conflicting! {
16///             a => {
17///                 "a"
18///             },
19///             b => {
20///                 "b"
21///             },
22///             c => {
23///                 "c"
24///             },
25///         }
26///     }
27/// ```
28///
29/// This will generate something like:
30/// ```ignore
31///     fn new() -> &'static str {
32///         #[cfg(feature = "a")]
33///         {
34///             "a"
35///             
36///             #[cfg(feature = "b")]
37///             compile_error!("a conflicts with b");
38///
39///             #[cfg(feature = "c")]
40///             compile_error!("a conflicts with c");
41///         }
42///
43///         #[cfg(feature = "b")]
44///         {
45///             "b"
46
47///             #[cfg(feature = "a")]
48///             compile_error!("b conflicts with a");
49///
50///             #[cfg(feature = "c")]
51///             compile_error!("b conflicts with c");
52///         }
53///
54///         #[cfg(feature = "c")]
55///         {
56///             "c"
57///
58///             #[cfg(feature = "a")]
59///             compile_error!("c conflicts with a");
60///
61///             #[cfg(feature = "b")]
62///             compile_error!("c conflicts with b");
63///         }
64///     }
65/// ```   
66#[proc_macro]
67pub fn conflicting(input: TokenStream) -> TokenStream {
68    let input = syn::parse_macro_input!(input as input::ConflictingInput);
69
70    let out: proc_macro2::TokenStream = input.0.iter().map(|gated_expr| {
71        let feature = &gated_expr.feature;
72        let expr = &gated_expr.expr;
73
74        let conflicting_cases = input.0.iter()
75            .filter(|other| other.feature != *feature)
76            .map(|other| {
77                let other_feature = &other.feature;
78                quote::quote! {
79                    #[cfg(feature = #other_feature)]
80                    {
81                        compile_error!(concat!(stringify!(#feature), " conflicts with ", stringify!(#other_feature)));
82                    }
83                }
84            });
85
86        quote::quote! {
87            #[cfg(feature = #feature)]
88            {
89                #(#conflicting_cases)*
90
91                #expr 
92            }
93        }
94    })
95    .collect();
96
97    out.into()
98}