Skip to content

TypeError: object.__new__(X) is not safe when class inherits from typing.Generic and defines __new__ #845

@dv-indy

Description

@dv-indy

Running pyrsistent on GraalPy 25.0.2 and hit a crash on import. Took a while to track down the root cause so figured I'd report it properly.

Error

TypeError: object.__new__(PMap) is not safe, use PMap.__new__()

Full traceback from import pyrsistent:

File ".../pyrsistent/_pmap.py", line 557, in <module>
    _EMPTY_PMAP = _turbo_mapping({}, 0)
File ".../pyrsistent/_pmap.py", line 554, in _turbo_mapping
    return PMap(len(initial), pvector().extend(buckets))
File ".../pyrsistent/_pmap.py", line 151, in __new__
    self = super(PMap, cls).__new__(cls)
TypeError: object.__new__(PMap) is not safe, use PMap.__new__()

Minimal reproducer

from typing import Generic, TypeVar
KT = TypeVar('KT')
VT = TypeVar('VT')

class Foo(Generic[KT, VT]):
    __slots__ = ('x', 'y')
    def __new__(cls, x, y):
        self = super(Foo, cls).__new__(cls)
        self.x = x
        self.y = y
        return self

f = Foo(1, 2)
print(f.x)  # should print 1

This raises TypeError: object.__new__(Foo) is not safe, use Foo.__new__() on GraalPy. Works fine on CPython 3.12 and PyPy.

Removing the Generic[KT, VT] base makes the error go away. So does removing __new__ and using __init__ instead. The issue is specifically the combination of inheriting from Generic[...] and defining __new__.

Why this matters

pyrsistent added Generic[...] bases to its persistent collection classes (PMap, PSet, PList, PDeque) in October 2023 purely for type-checker support. Those classes already had __new__ for __slots__ initialization. CPython treats this combination as completely valid — object.__new__(cls) inside a custom __new__ is the standard pattern for allocating an instance.

Where the check lives

Found it in WrapTpNew.java. The check walks up the MRO looking for the first non-Python (builtin) tp_new slot. For Foo(Generic[KT, VT]), it lands on Generic before reaching object, and Generic.tp_new != object.tp_new, so the safety check fires. In CPython, Generic doesn't override tp_new at all, so the walk goes straight through to object.

Workaround

Replacing __new__ with __init__ in the affected classes works around it. But this is a valid Python pattern and the error should not be raised — CPython's own tp_new_wrapper check is specifically guarding against things like object.__new__(dict) where you pass a completely unrelated type, not the normal super().__new__(cls) pattern inside a class's own __new__.

Environment

  • GraalPy 3.12.8 (GraalVM CE Native 25.0.2), macOS aarch64
  • pyrsistent 0.20.0
  • CPython 3.12.x: no issue

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions