python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.17k stars 2.77k forks source link

MyPy 1.11.0 loop variable persistance #17541

Open j6mes opened 1 month ago

j6mes commented 1 month ago

Bug Report In MyPy version 1.11.0, the type inference for loop variables seems to persist across different loops when the same variable name is used. This behavior differs from MyPy version 1.10.1, where the code works without issues.

To Reproduce

from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES

class Test:
  def test(self) -> None:
    self.stage_fn = lambda x: x
    for attr in WRAPPER_ASSIGNMENTS:
      object.__setattr__(self, attr, getattr(self.stage_fn, attr))

    for attr in WRAPPER_UPDATES:
      getattr(self, attr).update(getattr(self.stage_fn, attr, {}))

Expected Behavior In previous versions, this did not raise an issue.

Actual Behavior

test.py:9: error: Incompatible types in assignment (expression has type "Literal['__dict__']", variable has type "Literal['__module__', '__name__', '__qualname__', '__doc__', '__annotations__']")  [assignment]
Found 1 error in 1 file (checked 1 source file)

This is seemingly fixed by changing the loop variables

for attr_1 in WRAPPER_ASSIGNMENTS:
    object.__setattr__(self, attr_1, getattr(self.stage_fn, attr_1))

for attr_2 in WRAPPER_UPDATES:
    getattr(self, attr_2).update(getattr(self.stage_fn, attr_2, {}))

This workaround should not be necessary, as loop variables should be scoped independently.

Your Environment

mypy==1.11.0
mypy-boto3-s3==1.34.138
mypy-extensions==1.0.0

pyproject.toml:

[tool.mypy]
explicit_package_bases = true
files = "src,test"
mypy_path = "src"
plugins = ["pydantic.mypy"]
strict = true   
koogoro commented 1 month ago

I think this isn't a bug. Loop variables in Python are function-scoped and do continue existing beyond the end of the loop. Examples like

a: list[str]
b: list[int]

for x in a:
    pass
for x in b:
    pass

have failed since at least mypy 1.0. I think the difference is that mypy 1.11 now assigns the type Literal['__module__', '__name__', '__qualname__', '__doc__', '__annotations__', '__type_params__'] to attr on the first loop instead of the type str. If you want this to succeed without needing to rename the variable, maybe try declaring attr: str before the first loop?

hauntsaninja commented 1 month ago

Yeah, agree with koogoro. The relevant mypy change here is #17408