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

  1. Split the oversized module into several files so each use-def map stays far below billions of declarations
  2. 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)
  3. 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

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


AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20). Data as JSON: /api/errors/ae979dc0139c284f. Report an issue: GitHub.