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
- Upgrade or downgrade Ruff to a version where TID252 fix generation is not broken; check the changelog for the panic
- Reproduce with `ruff check --isolated --select TID252 --fix <file>` and file an issue at github.com/astral-sh/ruff with the minimized snippet
- 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
- 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
- Run `ruff check` without `--fix` first in CI, then apply fixes in a separate step so a panic never blocks diagnosis
- Keep Ruff pinned and upgraded deliberately; read changelogs for TID252/fix regressions
- Report panics upstream with minimized snippets instead of working around silently
- When extending Ruff, only call fix builders with the statement variant the diagnostic matched
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
- Expected ScopeKind::Function | ScopeKind::Lambda
- Expected Stmt::Import | Stmt::ImportFrom
- Expected ScopeKind::Function
- Expected CmpOp::Is | CmpOp::IsNot
- extra use-def data should have been retained
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/041bed56001e9606.
Report an issue: GitHub.