astral-sh/ruff · error
binding definition should have retained declarations
Error message
binding definition should have retained declarations
What it means
The use-def map stores two flavors of entry per definition: record_binding retains the declarations visible at the binding (declarations: Some, use_def.rs:1977) while record_declaration stores None for declaration-only definitions such as the declaring phase of an annotated assignment (use_def.rs:2374). declarations_at_binding unwraps the Some case because it is only meant to be called with definitions that bind a value; the expect fires when a declaration-only definition is passed instead.
Source
Thrown at crates/ty_python_core/src/use_def.rs:1165
nested_laziness: ScopeLaziness,
) -> EnclosingSnapshotResult<'_, 'db> {
let boundness_analysis = if nested_laziness.is_eager() {
BoundnessAnalysis::BasedOnUnboundVisibility
} else {
// TODO: We haven't implemented proper boundness analysis for nonlocal symbols, so we assume the boundness is bound for now.
BoundnessAnalysis::AssumeBound
};
let Some(extra) = self.extra.as_deref() else {
return EnclosingSnapshotResult::NotFound;
};
match extra.enclosing_snapshots.get(snapshot_id) {
Some(InternedEnclosingSnapshotId::Constraint(constraint)) => {
EnclosingSnapshotResult::FoundConstraint(*constraint)
}
Some(InternedEnclosingSnapshotId::Bindings(bindings_id)) => {
EnclosingSnapshotResult::FoundBindings(
self.bindings_iterator(
&self.interned_bindings[*bindings_id],
boundness_analysis,
),
)
}
None => EnclosingSnapshotResult::NotFound,
}
}
pub fn bindings_at_definition(
&self,
definition: Definition<'db>,
) -> BindingWithConstraintsIterator<'_, 'db> {
let bindings = self.definitions_by_definition.get(&definition).map_or_else(
|| ALWAYS_UNBOUND_BINDINGS.as_slice(),
|definitions| &self.interned_bindings[definitions.bindings],
);View on GitHub (pinned to 15f3fe6b15)
Solutions
- Verify the caller passes a binding Definition obtained from the bindings API (bindings_iterator and friends), not an arbitrary definition from the index
- If a new definition kind was added, route it through record_binding (or the combined declare-and-bind path) so declarations are retained for it
- Otherwise minimize the module and file a ty issue: the definition classification for that syntax node is wrong
Defensive patterns
Strategy: validation
Validate before calling
// Only query declarations at definitions that actually bind:
for binding in use_def_map.all_bindings(db, file) {
let decls = use_def_map.declarations_at_binding(binding);
} Prevention
- Pass only binding Definitions (from the bindings iteration APIs) to declarations_at_binding, never raw definitions from the index
- When adding a definition kind, decide explicitly whether it binds, declares, or both, and route it through the matching record_* path
- Cover declaration-only syntax (`x: int` with no value) in tests that query declarations
When it happens
Trigger: Calling declarations_at_binding with a Definition that was classified as a declaration rather than a binding, e.g., the declare-only half of an annotated assignment like `x: int` with no right-hand side, or any newly added definition kind routed through record_declaration but later queried through the binding API.
Common situations: Contributor changes that add a new definition kind or alter the declare-vs-bind split of annotated assignments without updating the retention path; semantic query code that iterates all definitions instead of only binding definitions.
Related errors
- extra use-def data should have been retained
- Expected live-declarations length to fit into a u32
- 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@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/b65432335f05b930.
Report an issue: GitHub.