astral-sh/ruff · warning
Binding must have a statement to convert into a list compreh
Error message
Binding must have a statement to convert into a list comprehension
What it means
This is a fix-generation failure in PerfLint's PLR6203-style manual-list-comprehension fix. To rewrite `for`-loop list building into a list comprehension, the fixer must first remove the binding statement (e.g. `xs = []` or `xs: list = []`) that initializes the target list. If the binding statement found for the variable is neither an `Assign` nor an `AnnAssign`, no range can be computed for deletion and the conversion aborts.
Source
Thrown at crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs:478
};
let text_to_replace = format!(
"{}{indentation}{comprehension_body}",
for_loop_inline_comments.join(&indentation)
);
Ok(Fix::unsafe_edit(Edit::range_replacement(
text_to_replace,
for_stmt.range,
)))
}
ComprehensionType::ListComprehension => {
let binding_stmt = binding.statement(semantic);
let binding_stmt_range = binding_stmt
.and_then(|stmt| match stmt {
ast::Stmt::AnnAssign(assign) => Some(assign.range),
ast::Stmt::Assign(assign) => Some(assign.range),
_ => None,
})
.ok_or(anyhow!(
"Binding must have a statement to convert into a list comprehension"
))?;
// If there are multiple binding statements in one line, we don't want to accidentally delete them
// Instead, we just delete the binding statement and leave any comments where they are
let (binding_stmt_deletion_range, binding_is_multiple_stmts) =
statement_deletion_range(checker, binding_stmt_range);
let annotations = match binding_stmt.and_then(|stmt| stmt.as_ann_assign_stmt()) {
Some(assign) => format!(": {}", locator.slice(assign.annotation.range())),
None => String::new(),
};
let comments_to_move = if binding_is_multiple_stmts {
for_loop_inline_comments
} else {
let mut new_comments =
comment_strings_in_range(checker, binding_stmt_deletion_range, &[]);View on GitHub (pinned to 26f38c119c)
Solutions
- Initialize the list with a simple `xs = []` (or `xs: list = []`) statement immediately before the loop, then rerun the fix
- Rewrite the loop into a list comprehension manually
- Restructure away tuple/multiple assignment of the list variable if you want the fix to apply
Example fix
# before (fix fails: binding not a plain assignment)
a, xs = 1, []
for i in range(3):
xs.append(i)
# after (fixable form)
xs = []
for i in range(3):
xs.append(i)
# or directly:
xs = [i for i in range(3)] Defensive patterns
Strategy: validation
Validate before calling
def list_binding_is_simple(loop_target: str, source: str) -> bool:
import ast
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, (ast.Assign, ast.AnnAssign)):
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
if any(getattr(t, 'id', None) == loop_target for t in targets):
return len(targets) == 1 if isinstance(node, ast.Assign) else True
return False Type guard
def is_simple_binding(stmt: ast.stmt) -> bool:
return isinstance(stmt, (ast.Assign, ast.AnnAssign)) Prevention
- Initialize loop-accumulated lists with a standalone `xs = []` statement
- Avoid tuple/chained assignment for list accumulators
- Prefer writing list comprehensions directly
When it happens
Trigger: `convert_to_list_extend` (via `manual_list_comprehension`) encounters a loop that appends/extends a list, but the statement binding that list is some other statement kind (e.g. assigned in a tuple-unpacking, `AugAssign`, walrus, or defined outside the recognized statement kinds).
Common situations: Autofixing performance-oriented code where the list variable is initialized in an unusual way (multiple assignment targets, chained assignment, initialized in another function); users of `ruff --fix` see the PLR rule reported but not fixed.
Related errors
- Failed to collapse `with`: {err}
- Unable to fix multiline statement
- Expected indented block to have at least one statement
- Expected outer with to have indented body
- Expected one inner with statement
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/6e10bf93f751d58e.
Report an issue: GitHub.