astral-sh/ruff · warning
Boolean conversion is not supported for union `{}` because `
Error message
Boolean conversion is not supported for union `{}` because `{}` doesn't implement `__bool__` correctly What it means
This is a ty type-checker diagnostic emitted when a boolean conversion (`bool(x)`, `not x`, `if x`, `assert x`) is applied to a value whose type is a union and at least one member's `__bool__` is statically known to be incorrect — e.g. it returns a non-`bool` value. ty reports the full union type plus the specific member that fails the check, via `report_diagnostic_impl` in `types/bool.rs`.
Source
Thrown at crates/ty_python_semantic/src/types/bool.rs:552
not_boolable_type.display(db, env)
),
);
// TODO: It would be nice to create an annotation here for
// where `__bool__` is defined. At time of writing, I couldn't
// figure out a straight-forward way of doing this. ---AG
diag.sub(sub);
}
Self::Union { union, .. } => {
let first_error = union
.elements(context.db())
.iter()
.find_map(|element| element.try_bool(db, env).err())
.unwrap();
builder.into_diagnostic(format_args!(
"Boolean conversion is not supported for union `{}` \
because `{}` doesn't implement `__bool__` correctly",
Type::Union(*union).display(db, env),
first_error.not_boolable_type().display(db, env),
));
}
Self::Other { not_boolable_type } => {
builder.into_diagnostic(format_args!(
"Boolean conversion is not supported for type `{}`; \
it incorrectly implements `__bool__`",
not_boolable_type.display(db, env)
));
}
}
}
}
View on GitHub (pinned to 15f3fe6b15)
Solutions
- Fix the offending member's `__bool__` so it returns exactly `bool`.
- Narrow the union before the truthiness test, e.g. `if x is not None:` or an `isinstance` check, so bool conversion applies to a single type.
- Adjust the annotation that produced the union if the wider type is unintended.
- If the class should rely on `__len__` instead, remove the incorrect `__bool__` rather than returning a non-bool.
Example fix
// before
class Flag:
def __bool__(self) -> int: # wrong return type
return 1
x: Flag | None
if x: ...
// after
class Flag:
def __bool__(self) -> bool:
return True
x: Flag | None
if x is not None:
if x: ... Defensive patterns
Strategy: type-guard
Validate before calling
# Narrow the union before truthiness testing
def check(x: Flag | None) -> None:
if x is not None:
reveal_type(x) # Flag, whose __bool__ is valid
if x:
... Type guard
from typing import TypeGuard
def is_flag(x: object) -> TypeGuard[Flag]:
return isinstance(x, Flag) Try / catch
# ty reports this statically; there is nothing to catch at runtime.
# Narrow to the member with a correct __bool__ instead of suppressing:
if isinstance(x, Flag) and bool(x):
... Prevention
- Annotate `__bool__` with `-> bool`, never `int` or other truthy types.
- Run `ty check` in CI so union bool-conversion issues surface before merge.
- Prefer `is not None` narrowing over bare truthiness on optional unions.
- Keep custom `__bool__`/`__len__` implementations minimal and consistent with typeshed.
When it happens
Trigger: Writing `bool(value)`, `if value:` or `not value` where `value` is inferred as a union (e.g. `A | B`) and one member defines `__bool__` with a wrong return annotation (such as `int` or `str`) or otherwise violates the `__bool__` protocol.
Common situations: Unioning a custom class with `None` or another type where the custom class implements `__bool__ -> int`, often after widening a function's return annotation to a union or after a Python/typeshed stubs version bump.
Related errors
- extra use-def data should have been retained
- Could not find the ruff binary in any of the following locat
- Brackets are not balanced for type {rust_type_str}
- Could not find mdtest executable after successful compilatio
- Expected a ty `File`, found a ruff `SourceFile`
AI-assisted analysis of astral-sh/ruff@15f3fe6b15 (2026-09-05).
Data as JSON: /api/errors/68ab487fb11c31af.
Report an issue: GitHub.