mirror of https://github.com/astral-sh/ruff
[ty] default-specialize class-literal types in assignment to generic-alias types
This commit is contained in:
parent
7bf50e70a7
commit
0d9429cc64
|
|
@ -152,61 +152,6 @@ class Foo(type[int]): ...
|
|||
reveal_mro(Foo) # revealed: (<class 'Foo'>, <class 'type'>, <class 'object'>)
|
||||
```
|
||||
|
||||
## Display of generic `type[]` types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
class Foo[T]: ...
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
class Bar(Generic[S]): ...
|
||||
|
||||
def _(x: Foo[int], y: Bar[str], z: list[bytes]):
|
||||
reveal_type(type(x)) # revealed: type[Foo[int]]
|
||||
reveal_type(type(y)) # revealed: type[Bar[str]]
|
||||
reveal_type(type(z)) # revealed: type[list[bytes]]
|
||||
```
|
||||
|
||||
## Checking generic `type[]` types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
pass
|
||||
|
||||
class D[T]:
|
||||
pass
|
||||
|
||||
var: type[C[int]] = C[int]
|
||||
var: type[C[int]] = D[int] # error: [invalid-assignment] "Object of type `<class 'D[int]'>` is not assignable to `type[C[int]]`"
|
||||
```
|
||||
|
||||
However, generic `Protocol` classes are still TODO:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Proto[U](Protocol):
|
||||
def some_method(self): ...
|
||||
|
||||
# TODO: should be error: [invalid-assignment]
|
||||
var: type[Proto[int]] = C[int]
|
||||
|
||||
def _(p: type[Proto[int]]):
|
||||
reveal_type(p) # revealed: type[@Todo(type[T] for protocols)]
|
||||
```
|
||||
|
||||
## `@final` classes
|
||||
|
||||
`type[]` types are eagerly converted to class-literal types if a class decorated with `@final` is
|
||||
|
|
|
|||
|
|
@ -274,3 +274,119 @@ class Foo[T]: ...
|
|||
# error: [invalid-parameter-default] "Default value of type `<class 'Foo'>` is not assignable to annotated parameter type `type[T@f]`"
|
||||
def f[T: Foo[Any]](x: type[T] = Foo): ...
|
||||
```
|
||||
|
||||
## Display of generic `type[]` types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
class Foo[T]: ...
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
class Bar(Generic[S]): ...
|
||||
|
||||
def _(x: Foo[int], y: Bar[str], z: list[bytes]):
|
||||
reveal_type(type(x)) # revealed: type[Foo[int]]
|
||||
reveal_type(type(y)) # revealed: type[Bar[str]]
|
||||
reveal_type(type(z)) # revealed: type[list[bytes]]
|
||||
```
|
||||
|
||||
## Checking generic `type[]` types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
pass
|
||||
|
||||
class D[T]:
|
||||
pass
|
||||
|
||||
var: type[C[int]] = C[int]
|
||||
var: type[C[int]] = D[int] # error: [invalid-assignment] "Object of type `<class 'D[int]'>` is not assignable to `type[C[int]]`"
|
||||
```
|
||||
|
||||
However, generic `Protocol` classes are still TODO:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Proto[U](Protocol):
|
||||
def some_method(self): ...
|
||||
|
||||
# TODO: should be error: [invalid-assignment]
|
||||
var: type[Proto[int]] = C[int]
|
||||
|
||||
def _(p: type[Proto[int]]):
|
||||
reveal_type(p) # revealed: type[@Todo(type[T] for protocols)]
|
||||
```
|
||||
|
||||
## Generic `@final` classes
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
An unspecialized generic final class object is assignable to its default-specialized `type[]` type
|
||||
(which is actually internally simplified to a GenericAlias type, since there cannot be subclasses.)
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class P[T]:
|
||||
x: T
|
||||
|
||||
def expects_type_p(x: type[P]):
|
||||
pass
|
||||
|
||||
def expects_type_p_of_int(x: type[P[int]]):
|
||||
pass
|
||||
|
||||
# OK, the default specialization of `P` is assignable to `type[P[Unknown]]`
|
||||
expects_type_p(P)
|
||||
|
||||
# also OK, because the default specialization is `P[Unknown]` which is assignable to `P[int]`
|
||||
expects_type_p_of_int(P)
|
||||
```
|
||||
|
||||
The same principles apply when typevar defaults are used, but the results are a bit different
|
||||
because the default-specialization is no longer a forgiving `Unknown` type:
|
||||
|
||||
```py
|
||||
@final
|
||||
class P[T = str]:
|
||||
x: T
|
||||
|
||||
def expects_type_p(x: type[P]):
|
||||
pass
|
||||
|
||||
def expects_type_p_of_int(x: type[P[int]]):
|
||||
pass
|
||||
|
||||
def expects_type_p_of_str(x: type[P[str]]):
|
||||
pass
|
||||
|
||||
# OK, the default specialization is now `P[str]`, but we have the default specialization on both
|
||||
# sides, so it is assignable.
|
||||
expects_type_p(P)
|
||||
|
||||
# Also OK if the explicit specialization lines up with the default, in either direction:
|
||||
expects_type_p(P[str])
|
||||
expects_type_p_of_str(P)
|
||||
|
||||
# Not OK if the specializations don't line up:
|
||||
expects_type_p(P[int]) # error: [invalid-argument-type]
|
||||
expects_type_p_of_int(P[str]) # error: [invalid-argument-type]
|
||||
expects_type_p_of_str(P[int]) # error: [invalid-argument-type]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2709,6 +2709,22 @@ impl<'db> Type<'db> {
|
|||
)
|
||||
})
|
||||
.unwrap_or_else(|| ConstraintSet::from(relation.is_assignability())),
|
||||
|
||||
// Similarly, `Literal[<class 'C'>]` is assignable to `C[...]` (a generic-alias type)
|
||||
// if the default specialization of `C` is assignable to `C[...]`. This scenario
|
||||
// occurs with final generic types, where `type[C[...]]` is simplified to the
|
||||
// generic-alias type `C[...]`, due to the fact that `C[...]` has no subclasses.
|
||||
(Type::ClassLiteral(class), Type::GenericAlias(target_alias)) => {
|
||||
class.default_specialization(db).has_relation_to_impl(
|
||||
db,
|
||||
ClassType::from(target_alias),
|
||||
inferable,
|
||||
relation,
|
||||
relation_visitor,
|
||||
disjointness_visitor,
|
||||
)
|
||||
}
|
||||
|
||||
(Type::GenericAlias(alias), Type::SubclassOf(target_subclass_ty)) => target_subclass_ty
|
||||
.subclass_of()
|
||||
.into_class(db)
|
||||
|
|
|
|||
Loading…
Reference in New Issue