astral-sh/ruff · error
Expected live-declarations length to fit into a u32
Error message
Expected live-declarations length to fit into a u32
What it means
RetainedDeclarationsBuilder flattens every scope's live use-def declarations into one Vec and records each scope's end offset as a u32, the index type used to address RetainedDeclarations. The expect fires when the cumulative live-declaration count pushed into a single UseDefMap exceeds u32::MAX (4,294,967,295) and can no longer be represented as an end offset. This is a capacity invariant of the compact interning scheme, not a configurable limit; a normal process would exhaust memory long before reaching it.
Source
Thrown at crates/ty_python_core/src/use_def.rs:557
}
struct RetainedDeclarationsBuilder {
ends: IndexVec<InternedDeclarationsId, u32>,
live_declarations: Vec<LiveDeclaration>,
}
impl RetainedDeclarationsBuilder {
fn with_capacity(declarations: usize) -> Self {
Self {
ends: IndexVec::with_capacity(declarations),
live_declarations: Vec::with_capacity(declarations),
}
}
fn push(&mut self, declarations: &Declarations) -> InternedDeclarationsId {
self.live_declarations.extend(declarations.iter().cloned());
let end = u32::try_from(self.live_declarations.len())
.expect("Expected live-declarations length to fit into a u32");
self.ends.push(end)
}
fn finish(
self,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
) -> RetainedDeclarations {
for declaration in &self.live_declarations {
reachability_constraints.mark_used(declaration.reachability_constraint);
}
RetainedDeclarations {
ends: self.ends.into(),
live_declarations: self.live_declarations.into_boxed_slice(),
}
}
}
impl Index<InternedDeclarationsId> for RetainedDeclarations {View on GitHub (pinned to 15f3fe6b15)
Solutions
- Split the oversized module into several files so each use-def map stays far below billions of declarations
- If the file is not abnormally large, capture it and file a ty issue: the panic signals a runaway declaration-interning loop (the same declarations interned repeatedly)
- As a contributor, if legitimate inputs could ever exceed u32::MAX, widen the end offsets to u64 or chunk the retained vectors rather than unwrapping the conversion
Defensive patterns
Strategy: fallback
Try / catch
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check_file(&db, file)));
match result {
Ok(diags) => diags,
Err(payload) => { log::warn!("ty panicked on {}: {:?}", file, payload); vec![] }
} Prevention
- Split machine-generated mega-modules so single-file use-def maps stay small
- If you embed ty, isolate per-file checking with catch_unwind so one pathological file does not kill the run
- Treat this panic as a canary: a normal-sized file hitting it means an interning loop bug - report it
When it happens
Trigger: Building the use-def map for a module whose total live declarations, summed across every scope and place that gets interned, crosses 4,294,967,295. Only astronomically large or adversarially generated input can approach it, or a runaway interning loop that duplicates declarations per scope.
Common situations: Machine-generated multi-gigabyte Python modules; fuzzing harnesses that synthesize huge numbers of scopes; in practice, hitting this panic almost always indicates a bug that duplicates declarations rather than a legitimately large file, because out-of-memory occurs first.
Related errors
- extra use-def data should have been retained
- 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@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/ae979dc0139c284f.
Report an issue: GitHub.