Skip to content

Commit

Permalink
Fetch updates from upstream Strum
Browse files Browse the repository at this point in the history
  • Loading branch information
billy1624 committed Feb 6, 2024
1 parent 3f253c5 commit 32cf8f7
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 23 deletions.
17 changes: 9 additions & 8 deletions sea-orm-macros/src/strum/enum_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
#[allow(
missing_copy_implementations,
)]
#vis struct #iter_name #impl_generics {
#vis struct #iter_name #impl_generics {
idx: usize,
back_idx: usize,
marker: ::core::marker::PhantomData #phantom_data,
}
impl #impl_generics core::fmt::Debug for #iter_name #ty_generics #where_clause {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {

impl #impl_generics ::core::fmt::Debug for #iter_name #ty_generics #where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
// We don't know if the variants implement debug themselves so the only thing we
// can really show is how many elements are left.
f.debug_struct(#iter_name_debug_struct)
Expand All @@ -90,7 +91,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}

impl #impl_generics #iter_name #ty_generics #where_clause {
fn get(&self, idx: usize) -> Option<#name #ty_generics> {
fn get(&self, idx: usize) -> ::core::option::Option<#name #ty_generics> {
match idx {
#(#arms),*
}
Expand All @@ -111,16 +112,16 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
impl #impl_generics Iterator for #iter_name #ty_generics #where_clause {
type Item = #name #ty_generics;

