1

I am writing a macro for a struct and implementing a method based on the field type. e.g. u8, Array or str.

let us say I have this enum represented as u32.

#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub enum ServerGreetingMode {
  Unavailable = 0,
  Unauthenticated = 1,
  Authenticated = 2,
  Encrypted = 4,
}

And I am applying a macro ToBytes on a struct field

#[repr(packed)]
#[derive(ToBytes, Debug, Clone, Copy)]
pub struct ServerGreetingFrame {
  pub unused: [u8; 12],
  pub mode: ServerGreetingMode,
  pub challenge: [u8; 16],
  pub salt: [u8; 16],
  pub count: u32,
  pub mbz: [u8; 12],
}

I am able to get it to a part where I am getting the type as ServerGreetingMode but I am unable to tell if it is an enum or not.

Here is my current implementation.

#[proc_macro_derive(ToBytes)]
pub fn derive(tokens: TokenStream) -> TokenStream {
  let tokens_item = tokens.clone();
  let items = syn::parse_macro_input!(tokens_item as syn::Item);
  let output = match items {
    syn::Item::Struct(item) => {
      let name = &item.ident;
      let statements = match &item.fields {
        syn::Fields::Named(ref fields) => {
          // eprint!("{:#?}", field);
          let vary_by_type = fields.named.iter().map(|field| {
            let field_name = &field.ident;
            let field_type = &field.ty;

            let statement = match field_type {
              syn::Type::Array(syn::TypeArray { elem, .. }) => {
                let ty = elem.as_ref();
                match ty {
                  syn::Type::Path(typepath)
                    if typepath.qself.is_none()
                      && typepath.path.leading_colon.is_none()
                      && typepath.path.segments.len() == 1 && typepath.path.is_ident("u8") =>
                  {
                    quote! {
                      bytes.extend_from_slice(&self.#field_name);
                    }
                  },
                  _ => todo!(),
                }
              }
              syn::Type::Path(ty) if ty.path.clone().is_ident("u32") => {
                quote! {
                  bytes.extend_from_slice(&(self.#field_name as u32).to_be_bytes().to_vec());
                }
              },
              _ => todo!(),
            };
            statement
          });
          vary_by_type
        }
        _ => todo!(),
      };
      quote! {
        impl #name {
          fn to_bytes(&self) -> Vec<u8> {
            let mut bytes: Vec<u8> = Vec::new();
            #(
              #statements
            )*
            bytes
          }
        }
      }
    }
    _ => todo!(),
  };
  output.into()
  // let s = syn::parse_macro_input!(tokens_struct as syn::ItemStruct);
  // let n = &s.ident;
  // let expanded = quote! {
  //   impl #n {
  //     fn to_bytes(&self) -> Vec<u8> {
  //       let mut bytes: Vec<u8> = Vec::new();
  //       bytes
  //     }
  //   }
  // };
  // expanded.into()
}

Thanks.

Yousuf Jawwad
  • 3,037
  • 7
  • 33
  • 60
  • 2
    Macros are basically expanded during parsing, before types are resolved etc. They only have access to the tokens that make up the source code, not any deeper insight that the compiler may later glean. In particular, your macro only knows that `ServerGreetingMode` is an identifier token—and `syn` is able to determine from the context that that token reperesents some type, but there is no access to information about whether that type exists, where it exists, what sort of type it is or anything else. – eggyal Oct 24 '21 at 13:48
  • 1
    Would adding a derive-specific attribute to the `mode` field help you at all? E.g. `#[bytes(Enum)] pub mode: ServerGreetingMode,` – eggyal Oct 24 '21 at 13:54
  • I think that might help. let me try – Yousuf Jawwad Oct 24 '21 at 18:20

0 Answers0