astral-sh/ruff · error

Expected Stmt::ImportFrom

Error message

Expected Stmt::ImportFrom

What it means

This is an internal panic in Ruff's TID252 (banned-relative-import) fix generator. `fix_banned_relative_import` is only supposed to be called with a `Stmt::ImportFrom` node (the caller `banned_relative_import` matches on that variant before calling it), so the `let-else` treats anything else as an unreachable invariant. Hitting it means Ruff's internal assumption was violated (a bug), not user input.

Source

Thrown at crates/ruff_linter/src/rules/flake8_tidy_imports/rules/relative_imports.rs:104

fn fix_banned_relative_import(
    stmt: &Stmt,
    level: u32,
    module: Option<&str>,
    module_path: Option<&[String]>,
    generator: Generator,
) -> Option<Fix> {
    // Only fix is the module path is known.
    let module_path = resolve_imported_module_path(level, module, module_path)?;

    // Require import to be a valid module:
    // https://python.org/dev/peps/pep-0008/#package-and-module-names
    if !module_path.split('.').all(is_identifier) {
        return None;
    }

    let Stmt::ImportFrom(ast::StmtImportFrom { names, is_lazy, .. }) = stmt else {
        panic!("Expected Stmt::ImportFrom");
    };
    let node = ast::StmtImportFrom {
        module: Some(Identifier::new(
            module_path.to_string(),
            TextRange::default(),
        )),
        names: names.clone(),
        level: 0,
        is_lazy: *is_lazy,
        range: TextRange::default(),
        node_index: ruff_python_ast::AtomicNodeIndex::NONE,
    };
    let content = generator.stmt(&node.into());
    Some(Fix::unsafe_edit(Edit::range_replacement(
        content,
        stmt.range(),
    )))
}

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Upgrade or downgrade Ruff to a version where TID252 fix generation is not broken; check the changelog for the panic
  2. Reproduce with `ruff check --isolated --select TID252 --fix <file>` and file an issue at github.com/astral-sh/ruff with the minimized snippet
  3. Disable the unsafe fix for the rule (`lint.fixable = ["TID252"]` removal or `--no-fix --select TID252`) and rewrite the relative import manually to an absolute one
  4. If invoking fixes through the API/rust bindings, ensure you only call `fix_banned_relative_import` with `Stmt::ImportFrom` nodes

Example fix

# before (pyproject.toml)
[tool.ruff.lint]
select = ["TID252"]

# after (workaround while the bug is unfixed: keep the diagnostic, drop the fix)
[tool.ruff.lint]
select = ["TID252"]
fixable = ["I", "F"]  # exclude TID252 from auto-fixing
Defensive patterns

Strategy: try-catch

Validate before calling

// shell: reproduce/verify before relying on fixes
ruff check --isolated --select TID252 --no-fix your_file.py  # diagnose without the fix path
// confirm the statement is a from-import before invoking fix generation (Rust):
assert matches!(stmt, ast::Stmt::ImportFrom(_));

Type guard

fn is_import_from(stmt: &Stmt) -> bool {
    matches!(stmt, ast::Stmt::ImportFrom(_))
}

Try / catch

// Ruff panics abort the process; guard process-level runs
let output = std::panic::catch_unwind(|| ruff_check_fix(path))
    .unwrap_or_else(|_| eprintln!("ruff panicked; rerun with --no-fix"));
// CLI equivalent: run `ruff check` first, apply `--fix` only if diagnostics succeed

Prevention

When it happens

Trigger: Running `ruff check --fix` (or a fix-only pass) on a file with a relative import (TID252) where the statement passed to the fix builder is not a `from ... import ...` statement. In practice only reachable via a Ruff regression, a buggy plugin/custom rule dispatching fix generation on the wrong statement, or mismatched parser output after AST changes.

Common situations: Users on a recent Ruff version after a parser/AST refactor; tooling that reuses Ruff's fix helpers out of context; corrupted or non-standard syntax that desynchronizes statement classification between the diagnostic and fix passes.

Related errors


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