astral-sh/ruff · error

last_target should have a corresponding entry

Error message

last_target should have a corresponding entry

What it means

In the TC (flake8_simplify) `duplicate_isinstance_call` rule, `duplicates` maps each distinct isinstance target to the list of call indices sharing it. The code pushes onto `duplicates.last_mut()` immediately after confirming the last target equals the current one; the expect asserts that correspondence. It is an internal invariant: last_target_option and duplicates are updated in lockstep.

Source

Thrown at crates/ruff_linter/src/rules/flake8_simplify/rules/ast_bool_op.rs:363

    };

    // Locate duplicate `isinstance` calls, represented as a vector of vectors
    // of indices of the relevant `Expr` instances in `values`.
    let mut duplicates: Vec<Vec<usize>> = Vec::new();
    let mut last_target_option: Option<ComparableExpr> = None;
    for (index, call) in values.iter().enumerate() {
        let Some(target) = isinstance_target(call, checker.semantic()) else {
            last_target_option = None;
            continue;
        };

        if last_target_option
            .as_ref()
            .is_some_and(|last_target| *last_target == ComparableExpr::from(target))
        {
            duplicates
                .last_mut()
                .expect("last_target should have a corresponding entry")
                .push(index);
        } else {
            last_target_option = Some(target.into());
            duplicates.push(vec![index]);
        }
    }

    // Generate a `Diagnostic` for each duplicate.
    for indices in duplicates {
        if indices.len() > 1 {
            // Grab the target used in each duplicate `isinstance` call (e.g., `obj` in
            // `isinstance(obj, int)`).
            let target = if let Expr::Call(ast::ExprCall {
                arguments: Arguments { args, .. },
                ..
            }) = &values[indices[0]]
            {
                args.first()

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Ensure `last_target_option` is always set in the same branch that pushes a new entry into `duplicates` (never update one without the other)
  2. Restructure to a HashMap<ComparableExpr, Vec<usize>> so the correspondence is enforced by construction instead of by `.last_mut()`
  3. Add a debug_assert that duplicates.len() equals the number of distinct targets seen

Example fix

// before
duplicates.last_mut().expect("last_target should have a corresponding entry").push(index);
// after
if let Some(entry) = duplicates.last_mut() {
    entry.push(index);
} else {
    // fall back to creating the entry, keeping the invariant explicit
    duplicates.push(vec![index]);
}
Defensive patterns

Strategy: validation

Validate before calling

debug_assert_eq!(last_target_option.is_some(), !duplicates.is_empty());

Type guard

if duplicates.last_mut().is_some() { /* safe to push */ }

Prevention

When it happens

Trigger: Not reachable from user input. A panic would mean the lockstep invariant was broken — e.g. code between iterations mutating `duplicates` (clearing/popping) without resetting `last_target_option`, so the Option says a duplicate exists while `duplicates` is empty.

Common situations: Hit only by contributors refactoring the iteration loop in ast_bool_op.rs, e.g. reordering the else branch or filtering duplicates after construction.

Related errors


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