rust-lang/rust-analyzer · error

Can't find ast id {:?} in AstIdMap: {:?}

Error message

Can't find ast id {:?} in AstIdMap:
{:?}

What it means

`AstIdMap::get_erased` looks up an ErasedFileAstId in the map's open-addressed hash table and panics when the id is absent. Every valid id must have been produced by this same map, so a miss means the id is stale (map was rebuilt after a file edit) or foreign (from another file/map). The panic dumps the id and all mapped entries to aid debugging.

Source

Thrown at crates/span/src/ast_id.rs:852

        let hash = hash_ptr(&ptr);
        let idx = *self.ptr_map.find(hash, |&idx| self.arena[idx].0 == ptr)?;
        Some(self.arena[idx].1)
    }

    // Don't bound on `AstIdNode` here, because `BlockExpr`s are also valid here (`ast::BlockExpr`
    // doesn't always have a matching `FileAstId`, but a `FileAstId<ast::BlockExpr>` always has
    // a matching node).
    pub fn get<N: AstNode>(&self, id: FileAstId<N>) -> AstPtr<N> {
        let ptr = self.get_erased(id.raw);
        AstPtr::try_from_raw(ptr)
            .unwrap_or_else(|| panic!("AstIdMap node mismatch with node `{ptr:?}`"))
    }

    pub fn get_erased(&self, id: ErasedFileAstId) -> SyntaxNodePtr {
        let hash = hash_ast_id(&id);
        match self.id_map.find(hash, |&idx| self.arena[idx].1 == id) {
            Some(&idx) => self.arena[idx].0,
            None => panic!(
                "Can't find ast id {:?} in AstIdMap:\n{:?}",
                id,
                self.arena.iter().map(|(_id, i)| i).collect::<Vec<_>>(),
            ),
        }
    }
}

#[cfg(not(no_salsa_async_drops))]
impl Drop for AstIdMap {
    fn drop(&mut self) {
        let arena = std::mem::take(&mut self.arena);
        let ptr_map = std::mem::take(&mut self.ptr_map);
        let id_map = std::mem::take(&mut self.id_map);
        static AST_ID_MAP_DROP_THREAD: std::sync::OnceLock<
            std::sync::mpsc::Sender<(
                Arena<(SyntaxNodePtr, ErasedFileAstId)>,
                hashbrown::HashTable<ArenaId>,

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Re-resolve the id from the current file's AstIdMap (query the fresh AstIdMap salsa input) instead of reusing a cached id
  2. Ensure ids are never stored/compared across different files or salsa revisions
  3. If persisting ids, validate the map revision before lookup and fall back to re-resolution on miss
Defensive patterns

Strategy: validation

Validate before calling

// Check the id resolves before using it (staleness guard):
if !map.contains(id.erased()) {
    id = recompute_file_ast_id(current_parse, symbol_key)?; // re-derive from current revision
}

Try / catch

let ptr = std::panic::catch_unwind(AssertUnwindSafe(|| map.get_erased(id)))
    .ok()
    .or_else(|| recompute_and_lookup(id))?;

Prevention

When it happens

Trigger: Looking up a FileAstId/ErasedFileAstId after the file changed and the AstIdMap was recomputed; using an id obtained from a different file's AstIdMap; decoding an id from raw bits incorrectly and querying it directly via `get_erased`.

Common situations: Salsa queries caching ids across revisions while the underlying file changed (edits, saves by formatter); name-resolution or completion code holding ids across an inconsistent input snapshot; tests constructing ids by hand.

Related errors


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