Skip to main content

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.

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: Listlist, Dictdict, Tupletuple, Setset, FrozenSetfrozenset, Typetype.
  • 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. 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

from typing import Dict, List, Tuple


def parse(rows: List[Dict[str, int]]) -> Tuple[int, ...]:
    return tuple(sum(r.values()) for r in rows)
The cleanup pass also rewrites collections.abc / re references:
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

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.
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.