Ident::new documentation is not clear that using a `r#` identifier will cause a panic
- Dominant language
- Rust
- Stars
- 934
- Forks
- 135
- PR merge metrics
- No merged PRs in 30d
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](https://doc.rust-lang.org/reference/keywords.html)) 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.
> ```rust
> 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
> ```rust
> // 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::` 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:
>
> ```rust
> let my_var_name = "let";
> let ident = match syn::parse_str::(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::(&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`](https://docs.rs/proc-macro2/1.0.17/proc_macro2/struct.Ident.html#method.new) to convert the field name into a identifier:
```rust
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:
```rust
pub fn ident(ident: &str) -> Ident {
match syn::parse_str::(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: ::fmt
1: core::fmt::write
2: ::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: ::next
11: ::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::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>::try_with
at /rustc/8d69840ab92ea7f4d323420088dd8c9775f180cd\src\libstd\thread\local.rs:262
18: std::thread::local::LocalKey>::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:
```rust
pub fn ident(ident: &str) -> Ident {
let mut ident = match syn::parse_str::(ident) {
Ok(ident) => ident,
Err(_) => syn::parse_str::(&format!("r#{}", ident)).unwrap(),
};
ident.set_span(Span::call_site());
ident
}
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.