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.
2324use 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};
3637type Result<T> = std::result::Result<T, Error>;
3839/// 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}
4445impl Error {
46pub(crate) fn into_compile_error(self) -> TokenStream {
47match self {
48 Error::Syn(err) => err.to_compile_error().into(),
49 Error::Darling(err) => err.write_errors().into(),
50 }
51 }
52}
5354impl From<DarlingError> for Error {
55fn from(err: DarlingError) -> Self {
56 Error::Darling(err)
57 }
58}
5960impl From<SynError> for Error {
61fn from(err: SynError) -> Self {
62 Error::Syn(err)
63 }
64}
6566/// 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 $(
84let $operation = format_ident!(
85"{}_{}",
86stringify!($operation),
87$ident
88);
89 )*
90 };
91}
9293/// 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)]
100rename: Option<Ident>,
101 fields: Fields<FieldParsed>,
102}
103104/// Struct for parsing relevant information from a variant field
105#[derive(FromField, Debug)]
106#[darling(forward_attrs)]
107struct FieldParsed {
108 ty: Type,
109}
110111/// 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}
118119impl From<VariantInput> for VariantParsed {
120fn from(variant: VariantInput) -> Self {
121let 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}
132133/// 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}
143144/// 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.
151variants
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.
157variants[(index + 1)..variants.len()].iter().try_for_each(|variant_b| {
158// Run the current pair against all validation fns
159validations.iter().try_for_each(|validation| validation(variant_a, variant_b))
160 })
161 })
162}
163164/// Validate that the used names for each variant will not cause naming conflicts.
165fn compare_used_names(a: &VariantParsed, b: &VariantParsed) -> Result<()> {
166if a.used_name == b.used_name {
167let 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 );
172Err(syn::Error::new(a.ident.span(), message).into())
173 } else {
174Ok(())
175 }
176}
177178fn derive_variantly_fns(target_type: &Ident, item_enum: ItemEnum) -> Result<TokenStream2> {
179let enum_name = &item_enum.ident; // Name of the enum the attribute is on (e.g., FooEnum)
180181 // For collecting impl functions
182let mut functions = vec![];
183184let variants = try_parse_variants(&item_enum)?;
185186 validate_compare(&variants, vec![compare_used_names])?;
187188for variant in variants {
189let variant_ident = &variant.ident; // Original variant identifier (e.g., MyVariant)
190let enum_variant_path = quote! { #enum_name::#variant_ident };
191192let ignore_pattern = match &variant.fields.style {
193 Tuple => {
194 handle_tuple_variant(&variant, &mut functions, enum_name, &enum_variant_path);
195quote!((..))
196 }
197 Struct => quote!({ .. }),
198 Unit => quote!(),
199 };
200201identify!(variant.used_name, [is, is_not, and, or]);
202203 functions.push(quote! {
204pub fn #is(&self) -> bool {
205match self.untag() {
206 #enum_variant_path #ignore_pattern => true,
207_ => false
208}
209 }
210211pub fn #is_not(&self) -> bool {
212 !self.#is()
213 }
214215// Assumes `and` is also of `target_type` and has `.untag()`
216pub fn #and(self, and: Self) -> Self {
217match (self.untag(), and.untag()) {
218 (#enum_variant_path #ignore_pattern, #enum_variant_path #ignore_pattern) => and,
219_ => self
220}
221 }
222223pub fn #or(self, or: Self) -> Self {
224match self.untag() {
225 #enum_variant_path #ignore_pattern => self,
226_ => or
227 }
228 }
229 });
230 }
231232// 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.
235let output = quote! {
236impl<'ob> #target_type<'ob> {
237 #(#functions)*
238 }
239 };
240241Ok(output)
242}
243244/// 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)
249enum_variant_path: &TokenStream2, // Full path to the enum variant (e.g. FooEnum::VariantName)
250) {
251let types: Vec<&Type> = variant.fields.fields.iter().map(|field| &field.ty).collect();
252// let ret_type = types.first().unwrap();
253254let return_types_owned = quote! { (#( #types ),*) };
255256identify!(variant.used_name, [expect, unwrap_or_else, unwrap_or, unwrap]);
257258let var_fn_name = &variant.used_name;
259let var_or_fn_name = format_ident!("{}_or", var_fn_name);
260let var_or_else_fn_name = format_ident!("{}_or_else", var_fn_name);
261262// Path for matching: Enum::Variant(vars)
263let match_path_owned = quote! { #enum_variant_path(x) }; // Use vars_pattern for ref binding
264265functions.push(quote! {
266pub fn #var_fn_name(self) -> std::option::Option<#return_types_owned> {
267match self.untag() {
268 #match_path_owned => std::option::Option::Some(x),
269_ => std::option::Option::None,
270 }
271 }
272273pub fn #var_or_fn_name<E>(self, err: E) -> std::result::Result<#return_types_owned, E> {
274self.#var_or_else_fn_name(|| err)
275 }
276277pub fn #var_or_else_fn_name<E, F: std::ops::FnOnce() -> E>(self, or_else: F) -> std::result::Result<#return_types_owned, E> {
278match self.untag() {
279 #match_path_owned => std::result::Result::Ok(x),
280_ => std::result::Result::Err(or_else())
281 }
282 }
283284pub fn #expect(self, msg: &str) -> #return_types_owned {
285self.#unwrap_or_else(|| panic!("{}", msg))
286 }
287288pub fn #unwrap(self) -> #return_types_owned {
289self.#unwrap_or_else(|| panic!("called `unwrap()` on a non-`{}` value", stringify!(#var_fn_name)))
290 }
291292pub fn #unwrap_or(self, default: #return_types_owned) -> #return_types_owned {
293self.#unwrap_or_else(|| default)
294 }
295296pub fn #unwrap_or_else<F: std::ops::FnOnce() -> #return_types_owned>(self, or_else: F) -> #return_types_owned {
297match self.untag() {
298 #match_path_owned => x,
299_ => or_else()
300 }
301 }
302 });
303}
304305pub(crate) fn variantly_attribute_macro_impl(
306 attr: TokenStream2,
307 item: TokenStream2,
308) -> Result<TokenStream2> {
309let target_type: Ident = syn::parse2(attr).map_err(|e: SynParseError| Error::Syn(e))?;
310311let enum_item: ItemEnum =
312 syn::parse2(item.clone()).map_err(|e: SynParseError| Error::Syn(e))?;
313314let fns = derive_variantly_fns(&target_type, enum_item)?;
315Ok(quote! {#item #fns})
316}