Apply type narrowing in case of x is not C where x is of type Union[type[C], SomethingElse], from that union to SomethingElse in case when C is made final.
If C is final, then C is the only possible value that satisfies the type type[C]. So, if we compare identity, that means we can narrow the type in either direction.
Pitch
Empty class definitions are often used as sentinel values in a union, together with something else. These sentinel values can be made @final.
Consider this code:
from typing import final, Union, Literal
@final
class Sentinel:
pass
x: Union[type[Sentinel], int]
if x is Sentinel:
pass
else:
# Should be `int`
# But is: "Union[Type[x.Sentinel], builtins.int]"
reveal_type(x)
Many libraries are doing that. A workaround that currently works is to use instantiated objects instead of types for sentinel values and narrow them by using an isinstance() check on the sentinel type, but that's far less clean, because for every sentinel we now have to deal with both the class and the instance.
Feature
Apply type narrowing in case of
x is not C
wherex
is of typeUnion[type[C], SomethingElse]
, from that union toSomethingElse
in case whenC
is madefinal
.If
C
is final, thenC
is the only possible value that satisfies the typetype[C]
. So, if we compare identity, that means we can narrow the type in either direction.Pitch
Empty class definitions are often used as sentinel values in a union, together with something else. These sentinel values can be made
@final
.Consider this code:
An example can be found in the
h11
library: https://github.com/python-hyper/h11/blob/cdccbeff44a39426b58010eb454a75015ec6f8bc/h11/_connection.py#L426Many libraries are doing that. A workaround that currently works is to use instantiated objects instead of types for sentinel values and narrow them by using an
isinstance()
check on the sentinel type, but that's far less clean, because for every sentinel we now have to deal with both the class and the instance.