astral-sh/ruff · warning

Compound statement cannot be inlined

Error message

Compound statement cannot be inlined

What it means

Fix-generation failure in the useless-else-on-loop (PLW0120) fix for multi-statement `else` bodies. When the `else` body contains a compound/indented block, the fixer must dedent the body to the loop's own indentation level; it obtains that level from the enclosing `for`/`while` statement. If `indentation()` returns None for the loop statement (irregular layout around the loop keyword), the dedent cannot be computed and the fix aborts.

Source

Thrown at crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs:157

) -> Result<Fix> {
    let Some(start) = orelse.first() else {
        return Err(anyhow::anyhow!("Empty `else` clause"));
    };
    let Some(end) = orelse.last() else {
        return Err(anyhow::anyhow!("Empty `else` clause"));
    };

    let start_indentation = indentation(locator.contents(), start);
    if start_indentation.is_none() {
        // Inline `else` block (e.g., `else: x = 1`).
        Ok(Fix::safe_edit(Edit::deletion(
            else_range.start(),
            start.start(),
        )))
    } else {
        // Identify the indentation of the loop itself (e.g., the `while` or `for`).
        let Some(desired_indentation) = indentation(locator.contents(), stmt) else {
            return Err(anyhow::anyhow!("Compound statement cannot be inlined"));
        };

        // Dedent the content from the end of the `else` to the end of the loop.
        let indented = adjust_indentation(
            TextRange::new(
                locator.full_line_end(else_range.start()),
                locator.full_line_end(end.end()),
            ),
            desired_indentation,
            locator,
            indexer,
            stylist,
        )?;

        // Replace the content from the start of the `else` to the end of the loop.
        Ok(Fix::safe_edit(Edit::replacement(
            indented,
            locator.line_start(else_range.start()),

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Normalize indentation of the loop and its `else` block (same style, loop keyword on its own line), then rerun the fix
  2. Remove the loop `else` manually and move its statements after the loop (or inline the single-statement case)
  3. Run `ruff format` on the file before applying fixes

Example fix

# before (fix fails: loop indentation undetectable)
for x in xs:  # irregular leading whitespace
    f()
else:
    g()
    h()
# after (dedented after removal)
for x in xs:
    f()
g()
h()
Defensive patterns

Strategy: try-catch

Validate before calling

import re
def loop_indent_detectable(source: str) -> bool:
    # loop keyword at line start with plain leading whitespace
    return re.search(r"^[ \t]*(for|while)\b", source, re.M) is not None

Try / catch

match ruff.fix(path) {
    Ok(fixes) => apply(fixes),
    Err(e) if e.to_string().contains("Compound statement cannot be inlined") => {
        // report PLW0120 without autofix; suggest manual cleanup
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `remove_else` handles an `else` body that is not inline (start_indentation is None path), then fails to detect the indentation of the enclosing `for`/`while` statement — e.g. the loop keyword is not at the start of an indentable line, tabs/spaces are mixed, or the statement layout is non-standard.

Common situations: Autofixing loops with an `else` block containing multiple statements in files with unusual or mixed indentation, or generated code; users see PLW0120 reported but not fixed by `ruff --fix`.

Related errors


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/1aeb23de0846035a. Report an issue: GitHub.