Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

syn crashes rustc ("foo is not a valid identifier") when I create an Ident from type that is being generated #950

Closed
Trolldemorted opened this issue Jan 5, 2021 · 1 comment

Comments

@Trolldemorted
Copy link

Trolldemorted commented Jan 5, 2021

I have this sample struct:

#[register_set]
struct SampleRegisterSet {
    R0: u64,
    R1: u32,
}

I want it to expand to:

pub struct SampleRegisterSet {
    R0: u64,
    R1: u32,
}

pub enum SampleRegisterSetEnum {
    R0,
    R1,
}

pub enum SampleRegisterSetWriteRegister {
    R0(u64),
    R1(u32),
}

impl SampleRegisterSet {
    pub fn apply_write_register(&mut self, write: &SampleRegisterSetWriteRegister ) {
        match write {
            SampleRegisterSetWriteRegister::R0(t) => {
                self.R0 = t;
            }
            SampleRegisterSetWriteRegister::R1(t) => {
                self.R1 = t;
            }
        }
    }
}

I can generate SampleRegisterSetEnum and SampleRegisterSetWriteRegister, but if I try to use their variants in a match arm rustc nopes out:

thread 'rustc' panicked at '`"SampleRegisterSetWriteRegister::R0"` is not a valid identifier', compiler\rustc_expand\src\proc_macro_server.rs:332:13
[...]
error: custom attribute panicked
 --> src\macro_tests.rs:3:5
  |
3 |     #[register_set]
  |     ^^^^^^^^^^^^^^^
  |
  = help: message: `"SampleRegisterSetWriteRegister::R0"` is not a valid identifier

I have narrowed it down to my syn::Ident::new call, where I build the enum's name and variant dynamically:

impl Parse for RegisterSet {
    fn parse(input: ParseStream) -> Result<Self> {
        let content;
        Ok(RegisterSet {
            struct_token: input.parse()?,
            ident: input.parse()?,
            brace_token: braced!(content in input),
            fields: content.parse_terminated(Field::parse_named)?,
        })
    }
}

#[proc_macro_attribute]
pub fn register_set(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as RegisterSet);

    let name = input.ident;
    let enum_name = syn::Ident::new(&format!("{}Enum", name), syn::export::Span::call_site());
    let write_register_enum_name = syn::Ident::new(&format!("{}WriteRegister", name), syn::export::Span::call_site());
    let mut register_declarations = quote![];
    let mut enum_variant_definitions = quote![];
    let mut write_register_definitions = quote![];
    let mut write_register_matches = quote![];

    for field in input.fields {
        match &field.ty {
            syn::Type::Path(t) => {
                let ident = field.ident.unwrap();
                register_declarations.extend(quote! {
                    #ident: #t,
                });
                enum_variant_definitions.extend(quote! {
                    #ident,
                });
                write_register_definitions.extend(quote! {
                    #ident(#t),
                });
                let match_arm = syn::Ident::new(&format!("{}WriteRegister::{}", name, ident), syn::export::Span::call_site());
                //write_register_matches.extend(quote!{
                //    #match_arm(t) => {
                //        self.#ident = t;
                //    }
                //});
            },
            _ => panic!("invalid field type")
        }
    }

    // struct definition
    let mut expanded = quote!{
        #[allow(non_snake_case)]
        #[derive(Debug)]
        pub struct #name {
            #register_declarations
        }
    };

    // struct implementation
    expanded.extend(quote! {
        impl #name {
            pub fn apply_write_register(&mut self, write: &#write_register_enum_name) {
                //match write {
                //    #write_register_matches
                //}
            }
        }
    });

    // enum definition
    expanded.extend(quote! {
        #[derive(Debug, Eq, PartialEq)]
        pub enum #enum_name {
            #enum_variant_definitions
        }
    });

    expanded.extend(quote! {
        pub enum #write_register_enum_name {
            #write_register_definitions
        }
    });
    TokenStream::from(expanded)
}

How can I use types that are being generated in Idents? I hope it is not impossible to generate code that uses generated types?

@taiki-e
Copy link
Contributor

taiki-e commented Jan 5, 2021

                let match_arm = syn::Ident::new(&format!("{}WriteRegister::{}", name, ident), syn::export::Span::call_site());

:: is not a valid identifier and it cannot be included in Ident. You have to write as follows (or create syn::Path):

-                 let match_arm = syn::Ident::new(&format!("{}WriteRegister::{}", name, ident), syn::export::Span::call_site());
+                 let name = syn::Ident::new(&format!("{}WriteRegister", name), proc_macro2::Span::call_site());
                  write_register_matches.extend(quote!{
-                     #match_arm(t) => {
+                     #name::#ident(t) => {
                          self.#ident = t;
                      }
                  });

    let enum_name = syn::Ident::new(&format!("{}Enum", name), syn::export::Span::call_site());

Also, note that syn::export is not a public API. It is not recommended to use it. It is recommended to import Span from proc_macro2.

syn/src/lib.rs

Lines 789 to 791 in 5e23a9b

// Not public API.
#[doc(hidden)]
pub mod export;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants