Skip to content

Incorrect resolution of type[TypeVar] returning TypeVar with numpy #20354

Open

Description

@Dr-Irv

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

Activity

  1. jorenham commented on Dec 2, 2025

    @jorenham
    Contributor

    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 🤔

  2. sterliakov commented on Dec 2, 2025

    @sterliakov
    Collaborator

    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]"
  3. jorenham commented on Dec 2, 2025

    @jorenham
    Contributor

    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]].

  4. sterliakov commented on Dec 2, 2025

    @sterliakov
    Collaborator

    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.

  5. jorenham commented on Dec 2, 2025

    @jorenham
    Contributor

    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

  6. jacopoabramo commented on Dec 5, 2025

    @jacopoabramo
    Contributor

    Just a hunch but wouldn't this be one of the cases covered by PEP 747?

  7. sterliakov commented on Dec 5, 2025

    @sterliakov
    Collaborator

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions