kittycad_unit_conversion_derive/
lib.rs

1/// Derive helpers for implementing unit conversions for enum types.
2
3#[macro_use]
4extern crate quote;
5#[macro_use]
6extern crate syn;
7
8/// Implement unit conversions based on an enum.
9#[proc_macro_derive(UnitConversion)]
10pub fn derive_unit_conversions(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
11    derive(parse_macro_input!(input)).into()
12}
13
14fn derive(item: syn::DeriveInput) -> proc_macro2::TokenStream {
15    let struct_name = &item.ident;
16
17    let measurement_struct_name = format_ident!("{}", struct_name.to_string().replace("Unit", ""));
18
19    // Make sure this is an enum.
20    match &item.data {
21        syn::Data::Enum(_) => {}
22        // Return early if this is not an enum.
23        _ => return quote!(),
24    }
25
26    // Get all the variants of the enum.
27    let variants: Vec<&proc_macro2::Ident> = match &item.data {
28        syn::Data::Enum(data) => data.variants.iter().map(|v| &v.ident).collect(),
29        _ => unreachable!(),
30    };
31
32    let mut items = quote!();
33    for variant in variants.clone() {
34        // Iterate over the variants again.
35        for to_variant in variants.clone() {
36            if variant == to_variant {
37                // If these two are equal our enum part is easier.
38                items = quote! {
39                    #items
40                    (#struct_name::#variant, #struct_name::#to_variant) => {
41                        input
42                    }
43                };
44            } else {
45                let from_fn = format_ident!("from_{}", clean_fn_name(&variant.to_string()));
46                let to_fn = format_ident!("as_{}", clean_fn_name(&to_variant.to_string()));
47                // Generate the conversions part of the function.
48                items = quote! {
49                    #items
50                    (#struct_name::#variant, #struct_name::#to_variant) => {
51                        let value = measurements::#measurement_struct_name::#from_fn(input);
52                        value.#to_fn()
53                    }
54                };
55            }
56        }
57    }
58
59    quote! {
60        impl #struct_name {
61            /// Do a unit conversion for this type.
62            pub fn convert_to(&self, to: #struct_name, input: f64) -> f64 {
63                match (self, to) {
64                    #items
65                }
66            }
67        }
68    }
69}
70// Rewrite some names to match the measurements lib.
71fn clean_fn_name(name: &str) -> String {
72    inflections::case::to_snake_case(
73        &name
74            .replace("Electronvolts", "e_v")
75            .replace("Kilocalories", "Kcalories"),
76    )
77}