# Enums

## Basic

```py
from enum import Enum
from typing import Literal

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

reveal_type(Color.RED)  # revealed: Literal[Color.RED]
reveal_type(Color.RED.name)  # revealed: Literal["RED"]
reveal_type(Color.RED.value)  # revealed: Literal[1]

# TODO: Could be `Literal[Color.RED]` to be more precise
reveal_type(Color["RED"])  # revealed: Color
reveal_type(Color(1))  # revealed: Color

reveal_type(Color.RED in Color)  # revealed: bool
```

## Enum members

### Basic

Simple enums with integer or string values:

```py
from enum import Enum
from ty_extensions import enum_members

class ColorInt(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(ColorInt))

class ColorStr(Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(ColorStr))
```

### When deriving from `IntEnum`

```py
from enum import IntEnum
from ty_extensions import enum_members

class ColorInt(IntEnum):
    RED = 1
    GREEN = 2
    BLUE = 3

# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(ColorInt))
```

### Declared non-member attributes

Attributes on the enum class that are declared are not considered members of the enum:

```py
from enum import Enum
from ty_extensions import enum_members

class Answer(Enum):
    YES = 1
    NO = 2

    non_member_1: int

    non_member_1: str = "some value"  # error: [invalid-enum-member-annotation]

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

Enum members are allowed to be marked `Final` (without a type), even if unnecessary:

```py
from enum import Enum
from typing import Final
from ty_extensions import enum_members

class Answer(Enum):
    YES: Final = 1
    NO: Final = 2

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

### Annotated enum members

The [typing spec] states that enum members should not have explicit type annotations. Type checkers
should report an error for annotated enum members because the annotation is misleading — the actual
type of an enum member is the enum class itself, not the annotated type.

```toml
[environment]
python-version = "3.11"
```

```py
from enum import Enum, IntEnum, StrEnum, member
from typing import Callable, Final

class Pet(Enum):
    CAT = 1
    DOG: int = 2  # error: [invalid-enum-member-annotation] "Type annotation on enum member `DOG` is not allowed"
    BIRD: str = "bird"  # error: [invalid-enum-member-annotation]
```

