Generic argument is not being identified
Bug Report
The code in this mypy playground raises the following error.
Name "T" is not defined [name-defined]
However, as seen in this pyright playground there is no error, which leads me to believe that the snippet is well defined.
This one is probably related but the revealed types do not seem to match as well (the ? in the type name seems to be rather strange).
mypy
"__main__.Repo[T?, U`-2]"
pyright
"Repo[T@some, U@some]"
To Reproduce (this is the same code as in the mypy playground)
from typing import cast, reveal_type
from collections.abc import Callable
class Repo[T, U]: ...
def some[T, U, **P](func: Callable[P, U]) -> Callable[P, U]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> U:
reveal_type(cast(Repo[T, U], args[0]))
return func(*args, **kwargs)
return wrapper
Expected Behavior
No errors from mypy, similar behavior to pyright.
Actual Behavior
I am not sure if mypy is misdiagnosing this, if you feel this is intended behavior I can try raising an issue with pyright.
Your Environment
- Mypy version used: 1.15.0 (playground)
- Pyright version used: 1.1.399 (playground)
- Mypy command-line flags: default settings in mypy playground (if I am not mistaken, there are no extra flags)
- Mypy configuration options from
mypy.ini(and other config files): N/A - Python version used: 3.13 (for both playgrounds)
Thank You
Edit: Simplified the example a bit by removing the bounds and awaitables.
Minimized:
def some[T]() -> None:
x: list[T]
reveal_type(x)
This is funny! Yes, type vars are now bound in the function body if typevar is used in argument types or return types (or comes from parent scope - class or function). It's a bug to not bind them by PEP695 syntax ("name-defined" is wrong error code). However, your usage (and especially the simplified snippet in the previous comment) is still invalid - semantically, T has no meaning there, it isn't coming from the signature or parent scopes. Same as in
from typing import TypeVar
T = TypeVar("T")
def foo() -> None:
x: T # E: Type variable "__main__.T" is unbound [valid-type] \
# N: (Hint: Use "Generic[T]" or "Protocol[T]" base class to bind "T" inside a class) \
# N: (Hint: Use "T" in function signature to bind "T" inside a function)
where mypy produces a correct error. IMO the cleanest solution would be to emit exactly the same error in the OP case.
Thanks for responding @sterliakov.
I tried a slightly modified version of @A5rocks' snippet (assigned x to an empty list to remove the unbound error). It indeed fails in Pyright when using TypeVar but not when using 695 syntax. For completion sake, here are all the 4 versions.
Pyright (TypeVar) - Intended behavior Type variable "T" has no meaning in this context
Pyright 695 - no issues (Bugged behavior?)
Mypy (TypeVar) - Intended behavior Type variable "__main__.T" is unbound [valid-type]
Mypy 696 - Intended behavior but misleading error message Name "T" is not defined [name-defined]
Is Pyright wrong and should I raise an issue there?
PS: I am aware I might have repeated what you both mentioned with different phrasing, but if I am planning to raise this in Pyright, I believe a single comment with the entire context could be useful.
PPS: This might be tangential and I can move this part to discussion (or any other relevant forum), do you mind helping me understand how I can express my original use case in mypy if this is intended behavior? I want Repo to be generic on T and U.
No, I think this should work with PEP 695 syntax. The user is explicitly binding the type variable in the function and mypy should respect that intent even if there's no arguments for it (maybe there should be an error about the [T] on the function).
@Alc-Alc the reason this doesn't work for TypeVar is because mypy doesn't know what is generic in T! Imagine this:
def f():
def g():
x: T
Which function is generic in T? (also it's an antipattern but I think it's important to have lints specifically for that antipattern)
but also, what @sterliakov is pointing out is that your function does not take any argument for T, so mypy will never know what type T is. This shouldn't matter at function declaration though (though it's a sign you're missing something) and at usage mypy can fill it in with Never.
You probably mean something like this:
from typing import cast, reveal_type
from collections.abc import Callable
class Repo[T, U]: ...
def some[U, **P](func: Callable[P, U]) -> Callable[P, U]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> U:
reveal_type(cast(Repo[object, U], args[0]))
return func(*args, **kwargs)
return wrapper
or even better:
def some[T, U, **P](func: Callable[Concatenate[Repo[T, U], P], U]) -> Callable[Concatenate[Repo[T, U], P], U]:
def wrapper(repo: Repo[T, U], /, *args: P.args, **kwargs: P.kwargs) -> U:
reveal_type(repo)
return func(repo, *args, **kwargs)
return wrapper
(all above code is untested)
same
class LockService[KeyT]:
def __init__(self) -> None:
self._locks: dict[KeyT, asyncio.Lock] = {}
def get(self, key: KeyT) -> asyncio.Lock:
if key not in self._locks:
self._locks[key] = asyncio.Lock()
return self._locks[key]
def get_locks_service[KeyT]() -> LockService[Any]:
return LockService[KeyT]()
got error: Name "KeyT" is not defined [name-defined]
(1) An error is emited even if the type parameter is bounded:
class A:
pass
def f[T:A]() -> None:
y = list[T]() # Line 9: error
(2) I agree with @sterliakov that in a language where we can express the relation between the type parameter T and the input arguments, it is poor code to have no input/output arguments depending on T. However, the python typing syntax is rather constrained.
Consider the following example:
from typing import Self, Protocol
class Comparable(Protocol):
def __le__(self, other:Self) -> bool: ...
class ConcatenableSequence[T](Protocol):
def __getitem__(self, x:int) -> T: ...
def __len__(self) -> int: ...
def __add__(self, other:Self) -> Self: ...
def concat_smallest_first[S:ConcatenableSequence,T:Comparable](s1:S, s2:S, __x:T|None=None) -> S|None:
"""
Concatenate two sequences, putting the sequence with smallest
first element first. To ensure the __add__ operator works (its
argument is of type "Self"), we need s1 and s2 to be of the same
(ConcatenableSequence[T]) type.
"""
x1 : T = s1[0]
x2 : T = s2[0]
if x1 <= x2:
return s1+s2
return s2+s1
The idea is that we want to concatenate two concatenable sequences (list, tuple or other type supporting the + operator), but with the sequence starting with the smallest element first. The above code, is valid, but if we remove the useless __x parameter mypy generates 3 errors (T undefined, T undefined, <= not supported). Still, (a) we need T to express we assume the sequences to be over a type supporting __le__ [omitting T everywhere makes mypy complain about the <= operator not being supported], (b) T does not occur in the input arguments, and (c) we can't use a bound such as S:ConcatenableSequence[T] since that is illegal (unfortunately, it would solve a lot of my problems). So there is no easy opportunity to link T to an input argument, while without annotating x1 and x2 we can't express that the type of the elements of the sequences should be Comparable.
We can in this specific case of course circumvent the problem by defining a Protocol class ConcatenableSequenceOverComparableElements, but even if we do that we would not be allowed to type annotate the elements x1 and x2. At least it seems that there are cases where a type not connected to function input parameters but relevant inside the body of the function could make semantically some sense.
If we look at the situation where the __x argument is present, I should say I'll always call this function without specifying a value, making __x is None, so nobody will learn something extra about the type T compared to the case without __x where T can be inferred from the assignment x1:T=s1[0].
the case without
__xwhere T can be inferred from the assignmentx1:T=s1[0].
The problem is that it will not be. You are looking for HKT, I think. Mypy (and Python's type system, generally) doesn't support that! In my opinion, this issue should be resolved by changing the error message in the PEP 695 case. There's nothing more to this I think!