Skip to content

generic fails to narrow, something to do with using NoReturn on contravariant generic #11508

Open

Description

@DetachHead
from typing import Generic, Mapping, NoReturn, Sequence, TypeVar

T = TypeVar("T", contravariant=True)


class Foo(Generic[T]):
    ...


foo = Foo[int]()
bar = Foo[str]()

a1: Sequence[Foo[NoReturn]] = [foo]  # no error
a2: Sequence[Foo[NoReturn]] = [bar]  # no error

# error (this one seemed to be fixed around commit 32448b88370f8327c390f4e21666065c87ca95e2, then broken again)
a3: Sequence[Foo[NoReturn]] = [foo, bar]

a4: Sequence[Foo[NoReturn]] = [foo] or [bar]  # error

a5: Mapping[str, Sequence[Foo[NoReturn]]] = {  # error
    "foo": [foo],
    "bar": [],  # error goes away if you change this to [foo]
}

a6: Sequence[Sequence[Foo[NoReturn]]] = [[foo], []]  # error

https://mypy-play.net/?mypy=latest&python=3.10&gist=175deca029e02ea95af74f7c4a02856c

Activity

  1. changed the title [-]dict value type fails to narrow when it can't be inferred for all values in the dict, when bounded to a type with a contravariant generic bounded to `NoReturn`[/-] [+]dict value type fails to narrow when it can't be inferred for all its values, when bounded to a type with a contravariant generic bounded to `NoReturn`[/+] on Nov 10, 2021
  2. KotlinIsland commented on Nov 10, 2021

    @KotlinIsland
    Contributor
    from typing import Generic, NoReturn, TypeVar
    
    T_cont = TypeVar("T_cont", contravariant=True)
    
    
    class Foo(Generic[T_cont]):
        ...
    
    foo = Foo[object]()
    
    a1: list[Foo[bool]] = [foo]
    a2: list[Foo[NoReturn]] = [foo]  # error: Incompatible types in assignment (expression has type "List[Foo[int]]", variable has type "List[Foo[NoReturn]]")  [assignment]
    # main.py:12: note: "List" is invariant

    🤔

  3. erictraut commented on Nov 10, 2021

    @erictraut

    You pointed out that "the error goes away if you change this to [foo]". That looks like a bug. It should be an error in this case too. Keep in mind that no type is assignable to NoReturn, and NoReturn is not assignable to any type.

    Also, PEP 484 specifically says:

    The NoReturn type is only valid as a return annotation of functions, and considered an error if it appears in other positions.

    So mypy should generate an error for the type annotation Sequence[Foo[NoReturn]]].

  4. changed the title [-]dict value type fails to narrow when it can't be inferred for all its values, when bounded to a type with a contravariant generic bounded to `NoReturn`[/-] [+]generic fails to narrow, something to do with using `NoReturn` on contravariant generic[/+] on Nov 10, 2021
  5. KotlinIsland commented on Nov 10, 2021

    @KotlinIsland
    Contributor

    So mypy should remove a lot of functionality it has implemented around NoReturn to act like an uninhabited type?

  6. erictraut commented on Nov 10, 2021

    @erictraut

    I'm not sure what you mean by "remove a lot of functionality it has implemented around NoReturn". I'm just pointing out that the semantics of NoReturn are well documented in PEP 484. Has mypy implemented functionality that is in conflict with the specification?

  7. DetachHead commented on Nov 10, 2021

    @DetachHead
    ContributorAuthor

    mypy lets you use it like a Never type

    from typing import NoReturn
    
    foo: NoReturn
    
    bar: int = foo

    (relevant discussion from a previous issue i raised: #11291 (comment))

  8. erictraut commented on Nov 10, 2021

    @erictraut

    Yes, you're right that NoReturn does act like a Never type. This is useful for explicit validation of exhaustive completion checks, like the following:

    def assert_never(x: NoReturn) -> NoReturn:
        assert False, "Unhandled type: {}".format(type(x).__name__)
    
    def handle_my_union(x: int | str) -> None:
        if isinstance(x, int):
            pass
        elif isinstance(x, str):
            pass
        else:
            assert_never(x)

    You can find more details about this use case if you search the issue tracker for assert_never.

    I guess this is a use case that PEP 484 didn't anticipate and technically doesn't allow (since NoReturn is being used in this context as a parameter type annotation, not a return type annotation). To my knowledge, this exception to PEP 484 is support by all Python type checkers.

    This works because of the property I mentioned above. The only type that can be assigned to NoReturn is NoReturn (or Never, which is another name for the same type).

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 wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions