{"record":{"id":"5725824cf3c93f63","repo":"astral-sh/ruff","slug":"expected-outer-if-to-have-indented-body-and-no-els","errorCode":null,"errorMessage":"Expected outer if to have indented body and no else","messagePattern":"Expected outer if to have indented body and no else","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs","lineNumber":368,"sourceCode":"\n        let indented_block = match_indented_block(&mut embedding.body)?;\n        indented_block.indent = Some(outer_indent);\n\n        let Some(statement) = indented_block.body.first_mut() else {\n            bail!(\"Expected indented block to have at least one statement\")\n        };\n        statement\n    };\n\n    let outer_if = match_if(statement)?;\n\n    let libcst_native::If {\n        body: libcst_native::Suite::IndentedBlock(outer_body),\n        orelse: None,\n        ..\n    } = outer_if\n    else {\n        bail!(\"Expected outer if to have indented body and no else\")\n    };\n\n    let [\n        libcst_native::Statement::Compound(libcst_native::CompoundStatement::If(\n            inner_if @ libcst_native::If { orelse: None, .. },\n        )),\n    ] = &mut *outer_body.body\n    else {\n        bail!(\"Expected one inner if statement\");\n    };\n\n    outer_if.test =\n        libcst_native::Expression::BooleanOperation(Box::new(libcst_native::BooleanOperation {\n            left: Box::new(parenthesize_and_operand(outer_if.test.clone())),\n            operator: libcst_native::BooleanOp::And {\n                whitespace_before: space(),\n                whitespace_after: space(),\n            },","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/astral-sh/ruff/blob/15f3fe6b15a5f00172f34b0f542f8ea277f5a586/crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs#L350-L386","documentation":"The SIM102 collapse destructures the outer `if` expecting an indented body and no `orelse`; `elif`/`else` clauses (or a suite libcst parsed as lines rather than an `IndentedBlock`) fail this pattern and abort the fix, because merging conditions is only well-defined for a plain two-level `if` nesting.","triggerScenarios":"`ruff check --fix` on `if a: ... elif b:` or `if a: ... else: ...` where the nested-if match put an `elif`/`else` in the outer node, or an outer `if` body libcst didn't represent as an `IndentedBlock`.","commonSituations":"`elif` chains that look collapsible, trailing `else` blocks after the inner `if`, unusual one-liner `if a: if b: c` formatting.","solutions":["Rewrite so the outer `if` has no else branch, or merge conditions manually into `if a and b:`","Restructure `elif` chains explicitly before relying on the autofix","Suppress SIM102 for constructs where the nesting is intentional"],"exampleFix":"// before\nif a:\n    if b:\n        do()\n    else:\n        other()\n// after (manual)\nif a and b:\n    do()\nelif a:\n    other()","handlingStrategy":"validation","validationCode":"python - <<'EOF'\nimport ast, sys\ntree = ast.parse(open(sys.argv[1]).read())\nfor n in ast.walk(tree):\n    if isinstance(n, ast.If) and (n.orelse or isinstance(getattr(n, 'finalbody', None), list)):\n        print('if/else at line', n.lineno, 'is not SIM102-autofixable')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Restructure `elif` chains manually before requesting SIM102 fixes","Keep nested guard `if`s free of `else` branches when a merge is intended","Review `ruff --diff` output to confirm which SIM102 findings are autofixable"],"tags":["ruff","flake8-simplify","sim102","elif"],"backgroundTag":"linter-autofix-skipped","analyzedSha":"15f3fe6b15a5f00172f34b0f542f8ea277f5a586","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}