astral-sh/ruff · info
Expected outer if to have indented body and no else
Error message
Expected outer if to have indented body and no else
What it means
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.
Source
Thrown at crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs:368
let indented_block = match_indented_block(&mut embedding.body)?;
indented_block.indent = Some(outer_indent);
let Some(statement) = indented_block.body.first_mut() else {
bail!("Expected indented block to have at least one statement")
};
statement
};
let outer_if = match_if(statement)?;
let libcst_native::If {
body: libcst_native::Suite::IndentedBlock(outer_body),
orelse: None,
..
} = outer_if
else {
bail!("Expected outer if to have indented body and no else")
};
let [
libcst_native::Statement::Compound(libcst_native::CompoundStatement::If(
inner_if @ libcst_native::If { orelse: None, .. },
)),
] = &mut *outer_body.body
else {
bail!("Expected one inner if statement");
};
outer_if.test =
libcst_native::Expression::BooleanOperation(Box::new(libcst_native::BooleanOperation {
left: Box::new(parenthesize_and_operand(outer_if.test.clone())),
operator: libcst_native::BooleanOp::And {
whitespace_before: space(),
whitespace_after: space(),
},View on GitHub (pinned to 15f3fe6b15)
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
Example fix
// before
if a:
if b:
do()
else:
other()
// after (manual)
if a and b:
do()
elif a:
other() Defensive patterns
Strategy: validation
Validate before calling
python - <<'EOF'
import ast, sys
tree = ast.parse(open(sys.argv[1]).read())
for n in ast.walk(tree):
if isinstance(n, ast.If) and (n.orelse or isinstance(getattr(n, 'finalbody', None), list)):
print('if/else at line', n.lineno, 'is not SIM102-autofixable')
EOF Prevention
- 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
When it happens
Trigger: `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`.
Common situations: `elif` chains that look collapsible, trailing `else` blocks after the inner `if`, unusual one-liner `if a: if b: c` formatting.
Related errors
- Failed to collapse `if`: {err}
- Unable to fix multiline statement
- Expected indented block to have at least one statement
- Expected one inner if statement
- Failed to collapse `with`: {err}
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-09-05).
Data as JSON: /api/errors/5725824cf3c93f63.
Report an issue: GitHub.