Skip to content

Commit

Permalink
[red-knot] Precise inference for __class__ attributes on objects of…
Browse files Browse the repository at this point in the history
… all types (#14921)
  • Loading branch information
AlexWaygood authored Dec 11, 2024
1 parent a543533 commit c361cf6
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 5 deletions.
40 changes: 40 additions & 0 deletions crates/red_knot_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
```
Original file line number Diff line number Diff line change
Expand Up @@ -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]`
Expand Down
8 changes: 4 additions & 4 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit c361cf6

Please # to comment.