From 3b66b2c16a7e9c2870fa25f6681a8872125aa0c6 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Sat, 16 Oct 2021 07:52:02 -0700 Subject: [PATCH 01/18] Add EnumConstIndex --- strum_macros/src/lib.rs | 10 +++ strum_macros/src/macros/enum_const_index.rs | 69 +++++++++++++++++++++ strum_macros/src/macros/mod.rs | 1 + strum_tests/tests/enum_const_index.rs | 44 +++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 strum_macros/src/macros/enum_const_index.rs create mode 100644 strum_tests/tests/enum_const_index.rs diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 38eb0b39..611a8ac4 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -374,6 +374,16 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } +#[proc_macro_derive(EnumConstIndex, attributes(strum))] +pub fn enum_const_index(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(input as DeriveInput); + + let toks = + macros::enum_const_index::enum_const_index_inner(&ast).unwrap_or_else(|err| err.to_compile_error()); + debug_print_generated(&ast, &toks); + toks.into() +} + /// Add a verbose message to an enum variant. /// /// Encode strings into the enum itself. The `strum_macros::EmumMessage` macro implements the `strum::EnumMessage` trait. diff --git a/strum_macros/src/macros/enum_const_index.rs b/strum_macros/src/macros/enum_const_index.rs new file mode 100644 index 00000000..4648de90 --- /dev/null +++ b/strum_macros/src/macros/enum_const_index.rs @@ -0,0 +1,69 @@ +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Data, DeriveInput}; + +use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties}; + +pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { + let name = &ast.ident; + let gen = &ast.generics; + let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); + let vis = &ast.vis; + //let type_properties = ast.get_type_properties()?; + //let strum_module_path = type_properties.crate_module_path(); + + if gen.lifetimes().count() > 0 { + return Err(syn::Error::new( + Span::call_site(), + "This macro doesn't support enums with lifetimes. \ + The resulting enums would be unbounded.", + )); + } + + let variants = match &ast.data { + Data::Enum(v) => &v.variants, + _ => return Err(non_enum_error()), + }; + + let mut arms = Vec::new(); + let mut idx = 0usize; + for variant in variants { + use syn::Fields::*; + + if variant.get_variant_properties()?.disabled.is_some() { + continue; + } + + let ident = &variant.ident; + let params = match &variant.fields { + Unit => quote! {}, + Unnamed(fields) => { + let defaults = ::std::iter::repeat(quote!(::core::default::Default::default())) + .take(fields.unnamed.len()); + quote! { (#(#defaults),*) } + } + Named(fields) => { + let fields = fields + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); + quote! { {#(#fields: ::core::default::Default::default()),*} } + } + }; + + arms.push(quote! {#idx => ::core::option::Option::Some(#name::#ident #params)}); + idx += 1; + } + + arms.push(quote! { _ => ::core::option::Option::None }); + + Ok(quote! { + impl #name #gen { + #vis const fn const_get(idx: usize) -> Option<#name #ty_generics> { + match idx { + #(#arms),* + } + } + } + }) +} diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index 9b707fbb..189470ae 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -1,3 +1,4 @@ +pub mod enum_const_index; pub mod enum_count; pub mod enum_discriminants; pub mod enum_iter; diff --git a/strum_tests/tests/enum_const_index.rs b/strum_tests/tests/enum_const_index.rs new file mode 100644 index 00000000..ebb18b4a --- /dev/null +++ b/strum_tests/tests/enum_const_index.rs @@ -0,0 +1,44 @@ +use strum::{EnumConstIndex}; + +#[derive(Debug, EnumConstIndex, PartialEq)] +enum Week { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, +} + +#[test] +fn simple_test() { + assert_eq!(Week::const_get(0), Some(Week::Sunday)); + assert_eq!(Week::const_get(6), Some(Week::Saturday)); + assert_eq!(Week::const_get(7), None); +} + +#[test] +fn crate_module_path_test() { + pub mod nested { + pub mod module { + pub use strum; + } + } + + #[derive(Debug, EnumConstIndex, PartialEq)] + #[strum(crate = "nested::module::strum")] + enum Week { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + } + + assert_eq!(Week::const_get(0), Some(Week::Sunday)); + assert_eq!(Week::const_get(6), Some(Week::Saturday)); + assert_eq!(Week::const_get(7), None); +} From 0dbc4c811750507bf7b9050536ec57635cabf0c0 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Sat, 16 Oct 2021 08:04:59 -0700 Subject: [PATCH 02/18] Get working with discriminants --- strum_macros/src/macros/enum_const_index.rs | 20 ++++++++++++++++++-- strum_tests/tests/enum_const_index.rs | 5 +++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/strum_macros/src/macros/enum_const_index.rs b/strum_macros/src/macros/enum_const_index.rs index 4648de90..af8e1fea 100644 --- a/strum_macros/src/macros/enum_const_index.rs +++ b/strum_macros/src/macros/enum_const_index.rs @@ -2,12 +2,12 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Data, DeriveInput}; -use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties}; +use crate::helpers::{non_enum_error, HasStrumVariantProperties}; pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; - let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); + let (_impl_generics, ty_generics, _where_clause) = gen.split_for_impl(); let vis = &ast.vis; //let type_properties = ast.get_type_properties()?; //let strum_module_path = type_properties.crate_module_path(); @@ -51,6 +51,22 @@ pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { } }; + if let Some((_eq,expr)) = &variant.discriminant { + if let syn::Expr::Lit(expr_lit) = expr { + if let syn::Lit::Int(int_lit) = &expr_lit.lit { + if let Ok(v) = int_lit.base10_parse() { + idx = v; + } else { + panic!() + } + } else { + panic!(); + } + } else { + panic!(); + } + } + arms.push(quote! {#idx => ::core::option::Option::Some(#name::#ident #params)}); idx += 1; } diff --git a/strum_tests/tests/enum_const_index.rs b/strum_tests/tests/enum_const_index.rs index ebb18b4a..fdca260d 100644 --- a/strum_tests/tests/enum_const_index.rs +++ b/strum_tests/tests/enum_const_index.rs @@ -8,14 +8,15 @@ enum Week { Wednesday, Thursday, Friday, - Saturday, + Saturday=8, } #[test] fn simple_test() { assert_eq!(Week::const_get(0), Some(Week::Sunday)); - assert_eq!(Week::const_get(6), Some(Week::Saturday)); assert_eq!(Week::const_get(7), None); + assert_eq!(Week::const_get(8), Some(Week::Saturday)); + assert_eq!(Week::const_get(9), None); } #[test] From 4db014b727a2ea6e1ab1db59c3e12b22a919988f Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Sat, 16 Oct 2021 10:04:30 -0700 Subject: [PATCH 03/18] Remove unused/(unneeded?) features --- strum_macros/src/macros/enum_const_index.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/strum_macros/src/macros/enum_const_index.rs b/strum_macros/src/macros/enum_const_index.rs index af8e1fea..9b32b483 100644 --- a/strum_macros/src/macros/enum_const_index.rs +++ b/strum_macros/src/macros/enum_const_index.rs @@ -7,10 +7,7 @@ use crate::helpers::{non_enum_error, HasStrumVariantProperties}; pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; - let (_impl_generics, ty_generics, _where_clause) = gen.split_for_impl(); let vis = &ast.vis; - //let type_properties = ast.get_type_properties()?; - //let strum_module_path = type_properties.crate_module_path(); if gen.lifetimes().count() > 0 { return Err(syn::Error::new( @@ -75,7 +72,7 @@ pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { Ok(quote! { impl #name #gen { - #vis const fn const_get(idx: usize) -> Option<#name #ty_generics> { + #vis const fn const_get(idx: usize) -> Option<#name #gen> { match idx { #(#arms),* } From 5f7cf5da663081d5b7684db1df612a25d87389a6 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Sun, 17 Oct 2021 08:39:28 -0700 Subject: [PATCH 04/18] Rename to EnumIndex and index(..). Make const_index conditional --- strum_macros/src/lib.rs | 48 +++++++++++++++++-- .../{enum_const_index.rs => enum_index.rs} | 23 ++++++++- strum_macros/src/macros/mod.rs | 2 +- .../{enum_const_index.rs => enum_index.rs} | 6 +-- 4 files changed, 70 insertions(+), 9 deletions(-) rename strum_macros/src/macros/{enum_const_index.rs => enum_index.rs} (78%) rename strum_tests/tests/{enum_const_index.rs => enum_index.rs} (87%) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 611a8ac4..2d405dff 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -374,12 +374,54 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -#[proc_macro_derive(EnumConstIndex, attributes(strum))] -pub fn enum_const_index(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +/// Add a function to enum that allows accessing variants by index +/// +/// The macro adds up to two standalone functions. The macro adds `index(idx: usize) -> Option` which +/// will use `Default::default()` for any additional data on the variant. For enums where there is +/// no additional data on any, a seocnd function `const_index(idx: usize) -> Option` is added. +/// This function is marked `const` allowing it to be used in a `const` context. Since +/// `Default::default()` is not `const` it is not possible to use it to accomodate additional data. +/// +/// You cannot derive `EnumIndex` on any type with a lifetime bound (`<'a>`) because the function would surely +/// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). +/// +/// ``` +/// +/// use strum_macros::EnumIndex; +/// +/// #[derive(EnumIndex, Debug, PartialEq)] +/// enum Color { +/// Red, +/// Green { range: usize }, +/// Blue(usize), +/// Yellow, +/// } +/// +/// assert_eq!(Some(Color::Red), Color::index(0)); +/// assert_eq!(Some(Color::Green {range: 0}), Color::index(1)); +/// assert_eq!(Some(Color::Blue(0)), Color::index(2)); +/// assert_eq!(Some(Color::Yellow), Color::index(3)); +/// assert_eq!(None, Color::index(4)); +/// +/// #[derive(EnumIndex, Debug, PartialEq)] +/// enum Vehicle { +/// Car = 1, +/// Truck = 3, +/// } +/// +/// assert_eq!(None, Vehicle::const_index(0)); +/// assert_eq!(Some(Vehicle::Car), Vehicle::const_index(1)); +/// assert_eq!(None, Vehicle::const_index(2)); +/// assert_eq!(Some(Vehicle::Truck), Vehicle::const_index(3)); +/// assert_eq!(None, Vehicle::const_index(4)); +/// ``` + +#[proc_macro_derive(EnumIndex, attributes(strum))] +pub fn enum_index(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(input as DeriveInput); let toks = - macros::enum_const_index::enum_const_index_inner(&ast).unwrap_or_else(|err| err.to_compile_error()); + macros::enum_index::enum_index_inner(&ast).unwrap_or_else(|err| err.to_compile_error()); debug_print_generated(&ast, &toks); toks.into() } diff --git a/strum_macros/src/macros/enum_const_index.rs b/strum_macros/src/macros/enum_index.rs similarity index 78% rename from strum_macros/src/macros/enum_const_index.rs rename to strum_macros/src/macros/enum_index.rs index 9b32b483..8bc90a1e 100644 --- a/strum_macros/src/macros/enum_const_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -4,7 +4,7 @@ use syn::{Data, DeriveInput}; use crate::helpers::{non_enum_error, HasStrumVariantProperties}; -pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { +pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; let vis = &ast.vis; @@ -24,6 +24,7 @@ pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { let mut arms = Vec::new(); let mut idx = 0usize; + let mut has_additional_data = false; for variant in variants { use syn::Fields::*; @@ -35,11 +36,13 @@ pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { let params = match &variant.fields { Unit => quote! {}, Unnamed(fields) => { + has_additional_data = true; let defaults = ::std::iter::repeat(quote!(::core::default::Default::default())) .take(fields.unnamed.len()); quote! { (#(#defaults),*) } } Named(fields) => { + has_additional_data = true; let fields = fields .named .iter() @@ -70,13 +73,29 @@ pub fn enum_const_index_inner(ast: &DeriveInput) -> syn::Result { arms.push(quote! { _ => ::core::option::Option::None }); + let const_impl = if has_additional_data { + quote! {} + } else { + quote! { + impl #name #gen { + #vis const fn const_index(idx: usize) -> Option<#name #gen> { + match idx { + #(#arms),* + } + } + } + } + }; + Ok(quote! { impl #name #gen { - #vis const fn const_get(idx: usize) -> Option<#name #gen> { + #vis fn index(idx: usize) -> Option<#name #gen> { match idx { #(#arms),* } } } + + #const_impl }) } diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index 189470ae..93f879e9 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -1,4 +1,4 @@ -pub mod enum_const_index; +pub mod enum_index; pub mod enum_count; pub mod enum_discriminants; pub mod enum_iter; diff --git a/strum_tests/tests/enum_const_index.rs b/strum_tests/tests/enum_index.rs similarity index 87% rename from strum_tests/tests/enum_const_index.rs rename to strum_tests/tests/enum_index.rs index fdca260d..72f81e45 100644 --- a/strum_tests/tests/enum_const_index.rs +++ b/strum_tests/tests/enum_index.rs @@ -1,6 +1,6 @@ -use strum::{EnumConstIndex}; +use strum::{EnumIndex}; -#[derive(Debug, EnumConstIndex, PartialEq)] +#[derive(Debug, EnumIndex, PartialEq)] enum Week { Sunday, Monday, @@ -27,7 +27,7 @@ fn crate_module_path_test() { } } - #[derive(Debug, EnumConstIndex, PartialEq)] + #[derive(Debug, EnumIndex, PartialEq)] #[strum(crate = "nested::module::strum")] enum Week { Sunday, From 93137823f975b2415cbd5c5d362931fcef1d1b46 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Sun, 17 Oct 2021 13:09:47 -0700 Subject: [PATCH 05/18] Get repr(..) working --- strum_macros/src/lib.rs | 1 + strum_macros/src/macros/enum_index.rs | 88 ++++++++++++++++++++++----- strum_tests/tests/enum_index.rs | 16 ++--- 3 files changed, 82 insertions(+), 23 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 2d405dff..e4e7b5da 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -404,6 +404,7 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// assert_eq!(None, Color::index(4)); /// /// #[derive(EnumIndex, Debug, PartialEq)] +/// #[repr(u8)] /// enum Vehicle { /// Car = 1, /// Truck = 3, diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index 8bc90a1e..88f03c79 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -1,6 +1,6 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{Data, DeriveInput}; +use syn::{Data, DeriveInput, PathArguments, Type, TypeParen, Ident}; use crate::helpers::{non_enum_error, HasStrumVariantProperties}; @@ -8,6 +8,51 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; let vis = &ast.vis; + let attrs = &ast.attrs; + + let mod_name = quote::format_ident!("{}Index",name); + + let mut index_type: Type = syn::parse("usize".parse().unwrap()).unwrap(); + for attr in attrs { + let path = &attr.path; + let tokens = &attr.tokens; + if path.leading_colon.is_some() { + continue + } + if path.segments.len() != 1 { + continue + } + let segment = path.segments.first().unwrap(); + if segment.ident != "repr" { + continue + } + if segment.arguments != PathArguments::None { + continue + } + let typ_paren = match syn::parse2::(tokens.clone()) { + Ok(Type::Paren(TypeParen { + elem, + .. + })) => *elem, + _ => { + continue + } + }; + let inner_path = match &typ_paren { + Type::Path(t) => t, + _ => { + continue + } + }; + if let Some(seg) = inner_path.path.segments.last() { + for t in ["u8","u16","u32","u64","usize","i8","i16","i32","i64","isize"] { + if seg.ident == t { + index_type = typ_paren; + break + } + } + } + } if gen.lifetimes().count() > 0 { return Err(syn::Error::new( @@ -23,8 +68,10 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { }; let mut arms = Vec::new(); - let mut idx = 0usize; + let mut const_defs = Vec::new(); + let mut var_idx = 0usize; let mut has_additional_data = false; + let mut prev_const_var_name = None; for variant in variants { use syn::Fields::*; @@ -51,14 +98,13 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } }; + let const_var_name = syn::parse_str::(&format!("VARIANT{}", var_idx)).unwrap(); + let mut discriminant_found = false; if let Some((_eq,expr)) = &variant.discriminant { if let syn::Expr::Lit(expr_lit) = expr { if let syn::Lit::Int(int_lit) = &expr_lit.lit { - if let Ok(v) = int_lit.base10_parse() { - idx = v; - } else { - panic!() - } + const_defs.push(quote! {pub const #const_var_name: #index_type = #int_lit;}); + discriminant_found = true; } else { panic!(); } @@ -66,9 +112,17 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { panic!(); } } + if !discriminant_found { + if let Some(prev) = &prev_const_var_name { + const_defs.push(quote! {pub const #const_var_name: #index_type = #prev + 1;}); + } else { + const_defs.push(quote! {pub const #const_var_name: #index_type = 0;}); + } + } - arms.push(quote! {#idx => ::core::option::Option::Some(#name::#ident #params)}); - idx += 1; + arms.push(quote! {v if v == #mod_name::#const_var_name => ::core::option::Option::Some(#name::#ident #params)}); + prev_const_var_name = Some(const_var_name); + var_idx += 1; } arms.push(quote! { _ => ::core::option::Option::None }); @@ -77,25 +131,27 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { quote! {} } else { quote! { - impl #name #gen { - #vis const fn const_index(idx: usize) -> Option<#name #gen> { - match idx { - #(#arms),* - } + #vis const fn const_index(idx: #index_type) -> Option<#name #gen> { + match idx { + #(#arms),* } } } }; Ok(quote! { + mod #mod_name { + #(#const_defs)* + } + impl #name #gen { - #vis fn index(idx: usize) -> Option<#name #gen> { + fn index(idx: #index_type) -> Option<#name #gen> { match idx { #(#arms),* } } + #const_impl } - #const_impl }) } diff --git a/strum_tests/tests/enum_index.rs b/strum_tests/tests/enum_index.rs index 72f81e45..9058fd20 100644 --- a/strum_tests/tests/enum_index.rs +++ b/strum_tests/tests/enum_index.rs @@ -1,6 +1,7 @@ use strum::{EnumIndex}; #[derive(Debug, EnumIndex, PartialEq)] +#[repr(u8)] enum Week { Sunday, Monday, @@ -13,10 +14,11 @@ enum Week { #[test] fn simple_test() { - assert_eq!(Week::const_get(0), Some(Week::Sunday)); - assert_eq!(Week::const_get(7), None); - assert_eq!(Week::const_get(8), Some(Week::Saturday)); - assert_eq!(Week::const_get(9), None); + assert_eq!(Week::const_index(0), Some(Week::Sunday)); + assert_eq!(Week::const_index(1), Some(Week::Monday)); + assert_eq!(Week::const_index(7), None); + assert_eq!(Week::const_index(8), Some(Week::Saturday)); + assert_eq!(Week::const_index(9), None); } #[test] @@ -39,7 +41,7 @@ fn crate_module_path_test() { Saturday, } - assert_eq!(Week::const_get(0), Some(Week::Sunday)); - assert_eq!(Week::const_get(6), Some(Week::Saturday)); - assert_eq!(Week::const_get(7), None); + assert_eq!(Week::const_index(0), Some(Week::Sunday)); + assert_eq!(Week::const_index(6), Some(Week::Saturday)); + assert_eq!(Week::const_index(7), None); } From 8d8f54809722e0c68d3e7580ce0f6276a04d25a8 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Mon, 18 Oct 2021 17:08:54 -0700 Subject: [PATCH 06/18] Fix issue to support rust 1.32 --- strum_macros/src/macros/enum_index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index 88f03c79..723c4e1f 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -45,7 +45,7 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } }; if let Some(seg) = inner_path.path.segments.last() { - for t in ["u8","u16","u32","u64","usize","i8","i16","i32","i64","isize"] { + for t in &["u8","u16","u32","u64","usize","i8","i16","i32","i64","isize"] { if seg.ident == t { index_type = typ_paren; break From 8f9caacccb0f7313bf575c973116c9365a76be5c Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Mon, 18 Oct 2021 17:22:16 -0700 Subject: [PATCH 07/18] Switch from VARIANT# to {ENUM}_{VARIANT} for variant constant names --- strum_macros/src/macros/enum_index.rs | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index 723c4e1f..3b32de7c 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -10,8 +10,6 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let vis = &ast.vis; let attrs = &ast.attrs; - let mod_name = quote::format_ident!("{}Index",name); - let mut index_type: Type = syn::parse("usize".parse().unwrap()).unwrap(); for attr in attrs { let path = &attr.path; @@ -69,9 +67,8 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let mut arms = Vec::new(); let mut const_defs = Vec::new(); - let mut var_idx = 0usize; let mut has_additional_data = false; - let mut prev_const_var_name = None; + let mut prev_const_var_ident = None; for variant in variants { use syn::Fields::*; @@ -98,12 +95,15 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } }; - let const_var_name = syn::parse_str::(&format!("VARIANT{}", var_idx)).unwrap(); + use heck::ShoutySnakeCase; + let const_var_name = format!("{}{}",name, variant.ident.to_string()).to_shouty_snake_case(); + let const_var_ident = syn::parse_str::(&const_var_name).unwrap(); + let mut discriminant_found = false; if let Some((_eq,expr)) = &variant.discriminant { if let syn::Expr::Lit(expr_lit) = expr { if let syn::Lit::Int(int_lit) = &expr_lit.lit { - const_defs.push(quote! {pub const #const_var_name: #index_type = #int_lit;}); + const_defs.push(quote! {pub const #const_var_ident: #index_type = #int_lit;}); discriminant_found = true; } else { panic!(); @@ -113,16 +113,15 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } } if !discriminant_found { - if let Some(prev) = &prev_const_var_name { - const_defs.push(quote! {pub const #const_var_name: #index_type = #prev + 1;}); + if let Some(prev) = &prev_const_var_ident { + const_defs.push(quote! {pub const #const_var_ident: #index_type = #prev + 1;}); } else { - const_defs.push(quote! {pub const #const_var_name: #index_type = 0;}); + const_defs.push(quote! {pub const #const_var_ident: #index_type = 0;}); } } - arms.push(quote! {v if v == #mod_name::#const_var_name => ::core::option::Option::Some(#name::#ident #params)}); - prev_const_var_name = Some(const_var_name); - var_idx += 1; + arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)}); + prev_const_var_ident = Some(const_var_ident); } arms.push(quote! { _ => ::core::option::Option::None }); @@ -140,9 +139,7 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { }; Ok(quote! { - mod #mod_name { - #(#const_defs)* - } + #(#const_defs)* impl #name #gen { fn index(idx: #index_type) -> Option<#name #gen> { From 43b850570379af09e121d3ed5d121f64568bbd96 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Mon, 18 Oct 2021 17:36:06 -0700 Subject: [PATCH 08/18] Expose constants as part of implementation --- strum_macros/src/lib.rs | 17 +++++++++----- strum_macros/src/macros/enum_index.rs | 32 ++++++++++++++++----------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index e4e7b5da..383f655a 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -376,11 +376,16 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Add a function to enum that allows accessing variants by index /// -/// The macro adds up to two standalone functions. The macro adds `index(idx: usize) -> Option` which -/// will use `Default::default()` for any additional data on the variant. For enums where there is -/// no additional data on any, a seocnd function `const_index(idx: usize) -> Option` is added. -/// This function is marked `const` allowing it to be used in a `const` context. Since -/// `Default::default()` is not `const` it is not possible to use it to accomodate additional data. +/// The macro adds up to two standalone functions as well as numeric constants for each variant. The +/// macro adds `index(idx: usize) -> Option` which will use `Default::default()` for any +/// additional data on the variant. +/// +/// For enums where there is no additional data on any, a second function +/// `const_index(idx: usize) -> Option` is added. This function is marked `const` allowing +/// it to be used in a `const` context. Since `Default::default()` is not `const` it is not possible +/// to define this function if there is additional data on any variant, therefore it is omitted +/// entirely. For these enums, a constant is also added for each variant of the form +/// `const {ENUM}_{VARIANT}: {repr_int_type} = {discriminant}` /// /// You cannot derive `EnumIndex` on any type with a lifetime bound (`<'a>`) because the function would surely /// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). @@ -415,6 +420,8 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// assert_eq!(None, Vehicle::const_index(2)); /// assert_eq!(Some(Vehicle::Truck), Vehicle::const_index(3)); /// assert_eq!(None, Vehicle::const_index(4)); +/// assert_eq!(VEHICLE_CAR, 1); +/// assert_eq!(VEHICLE_TRUCK, 3); /// ``` #[proc_macro_derive(EnumIndex, attributes(strum))] diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index 3b32de7c..ba0d6d04 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -126,11 +126,10 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { arms.push(quote! { _ => ::core::option::Option::None }); - let const_impl = if has_additional_data { - quote! {} - } else { - quote! { - #vis const fn const_index(idx: #index_type) -> Option<#name #gen> { + let nonconst_impl = quote! { + impl #name #gen { + fn index(idx: #index_type) -> Option<#name #gen> { + #(#const_defs)* match idx { #(#arms),* } @@ -138,17 +137,24 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } }; - Ok(quote! { - #(#const_defs)* - - impl #name #gen { - fn index(idx: #index_type) -> Option<#name #gen> { - match idx { - #(#arms),* + let const_impl = if has_additional_data { + quote! {} + } else { + quote! { + #(#const_defs)* + impl #name #gen { + #vis const fn const_index(idx: #index_type) -> Option<#name #gen> { + match idx { + #(#arms),* + } } } - #const_impl } + }; + + Ok(quote! { + #nonconst_impl + #const_impl }) } From 8a77028f6a5682650c4fdeb2469b697e45c94549 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Mon, 18 Oct 2021 17:43:15 -0700 Subject: [PATCH 09/18] Add discriminant error messages. Cargo fmt my code --- strum_macros/src/helpers/mod.rs | 7 +++++ strum_macros/src/lib.rs | 2 +- strum_macros/src/macros/enum_index.rs | 44 +++++++++++++-------------- strum_macros/src/macros/mod.rs | 2 +- strum_tests/tests/enum_index.rs | 4 +-- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 30787b28..294b18d0 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -15,6 +15,13 @@ pub fn non_enum_error() -> syn::Error { syn::Error::new(Span::call_site(), "This macro only supports enums.") } +pub fn non_integer_literal_discriminant_error() -> syn::Error { + syn::Error::new( + Span::call_site(), + "This macro only supports integer literal discriminants.", + ) +} + pub fn strum_discriminants_passthrough_error(span: impl Spanned) -> syn::Error { syn::Error::new( span.span(), diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 383f655a..293d3d55 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -384,7 +384,7 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// `const_index(idx: usize) -> Option` is added. This function is marked `const` allowing /// it to be used in a `const` context. Since `Default::default()` is not `const` it is not possible /// to define this function if there is additional data on any variant, therefore it is omitted -/// entirely. For these enums, a constant is also added for each variant of the form +/// entirely. For these enums, a constant is also added for each variant of the form /// `const {ENUM}_{VARIANT}: {repr_int_type} = {discriminant}` /// /// You cannot derive `EnumIndex` on any type with a lifetime bound (`<'a>`) because the function would surely diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index ba0d6d04..cde96ca8 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -1,8 +1,10 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{Data, DeriveInput, PathArguments, Type, TypeParen, Ident}; +use syn::{Data, DeriveInput, Ident, PathArguments, Type, TypeParen}; -use crate::helpers::{non_enum_error, HasStrumVariantProperties}; +use crate::helpers::{ + non_enum_error, non_integer_literal_discriminant_error, HasStrumVariantProperties, +}; pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; @@ -15,38 +17,33 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let path = &attr.path; let tokens = &attr.tokens; if path.leading_colon.is_some() { - continue + continue; } if path.segments.len() != 1 { - continue + continue; } let segment = path.segments.first().unwrap(); if segment.ident != "repr" { - continue + continue; } if segment.arguments != PathArguments::None { - continue + continue; } let typ_paren = match syn::parse2::(tokens.clone()) { - Ok(Type::Paren(TypeParen { - elem, - .. - })) => *elem, - _ => { - continue - } + Ok(Type::Paren(TypeParen { elem, .. })) => *elem, + _ => continue, }; let inner_path = match &typ_paren { Type::Path(t) => t, - _ => { - continue - } + _ => continue, }; if let Some(seg) = inner_path.path.segments.last() { - for t in &["u8","u16","u32","u64","usize","i8","i16","i32","i64","isize"] { + for t in &[ + "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize", + ] { if seg.ident == t { index_type = typ_paren; - break + break; } } } @@ -96,20 +93,23 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { }; use heck::ShoutySnakeCase; - let const_var_name = format!("{}{}",name, variant.ident.to_string()).to_shouty_snake_case(); + let const_var_name = + format!("{}{}", name, variant.ident.to_string()).to_shouty_snake_case(); let const_var_ident = syn::parse_str::(&const_var_name).unwrap(); let mut discriminant_found = false; - if let Some((_eq,expr)) = &variant.discriminant { + if let Some((_eq, expr)) = &variant.discriminant { if let syn::Expr::Lit(expr_lit) = expr { if let syn::Lit::Int(int_lit) = &expr_lit.lit { const_defs.push(quote! {pub const #const_var_ident: #index_type = #int_lit;}); discriminant_found = true; } else { - panic!(); + // Not sure if this is even reachable + return Err(non_integer_literal_discriminant_error()); } } else { - panic!(); + // Not sure if this is even reachable + return Err(non_integer_literal_discriminant_error()); } } if !discriminant_found { diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index 93f879e9..2515f702 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -1,6 +1,6 @@ -pub mod enum_index; pub mod enum_count; pub mod enum_discriminants; +pub mod enum_index; pub mod enum_iter; pub mod enum_messages; pub mod enum_properties; diff --git a/strum_tests/tests/enum_index.rs b/strum_tests/tests/enum_index.rs index 9058fd20..c23e867c 100644 --- a/strum_tests/tests/enum_index.rs +++ b/strum_tests/tests/enum_index.rs @@ -1,4 +1,4 @@ -use strum::{EnumIndex}; +use strum::EnumIndex; #[derive(Debug, EnumIndex, PartialEq)] #[repr(u8)] @@ -9,7 +9,7 @@ enum Week { Wednesday, Thursday, Friday, - Saturday=8, + Saturday = 8, } #[test] From 2a14e88dc60f8e572b321eb456afb200539188da Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Wed, 20 Oct 2021 08:36:51 -0700 Subject: [PATCH 10/18] Add rustversion to make compilation conditional on 1.46 --- strum/src/lib.rs | 1 + strum_macros/Cargo.toml | 1 + strum_macros/src/lib.rs | 24 ++++++++++++++++-------- strum_macros/src/macros/enum_index.rs | 19 ++++++++++++++----- strum_tests/Cargo.toml | 3 ++- strum_tests/tests/enum_index.rs | 16 +++++++++++++--- 6 files changed, 47 insertions(+), 17 deletions(-) diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 9f7b9de6..4691529d 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -212,6 +212,7 @@ DocumentMacroRexports! { Display, EnumCount, EnumDiscriminants, + EnumIndex, EnumIter, EnumMessage, EnumProperty, diff --git a/strum_macros/Cargo.toml b/strum_macros/Cargo.toml index ee84210a..fcf643c6 100644 --- a/strum_macros/Cargo.toml +++ b/strum_macros/Cargo.toml @@ -22,6 +22,7 @@ name = "strum_macros" heck = "0.3" proc-macro2 = "1.0" quote = "1.0" +rustversion = "1.0" syn = { version = "1.0", features = ["parsing", "extra-traits"] } [dev-dependencies] diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 293d3d55..85486521 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -380,7 +380,8 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// macro adds `index(idx: usize) -> Option` which will use `Default::default()` for any /// additional data on the variant. /// -/// For enums where there is no additional data on any, a second function +/// For rust compiler versions >= 1.46 where there is no additional data on any of the enum +/// variants, a second function /// `const_index(idx: usize) -> Option` is added. This function is marked `const` allowing /// it to be used in a `const` context. Since `Default::default()` is not `const` it is not possible /// to define this function if there is additional data on any variant, therefore it is omitted @@ -415,13 +416,20 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Truck = 3, /// } /// -/// assert_eq!(None, Vehicle::const_index(0)); -/// assert_eq!(Some(Vehicle::Car), Vehicle::const_index(1)); -/// assert_eq!(None, Vehicle::const_index(2)); -/// assert_eq!(Some(Vehicle::Truck), Vehicle::const_index(3)); -/// assert_eq!(None, Vehicle::const_index(4)); -/// assert_eq!(VEHICLE_CAR, 1); -/// assert_eq!(VEHICLE_TRUCK, 3); +/// assert_eq!(None, Vehicle::index(0)); +/// #[rustversion::before(1.46)] +/// fn const_test() { } +/// #[rustversion::since(1.46)] +/// fn const_test() { +/// assert_eq!(None, Vehicle::const_index(0)); +/// assert_eq!(Some(Vehicle::Car), Vehicle::const_index(1)); +/// assert_eq!(None, Vehicle::const_index(2)); +/// assert_eq!(Some(Vehicle::Truck), Vehicle::const_index(3)); +/// assert_eq!(None, Vehicle::const_index(4)); +/// assert_eq!(VEHICLE_CAR, 1); +/// assert_eq!(VEHICLE_TRUCK, 3); +/// } +/// const_test() /// ``` #[proc_macro_derive(EnumIndex, attributes(strum))] diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index cde96ca8..1a504319 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -6,6 +6,19 @@ use crate::helpers::{ non_enum_error, non_integer_literal_discriminant_error, HasStrumVariantProperties, }; +/// This ignores the const_impl before 1.46 since the const_impl does not compile +#[rustversion::before(1.46)] +fn combine_impls(nonconst_impl: TokenStream, _const_impl: TokenStream) -> TokenStream { + nonconst_impl +} + +#[rustversion::since(1.46)] +fn combine_impls(nonconst_impl: TokenStream, const_impl: TokenStream) -> TokenStream { + let mut combined_impl = nonconst_impl; + combined_impl.extend(const_impl); + combined_impl +} + pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; @@ -152,9 +165,5 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } }; - Ok(quote! { - #nonconst_impl - - #const_impl - }) + Ok(combine_impls(nonconst_impl, const_impl)) } diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml index a4b3470e..55fcf903 100644 --- a/strum_tests/Cargo.toml +++ b/strum_tests/Cargo.toml @@ -9,8 +9,9 @@ strum = { path = "../strum", features = ["derive"] } strum_macros = { path = "../strum_macros", features = [] } clap = "2.33.0" enum_variant_type = "=0.2.0" +rustversion = "1.0" structopt = "0.2.18" bitflags = "=1.2" [build-dependencies] -version_check = "0.9.2" \ No newline at end of file +version_check = "0.9.2" diff --git a/strum_tests/tests/enum_index.rs b/strum_tests/tests/enum_index.rs index c23e867c..4e2ac85d 100644 --- a/strum_tests/tests/enum_index.rs +++ b/strum_tests/tests/enum_index.rs @@ -14,6 +14,16 @@ enum Week { #[test] fn simple_test() { + assert_eq!(Week::index(0), Some(Week::Sunday)); + assert_eq!(Week::index(1), Some(Week::Monday)); + assert_eq!(Week::index(7), None); + assert_eq!(Week::index(8), Some(Week::Saturday)); + assert_eq!(Week::index(9), None); +} + +#[rustversion::since(1.46)] +#[test] +fn const_test() { assert_eq!(Week::const_index(0), Some(Week::Sunday)); assert_eq!(Week::const_index(1), Some(Week::Monday)); assert_eq!(Week::const_index(7), None); @@ -41,7 +51,7 @@ fn crate_module_path_test() { Saturday, } - assert_eq!(Week::const_index(0), Some(Week::Sunday)); - assert_eq!(Week::const_index(6), Some(Week::Saturday)); - assert_eq!(Week::const_index(7), None); + assert_eq!(Week::index(0), Some(Week::Sunday)); + assert_eq!(Week::index(6), Some(Week::Saturday)); + assert_eq!(Week::index(7), None); } From 2e637f9a3171750519be7e830476e5fee3987ce9 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Wed, 20 Oct 2021 08:54:22 -0700 Subject: [PATCH 11/18] Handle expr discriminants --- strum_macros/src/helpers/mod.rs | 7 ------- strum_macros/src/macros/enum_index.rs | 18 +++--------------- strum_tests/tests/enum_index.rs | 8 +++++--- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/strum_macros/src/helpers/mod.rs b/strum_macros/src/helpers/mod.rs index 294b18d0..30787b28 100644 --- a/strum_macros/src/helpers/mod.rs +++ b/strum_macros/src/helpers/mod.rs @@ -15,13 +15,6 @@ pub fn non_enum_error() -> syn::Error { syn::Error::new(Span::call_site(), "This macro only supports enums.") } -pub fn non_integer_literal_discriminant_error() -> syn::Error { - syn::Error::new( - Span::call_site(), - "This macro only supports integer literal discriminants.", - ) -} - pub fn strum_discriminants_passthrough_error(span: impl Spanned) -> syn::Error { syn::Error::new( span.span(), diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index 1a504319..9afef275 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -2,9 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Data, DeriveInput, Ident, PathArguments, Type, TypeParen}; -use crate::helpers::{ - non_enum_error, non_integer_literal_discriminant_error, HasStrumVariantProperties, -}; +use crate::helpers::{non_enum_error, HasStrumVariantProperties}; /// This ignores the const_impl before 1.46 since the const_impl does not compile #[rustversion::before(1.46)] @@ -112,18 +110,8 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let mut discriminant_found = false; if let Some((_eq, expr)) = &variant.discriminant { - if let syn::Expr::Lit(expr_lit) = expr { - if let syn::Lit::Int(int_lit) = &expr_lit.lit { - const_defs.push(quote! {pub const #const_var_ident: #index_type = #int_lit;}); - discriminant_found = true; - } else { - // Not sure if this is even reachable - return Err(non_integer_literal_discriminant_error()); - } - } else { - // Not sure if this is even reachable - return Err(non_integer_literal_discriminant_error()); - } + const_defs.push(quote! {pub const #const_var_ident: #index_type = #expr;}); + discriminant_found = true; } if !discriminant_found { if let Some(prev) = &prev_const_var_ident { diff --git a/strum_tests/tests/enum_index.rs b/strum_tests/tests/enum_index.rs index 4e2ac85d..26e7a19f 100644 --- a/strum_tests/tests/enum_index.rs +++ b/strum_tests/tests/enum_index.rs @@ -8,7 +8,7 @@ enum Week { Tuesday, Wednesday, Thursday, - Friday, + Friday = 4 + 3, Saturday = 8, } @@ -16,7 +16,8 @@ enum Week { fn simple_test() { assert_eq!(Week::index(0), Some(Week::Sunday)); assert_eq!(Week::index(1), Some(Week::Monday)); - assert_eq!(Week::index(7), None); + assert_eq!(Week::index(6), None); + assert_eq!(Week::index(7), Some(Week::Friday)); assert_eq!(Week::index(8), Some(Week::Saturday)); assert_eq!(Week::index(9), None); } @@ -26,7 +27,8 @@ fn simple_test() { fn const_test() { assert_eq!(Week::const_index(0), Some(Week::Sunday)); assert_eq!(Week::const_index(1), Some(Week::Monday)); - assert_eq!(Week::const_index(7), None); + assert_eq!(Week::const_index(6), None); + assert_eq!(Week::const_index(7), Some(Week::Friday)); assert_eq!(Week::const_index(8), Some(Week::Saturday)); assert_eq!(Week::const_index(9), None); } From f3411a7d0943ac902300f0dd7ba1426443e88289 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Wed, 20 Oct 2021 08:58:58 -0700 Subject: [PATCH 12/18] Fix generics handling --- strum_macros/src/macros/enum_index.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index 9afef275..fad7b6f9 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -20,6 +20,7 @@ fn combine_impls(nonconst_impl: TokenStream, const_impl: TokenStream) -> TokenSt pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; + let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); let vis = &ast.vis; let attrs = &ast.attrs; @@ -128,7 +129,7 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { arms.push(quote! { _ => ::core::option::Option::None }); let nonconst_impl = quote! { - impl #name #gen { + impl #impl_generics #name #ty_generics #where_clause { fn index(idx: #index_type) -> Option<#name #gen> { #(#const_defs)* match idx { @@ -143,8 +144,8 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { } else { quote! { #(#const_defs)* - impl #name #gen { - #vis const fn const_index(idx: #index_type) -> Option<#name #gen> { + impl #impl_generics #name #ty_generics #where_clause { + #vis const fn const_index(idx: #index_type) -> Option<#name #ty_generics> { match idx { #(#arms),* } From 48313162987e05b89b4f596f7513cd55ad885800 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Wed, 20 Oct 2021 09:16:34 -0700 Subject: [PATCH 13/18] Make constants always available. No need to only expose them when const_index is defined --- strum_macros/src/lib.rs | 19 ++++++++++--------- strum_macros/src/macros/enum_index.rs | 12 ++++++------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 85486521..d1ccbbe7 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -378,15 +378,16 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// /// The macro adds up to two standalone functions as well as numeric constants for each variant. The /// macro adds `index(idx: usize) -> Option` which will use `Default::default()` for any -/// additional data on the variant. -/// -/// For rust compiler versions >= 1.46 where there is no additional data on any of the enum -/// variants, a second function -/// `const_index(idx: usize) -> Option` is added. This function is marked `const` allowing -/// it to be used in a `const` context. Since `Default::default()` is not `const` it is not possible -/// to define this function if there is additional data on any variant, therefore it is omitted -/// entirely. For these enums, a constant is also added for each variant of the form -/// `const {ENUM}_{VARIANT}: {repr_int_type} = {discriminant}` +/// additional data on the variant. `idx` follows the same rules as the discriminant numbering, +/// where it starts at 0 and subsequent variants are incremented by 1 over the previous variant, +/// except for variants where the discriminant is specified. These indices are also made available +/// as constants of the form `const {ENUM}_{VARIANT}: {repr_int_type} = {discriminant}` +/// +/// For rust compiler versions >= 1.46 where there is no additional data on any of the enum variants, +/// a second function `const_index(idx: usize) -> Option` is added. This function is +/// marked `const` allowing it to be used in a `const` context. Since `Default::default()` is not +/// `const` it is not possible to define this function if there is additional data on any variant, +/// therefore it is omitted entirely. /// /// You cannot derive `EnumIndex` on any type with a lifetime bound (`<'a>`) because the function would surely /// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/enum_index.rs index fad7b6f9..82c5aeae 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/enum_index.rs @@ -75,7 +75,7 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { }; let mut arms = Vec::new(); - let mut const_defs = Vec::new(); + let mut constant_defs = Vec::new(); let mut has_additional_data = false; let mut prev_const_var_ident = None; for variant in variants { @@ -111,14 +111,14 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let mut discriminant_found = false; if let Some((_eq, expr)) = &variant.discriminant { - const_defs.push(quote! {pub const #const_var_ident: #index_type = #expr;}); + constant_defs.push(quote! {pub const #const_var_ident: #index_type = #expr;}); discriminant_found = true; } if !discriminant_found { if let Some(prev) = &prev_const_var_ident { - const_defs.push(quote! {pub const #const_var_ident: #index_type = #prev + 1;}); + constant_defs.push(quote! {pub const #const_var_ident: #index_type = #prev + 1;}); } else { - const_defs.push(quote! {pub const #const_var_ident: #index_type = 0;}); + constant_defs.push(quote! {pub const #const_var_ident: #index_type = 0;}); } } @@ -129,9 +129,10 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { arms.push(quote! { _ => ::core::option::Option::None }); let nonconst_impl = quote! { + #(#constant_defs)* + impl #impl_generics #name #ty_generics #where_clause { fn index(idx: #index_type) -> Option<#name #gen> { - #(#const_defs)* match idx { #(#arms),* } @@ -143,7 +144,6 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { quote! {} } else { quote! { - #(#const_defs)* impl #impl_generics #name #ty_generics #where_clause { #vis const fn const_index(idx: #index_type) -> Option<#name #ty_generics> { match idx { From d0e799c954b53bd5a12e62878e931b58241979ca Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Mon, 25 Oct 2021 07:13:30 -0700 Subject: [PATCH 14/18] Change to FromDiscriminant. Only output a single function --- strum/src/lib.rs | 2 +- strum_macros/src/lib.rs | 80 ++++++++------- .../{enum_index.rs => from_discriminant.rs} | 97 ++++++++----------- strum_macros/src/macros/mod.rs | 2 +- strum_tests/tests/enum_index.rs | 59 ----------- strum_tests/tests/from_discriminant.rs | 63 ++++++++++++ 6 files changed, 149 insertions(+), 154 deletions(-) rename strum_macros/src/macros/{enum_index.rs => from_discriminant.rs} (57%) delete mode 100644 strum_tests/tests/enum_index.rs create mode 100644 strum_tests/tests/from_discriminant.rs diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 4691529d..990b244d 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -212,12 +212,12 @@ DocumentMacroRexports! { Display, EnumCount, EnumDiscriminants, - EnumIndex, EnumIter, EnumMessage, EnumProperty, EnumString, EnumVariantNames, + FromDiscriminant, IntoStaticStr, ToString } diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index d1ccbbe7..b07603d1 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -374,29 +374,33 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { toks.into() } -/// Add a function to enum that allows accessing variants by index -/// -/// The macro adds up to two standalone functions as well as numeric constants for each variant. The -/// macro adds `index(idx: usize) -> Option` which will use `Default::default()` for any -/// additional data on the variant. `idx` follows the same rules as the discriminant numbering, -/// where it starts at 0 and subsequent variants are incremented by 1 over the previous variant, -/// except for variants where the discriminant is specified. These indices are also made available -/// as constants of the form `const {ENUM}_{VARIANT}: {repr_int_type} = {discriminant}` -/// -/// For rust compiler versions >= 1.46 where there is no additional data on any of the enum variants, -/// a second function `const_index(idx: usize) -> Option` is added. This function is -/// marked `const` allowing it to be used in a `const` context. Since `Default::default()` is not -/// `const` it is not possible to define this function if there is additional data on any variant, -/// therefore it is omitted entirely. -/// -/// You cannot derive `EnumIndex` on any type with a lifetime bound (`<'a>`) because the function would surely +/// Add a function to enum that allows accessing variants by its discriminant +/// +/// This macro adds a standalone function to obtain an enum variant by its discriminant. The macro adds +/// `from_discriminant(discriminant: usize) -> Option` as a standalone function on the enum. For +/// variants with additional data, the returned variant will use the `Default` trait to fill the +/// data. The discriminant follows the same rules as `rustc`. The first discriminant is zero and each +/// successive variant has a discriminant of one greater than the previous variant, expect where an +/// explicit discriminant is specified. The type of the discriminant will match the `repr` type if +/// it is specifed. +/// +/// The macro also adds numeric constants for each variant equal to its discriminant. The form for +/// the constant is `YourEnum::{VARIANT}_DISCRIMINANT` where `{VARIANT}` is replaced by the shouty +/// snake case version of the variant name. +/// +/// When the macro is applied using rustc >= 1.46 and when there is no additional data on any of +/// the variants, the `from_discriminant` function is marked `const`. rustc >= 1.46 is required +/// to allow `match` statements in `const fn`. The no additional data requirement is due to the +/// inability to use `Default::default()` in a `const fn`. +/// +/// You cannot derive `FromDiscriminant` on any type with a lifetime bound (`<'a>`) because the function would surely /// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). /// /// ``` /// -/// use strum_macros::EnumIndex; +/// use strum_macros::FromDiscriminant; /// -/// #[derive(EnumIndex, Debug, PartialEq)] +/// #[derive(FromDiscriminant, Debug, PartialEq)] /// enum Color { /// Red, /// Green { range: usize }, @@ -404,41 +408,45 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Yellow, /// } /// -/// assert_eq!(Some(Color::Red), Color::index(0)); -/// assert_eq!(Some(Color::Green {range: 0}), Color::index(1)); -/// assert_eq!(Some(Color::Blue(0)), Color::index(2)); -/// assert_eq!(Some(Color::Yellow), Color::index(3)); -/// assert_eq!(None, Color::index(4)); +/// assert_eq!(Some(Color::Red), Color::from_discriminant(0)); +/// assert_eq!(Some(Color::Green {range: 0}), Color::from_discriminant(1)); +/// assert_eq!(Some(Color::Blue(0)), Color::from_discriminant(2)); +/// assert_eq!(Some(Color::Yellow), Color::from_discriminant(3)); +/// assert_eq!(None, Color::from_discriminant(4)); /// -/// #[derive(EnumIndex, Debug, PartialEq)] +/// #[derive(FromDiscriminant, Debug, PartialEq)] /// #[repr(u8)] /// enum Vehicle { /// Car = 1, /// Truck = 3, /// } /// -/// assert_eq!(None, Vehicle::index(0)); +/// assert_eq!(None, Vehicle::from_discriminant(0)); /// #[rustversion::before(1.46)] /// fn const_test() { } /// #[rustversion::since(1.46)] /// fn const_test() { -/// assert_eq!(None, Vehicle::const_index(0)); -/// assert_eq!(Some(Vehicle::Car), Vehicle::const_index(1)); -/// assert_eq!(None, Vehicle::const_index(2)); -/// assert_eq!(Some(Vehicle::Truck), Vehicle::const_index(3)); -/// assert_eq!(None, Vehicle::const_index(4)); -/// assert_eq!(VEHICLE_CAR, 1); -/// assert_eq!(VEHICLE_TRUCK, 3); +/// // This is to test that it works in a const fn +/// const fn from_discriminant(discriminant: u8) -> Option { +/// Vehicle::from_discriminant(discriminant) +/// } +/// assert_eq!(None, from_discriminant(0)); +/// assert_eq!(Some(Vehicle::Car), from_discriminant(1)); +/// assert_eq!(None, from_discriminant(2)); +/// assert_eq!(Some(Vehicle::Truck), from_discriminant(3)); +/// assert_eq!(None, from_discriminant(4)); +/// assert_eq!(Vehicle::CAR_DISCRIMINANT, 1); +/// assert_eq!(Vehicle::TRUCK_DISCRIMINANT, 3); /// } /// const_test() /// ``` -#[proc_macro_derive(EnumIndex, attributes(strum))] -pub fn enum_index(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +#[proc_macro_derive(FromDiscriminant, attributes(strum))] +pub fn from_discriminant(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(input as DeriveInput); - let toks = - macros::enum_index::enum_index_inner(&ast).unwrap_or_else(|err| err.to_compile_error()); + let toks = macros::from_discriminant::from_discriminant_inner(&ast) + .unwrap_or_else(|err| err.to_compile_error()); debug_print_generated(&ast, &toks); toks.into() } diff --git a/strum_macros/src/macros/enum_index.rs b/strum_macros/src/macros/from_discriminant.rs similarity index 57% rename from strum_macros/src/macros/enum_index.rs rename to strum_macros/src/macros/from_discriminant.rs index 82c5aeae..f79811d6 100644 --- a/strum_macros/src/macros/enum_index.rs +++ b/strum_macros/src/macros/from_discriminant.rs @@ -1,30 +1,17 @@ use proc_macro2::{Span, TokenStream}; -use quote::quote; -use syn::{Data, DeriveInput, Ident, PathArguments, Type, TypeParen}; +use quote::{format_ident, quote}; +use syn::{Data, DeriveInput, PathArguments, Type, TypeParen}; use crate::helpers::{non_enum_error, HasStrumVariantProperties}; -/// This ignores the const_impl before 1.46 since the const_impl does not compile -#[rustversion::before(1.46)] -fn combine_impls(nonconst_impl: TokenStream, _const_impl: TokenStream) -> TokenStream { - nonconst_impl -} - -#[rustversion::since(1.46)] -fn combine_impls(nonconst_impl: TokenStream, const_impl: TokenStream) -> TokenStream { - let mut combined_impl = nonconst_impl; - combined_impl.extend(const_impl); - combined_impl -} - -pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { +pub fn from_discriminant_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); let vis = &ast.vis; let attrs = &ast.attrs; - let mut index_type: Type = syn::parse("usize".parse().unwrap()).unwrap(); + let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap(); for attr in attrs { let path = &attr.path; let tokens = &attr.tokens; @@ -54,7 +41,7 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize", ] { if seg.ident == t { - index_type = typ_paren; + discriminant_type = typ_paren; break; } } @@ -77,7 +64,7 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { let mut arms = Vec::new(); let mut constant_defs = Vec::new(); let mut has_additional_data = false; - let mut prev_const_var_ident = None; + let mut prev_qualified_var_name = None; for variant in variants { use syn::Fields::*; @@ -105,54 +92,50 @@ pub fn enum_index_inner(ast: &DeriveInput) -> syn::Result { }; use heck::ShoutySnakeCase; - let const_var_name = - format!("{}{}", name, variant.ident.to_string()).to_shouty_snake_case(); - let const_var_ident = syn::parse_str::(&const_var_name).unwrap(); - - let mut discriminant_found = false; - if let Some((_eq, expr)) = &variant.discriminant { - constant_defs.push(quote! {pub const #const_var_ident: #index_type = #expr;}); - discriminant_found = true; - } - if !discriminant_found { - if let Some(prev) = &prev_const_var_ident { - constant_defs.push(quote! {pub const #const_var_ident: #index_type = #prev + 1;}); - } else { - constant_defs.push(quote! {pub const #const_var_ident: #index_type = 0;}); - } - } + let const_var_str = format!("{}_DISCRIMINANT", variant.ident).to_shouty_snake_case(); + let const_var_ident = format_ident!("{}", const_var_str); + let qualified_var_name = quote! { Self::#const_var_ident }; + + let const_val_expr = match &variant.discriminant { + Some((_, expr)) => quote! { #expr }, + None => match &prev_qualified_var_name { + Some(prev) => quote! { #prev + 1 }, + None => quote! { 0 }, + }, + }; + + constant_defs + .push(quote! {pub const #const_var_ident: #discriminant_type = #const_val_expr;}); + arms.push(quote! {v if v == #qualified_var_name => ::core::option::Option::Some(#name::#ident #params)}); - arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)}); - prev_const_var_ident = Some(const_var_ident); + prev_qualified_var_name = Some(qualified_var_name); } arms.push(quote! { _ => ::core::option::Option::None }); - let nonconst_impl = quote! { - #(#constant_defs)* + let const_if_possible = if has_additional_data { + quote! {} + } else { + #[rustversion::before(1.46)] + fn filter_by_rust_version(s: TokenStream) -> TokenStream { + quote! {} + } - impl #impl_generics #name #ty_generics #where_clause { - fn index(idx: #index_type) -> Option<#name #gen> { - match idx { - #(#arms),* - } - } + #[rustversion::since(1.46)] + fn filter_by_rust_version(s: TokenStream) -> TokenStream { + s } + filter_by_rust_version(quote! { const }) }; - let const_impl = if has_additional_data { - quote! {} - } else { - quote! { - impl #impl_generics #name #ty_generics #where_clause { - #vis const fn const_index(idx: #index_type) -> Option<#name #ty_generics> { - match idx { - #(#arms),* - } + Ok(quote! { + impl #impl_generics #name #ty_generics #where_clause { + #(#constant_defs)* + #vis #const_if_possible fn from_discriminant(discriminant: #discriminant_type) -> Option<#name #ty_generics> { + match discriminant { + #(#arms),* } } } - }; - - Ok(combine_impls(nonconst_impl, const_impl)) + }) } diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index 2515f702..e6f2126a 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -1,10 +1,10 @@ pub mod enum_count; pub mod enum_discriminants; -pub mod enum_index; pub mod enum_iter; pub mod enum_messages; pub mod enum_properties; pub mod enum_variant_names; +pub mod from_discriminant; mod strings; diff --git a/strum_tests/tests/enum_index.rs b/strum_tests/tests/enum_index.rs deleted file mode 100644 index 26e7a19f..00000000 --- a/strum_tests/tests/enum_index.rs +++ /dev/null @@ -1,59 +0,0 @@ -use strum::EnumIndex; - -#[derive(Debug, EnumIndex, PartialEq)] -#[repr(u8)] -enum Week { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday = 4 + 3, - Saturday = 8, -} - -#[test] -fn simple_test() { - assert_eq!(Week::index(0), Some(Week::Sunday)); - assert_eq!(Week::index(1), Some(Week::Monday)); - assert_eq!(Week::index(6), None); - assert_eq!(Week::index(7), Some(Week::Friday)); - assert_eq!(Week::index(8), Some(Week::Saturday)); - assert_eq!(Week::index(9), None); -} - -#[rustversion::since(1.46)] -#[test] -fn const_test() { - assert_eq!(Week::const_index(0), Some(Week::Sunday)); - assert_eq!(Week::const_index(1), Some(Week::Monday)); - assert_eq!(Week::const_index(6), None); - assert_eq!(Week::const_index(7), Some(Week::Friday)); - assert_eq!(Week::const_index(8), Some(Week::Saturday)); - assert_eq!(Week::const_index(9), None); -} - -#[test] -fn crate_module_path_test() { - pub mod nested { - pub mod module { - pub use strum; - } - } - - #[derive(Debug, EnumIndex, PartialEq)] - #[strum(crate = "nested::module::strum")] - enum Week { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - } - - assert_eq!(Week::index(0), Some(Week::Sunday)); - assert_eq!(Week::index(6), Some(Week::Saturday)); - assert_eq!(Week::index(7), None); -} diff --git a/strum_tests/tests/from_discriminant.rs b/strum_tests/tests/from_discriminant.rs new file mode 100644 index 00000000..49fae128 --- /dev/null +++ b/strum_tests/tests/from_discriminant.rs @@ -0,0 +1,63 @@ +use strum::FromDiscriminant; + +#[derive(Debug, FromDiscriminant, PartialEq)] +#[repr(u8)] +enum Week { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday = 4 + 3, + Saturday = 8, +} + +#[test] +fn simple_test() { + assert_eq!(Week::from_discriminant(0), Some(Week::Sunday)); + assert_eq!(Week::from_discriminant(1), Some(Week::Monday)); + assert_eq!(Week::from_discriminant(6), None); + assert_eq!(Week::from_discriminant(7), Some(Week::Friday)); + assert_eq!(Week::from_discriminant(8), Some(Week::Saturday)); + assert_eq!(Week::from_discriminant(9), None); +} + +#[rustversion::since(1.46)] +#[test] +fn const_test() { + // This is to test that it works in a const fn + const fn from_discriminant(discriminant: u8) -> Option { + Week::from_discriminant(discriminant) + } + assert_eq!(from_discriminant(0), Some(Week::Sunday)); + assert_eq!(from_discriminant(1), Some(Week::Monday)); + assert_eq!(from_discriminant(6), None); + assert_eq!(from_discriminant(7), Some(Week::Friday)); + assert_eq!(from_discriminant(8), Some(Week::Saturday)); + assert_eq!(from_discriminant(9), None); +} + +#[test] +fn crate_module_path_test() { + pub mod nested { + pub mod module { + pub use strum; + } + } + + #[derive(Debug, FromDiscriminant, PartialEq)] + #[strum(crate = "nested::module::strum")] + enum Week { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + } + + assert_eq!(Week::from_discriminant(0), Some(Week::Sunday)); + assert_eq!(Week::from_discriminant(6), Some(Week::Saturday)); + assert_eq!(Week::from_discriminant(7), None); +} From c3339204e6e7a3357b56b6c63a423eefbf1d56a2 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Mon, 25 Oct 2021 08:12:41 -0700 Subject: [PATCH 15/18] Don't make constants accessible --- strum_macros/src/lib.rs | 6 ------ strum_macros/src/macros/from_discriminant.rs | 13 ++++++------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index b07603d1..9db154e3 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -384,10 +384,6 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// explicit discriminant is specified. The type of the discriminant will match the `repr` type if /// it is specifed. /// -/// The macro also adds numeric constants for each variant equal to its discriminant. The form for -/// the constant is `YourEnum::{VARIANT}_DISCRIMINANT` where `{VARIANT}` is replaced by the shouty -/// snake case version of the variant name. -/// /// When the macro is applied using rustc >= 1.46 and when there is no additional data on any of /// the variants, the `from_discriminant` function is marked `const`. rustc >= 1.46 is required /// to allow `match` statements in `const fn`. The no additional data requirement is due to the @@ -435,8 +431,6 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// assert_eq!(None, from_discriminant(2)); /// assert_eq!(Some(Vehicle::Truck), from_discriminant(3)); /// assert_eq!(None, from_discriminant(4)); -/// assert_eq!(Vehicle::CAR_DISCRIMINANT, 1); -/// assert_eq!(Vehicle::TRUCK_DISCRIMINANT, 3); /// } /// const_test() /// ``` diff --git a/strum_macros/src/macros/from_discriminant.rs b/strum_macros/src/macros/from_discriminant.rs index f79811d6..7a18f22e 100644 --- a/strum_macros/src/macros/from_discriminant.rs +++ b/strum_macros/src/macros/from_discriminant.rs @@ -64,7 +64,7 @@ pub fn from_discriminant_inner(ast: &DeriveInput) -> syn::Result { let mut arms = Vec::new(); let mut constant_defs = Vec::new(); let mut has_additional_data = false; - let mut prev_qualified_var_name = None; + let mut prev_const_var_ident = None; for variant in variants { use syn::Fields::*; @@ -94,21 +94,20 @@ pub fn from_discriminant_inner(ast: &DeriveInput) -> syn::Result { use heck::ShoutySnakeCase; let const_var_str = format!("{}_DISCRIMINANT", variant.ident).to_shouty_snake_case(); let const_var_ident = format_ident!("{}", const_var_str); - let qualified_var_name = quote! { Self::#const_var_ident }; let const_val_expr = match &variant.discriminant { Some((_, expr)) => quote! { #expr }, - None => match &prev_qualified_var_name { + None => match &prev_const_var_ident { Some(prev) => quote! { #prev + 1 }, None => quote! { 0 }, }, }; constant_defs - .push(quote! {pub const #const_var_ident: #discriminant_type = #const_val_expr;}); - arms.push(quote! {v if v == #qualified_var_name => ::core::option::Option::Some(#name::#ident #params)}); + .push(quote! {const #const_var_ident: #discriminant_type = #const_val_expr;}); + arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)}); - prev_qualified_var_name = Some(qualified_var_name); + prev_const_var_ident = Some(const_var_ident); } arms.push(quote! { _ => ::core::option::Option::None }); @@ -130,8 +129,8 @@ pub fn from_discriminant_inner(ast: &DeriveInput) -> syn::Result { Ok(quote! { impl #impl_generics #name #ty_generics #where_clause { - #(#constant_defs)* #vis #const_if_possible fn from_discriminant(discriminant: #discriminant_type) -> Option<#name #ty_generics> { + #(#constant_defs)* match discriminant { #(#arms),* } From d2bb9f7c8f18d202750a000588506cf5562ca573 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Thu, 28 Oct 2021 09:51:29 -0700 Subject: [PATCH 16/18] Make rustversion a dev dependency in strum-tests due to upstream change --- strum_tests/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/strum_tests/Cargo.toml b/strum_tests/Cargo.toml index 55fcf903..28664a28 100644 --- a/strum_tests/Cargo.toml +++ b/strum_tests/Cargo.toml @@ -9,9 +9,11 @@ strum = { path = "../strum", features = ["derive"] } strum_macros = { path = "../strum_macros", features = [] } clap = "2.33.0" enum_variant_type = "=0.2.0" -rustversion = "1.0" structopt = "0.2.18" bitflags = "=1.2" [build-dependencies] version_check = "0.9.2" + +[dev-dependencies] +rustversion = "1.0" From f9bf15aeea4c017a87aed4e25375ea528152b4a7 Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Thu, 28 Oct 2021 10:27:22 -0700 Subject: [PATCH 17/18] Cleanup doc tests for const --- strum_macros/src/lib.rs | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index 9db154e3..c0111304 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -410,6 +410,7 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// assert_eq!(Some(Color::Yellow), Color::from_discriminant(3)); /// assert_eq!(None, Color::from_discriminant(4)); /// +/// // Custom discriminant tests /// #[derive(FromDiscriminant, Debug, PartialEq)] /// #[repr(u8)] /// enum Vehicle { @@ -418,22 +419,30 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// } /// /// assert_eq!(None, Vehicle::from_discriminant(0)); -/// #[rustversion::before(1.46)] -/// fn const_test() { } -/// #[rustversion::since(1.46)] -/// fn const_test() { -/// // This is to test that it works in a const fn -/// const fn from_discriminant(discriminant: u8) -> Option { -/// Vehicle::from_discriminant(discriminant) -/// } -/// assert_eq!(None, from_discriminant(0)); -/// assert_eq!(Some(Vehicle::Car), from_discriminant(1)); -/// assert_eq!(None, from_discriminant(2)); -/// assert_eq!(Some(Vehicle::Truck), from_discriminant(3)); -/// assert_eq!(None, from_discriminant(4)); -/// } -/// const_test() /// ``` +#[rustversion::attr(since(1.46),doc=" +`const` tests (only works in rust >= 1.46) +``` +use strum_macros::FromDiscriminant; + +#[derive(FromDiscriminant, Debug, PartialEq)] +#[repr(u8)] +enum Number { + One = 1, + Three = 3, +} + +// This test confirms that the function works in a `const` context +const fn number_from_discriminant(d: u8) -> Option { + Number::from_discriminant(d) +} +assert_eq!(None, number_from_discriminant(0)); +assert_eq!(Some(Number::One), number_from_discriminant(1)); +assert_eq!(None, number_from_discriminant(2)); +assert_eq!(Some(Number::Three), number_from_discriminant(3)); +assert_eq!(None, number_from_discriminant(4)); +``` +")] #[proc_macro_derive(FromDiscriminant, attributes(strum))] pub fn from_discriminant(input: proc_macro::TokenStream) -> proc_macro::TokenStream { From 16dda13dad89a0920bda2aabe5fcdcff12c0fa1b Mon Sep 17 00:00:00 2001 From: Andrew Burkett Date: Sun, 31 Oct 2021 08:49:24 -0700 Subject: [PATCH 18/18] Rename to FromRepr/from_repr --- strum/src/lib.rs | 2 +- strum_macros/src/lib.rs | 48 +++++++------- .../{from_discriminant.rs => from_repr.rs} | 4 +- strum_macros/src/macros/mod.rs | 2 +- strum_tests/tests/from_discriminant.rs | 63 ------------------- strum_tests/tests/from_repr.rs | 63 +++++++++++++++++++ 6 files changed, 91 insertions(+), 91 deletions(-) rename strum_macros/src/macros/{from_discriminant.rs => from_repr.rs} (95%) delete mode 100644 strum_tests/tests/from_discriminant.rs create mode 100644 strum_tests/tests/from_repr.rs diff --git a/strum/src/lib.rs b/strum/src/lib.rs index 990b244d..fe12b92c 100644 --- a/strum/src/lib.rs +++ b/strum/src/lib.rs @@ -217,7 +217,7 @@ DocumentMacroRexports! { EnumProperty, EnumString, EnumVariantNames, - FromDiscriminant, + FromRepr, IntoStaticStr, ToString } diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs index c0111304..ad3dabe5 100644 --- a/strum_macros/src/lib.rs +++ b/strum_macros/src/lib.rs @@ -377,7 +377,7 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Add a function to enum that allows accessing variants by its discriminant /// /// This macro adds a standalone function to obtain an enum variant by its discriminant. The macro adds -/// `from_discriminant(discriminant: usize) -> Option` as a standalone function on the enum. For +/// `from_repr(discriminant: usize) -> Option` as a standalone function on the enum. For /// variants with additional data, the returned variant will use the `Default` trait to fill the /// data. The discriminant follows the same rules as `rustc`. The first discriminant is zero and each /// successive variant has a discriminant of one greater than the previous variant, expect where an @@ -385,18 +385,18 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// it is specifed. /// /// When the macro is applied using rustc >= 1.46 and when there is no additional data on any of -/// the variants, the `from_discriminant` function is marked `const`. rustc >= 1.46 is required +/// the variants, the `from_repr` function is marked `const`. rustc >= 1.46 is required /// to allow `match` statements in `const fn`. The no additional data requirement is due to the /// inability to use `Default::default()` in a `const fn`. /// -/// You cannot derive `FromDiscriminant` on any type with a lifetime bound (`<'a>`) because the function would surely +/// You cannot derive `FromRepr` on any type with a lifetime bound (`<'a>`) because the function would surely /// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html). /// /// ``` /// -/// use strum_macros::FromDiscriminant; +/// use strum_macros::FromRepr; /// -/// #[derive(FromDiscriminant, Debug, PartialEq)] +/// #[derive(FromRepr, Debug, PartialEq)] /// enum Color { /// Red, /// Green { range: usize }, @@ -404,28 +404,28 @@ pub fn enum_iter(input: proc_macro::TokenStream) -> proc_macro::TokenStream { /// Yellow, /// } /// -/// assert_eq!(Some(Color::Red), Color::from_discriminant(0)); -/// assert_eq!(Some(Color::Green {range: 0}), Color::from_discriminant(1)); -/// assert_eq!(Some(Color::Blue(0)), Color::from_discriminant(2)); -/// assert_eq!(Some(Color::Yellow), Color::from_discriminant(3)); -/// assert_eq!(None, Color::from_discriminant(4)); +/// assert_eq!(Some(Color::Red), Color::from_repr(0)); +/// assert_eq!(Some(Color::Green {range: 0}), Color::from_repr(1)); +/// assert_eq!(Some(Color::Blue(0)), Color::from_repr(2)); +/// assert_eq!(Some(Color::Yellow), Color::from_repr(3)); +/// assert_eq!(None, Color::from_repr(4)); /// /// // Custom discriminant tests -/// #[derive(FromDiscriminant, Debug, PartialEq)] +/// #[derive(FromRepr, Debug, PartialEq)] /// #[repr(u8)] /// enum Vehicle { /// Car = 1, /// Truck = 3, /// } /// -/// assert_eq!(None, Vehicle::from_discriminant(0)); +/// assert_eq!(None, Vehicle::from_repr(0)); /// ``` #[rustversion::attr(since(1.46),doc=" `const` tests (only works in rust >= 1.46) ``` -use strum_macros::FromDiscriminant; +use strum_macros::FromRepr; -#[derive(FromDiscriminant, Debug, PartialEq)] +#[derive(FromRepr, Debug, PartialEq)] #[repr(u8)] enum Number { One = 1, @@ -433,22 +433,22 @@ enum Number { } // This test confirms that the function works in a `const` context -const fn number_from_discriminant(d: u8) -> Option { - Number::from_discriminant(d) +const fn number_from_repr(d: u8) -> Option { + Number::from_repr(d) } -assert_eq!(None, number_from_discriminant(0)); -assert_eq!(Some(Number::One), number_from_discriminant(1)); -assert_eq!(None, number_from_discriminant(2)); -assert_eq!(Some(Number::Three), number_from_discriminant(3)); -assert_eq!(None, number_from_discriminant(4)); +assert_eq!(None, number_from_repr(0)); +assert_eq!(Some(Number::One), number_from_repr(1)); +assert_eq!(None, number_from_repr(2)); +assert_eq!(Some(Number::Three), number_from_repr(3)); +assert_eq!(None, number_from_repr(4)); ``` ")] -#[proc_macro_derive(FromDiscriminant, attributes(strum))] -pub fn from_discriminant(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +#[proc_macro_derive(FromRepr, attributes(strum))] +pub fn from_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(input as DeriveInput); - let toks = macros::from_discriminant::from_discriminant_inner(&ast) + let toks = macros::from_repr::from_repr_inner(&ast) .unwrap_or_else(|err| err.to_compile_error()); debug_print_generated(&ast, &toks); toks.into() diff --git a/strum_macros/src/macros/from_discriminant.rs b/strum_macros/src/macros/from_repr.rs similarity index 95% rename from strum_macros/src/macros/from_discriminant.rs rename to strum_macros/src/macros/from_repr.rs index 7a18f22e..3ea6d1b8 100644 --- a/strum_macros/src/macros/from_discriminant.rs +++ b/strum_macros/src/macros/from_repr.rs @@ -4,7 +4,7 @@ use syn::{Data, DeriveInput, PathArguments, Type, TypeParen}; use crate::helpers::{non_enum_error, HasStrumVariantProperties}; -pub fn from_discriminant_inner(ast: &DeriveInput) -> syn::Result { +pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result { let name = &ast.ident; let gen = &ast.generics; let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); @@ -129,7 +129,7 @@ pub fn from_discriminant_inner(ast: &DeriveInput) -> syn::Result { Ok(quote! { impl #impl_generics #name #ty_generics #where_clause { - #vis #const_if_possible fn from_discriminant(discriminant: #discriminant_type) -> Option<#name #ty_generics> { + #vis #const_if_possible fn from_repr(discriminant: #discriminant_type) -> Option<#name #ty_generics> { #(#constant_defs)* match discriminant { #(#arms),* diff --git a/strum_macros/src/macros/mod.rs b/strum_macros/src/macros/mod.rs index e6f2126a..b4129697 100644 --- a/strum_macros/src/macros/mod.rs +++ b/strum_macros/src/macros/mod.rs @@ -4,7 +4,7 @@ pub mod enum_iter; pub mod enum_messages; pub mod enum_properties; pub mod enum_variant_names; -pub mod from_discriminant; +pub mod from_repr; mod strings; diff --git a/strum_tests/tests/from_discriminant.rs b/strum_tests/tests/from_discriminant.rs deleted file mode 100644 index 49fae128..00000000 --- a/strum_tests/tests/from_discriminant.rs +++ /dev/null @@ -1,63 +0,0 @@ -use strum::FromDiscriminant; - -#[derive(Debug, FromDiscriminant, PartialEq)] -#[repr(u8)] -enum Week { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday = 4 + 3, - Saturday = 8, -} - -#[test] -fn simple_test() { - assert_eq!(Week::from_discriminant(0), Some(Week::Sunday)); - assert_eq!(Week::from_discriminant(1), Some(Week::Monday)); - assert_eq!(Week::from_discriminant(6), None); - assert_eq!(Week::from_discriminant(7), Some(Week::Friday)); - assert_eq!(Week::from_discriminant(8), Some(Week::Saturday)); - assert_eq!(Week::from_discriminant(9), None); -} - -#[rustversion::since(1.46)] -#[test] -fn const_test() { - // This is to test that it works in a const fn - const fn from_discriminant(discriminant: u8) -> Option { - Week::from_discriminant(discriminant) - } - assert_eq!(from_discriminant(0), Some(Week::Sunday)); - assert_eq!(from_discriminant(1), Some(Week::Monday)); - assert_eq!(from_discriminant(6), None); - assert_eq!(from_discriminant(7), Some(Week::Friday)); - assert_eq!(from_discriminant(8), Some(Week::Saturday)); - assert_eq!(from_discriminant(9), None); -} - -#[test] -fn crate_module_path_test() { - pub mod nested { - pub mod module { - pub use strum; - } - } - - #[derive(Debug, FromDiscriminant, PartialEq)] - #[strum(crate = "nested::module::strum")] - enum Week { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday, - } - - assert_eq!(Week::from_discriminant(0), Some(Week::Sunday)); - assert_eq!(Week::from_discriminant(6), Some(Week::Saturday)); - assert_eq!(Week::from_discriminant(7), None); -} diff --git a/strum_tests/tests/from_repr.rs b/strum_tests/tests/from_repr.rs new file mode 100644 index 00000000..bb7263f6 --- /dev/null +++ b/strum_tests/tests/from_repr.rs @@ -0,0 +1,63 @@ +use strum::FromRepr; + +#[derive(Debug, FromRepr, PartialEq)] +#[repr(u8)] +enum Week { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday = 4 + 3, + Saturday = 8, +} + +#[test] +fn simple_test() { + assert_eq!(Week::from_repr(0), Some(Week::Sunday)); + assert_eq!(Week::from_repr(1), Some(Week::Monday)); + assert_eq!(Week::from_repr(6), None); + assert_eq!(Week::from_repr(7), Some(Week::Friday)); + assert_eq!(Week::from_repr(8), Some(Week::Saturday)); + assert_eq!(Week::from_repr(9), None); +} + +#[rustversion::since(1.46)] +#[test] +fn const_test() { + // This is to test that it works in a const fn + const fn from_repr(discriminant: u8) -> Option { + Week::from_repr(discriminant) + } + assert_eq!(from_repr(0), Some(Week::Sunday)); + assert_eq!(from_repr(1), Some(Week::Monday)); + assert_eq!(from_repr(6), None); + assert_eq!(from_repr(7), Some(Week::Friday)); + assert_eq!(from_repr(8), Some(Week::Saturday)); + assert_eq!(from_repr(9), None); +} + +#[test] +fn crate_module_path_test() { + pub mod nested { + pub mod module { + pub use strum; + } + } + + #[derive(Debug, FromRepr, PartialEq)] + #[strum(crate = "nested::module::strum")] + enum Week { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, + } + + assert_eq!(Week::from_repr(0), Some(Week::Sunday)); + assert_eq!(Week::from_repr(6), Some(Week::Saturday)); + assert_eq!(Week::from_repr(7), None); +}