astral-sh/ruff · error
there must be a first type if there are conflicting types
Error message
there must be a first type if there are conflicting types
What it means
DeclaredTypeAndConflictingTypesBuilder assembles a symbol's declared type plus the list of conflicting declared types recorded across multiple declarations. Its invariant is that whenever a conflicting type is recorded, the original first declared type is stashed in first_type; build() panics if conflicting_types is non-empty while first_type was never set.
Source
Thrown at crates/ty_python_semantic/src/place.rs:1990
}
} else {
self.first_type = Some(element_ty);
}
}
self.qualifiers = self.qualifiers.union(element.qualifiers());
}
fn build(mut self) -> DeclaredTypeAndConflictingTypes<'db> {
let type_and_quals =
TypeAndQualifiers::new(self.inner.build(), TypeOrigin::Declared, self.qualifiers);
if self.conflicting_types.is_empty() {
(type_and_quals, None)
} else {
self.conflicting_types.insert_before(
0,
self.first_type
.expect("there must be a first type if there are conflicting types"),
);
(
type_and_quals,
Some(self.conflicting_types.into_boxed_slice()),
)
}
}
}
/// Implementation of [`place_from_declarations`].
///
/// ## Implementation Note
/// This function gets called cross-module. It, therefore, shouldn't
/// access any AST nodes from the file containing the declarations.
fn place_from_declarations_impl<'db>(
db: &'db dyn Db,
env: &ProgramEnvironment<'db>,
declarations_iterator: DeclarationsIterator<'_, 'db>,View on GitHub (pinned to fca5c7cf2c)
Solutions
- Minimize the module to the two conflicting annotations that trigger the panic and file a ty issue with it
- As a contributor: ensure every path that pushes onto conflicting_types sets first_type beforehand (record the original type before treating later types as conflicts)
- Workaround until fixed: unify or remove one of the conflicting declarations for that name
Defensive patterns
Strategy: fallback
Try / catch
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| check_file(&db, file)));
match result {
Ok(diags) => diags,
Err(payload) => { log::warn!("ty panicked while declaring {}: {:?}", file, payload); vec![] }
} Prevention
- Avoid mixing multiple different annotations for one name across branches in a single module while a triggering bug is unfixed
- When touching conflict collection in place.rs, set first_type on every path that appends to conflicting_types
- Add builder unit tests covering the one-conflict and many-conflicts sequences
When it happens
Trigger: A symbol that receives two or more different declared types (for example `x: int` in one branch and `x: str]` in another, or repeated annotations) where the code path that pushed the conflicting type never recorded the first type first - a builder misuse or a missed update when a new conflict-recording path was added.
Common situations: Modules with multiple differently-annotated declarations of the same name (conditional imports, branch-local annotations); contributor changes to place_from_declarations conflict collection.
Related errors
- extra use-def data should have been retained
- should be set because `extract_if` only yields elements with
- Should only ever pass a positive integer to `from_nonnegativ
- argument index should be valid
- Expected `NamedTuple` definition to be an assignment
AI-assisted analysis of astral-sh/ruff@fca5c7cf2c (2026-08-20).
Data as JSON: /api/errors/85469f50f6419ad7.
Report an issue: GitHub.