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

Ident::new documentation is not clear that using a r# identifier will cause a panic #234

Open
software-opal opened this issue May 26, 2020 · 0 comments
Labels

Comments

@software-opal
Copy link

It would be good to have some extra information in the documentation about how to handle r#-prefixed identifiers. For example:

Special care should be taken when using Rust reserved words(like let, match, type, more here) in an Ident. The Ident::new will not error when called with an keyword, but Rust will fail to compile any generated code that uses it.

let special_ident = Ident::new("let", Span::call_site());
// Will run fine, but if this is used in generated code, the generated code will fail to compile

Instead, you can use the Ident::new_raw(currently semver exempt) to generate a Ident that will generate valid Rust code when used as an identifier

// Is this call correct? Do I need to call this with "r#let" instead.
let special_ident = Ident::new_raw("let", Span::call_site());
// `special_ident` is now able to be used in code generation as an identifier
// it will appear prefixed by `r#` to escape the keyword.

You may be tempted to use the Ident::new with a "r#..." argument, however this will cause the compiler to crash during ?code generation?.
An alternative to using the Ident::new_raw function(and opting-in to semver exempt functionality) is to use the syn::parse_str::<Ident> function. This function also correctly handles r# identifiers. This can be combined to create a function that will generate the correct Ident instance for both valid identifiers and keywords:

let my_var_name = "let";
let ident = match syn::parse_str::<Ident>(ident) {
    Ok(ident) => ident,  // A valid identifier
    Err(_) => // Not a valid identifier, could be a keyword.
        // Call `.unwrap` to panic on a never-valid identifier like "" or "123"
        syn::parse_str::<Ident>(&format!("r#{}", ident)).unwrap(),
};

Some background about how I ended up here

I'm trying to generate some Rust structs from a file; and the names could be rust identifiers. In my code I used the Ident::new to convert the field name into a identifier:

let ident = Ident::new(field_name, Span::call_site())
...

This generates a valid ident, but the macro that uses it won't compile when the field_name is a rust keyword(like let). My first attempt to fix this involved this:

pub fn ident(ident: &str) -> Ident {
    match syn::parse_str::<Ident>(ident) {
        Ok(mut ident) => {
            ident.set_span(Span::call_site());
            ident
        }
        Err(_) => Ident::new(format!("r#{}", ident), Span::call_site()),
    }
}

However using this code causes the compiler to crash during the build.

thread 'rustc' panicked at '`"r#type"` is not a valid identifier', src\librustc_expand\proc_macro_server.rs:329:13
stack backtrace:
   0: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
   1: core::fmt::write
   2: <std::io::IoSlice as core::fmt::Debug>::fmt
   3: std::panicking::take_hook
   4: std::panicking::take_hook
   5: rustc_driver::report_ice
   6: std::panicking::rust_panic_with_hook
   7: rust_begin_unwind
   8: std::panicking::begin_panic_fmt
   9: rustc_expand::expand::AstFragment::make_variants
  10: <rustc_expand::mbe::transcribe::Frame as core::iter::traits::iterator::Iterator>::next
  11: <rustc_expand::mbe::macro_rules::TokenSet as core::fmt::Debug>::fmt
  12: _rust_maybe_catch_panic
  13: rustc_expand::base::MacEager::ty
  14: rustc_expand::base::MacEager::ty
  15: proc_macro::bridge::scoped_cell::ScopedCell<proc_macro::bridge::client::BridgeStateL>::replace
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\/src\libproc_macro\bridge\scoped_cell.rs:74
  16: proc_macro::bridge::client::{{impl}}::with::{{closure}}
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\/src\libproc_macro\bridge\client.rs:284
  17: std::thread::local::LocalKey<proc_macro::bridge::scoped_cell::ScopedCell<proc_macro::bridge::client::BridgeStateL>>::try_with
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\src\libstd\thread\local.rs:262
  18: std::thread::local::LocalKey<proc_macro::bridge::scoped_cell::ScopedCell<proc_macro::bridge::client::BridgeStateL>>::with
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\src\libstd\thread\local.rs:239
  19: proc_macro::bridge::client::BridgeState::with
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\/src\libproc_macro\bridge\client.rs:283
  20: proc_macro::bridge::Bridge::with
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\/src\libproc_macro\bridge\client.rs:314
  21: proc_macro::bridge::client::Ident::new
             at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\/src\libproc_macro\bridge\client.rs:230
  22: proc_macro2::imp::Ident::new
             at C:\Users\leesy\.cargo\registry\src\github.com-1ecc6299db9ec823\proc-macro2-1.0.12\src\wrapper.rs:631
  23: proc_macro2::Ident::new
             at C:\Users\leesy\.cargo\registry\src\github.com-1ecc6299db9ec823\proc-macro2-1.0.12\src\lib.rs:866

The docs don't make it clear that this setup of calling Ident::new with a r#-prefixed ident won't generate a valid Ident. Without the Ident::new_raw being stableized the only way I can see(from my very rudimentary understanding on how this magic works) is to call syn::parse_str twice:

pub fn ident(ident: &str) -> Ident {
    let mut ident = match syn::parse_str::<Ident>(ident) {
        Ok(ident) => ident,
        Err(_) => syn::parse_str::<Ident>(&format!("r#{}", ident)).unwrap(),
    };
    ident.set_span(Span::call_site());
    ident
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants