astral-sh/ruff · error

Expected at least one reference

Error message

Expected at least one reference

What it means

When generating the fix that quotes runtime imports inside a `if TYPE_CHECKING:` block, the rule collects quote_reference_edits and expects at least one. `rest.next().expect("Expected at least one reference")` panics on an empty list, because Fix::unsafe_edits needs a head edit. The invariant is that the function is only called when there is at least one reference to quote.

Source

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

                    let reference = checker.semantic().reference(*reference_id);
                    if reference.in_runtime_context() {
                        Some(quote_annotation(
                            reference.expression_id()?,
                            checker.semantic(),
                            checker.stylist(),
                            checker.locator(),
                            checker.default_string_flags(),
                        ))
                    } else {
                        None
                    }
                })
            })
            .collect::<Vec<_>>(),
    );

    let mut rest = quote_reference_edits.into_iter();
    let head = rest.next().expect("Expected at least one reference");
    Fix::unsafe_edits(head, rest).isolate(Checker::isolation(
        checker.semantic().parent_statement_id(node_id),
    ))
}

/// 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

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Guard the call site: return early (or use Fix::empty()) when quote_reference_edits is empty
  2. Ensure the function is only called after verifying at least one binding reference exists
  3. Replace the iterator/expect with a Vec and match on `first`/`split_first`

Example fix

// before
let head = rest.next().expect("Expected at least one reference");
Fix::unsafe_edits(head, rest)
// after
let Some(head) = rest.next() else { return Fix::empty(); };
Fix::unsafe_edits(head, rest)
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

if let [head, tail @ ..] = quote_reference_edits.as_slice() { /* build fix */ }

Prevention

When it happens

Trigger: quote_imports is invoked for an import whose bindings produced zero quote edits — e.g. all references were filtered out (unresolvable members, references outside the type-checking block, or deduplication removing everything) before the fix is built.

Common situations: Contributors hit this after changing the reference-collection or filtering logic upstream so that an import with no quotable references still reaches the fix-building path.

Related errors


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