Skip to content

Commit

Permalink
zv: derive: Value: Add support string representation for enums
Browse files Browse the repository at this point in the history
  • Loading branch information
katyo committed Feb 25, 2024
1 parent 8518df0 commit f60a387
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 15 deletions.
20 changes: 20 additions & 0 deletions zvariant/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,26 @@ mod tests {
Enum::try_from(Value::Str("Variant1".into())),
Err(Error::IncorrectType)
);

#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Type, Value, OwnedValue)]
#[zvariant(signature = "s", rename_all = "snake_case")]
enum Enum4 {
FooBar,
Baz,
}

assert_eq!(Enum4::signature(), "s");
assert_eq!(
Enum4::try_from(Value::Str("foo_bar".into())),
Ok(Enum4::FooBar)
);
assert_eq!(Enum4::try_from(Value::Str("baz".into())), Ok(Enum4::Baz));
assert_eq!(
Enum4::try_from(Value::Str("foo_baz".into())),
Err(Error::IncorrectType)
);
assert_eq!(Enum4::try_from(Value::U32(0)), Err(Error::IncorrectType));
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions zvariant_derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ def_attrs! {
pub StructAttributes("struct") { signature str, rename_all str, deny_unknown_fields none };
/// Attributes defined on fields.
pub FieldAttributes("field") { rename str };
/// Attributes defined on enumerations.
pub EnumAttributes("enum") { signature str, rename_all str };
/// Attributes defined on variants.
pub VariantAttributes("variant") { rename str };
}
96 changes: 81 additions & 15 deletions zvariant_derive/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
spanned::Spanned, Attribute, Data, DataEnum, DeriveInput, Error, Expr, Fields, Generics, Ident,
Lifetime, LifetimeDef,
Lifetime, LifetimeDef, Variant,
};
use zvariant_utils::case;

use crate::utils::*;

Expand Down Expand Up @@ -35,7 +36,9 @@ pub fn expand_derive(ast: DeriveInput, value_type: ValueType) -> Result<TokenStr
}
Fields::Unit => Err(Error::new(ast.span(), "Unit structures not supported")),
},
Data::Enum(data) => impl_enum(value_type, ast.ident, ast.generics, ast.attrs, data, &zv),
Data::Enum(data) => {
impl_enum(value_type, ast.ident, ast.generics, ast.attrs, data, &zv)
},
_ => Err(Error::new(
ast.span(),
"only structs and enums are supported",
Expand Down Expand Up @@ -236,26 +239,47 @@ fn impl_enum(
Some(repr_attr) => repr_attr.parse_args()?,
None => quote! { u32 },
};
let enum_attrs = EnumAttributes::parse(&attrs)?;
let str_enum = enum_attrs.signature.map(|sig| sig == "s").unwrap_or_default();

let mut variant_names = vec![];
let mut str_values = vec![];
for variant in &data.variants {
let variant_attrs = VariantAttributes::parse(&variant.attrs)?;
// Ensure all variants of the enum are unit type
match variant.fields {
Fields::Unit => {
variant_names.push(&variant.ident);
if str_enum {
let str_value = enum_name_for_variant(&variant, variant_attrs.rename,
enum_attrs.rename_all.as_ref().map(AsRef::as_ref))?;
str_values.push(str_value);
}
}
_ => return Err(Error::new(variant.span(), "must be a unit variant")),
}
}

let into_val = if str_enum {
quote! {
match e {
#(
#name::#variant_names => #str_values,
)*
}
}
} else {
quote! { e as #repr }
};

let (value_type, into_value) = match value_type {
ValueType::Value => (
quote! { #zv::Value<'_> },
quote! {
impl ::std::convert::From<#name> for #zv::Value<'_> {
#[inline]
fn from(e: #name) -> Self {
<#zv::Value as ::std::convert::From<_>>::from(e as #repr).into()
<#zv::Value as ::std::convert::From<_>>::from(#into_val)
}
}
},
Expand All @@ -269,34 +293,76 @@ fn impl_enum(
#[inline]
fn try_from(e: #name) -> #zv::Result<Self> {
<#zv::OwnedValue as ::std::convert::TryFrom<_>>::try_from(
<#zv::Value as ::std::convert::From<_>>::from(e as #repr)
<#zv::Value as ::std::convert::From<_>>::from(#into_val)
)
}
}
},
),
};

let from_val = if str_enum {
quote! {
let v: #zv::Str = ::std::convert::TryInto::try_into(value)?;

::std::result::Result::Ok(match v.as_str() {
#(
#str_values => #name::#variant_names,
)*
_ => return ::std::result::Result::Err(#zv::Error::IncorrectType),
})
}
} else {
quote! {
let v: #repr = ::std::convert::TryInto::try_into(value)?;

::std::result::Result::Ok(
#(
if v == #name::#variant_names as #repr {
#name::#variant_names
} else
)* {
return ::std::result::Result::Err(#zv::Error::IncorrectType);
}
)
}
};

Ok(quote! {
impl ::std::convert::TryFrom<#value_type> for #name {
type Error = #zv::Error;

#[inline]
fn try_from(value: #value_type) -> #zv::Result<Self> {
let v: #repr = ::std::convert::TryInto::try_into(value)?;

::std::result::Result::Ok(
#(
if v == #name::#variant_names as #repr {
#name::#variant_names
} else
)* {
return ::std::result::Result::Err(#zv::Error::IncorrectType);
}
)
#from_val
}
}

#into_value
})
}

fn enum_name_for_variant(
v: &Variant,
rename_attr: Option<String>,
rename_all_attr: Option<&str>,
) -> Result<String, Error> {
if let Some(name) = rename_attr {
Ok(name)
} else {
let ident = v.ident.to_string();

match rename_all_attr {
Some("lowercase") => Ok(ident.to_ascii_lowercase()),
Some("UPPERCASE") => Ok(ident.to_ascii_uppercase()),
Some("PascalCase") => Ok(case::pascal_or_camel_case(&ident, true)),
Some("camelCase") => Ok(case::pascal_or_camel_case(&ident, false)),
Some("snake_case") => Ok(case::snake_case(&ident)),
None => Ok(ident),
Some(other) => Err(Error::new(
v.span(),
format!("invalid `rename_all` attribute value {other}"),
)),
}
}
}

0 comments on commit f60a387

Please sign in to comment.