This is an automated email from the ASF dual-hosted git repository.

tlopex 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 c62b07c2 [CORE] Publish native layout traits from ObjectDef (#721)
c62b07c2 is described below

commit c62b07c2b27ec09a2616eb314a0ef2acc006d55d
Author: Linzhang Li <[email protected]>
AuthorDate: Wed Sep 2 13:33:02 2026 -0400

    [CORE] Publish native layout traits from ObjectDef (#721)
    
    Class::_type_final never reaches the runtime. TVMFFITypeInfo exposes the
    type index, depth, ancestors, fields and methods; TVMFFITypeMetadata
    adds the creator, total_size and the structural-eq kind. Neither carries
    num_child_slots / child_slots_can_overflow — those are arguments to the
    registration call and stay inside the registry. So nothing downstream
    can ask whether a class admits subtypes, and a binding generator that
    wants to store an object by value has no way to find out.
    
    Every ObjectDef<Class> now registers __ffi_type_final__ as a bool type
    attribute, reusing the existing RegisterTypeAttrValue mechanism (one
    bool Any per type, no C ABI change). ObjectInfo.is_final surfaces it to
    the stub generator.
    
    is_final stays None for a type registered without an ObjectDef (e.g.
    ffi.Object), so a registered False remains distinguishable from "never
    published" — the distinction matters because a consumer must not treat
    "not final" and "unknown" alike.
    
    Testing — tests/cpp/test_reflection.cc: a non-final type, a final one,
    and ffi.Module. tests/python/test_stubgen.py: the same three cases
    through ObjectInfo, including the None case.
    
    Signed-off-by: yuchuan <[email protected]>
---
 include/tvm/ffi/reflection/accessor.h |  7 +++++++
 include/tvm/ffi/reflection/registry.h |  2 ++
 python/tvm_ffi/stub/utils.py          |  3 +++
 tests/cpp/test_reflection.cc          |  8 ++++++++
 tests/python/test_stubgen.py          | 16 ++++++++++++++++
 5 files changed, 36 insertions(+)

diff --git a/include/tvm/ffi/reflection/accessor.h 
b/include/tvm/ffi/reflection/accessor.h
index 0f8337d9..c0e3d93f 100644
--- a/include/tvm/ffi/reflection/accessor.h
+++ b/include/tvm/ffi/reflection/accessor.h
@@ -564,6 +564,13 @@ inline constexpr const char* kDataToJson = 
"__data_to_json__";
 inline constexpr const char* kDataFromJson = "__data_from_json__";
 /*! \brief Per-class enum state: ordered entries, canonical indices, and 
extensible attrs. */
 inline constexpr const char* kEnumState = "__ffi_enum__";
+/*!
+ * \brief Whether the class is declared final (``Class::_type_final``).
+ *
+ * Registered as a ``bool`` by every ``ObjectDef<Class>``. A final class can
+ * never gain a subtype, so a binding generator may store it by value.
+ */
+inline constexpr const char* kTypeFinal = "__ffi_type_final__";
 }  // namespace type_attr
 
 /*!
diff --git a/include/tvm/ffi/reflection/registry.h 
b/include/tvm/ffi/reflection/registry.h
index 04e47d84..2d1b3a43 100644
--- a/include/tvm/ffi/reflection/registry.h
+++ b/include/tvm/ffi/reflection/registry.h
@@ -781,6 +781,8 @@ class ObjectDef : public ReflectionDefBase {
         }
       }
     }
+    // Step 4. Publish finality, which no other registered metadata exposes.
+    RegisterTypeAttrValue(type_index_, type_attr::kTypeFinal, 
Class::_type_final);
   }
 
   /*!
diff --git a/python/tvm_ffi/stub/utils.py b/python/tvm_ffi/stub/utils.py
index c16eb657..a053758e 100644
--- a/python/tvm_ffi/stub/utils.py
+++ b/python/tvm_ffi/stub/utils.py
@@ -202,6 +202,8 @@ class ObjectInfo:
     """Type keys of every ancestor, root first (``["ffi.Object", "ir.Expr"]`` 
for ``tirx.Add``)."""
     total_size: int | None = None
     """Native ``sizeof`` in bytes, or ``None`` when the type has no metadata 
of its own."""
+    is_final: bool | None = None
+    """``__ffi_type_final__``: the class admits no subtype. ``None`` when 
unregistered."""
 
     def has_overloaded_methods(self) -> bool:
         """Return whether reflection exposed multiple signatures for a 
method."""
@@ -264,4 +266,5 @@ class ObjectInfo:
             has_init=has_init,
             ancestors=[info.type_key for info in ancestor_infos],
             total_size=type_info.total_size if type_info._has_type_metadata 
else None,
+            is_final=_lookup_type_attr(type_info.type_index, 
"__ffi_type_final__"),
         )
diff --git a/tests/cpp/test_reflection.cc b/tests/cpp/test_reflection.cc
index 361ef9d5..af4629f2 100644
--- a/tests/cpp/test_reflection.cc
+++ b/tests/cpp/test_reflection.cc
@@ -375,6 +375,14 @@ TEST(Reflection, TypeAttrDefDirectValues) {
   EXPECT_EQ(func_attr[type_index].cast<Function>()(5).cast<int64_t>(), 10);
 }
 
+TEST(Reflection, ObjectDefPublishesTypeFinal) {
+  reflection::TypeAttrColumn final_attr(reflection::type_attr::kTypeFinal);
+
+  EXPECT_FALSE(final_attr[TestObjA::RuntimeTypeIndex()].cast<bool>());
+  EXPECT_TRUE(final_attr[TestObjADerived::RuntimeTypeIndex()].cast<bool>());
+  EXPECT_TRUE(final_attr[TypeKeyToIndex("ffi.Module")].cast<bool>());
+}
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::GlobalDef().def_method("testing.Int_GetValue", &TIntObj::GetValue);
diff --git a/tests/python/test_stubgen.py b/tests/python/test_stubgen.py
index 4fefdc24..199ad796 100644
--- a/tests/python/test_stubgen.py
+++ b/tests/python/test_stubgen.py
@@ -1030,6 +1030,22 @@ def test_objectinfo_total_size_requires_own_metadata() 
-> None:
     assert func.ancestors == ["ffi.Object"]
 
 
+def test_objectinfo_is_final() -> None:
+    """``ObjectDef`` publishes finality; a type registered without one 
publishes none."""
+    # A registered `False` must stay distinguishable from an unregistered 
attribute.
+    base = ObjectInfo.from_type_info(_TestCxxClassBase.__tvm_ffi_type_info__)  
# ty: ignore[unresolved-attribute]
+    assert base.is_final is False
+
+    derived = ObjectInfo.from_type_info(
+        
_lookup_or_register_type_info_from_type_key("testing.TestObjectDerived")
+    )
+    assert derived.is_final is True
+
+    # `ffi.Object` is registered without an `ObjectDef`, so it claims nothing.
+    root = 
ObjectInfo.from_type_info(_lookup_or_register_type_info_from_type_key("ffi.Object"))
+    assert root.is_final is None
+
+
 def test_named_type_schema_keeps_positional_signature() -> None:
     """Synthetic schemas (function params, tests) carry no layout facts."""
     schema = NamedTypeSchema("x", TypeSchema("int"))

Reply via email to