Why this macro add field operation do not work?

use quote::{quote};
use syn::{NestedMeta, ItemStruct, parse_macro_input, Lit, Fields};
use syn::parse::Parser;

fn parse_args(metadata: Vec<NestedMeta>) -> Vec<String> {
    metadata.iter().map(|i| {
        match i {
            NestedMeta::Lit(Lit::Int(i)) => format!("add_{}", i),
            _ => panic!("error value ")
        }
    }).collect()
}

#[proc_macro_attribute]
pub fn extend(args: TokenStream, input: TokenStream) -> TokenStream {
    let mut ast = parse_macro_input!(input as ItemStruct);
    let args = parse_macro_input!(args as Vec<NestedMeta>);
    match &mut ast.fields {
        Fields::Named(ref mut fields) => {
            let arg = parse_args(args);
            for name in arg {
                fields.named.push(syn::Field::parse_named.parse2(quote! { pub #name: String }).unwrap());
            }
        }
        _ => panic!("error")
    }

    return quote! {
                #ast
            }.into();
}

i have took a look at here

but the quote! { pub #name: String } parse failed.
error output

 = help: message: called `Result::unwrap()` on an `Err` value: Error("expected identifier")

here is a test code.

#[extend(1, 2, 3)]
#[derive(Debug, Default)]
pub struct Example
{
    pub b: i32,
}

fn main() {
    let example = Example { b: 2, ..Default::default() };
    println!("{:?}", example)
}

Strings quote to a string literal, so your expanded result won't contain identifiers but string literals. You should use proc_macro2::Ident instead.

can you give me a example? ...

You need to replace the String return type of parse_args with Ident. You can look up the API of Ident in its official documentation.

By the way, there are other problems with your code too:

  • don't use parse2(quote!{ … }).unwrap(), that'd be parse_quote!{ … } instead.
  • explicit returns at the end of the function are unnecessary and non-idiomatic. Rust is an expression language.
  • quoting a single variable is unnecessary. Just use its ToTokens impl to turn it into a TokenStream directly.

All in all, something like this should work. (Untested; the Playground can't run proc-macro code so you'll likely need to adjust some trivial errors such as missing imports.)

1 Like

thank for you suggestions.

use proc_macro::{TokenStream};
use std::hint::unreachable_unchecked;
use proc_macro2::{Ident, Span};
use quote::{quote};
use syn::{NestedMeta, ItemStruct, parse_macro_input, Lit, Fields};
use syn::parse::Parser;

fn parse_args(metadata: Vec<NestedMeta>) -> Vec<(Ident, &'static str)> {
    metadata.iter().map(|i| {
        match i {
            NestedMeta::Lit(Lit::Int(i)) => (Ident::new(format!("add_{}", i).as_str(), Span::call_site()), "i32"),
            NestedMeta::Lit(Lit::Float(i)) => (Ident::new(format!("add_{}", i.base10_digits().parse::<f64>().unwrap() as i32).as_str(), Span::call_site()), "f64"),
            _ => panic!("error type of value ")
        }
    }).collect()
}

#[proc_macro_attribute]
pub fn extend(args: TokenStream, input: TokenStream) -> TokenStream {
    let mut ast = parse_macro_input!(input as ItemStruct);
    let args = parse_macro_input!(args as Vec<NestedMeta>);
    match &mut ast.fields {
        Fields::Named(ref mut fields) => {
            let arg = parse_args(args);
            for (name, ori) in arg {
                match ori {
                    "i32" => fields.named.push(syn::Field::parse_named.parse2(quote! { pub #name: i32 }).unwrap()),
                    "f64" => fields.named.push(syn::Field::parse_named.parse2(quote! { pub #name: f64 }).unwrap()),
                    _ => unsafe { unreachable_unchecked() }
                }
            }
        }
        _ => panic!("error")
    }
    (quote! { #ast }).into()
}


this worked for me.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.