astral-sh/ruff · info

Empty `else` clause

Error message

Empty `else` clause

What it means

An internal guard in the useless-else-on-loop (PLW0120) fix removal path. When deleting a loop's `else` clause, the fixer takes the first and last statements of the `orelse` body to compute the deletion range. If the `else` body is empty (`orelse.first()` is None), there is nothing to remove and the fix aborts with 'Empty `else` clause'.

Source

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

        Stmt::For(ast::StmtFor { orelse, .. }) | Stmt::While(ast::StmtWhile { orelse, .. }) => {
            loop_exits_early(orelse)
        }
        Stmt::Break(_) => true,
        _ => false,
    })
}

/// Generate a [`Fix`] to remove the `else` clause from the given statement.
fn remove_else(
    stmt: &Stmt,
    orelse: &[Stmt],
    else_range: TextRange,
    locator: &Locator,
    indexer: &Indexer,
    stylist: &Stylist,
) -> 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"));
        };

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Treat the Err as 'nothing to fix' and skip the fix
  2. Delete the empty `else:` line manually if it is genuinely useless
  3. Ensure the rule's caller only requests a fix when `orelse` contains at least one statement
Defensive patterns

Strategy: type-guard

Validate before calling

def has_else_body(orelse: list) -> bool:
    return len(orelse) > 0

Type guard

def has_statements(orelse: list[ast.stmt] | None) -> bool:
    return bool(orelse)

Prevention

When it happens

Trigger: `remove_else` (called from `useless_else_on_loop`) is invoked with an `orelse` slice whose first statement is None — an `else:` with no body statements reaching the fix stage (normally the diagnostic only fires for non-empty else bodies, so this is a defensive path).

Common situations: Rarely hit by end users; mostly surfaced when the rule's fix logic is called with an `else` clause containing only comments/no statements, or by tooling invoking the fix API directly.

Related errors


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