Skip to content

Unions of Literals are not accepted as TypedDict Keys #16818

Open
@CarliJoy

Description

@CarliJoy

Bug Report

If Literals are given as Unions they aren't accepted as TypedDict Keys

To Reproduce

from typing import TypedDict, reveal_type, Final, Literal, TypeAlias, get_args
from datetime import datetime


class RangeInfo(TypedDict):
    begin: datetime
    end: datetime
    created_start: datetime
    created_end: datetime
    # …


TimeRange: TypeAlias = Literal["begin", "end"]
CreatedRange: TypeAlias = Literal["created_start", "created_end"]
RANGES: Final[tuple[tuple[TimeRange | CreatedRange, ...],... ]] = (
    get_args(TimeRange),
    get_args(CreatedRange),
    # … much more range names
)


def check_ranges(option: RangeInfo) -> None:
    """Ensure given datetime ranges are valid"""
    for range_tuple in RANGES:
        reveal_type(range_tuple)  # builtins.tuple[Union[Union[Literal['begin'], Literal['end']], Union[Literal['created_start'], Literal['created_end']]], ...]
        for range_val in range_tuple:
            reveal_type(range_val)  # Union[Union[Literal['begin'], Literal['end']], Union[Literal['created_start'], Literal['created_end']]]
            if not isinstance(option[range_val], datetime):  # ❌  TypedDict key must be a string literal; expected one of ("begin", "end", "created_start", "created_end")  [literal-required]
                raise ValueError(f"{range_val} is not a datetime")
        # … much more checks
        
def minimal_okay(union: Literal["begin"] | Literal["end"], option: RangeInfo) -> datetime:
    return option[union]
    

def minimal_fail(union: TimeRange | CreatedRange, option: RangeInfo) -> datetime:
    return option[union]   # ❌  TypedDict key must be a string literal; expected one of ("begin", "end", "created_start", "created_end")  [literal-required]

MyPy Play

Expected Behavior

There should be no type error, Literals in Unions should be flattened.

I would love if unions of literals are flattened in general.
Literal["a"] | Literal["b"] | (Literal["c"] | Literal["d"]) == Literal["a", "b", "c", "d"]
But I guess this is yet another issue.

Actual Behavior

Can't access typed dict without type error

Your Environment
(see mypy play)

Related #16813

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions