Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/libraries/numpy.md
Original file line number Diff line number Diff line change
@@ -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`"
Comment on lines +47 to +49
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These errors prevent us from running into the problem on main. But on #21553, these errors will go away, and then we need to properly understand all of this code here, or otherwise we see np.array calls failing all over the place.

_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")
```
20 changes: 20 additions & 0 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)) => {
relation_visitor.visit((self, target, relation), || {
self.satisfies_protocol(
Expand Down
Loading