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

  1. Initialize the list with a simple `xs = []` (or `xs: list = []`) statement immediately before the loop, then rerun the fix
  2. Rewrite the loop into a list comprehension manually
  3. 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

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


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/6e10bf93f751d58e. Report an issue: GitHub.