mirror of https://github.com/astral-sh/ruff
[ty] Fix subtyping of `type[Any]` / `type[T]` and protocols (#21678)
## Summary This is a bugfix for subtyping of `type[Any]` / `type[T]` and protocols. ## Test Plan Regression test that will only be really meaningful once https://github.com/astral-sh/ruff/pull/21553 lands.
This commit is contained in:
parent
566c959add
commit
0084e94f78
|
|
@ -0,0 +1,67 @@
|
||||||
|
# numpy
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.14"
|
||||||
|
```
|
||||||
|
|
||||||
|
## numpy's `dtype`
|
||||||
|
|
||||||
|
numpy functions often accept a `dtype` parameter. For example, one of `np.array`'s overloads accepts
|
||||||
|
a `dtype` parameter of type `DTypeLike | None`. Here, we build up something that resembles numpy's
|
||||||
|
internals in order to model the type `DTypeLike`. Many details have been left out.
|
||||||
|
|
||||||
|
`mini_numpy.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeVar, Generic, Any, Protocol, TypeAlias, runtime_checkable, final
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
_ItemT_co = TypeVar("_ItemT_co", default=Any, covariant=True)
|
||||||
|
|
||||||
|
class generic(Generic[_ItemT_co]):
|
||||||
|
@property
|
||||||
|
def dtype(self) -> _DTypeT_co:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
_BoolItemT_co = TypeVar("_BoolItemT_co", bound=builtins.bool, default=builtins.bool, covariant=True)
|
||||||
|
|
||||||
|
class bool(generic[_BoolItemT_co], Generic[_BoolItemT_co]): ...
|
||||||
|
|
||||||
|
@final
|
||||||
|
class object_(generic): ...
|
||||||
|
|
||||||
|
_ScalarT = TypeVar("_ScalarT", bound=generic)
|
||||||
|
_ScalarT_co = TypeVar("_ScalarT_co", bound=generic, default=Any, covariant=True)
|
||||||
|
|
||||||
|
@final
|
||||||
|
class dtype(Generic[_ScalarT_co]): ...
|
||||||
|
|
||||||
|
_DTypeT_co = TypeVar("_DTypeT_co", bound=dtype, default=dtype, covariant=True)
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class _SupportsDType(Protocol[_DTypeT_co]):
|
||||||
|
@property
|
||||||
|
def dtype(self) -> _DTypeT_co: ...
|
||||||
|
|
||||||
|
# TODO: no errors here
|
||||||
|
# error: [invalid-type-arguments] "Type `typing.TypeVar` is not assignable to upper bound `generic[Any]` of type variable `_ScalarT_co@dtype`"
|
||||||
|
# error: [invalid-type-arguments] "Type `typing.TypeVar` is not assignable to upper bound `generic[Any]` of type variable `_ScalarT_co@dtype`"
|
||||||
|
_DTypeLike: TypeAlias = type[_ScalarT] | dtype[_ScalarT] | _SupportsDType[dtype[_ScalarT]]
|
||||||
|
|
||||||
|
DTypeLike: TypeAlias = _DTypeLike[Any] | str | None
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can make sure that a function which accepts `DTypeLike | None` works as expected:
|
||||||
|
|
||||||
|
```py
|
||||||
|
import mini_numpy as np
|
||||||
|
|
||||||
|
def accepts_dtype(dtype: np.DTypeLike | None) -> None: ...
|
||||||
|
|
||||||
|
accepts_dtype(dtype=np.bool)
|
||||||
|
accepts_dtype(dtype=np.dtype[np.bool])
|
||||||
|
accepts_dtype(dtype=object)
|
||||||
|
accepts_dtype(dtype=np.object_)
|
||||||
|
accepts_dtype(dtype="U")
|
||||||
|
```
|
||||||
|
|
@ -2468,6 +2468,26 @@ impl<'db> Type<'db> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `type[Any]` is assignable to arbitrary protocols as it has arbitrary attributes
|
||||||
|
// (this is handled by a lower-down branch), but it is only a subtype of a given
|
||||||
|
// protocol if `type` is a subtype of that protocol. Similarly, `type[T]` will
|
||||||
|
// always be assignable to any protocol if `type[<upper bound of T>]` is assignable
|
||||||
|
// to that protocol (handled lower down), but it is only a subtype of that protocol
|
||||||
|
// if `type` is a subtype of that protocol.
|
||||||
|
(Type::SubclassOf(self_subclass_ty), Type::ProtocolInstance(_))
|
||||||
|
if (self_subclass_ty.is_dynamic() || self_subclass_ty.is_type_var())
|
||||||
|
&& !relation.is_assignability() =>
|
||||||
|
{
|
||||||
|
KnownClass::Type.to_instance(db).has_relation_to_impl(
|
||||||
|
db,
|
||||||
|
target,
|
||||||
|
inferable,
|
||||||
|
relation,
|
||||||
|
relation_visitor,
|
||||||
|
disjointness_visitor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
(_, Type::ProtocolInstance(protocol)) => {
|
(_, Type::ProtocolInstance(protocol)) => {
|
||||||
relation_visitor.visit((self, target, relation), || {
|
relation_visitor.visit((self, target, relation), || {
|
||||||
self.satisfies_protocol(
|
self.satisfies_protocol(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue