astral-sh/ruff · error
bindings must not be empty
Error message
bindings must not be empty
What it means
Bindings::from_union merges the per-element bindings of a union of callables into one value, using the first element as the accumulator for the shared elements buffer. The expect fires when the iterator is empty: from_union requires at least one Bindings to merge.
Source
Thrown at crates/ty_python_semantic/src/types/call/bind.rs:749
&mut self,
db: &'db dyn Db,
generic_context: GenericContext<'db>,
) {
for element in &mut self.elements {
for item in &mut element.items {
match item {
CallableItem::Regular(binding) => {
for overload in &mut binding.overloads {
overload.signature.generic_context = GenericContext::merge_optional(
db,
overload.signature.generic_context,
Some(generic_context),
);
}
}
CallableItem::Constructor(binding) => {
for overload in &mut binding.entry.overloads {
overload.signature.generic_context = GenericContext::merge_optional(
db,
overload.signature.generic_context,
Some(generic_context),
);
}
if let Some(downstream) = binding.downstream_constructor_mut() {
downstream.apply_generic_context_in_place(db, generic_context);
}
}
}
}
}
}
/// Creates a new `Bindings` from an iterator of [`Bindings`]s for a union type.
/// Each input `Bindings` becomes a union element, preserving any intersection structure.
/// Panics if the iterator is empty.
pub(crate) fn from_union<I>(callable_type: Type<'db>, bindings_iter: I) -> SelfView on GitHub (pinned to 15f3fe6b15)
Solutions
- Handle the empty case at the call site: return a not-callable/unbound binding instead of calling from_union with no elements
- File a ty issue with the call expression (Never-typed callables and empty unions are the usual triggers)
- As a contributor: encode the non-empty requirement in the signature (take head plus tail, or return Option) instead of unwrapping next()
Example fix
// before
let merged = Bindings::from_union(callable_type, bindings_iter);
// after
let mut bindings_iter = bindings_iter.into_iter();
let Some(first) = bindings_iter.next() else {
return Bindings::empty(callable_type);
};
let merged = Bindings::from_union(callable_type, once(first).chain(bindings_iter)); Defensive patterns
Strategy: validation
Validate before calling
let mut iter = bindings_iter.into_iter();
if let Some(first) = iter.next() {
Bindings::from_union(callable_type, std::iter::once(first).chain(iter));
} else {
// no callable elements to merge: produce a not-callable result instead of merging Prevention
- Check for at least one element before merging bindings of a union
- When filtering callable-union elements before a call, keep an explicit empty-case branch
- Encode non-emptiness in APIs (head + tail) instead of relying on unwrap of next()
When it happens
Trigger: Calling from_union with an empty iterator - e.g., invoking a value whose callable-union elements all got filtered out before binding, or an empty union (Never-adjacent) reaching the call path.
Common situations: Empty-union edge cases in call inference; refactors of union-call handling where filtering can now remove every branch before from_union runs.
Related errors
- ParamSpec sub-call should only contain a single CallableBind
- argument index should be valid
- checked bindings are stable across fixpoint iterations
- argument index should be valid
- extra use-def data should have been retained
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-08-20).
Data as JSON: /api/errors/e05e7f3dcf77fda0.
Report an issue: GitHub.