Skip to content

Type narrowing fails when narrowing Optional NewType #11995

Closed

Description

@bbatliner

Bug Report

from typing import Union, NewType

Int = NewType("Int", int)
Float = NewType("Float", float)

def raw_types(arg: Union[int, float, None]) -> None:
    if isinstance(arg, int):
        reveal_type(arg)  # Revealed type is "builtins.int"
    elif isinstance(arg, float):
        reveal_type(arg)  # Revealed type is "builtins.float"
    else:
        reveal_type(arg)  # Revealed type is "None"

def new_types(arg: Union[Int, Float, None]) -> None:
    if isinstance(arg, int):
        reveal_type(arg)  # Revealed type is "__main__.Int"
    elif isinstance(arg, float):
        reveal_type(arg)  # Revealed type is "Union[__main__.Int, __main__.Float]" (NARROWING FAILED)
    else:
        reveal_type(arg)  # Revealed type is "None"

def new_types_not_optional(arg: Union[Int, Float]) -> None:
    if isinstance(arg, int):
        reveal_type(arg)  # Revealed type is "__main__.Int"
    elif isinstance(arg, float):
        reveal_type(arg)  # Revealed type is "__main__.Float"
    else:
        reveal_type(arg)

The bug is fairly straightforward. When narrowing a Union of NewType types with isinstance, the narrowing fails if one of the possible types of the variable is None.

To Reproduce

https://mypy-play.net/?mypy=0.931&python=3.10&gist=557b54a098f8ed366fa62ff380ec12c8

Expected Behavior

All revealed types after isinstance(arg, float) would be builtins.float or __main__.Float.

Actual Behavior

The revealed type in the buggy case is still Union[__main__.Int, __main__.Float].

Activity

  1. sobolevn commented on Jan 15, 2022

    @sobolevn
    Member

    It is not just about Optional. Any extra type reveals narrowing issue:

    def new_types(arg: Union[Int, Float, str]) -> None:
        if isinstance(arg, int):
            reveal_type(arg)  # Revealed type is "__main__.Int"
        elif isinstance(arg, float):
            reveal_type(arg)  # Revealed type is "Union[__main__.Int, __main__.Float]" (NARROWING FAILED)
        else:
            reveal_type(arg)  # Revealed type is "builtins.str"
  2. sobolevn commented on Jan 15, 2022

    @sobolevn
    Member

    This happens because Int is treated as a subtype of float here:

    elif is_subtype(self.s, t):

    I don't think that this is correct 🤔

  3. sobolevn commented on Jan 15, 2022

    @sobolevn
    Member

    Funny enough, this does type-check:

    from typing import NewType
    
    UserId = NewType('UserId', int)
    some_id: int = UserId(1)
    other_id: float = UserId(2)
  4. erictraut commented on Aug 12, 2023

    @erictraut

    This bug appears to have been fixed. The latest version of mypy (1.5) does not exhibit the incorrect behavior indicated in the bug report.

  5. hauntsaninja commented on Aug 13, 2023

    @hauntsaninja
    Collaborator

    Fixed in #13781

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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