{"id":"defd47cb72832fa5","repo":"rust-lang/rust","slug":"unexpected-last-token-last-token","errorCode":null,"errorMessage":"Unexpected last token {last_token:?}","messagePattern":"Unexpected last token (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/rustc_ast/src/tokenstream.rs","lineNumber":416,"sourceCode":"                stack_top.inner.push(AttrTokenTree::AttrsTarget(target))\n            }\n            FlatToken::Empty => {}\n        }\n    }\n\n    if break_last_token > 0 {\n        let last_token = stack_top.inner.pop().unwrap();\n        if let AttrTokenTree::Token(last_token, spacing) = last_token {\n            let (unglued, _) = last_token.kind.break_two_token_op(break_last_token).unwrap();\n\n            // Tokens are always ASCII chars, so we can use byte arithmetic here.\n            let mut first_span = last_token.span.shrink_to_lo();\n            first_span =\n                first_span.with_hi(first_span.lo() + rustc_span::BytePos(break_last_token));\n\n            stack_top.inner.push(AttrTokenTree::Token(Token::new(unglued, first_span), spacing));\n        } else {\n            panic!(\"Unexpected last token {last_token:?}\")\n        }\n    }\n    AttrTokenStream::new(stack_top.inner)\n}\n\n/// Like `TokenTree`, but for `AttrTokenStream`.\n#[derive(Clone, Debug, Encodable, Decodable)]\npub enum AttrTokenTree {\n    Token(Token, Spacing),\n    Delimited(DelimSpan, DelimSpacing, Delimiter, AttrTokenStream),\n    /// Stores the attributes for an attribute target,\n    /// along with the tokens for that attribute target.\n    /// See `AttrsTarget` for more information\n    AttrsTarget(AttrsTarget),\n}\n\nimpl AttrTokenStream {\n    pub fn new(tokens: Vec<AttrTokenTree>) -> AttrTokenStream {","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_ast/src/tokenstream.rs#L398-L434","documentation":"`panic!(\"Unexpected last token {last_token:?}\")` fires inside `to_attr_token_stream` when `break_last_token > 0` (the parser asked to split the trailing token into two — e.g., to unglue a multi-char operator like `>>` or `..=`) but the top of the token stack is not an `AttrTokenTree::Token`. Only a real `Token` can be split via `Token::break_two_token_op`; a `Delimited` or `AttrsTarget` cannot, so reaching this panic indicates the break count was set while the stack top is a non-token tree.","triggerScenarios":"`break_last_token` is non-zero (set by attribute/token collection when the last token must be re-glued later) and `stack_top.inner.last()` is an `AttrTokenTree::Delimited` or `AttrTokenTree::AttrsTarget` rather than a `Token`. Triggered by attribute parsing edge cases where attributes attach to a node whose final tree is a delimited group.","commonSituations":"Bugs in attribute token collection (`collect_tokens`) that set `break_last_token` incorrectly. Macros placing attributes such that the collected token stream's last tree is a `Delimited`/`AttrsTarget`. Refactors to token gluing/breaking logic. Rare; usually an ICE on specific macro/attribute combinations.","solutions":["Reproduce with `RUST_BACKTRACE=1` and inspect the failing macro/attribute input — minimize it for a bug report.","Check that `break_last_token` is only set when the parser knows the final tree is a `Token`; reset it to 0 in paths where the stack top is `Delimited`/`AttrsTarget`.","Audit recent changes to `break_two_token_op` / attribute collection for off-by-one or wrong-variant assumptions."],"exampleFix":"// before (collection logic sets break unconditionally):\nbreak_last_token = n;\n\n// after (only break when top is a Token):\nif matches!(stack_top.inner.last(), Some(AttrTokenTree::Token(..))) {\n    break_last_token = n;\n} else {\n    break_last_token = 0;\n}","handlingStrategy":"validation","validationCode":"if let Some(last) = stream.last_token() {\n    match &last.kind {\n        TokenKind::Eof | TokenKind::Comma | TokenKind::Semi => { /* acceptable */ }\n        _ => { /* unexpected; do not invoke gluing logic */ }\n    }\n}","typeGuard":"fn is_glueable_last_token(t: &Token) -> bool {\n    matches!(t.kind, TokenKind::Eof | TokenKind::Comma | TokenKind::Semi)\n}","tryCatchPattern":"let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    AttrTokenStream::create_glued_extensions(stream)\n}));\nif result.is_err() {\n    // fall back to using the unextended stream\n}","preventionTips":["Token gluing only handles a small set of trailing tokens; check the kind before invoking gluing helpers.","When constructing `AttrTokenStream`s by hand, end on a well-formed terminator.","If you cannot predict the final token kind, wrap the glue call in `catch_unwind` as a last-resort fallback and report the offending token to diagnostics."],"tags":["rustc","internals","tokenstream","attributes","token-gluing"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}