# How Pyrefly Handles Enum Members and Their Literal Types

> Discover how Pyrefly models enum members as singleton literal types, converting them to TSP class types with the LITERAL flag for precise type narrowing and attribute resolution.

- Repository: [Meta/pyrefly](https://github.com/facebook/pyrefly)
- Tags: internals
- Published: 2026-05-21

---

**Pyrefly models Python `Enum` members as distinct singleton literal types using the `Lit::Enum` variant, converting them to TSP class types with the `LITERAL` flag to enable precise type narrowing and attribute resolution.**

Pyrefly, Facebook’s open-source Python type checker, provides sophisticated handling for `enum.Enum` members that goes beyond basic type inference. By representing each enum member as a unique literal type with full access to its underlying class metadata and value type, Pyrefly enables accurate singleton type checking, read-only attribute semantics, and intelligent IDE quick-fixes.

## Core Literal Representation

At the heart of Pyrefly’s enum handling is the `LitEnum` struct, which stores both the enum class reference and the member’s concrete value type.

In [`crates/pyrefly_types/src/literal.rs`](https://github.com/facebook/pyrefly/blob/main/crates/pyrefly_types/src/literal.rs), the `Lit` enum defines how literal values are represented internally:

```rust
pub enum Lit {
    // ... other variants ...
    Enum(Box<LitEnum>),   // ← enum member literal
    // ...
}

pub struct LitEnum {
    pub class: Class,   // the enum class object
    pub ty: Type,       // the value type of the member (e.g. `int`, `str`)
}

```

This structure allows Pyrefly to preserve the relationship between the enum member (like `Color.RED`) and its underlying value type (such as `int`), which is crucial for resolving the `.value` attribute correctly.

## Conversion to TSP Class Types

When Pyrefly converts internal literal representations to its Type-Signal-Protocol (TSP) system, enum members receive special treatment to mark them as singleton types.

In [`pyrefly/lib/tsp/type_conversion.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/tsp/type_conversion.rs) (lines 509‑523), the conversion logic wraps enum literals with the `LITERAL` flag:

```rust
Lit::Enum(e) => {
    // The enum class is used as the declaration source.
    let cls = e.class.class_object();
    let declaration = make_class_declaration(cls);
    TspType::Class(TspClassType {
        declaration: Declaration::Regular(declaration),
        flags: TypeFlags::LITERAL,           // ↳ marks a literal enum member
        id: next_id(),
        kind: TypeKind::Class,
        literal_value: None,
        // ...
    })
}

```

The resulting `TspClassType` represents **the specific enum member itself**, not just the generic enum class. This distinction enables Pyrefly to treat `Literal[Color.RED]` as a distinct type that is incompatible with `Literal[Color.BLUE]`, supporting exhaustive pattern matching and precise type narrowing.

## Attribute Resolution for Enum Members

Enum members expose two critical attributes that Pyrefly resolves through specialized lookup logic in [`pyrefly/lib/alt/class/enums.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/class/enums.rs).

### The `value` and `_value_` Attributes

When type-checking access to `.value` (or its internal alias `._value_`), Pyrefly uses `enum_value_lookup_on_member` for instances and `enum_value_lookup_on_class` for class-level access:

- **Instance access** (`color.value`): Returns the specific member’s value type stored in `LitEnum.ty`
- **Class access** (`Color.RED.value`): Computes the union of all member value types for the enum

This logic handles edge cases including mix-in classes, custom `__new__` methods, and Django-specific enum patterns.

### Read-Only Semantics

Pyrefly marks the `value` attribute with `ReadOnlyReason::EnumMemberValue`, making it read-only in all contexts except `self.__init__`, where a writable version is permitted during object construction.

## Developer Experience Features

Beyond type checking, Pyrefly provides IDE integrations that catch common enum usage errors.

### Quick-Fixes for Misspelled Members

When developers use string literals that match enum member names, Pyrefly suggests automatic replacements. The implementation in [`pyrefly/lib/state/lsp/quick_fixes/enum_member.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/state/lsp/quick_fixes/enum_member.rs) defines the `replace_with_enum_member_code_action` function:

```rust
pub(crate) fn replace_with_enum_member_code_action(
    module_info: &ModuleInfo,
    ast: &ModModule,
    error: &Error,
) -> Option<(String, Module, TextRange, String)> {
    let replacement = enum_member_replacement(error)?;
    let literal_range = enclosing_string_literal_range(ast, error.range())?;
    Some((
        format!("Replace with `{replacement}`"),
        module_info.dupe(),
        literal_range,
        replacement.to_owned(),
    ))
}

```

This allows editors to offer one-click fixes converting `"RED"` to `Color.RED` when the type system detects a mismatch.

### Enum Member Decorators

Functions decorated with `@enum_member` receive special treatment in [`pyrefly/lib/alt/function.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/function.rs) (lines 769‑836). The decorator maps to `SpecialDecorator::EnumMember`, which constrains the function’s first argument to accept only specific enum members rather than the general enum class.

## Practical Code Examples

The following patterns demonstrate how Pyrefly applies these internal mechanisms to real Python code:

```python
from enum import Enum
from typing import Literal

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

# 1️⃣ Literal enum member – its type is a distinct singleton.

x: Literal[Color.RED] = Color.RED          # ✅ accepted

# 2️⃣ Accessing the underlying value – Pyrefly infers `int`.

v: int = Color.RED.value                  # ✅ inferred as `int`

# 3️⃣ Using a misguided string literal – Pyrefly suggests a fix.

c: Literal["RED"] = "RED"                # ❌ error

# quick-fix → Replace with `Color.RED`

# 4️⃣ Enum-member decorator constrains the first argument.

@enum_member
def handler(member: Color.RED) -> None:
    reveal_type(member)  # Revealed type is Literal[Color.RED]

```

## Summary

- **Literal Representation**: Pyrefly stores enum members as `Lit::Enum` containing the class object and value type in [`crates/pyrefly_types/src/literal.rs`](https://github.com/facebook/pyrefly/blob/main/crates/pyrefly_types/src/literal.rs)
- **Type Conversion**: Enum literals convert to `TspClassType` with `TypeFlags::LITERAL` in [`pyrefly/lib/tsp/type_conversion.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/tsp/type_conversion.rs), creating singleton types
- **Attribute Lookup**: The `value` attribute resolves via `enum_value_lookup_on_member` and `enum_value_lookup_on_class` in [`pyrefly/lib/alt/class/enums.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/class/enums.rs), respecting read-only semantics
- **IDE Support**: Misspelled enum strings trigger quick-fixes via `replace_with_enum_member_code_action` in [`pyrefly/lib/state/lsp/quick_fixes/enum_member.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/state/lsp/quick_fixes/enum_member.rs)
- **Decorator Support**: The `@enum_member` decorator restricts function arguments to specific enum literals as implemented in [`pyrefly/lib/alt/function.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/function.rs)

## Frequently Asked Questions

### How does Pyrefly represent `Literal[MyEnum.A]` internally?

Pyrefly represents `Literal[MyEnum.A]` as `Lit::Enum(Box::new(LitEnum { class: <MyEnum>, ty: <value_type> }))` in its internal type system. This is then converted to a `TspClassType` with the `LITERAL` flag set, creating a distinct singleton type that can be compared with other literals and used in unions according to the implementation in [`pyrefly/lib/tsp/type_conversion.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/tsp/type_conversion.rs).

### Why does Pyrefly treat enum `.value` attributes as read-only?

Pyrefly marks enum `.value` attributes with `ReadOnlyReason::EnumMemberValue` to prevent accidental mutation of enum member values, which would violate the Enum contract in Python. The type checker only permits writing to `.value` within `self.__init__` during object construction, as implemented in the attribute lookup logic in [`pyrefly/lib/alt/class/enums.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/class/enums.rs).

### Can Pyrefly suggest fixes when I use string literals instead of enum members?

Yes. When Pyrefly detects a string literal that matches an enum member name but is used in a context expecting the enum type, it generates a `ReplaceWithEnumMember` diagnostic with a quick-fix action. The LSP integration in [`pyrefly/lib/state/lsp/quick_fixes/enum_member.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/state/lsp/quick_fixes/enum_member.rs) provides the exact replacement text (such as `Color.RED`) that editors can apply automatically.

### What is the `@enum_member` decorator used for in Pyrefly?

The `@enum_member` decorator indicates that a function’s first parameter must be a specific enum member rather than the enum class itself. According to [`pyrefly/lib/alt/function.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/function.rs), this maps to `SpecialDecorator::EnumMember` and causes the type checker to validate that the argument matches a literal enum member type, enabling APIs that operate on specific enum variants rather than the general enum class.