Enable Typing and generics based typing to support PEP585 - #727
Merged
Conversation
arhamchopra
marked this pull request as ready for review
July 24, 2026 20:57
arhamchopra
requested review from
AdamGlustein,
alexddobkin,
robambalu and
svatasoiu
as code owners
July 24, 2026 20:57
8 tasks
robambalu
reviewed
Jul 27, 2026
Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
Signed-off-by: Arham Chopra <arham.chopra@cubistsystematic.com>
robambalu
reviewed
Jul 29, 2026
robambalu
self-requested a review
July 29, 2026 16:23
robambalu
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
PEP 585 — and the tooling that enforces it (ruff rule
UP006, pyupgrade, …) — rewrites the classictypinggenerics to their builtin equivalents in annotations:typing.List[X]→list[X],typing.Dict[K, V]→dict[K, V], and so on.csp's type-introspection layer treated a time-series built from a builtin generic as a different
type from the same time-series built from the
typingform:Because
csp.const([...])(and type inference generally) emits thetypingform while a modernizedannotation becomes the builtin form, any code that compares time-series types with a strict
==brokeafter a routine
List→listautoformat. Concretely, incsp-gateway,GatewayChannels.set_channelvalidates an edge against the declared channel type with
==:This produced 258 such failures across the
csp-gatewaytest suite. The clean fix belongs in csp:ts[list[X]]andts[typing.List[X]]should be the same type.Root cause
TsType.__class_getitem__normalizes its parameter throughContainerTypeNormalizer.normalize_type, but for an already-parametrized generic the normalizer returnedthe type unchanged, so
list[int]andtyping.List[int]yielded two distinctProtocol[...]specializations that were neither
==nor hash-equal.csp's graph-wiring / type-resolution path already treated the two origins as equivalent (via
CspTypingUtils._ORIGIN_COMPAT_MAPandget_origin, which maplist→typing.List, etc.), which iswhy graphs ran fine — this was an internal inconsistency in how
TsTypeobjects were built andcompared, not a real semantic incompatibility.
What this PR does
Canonicalizes builtin (PEP 585) container generics to their
typingequivalents, recursively, so bothspellings normalize to a single representation that compares and hashes equal.
Direction chosen: builtin →
typing, because that is the representation csp inference already emits, soit is the lowest-blast-radius option. (Moving the canonical form toward the builtins is the more "modern"
long-term direction and is left for a follow-up — see Limitations.)
Specifically,
ContainerTypeNormalizer:list/set/dict/tuple→typing.List/Set/Dict/Tupleat any nestingdepth.
X | Noneandtyping.Optional[...]) and through preserved wrappers(
typing.Mapping,FastList,Callable,csp.typingnumpy array types, custom generics): builtincontainers nested inside them are canonicalized while the wrapper keeps its own origin/flavor.
list["T"]) to a stabletyping.ForwardRef(matching how
typing.List["T"]is stored) rather than minting a freshTypeVaron each call.to preserve object identity for callers.
It also makes
csp.snap/csp.snapkeyscalar validation normalize the expected type and compare with==instead ofis(pydantic_types.make_snap_validatorandinstantiation_type_resolver._is_scalar_value_matching_spec). This keeps container-typed snaps workingregardless of the
listvstyping.Listspelling, and also fixes a pre-existing failure where a snapinto a builtin-form-annotated scalar (
x: list[int]) was already rejected.Behavior
Current state
csp/tests/impl/types/test_tstype.py: equality + hash parity for all fourcontainers, deep nesting, unions, Mapping/FastList/Callable recursion,
ForwardRefargs, identitypreservation, empty tuple, and a node-binding end-to-end test; plus a
csp.snapbuiltin-generic scalartest in
csp/tests/test_dynamic.py.Limitations / explicitly out of scope
ts[list] != ts[typing.List](likewisedict/set/tuple). Their natural canonical direction is the opposite — empty/untyped inference (csp.const([]))yields the builtin
list, nottyping.List— and normalizing them touches the C++ actual-type boundary(
normalized_type_to_actual_python_type(typing.List)returnstyping.List, not thelistclass). Thisis deferred to a follow-up to keep this change focused and low-risk.
collections.abc.Mapping[...]vstyping.Mapping[...], orcollections.abc.Callable[...]vstyping.Callable[...], remain distinct(they are distinct at the Python level, and csp only bridges the four standard containers via
_ORIGIN_COMPAT_MAP). Builtin containers nested inside such wrappers are still canonicalized; only theouter abc-vs-typing distinction remains.
ts[int | None] == ts[typing.Optional[int]]) already compared equal andare unchanged.
References
X | YUP006(non-pep585-annotation) — the modernization that surfaces thiscsp/impl/types/container_type_normalizer.py,csp/impl/types/pydantic_types.py,csp/impl/types/instantiation_type_resolver.py