astral-sh/ruff · error

Expected at least one import

Error message

Expected at least one import

What it means

`move_imports` computes the earliest start offset of the imports being moved via `.min()` over reference start positions, expecting the `imports` slice to be non-empty. The panic fires when move_imports is called with zero import bindings, because there is no position to anchor the moved imports.

Source

Thrown at crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs:334

/// Generate a [`Fix`] to remove runtime imports from a type-checking block.
fn move_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
    let statement = checker.semantic().statement(node_id);
    let parent = checker.semantic().parent_statement(node_id);

    let member_names: Vec<Cow<'_, str>> = imports
        .iter()
        .map(|ImportBinding { import, .. }| import)
        .map(Imported::member_name)
        .collect();

    // Find the first reference across all imports.
    let at = imports
        .iter()
        .map(|ImportBinding { reference_id, .. }| {
            checker.semantic().reference(*reference_id).start()
        })
        .min()
        .expect("Expected at least one import");

    // Step 1) Remove the import.
    let remove_import_edit = fix::edits::remove_unused_imports(
        member_names.iter().map(AsRef::as_ref),
        statement,
        parent,
        checker.locator(),
        checker.stylist(),
        checker.indexer(),
    )?;

    // Step 2) Add the import to the top-level.
    let add_import_edit = checker.importer().runtime_import_edit(
        &ImportedMembers {
            statement,
            names: member_names.iter().map(AsRef::as_ref).collect(),
        },
        at,

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Early-return (or use Fix::empty()) when `imports.is_empty()` before computing `at`
  2. Assert/filter at the call site (runtime_import_in_type_checking) that at least one binding exists before building the fix
  3. Compute min via `iter().map(...).min()` into an Option and handle None explicitly

Example fix

// before
let at = imports.iter().map(...).min().expect("Expected at least one import");
// after
let Some(at) = imports.iter().map(...).min() else { return Fix::empty(); };
Defensive patterns

Strategy: validation

Validate before calling

if imports.is_empty() { return Fix::empty(); }

Type guard

fn non_empty<T>(v: &[T]) -> bool { !v.is_empty() }

Prevention

When it happens

Trigger: Calling move_imports with an empty `imports` vec — i.e. the runtime-import-in-type-checking rule determined a fix is needed but all ImportBinding entries were filtered out (e.g. bindings without usable reference ids) before the fix step.

Common situations: Hit by contributors when the binding-collection step and the fix step disagree on which imports qualify, typically after adding a new filter to ImportBinding collection without updating the fix entry condition.

Related errors


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