This is an automated email from the ASF dual-hosted git repository.
junrushao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new e10f635 fix(c_class): restore `__init__` installation from
`__ffi_init__` TypeAttrColumn (#543)
e10f635 is described below
commit e10f635544b48ad52b13085369a55b38bc88b7be
Author: Junru Shao <[email protected]>
AuthorDate: Mon Apr 13 00:38:46 2026 -0700
fix(c_class): restore `__init__` installation from `__ffi_init__`
TypeAttrColumn (#543)
## Summary
- Fixes a regression from cc1346a5 (#541) where `register_object`
stopped installing `__init__` from the C++ `__ffi_init__`
TypeAttrColumn, causing segfaults when constructing objects like
`IntPair` in `examples/python_packaging/`
- Adds `_install_init(cls, type_info)` in `registry.py` that restores
the invariant: if `__ffi_init__` exists, install `__init__` via
`_dunder._make_init`; if no reflection and no user `__init__`, install a
TypeError guard instead of allowing segfaults
- For `PyNativeObject` subclasses (e.g., `Shape`) that use `__new__`,
the helper is a no-op
## Test plan
- [x] `uv run pytest -vvs tests/python/` -- 2004 passed, 38 skipped, 3
xfailed
- [x] `examples/python_packaging/run_example.py` constructs `IntPair`
correctly
- [x] Pre-commit hooks all pass
---
python/tvm_ffi/dataclasses/c_class.py | 2 +-
python/tvm_ffi/registry.py | 51 ++++++++++++++++++++++++++++++++++-
2 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/python/tvm_ffi/dataclasses/c_class.py
b/python/tvm_ffi/dataclasses/c_class.py
index 9c4a4dc..b94dddb 100644
--- a/python/tvm_ffi/dataclasses/c_class.py
+++ b/python/tvm_ffi/dataclasses/c_class.py
@@ -111,7 +111,7 @@ def c_class(
)
def decorator(cls: _T) -> _T:
- cls = register_object(type_key)(cls)
+ cls = register_object(type_key, init=False)(cls)
type_info = getattr(cls, "__tvm_ffi_type_info__", None)
assert type_info is not None
_warn_missing_field_annotations(cls, type_info, stacklevel=2)
diff --git a/python/tvm_ffi/registry.py b/python/tvm_ffi/registry.py
index dde1a31..69a3cfe 100644
--- a/python/tvm_ffi/registry.py
+++ b/python/tvm_ffi/registry.py
@@ -33,7 +33,11 @@ _SKIP_UNKNOWN_OBJECTS = False
_T = TypeVar("_T", bound=type)
-def register_object(type_key: str | None = None) -> Callable[[_T], _T]:
+def register_object(
+ type_key: str | None = None,
+ *,
+ init: bool = True,
+) -> Callable[[_T], _T]:
"""Register object type.
Parameters
@@ -41,6 +45,11 @@ def register_object(type_key: str | None = None) ->
Callable[[_T], _T]:
type_key
The type key of the node. It requires ``type_key`` to be registered
already
on the C++ side. If not specified, the class name will be used.
+ init
+ If True (default), install ``__init__`` from the C++ ``__ffi_init__``
+ TypeAttrColumn when available, or a TypeError guard for ``Object``
+ subclasses that lack one. Set to False when a subsequent decorator
+ (e.g. ``@c_class``) will handle ``__init__`` installation.
Notes
-----
@@ -76,6 +85,8 @@ def register_object(type_key: str | None = None) ->
Callable[[_T], _T]:
info = core._register_object_by_index(type_index, cls)
_add_class_attrs(type_cls=cls, type_info=info)
setattr(cls, "__tvm_ffi_type_info__", info)
+ if init:
+ _install_init(cls, info)
return cls
if isinstance(type_key, str):
@@ -341,6 +352,44 @@ def init_ffi_api(namespace: str, target_module_name: str |
None = None) -> None:
setattr(target_module, fname, f)
+def _install_init(cls: type, type_info: TypeInfo) -> None:
+ """Install ``__init__`` from the C++ ``__ffi_init__`` TypeAttrColumn.
+
+ Skipped if the class body already defines ``__init__``.
+ This ensures that ``register_object`` alone provides a working
+ constructor, maintaining the invariant that ``c_class`` is a full
+ alias of ``register_object`` + dunder installation.
+
+ When no ``__ffi_init__`` is available and the class is an ``Object``
+ subclass, a TypeError guard is installed to prevent segfaults from
+ uninitialised handles.
+ """
+ if "__init__" in cls.__dict__:
+ return
+ ffi_init = core._lookup_type_attr(type_info.type_index, "__ffi_init__")
+ if ffi_init is not None:
+ from ._dunder import _make_init # noqa: PLC0415
+
+ cls.__init__ = _make_init( # type: ignore[attr-defined]
+ cls,
+ type_info,
+ ffi_init=ffi_init,
+ inplace=False,
+ )
+ elif issubclass(cls, core.Object):
+ type_name = cls.__name__
+
+ def __init__(self: Any, *args: Any, **kwargs: Any) -> None:
+ raise TypeError(
+ f"`{type_name}` cannot be constructed directly. "
+ f"Define a custom __init__ or use a factory method."
+ )
+
+ __init__.__qualname__ = f"{cls.__qualname__}.__init__"
+ __init__.__module__ = cls.__module__
+ cls.__init__ = __init__ # type: ignore[attr-defined]
+
+
def _add_class_attrs(type_cls: type, type_info: TypeInfo) -> type:
for field in type_info.fields:
name = field.name