astral-sh/ruff · error
extra use-def data should have been retained
Error message
extra use-def data should have been retained
What it means
UseDefMap stores rarely used tables (bindings-by-use, member place states, enclosing snapshots, loop headers) in an optional extra box that is only allocated when at least one of those collections is non-empty when the map finishes building (use_def.rs:2925-2937). extra() panics when an accessor that reads those tables (loop_header, member end-of-scope queries, bindings-by-use lookups) is called on a map built with extra == None.
Source
Thrown at crates/ty_python_core/src/use_def.rs:870
}
}
pub enum ApplicableConstraints<'map, 'db> {
UnboundBinding(NarrowingEvaluator<'map, 'db>),
ConstrainedBindings(BindingWithConstraintsIterator<'map, 'db>),
}
impl<'db> UseDefMap<'db> {
fn constraint_tables(&self) -> &ConstraintTables<'db> {
self.constraint_tables
.as_deref()
.map_or(&EMPTY_CONSTRAINT_TABLES, |tables| tables)
}
fn extra(&self) -> &UseDefMapExtra {
self.extra
.as_deref()
.expect("extra use-def data should have been retained")
}
pub fn loop_header(&self, id: LoopHeaderId) -> &LoopHeader {
&self.extra().loop_headers[id]
}
pub fn reachability_constraints(&self) -> &ReachabilityConstraints {
&self.constraint_tables().reachability_constraints
}
pub fn predicates(&self) -> &Predicates<'db> {
&self.constraint_tables().predicates
}
pub fn range_reachability(
&self,
) -> impl Iterator<Item = (TextRange, ScopedReachabilityConstraintId)> + '_ {
self.range_reachabilityView on GitHub (pinned to d1087a4b9e)
Solutions
- Re-derive LoopHeaderId/ScopedPlaceId/use IDs from the current semantic model instead of caching them across revisions, so IDs and the map they index come from the same snapshot
- Guard the query: only call extra-backed accessors for constructs that exist in this file's current semantic index (e.g., only for loops actually present)
- If the ID provably came from the same map, capture the backtrace and file a ty issue: the map handed out an ID whose backing table it did not retain
Defensive patterns
Strategy: validation
Validate before calling
// Re-derive IDs from the current snapshot instead of caching them: let index = semantic_index(db, file); // current revision let loop_id = index.loop_header(loop_node); // matches this UseDefMap let header = use_def_map.loop_header(loop_id);
Prevention
- Never cache LoopHeaderId, ScopedPlaceId, or use IDs across Salsa revisions or edits; re-derive them per query
- Only call extra-backed accessors (loop_header, member end-of-scope queries) for constructs present in the current file's semantic index
- When adding new UseDefMap queries, assert the corresponding table exists for the file shape before reading it
When it happens
Trigger: Calling UseDefMap::loop_header(LoopHeaderId), end_of_scope_declarations on a member place, or a bindings-by-use lookup on a map whose four extra collections were all empty at finish time. In practice this means the ID being looked up came from a different or stale use-def map revision, or a builder path recorded IDs but dropped their table.
Common situations: Editor/LSP flows where a cached LoopHeaderId or ScopedPlaceId outlives the Salsa revision that produced it after an edit; new query code that assumes a table always exists without checking that the module contains loops, member places, or multi-bindings.
Related errors
- Expected live-declarations length to fit into a u32
- binding definition should have retained declarations
- should be set because `extract_if` only yields elements with
- Should only ever pass a positive integer to `from_nonnegativ
- argument index should be valid
AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20).
Data as JSON: /api/errors/d6bc26410d8ea977.
Report an issue: GitHub.