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 bb13eaa3 doc: add dataclass reflection guide and update related docs 
(#494)
bb13eaa3 is described below

commit bb13eaa3a36eecb46fc1437b410338b7d2a9488b
Author: Junru Shao <[email protected]>
AuthorDate: Mon Mar 2 14:58:27 2026 -0800

    doc: add dataclass reflection guide and update related docs (#494)
    
    ## Summary
    
    Add a comprehensive guide for the dataclass-style reflection system
    (`ObjectDef`, field traits, auto-init, deep copy, repr, hashing,
    comparison) and update the Sphinx documentation configuration and
    cross-references.
    
    ## Changes
    
    ### New documentation
    
    - **`docs/guides/dataclass_reflection.rst`** — A 467-line guide
    covering:
    - Quick start: defining a C++ object with `ObjectDef`, using it from
    Python with full dataclass semantics.
    - Auto-generated constructors: how `__ffi_init__` is synthesized from
    reflected fields, including positional, optional, and keyword-only
    parameter ordering.
    - Field traits reference: `refl::default_()`, `refl::default_factory()`,
    `refl::kw_only()`, `refl::init()`, `refl::repr()`, `refl::hash()`,
    `refl::compare()`, `refl::Metadata`.
    - Dataclass operations: `DeepCopy`, `ReprPrint`, `RecursiveHash`,
    `RecursiveEq` with behavioral details (iterative DFS, cycle handling,
    DAG caching).
    - Custom hooks via `TypeAttrDef` for hash, equality, comparison, and
    repr overrides.
      - Python `c_class` decorator and `@register_object` delegation.
    - Inheritance semantics for field composition across parent/child object
    hierarchies.
    
    ### Sphinx configuration updates (`docs/conf.py`)
    
    - Exclude `tvm::ffi::reflection::default_` and
    `tvm::ffi::reflection::default_factory` from Doxygen symbol output
    (these are user-facing helpers that cause Doxygen parse noise).
    - Fix `_link_inherited_members` to rewrite `.CObject` references as
    `.Object` in cross-reference links, correcting broken autodoc
    inherited-member links.
    - Add `_move`, `__move_handle_from__`, and
    `__init_handle_by_constructor__` to `_autodoc_always_show`, ensuring
    these low-level Object lifecycle methods appear in auto-generated API
    docs.
    
    ### Cross-reference fixes in existing docs
    
    - **`docs/concepts/object_and_class.rst`**: Update
    `refl::DefaultValue(0)` to `refl::default_(0)` to match the current
    preferred API name.
    - **`docs/guides/export_func_cls.rst`**: Replace broken
    `:cpp:class:\`tvm::ffi::reflection::init\`` Sphinx cross-reference with
    inline code literal `` ``init<Args...>()`` `` (the template alias does
    not resolve as a Doxygen class).
    
    ### TOC integration
    
    - **`docs/index.rst`**: Insert `guides/dataclass_reflection.rst` into
    the Guides toctree between `export_func_cls` and `kernel_library_guide`.
    
    ## Behavioral changes
    
    None. This is a documentation-only change with no code modifications.
    
    ## Test evidence
    
    Documentation-only PR; no runtime behavior is affected. The guide's code
    examples are consistent with the existing C++ reflection API
    (`ObjectDef`, field traits) and Python `c_class`/`register_object`
    implementations validated by the existing test suite
    (`tests/python/test_dataclass.py`, `tests/cpp/test_reflection.cc`).
    
    ### Untested corner cases
    
    - Sphinx build with the new RST file has not been verified in CI yet
    (will be validated by the doc-build CI job).
    - The `.CObject` -> `.Object` rewrite in `_link_inherited_members` is a
    string replacement; edge cases where a class legitimately contains
    "CObject" in its qualified name would be incorrectly rewritten (none
    exist in the current codebase).
---
 docs/concepts/object_and_class.rst   |   2 +-
 docs/conf.py                         |   8 +-
 docs/guides/dataclass_reflection.rst | 467 +++++++++++++++++++++++++++++++++++
 docs/guides/export_func_cls.rst      |   2 +-
 docs/index.rst                       |   1 +
 5 files changed, 477 insertions(+), 3 deletions(-)

diff --git a/docs/concepts/object_and_class.rst 
b/docs/concepts/object_and_class.rst
index 4bd90d81..9d0d67ce 100644
--- a/docs/concepts/object_and_class.rst
+++ b/docs/concepts/object_and_class.rst
@@ -393,7 +393,7 @@ to register reflection metadata for object type ``T``:
    refl::ObjectDef<MyObjectObj>()
        .def_rw("value", &MyObjectObj::value,
                "The numeric value",                    // docstring
-               refl::DefaultValue(0),                  // default value
+               refl::default_(0),                       // default value
                refl::Metadata{{"min", 0}, {"max", 100}})  // custom metadata
        .def("add_to_value", &MyObjectObj::AddToValue,
             "Add a value to the object's value field");
diff --git a/docs/conf.py b/docs/conf.py
index 123b7c11..b8b10212 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -92,7 +92,8 @@ PREDEFINED             += TVM_FFI_DLL= TVM_FFI_DLL_EXPORT= 
TVM_FFI_INLINE= \
                           TVM_FFI_EXTRA_CXX_API= TVM_FFI_WEAK= 
TVM_FFI_DOXYGEN_MODE \
                           __cplusplus=201703
 EXCLUDE_SYMBOLS        += *details*  *TypeTraits* std \
-                         *use_default_type_traits_v* *is_optional_type_v* 
*operator*
+                         *use_default_type_traits_v* *is_optional_type_v* 
*operator* \
+                         tvm::ffi::reflection::default_ 
tvm::ffi::reflection::default_factory
 EXCLUDE_PATTERNS       += */function_details.h */container_details.h
 ENABLE_PREPROCESSING   = YES
 MACRO_EXPANSION        = YES
@@ -292,6 +293,8 @@ def _link_inherited_members(app, what, name, obj, options, 
lines) -> None:  # no
     if base in _py_native_classes or getattr(base, "__module__", "") == 
"builtins":
         return
     owner_fq = 
f"{base.__module__}.{base.__qualname__}".replace("tvm_ffi.core.", "tvm_ffi.")
+    if owner_fq.endswith(".CObject"):
+        owner_fq = owner_fq.removesuffix(".CObject") + ".Object"
     role = "attr" if what in {"attribute", "property"} else "meth"
     lines.clear()
     lines.append(
@@ -338,6 +341,9 @@ _autodoc_always_show = {
     "__ffi_init__",
     "__from_extern_c__",
     "__from_mlir_packed_safe_call__",
+    "_move",
+    "__move_handle_from__",
+    "__init_handle_by_constructor__",
 }
 # If a member method comes from one of these native types, hide it in the docs
 _py_native_classes: tuple[type, ...] = (
diff --git a/docs/guides/dataclass_reflection.rst 
b/docs/guides/dataclass_reflection.rst
new file mode 100644
index 00000000..d4a1a773
--- /dev/null
+++ b/docs/guides/dataclass_reflection.rst
@@ -0,0 +1,467 @@
+..  Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+..    http://www.apache.org/licenses/LICENSE-2.0
+
+..  Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+.. _dataclass-reflection:
+
+Dataclass-Style Reflection
+==========================
+
+TVM-FFI's reflection system provides Python-dataclass-style features for C++
+classes: auto-generated constructors, default values, keyword-only parameters,
+repr, hashing, comparison, and deep copy. These features are enabled by
+per-field and per-class traits registered via
+:cpp:class:`~tvm::ffi::reflection::ObjectDef`.
+
+This guide assumes familiarity with :doc:`export_func_cls` and
+:doc:`../concepts/object_and_class`.
+
+
+Quick Start
+-----------
+
+Define a C++ object with fields, register it with traits, and use it from
+Python with full dataclass semantics:
+
+.. code-block:: cpp
+
+   #include <tvm/ffi/tvm_ffi.h>
+
+   namespace ffi = tvm::ffi;
+
+   class PointObj : public ffi::Object {
+    public:
+     int64_t x;
+     int64_t y;
+     ffi::String label;
+
+     static constexpr bool _type_mutable = true;
+     TVM_FFI_DECLARE_OBJECT_INFO_FINAL("my_ext.Point", PointObj, ffi::Object);
+   };
+
+   TVM_FFI_STATIC_INIT_BLOCK() {
+     namespace refl = ffi::reflection;
+     refl::ObjectDef<PointObj>()
+         .def_rw("x", &PointObj::x)
+         .def_rw("y", &PointObj::y)
+         .def_rw("label", &PointObj::label, refl::default_(""));
+   }
+
+No ``refl::init<>()`` call is needed — the reflection system auto-generates a
+packed ``__ffi_init__`` from the reflected fields:
+
+.. code-block:: python
+
+   import tvm_ffi
+
+   @tvm_ffi.register_object("my_ext.Point")
+   class Point(tvm_ffi.Object):
+       x: int
+       y: int
+       label: str
+
+   p1 = Point(1, 2)                # positional args
+   p2 = Point(1, 2, label="origin")  # keyword arg with default
+
+
+.. _auto-init:
+
+Auto-Generated Constructors
+----------------------------
+
+When no explicit ``refl::init<Args...>()`` is registered, ``ObjectDef``
+auto-generates a packed constructor (``__ffi_init__``) from the reflected
+fields. The generated signature follows Python conventions:
+
+1. **Required positional** parameters come first.
+2. **Optional positional** parameters (those with defaults) come next.
+3. **Keyword-only** parameters follow after a ``*`` separator.
+
+Field Traits for Init
+~~~~~~~~~~~~~~~~~~~~~
+
+.. list-table::
+   :header-rows: 1
+   :widths: 30 70
+
+   * - Trait
+     - Effect on auto-init
+   * - ``refl::default_(value)``
+     - Makes the parameter optional with a literal default value.
+   * - ``refl::default_factory(fn)``
+     - Makes the parameter optional; calls ``fn()`` each time a default is
+       needed. Use for mutable defaults (e.g. ``Array``, ``Dict``).
+   * - ``refl::kw_only(true)``
+     - Moves the parameter after the ``*`` separator (keyword-only).
+   * - ``refl::init(false)``
+     - Excludes the field from the constructor entirely. The field must have
+       a default value or be initialized by a base-class constructor.
+
+.. code-block:: cpp
+
+   refl::ObjectDef<ConfigObj>()
+       .def_rw("batch_size", &ConfigObj::batch_size)
+       .def_rw("lr", &ConfigObj::lr, refl::default_(0.001))
+       .def_rw("device", &ConfigObj::device, refl::kw_only(true),
+               refl::default_("cpu"))
+       .def_rw("_cache", &ConfigObj::_cache, refl::init(false),
+               refl::default_factory([] { return ffi::Dict(); }));
+
+The generated Python signature is:
+
+.. code-block:: python
+
+   def __init__(self, batch_size, lr=0.001, *, device="cpu"):
+       ...
+   # _cache is excluded from __init__ but initialized to Dict()
+
+Suppressing Auto-Init
+~~~~~~~~~~~~~~~~~~~~~
+
+Pass ``refl::init(false)`` at the class level to suppress auto-init entirely:
+
+.. code-block:: cpp
+
+   refl::ObjectDef<InternalObj>(refl::init(false))
+       .def_rw("x", &InternalObj::x)
+       .def_rw("y", &InternalObj::y);
+
+The object will have no ``__ffi_init__`` method. Construction must happen
+through a custom factory or from C++.
+
+Explicit Constructors
+~~~~~~~~~~~~~~~~~~~~~
+
+Use ``refl::init<Args...>()`` to register an explicit typed constructor
+instead of auto-init:
+
+.. code-block:: cpp
+
+   refl::ObjectDef<IntPairObj>()
+       .def(refl::init<int64_t, int64_t>())
+       .def_ro("a", &IntPairObj::a)
+       .def_ro("b", &IntPairObj::b);
+
+This calls ``IntPairObj(int64_t, int64_t)`` directly. Auto-init is
+automatically suppressed when an explicit constructor is registered.
+
+
+Default Values
+--------------
+
+Literal Defaults
+~~~~~~~~~~~~~~~~
+
+``refl::default_(value)`` stores a literal default. The value is captured once
+at registration time:
+
+.. code-block:: cpp
+
+   .def_rw("threshold", &Obj::threshold, refl::default_(0.5))
+
+Factory Defaults
+~~~~~~~~~~~~~~~~
+
+``refl::default_factory(fn)`` calls ``fn()`` each time a default is needed.
+Use this for mutable containers to avoid aliasing:
+
+.. code-block:: cpp
+
+   .def_rw("items", &Obj::items,
+           refl::default_factory([] { return ffi::List<ffi::String>(); }))
+
+.. note::
+
+   ``refl::default_`` and ``refl::default_factory`` are the preferred names for
+   new code. The original names ``refl::DefaultValue`` and
+   ``refl::DefaultFactory`` are retained for backward compatibility.
+
+
+.. _field-traits-ref:
+
+Field Traits Reference
+----------------------
+
+All traits are passed as extra arguments to ``def_ro`` or ``def_rw``.
+Multiple traits can be combined on a single field:
+
+.. code-block:: cpp
+
+   .def_rw("name", &Obj::name,
+           refl::default_("unnamed"),
+           refl::kw_only(true),
+           refl::repr(false))
+
+.. list-table::
+   :header-rows: 1
+   :widths: 30 70
+
+   * - Trait
+     - Description
+   * - ``refl::init(bool)``
+     - Include/exclude field from auto-generated ``__ffi_init__``.
+   * - ``refl::kw_only(bool)``
+     - Mark field as keyword-only in auto-init.
+   * - ``refl::default_(value)``
+     - Literal default value for the field.
+   * - ``refl::default_factory(fn)``
+     - Factory function ``() -> Any`` for mutable defaults.
+   * - ``refl::repr(bool)``
+     - Include/exclude field from repr output.
+   * - ``refl::hash(bool)``
+     - Include/exclude field from recursive hashing.
+   * - ``refl::compare(bool)``
+     - Include/exclude field from recursive comparison.
+   * - ``refl::Metadata({...})``
+     - Attach custom key-value metadata to the field.
+
+
+.. _dataclass-operations:
+
+Dataclass Operations
+--------------------
+
+Once a class is registered with ``ObjectDef``, several dataclass operations are
+available automatically. These are defined in
+``include/tvm/ffi/extra/dataclass.h``:
+
+.. code-block:: cpp
+
+   #include <tvm/ffi/extra/dataclass.h>
+
+   ffi::Any value = ...;
+   ffi::Any copy = ffi::DeepCopy(value);
+   ffi::String repr = ffi::ReprPrint(value);
+   int64_t h = ffi::RecursiveHash(value);
+   bool eq = ffi::RecursiveEq(a, b);
+
+All operations use iterative DFS with an explicit stack (no recursion), so they
+are safe for deep object graphs.
+
+Deep Copy
+~~~~~~~~~
+
+``DeepCopy(value)`` recursively copies an object and all reachable objects in
+its graph. Objects that register a copy constructor via ``ObjectDef``
+automatically support deep copy (through the ``__ffi_shallow_copy__`` type
+attribute).
+
+- **Immutable leaves** (returned as-is): primitives, ``String``, ``Bytes``,
+  ``Shape``
+- **Recursively copied**: ``Array``, ``List``, ``Map``, ``Dict``, and
+  reflected objects
+
+Repr
+~~~~
+
+``ReprPrint(value)`` produces a human-readable string representation using
+field names and values from reflection metadata. It handles cycles (prints
+``...``) and DAG structures (caches repr strings).
+
+Exclude a field from repr output with ``refl::repr(false)``:
+
+.. code-block:: cpp
+
+   .def_ro("internal_state", &Obj::internal_state, refl::repr(false))
+
+Hashing
+~~~~~~~
+
+``RecursiveHash(value)`` computes a deterministic recursive hash. The hash is
+consistent with equality: if ``RecursiveEq(a, b)`` then
+``RecursiveHash(a) == RecursiveHash(b)``.
+
+Exclude a field from hashing with ``refl::hash(false)``:
+
+.. code-block:: cpp
+
+   .def_ro("cache_key", &Obj::cache_key, refl::hash(false))
+
+Comparison
+~~~~~~~~~~
+
+``RecursiveEq(a, b)`` tests structural equality. Ordering comparisons
+(``RecursiveLt``, ``RecursiveLe``, ``RecursiveGt``, ``RecursiveGe``) provide
+lexicographic field-by-field ordering.
+
+Exclude a field from comparison with ``refl::compare(false)``:
+
+.. code-block:: cpp
+
+   .def_ro("timestamp", &Obj::timestamp, refl::compare(false))
+
+In Python, these are wired up as ``__eq__``, ``__lt__``, ``__le__``, 
``__gt__``,
+``__ge__``, and ``__hash__`` on classes created with ``c_class``.
+
+
+Custom Hooks
+------------
+
+Override the default behavior for repr, hash, or comparison by registering
+type-level attributes via :cpp:class:`~tvm::ffi::reflection::TypeAttrDef`:
+
+.. code-block:: cpp
+
+   namespace refl = ffi::reflection;
+
+   // Custom hash: only hash the "key" field
+   refl::TypeAttrDef<MyObj>().def(
+       refl::type_attr::kHash,
+       [](const Object* self, const Function& fn_hash) -> int64_t {
+         auto* obj = static_cast<const MyObj*>(self);
+         return fn_hash(AnyView(obj->key)).cast<int64_t>();
+       });
+
+   // Custom equality: only compare "key" fields
+   refl::TypeAttrDef<MyObj>().def(
+       refl::type_attr::kEq,
+       [](const Object* lhs, const Object* rhs, const Function& fn_eq) -> bool 
{
+         auto* a = static_cast<const MyObj*>(lhs);
+         auto* b = static_cast<const MyObj*>(rhs);
+         return fn_eq(AnyView(a->key), AnyView(b->key)).cast<bool>();
+       });
+
+   // Custom three-way comparison
+   refl::TypeAttrDef<MyObj>().def(
+       refl::type_attr::kCompare,
+       [](const Object* lhs, const Object* rhs, const Function& fn_cmp) -> 
int32_t {
+         auto* a = static_cast<const MyObj*>(lhs);
+         auto* b = static_cast<const MyObj*>(rhs);
+         return fn_cmp(AnyView(a->key), AnyView(b->key)).cast<int32_t>();
+       });
+
+   // Custom repr
+   refl::TypeAttrDef<MyObj>().def(
+       refl::type_attr::kRepr,
+       [](const Object* self, const Function& fn_repr) -> String {
+         auto* obj = static_cast<const MyObj*>(self);
+         return "MyObj(key=" + fn_repr(AnyView(obj->key)).cast<String>() + ")";
+       });
+
+.. list-table:: Type attribute hooks
+   :header-rows: 1
+   :widths: 30 30 40
+
+   * - Attribute
+     - Constant
+     - Signature
+   * - ``__ffi_hash__``
+     - ``type_attr::kHash``
+     - ``(const Object* self, Function fn_hash) -> int64_t``
+   * - ``__ffi_eq__``
+     - ``type_attr::kEq``
+     - ``(const Object* lhs, const Object* rhs, Function fn_eq) -> bool``
+   * - ``__ffi_compare__``
+     - ``type_attr::kCompare``
+     - ``(const Object* lhs, const Object* rhs, Function fn_cmp) -> int32_t``
+   * - ``__ffi_repr__``
+     - ``type_attr::kRepr``
+     - ``(const Object* self, Function fn_repr) -> String``
+
+The ``fn_hash``, ``fn_eq``, ``fn_cmp``, and ``fn_repr`` callbacks are provided
+by the framework for recursing into child values.
+
+
+Python ``c_class`` Decorator
+-----------------------------
+
+On the Python side, ``tvm_ffi.dataclasses.c_class`` provides equivalent
+functionality to Python's ``@dataclass`` for C++-backed objects:
+
+.. code-block:: python
+
+   from tvm_ffi.dataclasses import c_class
+
+   @c_class("my_ext.Point")
+   class Point(tvm_ffi.Object):
+       x: int
+       y: int
+       label: str
+
+The decorator:
+
+1. Looks up the C++ type info registered by ``ObjectDef<PointObj>``.
+2. Matches Python annotations to C++ fields.
+3. Generates ``__init__`` from ``__ffi_init__``, respecting ``kw_only``,
+   defaults, and ``init(false)`` settings from C++.
+4. Installs ``__copy__``, ``__deepcopy__``, ``__eq__``, ``__hash__``,
+   ``__repr__``, and comparison operators.
+
+.. note::
+
+   ``@tvm_ffi.register_object`` can also be used, which delegates to
+   ``c_class`` internally for objects with reflected fields.
+
+
+Inheritance
+-----------
+
+Dataclass traits compose across inheritance. A child class inherits the
+parent's fields and adds its own:
+
+.. code-block:: cpp
+
+   class ParentObj : public ffi::Object {
+    public:
+     int64_t parent_required;
+     int64_t parent_default;
+
+     static constexpr bool _type_mutable = true;
+     static constexpr uint32_t _type_child_slots = 1;
+     TVM_FFI_DECLARE_OBJECT_INFO("my_ext.Parent", ParentObj, ffi::Object);
+   };
+
+   class ChildObj : public ParentObj {
+    public:
+     int64_t child_required;
+     int64_t child_kw_only;
+
+     TVM_FFI_DECLARE_OBJECT_INFO_FINAL("my_ext.Child", ChildObj, ParentObj);
+   };
+
+   TVM_FFI_STATIC_INIT_BLOCK() {
+     namespace refl = ffi::reflection;
+
+     refl::ObjectDef<ParentObj>()
+         .def_rw("parent_required", &ParentObj::parent_required)
+         .def_rw("parent_default", &ParentObj::parent_default,
+                 refl::default_(int64_t{5}));
+
+     refl::ObjectDef<ChildObj>()
+         .def_rw("child_required", &ChildObj::child_required)
+         .def_rw("child_kw_only", &ChildObj::child_kw_only, 
refl::kw_only(true));
+   }
+
+In Python, the child's auto-init includes all fields:
+
+.. code-block:: python
+
+   # Generated signature:
+   # def __init__(self, parent_required, child_required, parent_default=5, *, 
child_kw_only):
+   child = Child(1, 2, child_kw_only=3)
+   assert child.parent_required == 1
+   assert child.child_required == 2
+   assert child.parent_default == 5    # uses default
+   assert child.child_kw_only == 3
+
+
+Further Reading
+---------------
+
+- :doc:`export_func_cls`: Basic function and class export guide
+- :doc:`../concepts/object_and_class`: Object system fundamentals, type 
registry, and ABI
+- :ref:`sec-stubgen`: Auto-generating Python type stubs from reflection 
metadata
+- :doc:`cpp_lang_guide`: Full C++ API guide covering ``Any``, ``Function``, 
containers
diff --git a/docs/guides/export_func_cls.rst b/docs/guides/export_func_cls.rst
index cc0f32d3..ac528450 100644
--- a/docs/guides/export_func_cls.rst
+++ b/docs/guides/export_func_cls.rst
@@ -253,7 +253,7 @@ and instantiated from Python. The reflection helper
   :cpp:func:`ObjectDef::def <tvm::ffi::reflection::ObjectDef::def>`,
   static via
   :cpp:func:`ObjectDef::def_static 
<tvm::ffi::reflection::ObjectDef::def_static>`
-- **Constructors** via :cpp:class:`tvm::ffi::reflection::init`
+- **Constructors** via ``init<Args...>()``
 
 
 Register in C++
diff --git a/docs/index.rst b/docs/index.rst
index c00e6613..5fb646ef 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -48,6 +48,7 @@ Table of Contents
    :caption: Guides
 
    guides/export_func_cls.rst
+   guides/dataclass_reflection.rst
    guides/kernel_library_guide.rst
    guides/compiler_integration.md
    guides/cubin_launcher.rst

Reply via email to