rust-lang/rust-analyzer · error
syntax annotation id overflow
Error message
syntax annotation id overflow
What it means
The syntax editor hands each annotation a unique nonzero u32 id from a per-process `AtomicU32` counter. When the counter reaches `u32::MAX`, the next increment yields 0, which `NonZeroU32::new` rejects, so the code panics with 'syntax annotation id overflow'. It only fires after ~4.29 billion annotations have been created, which the authors deem impossible in practice.
Source
Thrown at crates/syntax/src/syntax_editor.rs:276
self.new_root
.descendants()
.find(|it| it.kind() == kind && it.text_range().start() - new_root_start == old_start)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SyntaxAnnotation(NonZeroU32);
impl Default for SyntaxAnnotation {
fn default() -> Self {
static COUNTER: AtomicU32 = AtomicU32::new(1);
// Only consistency within a thread matters, as SyntaxElements are !Send
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
Self(NonZeroU32::new(id).expect("syntax annotation id overflow"))
}
}
/// Position describing where to insert elements
#[derive(Debug)]
pub struct Position {
repr: PositionRepr,
}
impl Position {
pub(crate) fn parent(&self) -> SyntaxNode {
self.place().0
}
pub(crate) fn place(&self) -> (SyntaxNode, usize) {
match &self.repr {
PositionRepr::FirstChild(parent) => (parent.clone(), 0),
PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1),View on GitHub (pinned to e8f7e90aa3)
Solutions
- Restart the long-running process so the counter resets (the counter is process-global)
- Fix the unbounded loop that is creating annotations/editors in a hot path
- Reduce annotation churn by reusing one editor per batch of edits instead of creating editors per edit
- Patch to use AtomicU64 or wrap-with-reuse if you genuinely need more than 2^32 annotation ids
Example fix
null
Defensive patterns
Strategy: retry
Validate before calling
// No pre-call check exists; only mitigate structurally:
// bound the number of SyntaxEditor annotations per process (e.g. a counter guard).
let annotations_created = AtomicU64::new(0);
if annotations_created.fetch_add(1, Ordering::Relaxed) > u32::MAX as u64 {
// fail fast / restart process instead of panicking deep in NonZeroU32
} Try / catch
// This is a panic, not a Result; catch at a process/task boundary.
let result = std::panic::catch_unwind(AssertUnwindSafe(|| run_edit_batch()));
if result.is_err() { restart_worker(); } Prevention
- Do not create SyntaxEditors/annotations in unbounded loops
- Recycle a single editor per batch of edits
- Restart long-running IDE processes periodically or on memory/counter pressure
When it happens
Trigger: Creating more than `u32::MAX - 1` syntax annotations in one process lifetime, i.e. repeatedly running `SyntaxEditor`-based rewrites (each annotation allocation bumps the counter) over an extremely long-lived process such as an IDE/language-server session.
Common situations: An editor or LSP process kept alive for months continuously re-parsing and rewriting files with annotation-heavy edits; a runaway loop that creates editors/annotations without bound.
Related errors
- reachable plan nodes are not discarded
- an edit target must still be present
- We explicitly do not provide canonicalization API, as that i
- bad kind {other}
- bad spacing {other}
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/be40370d70f82b4f.
Report an issue: GitHub.