Skip to content

fix: handle parameterized generics in field(type=...) to avoid infinite recursion#325

Open
gaoflow wants to merge 1 commit into
tobgu:masterfrom
gaoflow:fix/generic-alias-infinite-recursion
Open

fix: handle parameterized generics in field(type=...) to avoid infinite recursion#325
gaoflow wants to merge 1 commit into
tobgu:masterfrom
gaoflow:fix/generic-alias-infinite-recursion

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Fixes #291.

Passing a parameterized generic like tuple[int, int] (a types.GenericAlias) or
typing.List[int] (a typing._GenericAlias) as the type= argument to field()
caused maybe_parse_user_type to recurse forever and raise a RecursionError.

Root cause: Both alias kinds satisfy isinstance(t, Iterable), so the code
entered the iterable branch and iterated the alias. Iterating a
types.GenericAlias yields a starred version of itself (e.g. *tuple[int, int]),
which is itself a GenericAlias and also Iterable — the recursion never bottoms out.

Fix: Before falling through to the generic-iterable branch, detect objects that
carry an __origin__ attribute but are not plain type instances (i.e. all
GenericAlias forms). Call typing.get_origin() to extract the bare origin class
and return that. This makes field(type=tuple[int, int]) behave like
field(type=tuple) for runtime isinstance checks while correctly rejecting
non-tuple values.

Minimal reproducer (before fix → RecursionError; after fix → works)

from pyrsistent import PClass, field

class Coords(PClass):
    position = field(type=tuple[int, int])  # RecursionError before this fix

c = Coords(position=(1, 2))  # OK

Test

Added test_field_type_generic_alias in tests/class_test.py covering
tuple[int, int], list[str], and dict[str, int] aliases. Full test suite
(635 tests) passes.

This pull request was prepared with the assistance of AI, under my direction and review.

…infinite recursion

Passing a parameterized generic such as `tuple[int, int]` (types.GenericAlias)
or `List[int]` (typing._GenericAlias) as the `type` argument to `field()` caused
`maybe_parse_user_type` to loop forever.  Both alias types satisfy
`isinstance(t, Iterable)`, but iterating them yields starred self-references
(e.g. `*tuple[int, int]`) that are themselves Iterable aliases of the same kind,
so the recursive expansion never bottoms out.

Fix: detect any object with an `__origin__` attribute that is not itself a plain
type (covers both `types.GenericAlias` and `typing._GenericAlias`), call
`typing.get_origin()` to extract the bare origin class, and return that.  This
makes `field(type=tuple[int, int])` behave like `field(type=tuple)` at runtime
while avoiding the recursion.

Closes tobgu#291
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type checking tuple type for fields causes infinite recursion

1 participant