{"record":{"id":"aead1702fe4ac472","repo":"astral-sh/ruff","slug":"should-be-set-because-extract-if-only-yields-ele","errorCode":null,"errorMessage":"should be set because `extract_if` only yields elements with a primary_span","messagePattern":"should be set because `extract_if` only yields elements with a primary_span","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_semantic/src/fixes.rs","lineNumber":113,"sourceCode":"    let has_fixable = diagnostics\n        .iter()\n        .any(|diagnostic| fix_mode.is_fixable(diagnostic));\n\n    // Early return if there are no diagnostics that can be suppressed to avoid all the heavy work below.\n    if !has_fixable {\n        return Ok(FixAllResults {\n            diagnostics,\n            count: 0,\n        });\n    }\n\n    let mut by_file: BTreeMap<File, Vec<_>> = BTreeMap::new();\n\n    // Group the diagnostics by file, leave the file-agnostic diagnostics in `diagnostics`.\n    for diagnostic in diagnostics.extract_if(.., |diagnostic| diagnostic.primary_span().is_some()) {\n        let span = diagnostic\n            .primary_span()\n            .expect(\"should be set because `extract_if` only yields elements with a primary_span\");\n\n        by_file\n            .entry(span.expect_ty_file())\n            .or_default()\n            .push(diagnostic);\n    }\n\n    // Identify all files with fixes and queue them for fixing.\n    let mut queue: Vec<(QueuedFile, Vec<ApplicableFix>)> = Vec::new();\n    let mut source_texts = SourceTexts::default();\n\n    for (&file, diagnostics) in &by_file {\n        let path = file.path(db);\n        let Some(path) = path.as_system_path() else {\n            tracing::debug!(\"Skipping read-only file `{path}`\");\n            continue;\n        };\n","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/astral-sh/ruff/blob/d1087a4b9e03d253a88703f34e0869ee4b805456/crates/ty_python_semantic/src/fixes.rs#L95-L131","documentation":"Diagnostics are partitioned with extract_if keeping only those whose primary_span() is Some, and inside the loop the span is fetched again with expect. This is an internal consistency check that Diagnostic::primary_span() is a pure, stable accessor: it must return the same Some on the second call as it did in the filter predicate.","triggerScenarios":"A Diagnostic type whose primary_span() is not idempotent: it returns Some during the filter call but None when re-queried, e.g., an accessor that takes out an internal Option, derives the span from mutable state, or is affected by mutation between the two calls.","commonSituations":"Newly added diagnostic kinds that do not store their primary span at construction; refactors that make span computation lazy or stateful.","solutions":["Make primary_span() a pure accessor over data stored at Diagnostic construction time","Audit the diagnostic type named in the panic backtrace for interior mutability or Option-consuming logic inside the span accessor","File a ty issue with the diagnostic name from the stack trace if the culprit is not obvious"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| fix_all(&mut db, diagnostics, fix_mode, &token, check)));\nmatch result {\n    Ok(r) => r?,\n    Err(_) => { log::warn!(\"fix-all panicked; falling back to check-only\"); FixAllResults { diagnostics: leftover, count: 0 } }\n}","preventionTips":["Keep Diagnostic::primary_span() a pure accessor over data fixed at construction","Never take() or mutate state inside span accessors","Add a unit test that calls primary_span() twice and asserts identical results for every diagnostic kind"],"tags":["rust","ty","diagnostics","fixes","panic","purity"],"backgroundTag":"non-idempotent-accessor","analyzedSha":"d1087a4b9e03d253a88703f34e0869ee4b805456","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}