Bare `Final` annotations are allowed (they don't specify a type):

```py
class Pet2(Enum):
    CAT: Final = 1  # OK
    DOG: Final = 2  # OK
```

But `Final` with a type argument is not allowed:

```py
class Pet3(Enum):
    CAT: Final[int] = 1  # error: [invalid-enum-member-annotation]
    DOG: Final[str] = "woof"  # error: [invalid-enum-member-annotation]
```

`enum.member` used as value wrapper is the standard way to declare members explicitly:

```py
class Pet4(Enum):
    CAT = member(1)  # OK
```

Dunder and private names are not enum members, so they don't trigger the diagnostic:

```py
class Pet5(Enum):
    CAT = 1
    __private: int = 2  # OK: dunder/private names are never members
    __module__: str = "my_module"  # OK
```

Pure declarations (annotations without values) are non-members and are fine:

```py
class Pet6(Enum):
    CAT = 1
    species: str  # OK: no value, so this is a non-member declaration
```

Callable values are never enum members at runtime, so annotating them is fine:

```py
def identity(x: int) -> int:
    return x

class Pet7(Enum):
    CAT = 1
    declared_callable: Callable[[int], int] = identity  # OK: callables are never members
```

The check also works for subclasses of `Enum`:

```py
class Status(IntEnum):
    OK: int = 200  # error: [invalid-enum-member-annotation]
    NOT_FOUND = 404  # OK

class Color(StrEnum):
    RED: str = "red"  # error: [invalid-enum-member-annotation]
    GREEN = "green"  # OK
```

Special sunder names like `_value_` and `_ignore_` are not flagged:

```py
class Pet8(Enum):
    _value_: int = 0  # OK: `_value_` is a special enum name
    _ignore_: str = "TEMP"  # OK: `_ignore_` is a special enum name
    CAT = 1
```

Names listed in `_ignore_` are not members, so annotating them is fine:

```py
class Pet9(Enum):
    _ignore_ = "A B"
    A: int = 42  # OK: `A` is listed in `_ignore_`
    B: str = "hello"  # OK: `B` is listed in `_ignore_`
    C: int = 3  # error: [invalid-enum-member-annotation]
```

### Declared `_value_` annotation

If a `_value_` annotation is defined on an `Enum` class, all enum member values must be compatible
with the declared type:

```pyi
from enum import Enum

class Color(Enum):
    _value_: int
    RED = 1
    GREEN = "green"  # error: [invalid-assignment]
    BLUE = ...
    YELLOW = None  # error: [invalid-assignment]
    PURPLE = []  # error: [invalid-assignment]
```

When `_value_` is annotated, `.value` and `._value_` are inferred as the declared type:

```py
from enum import Enum
from typing import Final

class Color2(Enum):
    _value_: int
    RED = 1
    GREEN = 2

reveal_type(Color2.RED.value)  # revealed: int
reveal_type(Color2.RED._value_)  # revealed: int

class WantsInt(Enum):
    _value_: int
    OK: Final = 1
    BAD: Final = "oops"  # error: [invalid-assignment]
```

### `_value_` annotation with `__init__`

When `__init__` is defined, member values are validated by synthesizing a call to `__init__`. The
`_value_` annotation still constrains assignments to `self._value_` inside `__init__`:

```py
from enum import Enum

class Planet(Enum):
    _value_: int

    def __init__(self, value: int, mass: float, radius: float):
        self._value_ = value

    MERCURY = (1, 3.303e23, 2.4397e6)
    SATURN = "saturn"  # error: [invalid-assignment]

reveal_type(Planet.MERCURY.value)  # revealed: int
reveal_type(Planet.MERCURY._value_)  # revealed: int
```

`Final`-annotated members are also validated against `__init__`:

```py
from enum import Enum
from typing import Final

class Planet(Enum):
    def __init__(self, mass: float, radius: float):
        self.mass = mass
        self.radius = radius

    MERCURY: Final = (3.303e23, 2.4397e6)
    BAD: Final = "not a planet"  # error: [invalid-assignment]
```

### `_value_` annotation incompatible with `__init__`

When `_value_` and `__init__` disagree, the assignment inside `__init__` is flagged:

```py
from enum import Enum

class Planet(Enum):
    _value_: str

    def __init__(self, value: int, mass: float, radius: float):
        self._value_ = value  # error: [invalid-assignment]

    MERCURY = (1, 3.303e23, 2.4397e6)
    SATURN = "saturn"  # error: [invalid-assignment]

reveal_type(Planet.MERCURY.value)  # revealed: str
reveal_type(Planet.MERCURY._value_)  # revealed: str
```

### `__init__` without `_value_` annotation

When `__init__` is defined but no explicit `_value_` annotation exists, member values are validated
against the `__init__` signature. Values that are incompatible with `__init__` are flagged:

```py
from enum import Enum

class Planet2(Enum):
    def __init__(self, mass: float, radius: float):
        self.mass = mass
        self.radius = radius

    MERCURY = (3.303e23, 2.4397e6)
    VENUS = (4.869e24, 6.0518e6)
    INVALID = "not a planet"  # error: [invalid-assignment]

reveal_type(Planet2.MERCURY.value)  # revealed: Any
reveal_type(Planet2.MERCURY._value_)  # revealed: Any
```

### Inherited `_value_` annotation

A `_value_` annotation on a parent enum is inherited by subclasses. Member values are validated
against the inherited annotation, and `.value` uses the declared type:

```py
from enum import Enum

class Base(Enum):
    _value_: int

class Child(Base):
    A = 1
    B = "not an int"  # error: [invalid-assignment]

reveal_type(Child.A.value)  # revealed: int
```

This also works through multiple levels of inheritance, where `_value_` is declared on an
intermediate class:

```py
from enum import Enum

class Grandparent(Enum):
    pass

class Parent(Grandparent):
    _value_: int

class Child(Parent):
    A = 1
    B = "not an int"  # error: [invalid-assignment]

reveal_type(Child.A.value)  # revealed: int
```

### Inherited `__init__`

A custom `__init__` on a parent enum is inherited by subclasses. Member values are validated against
the inherited `__init__` signature:

```py
from enum import Enum

class Base(Enum):
    def __init__(self, a: int, b: str):
        self._value_ = a

class Child(Base):
    A = (1, "foo")
    B = "should be checked against __init__"  # error: [invalid-assignment]

reveal_type(Child.A.value)  # revealed: Any
```

This also works through multiple levels of inheritance:

```py
from enum import Enum

class Grandparent(Enum):
    def __init__(self, a: int, b: str):
        self._value_ = a

class Parent(Grandparent):
    pass

class Child(Parent):
    A = (1, "foo")
    B = "bad"  # error: [invalid-assignment]

reveal_type(Child.A.value)  # revealed: Any
```

### Non-member attributes with disallowed type

Methods, callables, descriptors (including properties), and nested classes that are defined in the
class are not treated as enum members:

```py
from enum import Enum
from ty_extensions import enum_members
from typing import Callable, Literal

def identity(x) -> int:
    return x

class Descriptor:
    def __get__(self, instance, owner):
        return 0

class Answer(Enum):
    YES = 1
    NO = 2

    def some_method(self) -> None: ...
    @staticmethod
    def some_static_method() -> None: ...
    @classmethod
    def some_class_method(cls) -> None: ...

    some_callable = lambda x: 0
    declared_callable: Callable[[int], int] = identity
    function_reference = identity

    some_descriptor = Descriptor()

    @property
    def some_property(self) -> str:
        return ""

    class NestedClass: ...

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

### `enum.property`

Enum attributes that are defined using `enum.property` are not considered members:

```toml
[environment]
python-version = "3.11"
```

```py
from enum import Enum, property as enum_property
from typing import Any
from ty_extensions import enum_members

class Answer(Enum):
    YES = 1
    NO = 2

    @enum_property
    def some_property(self) -> str:
        return "property value"

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

Enum attributes defined using `enum.property` take precedence over generated attributes.

```py
from enum import Enum, property as enum_property

class Choices(Enum):
    A = 1
    B = 2

    @enum_property
    def value(self) -> Any: ...

# TODO: This should be `Any` - overridden by `@enum_property`
reveal_type(Choices.A.value)  # revealed: Literal[1]
```

### `types.DynamicClassAttribute`

Attributes defined using `types.DynamicClassAttribute` are not considered members:

```py
from enum import Enum
from ty_extensions import enum_members
from types import DynamicClassAttribute

class Answer(Enum):
    YES = 1
    NO = 2

    @DynamicClassAttribute
    def dynamic_property(self) -> str:
        return "dynamic value"

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

### In stubs

Stubs can optionally use `...` for the actual value:

```pyi
from enum import Enum
from ty_extensions import enum_members
from typing import cast

class Color(Enum):
    RED = ...
    GREEN = cast(int, ...)
    BLUE = 3

# revealed: tuple[Literal["RED"], Literal["GREEN"], Literal["BLUE"]]
reveal_type(enum_members(Color))
```

### Aliases

Enum members can have aliases, which are not considered separate members:

```py
from enum import Enum
from ty_extensions import enum_members

class Answer(Enum):
    YES = 1
    NO = 2

    DEFINITELY = YES

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))

