Incorrect resolution of `type[TypeVar]` returning `TypeVar` with `numpy`
Bug Report
This came up with pandas-stubs, but can reproduce with just numpy
To Reproduce
from typing import TypeVar
import numpy as np
NDArrayT = TypeVar("NDArrayT", bound=np.ndarray)
def foo(x: type[NDArrayT]) -> NDArrayT:
return x([1, 2])
reveal_type(foo(np.ndarray))
Expected Behavior
Revealed type should be ndarray[tuple[Any, ...], dtype[Any]], which is what pyright reports.
Actual Behavior
mypy reports numpy.ndarray[Any, Any]
Your Environment
- Mypy version used: 1.19.0
- Mypy command-line flags: None
- Mypy configuration options from
mypy.ini(and other config files): None - Python version used: 3.11
- numpy version: 2.3.5
- pyright version: 1.1.407
I thought this might be related to the PEP 696 type parameter defaults, but that doesn't seem to be the case:
from typing import Any, reveal_type
import numpy as np
def f[T: np.ndarray](t: type[T]) -> T: ...
def g[T: np.ndarray[tuple[Any, ...], np.dtype[Any]]](t: type[T]) -> T: ...
reveal_type(f(np.ndarray)) # Revealed type is "numpy.ndarray[Any, Any]"
reveal_type(g(np.ndarray)) # Revealed type is "numpy.ndarray[Any, Any]"
In fact, any type arguments to np.ndarray seem to be swallowed this way 🤔
Could you explain further why you expect different output? Any is compatible with any bound, type passed as argument is not parameterized hence defaults all tvars to any, why should it not be Any?
Zero-numpy example (playground):
from typing import Any, Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U", bound="Arr[int]")
class Arr(Generic[T]):
...
def f(t: type[U]) -> U: ...
reveal_type(f(Arr)) # Revealed type is "__main__.Arr[Any]"
type passed as argument is not parameterized hence defaults all tvars to any,
As my example shows, this also occurs when its fully parametrized.
Also note that the type parameter defaults of np.ndarray are tuple[Any, ...] and np.dtype[Any]. So np.ndarray (as type expression) is therefore exactly equivalent to np.ndarray[tuple[Any, ...], np.dtype[Any]].
Ah, sorry, I missed that numpy already started using tvars defaults. So the problem reduces to the following, right?
from typing import Any, Generic, TypeVar
T = TypeVar("T", default=int)
U = TypeVar("U", bound="Arr")
class Arr(Generic[T]):
...
def f(t: type[U]) -> U: ...
reveal_type(f(Arr)) # Revealed type is "__main__.Arr[Any]"
Probably the default should have been applied here.
So the problem reduces to the following, right?
Yea pretty much. There's also where f is parametrized with Arr[int] (so with type arg) that behaves unexpectedly:
# mypy: disable-error-code=empty-body
from typing import Any, Generic, TypeVar
T_co = TypeVar("T_co", default=int)
class Arr(Generic[T_co]): ...
def f[T: Arr](t: type[T]) -> T: ...
def g[T: Arr[int]](t: type[T]) -> T: ...
reveal_type(f(Arr)) # Revealed type is "__main__.Arr[Any]"
reveal_type(g(Arr)) # Revealed type is "__main__.Arr[Any]"
https://mypy-play.net/?mypy=latest&python=3.14&flags=strict&gist=b51972fc19e7c56363428c62dede27e7
Just a hunch but wouldn't this be one of the cases covered by PEP 747?
Nah, this is the correct use for type - TypeForm is not needed when you have an actual class. TypeForm is compatible with things like TypeVar itself, should be irrelevant here - the problem is not evaluating Arr in f(Arr) as if it was in a type context (failure to resolve the typevar to its default). The snippet is correct, rejecting it is a defect in mypy.