mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
Teach red-knot that type(x)
is the same as x.__class__
(#16301)
This commit is contained in:
parent
5347abc766
commit
224a36f5f3
6 changed files with 49 additions and 9 deletions
|
@ -884,13 +884,18 @@ def _(flag: bool):
|
|||
|
||||
## Objects of all types have a `__class__` method
|
||||
|
||||
The type of `x.__class__` is the same as `x`'s meta-type. `x.__class__` is always the same value as
|
||||
`type(x)`.
|
||||
|
||||
```py
|
||||
import typing_extensions
|
||||
|
||||
reveal_type(typing_extensions.__class__) # revealed: Literal[ModuleType]
|
||||
reveal_type(type(typing_extensions)) # revealed: Literal[ModuleType]
|
||||
|
||||
a = 42
|
||||
reveal_type(a.__class__) # revealed: Literal[int]
|
||||
reveal_type(type(a)) # revealed: Literal[int]
|
||||
|
||||
b = "42"
|
||||
reveal_type(b.__class__) # revealed: Literal[str]
|
||||
|
@ -906,8 +911,13 @@ reveal_type(e.__class__) # revealed: Literal[tuple]
|
|||
|
||||
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
|
||||
reveal_type(a.__class__) # revealed: type[int]
|
||||
reveal_type(type(a)) # revealed: type[int]
|
||||
|
||||
reveal_type(b.__class__) # revealed: Literal[str]
|
||||
reveal_type(type(b)) # revealed: Literal[str]
|
||||
|
||||
reveal_type(c.__class__) # revealed: type[int] | type[str]
|
||||
reveal_type(type(c)) # 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,
|
||||
|
|
|
@ -12,3 +12,26 @@ bool(1, 2)
|
|||
# TODO: We should emit an `unsupported-bool-conversion` error here because the argument doesn't implement `__bool__` correctly.
|
||||
bool(NotBool())
|
||||
```
|
||||
|
||||
## Calls to `type()`
|
||||
|
||||
A single-argument call to `type()` returns an object that has the argument's meta-type. (This is
|
||||
tested more extensively in `crates/red_knot_python_semantic/resources/mdtest/attributes.md`,
|
||||
alongside the tests for the `__class__` attribute.)
|
||||
|
||||
```py
|
||||
reveal_type(type(1)) # revealed: Literal[int]
|
||||
```
|
||||
|
||||
But a three-argument call to type creates a dynamic instance of the `type` class:
|
||||
|
||||
```py
|
||||
reveal_type(type("Foo", (), {})) # revealed: type
|
||||
```
|
||||
|
||||
Other numbers of arguments are invalid (TODO -- these should emit a diagnostic)
|
||||
|
||||
```py
|
||||
type("Foo", ())
|
||||
type("Foo", (), {}, weird_other_arg=42)
|
||||
```
|
||||
|
|
|
@ -97,12 +97,7 @@ else:
|
|||
## No narrowing for instances of `builtins.type`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
t = type("t", (), {})
|
||||
|
||||
# This isn't testing what we want it to test if we infer anything more precise here:
|
||||
reveal_type(t) # revealed: type
|
||||
|
||||
def _(flag: bool, t: type):
|
||||
x = 1 if flag else "foo"
|
||||
|
||||
if isinstance(x, t):
|
||||
|
|
|
@ -112,8 +112,7 @@ def _(flag: bool):
|
|||
reveal_type(t) # revealed: Literal[NoneType]
|
||||
|
||||
if issubclass(t, type(None)):
|
||||
# TODO: this should be just `Literal[NoneType]`
|
||||
reveal_type(t) # revealed: Literal[int, NoneType]
|
||||
reveal_type(t) # revealed: Literal[NoneType]
|
||||
```
|
||||
|
||||
## `classinfo` contains multiple types
|
||||
|
|
|
@ -2181,7 +2181,12 @@ impl<'db> Type<'db> {
|
|||
Some(KnownClass::Str) => arguments
|
||||
.first_argument()
|
||||
.map(|arg| arg.str(db))
|
||||
.unwrap_or(Type::string_literal(db, "")),
|
||||
.unwrap_or_else(|| Type::string_literal(db, "")),
|
||||
|
||||
Some(KnownClass::Type) => arguments
|
||||
.exactly_one_argument()
|
||||
.map(|arg| arg.to_meta_type(db))
|
||||
.unwrap_or_else(|| KnownClass::Type.to_instance(db)),
|
||||
|
||||
_ => Type::Instance(InstanceType { class }),
|
||||
},
|
||||
|
|
|
@ -35,6 +35,14 @@ impl<'a, 'db> CallArguments<'a, 'db> {
|
|||
self.0.first().map(Argument::ty)
|
||||
}
|
||||
|
||||
// TODO this should be eliminated in favor of [`bind_call`]
|
||||
pub(crate) fn exactly_one_argument(&self) -> Option<Type<'db>> {
|
||||
match &*self.0 {
|
||||
[arg] => Some(arg.ty()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this should be eliminated in favor of [`bind_call`]
|
||||
pub(crate) fn second_argument(&self) -> Option<Type<'db>> {
|
||||
self.0.get(1).map(Argument::ty)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue