astral-sh/ruff · info
No edits to make
Error message
No edits to make
What it means
This is an internal guard in Ruff's unused-import (F401) re-export fix. When an import is unused inside an `__init__.py` or re-export context, Ruff offers a fix that either makes the import a redundant alias (`import a as a`) or adds it to `__all__`. After computing the edit list, if it is unexpectedly empty the fixer raises 'No edits to make' instead of emitting a malformed empty fix.
Source
Thrown at crates/ruff_linter/src/rules/pyflakes/rules/unused_import.rs:636
.collect();
if imports.is_empty() {
bail!("Expected import bindings");
}
imports.sort_unstable();
imports
};
let edits = match dunder_all_exprs {
[] => fix::edits::make_redundant_alias(imports.into_iter(), statement),
[dunder_all] => {
fix::edits::add_to_dunder_all(imports.into_iter(), dunder_all, checker.stylist())
}
_ => bail!("Cannot offer a fix when there are multiple __all__ definitions"),
};
// Only emit a fix if there are edits.
let mut tail = edits.into_iter();
let head = tail.next().ok_or(anyhow!("No edits to make"))?;
let isolation = Checker::isolation(checker.semantic().parent_statement_id(node_id));
Ok(Fix::safe_edits(head, tail).isolate(isolation))
}
/// Returns an iterator over bindings to import statements that appear unused.
///
/// The stable behavior is to return those bindings to imports
/// satisfying the following properties:
///
/// - they are not shadowed
/// - they are not `global`, not `nonlocal`, and not explicit exports (i.e. `import foo as foo`)
/// - they have no references, according to the semantic model
///
/// Under preview, there is a more refined analysis performed
/// in the case where all bindings shadowed by a given import
/// binding (including the binding itself) are of a simple form:
/// they are required to be un-aliased imports or submodule imports.View on GitHub (pinned to 26f38c119c)
Solutions
- Handle the Err gracefully and fall back to reporting the diagnostic without a fix (this is the library's intended behavior)
- Manually re-export: change `import a` to `import a as a` or add the name to `__all__`
- Report the snippet upstream if a plain `import a` in `__init__.py` still yields no fix
Example fix
# before (unused re-export) from pkg import thing # after (explicit re-export so F401 fix applies cleanly) from pkg import thing as thing # or __all__ = ["thing"]
Defensive patterns
Strategy: try-catch
Try / catch
match fix_by_reexporting(...) {
Ok(fix) => Some(fix),
Err(e) if e.to_string().contains("No edits to make") => None, // diagnostic only
Err(e) => return Err(e),
} Prevention
- Keep exactly one `__all__` definition per `__init__.py`
- Use explicit re-exports (`import a as a`) rather than relying on autofix
- Handle fix-generation Errs as 'diagnostic without fix' by design
When it happens
Trigger: `fix_by_reexporting` runs for an unused import binding that qualifies for the re-export fix (e.g. in `__init__.py`), but the chosen edit generator (`make_redundant_alias` or `add_to_dunder_all`) returns an empty edit list for this statement layout.
Common situations: Autofixing unused imports in package `__init__.py` files with unusual `__all__` layouts or import forms; plugin authors consuming F401's fix see the Err instead of a Fix.
Related errors
- Expected import bindings
- Cannot offer a fix when there are multiple __all__ definitio
- Failed to fix invalid comparison: {node:?}
- Failed to fix invalid comparison due to missing op
- Failed to collapse `with`: {err}
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/edf75e3eec9b9f70.
Report an issue: GitHub.