astral-sh/ruff · info

Expected one inner if statement

Error message

Expected one inner if statement

What it means

After matching the outer `if`, the collapse requires its indented body to contain exactly one statement and that statement to be a plain `if` without `orelse`. Anything else (multiple statements, an `elif`/`else` on the inner node, other compound statements) bails with this message, since `if a: if b:` can only merge when the inner body is a single guard `if`.

Source

Thrown at crates/ruff_linter/src/rules/flake8_simplify/rules/collapsible_if.rs:377

    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(),
            },
            right: Box::new(parenthesize_and_operand(inner_if.test.clone())),
            lpar: vec![],
            rpar: vec![],
        }));
    outer_if.body = inner_if.body.clone();

    // Reconstruct and reformat the code.
    let module_text = tree.codegen_stylist(stylist);
    let module_text = if outer_indent.is_empty() {

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Move any extra statements out of the outer body (or merge them into the innermost block) so only the inner `if` remains, then rerun the fix
  2. Manually combine conditions into `if a and b:` when restructuring by hand
  3. Add `# noqa: SIM102` if the multi-statement nesting is intentional

Example fix

// before
if a:
    setup()
    if b:
        do()
// after
setup_ok = not a or False  # or restructure:
if a and b:
    do()
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 len(n.body) == 1 and isinstance(n.body[0], ast.If):
        inner = n.body[0]
        if not (len(inner.body) >= 1 and not inner.orelse) or not n.orelse:
            pass
        if inner.orelse or len(n.body) != 1:
            print('not a single plain inner if at line', inner.lineno)
EOF

Prevention

When it happens

Trigger: `ruff check --fix` on an outer `if` whose body holds several statements before/after the inner `if`, or the inner `if` has its own `else`/`elif`.

Common situations: Nested guards with trailing statements (`if a: x = 1; if b: ...` on separate lines), inner `if` with `else`, bodies mixing simple and compound statements.

Related errors


AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-09-05). Data as JSON: /api/errors/da41eb63299b9412. Report an issue: GitHub.