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

Reply via email to