astral-sh/ruff · error
should be set because `extract_if` only yields elements with
Error message
should be set because `extract_if` only yields elements with a primary_span
What it means
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.
Source
Thrown at crates/ty_python_semantic/src/fixes.rs:113
let has_fixable = diagnostics
.iter()
.any(|diagnostic| fix_mode.is_fixable(diagnostic));
// Early return if there are no diagnostics that can be suppressed to avoid all the heavy work below.
if !has_fixable {
return Ok(FixAllResults {
diagnostics,
count: 0,
});
}
let mut by_file: BTreeMap<File, Vec<_>> = BTreeMap::new();
// Group the diagnostics by file, leave the file-agnostic diagnostics in `diagnostics`.
for diagnostic in diagnostics.extract_if(.., |diagnostic| diagnostic.primary_span().is_some()) {
let span = diagnostic
.primary_span()
.expect("should be set because `extract_if` only yields elements with a primary_span");
by_file
.entry(span.expect_ty_file())
.or_default()
.push(diagnostic);
}
// Identify all files with fixes and queue them for fixing.
let mut queue: Vec<(QueuedFile, Vec<ApplicableFix>)> = Vec::new();
let mut source_texts = SourceTexts::default();
for (&file, diagnostics) in &by_file {
let path = file.path(db);
let Some(path) = path.as_system_path() else {
tracing::debug!("Skipping read-only file `{path}`");
continue;
};
View on GitHub (pinned to d1087a4b9e)
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
Defensive patterns
Strategy: fallback
Try / catch
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| fix_all(&mut db, diagnostics, fix_mode, &token, check)));
match result {
Ok(r) => r?,
Err(_) => { log::warn!("fix-all panicked; falling back to check-only"); FixAllResults { diagnostics: leftover, count: 0 } }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Newly added diagnostic kinds that do not store their primary span at construction; refactors that make span computation lazy or stateful.
Related errors
- `parsed_module` should have assigned a node index
- argument index should not be out of range
- extra use-def data should have been retained
- Expected a ty `File`, found a ruff `SourceFile`
- System should be writable
AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20).
Data as JSON: /api/errors/aead1702fe4ac472.
Report an issue: GitHub.