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

  1. Restart the long-running process so the counter resets (the counter is process-global)
  2. Fix the unbounded loop that is creating annotations/editors in a hot path
  3. Reduce annotation churn by reusing one editor per batch of edits instead of creating editors per edit
  4. 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

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


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/be40370d70f82b4f. Report an issue: GitHub.