Assigning to intermediate variable changes type checking results
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 20.6k
- Forks
- 3.3k
- PR merge metrics
- PR metrics pending
Description
Bug Report
- Assigning to an intermediate variable can change inference results.
- Incorrect inference when inside a function call.
# mypy: disable-error-code=empty-body
# fmt: off
from typing import Iterable, Iterator
class Vec[T]: # proxy for list[T]
def getitem(self, i: int) -> T: ... # ensure invariance of T
def setitem(self, i: int, v: T) -> None: ... # ensure invariance of T
def __init__(self, iterable: Iterable[T], /) -> None: ...
def __iter__(self) -> Iterator[T]: ...
def __add__[S](self, value: "Vec[S]", /) -> "Vec[S | T]": ...
def fmt(arg: Iterable[int | str]) -> None: ... # <-- union plays a role
l1: Vec[int] = Vec([1])
l2: Vec[int] = Vec([1])
fmt(l1 + l2) # ❌ Unsupported operand types for + ("Vec[int]" and "Vec[int]")
dummy = l1 + l2
fmt(dummy) # ✅
Same example without PEP 695
https://mypy-play.net/?mypy=latest&python=3.12&gist=43a91e52a767ac27f2706795d45bdef1
# mypy: disable-error-code=empty-body
# fmt: off
from typing import TypeVar, Generic, Iterable, Iterator
T = TypeVar("T")
S = TypeVar("S")
class Vec(Generic[T]):
def getitem(self, i: int) -> T: ... # ensure invariance of T
def setitem(self, i: int, v: T) -> None: ... # ensure invariance of T
def __init__(self, iterable: Iterable[T], /) -> None: ...
def __iter__(self) -> Iterator[T]: ...
def __add__(self, value: "Vec[S]", /) -> "Vec[S | T]": ...
def fmt(arg: Iterable[int | str]) -> None: ...
l1: Vec[int] = Vec([1])
l2: Vec[int] = Vec([1])
fmt(l1 + l2) # ❌ Unsupported operand types for + ("Vec[int]" and "Vec[int]")
dummy = l1 + l2
fmt(dummy) # ✅
original bug report
Bug Report
I was testing this PR (https://github.com/python/typeshed/issues/14283) for typeshed that simplifies list.__add__ from
@overload
def __add__(self, value: list[_T], /) -> list[_T]: ...
@overload
def __add__(self, value: list[_S], /) -> list[_S | _T]: ...
to
def __add__(self, value: list[_S], /) -> list[_S | _T]: ...
This seems to work generally, but there are some weird circumstances when it bugs out. It seems most of them appear when a concatenation is given as an argument to another function.
As an example, this is one of the lines that gets flagged:
https://github.com/python/mypy/blob/5081c59b9c0c7ebe7070c62a4aeaf3d0de203a24/mypyc/crash.py#L29
However, mypy stops complaing if it is changed to
dummy = tb + tb2
for s in traceback.format_list(dummy):
Assigning to an intermediate variable changed the type checking results (!)
I couldn't reproduce this behavior using a custom class, which makes me believe this is probably due to some weird special casing for builtins.
To Reproduce
git clone --branch polymorphic_overload_test https://github.com/randolf-scholz/typeshed.git
git clone https://github.com/python/mypy.git
cd typeshed
uv venv --seed
source .venv/bin/activate
uv pip install -r requirements-tests.txt
mkdir tmp
cp ../mypy/mypyc/crash.py tmp/tmp.py
python -m mypy.stubtest --custom-typeshed-dir=../typeshed tmp
Expected Behavior
Assigning to an intermediate variable shouldn't affect type inference.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the examples in the issue and reproduce the behavior using the provided mypy-playground links. Then run the listed stubtest command with mypyc/crash.py, comparing the direct function call with the intermediate-variable version. Done means assigning the intermediate result no longer changes the type-checking outcome.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100