reveal_type(Answer.DEFINITELY)  # revealed: Literal[Answer.YES]
```

If a value is duplicated, we also treat that as an alias:

```py
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2

    red = 1
    green = 2

# revealed: tuple[Literal["RED"], Literal["GREEN"]]
reveal_type(enum_members(Color))

# revealed: Literal[Color.RED]
reveal_type(Color.red)
```

Multiple aliases to the same member are also supported. This is a regression test for
<https://github.com/astral-sh/ty/issues/1293>:

```py
from ty_extensions import enum_members

class ManyAliases(Enum):
    real_member = "real_member"
    alias1 = "real_member"
    alias2 = "real_member"
    alias3 = "real_member"

    other_member = "other_real_member"

# revealed: tuple[Literal["real_member"], Literal["other_member"]]
reveal_type(enum_members(ManyAliases))

reveal_type(ManyAliases.real_member)  # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.alias1)  # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.alias2)  # revealed: Literal[ManyAliases.real_member]
reveal_type(ManyAliases.alias3)  # revealed: Literal[ManyAliases.real_member]

reveal_type(ManyAliases.real_member.value)  # revealed: Literal["real_member"]
reveal_type(ManyAliases.real_member.name)  # revealed: Literal["real_member"]

reveal_type(ManyAliases.alias1.value)  # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias1.name)  # revealed: Literal["real_member"]

