Nondeterministic output when using tuple type vars
Bug Report Mypy gives an inconsistent result when run on the same code.
To Reproduce Create the following file:
# test.py
from typing import Any, Generic, Tuple, TypeVar, overload
_TP = TypeVar("_TP", bound=Tuple[Any, ...])
class MyClass(Generic[_TP]):
pass
_Single = TypeVar("_Single")
_Multiple = TypeVar(
"_Multiple",
bound=tuple[Any, Any] | tuple[Any, Any, Any] | tuple[Any, Any, Any, Any] | tuple[Any, Any, Any, Any, Any],
)
@overload
async def inner(query: MyClass[tuple[_Single]]) -> list[_Single]: ...
@overload
async def inner(query: MyClass[_Multiple]) -> list[_Multiple]: ...
async def inner(query: MyClass[tuple[_Single]] | MyClass[_Multiple]) -> list[_Single] | list[_Multiple]:
return []
@overload
async def outer(query: MyClass[tuple[_Single]]) -> _Single: ...
@overload
async def outer(query: MyClass[_Multiple]) -> _Multiple: ...
async def outer(query: MyClass[tuple[_Single]] | MyClass[_Multiple]) -> _Single | _Multiple:
result = await inner(query)
if result:
return result[0]
raise ValueError
And run mypy with:
mypy --strict --no-incremental test.py
Expected Behavior Mypy should give a consistent output, either approve or deny the code.
Actual Behavior I get two different outputs if I run it 3-4 times:
Success: no issues found in 1 source file
test.py:43: error: Returning Any from function declared to return "_Single | _Multiple" [no-any-return]
Found 1 error in 1 file (checked 1 source file)
Notes The code might seem strange and is just something I cooked out of our real life use case (DB queries with SQLAlchemy). Perhaps I'm using an incorrect pattern here in the first place?
Your Environment
- Mypy version used: 1.20.0+dev.ad0f41eea63ef227739b66f08afbb67544597d79 (compiled: no)
- Mypy command-line flags:
--strict --no-incremental - Mypy configuration options from
mypy.ini(and other config files): None - Python version used: 3.13.3
Thanks for the great repro!
Bisects to #16345 , which isn't the most helpful
Slightly trimmed down version of your repro:
from typing import Any, Generic, TypeVar, overload
_Single = TypeVar("_Single")
_Multiple = TypeVar("_Multiple", bound=tuple[Any, Any] | tuple[Any, Any, Any])
_TP = TypeVar("_TP")
class MyClass(Generic[_TP]): ...
@overload
async def inner(query: MyClass[_Single]) -> _Single: ...
@overload
async def inner(query: MyClass[_Multiple]) -> _Multiple: ...
async def inner(*a: Any, **kw: Any) -> Any:
raise NotImplementedError
async def outer(query: MyClass[_Single] | MyClass[_Multiple]) -> _Single | _Multiple:
result = await inner(query)
if result:
return result
raise ValueError
Looks like another case of non-commutative join, looking more...