[red-knot] Precise inference for __class__ attributes on objects of all types (#14921)

This commit is contained in:
Alex Waygood 2024-12-11 17:30:34 +00:00 committed by GitHub
parent a54353392f
commit c361cf66ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 45 additions and 5 deletions

View file

@ -115,3 +115,43 @@ def _(flag: bool):
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
reveal_type(C.x) # revealed: Unknown
```
## Objects of all types have a `__class__` method
```py
import typing
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
a = 42
reveal_type(a.__class__) # revealed: Literal[int]
b = "42"
reveal_type(b.__class__) # revealed: Literal[str]
c = b"42"
reveal_type(c.__class__) # revealed: Literal[bytes]
d = True
reveal_type(d.__class__) # revealed: Literal[bool]
e = (42, 42)
reveal_type(e.__class__) # revealed: Literal[tuple]
def f(a: int, b: typing.LiteralString, c: int | str, d: type[str]):
reveal_type(a.__class__) # revealed: type[int]
reveal_type(b.__class__) # revealed: Literal[str]
reveal_type(c.__class__) # revealed: type[int] | type[str]
# `type[type]`, a.k.a., either the class `type` or some subclass of `type`.
# It would be incorrect to infer `Literal[type]` here,
# as `c` could be some subclass of `str` with a custom metaclass.
# All we know is that the metaclass must be a (non-strict) subclass of `type`.
reveal_type(d.__class__) # revealed: type[type]
reveal_type(f.__class__) # revealed: Literal[FunctionType]
class Foo: ...
reveal_type(Foo.__class__) # revealed: Literal[type]
```

View file

@ -59,7 +59,7 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]
# These come from `builtins.object`, not `types.ModuleType`:
reveal_type(typing.__eq__) # revealed: Literal[__eq__]
reveal_type(typing.__class__) # revealed: Literal[type]
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
# TODO: needs support for attribute access on instances, properties and generics;
# should be `dict[str, Any]`

View file

@ -1272,6 +1272,10 @@ impl<'db> Type<'db> {
/// as accessed from instances of the `Bar` class.
#[must_use]
pub(crate) fn member(&self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
if name == "__class__" {
return self.to_meta_type(db).into();
}
match self {
Type::Any => Type::Any.into(),
Type::Never => {
@ -2697,10 +2701,6 @@ impl<'db> Class<'db> {
return Type::tuple(db, &tuple_elements).into();
}
if name == "__class__" {
return self.metaclass(db).into();
}
for superclass in self.iter_mro(db) {
match superclass {
// TODO we may instead want to record the fact that we encountered dynamic, and intersect it with