reveal_type(ManyAliases.alias2.value)  # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias2.name)  # revealed: Literal["real_member"]

reveal_type(ManyAliases.alias3.value)  # revealed: Literal["real_member"]
reveal_type(ManyAliases.alias3.name)  # revealed: Literal["real_member"]
```

### Using `auto()`

```toml
[environment]
python-version = "3.11"
```

```py
from enum import Enum, auto
from ty_extensions import enum_members

class Answer(Enum):
    YES = auto()
    NO = auto()

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))

reveal_type(Answer.YES.value)  # revealed: Literal[1]
reveal_type(Answer.NO.value)  # revealed: Literal[2]

class SingleMember(Enum):
    SINGLE = auto()

reveal_type(SingleMember.SINGLE.value)  # revealed: Literal[1]
```

Usages of `auto()` can be combined with manual value assignments:

```py
class Mixed(Enum):
    MANUAL_1 = -1
    AUTO_1 = auto()
    MANUAL_2 = -2
    AUTO_2 = auto()

reveal_type(Mixed.MANUAL_1.value)  # revealed: Literal[-1]
reveal_type(Mixed.AUTO_1.value)  # revealed: Literal[1]
reveal_type(Mixed.MANUAL_2.value)  # revealed: Literal[-2]
reveal_type(Mixed.AUTO_2.value)  # revealed: Literal[2]
```

When using `auto()` with `StrEnum`, the value is the lowercase name of the member:

```py
from enum import StrEnum, auto

class Answer(StrEnum):
    YES = auto()
    NO = auto()

reveal_type(Answer.YES.value)  # revealed: Literal["yes"]
reveal_type(Answer.NO.value)  # revealed: Literal["no"]

class SingleMember(StrEnum):
    SINGLE = auto()

reveal_type(SingleMember.SINGLE.value)  # revealed: Literal["single"]
```

Using `auto()` with `IntEnum` also works as expected. `IntEnum` declares `_value_: int` in typeshed,
so `.value` is typed as `int` rather than a precise literal:

```py
from enum import IntEnum, auto

class Answer(IntEnum):
    YES = auto()
    NO = auto()

reveal_type(Answer.YES.value)  # revealed: int
reveal_type(Answer.NO.value)  # revealed: int
```

As does using `auto()` for other enums that use `int` as a mixin:

```py
from enum import Enum, auto

class Answer(int, Enum):
    YES = auto()
    NO = auto()

reveal_type(Answer.YES.value)  # revealed: Literal[1]
reveal_type(Answer.NO.value)  # revealed: Literal[2]
```

It's [hard to predict](https://github.com/astral-sh/ruff/pull/20541#discussion_r2381878613) what the
effect of using `auto()` will be for an arbitrary non-integer mixin, so for anything that isn't a
`StrEnum` and has a non-`int` mixin, we simply fallback to typeshed's annotation of `Any` for the
`value` property:

```python
from enum import Enum, auto

class A(str, Enum):
    X = auto()
    Y = auto()

reveal_type(A.X.value)  # revealed: Any

class B(bytes, Enum):
    X = auto()
    Y = auto()

reveal_type(B.X.value)  # revealed: Any

class C(tuple, Enum):
    X = auto()
    Y = auto()

reveal_type(C.X.value)  # revealed: Any

class D(float, Enum):
    X = auto()
    Y = auto()

reveal_type(D.X.value)  # revealed: Any
```

Combining aliases with `auto()`:

```py
from enum import Enum, auto

class Answer(Enum):
    YES = auto()
    NO = auto()

    DEFINITELY = YES

# TODO: This should ideally be `tuple[Literal["YES"], Literal["NO"]]`
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["DEFINITELY"]]
reveal_type(enum_members(Answer))
```

`auto()` values are computed at runtime by the enum metaclass, so we skip validation against both
`_value_` annotations and custom `__init__` signatures:

```py
from enum import Enum, auto

class WithValue(Enum):
    _value_: int
    A = auto()
    B = auto()

reveal_type(WithValue.A.value)  # revealed: int

class WithInit(Enum):
    def __init__(self, mass: float, radius: float):
        self.mass = mass
        self.radius = radius

    MERCURY = (3.303e23, 2.4397e6)
    AUTO = auto()

