Skip to content
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

False positive and negative with lambda narrowing #16056

Closed
md2468 opened this issue Sep 6, 2023 · 6 comments
Closed

False positive and negative with lambda narrowing #16056

md2468 opened this issue Sep 6, 2023 · 6 comments
Labels
bug mypy got something wrong

Comments

@md2468
Copy link

md2468 commented Sep 6, 2023

Bug Report

Type narrowing does not work correctly in function bodies. Even if the type is narrowed down in the context in which a function or a lambda is defined, that narrowing seems to be 'forgotten' within the function body.

To Reproduce

x: int | None = 1

# Either of these two type checks produces identical results:
if isinstance(x, int):
# if x is not None:

    # Revealed type is "builtins.int"
    reveal_type(x)
    
    def f() -> int: 
         # Revealed type is "Union[builtins.int, None]"
        reveal_type(x)
        
        # error: Unsupported operand types for + ("None" and "int")  [operator]
        # note: Left operand is of type "int | None"
        # return x + 2 
        
        # No errors here:
        return 1 + 2
    
    # error: Unsupported operand types for + ("None" and "int")  [operator]
    # note: Left operand is of type "int | None"
    # (lambda: x + 2)()
    
    # No errors here:
    (lambda: 1 + 2)()
    
    # However, defining a lambda without calling it does not cause errors:
    l = lambda: x + 2

Gist URL: https://gist.github.com/mypy-play/41141b62a73edc56b6561088adea8ebf
Playground URL: https://mypy-play.net/?mypy=latest&python=3.11&flags=strict&gist=41141b62a73edc56b6561088adea8ebf

Expected Behavior

No errors in the lines where errors are actually reported. Type narrowing should make all of these expressions valid.

Actual Behavior

The errors given in the comments.

Your Environment

  • Mypy version used: 1.5.1.
  • Mypy command-line flags: tested with no flags and --strict.
  • Mypy configuration options from mypy.ini (and other config files): None.
  • Python version used: 3.11.
@md2468 md2468 added the bug mypy got something wrong label Sep 6, 2023
@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Sep 6, 2023

Mypy is basically correct here, although it should probably also complain about the lambda (looks a little tricky to do). See https://mypy.readthedocs.io/en/stable/common_issues.html#narrowing-and-inner-functions.

Note when not at top level you can't mutate locals from outside so it's sounder to have heuristics that avoid false positives: #15133 This is why if you put your entire code snippet inside a checked function, it will pass.

@hauntsaninja hauntsaninja closed this as not planned Won't fix, can't repro, duplicate, stale Sep 6, 2023
@md2468
Copy link
Author

md2468 commented Sep 6, 2023

Thank you very much for the explanation, that was a pretty bad oversight on my part.

@erictraut
Copy link

If you put your entire code snippet inside a checked function, it will pass.

I agree that it should pass in this case, but it apparently does not. The following code still results in a type error for the lambda. This seems like a mypy bug.

def func(x: int | None) -> None:
    if isinstance(x, int):

        def f() -> int:
            return x + 2 # No Error

        (lambda: x + 2)() # Error

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Sep 6, 2023

OP's example does work when put in a function as-is. The difference seems to be with immediately evaluated lambdas:

def func(x: int | None = 1) -> None:
    if isinstance(x, int):

        def f() -> int: 
            return x + 2

        l = lambda: x + 2
        l()

@hauntsaninja hauntsaninja changed the title Type narrowing does not work correctly in function bodies. False positive and negative with lambda narrowing Sep 6, 2023
@hauntsaninja hauntsaninja reopened this Sep 6, 2023
@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Sep 6, 2023

Reopened and retitled the issue. The false positive is Eric's case, the false negative is for the lambda in:

x: int | None = 1
if isinstance(x, int):
    def f() -> int: 
        return x + 2
    ff = lambda: x + 2
x = None

@hauntsaninja
Copy link
Collaborator

Both should be fixed by #16407

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

4 participants
@erictraut @hauntsaninja @md2468 and others