Skip to content

Type narrowing fails when narrowing Optional NewType #11995

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
bbatliner opened this issue Jan 14, 2022 · 5 comments
Closed

Type narrowing fails when narrowing Optional NewType #11995

bbatliner opened this issue Jan 14, 2022 · 5 comments
Labels
bug mypy got something wrong topic-type-narrowing Conditional type narrowing / binder

Comments

@bbatliner
Copy link

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].

@bbatliner bbatliner added the bug mypy got something wrong label Jan 14, 2022
@sobolevn
Copy link
Member

sobolevn commented Jan 15, 2022

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"

@sobolevn
Copy link
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 🤔

@sobolevn
Copy link
Member

sobolevn commented Jan 15, 2022

Funny enough, this does type-check:

from typing import NewType

UserId = NewType('UserId', int)
some_id: int = UserId(1)
other_id: float = UserId(2)

@JelleZijlstra JelleZijlstra added the topic-type-narrowing Conditional type narrowing / binder label Mar 19, 2022
@erictraut
Copy link

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.

@hauntsaninja
Copy link
Collaborator

Fixed in #13781

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-narrowing Conditional type narrowing / binder
Projects
None yet
Development

No branches or pull requests

5 participants