reveal_type(WithInit.MERCURY.value)  # revealed: Any
```

### `member` and `nonmember`

```toml
[environment]
python-version = "3.11"
```

```py
from enum import Enum, auto, member, nonmember
from ty_extensions import enum_members

class Answer(Enum):
    YES = member(1)
    NO = member(2)
    OTHER = nonmember(17)

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))

# `nonmember` attributes are unwrapped to the inner value type when accessed.
# revealed: int
reveal_type(Answer.OTHER)
```

`member` can also be used as a decorator:

```py
from enum import Enum, member
from ty_extensions import enum_members

class Answer(Enum):
    yes = member(1)
    no = member(2)

    @member
    def maybe(self) -> None:
        return

# revealed: tuple[Literal["yes"], Literal["no"], Literal["maybe"]]
reveal_type(enum_members(Answer))
```

### Dunder and class-private names

An attribute with a name beginning with a double underscore is treated as a non-member. This
includes both [class-private names] (not ending in `__`) and dunder names (ending in `__`).
CPython's enum metaclass excludes all such names from membership:

```py
from enum import Enum, IntEnum
from ty_extensions import enum_members

class Answer(Enum):
    YES = 1
    NO = 2

    __private_member = 3
    __maybe__ = 4

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

Setting `__module__` (a common pattern to control `repr()` and `pickle` behavior) does not make it
an enum member, even when the value type differs from the enum's value type:

```py
class ExitCode(IntEnum):
    OK = 0
    ERROR = 1

    __module__ = "my_package"  # no error, not a member

# revealed: tuple[Literal["OK"], Literal["ERROR"]]
reveal_type(enum_members(ExitCode))
```

### Ignored names

An enum class can define a class symbol named `_ignore_`. This can be a string containing a
whitespace-delimited list of names:

```py
from enum import Enum
from ty_extensions import enum_members

class Answer(Enum):
    _ignore_ = "IGNORED _other_ignored       also_ignored"

    YES = 1
    NO = 2

    IGNORED = 3
    _other_ignored = "test"
    also_ignored = "test2"

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

`_ignore_` can also be a list of names:

```py
class Answer2(Enum):
    _ignore_ = ["MAYBE", "_other"]

    YES = 1
    NO = 2

    MAYBE = 3
    _other = "test"

# TODO: This should be `tuple[Literal["YES"], Literal["NO"]]`
# revealed: tuple[Literal["YES"], Literal["NO"], Literal["MAYBE"], Literal["_other"]]
reveal_type(enum_members(Answer2))
```

### Special names

Make sure that special names like `name` and `value` can be used for enum members (without
conflicting with `Enum.name` and `Enum.value`):

```py
from enum import Enum
from ty_extensions import enum_members

class Answer(Enum):
    name = 1
    value = 2

# revealed: tuple[Literal["name"], Literal["value"]]
reveal_type(enum_members(Answer))

reveal_type(Answer.name)  # revealed: Literal[Answer.name]
reveal_type(Answer.value)  # revealed: Literal[Answer.value]
```

## Iterating over enum members

```py
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

for color in Color:
    reveal_type(color)  # revealed: Color

# TODO: Should be `list[Color]`
reveal_type(list(Color))  # revealed: list[Unknown]
```

## Methods / non-member attributes

Methods and non-member attributes defined in the enum class can be accessed on enum members:

```py
from enum import Enum

class Answer(Enum):
    YES = 1
    NO = 2

    def is_yes(self) -> bool:
        return self == Answer.YES

    constant: int

reveal_type(Answer.YES.is_yes())  # revealed: bool
reveal_type(Answer.YES.constant)  # revealed: int

class MyEnum(Enum):
    def some_method(self) -> None:
        pass

class MyAnswer(MyEnum):
    YES = 1
    NO = 2

reveal_type(MyAnswer.YES.some_method())  # revealed: None
```

## Accessing enum members from enum members / instances

```py
from enum import Enum

class Answer(Enum):
    YES = 1
    NO = 2

reveal_type(Answer.YES.NO)  # revealed: Literal[Answer.NO]

def _(answer: Answer) -> None:
    reveal_type(answer.YES)  # revealed: Literal[Answer.YES]
    reveal_type(answer.NO)  # revealed: Literal[Answer.NO]
