{"record":{"id":"456b1cb313a077a0","repo":"iced-rs/iced","slug":"caches-must-not-be-empty","errorCode":null,"errorMessage":"Caches must not be empty","messagePattern":"Caches must not be empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"highlighter/src/lib.rs","lineNumber":89,"sourceCode":"            self.current_line = snapshot * LINES_PER_SNAPSHOT;\n        } else {\n            self.caches.truncate(1);\n            self.current_line = 0;\n        }\n\n        let (parser, stack) = self.caches.last().cloned().unwrap_or_else(|| {\n            (\n                parsing::ParseState::new(self.syntax),\n                parsing::ScopeStack::new(),\n            )\n        });\n\n        self.caches.push((parser, stack));\n    }\n\n    fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> {\n        if self.current_line / LINES_PER_SNAPSHOT >= self.caches.len() {\n            let (parser, stack) = self.caches.last().expect(\"Caches must not be empty\");\n\n            self.caches.push((parser.clone(), stack.clone()));\n        }\n\n        self.current_line += 1;\n\n        let (parser, stack) = self.caches.last_mut().expect(\"Caches must not be empty\");\n\n        let ops = parser.parse_line(line, &SYNTAXES).unwrap_or_default();\n\n        Box::new(scope_iterator(ops, line, stack, &self.highlighter))\n    }\n\n    fn current_line(&self) -> usize {\n        self.current_line\n    }\n}\n","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/iced-rs/iced/blob/3de451447bd28217bb535632867550908e29d5d0/highlighter/src/lib.rs#L71-L107","documentation":"The syntect-backed Highlighter keeps parser snapshots (ParseState, ScopeStack) every LINES_PER_SNAPSHOT = 50 lines in caches: Vec<...>. highlight_line asserts with caches.last().expect(\"Caches must not be empty\") (highlighter/src/lib.rs:89) that at least one snapshot exists. The stock lifecycle (new, update, change_line) always keeps the vec non-empty - change_line truncates then pushes - so this panic signals a broken invariant: a bug in the iced/highlighter version in use or a hand-written Highlighter trait implementation driving the editor.","triggerScenarios":"highlight_line called when the caches vec is empty - not producible via Highlighter::new/update/change_line; the line-89 expect also fires when current_line has advanced past the available snapshots (current_line / 50 >= caches.len()) while caches is somehow empty, e.g. a custom trait impl that cleared it or a fork with an off-by-one in change_line.","commonSituations":"Custom core::text::highlighter::Highlighter implementations that manage snapshots themselves; older/forked iced versions with change_line bugs; driving highlight_line directly in tests or fuzzing outside the editor widget lifecycle.","solutions":["Use only the stock lifecycle: Highlighter::new(Settings), update, change_line, highlight_line","If you implement the Highlighter trait yourself, guarantee caches always holds at least one entry before any highlight_line call","Call change_line(line) whenever highlighting jumps to a new region instead of only letting current_line advance","If hit with unmodified iced, upgrade iced/iced_highlighter and report the panic with a reproducer"],"exampleFix":"// before - custom Highlighter impl with empty caches\nfn highlight_line(&mut self, line: &str) -> Self::Iterator<'_> {\n    let (parser, stack) = self.caches.last().expect(\"Caches must not be empty\");\n    // ...\n}\n\n// after - keep the invariant: always seed one snapshot\nfn new(settings: &Settings) -> Self {\n    Self {\n        caches: vec![(ParseState::new(syntax), ScopeStack::new())],\n        current_line: 0,\n        // ...\n    }\n}","handlingStrategy":"validation","validationCode":"// when highlighting jumps to a new region, resync snapshots first\nif target_line / 50 != highlighter.current_line() / 50 {\n    highlighter.change_line(target_line);\n}\nhighlighter.highlight_line(line_text);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Drive the highlighter only through new/update/change_line/highlight_line","In custom Highlighter impls, seed one snapshot in the constructor and keep it non-empty","Call change_line when jumping instead of relying on current_line advancing","Report stock-code hits upstream and upgrade iced"],"tags":["rust","iced","highlighter","syntect","invariant","panic"],"backgroundTag":"collection-unexpectedly-empty","analyzedSha":"3de451447bd28217bb535632867550908e29d5d0","analyzedAt":"2026-08-17T10:40:26.997Z","contentChangedAt":"2026-08-17T10:40:26.997Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}