2

I am trying to add a procedural macro that adds constant field to a struct.

Example:

#[add_const]
struct MyStruct {
    id: u32,
    name: String
}

// Expands to:
struct MyStruct {
    id: u32,
    name: String
}
impl MyStruct {
    const FIELDS: [&'static str; 2] = ["id", "name"];
    const TYPES: [syn::Type; 2] = [/*Types of the fields*/];
}

My code for the procedural macro is the following:

#[proc_macro_attribute]
pub fn add_const(_attrs: TokenStream, input: TokenStream) -> TokenStream {
    let item_struct = parse_macro_input!(input as ItemStruct);
    let struct_name = &item_struct.ident;
    let fields: Vec<(String, syn::Type)>;
    if let syn::Fields::Named(ref _fields) = item_struct.fields {
        let _fields = &_fields.named;
        fields = _fields.iter().map(|field| {
            if let Some(ident) = &field.ident {
                let field_name: String = ident.to_string();
                let field_type = &field.ty;
                let entry = (field_name, field_type.to_owned());
                entry
            } else {
                panic!("Only named fields are supported.")
            }
        }).collect();
    } else {
        panic!("The row struct has no fields.");
    }

    let mut field_names: Vec<String> = Vec::new();
    let mut field_types: Vec<syn::Type> = Vec::new();
    
    fields.iter().for_each(|field| {
        let (_name, _type) = field;
        field_names.push(_name.to_owned());
        field_types.push(_type.to_owned());
    });

    TokenStream::from(
        quote!(
            #item_struct
            
            impl #struct_name {
                const FIELDS: [&'static str; #field_len] = [#(#field_names),*];
                const TYPES: [syn::Type; #field_len] = [#(#field_types),*];
            }
        )
    )
}

This throws the following errors:

error[E0423]: expected value, found builtin type `i32`
 --> tests/table_row.rs:7:13
  |
7 |         id: i32,
  |             ^^^ not a value

error[E0423]: expected value, found struct `String`
   --> tests/table_row.rs:8:15
    |
8   |         name: String,
    |               ^^^^^^ help: use struct literal syntax instead: `String { vec: val }`
    |
   ::: /Users/jonaseveraert/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/string.rs:292:1
    |
292 | pub struct String {
    | ----------------- `String` defined here

I have been able to iron down where the problem lies;

/*CODE*/
impl #struct_name {
    const FIELDS: [&'static str; #field_len] = [#(#field_names),*];
    // const TYPES: [syn::Type; #field_len] = [#(#field_types),*]; // Commenting out this line makes the errors disappear
}
/*MORE CODE*/

How can I fix this? i.e. how can I add the second constant without the errors appearing and why do those errors appear?

Thank you in advance, Jonas

Jomy
  • 514
  • 6
  • 22
  • Note: panicking in proc macros is not good, use [`syn::Error::into_compile_error()`](https://docs.rs/syn/latest/syn/struct.Error.html#method.into_compile_error) instead. – Chayim Friedman Jan 10 '22 at 21:42
  • Also: `.to_owned()` on `syn::Type` is confusing and redundant, use `.clone()`. – Chayim Friedman Jan 10 '22 at 21:43
  • Also, when you have an iterator of tuple, you can use [`Iterator::unzip()`](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.unzip). – Chayim Friedman Jan 10 '22 at 21:44
  • 2
    Why do you represent types as `syn` types? This library is not meant for use in user code, only in proc macros and the like. You cannot do much with its types. Anyway, `quote!`ing `syn::Type` does not give you an instance but a Rust type as source code. Also, you don't define `field_len`. – Chayim Friedman Jan 10 '22 at 21:46
  • Wow, thank you for all the suggestions on improving my code @ChayimFriedman! The `field_len` is actually defined in my code, I just forgot to copy it over, so my bad. As a solution, I have converted the types to strings, which seems to work. – Jomy Jan 10 '22 at 23:18

0 Answers0