Skip to content

Narrowing depends on the order of isinstance checks when using a genric type #10178

Closed
@heg-ulmuten

Description

@heg-ulmuten

Bug Report

Mypy is incoherent in the following code even though the only difference is the order of the isinstance checks. I know that Optional is the idiomatic mypy way to represent possibly missing values, but this is the smallest example I found that shows the problem.

from dataclasses import dataclass
from typing import Generic, TypeVar, Union, NoReturn


def assert_never(a: NoReturn) -> NoReturn:
    raise AssertionError("Unhandled type: {}".format(type(a).__name__))


@dataclass
class Nothing:
    pass


T = TypeVar("T")


@dataclass
class Just(Generic[T]):
    value: T


Maybe = Union[Nothing, Just[T]]


def ok(a: Maybe[int]) -> None:
    if isinstance(a, Just):
        print(a.value)
    elif isinstance(a, Nothing):
        print("nothing")
    else:
        assert_never(a)


def ko(a: Maybe[int]) -> None:
    if isinstance(a, Nothing):
        print("nothing")
    elif isinstance(a, Just):
        print(a.value)
    else:
        assert_never(a)

Expected Behavior

Either mypy finds an error in both functions or neither.

Actual Behavior

In the ko function, mypy finds this error for the last line:
Argument 1 to "assert_never" has incompatible type "Just[int]"; expected "NoReturn"

If I use a concrete type instead of a generic one in the Just class no error is reported in both functions.

Your Environment

  • Mypy version used: 0.812
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.9.2
  • Operating system and version: N/A

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