-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Bug Report
When a generic class uses TypeVarTuple (PEP 646) and a method has a self-type annotation that "reshapes" the type parameters, mypy incorrectly infers the return type when the class is instantiated with Unpack[Tuple[Any, ...]].
Specifically, mypy infers the return type as None instead of Any | None, leading to false "unreachable code" errors.
To Reproduce
from typing import Generic, TypeVar, Optional, Iterator, Any, Tuple
from typing_extensions import TypeVarTuple, Unpack
_T = TypeVar("_T", bound=Any)
_Ts = TypeVarTuple("_Ts")
class Result(Generic[Unpack[_Ts]]):
"""Generic result class that takes variadic type parameters.
This mimics SQLAlchemy's Result class which is generic over Unpack[_Ts].
"""
def scalar(self: "Result[_T, Unpack[Tuple[Any, ...]]]") -> Optional[_T]:
return None
def test_function() -> Iterator[Any]:
result: Result[Unpack[Tuple[Any, ...]]] = Result()
reveal_type(result)
data = result.scalar()
reveal_type(data)
if data is None:
return
yield from iter(data)Expected Behavior
If we run pyright, the type of "data" is "Any | None" and the yield from is reachable:
$ pyright mypy_bug_repro.py
/home/classic/dev/sqlalchemy/mypy_bug_repro.py
/home/classic/dev/sqlalchemy/mypy_bug_repro.py:22:17 - information: Type of "result" is "Result[*tuple[Any, ...]]"
/home/classic/dev/sqlalchemy/mypy_bug_repro.py:24:17 - information: Type of "data" is "Any | None"
0 errors, 0 warnings, 2 informations
Actual Behavior
Mypy sees "data" as None in all cases
$ mypy --warn-unreachable mypy_bug_repro.py
mypy_bug_repro.py:22: note: Revealed type is "mypy_bug_repro.Result[Unpack[builtins.tuple[Any, ...]]]"
mypy_bug_repro.py:24: note: Revealed type is "None"
mypy_bug_repro.py:27: error: Statement is unreachable [unreachable]
Found 1 error in 1 file (checked 1 source file)
Your Environment
- Mypy version: mypy 1.19.1 (compiled: yes)
- Python version: 3.14
- typing_extensions version: latest (for TypeVarTuple, Unpack)
- Pyright version (for comparison): 1.1.408
notes
So I used Claude to refactor SQLAlchemy's structures into standalone features. Additional commentary from Claude which may or may not be helpful:
The bug appears to be in mypy's type parameter matching logic. When trying to match:
- Instance type:
Result[Unpack[Tuple[Any, ...]]] - Against self-type:
Result[_T, Unpack[Tuple[Any, ...]]]
Mypy should extract _T = Any from the first element of the unpacked tuple, but instead appears to fail at this extraction, possibly binding _T to Never or treating it as non-existent, resulting in Optional[Never] = None.
downstream SQLAlchemy issues is at sqlalchemy/sqlalchemy#13089 which is local to the new 2.1.0beta1 release that uses pep-646