astral-sh/ruff · info

Failed to collapse `if`: {err}

Error message

Failed to collapse `if`: {err}

What it means

Rule SIM102 (collapsible nested `if`) attempts to rewrite `if a: if b:` into `if a and b:` using a libcst-native transformer; if that transformer returns an error, the check bails with this message wrapping the inner error. The diagnostic remains, only the autofix is lost.

Source

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

                            (&nested_if).into(),
                            checker.locator(),
                            checker.settings().pycodestyle.max_line_length,
                            checker.settings().tab_size,
                        )
                    }) {
                        Ok(Some(Fix::applicable_edit(
                            edit,
                            if is_collapsible_if_fix_safe_enabled(checker.settings()) {
                                Safe
                            } else {
                                Unsafe
                            },
                        )))
                    } else {
                        Ok(None)
                    }
                }
                Err(err) => bail!("Failed to collapse `if`: {err}"),
            }
        });
    }
}

#[derive(Debug, Clone, Copy)]
pub(super) enum NestedIf<'a> {
    If(&'a ast::StmtIf),
    Elif(&'a ElifElseClause),
}

impl<'a> NestedIf<'a> {
    fn body(self) -> &'a [Stmt] {
        match self {
            NestedIf::If(stmt_if) => &stmt_if.body,
            NestedIf::Elif(clause) => &clause.body,
        }
    }

View on GitHub (pinned to 15f3fe6b15)

Solutions

  1. Manually combine the conditions: replace `if a: if b:` with `if a and b:` and dedent the body, then rerun ruff
  2. Read the wrapped `{err}` detail to find the libcst failure and adjust the source (e.g. move comments) so the fix applies
  3. Add `# noqa: SIM102` if the nesting is intentional (e.g. readability or short-circuit side effects)

Example fix

// before
if a:
    if b:
        do()
// after
if a and b:
    do()
Defensive patterns

Strategy: try-catch

Validate before calling

# only simple two-level nesting is fixable
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) and not n.orelse and not n.body[0].orelse:
        print('SIM102 candidate at line', n.lineno)
EOF

Try / catch

ruff check --select SIM102 --fix . 2>&1 | tee fix.log; grep -q 'Failed to collapse' fix.log && echo 'collapse failed: merge conditions manually'

Prevention

When it happens

Trigger: `ruff check --fix` on a nested `if` (including `elif` forms) whose libcst round-trip fails — e.g. the outer body contains comments or structure the collapse transformer rejects.

Common situations: Nested ifs with `elif` chains, comments between the two `if` headers, or unusual indentation that the libcst collapse can't normalize.

Related errors


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