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
- Ensure `last_target_option` is always set in the same branch that pushes a new entry into `duplicates` (never update one without the other)
- Restructure to a HashMap<ComparableExpr, Vec<usize>> so the correspondence is enforced by construction instead of by `.last_mut()`
- 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
- Update correlated structures in the same branch/atomic step
- Prefer HashMap keyed by target over parallel last-element invariants
- Add debug_assertions for paired-structure invariants
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
- Expected `NamedTuple` definition r.h.s. to be a call express
- PySourceType always parses into a module
- Stack to never be empty
- module to include at least one segment
- storage-free owned constraint sets must have terminal roots
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-09-05).
Data as JSON: /api/errors/bfd1e24ea5ac77ef.
Report an issue: GitHub.