{"record":{"id":"6e10bf93f751d58e","repo":"astral-sh/ruff","slug":"binding-must-have-a-statement-to-convert-into-a-li","errorCode":null,"errorMessage":"Binding must have a statement to convert into a list comprehension","messagePattern":"Binding must have a statement to convert into a list comprehension","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs","lineNumber":478,"sourceCode":"            };\n            let text_to_replace = format!(\n                \"{}{indentation}{comprehension_body}\",\n                for_loop_inline_comments.join(&indentation)\n            );\n            Ok(Fix::unsafe_edit(Edit::range_replacement(\n                text_to_replace,\n                for_stmt.range,\n            )))\n        }\n        ComprehensionType::ListComprehension => {\n            let binding_stmt = binding.statement(semantic);\n            let binding_stmt_range = binding_stmt\n                .and_then(|stmt| match stmt {\n                    ast::Stmt::AnnAssign(assign) => Some(assign.range),\n                    ast::Stmt::Assign(assign) => Some(assign.range),\n                    _ => None,\n                })\n                .ok_or(anyhow!(\n                    \"Binding must have a statement to convert into a list comprehension\"\n                ))?;\n\n            // If there are multiple binding statements in one line, we don't want to accidentally delete them\n            // Instead, we just delete the binding statement and leave any comments where they are\n            let (binding_stmt_deletion_range, binding_is_multiple_stmts) =\n                statement_deletion_range(checker, binding_stmt_range);\n\n            let annotations = match binding_stmt.and_then(|stmt| stmt.as_ann_assign_stmt()) {\n                Some(assign) => format!(\": {}\", locator.slice(assign.annotation.range())),\n                None => String::new(),\n            };\n\n            let comments_to_move = if binding_is_multiple_stmts {\n                for_loop_inline_comments\n            } else {\n                let mut new_comments =\n                    comment_strings_in_range(checker, binding_stmt_deletion_range, &[]);","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/astral-sh/ruff/blob/26f38c119cac42e4d320ba08f09224fdec74af2c/crates/ruff_linter/src/rules/perflint/rules/manual_list_comprehension.rs#L460-L496","documentation":"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.","triggerScenarios":"`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).","commonSituations":"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.","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"],"exampleFix":"# before (fix fails: binding not a plain assignment)\na, xs = 1, []\nfor i in range(3):\n    xs.append(i)\n# after (fixable form)\nxs = []\nfor i in range(3):\n    xs.append(i)\n# or directly:\nxs = [i for i in range(3)]","handlingStrategy":"validation","validationCode":"def list_binding_is_simple(loop_target: str, source: str) -> bool:\n    import ast\n    tree = ast.parse(source)\n    for node in ast.walk(tree):\n        if isinstance(node, (ast.Assign, ast.AnnAssign)):\n            targets = node.targets if isinstance(node, ast.Assign) else [node.target]\n            if any(getattr(t, 'id', None) == loop_target for t in targets):\n                return len(targets) == 1 if isinstance(node, ast.Assign) else True\n    return False","typeGuard":"def is_simple_binding(stmt: ast.stmt) -> bool:\n    return isinstance(stmt, (ast.Assign, ast.AnnAssign))","tryCatchPattern":null,"preventionTips":["Initialize loop-accumulated lists with a standalone `xs = []` statement","Avoid tuple/chained assignment for list accumulators","Prefer writing list comprehensions directly"],"tags":["ruff","autofix","perflint","list-comprehension"],"backgroundTag":"autofix-generation-failed","analyzedSha":"26f38c119cac42e4d320ba08f09224fdec74af2c","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}