Skip to content

Commit 80bc86e

Browse files
committed
[ty] Workaround for problem with numpy's dtype
1 parent c534bfa commit 80bc86e

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# numpy
2+
3+
```toml
4+
[environment]
5+
python-version = "3.14"
6+
```
7+
8+
## numpy's `dtype`
9+
10+
numpy functions often accept a `dtype` parameter. For example, one of `np.array`'s overloads accepts
11+
a `dtype` parameter of type `DTypeLike | None`. Here, we build up something that resembles numpy's
12+
internals in order to model the type `DTypeLike`. Many details have been left out.
13+
14+
`mini_numpy.py`:
15+
16+
```py
17+
from typing import TypeVar, Generic, Any, Protocol, TypeAlias, runtime_checkable, final
18+
import builtins
19+
20+
_ItemT_co = TypeVar("_ItemT_co", default=Any, covariant=True)
21+
22+
class generic(Generic[_ItemT_co]):
23+
@property
24+
def dtype(self) -> _DTypeT_co:
25+
raise NotImplementedError
26+
27+
_BoolItemT_co = TypeVar("_BoolItemT_co", bound=builtins.bool, default=builtins.bool, covariant=True)
28+
29+
class bool(generic[_BoolItemT_co], Generic[_BoolItemT_co]): ...
30+
31+
@final
32+
class object_(generic): ...
33+
34+
_ScalarT = TypeVar("_ScalarT", bound=generic)
35+
_ScalarT_co = TypeVar("_ScalarT_co", bound=generic, default=Any, covariant=True)
36+
37+
@final
38+
class dtype(Generic[_ScalarT_co]): ...
39+
40+
_DTypeT_co = TypeVar("_DTypeT_co", bound=dtype, default=dtype, covariant=True)
41+
42+
@runtime_checkable
43+
class _SupportsDType(Protocol[_DTypeT_co]):
44+
@property
45+
def dtype(self) -> _DTypeT_co: ...
46+
47+
# TODO: no errors here
48+
# error: [invalid-type-arguments] "Type `typing.TypeVar` is not assignable to upper bound `generic[Any]` of type variable `_ScalarT_co@dtype`"
49+
# error: [invalid-type-arguments] "Type `typing.TypeVar` is not assignable to upper bound `generic[Any]` of type variable `_ScalarT_co@dtype`"
50+
_DTypeLike: TypeAlias = type[_ScalarT] | dtype[_ScalarT] | _SupportsDType[dtype[_ScalarT]]
51+
52+
DTypeLike: TypeAlias = _DTypeLike[Any] | str | None
53+
```
54+
55+
Now we can make sure that a function which accepts `DTypeLike | None` works as expected:
56+
57+
```py
58+
import mini_numpy as np
59+
60+
def accepts_dtype(dtype: np.DTypeLike | None) -> None: ...
61+
62+
accepts_dtype(dtype=np.bool)
63+
accepts_dtype(dtype=np.dtype[np.bool])
64+
accepts_dtype(dtype=object)
65+
accepts_dtype(dtype=np.object_)
66+
accepts_dtype(dtype="U")
67+
```

crates/ty_python_semantic/src/types.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2468,6 +2468,31 @@ impl<'db> Type<'db> {
24682468
})
24692469
}
24702470

2471+
// TODO: The following two branches are a temporary workaround to prevent an important union type
2472+
// in numpy's internals from collapsing to a single type, which causes thousands of downstream
2473+
// false positive diagnostics across the ecosytem. See https://github.com/astral-sh/ty/issues/1666
2474+
// for details.
2475+
(Type::SubclassOf(self_subclass_ty), Type::ProtocolInstance(_))
2476+
if self_subclass_ty.is_type_var() && !relation.is_assignability() =>
2477+
{
2478+
ConstraintSet::from(false)
2479+
}
2480+
// `type[Any]` is assignable to arbitrary protocols as it has arbitrary attributes
2481+
// (this is handled by a lower-down branch), but it is only a subtype of
2482+
// a given protocol if `type` is a subtype of that protocol
2483+
(Type::SubclassOf(self_subclass_ty), Type::ProtocolInstance(_))
2484+
if self_subclass_ty.is_dynamic() && !relation.is_assignability() =>
2485+
{
2486+
KnownClass::Type.to_instance(db).has_relation_to_impl(
2487+
db,
2488+
target,
2489+
inferable,
2490+
relation,
2491+
relation_visitor,
2492+
disjointness_visitor,
2493+
)
2494+
}
2495+
24712496
(_, Type::ProtocolInstance(protocol)) => {
24722497
relation_visitor.visit((self, target, relation), || {
24732498
self.satisfies_protocol(

0 commit comments

Comments
 (0)