fix(py_class): support super().__init__() in init=False subclasses#532
Merged
junrushao merged 3 commits intoapache:mainfrom Apr 10, 2026
Merged
Conversation
When a `@py_class(init=False)` subclass defines a custom `__init__` that calls `super().__init__()` followed by field assignment, the parent's auto-generated `__init__` would forward to `__ffi_init__` with the child type's C++ constructor, which requires field arguments that were not provided, causing a segfault. The fix adds a `ffi.NewEmpty` C++ global function that allocates a zero-initialized (calloc'd) object by type index via `CreateEmptyObject`. On the Python side, each auto-generated `__init__` now detects the super().__init__() call pattern -- no args/kwargs and `type(self)` differs from the declaring class -- and allocates an empty object of the child's type instead of dispatching to `__ffi_init__`. The `_install_init` wrapper for `init=False` classes with user-defined `__init__` pre-allocates the empty object before calling the user's `__init__`, ensuring field setters operate on a valid C++ backing object.
Contributor
There was a problem hiding this comment.
Code Review
This pull request enables support for the super().__init__() pattern in @py_class(init=False) subclasses by introducing a mechanism to pre-allocate empty, zero-initialized FFI objects. This prevents crashes that previously occurred when calling parent constructors without field arguments. The implementation includes a new C++ helper ffi.NewEmpty and updates to the Python registry to handle object allocation during initialization. I have provided a suggestion to use functools.wraps in the _install_init wrapper to better preserve metadata during function wrapping.
- Ruff: move closing docstring quote to its own line - Ty: add `type: ignore[missing-argument]` for intentional no-args calls that are intercepted at runtime
- Replace functools.cache (3.9+) with functools.lru_cache(maxsize=None) - Use functools.wraps for the _install_init wrapper to properly preserve __doc__, __annotations__, and other metadata
tqchen
approved these changes
Apr 10, 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.
Summary
@py_class(init=False)subclasses usesuper().__init__()followed by field assignmentffi.NewEmptyC++ function to allocate zero-initialized objects by type index_make_init()and pre-allocate instead of dispatching to__ffi_init__Motivation
The common Python pattern of defining a custom
__init__that callssuper().__init__()then sets fields crashes when used with@py_class(init=False):The parent's auto-generated
__init__forwards to__ffi_init__using the child type's C++ constructor, which expects field arguments that were not provided.Design
C++ side (
src/ffi/extra/dataclass.cc): Registerffi.NewEmpty(type_index) -> ObjectRefthat allocates a zero-initialized object viaCreateEmptyObject. The calloc'd state is valid:NoneforAny/ObjectReffields,0for scalars.Python side (
python/tvm_ffi/registry.py):_ffi_alloc_empty(obj): Callsffi.NewEmptyto allocate an empty FFI object fortype(obj)and moves the handle intoobj. No-op if already allocated._is_super_init_from_subclass(self): Comparestype(self).__tvm_ffi_type_info__identity against the declaring class'stype_info. ReturnsTrueonly for registered@py_classsubclasses — undecorated subclasses inherit the parent'stype_infoso the identity check correctly filters them out._make_initcode paths (with/without__post_init__) intercept the no-args super-init-from-subclass call and allocate an empty object instead of dispatching to__ffi_init__.Test plan
uv run pytest -vvs tests/python/test_dataclass_py_class.py::TestSuperInitPattern— 12 tests passuv run pytest -vvs tests/python/— 1970 passed, 38 skipped, 3 xfailed, 0 failuresTests cover: basic super-init, deep hierarchy (Node→BaseType→PointerType), calloc defaults, intermediate custom/auto inits, normal init unaffected, non-py_class subclass error handling, isinstance checks, field overwrite, copy/deepcopy, and direct-from-Object inheritance.
🤖 Generated with Claude Code