rune_macros/
variantly.rs

1#![allow(clippy::manual_unwrap_or_default)]
2// MIT License
3//
4// Copyright (c) 2020 Luke Roberts
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24use darling::Error as DarlingError;
25use darling::ast::Style::{Struct, Tuple, Unit};
26use darling::{FromField, FromVariant, ast::Fields};
27use inflector::cases::snakecase::to_snake_case;
28use proc_macro::TokenStream;
29use proc_macro2::TokenStream as TokenStream2;
30use quote::format_ident;
31use quote::quote;
32use syn::Error as SynError;
33use syn::Ident;
34use syn::parse::Error as SynParseError;
35use syn::{ItemEnum, Type};
36
37type Result<T> = std::result::Result<T, Error>;
38
39/// Custom error type for wrapping syn & darling errors as well as any other custom errors that may become necessary.
40pub(crate) enum Error {
41    Syn(SynError),
42    Darling(DarlingError),
43}
44
45impl Error {
46    pub(crate) fn into_compile_error(self) -> TokenStream {
47        match self {
48            Error::Syn(err) => err.to_compile_error().into(),
49            Error::Darling(err) => err.write_errors().into(),
50        }
51    }
52}
53
54impl From<DarlingError> for Error {
55    fn from(err: DarlingError) -> Self {
56        Error::Darling(err)
57    }
58}
59
60impl From<SynError> for Error {
61    fn from(err: SynError) -> Self {
62        Error::Syn(err)
63    }
64}
65
66/// Declare a series of vars named by `operation` that contain an ident created
67/// by concatenating the stringified `operation`, and the passed in `ident`.
68/// # Examples
69/// ```ignore
70/// # use quote::format_ident;
71/// # use inflector::cases::snakecase::to_snake_case;
72/// let foo = format_ident!("{}", "foo");
73/// identify!(foo, [get, and]);
74/// // Expands to:
75/// // let get = format_ident!("{}_{}", stringify!(get), to_snake_case(&foo.to_string()));
76/// // let and = format_ident!("{}_{}", stringify!(and), to_snake_case(&foo.to_string()));
77/// // Which results in:
78/// // assert_eq!(get.to_string(), "get_foo");
79/// // assert_eq!(and.to_string(), "and_foo");
80/// ```
81macro_rules! identify {
82    ($ident:expr, [$($operation:ident$(,)*)*]) => {
83        $(
84            let $operation = format_ident!(
85                "{}_{}",
86                stringify!($operation),
87                $ident
88            );
89        )*
90    };
91}
92
93/// Generate the given number of unique and random idents and collect them into a vec.
94/// Struct for parsing relevant input to each variant of a variantly derived enum.
95#[derive(FromVariant, Debug)]
96#[darling(attributes(variantly))]
97struct VariantInput {
98    ident: Ident,
99    #[darling(default)]
100    rename: Option<Ident>,
101    fields: Fields<FieldParsed>,
102}
103
104/// Struct for parsing relevant information from a variant field
105#[derive(FromField, Debug)]
106#[darling(forward_attrs)]
107struct FieldParsed {
108    ty: Type,
109}
110
111/// Parsed input to each variant of a variantly derived enum.
112#[derive(Debug)]
113struct VariantParsed {
114    ident: Ident,
115    used_name: Ident,
116    fields: Fields<FieldParsed>,
117}
118
119impl From<VariantInput> for VariantParsed {
120    fn from(variant: VariantInput) -> Self {
121        let ident = &variant.ident;
122        VariantParsed {
123            used_name: format_ident!(
124                "{}",
125                to_snake_case(&variant.rename.unwrap_or_else(|| ident.clone()).to_string())
126            ),
127            ident: variant.ident,
128            fields: variant.fields,
129        }
130    }
131}
132
133/// Attempt to parse an `ItemEnum` into a vec of parsed variants.
134fn try_parse_variants(item_enum: &ItemEnum) -> Result<Vec<VariantParsed>> {
135    item_enum
136        .variants
137        .iter()
138        .map(|variant| {
139            VariantInput::from_variant(variant).map(VariantInput::into).map_err(Error::from)
140        })
141        .collect()
142}
143
144/// Helper function for validation that requires comparing each variant with each other variant.
145/// Visits each pair only once and early returns on the first failure.
146fn validate_compare<F: Fn(&VariantParsed, &VariantParsed) -> Result<()>>(
147    variants: &Vec<VariantParsed>,
148    validations: Vec<F>,
149) -> Result<()> {
150    // Enumerate over the entire set.
151    variants
152        .as_slice()
153        .iter()
154        .enumerate()
155        .try_for_each(|(index, variant_a)| -> Result<()> {
156            // Iterate over variants not visited already by the primary iterator.
157            variants[(index + 1)..variants.len()].iter().try_for_each(|variant_b| {
158                // Run the current pair against all validation fns
159                validations.iter().try_for_each(|validation| validation(variant_a, variant_b))
160            })
161        })
162}
163
164/// Validate that the used names for each variant will not cause naming conflicts.
165fn compare_used_names(a: &VariantParsed, b: &VariantParsed) -> Result<()> {
166    if a.used_name == b.used_name {
167        let message = format!(
168            "`{}` cannot be coerced into a unique & idiomatic snake_case function name as it would collide with the `{}` variant of the same Enum. \
169            use the following attribute on this or the conflicting variant to resolve: `#[variantly(rename = \"some_other_name\")]`",
170            &a.ident, &b.ident
171        );
172        Err(syn::Error::new(a.ident.span(), message).into())
173    } else {
174        Ok(())
175    }
176}
177
178fn derive_variantly_fns(target_type: &Ident, item_enum: ItemEnum) -> Result<TokenStream2> {
179    let enum_name = &item_enum.ident; // Name of the enum the attribute is on (e.g., FooEnum)
180
181    // For collecting impl functions
182    let mut functions = vec![];
183
184    let variants = try_parse_variants(&item_enum)?;
185
186    validate_compare(&variants, vec![compare_used_names])?;
187
188    for variant in variants {
189        let variant_ident = &variant.ident; // Original variant identifier (e.g., MyVariant)
190        let enum_variant_path = quote! { #enum_name::#variant_ident };
191
192        let ignore_pattern = match &variant.fields.style {
193            Tuple => {
194                handle_tuple_variant(&variant, &mut functions, enum_name, &enum_variant_path);
195                quote!((..))
196            }
197            Struct => quote!({ .. }),
198            Unit => quote!(),
199        };
200
201        identify!(variant.used_name, [is, is_not, and, or]);
202
203        functions.push(quote! {
204            pub fn #is(&self) -> bool {
205                match self.untag() {
206                    #enum_variant_path #ignore_pattern => true,
207                    _ => false
208                }
209            }
210
211            pub fn #is_not(&self) -> bool {
212                !self.#is()
213            }
214
215            // Assumes `and` is also of `target_type` and has `.untag()`
216            pub fn #and(self, and: Self) -> Self {
217                match (self.untag(), and.untag()) {
218                    (#enum_variant_path #ignore_pattern, #enum_variant_path #ignore_pattern) => and,
219                    _ => self
220                }
221            }
222
223            pub fn #or(self, or: Self) -> Self {
224                match self.untag() {
225                    #enum_variant_path #ignore_pattern => self,
226                    _ => or
227                }
228            }
229        });
230    }
231
232    // Generics of the enum are not directly applied to the impl of `target_type`.
233    // If `target_type` is generic, the user must ensure its definition is available
234    // and might need to adjust the generated `impl` line if generics are involved.
235    let output = quote! {
236        impl<'ob> #target_type<'ob> {
237            #(#functions)*
238        }
239    };
240
241    Ok(output)
242}
243
244/// Construct all impl functions related to variants with tuple style internal variables.
245fn handle_tuple_variant(
246    variant: &VariantParsed,
247    functions: &mut Vec<TokenStream2>,
248    _enum_name: &Ident,               // Name of the enum itself (e.g. FooEnum)
249    enum_variant_path: &TokenStream2, // Full path to the enum variant (e.g. FooEnum::VariantName)
250) {
251    let types: Vec<&Type> = variant.fields.fields.iter().map(|field| &field.ty).collect();
252    // let ret_type = types.first().unwrap();
253
254    let return_types_owned = quote! { (#( #types ),*) };
255
256    identify!(variant.used_name, [expect, unwrap_or_else, unwrap_or, unwrap]);
257
258    let var_fn_name = &variant.used_name;
259    let var_or_fn_name = format_ident!("{}_or", var_fn_name);
260    let var_or_else_fn_name = format_ident!("{}_or_else", var_fn_name);
261
262    // Path for matching: Enum::Variant(vars)
263    let match_path_owned = quote! { #enum_variant_path(x) }; // Use vars_pattern for ref binding
264
265    functions.push(quote! {
266        pub fn #var_fn_name(self) -> std::option::Option<#return_types_owned> {
267            match self.untag() {
268                #match_path_owned => std::option::Option::Some(x),
269                _ => std::option::Option::None,
270            }
271        }
272
273        pub fn #var_or_fn_name<E>(self, err: E) -> std::result::Result<#return_types_owned, E> {
274            self.#var_or_else_fn_name(|| err)
275        }
276
277        pub fn #var_or_else_fn_name<E, F: std::ops::FnOnce() -> E>(self, or_else: F) -> std::result::Result<#return_types_owned, E> {
278            match self.untag() {
279                #match_path_owned => std::result::Result::Ok(x),
280                _ => std::result::Result::Err(or_else())
281            }
282        }
283
284        pub fn #expect(self, msg: &str) -> #return_types_owned {
285            self.#unwrap_or_else(|| panic!("{}", msg))
286        }
287
288        pub fn #unwrap(self) -> #return_types_owned {
289            self.#unwrap_or_else(|| panic!("called `unwrap()` on a non-`{}` value", stringify!(#var_fn_name)))
290        }
291
292        pub fn #unwrap_or(self, default: #return_types_owned) -> #return_types_owned {
293            self.#unwrap_or_else(|| default)
294        }
295
296        pub fn #unwrap_or_else<F: std::ops::FnOnce() -> #return_types_owned>(self, or_else: F) -> #return_types_owned {
297            match self.untag() {
298                #match_path_owned => x,
299                _ => or_else()
300            }
301        }
302    });
303}
304
305pub(crate) fn variantly_attribute_macro_impl(
306    attr: TokenStream2,
307    item: TokenStream2,
308) -> Result<TokenStream2> {
309    let target_type: Ident = syn::parse2(attr).map_err(|e: SynParseError| Error::Syn(e))?;
310
311    let enum_item: ItemEnum =
312        syn::parse2(item.clone()).map_err(|e: SynParseError| Error::Syn(e))?;
313
314    let fns = derive_variantly_fns(&target_type, enum_item)?;
315    Ok(quote! {#item #fns})
316}