Skip to content

Commit c361cf6

Browse files
authored
[red-knot] Precise inference for __class__ attributes on objects of all types (#14921)
1 parent a543533 commit c361cf6

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

crates/red_knot_python_semantic/resources/mdtest/attributes.md

+40
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,43 @@ def _(flag: bool):
115115
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
116116
reveal_type(C.x) # revealed: Unknown
117117
```
118+
119+
## Objects of all types have a `__class__` method
120+
121+
```py
122+
import typing
123+
124+
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
125+
126+
a = 42
127+
reveal_type(a.__class__) # revealed: Literal[int]
128+
129+
b = "42"
130+
reveal_type(b.__class__) # revealed: Literal[str]
131+
132+
c = b"42"
133+
reveal_type(c.__class__) # revealed: Literal[bytes]
134+
135+
d = True
136+
reveal_type(d.__class__) # revealed: Literal[bool]
137+
138+
e = (42, 42)
139+
reveal_type(e.__class__) # revealed: Literal[tuple]
140+
141+
def f(a: int, b: typing.LiteralString, c: int | str, d: type[str]):
142+
reveal_type(a.__class__) # revealed: type[int]
143+
reveal_type(b.__class__) # revealed: Literal[str]
144+
reveal_type(c.__class__) # revealed: type[int] | type[str]
145+
146+
# `type[type]`, a.k.a., either the class `type` or some subclass of `type`.
147+
# It would be incorrect to infer `Literal[type]` here,
148+
# as `c` could be some subclass of `str` with a custom metaclass.
149+
# All we know is that the metaclass must be a (non-strict) subclass of `type`.
150+
reveal_type(d.__class__) # revealed: type[type]
151+
152+
reveal_type(f.__class__) # revealed: Literal[FunctionType]
153+
154+
class Foo: ...
155+
156+
reveal_type(Foo.__class__) # revealed: Literal[type]
157+
```

crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]
5959
# These come from `builtins.object`, not `types.ModuleType`:
6060
reveal_type(typing.__eq__) # revealed: Literal[__eq__]
6161

62-
reveal_type(typing.__class__) # revealed: Literal[type]
62+
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
6363

6464
# TODO: needs support for attribute access on instances, properties and generics;
6565
# should be `dict[str, Any]`

crates/red_knot_python_semantic/src/types.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,10 @@ impl<'db> Type<'db> {
12721272
/// as accessed from instances of the `Bar` class.
12731273
#[must_use]
12741274
pub(crate) fn member(&self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
1275+
if name == "__class__" {
1276+
return self.to_meta_type(db).into();
1277+
}
1278+
12751279
match self {
12761280
Type::Any => Type::Any.into(),
12771281
Type::Never => {
@@ -2697,10 +2701,6 @@ impl<'db> Class<'db> {
26972701
return Type::tuple(db, &tuple_elements).into();
26982702
}
26992703

2700-
if name == "__class__" {
2701-
return self.metaclass(db).into();
2702-
}
2703-
27042704
for superclass in self.iter_mro(db) {
27052705
match superclass {
27062706
// TODO we may instead want to record the fact that we encountered dynamic, and intersect it with

0 commit comments

Comments
 (0)