> ## Documentation Index
> Fetch the complete documentation index at: https://docs.refactron.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# pep585_generics

> Rewrite typing.List / Dict / Tuple / etc. to the built-in lowercase generics (PEP 585).

**Transform ID:** `pep585_generics`
**Language:** Python

## What it does

Rewrites the deprecated capitalised generics from `typing` to their built-in / `collections.abc` / `re` equivalents introduced in PEP 585. After the rewrite, the cleanup pass drops the now-unused names from any `from typing import …` line and adds `collections.abc` / `re` imports as needed.

The conversion set:

* **Builtins:** `List` → `list`, `Dict` → `dict`, `Tuple` → `tuple`, `Set` → `set`, `FrozenSet` → `frozenset`, `Type` → `type`.
* **`collections`:** `DefaultDict`, `OrderedDict`, `Counter`, `Deque`, `ChainMap` → attribute form (`collections.defaultdict`, etc.); the sidecar adds `import collections` if needed.
* **`collections.abc`:** `Mapping`, `Sequence`, `Iterable`, `Iterator`, `Awaitable`, `Callable`, `Generator`, `AsyncIterable`, `AsyncIterator`, `KeysView`, `ValuesView`, `ItemsView`, `MappingView`, `AbstractSet` (→ `Set`), `MutableSet`, `MutableMapping`, `MutableSequence`, `Collection`, `Container`, `Hashable`, `Sized`, `Reversible`, `Coroutine`.
* **`re`:** `Pattern`, `Match`.

`Optional[X]` and `Union[A, B]` are intentionally NOT touched — those are owned by [`pep604_optional_union`](/transforms/pep604-optional-union). The sidecar lives at `src/transform/transforms/python/_py/pep585_generics.py`.

## Detector pattern

The detector at `src/analyze/detectors/python/typing-generic.ts` flags `Subscript` nodes whose value is one of the recognised typing names — either via `from typing import X` or via `typing.X` / `<alias>.X`.

## Preconditions

1. **`python_version_too_low`** — refuses when the resolved Python version is `< 3.9` **and** the file does not start with `from __future__ import annotations`. PEP 585 generics aren't subscriptable on 3.8 at runtime; PEP 563's `__future__.annotations` defers evaluation so the new syntax is legal as an annotation on 3.7+.
2. **`runtime_subscript_unsafe`** — refuses when the `__future__.annotations` override is the only thing carrying the file but the source uses `isinstance(x, List[int])` (or any other runtime subscript of a target name). The override covers annotations only — the rewritten `list[int]` would `TypeError` at runtime on `< 3.9`.
3. **`runtime_type_eval_unsafe`** — refuses when the file imports Pydantic v1 (`from pydantic import BaseModel` without v2 markers) OR calls `typing.get_type_hints` without `from __future__ import annotations`. Pydantic v1 evaluates annotations at runtime; the new generics may fail `__class_getitem__` on Python 3.9-3.10.
4. Only the bare (or no-op `as <same>`) form of `from typing import X` binds the canonical name we rewrite. Genuine renames are left untouched.

## Before / after

<CodeGroup>
  ```python before.py theme={null}
  from typing import Dict, List, Tuple


  def parse(rows: List[Dict[str, int]]) -> Tuple[int, ...]:
      return tuple(sum(r.values()) for r in rows)
  ```

  ```python after.py theme={null}
  def parse(rows: list[dict[str, int]]) -> tuple[int, ...]:
      return tuple(sum(r.values()) for r in rows)
  ```
</CodeGroup>

The cleanup pass also rewrites `collections.abc` / `re` references:

<CodeGroup>
  ```python before.py theme={null}
  from typing import Callable, Iterable, Pattern


  def each(xs: Iterable[int], f: Callable[[int], int]) -> None:
      for x in xs:
          f(x)


  def first(rx: Pattern[str], s: str) -> int:
      return rx.search(s).start() if rx.search(s) else -1
  ```

  ```python after.py theme={null}
  import re
  from collections.abc import Callable, Iterable


  def each(xs: Iterable[int], f: Callable[[int], int]) -> None:
      for x in xs:
          f(x)


  def first(rx: re.Pattern[str], s: str) -> int:
      return rx.search(s).start() if rx.search(s) else -1
  ```
</CodeGroup>

## Edge cases NOT handled (skip via precondition)

* Pydantic v1 models in the same file — `runtime_type_eval_unsafe` refusal; Pydantic v1's runtime annotation eval can fail on the new generics under 3.9 / 3.10.
* `typing.get_type_hints(...)` without `__future__.annotations` — same refusal, same reason.
* `isinstance(x, List[int])` on a `< 3.9` interpreter with the `__future__` override — `runtime_subscript_unsafe`. The rewritten `list[int]` is not a valid `isinstance` arg on 3.8.
* Aliased imports (`from typing import List as ListOf`) — the alias rebinds the name; we don't follow it.
* `from typing import *` — every canonical name is conservatively treated as in scope.

<Note>
  The `__future__ import annotations` override lifts the version gate for annotations — but only for annotations. Anything that subscripts a typing name at runtime still needs Python 3.9. The `runtime_subscript_unsafe` precondition keeps this safe.
</Note>
