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

  1. Check the context for root before using it as a salsa key (e.g. `if ctx.is_root()` return early / use Option).
  2. Update rust-analyzer — this indicates an internal hygiene bug fixed upstream.
  3. If you control the call site, switch the API to handle Option<SyntaxContext> or a non-root guarantee explicitly.
  4. 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

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


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