foundationdb_macros/
lib.rs

1//! Macro definitions used to maintain the crate
2use proc_macro::TokenStream;
3use quote::quote;
4use std::collections::HashMap;
5use syn::__private::TokenStream2;
6use syn::parse::Parser;
7use syn::{Item, LitInt};
8use try_map::FallibleMapExt;
9
10/// Allow to compute the range of supported api versions for a functionality.
11///
12/// This macro came out from the frustration of bumping fdb's version, where
13/// we are spending most of our time searching for things like:
14/// `#[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2", feature = "fdb-6_0"))]`
15/// and adding the new version manually.
16///
17/// Thanks to the macro, we can now specify a `minimum` and an optional `max` version, and
18/// generate the right list of any. Not specifying a `max` allow easy bump to a new version.
19///
20/// `#[cfg_api_versions(min = 510, max = 600)]` will be translated to:
21/// `#[cfg(any(feature = "fdb-5_1", feature = "fdb-5_2", feature = "fdb-6_0"))]`
22#[proc_macro_attribute]
23pub fn cfg_api_versions(args: TokenStream, input: TokenStream) -> TokenStream {
24    let args = proc_macro2::TokenStream::from(args);
25    let input = proc_macro2::TokenStream::from(input);
26    cfg_api_versions_impl(args, input).into()
27}
28
29fn cfg_api_versions_impl(
30    args: proc_macro2::TokenStream,
31    input: proc_macro2::TokenStream,
32) -> proc_macro2::TokenStream {
33    let mut min: Option<LitInt> = None;
34    let mut max: Option<LitInt> = None;
35    let version_parser = syn::meta::parser(|meta| {
36        if meta.path.is_ident("min") {
37            min = Some(meta.value()?.parse()?);
38            Ok(())
39        } else if meta.path.is_ident("max") {
40            max = Some(meta.value()?.parse()?);
41            Ok(())
42        } else {
43            Err(meta.error("unsupported cfg_api_versions property"))
44        }
45    });
46
47    Parser::parse2(version_parser, args).expect("Unable to parse attribute cfg_api_versions");
48
49    let input: Item = syn::parse2(input).expect("Unable to parse input");
50
51    let minimum_version = min
52        .expect("min property must be provided")
53        .base10_parse::<i32>()
54        .expect("Unable to parse min version");
55    let maximum_version = max
56        .try_map(|x| x.base10_parse::<i32>())
57        .expect("Unable to parse max version");
58    generate_feature_range(&input, minimum_version, maximum_version)
59}
60
61fn generate_feature_range(
62    input: &Item,
63    minimum_version: i32,
64    maximum_version: Option<i32>,
65) -> proc_macro2::TokenStream {
66    let allowed_fdb_versions: Vec<TokenStream2> =
67        get_supported_feature_range(minimum_version, maximum_version)
68            .iter()
69            .map(|fdb_version| quote!(feature = #fdb_version))
70            .collect();
71
72    quote!(
73        #[cfg(any(#(#allowed_fdb_versions),*))]
74        #input
75    )
76}
77
78/// Given a range of version, this function will generate the appropriate macro text.
79fn get_supported_feature_range(minimum_version: i32, maximum_version: Option<i32>) -> Vec<String> {
80    let mut values: Vec<String> = get_version_mapping()
81        .iter()
82        .filter(|(_, version)| match maximum_version {
83            None => minimum_version <= **version,
84            Some(maximum) => minimum_version <= **version && version <= &&maximum,
85        })
86        .map(|(feature, _)| feature.to_owned())
87        .collect();
88    values.sort();
89
90    values
91}
92
93// TODO: Should we import something like lazy_static?
94fn get_version_mapping() -> HashMap<String, i32> {
95    let mut version_mapping = HashMap::with_capacity(9);
96    version_mapping.insert("fdb-7_4".into(), 740);
97    version_mapping.insert("fdb-7_3".into(), 730);
98    version_mapping.insert("fdb-7_1".into(), 710);
99    version_mapping.insert("fdb-7_0".into(), 700);
100    version_mapping.insert("fdb-6_3".into(), 630);
101    version_mapping.insert("fdb-6_2".into(), 620);
102    version_mapping.insert("fdb-6_1".into(), 610);
103    version_mapping.insert("fdb-6_0".into(), 600);
104    version_mapping.insert("fdb-5_2".into(), 520);
105    version_mapping.insert("fdb-5_1".into(), 510);
106    version_mapping.insert("fdb-5_0".into(), 500);
107    version_mapping
108}
109
110#[cfg(test)]
111mod tests {
112    use crate::cfg_api_versions_impl;
113    use crate::get_supported_feature_range;
114    use proc_macro2::TokenStream;
115    use quote::quote;
116
117    #[test]
118    fn test_create_supported_list() {
119        let v = get_supported_feature_range(700, None);
120        assert_eq!(v.len(), 4);
121        assert!(v.contains(&String::from("fdb-7_0")));
122        assert!(v.contains(&String::from("fdb-7_1")));
123        assert!(v.contains(&String::from("fdb-7_3")));
124        assert!(v.contains(&String::from("fdb-7_4")));
125
126        let v = get_supported_feature_range(600, Some(700));
127        assert_eq!(v.len(), 5);
128        assert!(v.contains(&String::from("fdb-7_0")));
129        assert!(v.contains(&String::from("fdb-6_3")));
130        assert!(v.contains(&String::from("fdb-6_2")));
131        assert!(v.contains(&String::from("fdb-6_1")));
132        assert!(v.contains(&String::from("fdb-6_0")));
133
134        let v = get_supported_feature_range(500, Some(610));
135        assert_eq!(v.len(), 5);
136        assert!(v.contains(&String::from("fdb-6_1")));
137        assert!(v.contains(&String::from("fdb-6_0")));
138        assert!(v.contains(&String::from("fdb-5_2")));
139        assert!(v.contains(&String::from("fdb-5_1")));
140        assert!(v.contains(&String::from("fdb-5_0")));
141
142        let v = get_supported_feature_range(500, None);
143        assert_eq!(v.len(), 11);
144        assert!(v.contains(&String::from("fdb-7_4")));
145        assert!(v.contains(&String::from("fdb-7_3")));
146        assert!(v.contains(&String::from("fdb-7_1")));
147        assert!(v.contains(&String::from("fdb-7_0")));
148        assert!(v.contains(&String::from("fdb-6_3")));
149        assert!(v.contains(&String::from("fdb-6_2")));
150        assert!(v.contains(&String::from("fdb-6_1")));
151        assert!(v.contains(&String::from("fdb-6_0")));
152        assert!(v.contains(&String::from("fdb-5_2")));
153        assert!(v.contains(&String::from("fdb-5_1")));
154        assert!(v.contains(&String::from("fdb-5_0")));
155    }
156
157    fn test_cfg_versions(expected_versions: TokenStream, attrs: TokenStream) {
158        let input = quote! {
159            fn ma_fonction() {}
160        };
161
162        let expected = quote! {
163            #[cfg(any(#expected_versions))]
164            fn ma_fonction() {}
165        };
166
167        let result = cfg_api_versions_impl(attrs, input);
168        assert_eq!(result.to_string(), expected.to_string())
169    }
170
171    #[test]
172    fn test_min_700_no_max_version() {
173        let data = quote!(
174            feature = "fdb-7_0",
175            feature = "fdb-7_1",
176            feature = "fdb-7_3"
177            feature = "fdb-7_4"
178        );
179
180        let attrs = quote!(min = 700);
181
182        test_cfg_versions(data, attrs)
183    }
184
185    #[test]
186    fn test_min_600_max_700() {
187        let expected_versions = quote!(
188            feature = "fdb-6_0",
189            feature = "fdb-6_1",
190            feature = "fdb-6_2",
191            feature = "fdb-6_3",
192            feature = "fdb-7_0"
193        );
194
195        let attrs = quote!(min = 600, max = 700);
196
197        test_cfg_versions(expected_versions, attrs)
198    }
199
200    #[test]
201    fn test_min_500_max_610() {
202        let expected_versions = quote!(
203            feature = "fdb-5_0",
204            feature = "fdb-5_1",
205            feature = "fdb-5_2",
206            feature = "fdb-6_0",
207            feature = "fdb-6_1"
208        );
209
210        let attrs = quote!(min = 500, max = 610);
211
212        test_cfg_versions(expected_versions, attrs)
213    }
214
215    #[test]
216    fn test_min_500_no_max() {
217        let expected_versions = quote!(
218            feature = "fdb-5_0",
219            feature = "fdb-5_1",
220            feature = "fdb-5_2",
221            feature = "fdb-6_0",
222            feature = "fdb-6_1",
223            feature = "fdb-6_2",
224            feature = "fdb-6_3",
225            feature = "fdb-7_0",
226            feature = "fdb-7_1",
227            feature = "fdb-7_3"
228            feature = "fdb-7_4"
229        );
230
231        let attrs = quote!(min = 500);
232
233        test_cfg_versions(expected_versions, attrs)
234    }
235
236    #[test]
237    #[should_panic]
238    fn test_no_min_version() {
239        let expected_versions = quote!(feature = "fdb-5_0",);
240
241        let attrs = quote!(max = 500);
242
243        test_cfg_versions(expected_versions, attrs)
244    }
245}