{"record":{"id":"e356da4f51e0ce7c","repo":"rust-lang/rust-analyzer","slug":"next-token-must-be-ident","errorCode":null,"errorMessage":"Next token must be ident","messagePattern":"Next token must be ident","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/syntax-bridge/src/to_parser_input.rs","lineNumber":33,"sourceCode":"    let mut syntax_context_to_edition_cache = FxHashMap::default();\n    let mut ctx_edition =\n        |ctx| *syntax_context_to_edition_cache.entry(ctx).or_insert_with(|| span_to_edition(ctx));\n\n    while !current.eof() {\n        let tt = current.token_tree();\n\n        // Check if it is lifetime\n        if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = tt\n            && punct.char == '\\''\n        {\n            current.bump();\n            match current.token_tree() {\n                Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => {\n                    res.push(LIFETIME_IDENT, ctx_edition(ident.span.ctx));\n                    current.bump();\n                    continue;\n                }\n                _ => panic!(\"Next token must be ident\"),\n            }\n        }\n\n        match tt {\n            Some(tt::TokenTree::Leaf(leaf)) => {\n                match leaf {\n                    tt::Leaf::Literal(lit) => {\n                        let kind = match lit.kind {\n                            tt::LitKind::Byte => SyntaxKind::BYTE,\n                            tt::LitKind::Char => SyntaxKind::CHAR,\n                            tt::LitKind::Integer => SyntaxKind::INT_NUMBER,\n                            tt::LitKind::Float => SyntaxKind::FLOAT_NUMBER,\n                            tt::LitKind::Str | tt::LitKind::StrRaw(_) => SyntaxKind::STRING,\n                            tt::LitKind::ByteStr | tt::LitKind::ByteStrRaw(_) => {\n                                SyntaxKind::BYTE_STRING\n                            }\n                            tt::LitKind::CStr | tt::LitKind::CStrRaw(_) => SyntaxKind::C_STRING,\n                            tt::LitKind::Err(_) => SyntaxKind::ERROR,","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/syntax-bridge/src/to_parser_input.rs#L15-L51","documentation":"`to_parser_input` lowers a token tree into the parser's input format. Inside a macro definition, a `$var` metavariable followed by a fragment-specifier must be `Ident` (e.g. `$x:expr`); when the token after `$`-substitution is not an Ident where the lifetime/fragment path expects one, the invariant is violated and it panics. It indicates malformed macro-definition token trees reaching the converter.","triggerScenarios":"Calling `to_parser_input` on a token tree representing a macro_rules definition where after a `$` metavariable the next token is not an identifier (e.g. a literal, group, or punct instead of the fragment specifier like `expr`/`tt`), or where a `$`-lifetime path sees a non-ident token.","commonSituations":"See trigger scenarios.","solutions":["Ensure only well-formed macro-definition token trees (valid `$name:frag` pairs) are passed to `to_parser_input`","Pre-validate macro definitions: after each `$` metavariable ident, require `:` + ident before conversion","Harden the converter to skip or recover from malformed `$` sequences instead of panicking"],"exampleFix":"// before\n_ => panic!(\"Next token must be ident\"),\n// after\n_ => {\n    stdx::never!(\"Next token must be ident\");\n    res.push(LIFETIME_IDENT, ctx_edition(current_span.ctx));\n    current.bump();\n    continue;\n}","handlingStrategy":"validation","validationCode":"// Validate macro-definition shape before lowering:\nfn has_valid_fragment_specifiers(tt: &tt::Subtree) -> bool {\n    // every `$ident` must be followed by ':' ident\n    let mut iter = tt.token_trees.iter().peekable();\n    while let Some(t) = iter.next() {\n        if is_dollar_punct(t) {\n            match (iter.next(), iter.peek()) {\n                (Some(ident), Some(colon)) if is_ident(ident) && is_colon(colon) => { iter.next(); }\n                _ => return false,\n            }\n        }\n    }\n    true\n}","typeGuard":null,"tryCatchPattern":"let input = std::panic::catch_unwind(AssertUnwindSafe(||\n    to_parser_input(&token_tree)\n)).unwrap_or_else(|_| ParserInput::empty()); // degrade instead of crashing the IDE","preventionTips":["Only pass macro-definition token trees (not expansion output) to to_parser_input","Pre-scan `$` sequences for `name:frag` shape","Prefer recovering (never! + skip) over panicking on malformed user macros"],"tags":["rust","panic","macros","parser","token-tree"],"backgroundTag":"malformed-macro-fragment-specifier","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}