python / python/mypy

Assigning to intermediate variable changes type checking results

Open
#19,304 9 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-type-context
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-playground

# 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.