{"record":{"id":"5dfae9d4d56615cc","repo":"astral-sh/ruff","slug":"expected-at-least-one-import","errorCode":null,"errorMessage":"Expected at least one import","messagePattern":"Expected at least one import","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs","lineNumber":334,"sourceCode":"/// Generate a [`Fix`] to remove runtime imports from a type-checking block.\nfn move_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {\n    let statement = checker.semantic().statement(node_id);\n    let parent = checker.semantic().parent_statement(node_id);\n\n    let member_names: Vec<Cow<'_, str>> = imports\n        .iter()\n        .map(|ImportBinding { import, .. }| import)\n        .map(Imported::member_name)\n        .collect();\n\n    // Find the first reference across all imports.\n    let at = imports\n        .iter()\n        .map(|ImportBinding { reference_id, .. }| {\n            checker.semantic().reference(*reference_id).start()\n        })\n        .min()\n        .expect(\"Expected at least one import\");\n\n    // Step 1) Remove the import.\n    let remove_import_edit = fix::edits::remove_unused_imports(\n        member_names.iter().map(AsRef::as_ref),\n        statement,\n        parent,\n        checker.locator(),\n        checker.stylist(),\n        checker.indexer(),\n    )?;\n\n    // Step 2) Add the import to the top-level.\n    let add_import_edit = checker.importer().runtime_import_edit(\n        &ImportedMembers {\n            statement,\n            names: member_names.iter().map(AsRef::as_ref).collect(),\n        },\n        at,","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/astral-sh/ruff/blob/26f38c119cac42e4d320ba08f09224fdec74af2c/crates/ruff_linter/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs#L316-L352","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Early-return (or use Fix::empty()) when `imports.is_empty()` before computing `at`","Assert/filter at the call site (runtime_import_in_type_checking) that at least one binding exists before building the fix","Compute min via `iter().map(...).min()` into an Option and handle None explicitly"],"exampleFix":"// before\nlet at = imports.iter().map(...).min().expect(\"Expected at least one import\");\n// after\nlet Some(at) = imports.iter().map(...).min() else { return Fix::empty(); };","handlingStrategy":"validation","validationCode":"if imports.is_empty() { return Fix::empty(); }","typeGuard":"fn non_empty<T>(v: &[T]) -> bool { !v.is_empty() }","tryCatchPattern":null,"preventionTips":["Early-return on empty binding lists before computing anchors","Verify upstream filters cannot drop all bindings once a diagnostic is emitted","Handle Option from min() explicitly"],"tags":["rust","panic","empty-collection","imports"],"backgroundTag":"empty-collection-panic","analyzedSha":"26f38c119cac42e4d320ba08f09224fdec74af2c","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}