rust-lang/rust-analyzer · error
`SyntaxContext::as_id()` called on a root `SyntaxContext`
Error message
`SyntaxContext::as_id()` called on a root `SyntaxContext`
What it means
SyntaxContext in rust-analyzer's span crate is backed by salsa; non-root contexts carry a salsa Id while the root context has none. The generated AsId impl calls as_salsa_id() and panics if called on the root SyntaxContext, since root has no underlying Id. This marks a misuse of the salsa ingredient API on the root sentinel value.
Source
Thrown at crates/span/src/hygiene.rs:145
D: zalsa_::serde::Deserializer<'de>,
{
unimplemented!("attempted to deserialize value that cannot set `PERSIST` to false");
}
}
impl SyntaxContext {
pub fn ingredient(zalsa: &zalsa_::Zalsa) -> &zalsa_struct_::IngredientImpl<Self> {
static CACHE: zalsa_::IngredientCache<zalsa_struct_::IngredientImpl<SyntaxContext>> =
zalsa_::IngredientCache::new();
// SAFETY: The ingredient at offset 0 in `JarImpl<SyntaxContext>` has type
// `IngredientImpl<SyntaxContext>`.
unsafe { CACHE.get_or_create::<zalsa_struct_::JarImpl<SyntaxContext>, 0>(zalsa) }
}
}
impl zalsa_::AsId for SyntaxContext {
fn as_id(&self) -> salsa::Id {
self.as_salsa_id().expect("`SyntaxContext::as_id()` called on a root `SyntaxContext`")
}
}
impl zalsa_::FromId for SyntaxContext {
fn from_id(id: salsa::Id) -> Self {
Self::from_salsa_id(id)
}
}
unsafe impl Send for SyntaxContext {}
unsafe impl Sync for SyntaxContext {}
impl zalsa_::SalsaStructInDb for SyntaxContext {
type MemoIngredientMap = salsa::plumbing::MemoIngredientSingletonIndex;
const LEAF_TYPE_IDS: &[salsa::plumbing::ConstTypeId] =
&[salsa::plumbing::ConstTypeId::of::<SyntaxContext>()];
fn lookup_ingredient_index(aux: &zalsa_::Zalsa) -> salsa::plumbing::IngredientIndices {
aux.lookup_jar_by_type::<zalsa_struct_::JarImpl<SyntaxContext>>().into()View on GitHub (pinned to e8f7e90aa3)
Solutions
- Check the context for root before using it as a salsa key (e.g. `if ctx.is_root()` return early / use Option).
- Update rust-analyzer — this indicates an internal hygiene bug fixed upstream.
- If you control the call site, switch the API to handle Option<SyntaxContext> or a non-root guarantee explicitly.
- Write a regression test where a macro expansion with root hygiene flows into the salsa query.
Example fix
// before
let id = zalsa_::AsId::as_id(&ctx);
// after
let id = if ctx.is_root() { None } else { Some(ctx.as_salsa_id().unwrap()) }; Defensive patterns
Strategy: type-guard
Type guard
fn as_id_opt(ctx: &SyntaxContext) -> Option<salsa::Id> { ctx.as_salsa_id().ok() } Try / catch
let Some(id) = ctx.as_salsa_id() else { return fallback_for_root(); }; Prevention
- Special-case the root SyntaxContext before salsa interning/lookups
- Prefer Option-returning helpers over expect on hygiene internals
- Add regression tests for macro expansions with root hygiene
When it happens
Trigger: Code path converts a SyntaxContext to its salsa Id (zalsa_::AsId) when that context is the root context — e.g. interning/lookup logic that failed to special-case the root and passed it into a salsa query or ingredient operation.
Common situations: rust-analyzer development: macro hygiene machinery calling salsa lookups with a default/root SyntaxContext instead of a real interned context; refactorings that lost the root check before invoking salsa APIs.
Related errors
- not a BlockExpr
- this node should have an ast id
- Can't find SyntaxNodePtr {:?} in AstIdMap: {:?}
- Can't find ast id {:?} in AstIdMap: {:?}
- No file available to rename
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/6666dcd017d81acf.
Report an issue: GitHub.