python / mypy

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

(🐞) `for`/`else` with `break` statement incorrect possibly-undefined error #14209

Open KotlinIsland opened 1 year ago

KotlinIsland commented 1 year ago

https://github.com/python/mypy/pull/14191#issuecomment-1328372923

a: list[int]

for i in a:
    if i:
        b = i
        break
else:
    b = 1

print(b)  # error: Name "b" may be undefined  [possibly-undefined]
Dreamsorcerer commented 1 year ago

Also when the else condition raises:

x: int
while x:
    if x == 5:
        foo = 10
        break
else:
    raise RuntimeError()

foo  # possibly-undefined
erictraut commented 1 year ago

I'm not able to repro the problem with the latest version of mypy. But then again, I'm not able to repro with any versions going back to 0.990, so maybe I'm not using the right configuration.

@KotlinIsland, are you able to repro this issue with the latest version?

BTW, it would be useful in the future if you included the mypy version in your bug reports as requested in the bug template.

Dreamsorcerer commented 1 year ago

Did you miss enabling the error code? mypy --enable-error-code 'possibly-undefined'

Both still reproduce on 1.5.

erictraut commented 1 year ago

Ah, that was what I was missing. I though this error code was enabled by default in recent versions of mypy.

KotlinIsland commented 1 year ago

@erictraut You should try using basedmypy, all errors are enabled by default.

Dreamsorcerer commented 1 year ago

It also seems impossible to get something like this to type check:

for i in range(5):
    b = True

print(b)  # error: Name "b" may be undefined  [possibly-undefined]

I guess mypy doesn't know how to confirm that the loop is guaranteed to execute.

giacomo-alzetta-aiven commented 9 months ago

This also fails:

for element in sequence:
    if (something := something_else.get(element)) is not None:
        break
else:
    return None
print(something)   # triggers possibly-undefined

Seems like mypy doesn't understand that the else clause of the for loop will run if sequence is empty...

NMertsch commented 2 months ago

I never contributed code to mypy before, but would love to get this fixed. Is this a "good first issue" candidate?

KotlinIsland commented 2 months ago

Yeah, it would be a relatively simple scenario. Likely only one spot that needs fixing

NMertsch commented 2 months ago

I started my Draft PR (#17720) by adding three failing test cases reproducing the issue. If someone can help me with a builtins/fixture issue, that would be highly appreciated.

It's not a blocker yet (I have two "good" failing tests to work with), but I'm curious to learn more about how this is done "properly" 🙂

KotlinIsland commented 2 months ago

update unit/fixtures/exception.pyi to:

class list:
    def __iter__(self): ...
NMertsch commented 2 months ago

While investigating this issue, I determined five separate issues or possible improvements with possibly-undefined and loops:

  1. for and while loop: else execution is dependent on position, not just presence of break (cause for this issue)
  2. for and while loop: else is not just "no break", but also executed if the body is not executed
  3. for loop: index is only defined if the body is executed
    • fix in #17720
  4. for loop: infer emptiness of expression
  5. while loop: infer truthiness of expression

I hope I can get #17720 merged, but currently don't have the capacity to work on the other points.