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

  1. Handle the Err gracefully and fall back to reporting the diagnostic without a fix (this is the library's intended behavior)
  2. Manually re-export: change `import a` to `import a as a` or add the name to `__all__`
  3. 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

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


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