Description
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. TheIdent::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 compileInstead, 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 theIdent::new_raw
function(and opting-in to semver exempt functionality) is to use thesyn::parse_str::<Ident>
function. This function also correctly handlesr#
identifiers. This can be combined to create a function that will generate the correctIdent
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
}