```

## Accessing enum members from `type[…]`

```py
from enum import Enum

class Answer(Enum):
    YES = 1
    NO = 2

def _(answer: type[Answer]) -> None:
    reveal_type(answer.YES)  # revealed: Literal[Answer.YES]
    reveal_type(answer.NO)  # revealed: Literal[Answer.NO]
```

## Calling enum variants

```py
from enum import Enum
from typing import Callable
import sys

class Printer(Enum):
    STDOUT = 1
    STDERR = 2

    def __call__(self, msg: str) -> None:
        if self == Printer.STDOUT:
            print(msg)
        elif self == Printer.STDERR:
            print(msg, file=sys.stderr)

Printer.STDOUT("Hello, world!")
Printer.STDERR("An error occurred!")

callable: Callable[[str], None] = Printer.STDOUT
callable("Hello again!")
callable = Printer.STDERR
callable("Another error!")
```

## Special attributes on enum members

### `name` and `_name_`

```py
from enum import Enum
from typing import Literal

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

reveal_type(Color.RED._name_)  # revealed: Literal["RED"]

def _(red_or_blue: Literal[Color.RED, Color.BLUE]):
    reveal_type(red_or_blue.name)  # revealed: Literal["RED", "BLUE"]

def _(color: Color):
    reveal_type(color.name)  # revealed: Literal["RED", "GREEN", "BLUE"]
```

### `value` and `_value_`

```toml
[environment]
python-version = "3.11"
```

```py
from enum import Enum, StrEnum
from typing import Literal

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

reveal_type(Color.RED.value)  # revealed: Literal[1]
reveal_type(Color.RED._value_)  # revealed: Literal[1]

reveal_type(Color.GREEN.value)  # revealed: Literal[2]
reveal_type(Color.GREEN._value_)  # revealed: Literal[2]

def _(color: Color):
    reveal_type(color.value)  # revealed: Literal[1, 2, 3]

class Answer(StrEnum):
    YES = "yes"
    NO = "no"

reveal_type(Answer.YES.value)  # revealed: Literal["yes"]
reveal_type(Answer.YES._value_)  # revealed: Literal["yes"]

reveal_type(Answer.NO.value)  # revealed: Literal["no"]
reveal_type(Answer.NO._value_)  # revealed: Literal["no"]

def _(answer: Answer):
    reveal_type(answer.value)  # revealed: Literal["yes", "no"]
```

## Properties of enum types

### Implicitly final

An enum with one or more defined members cannot be subclassed. They are implicitly "final".

```py
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# error: [subclass-of-final-class] "Class `ExtendedColor` cannot inherit from final class `Color`"
class ExtendedColor(Color):
    YELLOW = 4

def f(color: Color):
    if isinstance(color, int):
        reveal_type(color)  # revealed: Never
```

An `Enum` subclass without any defined members can be subclassed:

```py
from enum import Enum
from ty_extensions import enum_members

class MyEnum(Enum):
    def some_method(self) -> None:
        pass

class Answer(MyEnum):
    YES = 1
    NO = 2

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

### Meta-type

```py
from enum import Enum

class Answer(Enum):
    YES = 1
    NO = 2

reveal_type(type(Answer.YES))  # revealed: <class 'Answer'>

class NoMembers(Enum): ...

def _(answer: Answer, no_members: NoMembers):
    reveal_type(type(answer))  # revealed: <class 'Answer'>
    reveal_type(type(no_members))  # revealed: type[NoMembers]
```

### Cyclic references

```py
from enum import Enum
from typing import Literal
from ty_extensions import enum_members

class Answer(Enum):
    YES = 1
    NO = 2

    @classmethod
    def yes(cls) -> "Literal[Answer.YES]":
        return Answer.YES

# revealed: tuple[Literal["YES"], Literal["NO"]]
reveal_type(enum_members(Answer))
```

## Custom enum types

Enum classes can also be defined using a subclass of `enum.Enum` or any class that uses
`enum.EnumType` (or a subclass thereof) as a metaclass. `enum.EnumType` was called `enum.EnumMeta`
prior to Python 3.11.

### Subclasses of `Enum`

