dtolnay / syn

Parser for Rust source code
Apache License 2.0
2.79k stars 306 forks source link

parse_nested_meta don't work on attribute with multiple meta #1669

Closed NicolasWent closed 1 month ago

NicolasWent commented 2 months ago

Hello,

When I try to run the function: parse_nested_meta on this structure:

#[TestMacro]
pub struct TestStruct {
    #[button(
        field_one = "test",
        field_two = "field 2",
        field_three = "field 3",
        field_four = "very long field 4",
        field_five = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat"
    )]
    pub my_element: u32,
}

I am getting the following error: Got error while parsing meta: Error("expected ,")

Full code ```rust use syn::DeriveInput; use quote::quote; pub fn generate(input: DeriveInput) -> proc_macro2::TokenStream { if let syn::Data::Struct(s) = &input.data { let mut ret = quote! {}; for field in &s.fields { for attr in &field.attrs { if attr.path().is_ident("button") { attr.parse_nested_meta(|meta| { let path_name = meta.path.get_ident().unwrap().to_string(); println!("Path name: {path_name}"); ret.extend(quote! { println!("Path: {}", #path_name); }); println!("NEXT"); Ok(()) }).expect("Got error while parsing meta"); } } } quote! { fn random_func() { #ret } } } else { panic!("TestMacro is only available for struct"); } } #[cfg(test)] mod test { use super::generate; use prettyplease::unparse; use proc_macro2::TokenStream; use quote::quote; use syn::{parse_quote, DeriveInput, File, Item, ItemMod}; #[test] fn test_generation() { let input: DeriveInput = parse_quote! { #[TestMacro] pub struct TestStruct { #[button( field_one = "test", field_two = "field 2", field_three = "field 3", field_four = "very long field 4", field_five = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat" )] pub my_element: u32, } }; let result: TokenStream = generate(input); let wrapped_code = quote! { mod dummy { #result } }; // Convert the TokenStream to a syntax tree and then format it let syntax_tree: ItemMod = syn::parse2(wrapped_code).expect("Failed to parse TokenStream as ItemMod"); let file = File { shebang: None, attrs: Vec::new(), items: vec![Item::Mod(syntax_tree)], }; let formatted_code = unparse(&file); // Print the formatted code println!("{}", formatted_code); // Panic to keep the output visible panic!(); } } ```
Cargo.toml ```toml [package] name = "telegram-button-maccro" version = "0.1.0" edition = "2021" [lib] proc-macro = true [dependencies] syn = { version = "2.0", features = ["full", "extra-traits"] } quote = "1.0" proc-macro2 = "1.0" [dev-dependencies] prettyplease = "0.2" ```
cargo test ```log Compiling telegram-button-maccro v0.1.0 (/home/nicolas/Documents/projects2/poc/telegram-button-maccro) Finished test [unoptimized + debuginfo] target(s) in 0.48s Running unittests src/lib.rs (target/debug/deps/telegram_button_maccro-b1f06e26b47677b2) running 1 test test generate::test::test_generation ... FAILED failures: ---- generate::test::test_generation stdout ---- Path name: field_one NEXT thread 'generate::test::test_generation' panicked at src/generate.rs:19:24: Got error while parsing meta: Error("expected `,`") note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: generate::test::test_generation test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--lib` ```

EDIT: After looking at #1426 I realized that I needed to actually extract the values:

Generate function fixed ```rust pub fn generate(input: DeriveInput) -> proc_macro2::TokenStream { if let syn::Data::Struct(s) = &input.data { let mut ret = quote! {}; for field in &s.fields { for attr in &field.attrs { if attr.path().is_ident("button") { attr.parse_nested_meta(|meta| { let path_name = meta.path.get_ident().unwrap().to_string(); println!("Path name: {path_name}"); let value = meta.value()?; let s: LitStr = value.parse()?; let val = s.value(); ret.extend(quote! { println!("Path: {} with val {}", #path_name, #val); }); println!("NEXT"); Ok(()) }).expect("Got error while parsing meta"); } } } quote! { fn random_func() { #ret } } } else { panic!("TestMacro is only available for struct"); } } ```

When we are using parse_nested_meta we actually must parse all of the "sub-meta" also otherwise we get an error.

Is it possible to have a macro or something that allow us to "ignore all" sub-meta when we don't need them?

TheNeikos commented 2 months ago

Ah yes, I just ran into this as well and was almost going crazy, wondering why it was not working!

dtolnay commented 1 month ago

I think this is a duplicate of #1426.