rust-lang / rust-lang/rust-analyzer
Proc Macros sometimes recieve unnecessary None Groups
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 16.9k
- Forks
- 2.2k
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 72
Description
rust-analyzer version: rust-analyzer version: 0.4.2738-standalone [[redacted home dir]/.vscode-insiders/extensions/rust-lang.rust-analyzer-0.4.2738-linux-x64/server/rust-analyzer]
rustc version: rustc 1.93.0-nightly (b84478a1c 2025-11-30)
editor or extension: VSCode pre-release (also occurs on release version)
relevant settings: N/A
code snippet to reproduce:
use test_helper::Foo;
#[derive(Foo)]
pub struct Bar {
x: u8,
}
fn main() {
println!("Hello, world!");
}
// crate test_helper
// --proc-macro
use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree};
#[proc_macro_derive(Foo)]
pub fn foo(attrs: TokenStream) -> TokenStream {
let tt_str = format!("{attrs:?}");
[
TokenTree::Ident(Ident::new("core", Span::mixed_site())),
TokenTree::Punct(Punct::new(':', proc_macro::Spacing::Joint)),
TokenTree::Punct(Punct::new(':', proc_macro::Spacing::Alone)),
TokenTree::Ident(Ident::new("compile_error", Span::mixed_site())),
TokenTree::Punct(Punct::new('!', proc_macro::Spacing::Alone)),
TokenTree::Group(Group::new(
proc_macro::Delimiter::Parenthesis,
[TokenTree::Literal(Literal::string(&tt_str))]
.into_iter()
.collect(),
)),
TokenTree::Punct(Punct::new(';', proc_macro::Spacing::Alone)),
]
.into_iter()
.collect()
}
The behaviour of the above code is as follows:
On rustc, it produces a compile_error with roughly the following output (which is expected per the reference):
error: TokenStream [Ident { ident: "pub", span: #0 bytes(38..41) }, Ident { ident: "struct", span: #0 bytes(42..48) }, Ident { ident: "Bar", span: #0 bytes(49..52) }, Group { delimiter: Brace, stream: TokenStream [Ident { ident: "x", span: #0 bytes(59..60) }, Punct { ch: ':', spacing: Alone, span: #0 bytes(60..61) }, Ident { ident: "u8", span: #0 bytes(62..64) }, Punct { ch: ',', spacing: Alone, span: #0 bytes(64..65) }], span: #0 bytes(53..67) }]
--> src/main.rs:3:10
|
3 | #[derive(Foo)]
| ^^^
|
= note: this error originates in the derive macro `Foo` (in Nightly builds, run with -Z macro-backtrace for more info)
On rust-analyzer, it produces a different compile_error, which demonstrates a different (and incorrect) input TokenStream:
TokenStream [Group { delimiter: None, stream: TokenStream [Ident { ident: "pub", span: /* simplified */), ctx: SyntaxContext(4294967036) } }, Ident { ident: "struct", span: /* simplified */), ctx: SyntaxContext(4294967036) } }, Ident { ident: "Bar", span: /* simplified */), ctx: SyntaxContext(4294967036) } }, Group { delimiter: Brace, stream: TokenStream [Ident { ident: "x", span: /* simplified */), ctx: SyntaxContext(4294967036) } }, Punct { ch: ':', spacing: Alone, span: /* simplified */), ctx: SyntaxContext(4294967036) } }, Ident { ident: "u8", span: /* simplified */), ctx: SyntaxContext(4294967036) } }, Punct { ch: ',', spacing: Alone, span: /* simplified */), ctx: SyntaxContext(4294967036) } }], span: /* simplified */), ctx: SyntaxContext(4294967036) } }], span: /* simplified */), ctx: SyntaxContext(4294967036) } }]
(Span markers simplified manually to avoid unnecessary clutter).
In the above, rustc passes the Foo definition directly, while r-a encloses it in a None delimited group (which is both unnecessary and incorrect - None groups are for delimiting the contents of expanded non-literal metavariables (like $expr, $item, or $stmt) only).
Note that while this is a contrived example and only meaningfully differs in compiler diagnostics, the added None group where not expected can affect the ability to parse some of the input. As an example, I get unsupported type errors using the bitfield_struct crate on rust-analyzer only, which I can only assume is caused by the same bug and the macro not being forgiving about None groups arround the type names (which it expects to be literal identifiers).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the #[proc_macro_derive] entry point and the proc_macro TokenStream, Group, and TokenTree behavior shown in the reproduction. Compare rust-analyzer's generated input with rustc's reference output, focusing on unnecessary None-delimited groups around the item tokens. Done means the reproduction receives the same token structure as rustc without affecting valid delimiter groups.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100