Skip to content

Commit

Permalink
[eclipse-iceoryx#491] Tweak implementation of StringLiteral derive macro
Browse files Browse the repository at this point in the history
  • Loading branch information
orecham committed Nov 18, 2024
1 parent d06c37c commit 53e0673
Showing 1 changed file with 56 additions and 38 deletions.
94 changes: 56 additions & 38 deletions iceoryx2-bb/derive-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Literal;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Expr, ExprLit, Fields, Lit};

Expand Down Expand Up @@ -100,96 +101,113 @@ pub fn placement_default_derive(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}

/// Implements the [`AsStringLiteral`] trait for enums to provide a string representation of each enum variant.
///
/// The string representation can be customized using the `CustomString` attribute, otherwise it will
/// convert the variant name to lowercase and replace underscores with spaces.
///
/// # Example
/// ```
/// use iceoryx2_bb_derive_macros::StringLiteral;
///
/// #[derive(StringLiteral)]
/// enum MyEnum {
/// #[CustomString = "custom variant one"]
/// VariantOne,
/// VariantTwo,
/// }
///
/// let v1 = MyEnum::VariantOne;
/// assert_eq!(v1.as_str_literal(), "custom variant one");
///
/// let v2 = MyEnum::VariantTwo;
/// assert_eq!(v2.as_str_literal(), "variant two");
/// ```
#[proc_macro_derive(StringLiteral, attributes(CustomString))]
pub fn string_literal_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

let string_literal_impl = match input.data {
// generate implementation converting enums to a string representation
let as_string_literal_impl = match input.data {
Data::Enum(ref data_enum) => {
let match_arms = data_enum.variants.iter().map(|variant| {
let variant_ident = &variant.ident;

let static_string = variant
let enum_to_string_mapping = data_enum.variants.iter().map(|variant| {
let enum_name = &variant.ident;
let enum_string_literal = variant
.attrs
.iter()
.find_map(|attr| {
// use CustomString as the string representation, if provided
if !attr.path().is_ident("CustomString") {
return None;
}

// get the value of CustomString as a string literal
match attr.meta.require_name_value() {
Ok(meta) => {
if let Expr::Lit(ExprLit {
Ok(meta) => match &meta.value {
Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = &meta.value
{
Some(lit.value())
} else {
None
}
}
}) => Some(Literal::string(&lit.value())),
_ => None,
},
_ => None,
}
})
.unwrap_or_else(|| {
let variant_str = variant_ident.to_string();
variant_str
// if no CustomString, generates default string literal in the form
// MyEnum::MyVariantName => 'my variant name'
let enum_string_literal = enum_name
.to_string()
.chars()
.enumerate()
.map(|(_, c)| {
if c == '_' {
' '
} else {
c.to_ascii_lowercase()
}
.map(|c| match c {
'_' => ' ',
c => c.to_ascii_lowercase(),
})
.collect::<String>()
.collect::<String>();
Literal::string(&enum_string_literal)
});

// maps each enum variant to its string representation
match &variant.fields {
Fields::Unit => {
quote! {
Self::#variant_ident => concat!(#static_string, "\0").as_ptr()
Self::#enum_name => #enum_string_literal
}
}
Fields::Unnamed(_) => {
quote! {
Self::#variant_ident(..) => concat!(#static_string, "\0").as_ptr()
Self::#enum_name(..) => #enum_string_literal
}
}
Fields::Named(_) => {
quote! {
Self::#variant_ident{..} => concat!(#static_string, "\0").as_ptr()
Self::#enum_name{..} => #enum_string_literal
}
}
}
});

// generate the mapping for the enum variant
quote! {
fn as_str_literal(&self) -> &'static str {
unsafe {
std::str::from_utf8_unchecked(
std::ffi::CStr::from_ptr(match self {
#(#match_arms,)*
} as *const i8)
.to_bytes()
)
match self {
#(#enum_to_string_mapping,)*
}
}
}
}
_ => {
// does not work for non-enum types
let err =
syn::Error::new_spanned(&input, "AsStringLiteral can only be derived for enums");
return err.to_compile_error().into();
}
};

// implement the trait with the generated implementation
let expanded = quote! {
impl #impl_generics AsStringLiteral for #name #ty_generics #where_clause {
#string_literal_impl
impl #impl_generics AsStringLiteral for #name #type_generics #where_clause {
#as_string_literal_impl
}
};

Expand Down

0 comments on commit 53e0673

Please sign in to comment.