Skip to content

Commit 422f31e

Browse files
committed
Workaround for numpy problem
1 parent 69cdc8c commit 422f31e

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
_DTypeLike: TypeAlias = type[_ScalarT] | dtype[_ScalarT] | _SupportsDType[dtype[_ScalarT]]
48+
49+
DTypeLike: TypeAlias = _DTypeLike[Any] | str | None
50+
```
51+
52+
Now we can make sure that a function which accepts `DTypeLike | None` works as expected:
53+
54+
```py
55+
import mini_numpy as np
56+
57+
def accepts_dtype(dtype: np.DTypeLike | None) -> None: ...
58+
59+
accepts_dtype(dtype=np.bool)
60+
accepts_dtype(dtype=np.dtype[np.bool])
61+
accepts_dtype(dtype=object)
62+
accepts_dtype(dtype=np.object_)
63+
accepts_dtype(dtype="U")
64+
```

crates/ty_python_semantic/resources/mdtest/type_of/generics.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ def _[T: int](_: T):
163163
static_assert(is_assignable_to(type[T], Callable[..., T]))
164164
static_assert(not is_disjoint_from(type[T], Callable[..., T]))
165165

166-
static_assert(is_assignable_to(type[T], IntCallback))
166+
# TODO: No error here.
167+
static_assert(is_assignable_to(type[T], IntCallback)) # error: [static-assert-error]
167168
static_assert(not is_disjoint_from(type[T], IntCallback))
168169

169170
static_assert(is_subtype_of(type[T], type[T] | None))

crates/ty_python_semantic/src/types.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2465,6 +2465,33 @@ impl<'db> Type<'db> {
24652465
})
24662466
}
24672467

2468+
// TODO: The following two branches are a temporary workaround to prevent an important union type
2469+
// in numpy's internals from collapsing to a single type, which causes thousands of downstream
2470+
// false positive diagnostics across the ecosytem.
2471+
(Type::SubclassOf(self_subclass_ty), Type::ProtocolInstance(_))
2472+
if self_subclass_ty.is_type_var() =>
2473+
{
2474+
ConstraintSet::from(false)
2475+
}
2476+
(Type::SubclassOf(self_subclass_ty), Type::ProtocolInstance(_))
2477+
if self_subclass_ty.is_dynamic() =>
2478+
{
2479+
match relation {
2480+
TypeRelation::Assignability => {
2481+
// type[Dynamic] has arbitrary attributes, so it satisfies all protocols.
2482+
ConstraintSet::from(true)
2483+
}
2484+
_ => KnownClass::Type.to_instance(db).has_relation_to_impl(
2485+
db,
2486+
target,
2487+
inferable,
2488+
relation,
2489+
relation_visitor,
2490+
disjointness_visitor,
2491+
),
2492+
}
2493+
}
2494+
24682495
(_, Type::ProtocolInstance(protocol)) => {
24692496
relation_visitor.visit((self, target, relation), || {
24702497
self.satisfies_protocol(

0 commit comments

Comments
 (0)