Skip to content

Iterator evaluators (e.g. any) don't erase types when in conjunction with Never functions #15703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
C-Ezra-M opened this issue Jul 18, 2023 · 3 comments
Labels
bug mypy got something wrong

Comments

@C-Ezra-M
Copy link

Bug Report

Functions that evaluate iterators (like any) don't disqualify a value from being of another type if there's a function that never returns (like sys.exit).

To Reproduce

https://mypy-play.net/?mypy=latest&python=3.11&gist=bf0b47681fdf97d19050eff3473d03f6

Actual Behavior

main.py:16: error: Argument 1 to "some_function" has incompatible type "int | None"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.4.1
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): defaults
  • Python version used: 3.11
@C-Ezra-M C-Ezra-M added the bug mypy got something wrong label Jul 18, 2023
@C-Ezra-M
Copy link
Author

I was going to report that to #13592, but I figured I should start a separate issue.

Another good example: https://mypy-play.net/?mypy=latest&python=3.11&gist=be72c683d77ea7655f78aa56f603e689
This gist is the same as https://github.com/Keyacom/small-python-projects/blob/main/fire-tools/expcalc.py, except that I removed the type: ignore comment on line 72.
Here, mypy fails to acknowledge the fact any code beyond sys.exit() is not reachable. Pyright also behaves like this.
In lines 62~65, I'm not allowing chal, level, or fb to be None. They're initialized as None, but they can be fetched from command line arguments:

    if any(x is None for x in (chal, level, fb)):
        print("Incomplete arguments.")
        print(USAGE)
        exit(2)

This later causes errors on line 72:

        calc_exp(fb, chal, level)

with the mypy output:

main.py:72: error: Argument 1 to "calc_exp" has incompatible type "int | None"; expected "int"  [arg-type]
main.py:72: error: Argument 2 to "calc_exp" has incompatible type "int | None"; expected "int"  [arg-type]
main.py:72: error: Argument 3 to "calc_exp" has incompatible type "int | None"; expected "int"  [arg-type]
Found 3 errors in 1 file (checked 1 source file)

For reference, here's calc_exp's signature:

def calc_exp(fb: int, chal: int, level: int) -> int:
    ...

NOTE: If I change line 62 to the one below, there are no errors. It's just more verbose.

    if chal is None or level is None or fb is None:

@A5rocks
Copy link
Collaborator

A5rocks commented Jul 19, 2023

Err, I don't think any narrows to begin with. Normally, narrowing does happen if you exit:

from sys import argv, exit

def some_function(arg: int) -> int:
    return arg

num = None

try:
    num = int(argv[1])
except ValueError:
    pass

if num is None:
    exit(2)

print(some_function(num))

(notice the slightly modified if statement.)

@hauntsaninja
Copy link
Collaborator

This narrowing pattern is a little complex, I'd recommend using a PEP 647 typeguard. I'm not aware of any other Python static type checker that narrows like this.

@hauntsaninja hauntsaninja closed this as not planned Won't fix, can't repro, duplicate, stale Aug 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

3 participants