microsoft / pyright

Static Type Checker for Python
Other
13.25k stars 1.43k forks source link

'Never' doesn't work with Generic class #8432

Closed beaudrychase closed 3 months ago

beaudrychase commented 3 months ago

Describe the bug I'm trying to create a class that allows you to pass in whether certain types are Optional though Generics. I was able to reproduce the issue with the code snippet below. mypy says the snippet is valid, but pyright warns that there's an error.

Code or Screenshots

from typing import Generic, Never, TypeVar

O = TypeVar("O", Never, None)

class TestClass(Generic[O]):
    status: int | O

    def __init__(self, status: int | O):
        self.status = status

a = TestClass[None](1)
# This warns: Type "Never" cannot be assigned to type variable "O@TestClass"
b = TestClass[Never](1)

mypy Playground link pyright Playground link

max-muoto commented 3 months ago

As an interesting note, specifying bound=Never|None works here. Unsurprisingly, NoReturn also doesn't work in the same context as above though.

erictraut commented 3 months ago

The typing spec is not clear on whether Never should be allowed as a constraint for a value-constrained TypeVar. In fact, the entire topic of value-constrained type variables is woefully underspecified in the spec currently. It's on a long list of items that I would like to formally include in the typing spec.

I think there's a good argument to be made for disallowing both Never and Any as value constraints and upper bounds.

I'm somewhat reluctant to make a change here until we have a chance to discuss this in context of the typing spec and decide collectively on the appropriate behavior.

Given that mypy already accepts this construct, that gives me some confidence that changing pyright's behavior is less likely to cause backward compatibility issues for existing code bases.

I'm going to think about this a bit more before making a decision on how to address it.

erictraut commented 3 months ago

As an interesting note, specifying bound=Never|None works here. Unsurprisingly, NoReturn also doesn't work in the same context as above though.

That shouldn't be surprising. The type Never | None is equivalent to None. The type Never is equivalent to NoReturn.

erictraut commented 3 months ago

This is included in pyright 1.1.372.