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
- Re-resolve the id from the current file's AstIdMap (query the fresh AstIdMap salsa input) instead of reusing a cached id
- Ensure ids are never stored/compared across different files or salsa revisions
- 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
- Treat FileAstId as revision-scoped: re-query the AstIdMap after any file change
- Never persist ids across saves or across files
- Use semantic keys (names/paths) plus fresh id resolution instead of long-lived raw ids
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
- Can't find SyntaxNodePtr {:?} in AstIdMap: {:?}
- AstIdMap node mismatch with node `{ptr:?}`
- 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/8f120ce24badaf0e.
Report an issue: GitHub.