```py
from enum import Enum, EnumMeta

class CustomEnumSubclass(Enum):
    def custom_method(self) -> int:
        return 0

class EnumWithCustomEnumSubclass(CustomEnumSubclass):
    NO = 0
    YES = 1

reveal_type(EnumWithCustomEnumSubclass.NO)  # revealed: Literal[EnumWithCustomEnumSubclass.NO]
reveal_type(EnumWithCustomEnumSubclass.NO.custom_method())  # revealed: int
```

### Enums with (subclasses of) `EnumMeta` as metaclass

```toml
[environment]
python-version = "3.9"
```

```py
from enum import Enum, EnumMeta

class EnumWithEnumMetaMetaclass(metaclass=EnumMeta):
    # Using `EnumMeta` as a metaclass without inheriting `Enum` requires an `__init__`
    # method that will accept member values (TODO we could catch the lack of this):
    def __init__(self, val): ...
    NO = 0
    YES = 1

reveal_type(EnumWithEnumMetaMetaclass.NO)  # revealed: Literal[EnumWithEnumMetaMetaclass.NO]

class SubclassOfEnumMeta(EnumMeta): ...

class EnumWithSubclassOfEnumMetaMetaclass(metaclass=SubclassOfEnumMeta):
    def __init__(self, val): ...
    NO = 0
    YES = 1

reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO)  # revealed: Literal[EnumWithSubclassOfEnumMetaMetaclass.NO]

# Attributes `.value` and `.name` can *not* be accessed on members of these enums:

# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.value
# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.name

# But the internal underscore attributes are available:

reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._value_)  # revealed: Any
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._name_)  # revealed: Literal["NO"]

def _(x: EnumWithSubclassOfEnumMetaMetaclass):
    # error: [unresolved-attribute]
    x.value
    # error: [unresolved-attribute]
    x.name
    reveal_type(x._value_)  # revealed: Any
    reveal_type(x._name_)  # revealed: Literal["NO", "YES"]
```

### Enums with (subclasses of) `EnumType` as metaclass

In Python 3.11, the meta-type was renamed to `EnumType`.

```toml
[environment]
python-version = "3.11"
```

```py
from enum import Enum, EnumType

class EnumWithEnumMetaMetaclass(metaclass=EnumType):
    def __init__(self, val): ...
    NO = 0
    YES = 1

reveal_type(EnumWithEnumMetaMetaclass.NO)  # revealed: Literal[EnumWithEnumMetaMetaclass.NO]

class SubclassOfEnumMeta(EnumType): ...

class EnumWithSubclassOfEnumMetaMetaclass(metaclass=SubclassOfEnumMeta):
    def __init__(self, val): ...
    NO = 0
    YES = 1

reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO)  # revealed: Literal[EnumWithSubclassOfEnumMetaMetaclass.NO]

# Attributes `.value` and `.name` can *not* be accessed on members of these enums:

# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.value
# error: [unresolved-attribute]
EnumWithSubclassOfEnumMetaMetaclass.NO.name

# But the internal underscore attributes are available:

reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._value_)  # revealed: Any
reveal_type(EnumWithSubclassOfEnumMetaMetaclass.NO._name_)  # revealed: Literal["NO"]

def _(x: EnumWithSubclassOfEnumMetaMetaclass):
    # error: [unresolved-attribute]
    x.value
    # error: [unresolved-attribute]
    x.name
    reveal_type(x._value_)  # revealed: Any
    reveal_type(x._name_)  # revealed: Literal["NO", "YES"]
```

## Function syntax

To do: <https://typing.python.org/en/latest/spec/enums.html#enum-definition>

## Exhaustiveness checking

## `if` statements

```py
from enum import Enum
from typing_extensions import assert_never

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

def color_name(color: Color) -> str:
    if color is Color.RED:
        return "Red"
    elif color is Color.GREEN:
        return "Green"
    elif color is Color.BLUE:
        return "Blue"
    else:
        assert_never(color)

# No `invalid-return-type` error here because the implicit `else` branch is detected as unreachable:
def color_name_without_assertion(color: Color) -> str:
    if color is Color.RED:
        return "Red"
    elif color is Color.GREEN:
        return "Green"
    elif color is Color.BLUE:
        return "Blue"

def color_name_misses_one_variant(color: Color) -> str:
    if color is Color.RED:
        return "Red"
    elif color is Color.GREEN:
        return "Green"
    else:
        assert_never(color)  # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"

class Singleton(Enum):
    VALUE = 1

def singleton_check(value: Singleton) -> str:
    if value is Singleton.VALUE:
        return "Singleton value"
    else:
        assert_never(value)
```

