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

can't narrow a union of type[None] #16279

Closed
ghost opened this issue Oct 17, 2023 · 3 comments · Fixed by #16315
Closed

can't narrow a union of type[None] #16279

ghost opened this issue Oct 17, 2023 · 3 comments · Fixed by #16315
Labels
bug mypy got something wrong

Comments

@ghost
Copy link

ghost commented Oct 17, 2023

Bug Report

There's something about type[None] that (understandably) confuses mypy and makes it start producing nonsense results.

While defining a function that takes arguments of type[None | A | B], mypy is able to narrow to type[None|A] (or type[None|B]) but any attempt to narrow beyond that causes no change in type. This makes it impossible to produce an exhaustive if/elif/else that can assert_never in the else case.

To Reproduce

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

Expected Behavior

I should be able to write an elif clause that can assert_type(cls, type[None]).
Actual Behavior

Currently there's no way to narrow a type Union[type[None], T] type. (That I found.)

Your Environment

https://mypy-play.net

@erictraut
Copy link

I agree this is a bug in mypy, but there were a few bugs in the sample code. Here's an updated sample that should pass type checking.

import types
import typing_extensions as t

NoneType = type(None)

def type_map(cls: type[None | int | str]) -> None:
    if issubclass(cls, int):
        t.assert_type(cls, "type[int]")
        return
    else:
        t.assert_type(cls, type[None | str])

    if issubclass(cls, str):
        t.assert_type(cls, "type[str]")
        return
    else:
        t.assert_type(cls, type[None])

    t.assert_type(cls, "type[None]")
    if issubclass(cls, str):
        t.assert_never(cls)
    else:
        t.assert_type(cls, type[None])

    if cls is None:
        t.assert_never(cls)
    else:
        t.assert_type(cls, type[None])

    if issubclass(cls, type(None)):
        t.assert_type(cls, type[None])
    else:
        t.assert_never(cls)

    if issubclass(cls, NoneType):
        t.assert_type(cls, type[None])
    else:
        t.assert_never(cls)

    if issubclass(cls, types.NoneType):
        t.assert_type(cls, type[None])
    else:
        t.assert_never(cls)

Incidentally, pyright had the same bug as mypy here. It will be fixed in the next release of pyright.

@tyralla
Copy link
Contributor

tyralla commented Oct 23, 2023

Thanks for the detailed test case. It helped me to discover three scenarios where Mypy currently fails due to different limitations:

from typing import Type, Union

def f(cls: Type[Union[None, int]]) -> None:
    if issubclass(cls, int):
        reveal_type(cls)  # note: Revealed type is "Type[builtins.int]" (right)
    else:
        reveal_type(cls)  # note: Revealed type is "Union[Type[None], Type[builtins.int]]" (wrong)
from typing import Type, Union

def f(cls: Type[Union[None, int]]) -> None:
    if issubclass(cls, type(None)):
        reveal_type(cls)  # note: Revealed type is "Union[Type[None], Type[builtins.int]]" (wrong)
    else:
        reveal_type(cls)  # note: Revealed type is "Union[Type[None], Type[builtins.int]]" (wrong)
from typing import Type, Union

NoneType_ = type(None)

def f(cls: Type[Union[None, int]]) -> None:
    if issubclass(cls, NoneType_):  # error: Parameterized generics cannot be used with class or instance checks  [misc] (wrong)
                                    # error: Argument 2 to "issubclass" has incompatible type "<typing special form>"; expected "_ClassInfo"  [arg-type] (wrong)
        reveal_type(cls)  # note: Revealed type is "Union[Type[None], Type[builtins.int]]" (wrong)
    else:
        reveal_type(cls)  # note: Revealed type is "Union[Type[None], Type[builtins.int]]" (wrong)

I provided pull request #16315. If accepted by the maintainers, these limitations should be fixed in the next Mypy release, too.

@ghost
Copy link
Author

ghost commented Oct 23, 2023

Thanks, all!

hauntsaninja pushed a commit that referenced this issue Jan 14, 2024
Fixes #16279

See my comment in the referenced issue.
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

Successfully merging a pull request may close this issue.

2 participants