Skip to content

Mypy performs wrong narrowing with == None and in (None,) conditions #15759

Closed
@intgr

Description

@intgr

Bug Report

This is a pretty ridiculous bug, I'm surprised it has not shown up until now. 😆 It goes back all the way to mypy 0.800. I will open a PR with a fix shortly.

Thankfully this isn't a problem in practice, since None comparisons are usually done with is None instead of == None.

def func(val: str | None) -> None:
    if val == None:
        reveal_type(val)  # ❌ Revealed type is "builtins.str"
    if val in (None,):
        reveal_type(val)  # ❌ Revealed type is "builtins.str"

This is because type narrowing logic internally uses is_optional() to check whether a value can be None. is_optional() only considers UnionType, whereas NoneType value may also have value None.

To Reproduce

Example from above: https://mypy-play.net/?mypy=1.4.1&python=3.11&gist=fc584a3bb43ece3480b70b0b6b7a1499

Expected Behavior

== None condition should be narrowed to remove Optional wrapping.

Actual Behavior

After applying my fix:

def func(val: str | None) -> None:
    if val == None:
        reveal_type(val)  # ✅ Revealed type is "Union[builtins.str, None]"
    if val in (None,):
        reveal_type(val)  # ✅ Revealed type is "Union[builtins.str, None]"

The narrowing logic here could be further improved, but that's out of scope for the bugfix pull request

Your Environment

  • Mypy version used: git master, 1.4.1, 0.800
  • Mypy command-line flags: -
  • Mypy configuration options from mypy.ini (and other config files): -
  • Python version used: 3.11

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-type-narrowingConditional type narrowing / binder

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions