{"record":{"id":"a9298f7e13dd6d0f","repo":"rust-lang/rust-analyzer","slug":"expected-at-least-one-token","errorCode":null,"errorMessage":"expected at least one token","messagePattern":"expected at least one token","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/syntax-bridge/src/lib.rs","lineNumber":1011,"sourceCode":"                    }\n                    None => {\n                        let parent = self.cursor.end();\n                        match delim_to_str(parent.delimiter.kind, true) {\n                            Some(it) => (it, parent.delimiter.close),\n                            None => continue,\n                        }\n                    }\n                };\n            };\n            self.buf += text;\n            self.text_pos += TextSize::of(text);\n            combined_span = match combined_span {\n                None => Some(span),\n                Some(prev_span) => Some(Self::merge_spans(prev_span, span)),\n            }\n        }\n\n        self.token_map.push(self.text_pos, combined_span.expect(\"expected at least one token\"));\n        self.inner.token(kind, self.buf.as_str());\n        self.buf.clear();\n        // FIXME: Emitting whitespace for this is really just a hack, we should get rid of it.\n        // Add whitespace between adjoint puncts\n        if let Some([tt::Leaf::Punct(curr), tt::Leaf::Punct(next)]) = last_two {\n            // Note: We always assume the semi-colon would be the last token in\n            // other parts of RA such that we don't add whitespace here.\n            //\n            // When `next` is a `Punct` of `'`, that's a part of a lifetime identifier so we don't\n            // need to add whitespace either.\n            if curr.spacing == tt::Spacing::Alone && curr.char != ';' && next.char != '\\'' {\n                self.inner.token(WHITESPACE, \" \");\n                self.text_pos += TextSize::of(' ');\n                self.token_map.push(self.text_pos, curr.span);\n            }\n        }\n    }\n","sourceCodeStart":993,"sourceCodeEnd":1029,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/syntax-bridge/src/lib.rs#L993-L1029","documentation":"In syntax-bridge's token-collector, when a buffered token is flushed to the sink it combines the spans of all collected text ranges and calls .expect(\"expected at least one token\") on the combined span. The invariant is that flush is only called after at least one span was pushed; hitting the panic means the token map and buffer got out of sync — text was buffered with no recorded span.","triggerScenarios":"Feeding a token tree into the converter where a flush (via token()/push with kind) occurs with an empty span list — i.e., malformed or degenerate proc-macro/token-tree input where no text position range was recorded before the flush at line 1011.","commonSituations":"Converting output of buggy proc macros into syntax trees; token trees containing empty or zero-width tokens; version mismatches between proc-macro server and client producing unexpected tt streams.","solutions":["Inspect the proc-macro/token-tree input being converted; fix the producer so every emitted token has a span.","Update rust-analyzer/proc-macro server versions to match — older versions emitted spans the collector didn't record.","Reduce the failing input to a minimal token tree and file a bug with the reproduction if the input looks valid."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before converting, ensure every tt::TokenTree you feed carries a span\nfn has_spans(tt: &tt::TopSubtree) -> bool {\n    let mut ok = true;\n    tt.top_subtree().token_trees().for_each(|t| match t.leaf() {\n        Some(leaf) => ok &= leaf.span().range != None.into(),\n        None => {}\n    });\n    ok\n}","typeGuard":null,"tryCatchPattern":"// Bridge panics via expect; isolate conversion\nlet result = std::panic::catch_unwind(AssertUnwindSafe(|| {\n    syntax_bridge::syntax_tree_to_token_tree(...) \n}));\nmatch result {\n    Ok(tree) => tree,\n    Err(_) => report_bridge_bug(input),\n}","preventionTips":["Keep proc-macro server and rust-analyzer versions in sync.","Test proc-macro expansions on minimal inputs before large builds.","Never feed hand-crafted token trees with missing/zero spans into the bridge.","Report panics with the minimal reproducing token tree upstream."],"tags":["rust","proc-macro","syntax","parser"],"backgroundTag":"empty-token-span-invariant","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"}