[red-knot] Infer unary not operation for instances (#13827)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz (push) Blocked by required conditions
CI / Fuzz the parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions

Handle unary `not` on instances by calling the `__bool__` dunder.

## Test Plan

Added a new test case with some examples from these resources:

- https://docs.python.org/3/library/stdtypes.html#truth-value-testing
- <https://docs.python.org/3/reference/datamodel.html#object.__len__>
- <https://docs.python.org/3/reference/datamodel.html#object.__bool__>

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Shaygan Hooshyari 2024-11-14 00:31:36 +01:00 committed by GitHub
parent 77e8da7497
commit 924741cb11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 132 additions and 6 deletions

View file

@ -1197,14 +1197,40 @@ impl<'db> Type<'db> {
// TODO: see above
Truthiness::Ambiguous
}
Type::Instance(InstanceType { class }) => {
// TODO: lookup `__bool__` and `__len__` methods on the instance's class
// More info in https://docs.python.org/3/library/stdtypes.html#truth-value-testing
// For now, we only special-case some builtin classes
instance_ty @ Type::Instance(InstanceType { class }) => {
if class.is_known(db, KnownClass::NoneType) {
Truthiness::AlwaysFalse
} else {
Truthiness::Ambiguous
// We only check the `__bool__` method for truth testing, even though at
// runtime there is a fallback to `__len__`, since `__bool__` takes precedence
// and a subclass could add a `__bool__` method. We don't use
// `Type::call_dunder` here because of the need to check for `__bool__ = bool`.
// Don't trust a maybe-unbound `__bool__` method.
let Symbol::Type(bool_method, Boundness::Bound) =
instance_ty.to_meta_type(db).member(db, "__bool__")
else {
return Truthiness::Ambiguous;
};
// Check if the class has `__bool__ = bool` and avoid infinite recursion, since
// `Type::call` on `bool` will call `Type::bool` on the argument.
if bool_method
.into_class_literal()
.is_some_and(|ClassLiteralType { class }| {
class.is_known(db, KnownClass::Bool)
})
{
return Truthiness::Ambiguous;
}
if let Some(Type::BooleanLiteral(bool_val)) =
bool_method.call(db, &[*instance_ty]).return_ty(db)
{
bool_val.into()
} else {
Truthiness::Ambiguous
}
}
}
Type::KnownInstance(known_instance) => known_instance.bool(),