fn next(&mut self) -> Option<<Self as Iterator>::Item> {
fn next(&mut self) -> ::core::option::Option<<Self as Iterator>::Item> {
self.nth(0)
}

fn size_hint(&self) -> (usize, Option<usize>) {
fn size_hint(&self) -> (usize, ::core::option::Option<usize>) {
let t = if self.idx + self.back_idx >= #variant_count { 0 } else { #variant_count - self.idx - self.back_idx };
(t, Some(t))
}

fn nth(&mut self, n: usize) -> Option<<Self as Iterator>::Item> {
fn nth(&mut self, n: usize) -> ::core::option::Option<<Self as Iterator>::Item> {
let idx = self.idx + n + 1;
if idx + self.back_idx > #variant_count {
// We went past the end of the iterator. Freeze idx at #variant_count
Expand All @@ -142,7 +143,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
}

impl #impl_generics DoubleEndedIterator for #iter_name #ty_generics #where_clause {
fn next_back(&mut self) -> Option<<Self as Iterator>::Item> {
fn next_back(&mut self) -> ::core::option::Option<<Self as Iterator>::Item> {
let back_idx = self.back_idx + 1;

if self.idx + back_idx > #variant_count {
Expand Down
79 changes: 70 additions & 9 deletions sea-orm-macros/src/strum/helpers/case_style.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use heck::{
ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToUpperCamelCase,
ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToTitleCase, ToTrainCase,
ToUpperCamelCase,
};
use std::str::FromStr;
use syn::{
Expand All @@ -20,6 +21,7 @@ pub enum CaseStyle {
LowerCase,
ScreamingKebabCase,
PascalCase,
TrainCase,
}

const VALID_CASE_STYLES: &[&str] = &[
Expand All @@ -33,6 +35,7 @@ const VALID_CASE_STYLES: &[&str] = &[
"UPPERCASE",
"title_case",
"mixed_case",
"Train-Case",
];

impl Parse for CaseStyle {
Expand All @@ -57,18 +60,21 @@ impl FromStr for CaseStyle {

fn from_str(text: &str) -> Result<Self, ()> {
Ok(match text {
"camel_case" | "PascalCase" => CaseStyle::PascalCase,
// "camel_case" is a soft-deprecated case-style left for backward compatibility.
// <https://github.com/Peternator7/strum/pull/250#issuecomment-1374682221>
"PascalCase" | "camel_case" => CaseStyle::PascalCase,
"camelCase" => CaseStyle::CamelCase,
"snake_case" | "snek_case" => CaseStyle::SnakeCase,
"kebab_case" | "kebab-case" => CaseStyle::KebabCase,
"kebab-case" | "kebab_case" => CaseStyle::KebabCase,
"SCREAMING-KEBAB-CASE" => CaseStyle::ScreamingKebabCase,
"shouty_snake_case" | "shouty_snek_case" | "SCREAMING_SNAKE_CASE" => {
"SCREAMING_SNAKE_CASE" | "shouty_snake_case" | "shouty_snek_case" => {
CaseStyle::ShoutySnakeCase
}
"title_case" => CaseStyle::TitleCase,
"mixed_case" => CaseStyle::MixedCase,
"lowercase" => CaseStyle::LowerCase,
"UPPERCASE" => CaseStyle::UpperCase,
"Train-Case" => CaseStyle::TrainCase,
_ => return Err(()),
})
}
Expand All @@ -92,6 +98,7 @@ impl CaseStyleHelpers for Ident {
CaseStyle::UpperCase => ident_string.to_uppercase(),
CaseStyle::LowerCase => ident_string.to_lowercase(),
CaseStyle::ScreamingKebabCase => ident_string.to_kebab_case().to_uppercase(),
CaseStyle::TrainCase => ident_string.to_train_case(),
CaseStyle::CamelCase => {
let camel_case = ident_string.to_upper_camel_case();
let mut pascal = String::with_capacity(camel_case.len());
Expand All @@ -109,9 +116,63 @@ impl CaseStyleHelpers for Ident {
}
}

#[test]
fn test_convert_case() {
let id = Ident::new("test_me", proc_macro2::Span::call_site());
assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase)));
assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase)));
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_convert_case() {
let id = Ident::new("test_me", proc_macro2::Span::call_site());
assert_eq!("testMe", id.convert_case(Some(CaseStyle::CamelCase)));
assert_eq!("TestMe", id.convert_case(Some(CaseStyle::PascalCase)));
assert_eq!("Test-Me", id.convert_case(Some(CaseStyle::TrainCase)));
}

#[test]
fn test_impl_from_str_for_case_style_pascal_case() {
use CaseStyle::*;
let f = CaseStyle::from_str;

assert_eq!(PascalCase, f("PascalCase").unwrap());
assert_eq!(PascalCase, f("camel_case").unwrap());

assert_eq!(CamelCase, f("camelCase").unwrap());

assert_eq!(SnakeCase, f("snake_case").unwrap());
assert_eq!(SnakeCase, f("snek_case").unwrap());

assert_eq!(KebabCase, f("kebab-case").unwrap());
assert_eq!(KebabCase, f("kebab_case").unwrap());

assert_eq!(ScreamingKebabCase, f("SCREAMING-KEBAB-CASE").unwrap());

assert_eq!(ShoutySnakeCase, f("SCREAMING_SNAKE_CASE").unwrap());
assert_eq!(ShoutySnakeCase, f("shouty_snake_case").unwrap());
assert_eq!(ShoutySnakeCase, f("shouty_snek_case").unwrap());

assert_eq!(LowerCase, f("lowercase").unwrap());

assert_eq!(UpperCase, f("UPPERCASE").unwrap());

assert_eq!(TitleCase, f("title_case").unwrap());

assert_eq!(MixedCase, f("mixed_case").unwrap());
}
}

/// heck doesn't treat numbers as new words, but this function does.
/// E.g. for input `Hello2You`, heck would output `hello2_you`, and snakify would output `hello_2_you`.
pub fn snakify(s: &str) -> String {
let mut output: Vec<char> = s.to_string().to_snake_case().chars().collect();
let mut num_starts = vec![];
for (pos, c) in output.iter().enumerate() {
if c.is_digit(10) && pos != 0 && !output[pos - 1].is_digit(10) {
num_starts.push(pos);
}
}
// need to do in reverse, because after inserting, all chars after the point of insertion are off
for i in num_starts.into_iter().rev() {
output.insert(i, '_')
}
output.into_iter().collect()
}
33 changes: 33 additions & 0 deletions sea-orm-macros/src/strum/helpers/inner_variant_props.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use super::metadata::{InnerVariantExt, InnerVariantMeta};
use super::occurrence_error;
use syn::{Field, LitStr};

pub trait HasInnerVariantProperties {
fn get_variant_inner_properties(&self) -> syn::Result<StrumInnerVariantProperties>;
}

#[derive(Clone, Eq, PartialEq, Debug, Default)]
pub struct StrumInnerVariantProperties {
pub default_with: Option<LitStr>,
}

impl HasInnerVariantProperties for Field {
fn get_variant_inner_properties(&self) -> syn::Result<StrumInnerVariantProperties> {
let mut output = StrumInnerVariantProperties { default_with: None };

let mut default_with_kw = None;
for meta in self.get_named_metadata()? {
match meta {
InnerVariantMeta::DefaultWith { kw, value } => {
if let Some(fst_kw) = default_with_kw {
return Err(occurrence_error(fst_kw, kw, "default_with"));
}
default_with_kw = Some(kw);
output.default_with = Some(value);
}
}
}

Ok(output)
}
}
60 changes: 57 additions & 3 deletions sea-orm-macros/src/strum/helpers/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use syn::{
parse::{Parse, ParseStream},
parse2, parse_str,
punctuated::Punctuated,
Attribute, DeriveInput, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, Path,
Token, Variant, Visibility,
Attribute, DeriveInput, Expr, ExprLit, Field, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue,
Path, Token, Variant, Visibility,
};

use super::case_style::CaseStyle;
Expand All @@ -17,6 +17,7 @@ pub mod kw {
// enum metadata
custom_keyword!(serialize_all);
custom_keyword!(use_phf);
custom_keyword!(prefix);

// enum discriminant metadata
custom_keyword!(derive);
Expand All @@ -30,6 +31,7 @@ pub mod kw {
custom_keyword!(to_string);
custom_keyword!(disabled);
custom_keyword!(default);
custom_keyword!(default_with);
custom_keyword!(props);
custom_keyword!(ascii_case_insensitive);
}
Expand All @@ -45,6 +47,10 @@ pub enum EnumMeta {
crate_module_path: Path,
},
UsePhf(kw::use_phf),
Prefix {
kw: kw::prefix,
prefix: LitStr,
},
}

impl Parse for EnumMeta {
Expand All @@ -69,6 +75,11 @@ impl Parse for EnumMeta {
Ok(EnumMeta::AsciiCaseInsensitive(input.parse()?))
} else if lookahead.peek(kw::use_phf) {
Ok(EnumMeta::UsePhf(input.parse()?))
} else if lookahead.peek(kw::prefix) {
let kw = input.parse::<kw::prefix>()?;
input.parse::<Token![=]>()?;
let prefix = input.parse()?;
Ok(EnumMeta::Prefix { kw, prefix })
} else {
Err(lookahead.error())
}
Expand Down Expand Up @@ -155,6 +166,10 @@ pub enum VariantMeta {
},
Disabled(kw::disabled),
Default(kw::default),
DefaultWith {
kw: kw::default_with,
value: LitStr,
},
AsciiCaseInsensitive {
kw: kw::ascii_case_insensitive,
value: bool,
Expand Down Expand Up @@ -192,6 +207,11 @@ impl Parse for VariantMeta {
Ok(VariantMeta::Disabled(input.parse()?))
} else if lookahead.peek(kw::default) {
Ok(VariantMeta::Default(input.parse()?))
} else if lookahead.peek(kw::default_with) {
let kw = input.parse()?;
let _: Token![=] = input.parse()?;
let value = input.parse()?;
Ok(VariantMeta::DefaultWith { kw, value })
} else if lookahead.peek(kw::ascii_case_insensitive) {
let kw = input.parse()?;
let value = if input.peek(Token![=]) {
Expand Down Expand Up @@ -243,7 +263,7 @@ impl VariantExt for Variant {
let result = get_metadata_inner("strum", &self.attrs)?;
self.attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
.filter(|attr| attr.meta.path().is_ident("doc"))
.try_fold(result, |mut vec, attr| {
if let Meta::NameValue(MetaNameValue {
value:
Expand Down Expand Up @@ -274,3 +294,37 @@ fn get_metadata_inner<'a, T: Parse>(
Ok(vec)
})
}

#[derive(Debug)]
pub enum InnerVariantMeta {
DefaultWith { kw: kw::default_with, value: LitStr },
}

impl Parse for InnerVariantMeta {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::default_with) {
let kw = input.parse()?;
let _: Token![=] = input.parse()?;
let value = input.parse()?;
Ok(InnerVariantMeta::DefaultWith { kw, value })
} else {
Err(lookahead.error())
}
}
}

pub trait InnerVariantExt {
/// Get all the metadata associated with an enum variant inner.
fn get_named_metadata(&self) -> syn::Result<Vec<InnerVariantMeta>>;
}

impl InnerVariantExt for Field {
fn get_named_metadata(&self) -> syn::Result<Vec<InnerVariantMeta>> {
let result = get_metadata_inner("strum", &self.attrs)?;
self.attrs
.iter()
.filter(|attr| attr.meta.path().is_ident("default_with"))
.try_fold(result, |vec, _attr| Ok(vec))
}
}
19 changes: 19 additions & 0 deletions sea-orm-macros/src/strum/helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
pub use self::case_style::snakify;
pub use self::inner_variant_props::HasInnerVariantProperties;
pub use self::type_props::HasTypeProperties;
pub use self::variant_props::HasStrumVariantProperties;

pub mod case_style;
pub mod inner_variant_props;
mod metadata;
pub mod type_props;
pub mod variant_props;

use proc_macro2::Span;
use quote::ToTokens;
use syn::spanned::Spanned;

pub fn non_enum_error() -> syn::Error {
syn::Error::new(Span::call_site(), "This macro only supports enums.")
}

pub fn non_unit_variant_error() -> syn::Error {
syn::Error::new(
Span::call_site(),
"This macro only supports enums of strictly unit variants. Consider \
using it in conjunction with [`EnumDiscriminants`]",
)
}

pub fn strum_discriminants_passthrough_error(span: &impl Spanned) -> syn::Error {
syn::Error::new(
span.span(),
"expected a pass-through attribute, e.g. #[strum_discriminants(serde(rename = \"var0\"))]",
)
}

pub fn occurrence_error<T: ToTokens>(fst: T, snd: T, attr: &str) -> syn::Error {
let mut e = syn::Error::new_spanned(
snd,
Expand Down
Loading

0 comments on commit 32cf8f7

Please sign in to comment.