Amend PEP 586 to make `enum` values subtypes of `Literal`

I don’t think this should be allowed. We should instead say clearly that Literal[X] means that the object is exactly that value, i.e. Literal[1] is only the instance of int, not a subclass.

Allowing subclasses of ints to inhabit Literal[1] would break a lot of assumptions that type checkers use and that make Literals convenient to use. For example, type checkers may use x == 1 to narrow a type to Literal[1]; or if x to narrow Literal[0] out of a type; or they may support mathematical operations on literals. All of those become unsafe if the object may be a subclass of int.

Here’s a sample of unsound behaviors in pyright due to this feature:

from enum import IntEnum
from typing import Literal

class X(IntEnum):
    a = 1

    def __add__(self, value: int, /) -> int:
        return self.value + value + 42

    def __eq__(self, other: object):
        return False

    def __bool__(self) -> bool:
        return False

def f(x: Literal[1]):
    print(reveal_type(x + 1))  # 44 at runtime, 2 according to pyright

def g(x: Literal[1, 2]):
    if x == 1:  # false for X.a
        reveal_type(x)  # Literal[1]
    else:
        reveal_type(x)  # Literal[2]

def h(x: Literal[0, 1]):
    if x:  # false for X.a
        reveal_type(x)  # Literal[1]
    else:
        reveal_type(x)  # Literal[0]

f(X.a)
g(X.a)
h(X.a)
13 Likes