## `match` statements

```toml
[environment]
python-version = "3.10"
```

```py
from enum import Enum
from typing_extensions import assert_never

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

def color_name(color: Color) -> str:
    match color:
        case Color.RED:
            return "Red"
        case Color.GREEN:
            return "Green"
        case Color.BLUE:
            return "Blue"
        case _:
            assert_never(color)

def color_name_without_assertion(color: Color) -> str:
    match color:
        case Color.RED:
            return "Red"
        case Color.GREEN:
            return "Green"
        case Color.BLUE:
            return "Blue"

def color_name_misses_one_variant(color: Color) -> str:
    match color:
        case Color.RED:
            return "Red"
        case Color.GREEN:
            return "Green"
        case _:
            assert_never(color)  # error: [type-assertion-failure] "Type `Literal[Color.BLUE]` is not equivalent to `Never`"

class Singleton(Enum):
    VALUE = 1

def singleton_check(value: Singleton) -> str:
    match value:
        case Singleton.VALUE:
            return "Singleton value"
        case _:
            assert_never(value)
```

## `__eq__` and `__ne__`

### No `__eq__` or `__ne__` overrides

```py
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2

reveal_type(Color.RED == Color.RED)  # revealed: Literal[True]
reveal_type(Color.RED != Color.RED)  # revealed: Literal[False]
```

### Overridden `__eq__`

```py
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2

    def __eq__(self, other: object) -> bool:
        return False

reveal_type(Color.RED == Color.RED)  # revealed: bool
```

### Overridden `__ne__`

```py
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2

    def __ne__(self, other: object) -> bool:
        return False

reveal_type(Color.RED != Color.RED)  # revealed: bool
```

## Generic enums are invalid

Enum classes cannot be generic. Python does not support generic enums, and attempting to create one
will result in a `TypeError` at runtime.

### PEP 695 syntax

Using PEP 695 type parameters on an enum is invalid:

```toml
[environment]
python-version = "3.12"
```

```py
from enum import Enum

# error: [invalid-generic-enum] "Enum class `E` cannot be generic"
class E[T](Enum):
    A = 1
    B = 2
```

### Legacy `Generic` base class

Inheriting from both `Enum` and `Generic[T]` is also invalid:

```py
from enum import Enum
from typing import Generic, TypeVar

T = TypeVar("T")

# error: [invalid-generic-enum] "Enum class `F` cannot be generic"
class F(Enum, Generic[T]):
    A = 1
    B = 2
```

### Swapped order (`Generic` first)

The order of bases doesn't matter; it's still invalid:

```py
from enum import Enum
from typing import Generic, TypeVar

T = TypeVar("T")

# error: [invalid-generic-enum] "Enum class `G` cannot be generic"
class G(Generic[T], Enum):
    A = 1
    B = 2
```

### Enum subclasses

Subclasses of enum base classes also cannot be generic:

```toml
[environment]
python-version = "3.12"
```

```py
from enum import Enum, IntEnum
from typing import Generic, TypeVar

T = TypeVar("T")

# error: [invalid-generic-enum] "Enum class `MyIntEnum` cannot be generic"
class MyIntEnum[T](IntEnum):
    A = 1

# error: [invalid-generic-enum] "Enum class `MyFlagEnum` cannot be generic"
class MyFlagEnum(IntEnum, Generic[T]):
    A = 1
```

### Custom enum base class

Even with custom enum subclasses that don't have members, they cannot be made generic:

```toml
[environment]
python-version = "3.12"
```

```py
from enum import Enum
from typing import Generic, TypeVar

T = TypeVar("T")

class MyEnumBase(Enum):
    def some_method(self) -> None: ...

# error: [invalid-generic-enum] "Enum class `MyEnum` cannot be generic"
class MyEnum[T](MyEnumBase):
    A = 1
```

## References

- Typing spec: <https://typing.python.org/en/latest/spec/enums.html>
- Documentation: <https://docs.python.org/3/library/enum.html>

[class-private names]: https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers
[typing spec]: https://typing.python.org/en/latest/spec/enums.html#enum-members
