Skip to content

a constrained TypeVar should be assignable to another constrained TypeVar with a superset of its constraints #2728

@neutrinoceros

Description

@neutrinoceros

Summary

Here's a minimal example:
src/pkg contains

__init__.py # empty
types.py
a.py
b.py

with

# types.py
from numpy import float32 as f32
from numpy import float64 as f64
from typing import TypeVar

F = TypeVar("F", f32, f64)
# a.py
from .types import F
from numpy.typing import NDArray

def a(a: NDArray[F]) -> NDArray[F]:
    return a
# b.py
from .a import a
from numpy.typing import NDArray
from numpy import float32 as f32
from numpy import float64 as f64
from typing import TypeVar

B = TypeVar("B", f32, f64)

def b(b: NDArray[B]) -> NDArray[B]:
    return a(b)

running ty check src gives me

error[invalid-argument-type]: Argument to function `a` is incorrect
  --> src/reprod_ty/b.py:11:14
   |
10 | def b(b: NDArray[B]) -> NDArray[B]:
11 |     return a(b)
   |              ^ Argument type `B@b` does not satisfy constraints (`floating[_32Bit]`, `float64`) of type variable `F`
   |
info: Type variable defined here
 --> src/reprod_ty/types.py:5:1
  |
3 | from typing import TypeVar
4 |
5 | F = TypeVar("F", f32, f64)
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
info: rule `invalid-argument-type` is enabled by default

B and F are distinct, but actually equivalent type vars. I would expect B to behave as an adequate substitute for F.

This comes from a real life case where F and a are defined in some external package, with a being part of the public interface but F is not (type definitions are private), so I wanted to replicate the type's definition in code I control in b.py. FWIW, mypy doesn't reject this code, though I don't consider this proof that it shouldn't. Maybe there's something off in my understanding of how typevars are supposed to work ?

Version

ty 0.0.15 (eb43dff 2026-02-04)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions