Skip to content

Commit

Permalink
Merge branch 'Peternator7:master' into 352-const-into-str
Browse files Browse the repository at this point in the history
  • Loading branch information
biryukovmaxim authored Jun 19, 2024
2 parents c37398b + 7ab74c8 commit fbcd7d4
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 42 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.26.4 (strum_macros)

* [#360](/~https://github.com/Peternator7/strum/pull/360): Fixes bug introduced with new string interpolation feature where
unit structs took an implicit unnecessary dependency on `::core::alloc`.

## 0.26.3 (strum_macros)

* [#344](/~https://github.com/Peternator7/strum/pull/344): Hide `EnumTable` because it's going to be deprecated in the next
version.
* [#357](/~https://github.com/Peternator7/strum/pull/357): Fixes an incompatiblity with `itertools` by using the fully
qualified name rather than the inherent method.
* [#345](/~https://github.com/Peternator7/strum/pull/345): Allows unnamed tuple like variants to use their variants in
string interpolation. `#[strum(to_string = "Field 0: {0}, Field 1: {1})")]` will now work for tuple variants

## 0.26.2

* [#337](/~https://github.com/Peternator7/strum/pull/337): Fix missing generic impls for `EnumTryAs`
Expand Down
2 changes: 1 addition & 1 deletion strum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl std::error::Error for ParseError {
}

/// This trait designates that an `Enum` can be iterated over. It can
/// be auto generated using `strum_macros` on your behalf.
/// be auto generated using the [`EnumIter`](derive.EnumIter.html) derive macro.
///
/// # Example
///
Expand Down
2 changes: 1 addition & 1 deletion strum_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "strum_macros"
version = "0.26.2"
version = "0.26.4"
edition = "2018"
authors = ["Peter Glotfelty <peter.glotfelty@microsoft.com>"]
license = "MIT"
Expand Down
10 changes: 8 additions & 2 deletions strum_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
//! Strum is a set of macros and traits for working with
//! enums and strings easier in Rust.
//!
//! This crate only contains derive macros for use with the
//! [`strum`](https://docs.rs/strum)
//! crate. The macros provied by this crate are also available by
//! enabling the `derive` feature in aforementioned `strum` crate.
#![recursion_limit = "128"]

Expand Down Expand Up @@ -367,14 +371,16 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// 3. The name of the variant will be used if there are no `serialize` or `to_string` attributes.
/// 4. If the enum has a `strum(prefix = "some_value_")`, every variant will have that prefix prepended
/// to the serialization.
/// 5. Enums with named fields support named field interpolation. The value will be interpolated into the output string.
/// 5. Enums with fields support string interpolation.
/// Note this means the variant will not "round trip" if you then deserialize the string.
///
/// ```rust
/// #[derive(strum_macros::Display)]
/// pub enum Color {
/// #[strum(to_string = "saturation is {sat}")]
/// Red { sat: usize },
/// #[strum(to_string = "hue is {1}, saturation is {0}")]
/// Blue(usize, usize),
/// }
/// ```
///
Expand Down Expand Up @@ -426,7 +432,7 @@ pub fn display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// Creates a new type that iterates of the variants of an enum.
///
/// Iterate over the variants of an Enum. Any additional data on your variants will be set to `Default::default()`.
/// The macro implements `strum::IntoEnumIterator` on your enum and creates a new type called `YourEnumIter` that is the iterator object.
/// The macro implements [`strum::IntoEnumIterator`](https://docs.rs/strum/latest/strum/trait.IntoEnumIterator.html) on your enum and creates a new type called `YourEnumIter` that is the iterator object.
/// You cannot derive `EnumIter` on any type with a lifetime bound (`<'a>`) because the iterator would surely
/// create [unbounded lifetimes](https://doc.rust-lang.org/nightly/nomicon/unbounded-lifetimes.html).
///
Expand Down
4 changes: 2 additions & 2 deletions strum_macros/src/macros/enum_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
::core::option::Option::None
} else {
self.idx = idx;
self.get(idx - 1)
#iter_name::get(self, idx - 1)
}
}
}
Expand All @@ -154,7 +154,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
::core::option::Option::None
} else {
self.back_idx = back_idx;
self.get(#variant_count - self.back_idx)
#iter_name::get(self, #variant_count - self.back_idx)
}
}
}
Expand Down
125 changes: 90 additions & 35 deletions strum_macros/src/macros/strings/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,20 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {

let params = match variant.fields {
Fields::Unit => quote! {},
Fields::Unnamed(..) => quote! { (..) },
Fields::Unnamed(ref unnamed_fields) => {
// Transform unnamed params '(String, u8)' to '(ref field0, ref field1)'
let names: Punctuated<_, Token!(,)> = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(index, field)| {
assert!(field.ident.is_none());
let ident = syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap();
quote! { ref #ident }
})
.collect();
quote! { (#names) }
}
Fields::Named(ref field_names) => {
// Transform named params '{ name: String, age: u8 }' to '{ ref name, ref age }'
let names: Punctuated<TokenStream, Token!(,)> = field_names
Expand Down Expand Up @@ -58,33 +71,70 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}
}
} else {
let arm = if let Fields::Named(ref field_names) = variant.fields {
let used_vars = capture_format_string_idents(&output)?;
if used_vars.is_empty() {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
} else {
// Create args like 'name = name, age = age' for format macro
let args: Punctuated<_, Token!(,)> = field_names
.named
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref().unwrap();
// Only contain variables that are used in format string
if !used_vars.contains(ident) {
None
} else {
Some(quote! { #ident = #ident })
}
})
.collect();

quote! {
#[allow(unused_variables)]
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
let arm = match variant.fields {
Fields::Named(ref field_names) => {
let used_vars = capture_format_string_idents(&output)?;
if used_vars.is_empty() {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
} else {
// Create args like 'name = name, age = age' for format macro
let args: Punctuated<_, Token!(,)> = field_names
.named
.iter()
.filter_map(|field| {
let ident = field.ident.as_ref().unwrap();
// Only contain variables that are used in format string
if !used_vars.contains(ident) {
None
} else {
Some(quote! { #ident = #ident })
}
})
.collect();

quote! {
#[allow(unused_variables)]
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
}
}
},
Fields::Unnamed(ref unnamed_fields) => {
let used_vars = capture_format_strings(&output)?;
if used_vars.iter().any(String::is_empty) {
return Err(syn::Error::new_spanned(
&output,
"Empty {} is not allowed; Use manual numbering ({0})",
))
}
if used_vars.is_empty() {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
} else {
let args: Punctuated<_, Token!(,)> = unnamed_fields
.unnamed
.iter()
.enumerate()
.map(|(index, field)| {
assert!(field.ident.is_none());
syn::parse_str::<Ident>(format!("field{}", index).as_str()).unwrap()
})
.collect();
quote! {
#[allow(unused_variables)]
#name::#ident #params => ::core::fmt::Display::fmt(&format!(#output, #args), f)
}
}
}
} else {
quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
Fields::Unit => {
let used_vars = capture_format_strings(&output)?;
if !used_vars.is_empty() {
return Err(syn::Error::new_spanned(
&output,
"Unit variants do not support interpolation",
));
}

quote! { #name::#ident #params => ::core::fmt::Display::fmt(#output, f) }
}
};

arms.push(arm);
Expand All @@ -107,11 +157,22 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}

fn capture_format_string_idents(string_literal: &LitStr) -> syn::Result<Vec<Ident>> {
capture_format_strings(string_literal)?.into_iter().map(|ident| {
syn::parse_str::<Ident>(ident.as_str()).map_err(|_| {
syn::Error::new_spanned(
string_literal,
"Invalid identifier inside format string bracket",
)
})
}).collect()
}

fn capture_format_strings(string_literal: &LitStr) -> syn::Result<Vec<String>> {
// Remove escaped brackets
let format_str = string_literal.value().replace("{{", "").replace("}}", "");

let mut new_var_start_index: Option<usize> = None;
let mut var_used: Vec<Ident> = Vec::new();
let mut var_used = Vec::new();

for (i, chr) in format_str.bytes().enumerate() {
if chr == b'{' {
Expand All @@ -132,14 +193,8 @@ fn capture_format_string_idents(string_literal: &LitStr) -> syn::Result<Vec<Iden
))?;

let inside_brackets = &format_str[start_index + 1..i];
let ident_str = inside_brackets.split(":").next().unwrap();
let ident = syn::parse_str::<Ident>(ident_str).map_err(|_| {
syn::Error::new_spanned(
string_literal,
"Invalid identifier inside format string bracket",
)
})?;
var_used.push(ident);
let ident_str = inside_brackets.split(":").next().unwrap().trim_end();
var_used.push(ident_str.to_owned());
}
}

Expand Down
2 changes: 1 addition & 1 deletion strum_nostd_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod tests {
use core::str::FromStr;
use strum::EnumString;

#[derive(Debug, Eq, PartialEq, EnumString)]
#[derive(Debug, Eq, PartialEq, EnumString, strum::Display)]
enum Color {
Red,
Blue {
Expand Down
10 changes: 10 additions & 0 deletions strum_tests/tests/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ enum Color {
Purple { sat: usize },
#[strum(default)]
Green(String),
#[strum(to_string = "Orange({0})")]
Orange(usize),
}

#[test]
Expand Down Expand Up @@ -64,6 +66,14 @@ fn to_green_string() {
);
}

#[test]
fn to_orange_string() {
assert_eq!(
String::from("Orange(10)"),
Color::Orange(10).to_string().as_ref()
);
}

#[derive(Debug, Eq, PartialEq, EnumString, strum::Display)]
enum ColorWithDefaultAndToString {
#[strum(default, to_string = "GreenGreen")]
Expand Down

0 comments on commit fbcd7d4

Please sign in to comment.