From 52e39e4d7cb7a9bf4fee42f382ffbfc5103bfa34 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Mon, 24 Sep 2018 13:55:36 +1200 Subject: [PATCH] Add `impl<'_enum> From<&'_enum MyEnum> for MyEnumDiscriminants`. Issue #33 --- strum_macros/src/enum_discriminants.rs | 58 +++++++++++++++++++++---- strum_macros/src/lib.rs | 1 + strum_tests/tests/enum_discriminants.rs | 6 +++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/strum_macros/src/enum_discriminants.rs b/strum_macros/src/enum_discriminants.rs index c3802b84..2108f679 100644 --- a/strum_macros/src/enum_discriminants.rs +++ b/strum_macros/src/enum_discriminants.rs @@ -8,7 +8,6 @@ use helpers::{ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let vis = &ast.vis; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); let variants = match ast.data { syn::Data::Enum(ref v) => &v.variants, @@ -64,7 +63,25 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { discriminants.push(quote!{ #(#attrs)* #ident }); } - // Add match arms for `From< TheEnum > + // Ideally: + // + // * For `Copy` types, we `impl From for TheEnumDiscriminants` + // * For `!Copy` types, we `impl<'enum> From<&'enum TheEnum> for TheEnumDiscriminants` + // + // That way we ensure users are not able to pass a `Copy` type by reference. However, the + // `#[derive(..)]` attributes are not in the parsed tokens, so we are not able to check if a + // type is `Copy`, so we just implement both. + // + // See + // --- + // let is_copy = unique_meta_list(type_meta.iter(), "derive") + // .map(extract_list_metas) + // .map(|metas| { + // metas + // .filter_map(get_meta_ident) + // .any(|derive| derive.to_string() == "Copy") + // }).unwrap_or(false); + let arms = variants .iter() .map(|variant| { @@ -83,6 +100,34 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { quote! { #name::#ident #params => #discriminants_name::#ident } }).collect::>(); + let from_fn_body = quote! { match val { #(#arms),* } }; + + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + let impl_from = quote! { + impl #impl_generics ::std::convert::From< #name #ty_generics > for #discriminants_name #where_clause { + fn from(val: #name #ty_generics) -> #discriminants_name { + #from_fn_body + } + } + }; + let impl_from_ref = { + let mut generics = ast.generics.clone(); + + let lifetime = parse_quote!('_enum); + let enum_life = quote! { & #lifetime }; + generics.params.push(lifetime); + + // Shadows the earlier `impl_generics` + let (impl_generics, _, _) = generics.split_for_impl(); + + quote! { + impl #impl_generics ::std::convert::From< #enum_life #name #ty_generics > for #discriminants_name #where_clause { + fn from(val: #enum_life #name #ty_generics) -> #discriminants_name { + #from_fn_body + } + } + } + }; quote!{ /// Auto-generated discriminant enum variants @@ -92,12 +137,7 @@ pub fn enum_discriminants_inner(ast: &syn::DeriveInput) -> TokenStream { #(#discriminants),* } - impl #impl_generics ::std::convert::From< #name #ty_generics > for #discriminants_name #where_clause { - fn from(s: #name #ty_generics) -> #discriminants_name { - match s { - #(#arms),* - } - } - } + #impl_from + #impl_from_ref } } diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 560586c2..3003a645 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -10,6 +10,7 @@ #![recursion_limit = "128"] extern crate heck; +#[macro_use] extern crate syn; #[macro_use] extern crate quote; diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs index 1c97c71e..c22a414a 100644 --- a/strum_tests/tests/enum_discriminants.rs +++ b/strum_tests/tests/enum_discriminants.rs @@ -157,3 +157,9 @@ fn from_test() { assert_eq!(EnumIntoDiscriminants::A, EnumInto::A(true).into()); assert_eq!(EnumIntoDiscriminants::B, EnumInto::B(1).into()); } + +#[test] +fn from_ref_test() { + assert_eq!(EnumIntoDiscriminants::A, (&EnumInto::A(true)).into()); + assert_eq!(EnumIntoDiscriminants::B, (&EnumInto::B(1)).into()); +}