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

tqchen 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 88d9f706 [FEAT] Add typed reflection attribute keys (#846)
88d9f706 is described below

commit 88d9f7068717a8dc0df7d2ad06e1567dfc24472f
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Oct 13 22:37:30 2026 -0400

    [FEAT] Add typed reflection attribute keys (#846)
    
    Introduce generic `reflection::AttrKey<T>` descriptors with strict
    optional extraction and typed value registration alongside dynamic
    attribute names.
    
    Co-locate canonical `tvm::ffi::type_attr` contracts with their owning
    APIs while preserving erased columns and name-based packed registration.
    Add `native_fn_attr<&Fn>(key)` and enum `set_native_fn_attr<&Fn>(key)`
    for explicit key-directed native callbacks.
---
 docs/concepts/structural_eq_hash.rst      |  11 +-
 docs/guides/dataclass_reflection.rst      |   8 +-
 include/tvm/ffi/enum.h                    |  11 +-
 include/tvm/ffi/extra/dataclass.h         |  47 +++++
 include/tvm/ffi/extra/serialization.h     |  23 +++
 include/tvm/ffi/extra/structural_equal.h  |  20 +++
 include/tvm/ffi/extra/structural_hash.h   |  19 ++
 include/tvm/ffi/extra/structural_mutate.h |  52 +++++-
 include/tvm/ffi/extra/structural_visit.h  |  23 ++-
 include/tvm/ffi/reflection/accessor.h     | 282 ++++++++----------------------
 include/tvm/ffi/reflection/creator.h      |   8 +-
 include/tvm/ffi/reflection/enum_def.h     |  14 +-
 include/tvm/ffi/reflection/overload.h     |   3 +-
 include/tvm/ffi/reflection/registry.h     |  45 +++--
 src/ffi/container.cc                      |  66 ++++---
 src/ffi/extra/dataclass.cc                |  77 ++++----
 src/ffi/extra/reflection_extra.cc         |  15 +-
 src/ffi/extra/serialization.cc            |  16 +-
 src/ffi/extra/structural_equal.cc         |  13 +-
 src/ffi/extra/structural_hash.cc          |  15 +-
 src/ffi/extra/structural_mutate.cc        |  46 ++---
 src/ffi/extra/structural_visit.cc         |  22 +--
 src/ffi/object.cc                         |   2 +-
 src/ffi/testing/testing.cc                |  27 +--
 tests/cpp/extra/test_serialization.cc     |  13 +-
 tests/cpp/extra/test_structural_mutate.cc |  11 +-
 tests/cpp/test_reflection.cc              |  25 ++-
 tests/cpp/testing_object.h                |  45 ++---
 28 files changed, 523 insertions(+), 436 deletions(-)

diff --git a/docs/concepts/structural_eq_hash.rst 
b/docs/concepts/structural_eq_hash.rst
index 51840d5d..1e869b1e 100644
--- a/docs/concepts/structural_eq_hash.rst
+++ b/docs/concepts/structural_eq_hash.rst
@@ -807,17 +807,18 @@ Example (C++)
            .def_ro("params", &MyNodeObj::params)
            .def_ro("body", &MyNodeObj::body);
        refl::TypeAttrDef<MyNodeObj>()
-           .def(refl::type_attr::kSEqual, &MyNodeObj::SEqual)
-           .def(refl::type_attr::kSHash, &MyNodeObj::SHash);
+           .def(type_attr::kSEqual.name, &MyNodeObj::SEqual)
+           .def(type_attr::kSHash.name, &MyNodeObj::SHash);
      }
 
      static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
      TVM_FFI_DECLARE_OBJECT_INFO_FINAL("my.Node", MyNodeObj, Object);
    };
 
-See :cpp:var:`tvm::ffi::reflection::type_attr::kSEqual` and
-:cpp:var:`tvm::ffi::reflection::type_attr::kSHash` in
-``include/tvm/ffi/reflection/accessor.h`` for the full reference.
+See :cpp:var:`tvm::ffi::type_attr::kSEqual` and
+:cpp:var:`tvm::ffi::type_attr::kSHash` in
+``include/tvm/ffi/extra/structural_equal.h`` and
+``include/tvm/ffi/extra/structural_hash.h`` for the full reference.
 
 
 All Kinds at a Glance
diff --git a/docs/guides/dataclass_reflection.rst 
b/docs/guides/dataclass_reflection.rst
index 803ada55..3e27ed6e 100644
--- a/docs/guides/dataclass_reflection.rst
+++ b/docs/guides/dataclass_reflection.rst
@@ -320,7 +320,7 @@ type-level attributes via 
:cpp:class:`~tvm::ffi::reflection::TypeAttrDef`:
 
    // Custom hash: only hash the "key" field
    refl::TypeAttrDef<MyObj>().def(
-       refl::type_attr::kHash,
+       type_attr::kHash.name,
        [](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>();
@@ -328,7 +328,7 @@ type-level attributes via 
:cpp:class:`~tvm::ffi::reflection::TypeAttrDef`:
 
    // Custom equality: only compare "key" fields
    refl::TypeAttrDef<MyObj>().def(
-       refl::type_attr::kEq,
+       type_attr::kEq.name,
        [](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);
@@ -337,7 +337,7 @@ type-level attributes via 
:cpp:class:`~tvm::ffi::reflection::TypeAttrDef`:
 
    // Custom three-way comparison
    refl::TypeAttrDef<MyObj>().def(
-       refl::type_attr::kCompare,
+       type_attr::kCompare.name,
        [](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);
@@ -346,7 +346,7 @@ type-level attributes via 
:cpp:class:`~tvm::ffi::reflection::TypeAttrDef`:
 
    // Custom repr
    refl::TypeAttrDef<MyObj>().def(
-       refl::type_attr::kRepr,
+       type_attr::kRepr.name,
        [](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>() + ")";
diff --git a/include/tvm/ffi/enum.h b/include/tvm/ffi/enum.h
index 97d12442..6661e949 100644
--- a/include/tvm/ffi/enum.h
+++ b/include/tvm/ffi/enum.h
@@ -184,11 +184,16 @@ class StrEnum : public Enum {
   /// \endcond
 };
 
+namespace type_attr {
+/*! \brief Per-class enum state: ordered entries, canonical indices, and 
extensible attrs. */
+inline constexpr reflection::AttrKey<EnumState> kEnumState{"__ffi_enum__"};
+}  // namespace type_attr
+
 template <typename Index>
 inline Enum EnumObj::GetByIndex(int32_t type_index, const Index& index) {
-  static reflection::TypeAttrColumn 
state_column(reflection::type_attr::kEnumState);
-  if (AnyView value = state_column[type_index]; value != nullptr) {
-    EnumState state = value.cast<EnumState>();
+  static reflection::TypeAttrColumn state_column(type_attr::kEnumState);
+  if (auto value = type_attr::kEnumState.ValueFrom(state_column[type_index])) {
+    const EnumState& state = *value;
     if (auto entry = state->indexes.Get(Any(index))) return 
entry->as_or_throw<Enum>();
   }
   TVM_FFI_THROW(ValueError) << "Enum `" << TypeIndexToTypeKey(type_index)
diff --git a/include/tvm/ffi/extra/dataclass.h 
b/include/tvm/ffi/extra/dataclass.h
index 6c849725..c2409a97 100644
--- a/include/tvm/ffi/extra/dataclass.h
+++ b/include/tvm/ffi/extra/dataclass.h
@@ -26,7 +26,9 @@
 #include <tvm/ffi/any.h>
 #include <tvm/ffi/container/variant.h>
 #include <tvm/ffi/extra/base.h>
+#include <tvm/ffi/function.h>
 #include <tvm/ffi/optional.h>
+#include <tvm/ffi/reflection/accessor.h>
 #include <tvm/ffi/string.h>
 
 #include <ostream>
@@ -136,6 +138,51 @@ inline std::ostream& operator<<(std::ostream& os, const 
Optional<T>& value) {
   return os << ReprPrint(Any(value));
 }
 
+namespace type_attr {
+/*!
+ * \brief Custom recursive repr hook.
+ *
+ * If registered, ``RecursiveRepr`` (Python ``__repr__``) calls this instead
+ * of the default field-by-field formatting.  The hook receives a callback
+ * ``fn_repr`` to recursively format nested values, avoiding infinite loops.
+ */
+inline constexpr reflection::AttrKey<
+    TypedFunction<String(const Object*, const 
TypedFunction<String(AnyView)>&)>>
+    kRepr{"__ffi_repr__"};
+/*!
+ * \brief Custom recursive hash hook.
+ *
+ * If registered, ``RecursiveHash`` (Python ``hash()``) calls this instead
+ * of the default field-by-field hashing.  The hook receives a callback
+ * ``fn_hash`` to recursively hash nested values, with cycle detection.
+ */
+inline constexpr reflection::AttrKey<
+    TypedFunction<int64_t(const Object*, const 
TypedFunction<int64_t(AnyView)>&)>>
+    kHash{"__ffi_hash__"};
+/*!
+ * \brief Custom recursive equality hook.
+ *
+ * If registered, ``RecursiveEq`` (Python ``==``) calls this instead of the
+ * default field-by-field comparison.  Falls back to ``kCompare`` if only the
+ * compare hook is present.  The hook receives a callback ``fn_eq`` to
+ * recursively compare nested values.
+ */
+inline constexpr reflection::AttrKey<
+    TypedFunction<bool(const Object*, const Object*, const 
TypedFunction<bool(AnyView, AnyView)>&)>>
+    kEq{"__ffi_eq__"};
+/*!
+ * \brief Custom recursive three-way comparison hook.
+ *
+ * If registered, ``RecursiveCompare`` (Python ``<``, ``<=``, ``>``, ``>=``)
+ * calls this.  Also used as fallback for ``RecursiveEq`` when ``kEq`` is not
+ * registered.  The hook receives a callback ``fn_cmp`` to recursively
+ * compare nested values, returning < 0, == 0, or > 0.
+ */
+inline constexpr reflection::AttrKey<TypedFunction<int32_t(
+    const Object*, const Object*, const TypedFunction<int32_t(AnyView, 
AnyView)>&)>>
+    kCompare{"__ffi_compare__"};
+}  // namespace type_attr
+
 }  // namespace ffi
 }  // namespace tvm
 #endif  // TVM_FFI_EXTRA_DATACLASS_H_
diff --git a/include/tvm/ffi/extra/serialization.h 
b/include/tvm/ffi/extra/serialization.h
index b5aa2891..f8baa6cb 100644
--- a/include/tvm/ffi/extra/serialization.h
+++ b/include/tvm/ffi/extra/serialization.h
@@ -25,6 +25,8 @@
 
 #include <tvm/ffi/extra/base.h>
 #include <tvm/ffi/extra/json.h>
+#include <tvm/ffi/function.h>
+#include <tvm/ffi/reflection/accessor.h>
 
 namespace tvm {
 namespace ffi {
@@ -67,6 +69,27 @@ TVM_FFI_EXTRA_CXX_API json::Value ToJSONGraph(const Any& 
value, const Any& metad
  */
 TVM_FFI_EXTRA_CXX_API Any FromJSONGraph(const json::Value& value);
 
+namespace type_attr {
+/*!
+ * \brief Serialize object data to a JSON-compatible value.
+ *
+ * If registered, ``ToJSONGraph`` calls this instead of the default
+ * field-by-field serialization.  Allows types with non-trivial internal
+ * state (e.g. ``TInt`` storing a plain ``int64_t``) to define a compact
+ * custom JSON representation.
+ */
+inline constexpr reflection::AttrKey<TypedFunction<Any(AnyView)>> 
kDataToJson{"__data_to_json__"};
+/*!
+ * \brief Deserialize object data from a JSON-compatible value.
+ *
+ * Counterpart to ``kDataToJson``.  If registered, ``FromJSONGraph`` calls
+ * this to reconstruct the object from its serialized representation
+ * instead of using field-by-field deserialization.
+ */
+inline constexpr reflection::AttrKey<TypedFunction<Any(AnyView)>> 
kDataFromJson{
+    "__data_from_json__"};
+}  // namespace type_attr
+
 }  // namespace ffi
 }  // namespace tvm
 #endif  // TVM_FFI_EXTRA_SERIALIZATION_H_
diff --git a/include/tvm/ffi/extra/structural_equal.h 
b/include/tvm/ffi/extra/structural_equal.h
index 15876a56..13003fad 100644
--- a/include/tvm/ffi/extra/structural_equal.h
+++ b/include/tvm/ffi/extra/structural_equal.h
@@ -25,11 +25,14 @@
 
 #include <tvm/ffi/any.h>
 #include <tvm/ffi/extra/base.h>
+#include <tvm/ffi/function.h>
 #include <tvm/ffi/optional.h>
 #include <tvm/ffi/reflection/access_path.h>
+#include <tvm/ffi/reflection/accessor.h>
 
 namespace tvm {
 namespace ffi {
+
 /*!
  * \brief Structural equality comparators
  */
@@ -73,6 +76,23 @@ class StructuralEqual {
   }
 };
 
+namespace type_attr {
+/*!
+ * \brief Custom structural equality hook (used by ``StructuralEqual``).
+ *
+ * Unlike ``kEq`` (which is for ``RecursiveEq`` / Python ``==``), this is for
+ * the ``StructuralEqual`` system that supports def/use region semantics and
+ * alpha-equivalence of bound variables.  The hook receives a callback to
+ * recursively compare sub-values.
+ *
+ * ``eq_cb`` recursively compares sub-values; its integer 
``TVMFFIDefRegionKind``
+ * argument controls free-variable mapping; ``field_name`` is used for 
mismatch path reporting.
+ */
+inline constexpr reflection::AttrKey<TypedFunction<bool(
+    const Object*, const Object*, const TypedFunction<bool(AnyView, AnyView, 
int, AnyView)>&)>>
+    kSEqual{"__s_equal__"};
+}  // namespace type_attr
+
 }  // namespace ffi
 }  // namespace tvm
 #endif  // TVM_FFI_EXTRA_STRUCTURAL_EQUAL_H_
diff --git a/include/tvm/ffi/extra/structural_hash.h 
b/include/tvm/ffi/extra/structural_hash.h
index bfe023c3..41c07a52 100644
--- a/include/tvm/ffi/extra/structural_hash.h
+++ b/include/tvm/ffi/extra/structural_hash.h
@@ -25,6 +25,8 @@
 
 #include <tvm/ffi/any.h>
 #include <tvm/ffi/extra/base.h>
+#include <tvm/ffi/function.h>
+#include <tvm/ffi/reflection/accessor.h>
 
 namespace tvm {
 namespace ffi {
@@ -52,6 +54,23 @@ class StructuralHash {
   TVM_FFI_INLINE uint64_t operator()(const Any& value) const { return 
Hash(value); }
 };
 
+namespace type_attr {
+/*!
+ * \brief Custom structural hash hook (used by ``StructuralHash``).
+ *
+ * Unlike ``kHash`` (which is for ``RecursiveHash`` / Python ``hash()``),
+ * this is for the ``StructuralHash`` system that supports def/use region
+ * semantics (``map_free_vars``).  The hook receives the current accumulated
+ * hash and a callback to recursively hash sub-values.
+ *
+ * ``hash_cb`` recursively hashes a sub-value; its integer 
``TVMFFIDefRegionKind``
+ * argument controls free-variable mapping.
+ */
+inline constexpr reflection::AttrKey<TypedFunction<int64_t(
+    const Object*, int64_t, const TypedFunction<int64_t(AnyView, int64_t, 
int)>&)>>
+    kSHash{"__s_hash__"};
+}  // namespace type_attr
+
 }  // namespace ffi
 }  // namespace tvm
 #endif  // TVM_FFI_EXTRA_STRUCTURAL_HASH_H_
diff --git a/include/tvm/ffi/extra/structural_mutate.h 
b/include/tvm/ffi/extra/structural_mutate.h
index b8db9197..d768c3a0 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -164,10 +164,6 @@ struct StructuralMutatorVTable {
   FStructuralVarRemapSet var_remap_set = nullptr;
 };
 
-/*! \brief Borrowed structural mutation hook at attribute lookup and call. */
-using FStructuralMutate =
-    reflection::NativeFunctionView<UnchangedOr<Any>(StructuralMutatorObj*, 
AnyView)>;
-
 namespace details {
 template <typename Parent>
 class StructuralMutateDynEngine;
@@ -433,6 +429,45 @@ TVM_FFI_COLD_CODE inline Unexpected<Error> 
SMutateDeclaredTypeError() noexcept {
 }
 }  // namespace details
 
+/*! \brief Borrowed structural mutation hook at attribute lookup and call. */
+using FStructuralMutate =
+    reflection::NativeFunctionView<UnchangedOr<Any>(StructuralMutatorObj*, 
AnyView)>;
+
+namespace type_attr {
+/*!
+ * \brief Custom structural mutation hook used by ``StructuralMutator``.
+ *
+ * The hook receives the active mutator and the input value. It should 
recursively mutate structural
+ * children through the mutator, return ``Unchanged`` if no fields are 
changed, and avoid
+ * intentionally mutating the source object. The input is borrowed and remains 
valid for the
+ * duration of the call.
+ *
+ * Value type: either an opaque native hook pointer returning raw
+ * ``Expected<UnchangedOr<Any>>`` storage, or an ``ffi::Function``.
+ *
+ * This is the canonical mutation hook. A type that defines
+ * ``kStructuralMaybeInplaceMutate`` must also define this attribute.
+ */
+inline constexpr reflection::AttrKey<FStructuralMutate> 
kStructuralMutate{"__s_mutate__"};
+/*!
+ * \brief Optional custom mutation hook that may reuse its input object.
+ *
+ * The hook receives the active mutator and a borrowed input value that is 
safe to mutate in place.
+ * Callers must route shared objects through non-in-place mutation instead. 
The hook may mutate and
+ * return the source object, delegate to non-in-place mutation, or return 
another replacement.
+ *
+ * Value type: either an opaque native hook pointer returning raw
+ * ``Expected<UnchangedOr<Any>>`` storage, or an ``ffi::Function``.
+ *
+ * This hook is optional. When it is absent, ``DefaultMutateExpected`` with
+ * ``inplace_mode=InplaceMode::kAllow`` falls back to non-in-place mutation 
through
+ * ``kStructuralMutate`` or reflected structural fields. In-place mutation is 
therefore explicitly
+ * opt-in and is never inferred from ownership by the reflected fallback.
+ */
+inline constexpr reflection::AttrKey<FStructuralMutate> 
kStructuralMaybeInplaceMutate{
+    "__s_maybe_inplace_mutate__"};
+}  // namespace type_attr
+
 /*!
  * \brief Object node of a structural mutator.
  */
@@ -620,7 +655,7 @@ class StructuralMutatorObj : public Object {
   // Engine-internal: subclasses call the Expected forms above.
   /*! \brief Raw default mutation: attr lookup then hook, favouring the fn-ptr 
case. */
   TVM_FFI_INLINE TVMFFIAny DefaultMutateRaw(AnyView value) noexcept {
-    static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralMutate);
+    static reflection::TypeAttrColumn column(type_attr::kStructuralMutate);
     AnyView attr = column[value.type_index()];
     // Exactly one frame per node: hooks propagate errors untouched, and this 
is the engine
     // dispatching into `value`, so both exits below name it here and nowhere 
else.
@@ -705,7 +740,7 @@ class StructuralMutatorObj : public Object {
    *       and every other shape is handed to the out-of-line remainder.
    */
   TVM_FFI_INLINE TVMFFIAny DefaultMaybeInplaceMutateRaw(AnyView value) 
noexcept {
-    static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralMaybeInplaceMutate);
+    static reflection::TypeAttrColumn 
column(type_attr::kStructuralMaybeInplaceMutate);
     AnyView attr = column[value.type_index()];
     if (TVM_FFI_PREDICT_TRUE(attr.type_index() == 
TypeIndex::kTVMFFIOpaquePtr)) {
       // This is the engine dispatching into `value`; hooks propagate errors 
untouched, so the
@@ -790,7 +825,7 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
   const Object* obj = value.as<Object>();
   int32_t type_index = obj->type_index();
 
-  static reflection::TypeAttrColumn 
column(reflection::type_attr::kShallowCopy);
+  static reflection::TypeAttrColumn column(type_attr::kShallowCopy);
   AnyView attr = column[type_index];
   if (TVM_FFI_PREDICT_FALSE(attr.type_index() != TypeIndex::kTVMFFIFunction)) {
     return Unexpected(Error("TypeError", "__ffi_shallow_copy__ must be an 
ffi.Function", ""));
@@ -957,7 +992,8 @@ namespace details {
  *   copy->b = std::move(b).ValueOrUnchanged(std::move(copy->b));
  *   return copy;
  * }
- * FStructuralMutate hook = FStructuralMutate::FromNative<&FooMutate>();
+ * FStructuralMutate hook =
+ * FStructuralMutate::FromNative<&FooMutate>();
  * Expected<UnchangedOr<Any>> result = hook.CallExpected(mutator, value);
  * \endcode
  *
diff --git a/include/tvm/ffi/extra/structural_visit.h 
b/include/tvm/ffi/extra/structural_visit.h
index de3eba98..055226c4 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -132,6 +132,24 @@ struct StructuralVisitorVTable {
 using FStructuralVisit =
     
reflection::NativeFunctionView<Optional<VisitInterrupt>(StructuralVisitorObj*, 
AnyView)>;
 
+namespace type_attr {
+/*!
+ * \brief Custom structural visitor hook (used by ``StructuralVisitor``).
+ *
+ * The hook receives the active visitor and the current object value. Opaque
+ * pointer hooks return a ``TVMFFIAny`` that stores an
+ * ``Expected<Optional<VisitInterrupt>>`` result.
+ *
+ * Value type: either an opaque native hook pointer or an ``ffi::Function``.
+ *
+ * On success, the hook returns ``None`` for no interrupt, or a
+ * ``VisitInterrupt`` to halt traversal. On failure, it returns an ``Error``.
+ * The ``visitor`` parameter is the active traversal context used to 
recursively
+ * visit structural children.
+ */
+inline constexpr reflection::AttrKey<FStructuralVisit> 
kStructuralVisit{"__s_visit__"};
+}  // namespace type_attr
+
 /*!
  * \brief Object node of a structural visitor.
  *
@@ -238,7 +256,7 @@ class StructuralVisitorObj : public Object {
  private:
   /*! \brief Internal raw ABI descent, avoiding a typed result around compiled 
hooks. */
   TVM_FFI_INLINE TVMFFIAny DefaultVisitRaw(AnyView value) noexcept {
-    static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralVisit);
+    static reflection::TypeAttrColumn column(type_attr::kStructuralVisit);
     AnyView attr = column[value.type_index()];
     if (TVM_FFI_PREDICT_TRUE(attr.type_index() == 
TypeIndex::kTVMFFIOpaquePtr)) {
       return 
(*reinterpret_cast<FStructuralVisitABI>(attr.cast<void*>()))(this, value);
@@ -532,7 +550,8 @@ namespace details {
  *   TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->b));
  *   return Optional<VisitInterrupt>(std::nullopt);
  * }
- * FStructuralVisit hook = FStructuralVisit::FromNative<&FooVisit>();
+ * FStructuralVisit hook =
+ * FStructuralVisit::FromNative<&FooVisit>();
  * Expected<Optional<VisitInterrupt>> result = hook.CallExpected(visitor, 
value);
  * \endcode
  *
diff --git a/include/tvm/ffi/reflection/accessor.h 
b/include/tvm/ffi/reflection/accessor.h
index 18aa2f66..5c251787 100644
--- a/include/tvm/ffi/reflection/accessor.h
+++ b/include/tvm/ffi/reflection/accessor.h
@@ -26,13 +26,17 @@
 #include <tvm/ffi/any.h>
 #include <tvm/ffi/c_api.h>
 #include <tvm/ffi/function.h>
+#include <tvm/ffi/reflection/native_function.h>
 #include <tvm/ffi/type_traits.h>
 
+#include <optional>
 #include <string>
 
 namespace tvm {
 namespace ffi {
 namespace reflection {
+template <typename T>
+struct AttrKey;
 
 /*!
  * \brief helper function to get reflection field info by type key and field 
name
@@ -177,6 +181,10 @@ class TypeAttrColumn {
       TVM_FFI_THROW(RuntimeError) << "Cannot find type attribute " << 
attr_name;
     }
   }
+  /*! \brief Look up a typed key's column while preserving erased entry 
access. */
+  template <typename T>
+  explicit TypeAttrColumn(AttrKey<T> key) : TypeAttrColumn(key.name) {}
+
   /*!
    * \brief Get the type attribute column by type index.
    * \param type_index The type index.
@@ -325,13 +333,53 @@ inline bool ForEachFieldInfoWithEarlyStop(const TypeInfo* 
type_info,
   return false;
 }
 
+/*!
+ * \brief C++17 constexpr helper: convert a ``const char*`` to 
``TVMFFIByteArray``.
+ * \param s A null-terminated string literal.
+ * \return A ``TVMFFIByteArray`` whose ``data`` points to *s* and ``size`` 
equals ``strlen(s)``.
+ */
+inline constexpr TVMFFIByteArray AsByteArray(const char* s) {
+  return {s, std::char_traits<char>::length(s)};
+}
+
+/*!
+ * \brief A typed attribute name. Null-terminated name storage must outlive 
the key.
+ *
+ * \code{.cpp}
+ * // In namespace tvm::ffi:
+ * namespace type_attr {
+ * inline constexpr reflection::AttrKey<bool> 
kSerializable{"example.serializable"};
+ * }
+ * reflection::TypeAttrDef<MyObject>().attr(type_attr::kSerializable, true);
+ * \endcode
+ */
+template <typename T>
+struct AttrKey {
+  /*! \brief The value type associated with this key. */
+  using ValueType = T;
+  /*! \brief Borrowed null-terminated attribute name; its storage must outlive 
the key. */
+  const char* name;
+
+  /*!
+   * \brief Extract a present value strictly; None denotes an absent entry.
+   * \throws Error if a present value has the wrong type. No fallback 
conversion is run.
+   * \note Borrowed results remain valid only while the source value's owner 
remains alive.
+   */
+  TVM_FFI_INLINE std::optional<ValueType> ValueFrom(AnyView value) const {
+    if (value == nullptr) return std::nullopt;
+    return value.template as_or_throw<ValueType>();
+  }
+};
+
+}  // namespace reflection
+
 /*!
  * \brief Type attribute names used by the reflection system.
  *
- * Each constant names a TypeAttrColumn — a sparse, type-indexed slot that
+ * Each key names a TypeAttrColumn — a sparse, type-indexed slot that
  * stores a ``Function`` or any other Any-compliant value.
  *
- *  - ``TypeAttrDef<T>.def(type_attr::kFoo, ...)`` to register a hook,
+ *  - ``TypeAttrDef<T>.def(type_attr::kFoo.name, ...)`` to register a hook,
  *  - ``TypeAttrColumn(type_attr::kFoo)[type_index]`` to look one up,
  *
  */
@@ -342,10 +390,8 @@ namespace type_attr {
  * Allocates a zero-initialised object of the registered type.  For C++ types
  * this wraps ``metadata->creator``; for Python ``@py_class`` types it is a
  * ``calloc``-based allocator.
- *
- * Signature: ``() -> TSelf``, where ``TSelf`` is a subclass of ObjectRef.
  */
-inline constexpr const char* kNew = "__ffi_new__";
+inline constexpr reflection::AttrKey<TypedFunction<ObjectRef()>> 
kNew{"__ffi_new__"};
 /*!
  * \brief Packed constructor — creates **and** initialises a new object.
  *
@@ -356,21 +402,17 @@ inline constexpr const char* kNew = "__ffi_new__";
  * For C++ types with ``refl::init<Args...>()``, the function is the explicit
  * init; otherwise ``ffi._RegisterFFIInit`` generates a default one using 
reflection.
  *
- * Signature: ``(*args, **kwargs) -> TSelf``, where ``TSelf`` is a subclass of 
ObjectRef.
- *
  * Keyword arguments are packed as ``[KWARGS, key0, val0, key1, val1, ...]``.
  */
-inline constexpr const char* kInit = "__ffi_init__";
+inline constexpr reflection::AttrKey<Function> kInit{"__ffi_init__"};
 /*!
  * \brief Convert ``AnyView`` to a specific reflected ``TSelf`` type.
  *
  * Registered via ``TypeAttrDef<TObj>().def_convert<TSelf>()`` or
  * ``ObjectDef<TObj>().def_convert<TSelf>()``. Used by the Python type 
converter
  * to marshal values into the correct ``TSelf`` subclass.
- *
- * Signature: ``(AnyView src) -> TSelf``, where ``TSelf`` has a registered 
TypeTraits converter.
  */
-inline constexpr const char* kConvert = "__ffi_convert__";
+inline constexpr reflection::AttrKey<TypedFunction<Any(AnyView)>> 
kConvert{"__ffi_convert__"};
 /*!
  * \brief Type schema accepted by ``kConvert`` before it returns ``TSelf``.
  *
@@ -378,232 +420,52 @@ inline constexpr const char* kConvert = 
"__ffi_convert__";
  * generation uses this attribute to render widened input annotations while
  * keeping output annotations precise.
  */
-inline constexpr const char* kConvertTypeSchema = 
"__ffi_convert_type_schema__";
+inline constexpr reflection::AttrKey<Any> 
kConvertTypeSchema{"__ffi_convert_type_schema__"};
 /*!
  * \brief Shallow-copy factory.
  *
  * Allocates a new object and copies all reflected field values from the
  * source.  Used by Python ``copy.copy()`` via ``__copy__`` and by
  * ``copy.replace()`` via ``__replace__``.
- *
- * Signature: ``(TSelf self) -> TSelf``, where ``TSelf`` is a subclass of 
ObjectRef.
  */
-inline constexpr const char* kShallowCopy = "__ffi_shallow_copy__";
+inline constexpr reflection::AttrKey<TypedFunction<ObjectRef(const Object*)>> 
kShallowCopy{
+    "__ffi_shallow_copy__"};
 /*!
- * \brief Custom recursive repr hook.
- *
- * If registered, ``RecursiveRepr`` (Python ``__repr__``) calls this instead
- * of the default field-by-field formatting.  The hook receives a callback
- * ``fn_repr`` to recursively format nested values, avoiding infinite loops.
- *
- * Signature: ``(TSelf self, fn_repr: FnRepr) -> String``, where ``TSelf`` is 
a subclass of
- * ObjectRef, and ``FnRepr: (AnyView value) -> String`` formats a nested value.
- */
-inline constexpr const char* kRepr = "__ffi_repr__";
-/*!
- * \brief Custom recursive hash hook.
- *
- * If registered, ``RecursiveHash`` (Python ``hash()``) calls this instead
- * of the default field-by-field hashing.  The hook receives a callback
- * ``fn_hash`` to recursively hash nested values, with cycle detection.
- *
- * Signature: ``(TSelf self, fn_hash: FnHash) -> int64``, where ``TSelf`` is a 
subclass of
- * ObjectRef, and ``FnHash: (AnyView value) -> int64`` hashes a nested value.
- */
-inline constexpr const char* kHash = "__ffi_hash__";
-/*!
- * \brief Custom recursive equality hook.
- *
- * If registered, ``RecursiveEq`` (Python ``==``) calls this instead of the
- * default field-by-field comparison.  Falls back to ``kCompare`` if only the
- * compare hook is present.  The hook receives a callback ``fn_eq`` to
- * recursively compare nested values.
- *
- * Signature: ``(TSelf lhs, TSelf rhs, fn_eq: FnEq) -> bool``, where ``TSelf`` 
is a subclass of
- * ObjectRef, and ``FnEq: (AnyView lhs, AnyView rhs) -> bool`` compares nested 
values.
- */
-inline constexpr const char* kEq = "__ffi_eq__";
-/*!
- * \brief Custom recursive three-way comparison hook.
- *
- * If registered, ``RecursiveCompare`` (Python ``<``, ``<=``, ``>``, ``>=``)
- * calls this.  Also used as fallback for ``RecursiveEq`` when ``kEq`` is not
- * registered.  The hook receives a callback ``fn_cmp`` to recursively
- * compare nested values.
+ * \brief Whether the class is declared final (``Class::_type_final``).
  *
- * Signature: ``(TSelf lhs, TSelf rhs, fn_cmp: FnCmp) -> int32``, where 
``TSelf`` is a subclass of
- * ObjectRef, and ``FnCmp: (TSelf lhs, TSelf rhs) -> int32`` returns < 0, == 
0, or > 0.
+ * 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* kCompare = "__ffi_compare__";
+inline constexpr reflection::AttrKey<bool> kTypeFinal{"__ffi_type_final__"};
 /*!
  * \brief Custom Any-level hash for use as ``Map``/``Dict`` key.
  *
- * Unlike ``kHash`` (which operates on ``ObjectRef`` and is recursive), this
+ * Unlike ``kHash`` (which operates on objects and is recursive), this
  * hook operates at the ``Any`` level — it is called by ``AnyHash`` whenever
  * an ``Any`` holding an object of this type is used as a container key.
  *
- * Register a native hook with ``NativeFunction<int64_t(const 
Any&)>::FromNative<&Fn>()``
- * or a packed ``Function`` object.
+ * Register a native hook with ``TypeAttrDef<T>::native_fn_attr<&Fn>(key)``
+ * or a packed ``Function`` object through the name-based registration API.
  *
- * Native adapter signature: ``TVMFFIAny (*)(const Any& src) noexcept``
- *
- * Function signature: ``(Any src) -> int64``
+ * Native adapters use a context-free noexcept ABI and return raw 
``TVMFFIAny`` storage.
  */
-inline constexpr const char* kAnyHash = "__any_hash__";
+inline constexpr 
reflection::AttrKey<reflection::NativeFunctionView<int64_t(const Any&)>> 
kAnyHash{
+    "__any_hash__"};
 /*!
  * \brief Custom Any-level equality for use as ``Map``/``Dict`` key.
  *
- * Unlike ``kEq`` (which operates on ``ObjectRef``), this hook operates at the
+ * Unlike ``kEq`` (which operates on objects), this hook operates at the
  * ``Any`` level — called by ``AnyEqual`` for container key comparison.
  *
- * Register a native hook with
- * ``NativeFunction<bool(const Any&, const Any&)>::FromNative<&Fn>()``
- * or a packed ``Function`` object.
- *
- * Native adapter signature: ``TVMFFIAny (*)(const Any& lhs, const Any& rhs) 
noexcept``
- *
- * Function signature: ``(Any lhs, Any rhs) -> bool``
- */
-inline constexpr const char* kAnyEqual = "__any_equal__";
-/*!
- * \brief Custom structural hash hook (used by ``StructuralHash``).
- *
- * Unlike ``kHash`` (which is for ``RecursiveHash`` / Python ``hash()``),
- * this is for the ``StructuralHash`` system that supports def/use region
- * semantics (``map_free_vars``).  The hook receives the current accumulated
- * hash and a callback to recursively hash sub-values.
- *
- * Signature: ``(TSelf self, int64 init_hash, Function hash_cb) -> int64``, 
where
- * ``TSelf`` is a subclass of ``ObjectRef``.
- *
- * ``hash_cb(AnyView val, int64 init_hash, bool def_region) -> int64``
- * recursively hashes a sub-value; ``def_region`` controls free-variable
- * mapping.
- */
-inline constexpr const char* kSHash = "__s_hash__";
-/*!
- * \brief Custom structural equality hook (used by ``StructuralEqual``).
- *
- * Unlike ``kEq`` (which is for ``RecursiveEq`` / Python ``==``), this is for
- * the ``StructuralEqual`` system that supports def/use region semantics and
- * alpha-equivalence of bound variables.  The hook receives a callback to
- * recursively compare sub-values.
- *
- * Signature: ``(TSelf lhs, TSelf rhs, Function eq_cb) -> bool``, where 
``TSelf`` is a
- * subclass of ``ObjectRef``.
+ * Register a native hook with ``TypeAttrDef<T>::native_fn_attr<&Fn>(key)``
+ * or a packed ``Function`` object through the name-based registration API.
  *
- * ``eq_cb(AnyView lhs, AnyView rhs, bool def_region, AnyView field_name) -> 
bool``
- * recursively compares sub-values; ``def_region`` controls free-variable
- * mapping; ``field_name`` is used for mismatch path reporting.
+ * Native adapters use a context-free noexcept ABI and return raw 
``TVMFFIAny`` storage.
  */
-inline constexpr const char* kSEqual = "__s_equal__";
-/*!
- * \brief Custom structural visitor hook (used by ``StructuralVisitor``).
- *
- * The hook receives the active visitor and the current object value. Opaque
- * pointer hooks return a ``TVMFFIAny`` that stores an
- * ``Expected<Optional<VisitInterrupt>>`` result.
- *
- * Value type: either an opaque function pointer to a C++ structural visit hook
- *
- * ``TVMFFIAny (*)(StructuralVisitorObj* visitor, AnyView value) noexcept``
- *
- * or an ``ffi::Function`` with signature
- *
- * ``(StructuralVisitor visitor, Any value) -> Optional<VisitInterrupt>``.
- *
- * On success, the hook returns ``None`` for no interrupt, or a
- * ``VisitInterrupt`` to halt traversal. On failure, it returns an ``Error``.
- * The ``visitor`` parameter is the active traversal context used to 
recursively
- * visit structural children.
- */
-inline constexpr const char* kStructuralVisit = "__s_visit__";
-
-/*!
- * \brief Custom structural mutation hook used by ``StructuralMutator``.
- *
- * The hook receives the active mutator and the input value. It should 
recursively mutate structural
- * children through the mutator, return the input unchanged if no fields are 
changed, and avoid
- * intentionally mutating the source object. The input is borrowed and remains 
valid for the
- * duration of the call.
- *
- * Value type: either an opaque function pointer to a C++ structural mutation 
hook
- *
- * ``TVMFFIAny (*)(StructuralMutatorObj* mutator, AnyView value) noexcept``
- *
- * returning raw ``Expected<Any>`` storage, or an ``ffi::Function`` with 
signature
- *
- * ``(StructuralMutator mutator, Any value) -> Any``.
- *
- * This is the canonical mutation hook. A type that defines
- * ``kStructuralMaybeInplaceMutate`` must also define this attribute.
- */
-inline constexpr const char* kStructuralMutate = "__s_mutate__";
-/*!
- * \brief Optional custom mutation hook that may reuse its input object.
- *
- * The hook receives the active mutator and a borrowed input value that is 
safe to mutate in place.
- * Callers must route shared objects through non-in-place mutation instead. 
The hook may mutate and
- * return the source object, delegate to non-in-place mutation, or return 
another replacement.
- *
- * Value type: either an opaque function pointer to a C++ structural mutation 
hook
- *
- * ``TVMFFIAny (*)(StructuralMutatorObj* mutator, AnyView value) noexcept``
- *
- * returning raw ``Expected<Any>`` storage, or an
- * ``ffi::Function`` with signature
- *
- * ``(StructuralMutator mutator, Any value) -> Any``.
- *
- * This hook is optional. When it is absent, ``DefaultMutateExpected`` with
- * ``inplace_mode=InplaceMode::kAllow`` falls back to non-in-place mutation 
through
- * ``kStructuralMutate`` or reflected structural fields. In-place mutation is 
therefore explicitly
- * opt-in and is never inferred from ownership by the reflected fallback.
- */
-inline constexpr const char* kStructuralMaybeInplaceMutate = 
"__s_maybe_inplace_mutate__";
-
-/*!
- * \brief Serialize object data to a JSON-compatible value.
- *
- * If registered, ``ToJSONGraph`` calls this instead of the default
- * field-by-field serialization.  Allows types with non-trivial internal
- * state (e.g. ``TInt`` storing a plain ``int64_t``) to define a compact
- * custom JSON representation.
- *
- * Signature: ``(TSelf self) -> Any``, where ``TSelf`` is a subclass of 
``ObjectRef``.
- */
-inline constexpr const char* kDataToJson = "__data_to_json__";
-/*!
- * \brief Deserialize object data from a JSON-compatible value.
- *
- * Counterpart to ``kDataToJson``.  If registered, ``FromJSONGraph`` calls
- * this to reconstruct the object from its serialized representation
- * instead of using field-by-field deserialization.
- *
- * Signature: ``(Any json_data) -> TSelf``, where ``TSelf`` is a subclass of 
``ObjectRef``.
- */
-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__";
+inline constexpr reflection::AttrKey<reflection::NativeFunctionView<bool(const 
Any&, const Any&)>>
+    kAnyEqual{"__any_equal__"};
 }  // namespace type_attr
 
-/*!
- * \brief C++17 constexpr helper: convert a ``const char*`` to 
``TVMFFIByteArray``.
- * \param s A null-terminated string literal.
- * \return A ``TVMFFIByteArray`` whose ``data`` points to *s* and ``size`` 
equals ``strlen(s)``.
- */
-inline constexpr TVMFFIByteArray AsByteArray(const char* s) {
-  return {s, std::char_traits<char>::length(s)};
-}
-
-}  // namespace reflection
 }  // namespace ffi
 }  // namespace tvm
 #endif  // TVM_FFI_REFLECTION_ACCESSOR_H_
diff --git a/include/tvm/ffi/reflection/creator.h 
b/include/tvm/ffi/reflection/creator.h
index 588a035a..1873d7b5 100644
--- a/include/tvm/ffi/reflection/creator.h
+++ b/include/tvm/ffi/reflection/creator.h
@@ -48,14 +48,14 @@ inline ObjectPtr<Object> CreateEmptyObject(const 
TVMFFITypeInfo* type_info) {
     return 
details::ObjectUnsafe::ObjectPtrFromOwned<Object>(static_cast<TVMFFIObject*>(handle));
   }
   // Fallback: __ffi_new__ type attr (Python-defined types)
-  constexpr TVMFFIByteArray kFFINewAttrName = 
reflection::AsByteArray(reflection::type_attr::kNew);
+  constexpr TVMFFIByteArray kFFINewAttrName = 
reflection::AsByteArray(type_attr::kNew.name);
   const TVMFFITypeAttrColumn* column = 
TVMFFIGetTypeAttrColumn(&kFFINewAttrName);
   if (column != nullptr) {
     int32_t offset = type_info->type_index - column->begin_index;
     if (offset >= 0 && offset < column->size) {
       AnyView attr_view = AnyView::CopyFromTVMFFIAny(column->data[offset]);
-      if (auto opt_func = attr_view.try_cast<Function>()) {
-        ObjectRef obj_ref = (*opt_func)().cast<ObjectRef>();
+      if (auto opt_func = type_attr::kNew.ValueFrom(attr_view)) {
+        ObjectRef obj_ref = (*opt_func)();
         return 
details::ObjectUnsafe::ObjectPtrFromObjectRef<Object>(std::move(obj_ref));
       }
     }
@@ -78,7 +78,7 @@ inline bool HasCreator(const TVMFFITypeInfo* type_info) {
   if (type_info->metadata != nullptr && type_info->metadata->creator != 
nullptr) {
     return true;
   }
-  constexpr TVMFFIByteArray kFFINewAttrName = 
reflection::AsByteArray(reflection::type_attr::kNew);
+  constexpr TVMFFIByteArray kFFINewAttrName = 
reflection::AsByteArray(type_attr::kNew.name);
   const TVMFFITypeAttrColumn* column = 
TVMFFIGetTypeAttrColumn(&kFFINewAttrName);
   if (column != nullptr) {
     int32_t offset = type_info->type_index - column->begin_index;
diff --git a/include/tvm/ffi/reflection/enum_def.h 
b/include/tvm/ffi/reflection/enum_def.h
index 4b72fe3a..f444014a 100644
--- a/include/tvm/ffi/reflection/enum_def.h
+++ b/include/tvm/ffi/reflection/enum_def.h
@@ -93,6 +93,18 @@ class EnumDef : public ReflectionDefBase {
     return *this;
   }
 
+  /*! \brief Set an enum attribute using its key's declared value type. */
+  template <typename T, typename Value>
+  EnumDef& set_attr(AttrKey<T> key, Value&& value) {
+    return set_attr(key.name, T(std::forward<Value>(value)));
+  }
+
+  /*! \brief Set a named native callback using its key's declared adapter. */
+  template <auto Fn, typename T>
+  EnumDef& set_native_fn_attr(AttrKey<T> key) {
+    return set_attr(key, T::template FromNative<Fn>());
+  }
+
  private:
   void Register(const EnumState& state, int64_t int_index, String str_index) {
     List<ObjectRef> entries = state->entries;
@@ -133,7 +145,7 @@ class EnumDef : public ReflectionDefBase {
     ::tvm::ffi::details::ObjectUnsafe::GetHeader(state_obj.get())->type_index 
= state_type_index;
     EnumState state =
         
::tvm::ffi::details::ObjectUnsafe::ObjectRefFromObjectPtr<EnumState>(std::move(state_obj));
-    constexpr TVMFFIByteArray name = AsByteArray(type_attr::kEnumState);
+    constexpr TVMFFIByteArray name = AsByteArray(type_attr::kEnumState.name);
     TVMFFIAny value_any = AnyView(state).CopyToTVMFFIAny();
     TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index_, &name, 
&value_any));
     return state;
diff --git a/include/tvm/ffi/reflection/overload.h 
b/include/tvm/ffi/reflection/overload.h
index c9193c40..324704cc 100644
--- a/include/tvm/ffi/reflection/overload.h
+++ b/include/tvm/ffi/reflection/overload.h
@@ -30,6 +30,7 @@
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/function_details.h>
 #include <tvm/ffi/optional.h>
+#include <tvm/ffi/reflection/accessor.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ffi/string.h>
 #include <tvm/ffi/type_traits.h>
@@ -398,7 +399,7 @@ class OverloadObjectDef : private ObjectDef<Class> {
     // via Register() are automatically visible through the TypeAttrColumn.
     if (is_first) {
       const TVMFFITypeInfo* tinfo = TVMFFIGetTypeInfo(type_index_);
-      constexpr TVMFFIByteArray attr_name = AsByteArray(type_attr::kInit);
+      constexpr TVMFFIByteArray attr_name = AsByteArray(type_attr::kInit.name);
       for (int32_t i = 0; i < tinfo->num_methods; ++i) {
         if (tinfo->methods[i].name.size == attr_name.size &&
             std::strncmp(tinfo->methods[i].name.data, attr_name.data, 
attr_name.size) == 0) {
diff --git a/include/tvm/ffi/reflection/registry.h 
b/include/tvm/ffi/reflection/registry.h
index 6b9f8be4..e702fa64 100644
--- a/include/tvm/ffi/reflection/registry.h
+++ b/include/tvm/ffi/reflection/registry.h
@@ -29,7 +29,6 @@
 #include <tvm/ffi/container/variant.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/function_details.h>
-#include <tvm/ffi/optional.h>
 #include <tvm/ffi/reflection/creator.h>
 #include <tvm/ffi/string.h>
 #include <tvm/ffi/type_traits.h>
@@ -47,8 +46,8 @@ namespace tvm {
 namespace ffi {
 /*! \brief Reflection namespace */
 namespace reflection {
-namespace details {
 
+namespace details {
 template <typename T, typename = void>
 struct HasContainerType : public std::false_type {};
 
@@ -755,8 +754,8 @@ class ObjectDef : public ReflectionDefBase {
     if constexpr (std::is_copy_constructible_v<Class>) {
       Function fn = Function::FromTyped(
           [](const Class* self) { return 
ObjectRef(ffi::make_object<Class>(*self)); },
-          std::string(type_key_) + "." + type_attr::kShallowCopy);
-      TVMFFIByteArray attr = AsByteArray(type_attr::kShallowCopy);
+          std::string(type_key_) + "." + type_attr::kShallowCopy.name);
+      TVMFFIByteArray attr = AsByteArray(type_attr::kShallowCopy.name);
       TVMFFIAny fn_any = AnyView(fn).CopyToTVMFFIAny();
       TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index_, &attr, 
&fn_any));
     }
@@ -770,8 +769,8 @@ class ObjectDef : public ReflectionDefBase {
             return 
ObjectRef(::tvm::ffi::details::ObjectUnsafe::ObjectPtrFromOwned<Object>(
                 static_cast<TVMFFIObject*>(handle)));
           },
-          std::string(type_key_) + "." + type_attr::kNew);
-      TVMFFIByteArray attr = AsByteArray(type_attr::kNew);
+          std::string(type_key_) + "." + type_attr::kNew.name);
+      TVMFFIByteArray attr = AsByteArray(type_attr::kNew.name);
       TVMFFIAny fn_any = AnyView(fn).CopyToTVMFFIAny();
       TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index_, &attr, 
&fn_any));
       // Step 3. Register `__ffi_init__` if no explicit init is defined.
@@ -784,7 +783,7 @@ class ObjectDef : public ReflectionDefBase {
       }
     }
     // Step 4. Publish finality, which no other registered metadata exposes.
-    RegisterTypeAttrValue(type_index_, type_attr::kTypeFinal, 
Class::_type_final);
+    RegisterTypeAttrValue(type_index_, type_attr::kTypeFinal.name, 
Class::_type_final);
   }
 
   /*!
@@ -918,7 +917,8 @@ class ObjectDef : public ReflectionDefBase {
       static_assert(std::is_same_v<typename TSelf::ContainerType, Class>,
                     "TSelf::ContainerType must match ObjectDef's object type");
     }
-    return def_type_attr(type_attr::kConvert, 
&details::FFIConvertFromAnyViewToObjectRef<TSelf>);
+    return def_type_attr(type_attr::kConvert.name,
+                         &details::FFIConvertFromAnyViewToObjectRef<TSelf>);
   }
 
   /*!
@@ -951,7 +951,7 @@ class ObjectDef : public ReflectionDefBase {
                    std::forward<Extra>(extra)...);
     // Also mirror into __ffi_init__ TypeAttrColumn for runtime dispatch.
     const TVMFFITypeInfo* tinfo = TVMFFIGetTypeInfo(type_index_);
-    constexpr TVMFFIByteArray attr_name = AsByteArray(type_attr::kInit);
+    constexpr TVMFFIByteArray attr_name = AsByteArray(type_attr::kInit.name);
     for (int32_t i = 0; i < tinfo->num_methods; ++i) {
       if (tinfo->methods[i].name.size == attr_name.size &&
           std::strncmp(tinfo->methods[i].name.data, attr_name.data, 
attr_name.size) == 0) {
@@ -1050,7 +1050,7 @@ class ObjectDef : public ReflectionDefBase {
   int32_t type_index_;
   const char* type_key_;
   bool has_explicit_init_{false};
-  static constexpr const char* kInitMethodName = type_attr::kInit;
+  static constexpr const char* kInitMethodName = type_attr::kInit.name;
 };
 
 /*!
@@ -1075,6 +1075,12 @@ class TypeAttrDef : public ReflectionDefBase {
   explicit TypeAttrDef(ExtraArgs&&... extra_args)
       : type_index_(Class::_GetOrAllocRuntimeTypeIndex()), 
type_key_(Class::_type_key) {}
 
+  /*! \brief Register a named native callback using its key's declared 
adapter. */
+  template <auto Fn, typename T>
+  TypeAttrDef& native_fn_attr(AttrKey<T> key) {
+    return attr(key, T::template FromNative<Fn>());
+  }
+
   /*!
    * \brief Define a value type attribute.
    *
@@ -1131,7 +1137,15 @@ class TypeAttrDef : public ReflectionDefBase {
       static_assert(std::is_same_v<typename TSelf::ContainerType, Class>,
                     "TSelf::ContainerType must match TypeAttrDef's object 
type");
     }
-    return def(type_attr::kConvert, 
&details::FFIConvertFromAnyViewToObjectRef<TSelf>);
+    return def(type_attr::kConvert.name, 
&details::FFIConvertFromAnyViewToObjectRef<TSelf>);
+  }
+
+  /*! \brief Register a value using its key's declared type. */
+  template <typename T, typename Value>
+  TypeAttrDef& attr(AttrKey<T> key, Value&& value) {
+    T typed_value(std::forward<Value>(value));
+    RegisterTypeAttrValue(type_index_, key.name, std::move(typed_value));
+    return *this;
   }
 
   /*!
@@ -1145,7 +1159,8 @@ class TypeAttrDef : public ReflectionDefBase {
    * \return The TypeAttrDef object.
    */
   template <typename T>
-  TypeAttrDef& attr(const char* name, T value) {  // 
NOLINT(performance-unnecessary-value-param)
+  TypeAttrDef& attr(const char* name,
+                    T value) {  // NOLINT(performance-unnecessary-value-param)
     RegisterTypeAttrValue(type_index_, name, std::move(value));
     return *this;
   }
@@ -1167,6 +1182,12 @@ inline void EnsureTypeAttrColumn(std::string_view name) {
                                                  reinterpret_cast<const 
TVMFFIAny*>(&any_view)));
 }
 
+/*! \brief Ensure the erased column named by a typed key exists. */
+template <typename T>
+inline void EnsureTypeAttrColumn(AttrKey<T> key) {
+  EnsureTypeAttrColumn(key.name);
+}
+
 /// \cond Doxygen_Suppress
 namespace details {
 
diff --git a/src/ffi/container.cc b/src/ffi/container.cc
index 37f000ef..f36f5e74 100644
--- a/src/ffi/container.cc
+++ b/src/ffi/container.cc
@@ -26,7 +26,10 @@
 #include <tvm/ffi/container/map.h>
 #include <tvm/ffi/container/tensor.h>
 #include <tvm/ffi/device.h>
+#include <tvm/ffi/extra/dataclass.h>
+#include <tvm/ffi/extra/serialization.h>
 #include <tvm/ffi/function.h>
+#include <tvm/ffi/reflection/native_function.h>
 #include <tvm/ffi/reflection/registry.h>
 
 #include <sstream>
@@ -103,44 +106,57 @@ class MapForwardIterFunctor {
   ffi::MapObj::iterator end_;
 };
 
+int64_t BigIntAnyHash(const Any& value) {
+  return details::int_ops::BitcastToInt64(value.as_or_throw<BigInt>().hash());
+}
+
+bool BigIntAnyEqual(const Any& lhs, const Any& rhs) {
+  return lhs.as_or_throw<BigInt>() == rhs.as_or_throw<BigInt>();
+}
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
-  refl::EnsureTypeAttrColumn(refl::type_attr::kAnyHash);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kAnyEqual);
+  refl::EnsureTypeAttrColumn(type_attr::kAnyHash);
+  refl::EnsureTypeAttrColumn(type_attr::kAnyEqual);
   refl::ObjectDef<details::BigIntObj>()
-      .def_type_attr(refl::type_attr::kAnyHash,
-                     [](const BigInt& value) -> int64_t {
-                       return details::int_ops::BitcastToInt64(value.hash());
+      .def_type_attr(
+          type_attr::kAnyHash.name,
+          reflection::NativeFunctionView<int64_t(const 
Any&)>::FromNative<&BigIntAnyHash>())
+      .def_type_attr(type_attr::kAnyEqual.name,
+                     reflection::NativeFunctionView<bool(
+                         const Any&, const 
Any&)>::FromNative<&BigIntAnyEqual>())
+      .def_type_attr(type_attr::kEq.name,
+                     [](const Object* lhs, const Object* rhs, const Function&) 
{
+                       return AnyView(lhs).as_or_throw<BigInt>() ==
+                              AnyView(rhs).as_or_throw<BigInt>();
                      })
-      .def_type_attr(refl::type_attr::kAnyEqual,
-                     [](const BigInt& lhs, const BigInt& rhs) { return lhs == 
rhs; })
-      .def_type_attr(refl::type_attr::kEq, [](const BigInt& lhs, const BigInt& 
rhs,
-                                              const Function&) { return lhs == 
rhs; })
-      .def_type_attr(refl::type_attr::kCompare,
-                     [](const BigInt& lhs, const BigInt& rhs, const Function&) 
{
+      .def_type_attr(type_attr::kCompare.name,
+                     [](const Object* lhs, const Object* rhs, const Function&) 
{
                        return details::int_ops::CompareFallback(
-                           details::BigIntUnsafe::GetArrayView(lhs),
-                           details::BigIntUnsafe::GetArrayView(rhs));
-                     })
-      .def_type_attr(refl::type_attr::kHash,
-                     [](const BigInt& value, const Function&) -> int64_t {
-                       return details::int_ops::BitcastToInt64(value.hash());
+                           
details::BigIntUnsafe::GetArrayView(AnyView(lhs).as_or_throw<BigInt>()),
+                           
details::BigIntUnsafe::GetArrayView(AnyView(rhs).as_or_throw<BigInt>()));
                      })
-      .def_type_attr(refl::type_attr::kDataToJson,
-                     [](const BigInt& value) -> String {
+      .def_type_attr(
+          type_attr::kHash.name,
+          [](const Object* value, const Function&) -> int64_t {
+            return 
details::int_ops::BitcastToInt64(AnyView(value).as_or_throw<BigInt>().hash());
+          })
+      .def_type_attr(type_attr::kDataToJson.name,
+                     [](AnyView value) -> Any {
                        std::ostringstream stream;
-                       stream << value;
-                       return stream.str();
+                       stream << value.as_or_throw<BigInt>();
+                       return String(stream.str());
                      })
-      .def_type_attr(refl::type_attr::kDataFromJson, [](const String& data) -> 
BigInt {
+      .def_type_attr(type_attr::kDataFromJson.name, [](AnyView value) -> Any {
+        String data = value.as_or_throw<String>();
         std::istringstream stream(static_cast<std::string>(data));
-        BigInt value;
-        stream >> std::noskipws >> value;
+        BigInt result;
+        stream >> std::noskipws >> result;
         // A graph payload must be one complete decimal integer, without 
whitespace or junk.
         if (stream.fail() || !stream.eof()) {
           TVM_FFI_THROW(ValueError) << "Invalid decimal string for ffi.BigInt";
         }
-        return value;
+        return result;
       });
   refl::GlobalDef()
       .def_packed("ffi.Array",
diff --git a/src/ffi/extra/dataclass.cc b/src/ffi/extra/dataclass.cc
index b1d7ffbc..170adb3a 100644
--- a/src/ffi/extra/dataclass.cc
+++ b/src/ffi/extra/dataclass.cc
@@ -340,12 +340,12 @@ class ObjectDeepCopier : public 
ObjectGraphDFS<ObjectDeepCopier, CopyFrame, Any>
       default: {
         // Reflected object: shallow-copy and register
         const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(ti);
-        TVM_FFI_ICHECK((*column_)[ti] != nullptr)
+        auto copy_fn = type_attr::kShallowCopy.ValueFrom((*column_)[ti]);
+        TVM_FFI_ICHECK(copy_fn.has_value())
             << "Cannot deep copy object of type \""
             << std::string_view(type_info->type_key.data, 
type_info->type_key.size)
             << "\" because it is not copy-constructible";
-        Function copy_fn = (*column_)[ti].cast<Function>();
-        f.copy = copy_fn(f.obj);
+        f.copy = (*copy_fn)(f.obj);
         copy_map_[f.obj] = f.copy;
         break;
       }
@@ -884,13 +884,11 @@ class ReprPrinter : public ObjectGraphDFS<ReprPrinter, 
ReprFrame, std::string> {
       return true;
     }
     // Custom __ffi_repr__ hook
-    static refl::TypeAttrColumn repr_column(refl::type_attr::kRepr);
-    AnyView custom_repr = repr_column[ti];
-    if (custom_repr != nullptr) {
+    static refl::TypeAttrColumn repr_column(type_attr::kRepr);
+    if (auto repr_fn = type_attr::kRepr.ValueFrom(repr_column[ti])) {
       state_[obj] = State::kInProgress;
-      Function repr_fn = custom_repr.cast<Function>();
-      Function fn_repr = CreateFnRepr();
-      String r = repr_fn(obj, fn_repr).cast<String>();
+      TypedFunction<String(AnyView)> fn_repr = CreateFnRepr();
+      String r = (*repr_fn)(obj, fn_repr);
       std::string result(r.data(), r.size());
       if (show_addr_ && (ti == TypeIndex::kTVMFFIArray || ti == 
TypeIndex::kTVMFFIList ||
                          ti == TypeIndex::kTVMFFIMap || ti == 
TypeIndex::kTVMFFIDict)) {
@@ -1095,13 +1093,11 @@ class RecursiveHasher : public 
ObjectGraphDFS<RecursiveHasher, HashFrame, uint64
       return true;
     }
     // Check for custom __ffi_hash__ hook
-    static refl::TypeAttrColumn hash_column(refl::type_attr::kHash);
-    AnyView custom = hash_column[obj->type_index()];
-    if (custom != nullptr) {
+    static refl::TypeAttrColumn hash_column(type_attr::kHash);
+    if (auto hook = 
type_attr::kHash.ValueFrom(hash_column[obj->type_index()])) {
       on_stack_.insert(obj);
-      Function hook = custom.cast<Function>();
-      Function fn_hash = CreateFnHash();
-      int64_t r = hook(obj, fn_hash).cast<int64_t>();
+      TypedFunction<int64_t(AnyView)> fn_hash = CreateFnHash();
+      int64_t r = (*hook)(obj, fn_hash);
       uint64_t h = static_cast<uint64_t>(r);
       memo_[obj] = h;
       on_stack_.erase(obj);
@@ -1111,8 +1107,8 @@ class RecursiveHasher : public 
ObjectGraphDFS<RecursiveHasher, HashFrame, uint64
     // For reflected types (not built-in containers), error if the type has
     // __ffi_eq__ or __ffi_compare__ but no __ffi_hash__.
     if (ti >= TypeIndex::kTVMFFIStaticObjectEnd) {
-      static refl::TypeAttrColumn eq_column(refl::type_attr::kEq);
-      static refl::TypeAttrColumn cmp_column(refl::type_attr::kCompare);
+      static refl::TypeAttrColumn eq_column(type_attr::kEq);
+      static refl::TypeAttrColumn cmp_column(type_attr::kCompare);
       if (eq_column[obj->type_index()] != nullptr || 
cmp_column[obj->type_index()] != nullptr) {
         const TVMFFITypeInfo* info = TVMFFIGetTypeInfo(ti);
         TVM_FFI_THROW(ValueError)
@@ -1402,39 +1398,36 @@ class RecursiveComparer : public 
ObjectGraphDFS<RecursiveComparer, CompareFrame,
                                << String(lhs_info->type_key) << " vs "
                                << String(rhs_info->type_key);
     }
-    static refl::TypeAttrColumn eq_column(refl::type_attr::kEq);
-    static refl::TypeAttrColumn cmp_column(refl::type_attr::kCompare);
+    static refl::TypeAttrColumn eq_column(type_attr::kEq);
+    static refl::TypeAttrColumn cmp_column(type_attr::kCompare);
     int32_t ti = lhs_obj->type_index();
     AnyView custom_eq = eq_column[ti];
     AnyView custom_cmp = cmp_column[ti];
     if (eq_only_) {
-      if (custom_eq != nullptr) {
+      if (auto hook = type_attr::kEq.ValueFrom(custom_eq)) {
         auto pair = std::make_pair(lhs_obj, rhs_obj);
         on_stack_.insert(pair);
-        Function hook = custom_eq.cast<Function>();
-        Function fn_eq = CreateFnEq();
-        bool result = hook(lhs_obj, rhs_obj, fn_eq).cast<bool>();
+        TypedFunction<bool(AnyView, AnyView)> fn_eq = CreateFnEq();
+        bool result = (*hook)(lhs_obj, rhs_obj, fn_eq);
         on_stack_.erase(pair);
         *out = result ? 0 : 1;
         return true;
       }
-      if (custom_cmp != nullptr) {
+      if (auto hook = type_attr::kCompare.ValueFrom(custom_cmp)) {
         auto pair = std::make_pair(lhs_obj, rhs_obj);
         on_stack_.insert(pair);
-        Function hook = custom_cmp.cast<Function>();
-        Function fn_cmp = CreateFnCompare();
-        int32_t result = hook(lhs_obj, rhs_obj, fn_cmp).cast<int32_t>();
+        TypedFunction<int32_t(AnyView, AnyView)> fn_cmp = CreateFnCompare();
+        int32_t result = (*hook)(lhs_obj, rhs_obj, fn_cmp);
         on_stack_.erase(pair);
         *out = result;
         return true;
       }
     } else {
-      if (custom_cmp != nullptr) {
+      if (auto hook = type_attr::kCompare.ValueFrom(custom_cmp)) {
         auto pair = std::make_pair(lhs_obj, rhs_obj);
         on_stack_.insert(pair);
-        Function hook = custom_cmp.cast<Function>();
-        Function fn_cmp = CreateFnCompare();
-        int32_t result = hook(lhs_obj, rhs_obj, fn_cmp).cast<int32_t>();
+        TypedFunction<int32_t(AnyView, AnyView)> fn_cmp = CreateFnCompare();
+        int32_t result = (*hook)(lhs_obj, rhs_obj, fn_cmp);
         on_stack_.erase(pair);
         *out = result;
         return true;
@@ -2138,7 +2131,7 @@ Function MakeInit(int32_t type_index) {
 void RegisterFFIInit(int32_t type_index) {
   namespace refl = ::tvm::ffi::reflection;
   Function auto_init_fn = MakeInit(type_index);
-  TVMFFIByteArray attr_name = refl::AsByteArray(refl::type_attr::kInit);
+  TVMFFIByteArray attr_name = refl::AsByteArray(type_attr::kInit.name);
   TVMFFIAny attr_value = AnyView(auto_init_fn).CopyToTVMFFIAny();
   TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index, &attr_name, 
&attr_value));
 }
@@ -2167,7 +2160,7 @@ void PyClassRegisterTypeAttrColumns(int32_t type_index, 
int32_t total_size) {
     ConstructPyClassFields(obj, type_info);
     return ObjectRef(details::ObjectUnsafe::ObjectPtrFromOwned<Object>(obj));
   });
-  TVMFFIByteArray attr_name = refl::AsByteArray(refl::type_attr::kNew);
+  TVMFFIByteArray attr_name = refl::AsByteArray(type_attr::kNew.name);
   TVMFFIAny attr_value = AnyView(new_fn).CopyToTVMFFIAny();
   TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index, &attr_name, 
&attr_value));
   // Step 3. Register `__ffi_shallow_copy__`
@@ -2197,7 +2190,7 @@ void PyClassRegisterTypeAttrColumns(int32_t type_index, 
int32_t total_size) {
         Object* obj = reinterpret_cast<Object*>(obj_ptr);
         return 
ObjectRef(details::ObjectUnsafe::ObjectPtrFromOwned<Object>(obj));
       });
-  TVMFFIByteArray copy_attr_name = 
refl::AsByteArray(refl::type_attr::kShallowCopy);
+  TVMFFIByteArray copy_attr_name = 
refl::AsByteArray(type_attr::kShallowCopy.name);
   TVMFFIAny copy_attr_value = AnyView(copy_fn).CopyToTVMFFIAny();
   TVM_FFI_CHECK_SAFE_CALL(TVMFFITypeRegisterAttr(type_index, &copy_attr_name, 
&copy_attr_value));
 }
@@ -2209,7 +2202,7 @@ void PyClassRegisterTypeAttrColumns(int32_t type_index, 
int32_t total_size) {
 // ============================================================================
 
 Any DeepCopy(const Any& value) {
-  static refl::TypeAttrColumn column(refl::type_attr::kShallowCopy);
+  static refl::TypeAttrColumn column(type_attr::kShallowCopy);
   ObjectDeepCopier copier(&column);
   return copier.Run(value);
 }
@@ -2263,13 +2256,13 @@ bool RecursiveGe(const Any& lhs, const Any& rhs) {
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
-  refl::EnsureTypeAttrColumn(refl::type_attr::kShallowCopy);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kRepr);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kHash);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kEq);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kCompare);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kNew);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kInit);
+  refl::EnsureTypeAttrColumn(type_attr::kShallowCopy);
+  refl::EnsureTypeAttrColumn(type_attr::kRepr);
+  refl::EnsureTypeAttrColumn(type_attr::kHash);
+  refl::EnsureTypeAttrColumn(type_attr::kEq);
+  refl::EnsureTypeAttrColumn(type_attr::kCompare);
+  refl::EnsureTypeAttrColumn(type_attr::kNew);
+  refl::EnsureTypeAttrColumn(type_attr::kInit);
   refl::GlobalDef()
       .def("ffi._RegisterFFIInit", RegisterFFIInit)
       .def("ffi.MakeFieldSetter", MakeFieldSetter)
diff --git a/src/ffi/extra/reflection_extra.cc 
b/src/ffi/extra/reflection_extra.cc
index 51efcd38..28f86628 100644
--- a/src/ffi/extra/reflection_extra.cc
+++ b/src/ffi/extra/reflection_extra.cc
@@ -21,6 +21,7 @@
  *
  * \brief Extra reflection registrations. *
  */
+#include <tvm/ffi/extra/dataclass.h>
 #include <tvm/ffi/extra/structural_key.h>
 #include <tvm/ffi/reflection/access_path.h>
 #include <tvm/ffi/reflection/accessor.h>
@@ -117,8 +118,8 @@ inline void AccessStepRegisterReflection() {
   //   kArrayItemMissing -> "[<missing:index>]"
   //   kMapItemMissing   -> "[<missing:<repr(key)>>]"
   refl::TypeAttrDef<AccessStepObj>().def(
-      refl::type_attr::kRepr,
-      [](const AccessStep& step, const ffi::Function& fn_repr) -> ffi::String {
+      type_attr::kRepr.name, [](const Object* self, const ffi::Function& 
fn_repr) -> ffi::String {
+        const auto* step = AnyView(self).as_or_throw<const AccessStepObj*>();
         std::ostringstream os;
         switch (step->kind) {
           case AccessKind::kAttr:
@@ -168,8 +169,8 @@ inline void AccessPathRegisterReflection() {
   // Register __ffi_repr__ for AccessPath: flatten via ToSteps() and walk the 
resulting vector.
   // Root (depth == 0) emits "<root>"; non-root nodes concatenate each step's 
fragment.
   refl::TypeAttrDef<AccessPathObj>().def(
-      refl::type_attr::kRepr,
-      [](const AccessPath& path, const ffi::Function& fn_repr) -> ffi::String {
+      type_attr::kRepr.name, [](const Object* self, const ffi::Function& 
fn_repr) -> ffi::String {
+        const auto* path = AnyView(self).as_or_throw<const AccessPathObj*>();
         Array<AccessStep> steps = path->ToSteps();
         std::ostringstream os;
         os << "<root>";
@@ -207,10 +208,8 @@ inline void StructuralKeyRegisterReflection() {
       .def_ro("key", &StructuralKeyObj::key)
       .def_ro("hash_i64", &StructuralKeyObj::hash_i64);
   refl::TypeAttrDef<StructuralKeyObj>()
-      .attr(type_attr::kAnyHash,
-            NativeFunction<int64_t(const 
Any&)>::FromNative<&StructuralKeyHash>())
-      .attr(type_attr::kAnyEqual,
-            NativeFunction<bool(const Any&, const 
Any&)>::FromNative<&StructuralKeyEqual>());
+      .native_fn_attr<&StructuralKeyHash>(type_attr::kAnyHash)
+      .native_fn_attr<&StructuralKeyEqual>(type_attr::kAnyEqual);
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/ffi/extra/serialization.cc b/src/ffi/extra/serialization.cc
index 046ab455..076ffb13 100644
--- a/src/ffi/extra/serialization.cc
+++ b/src/ffi/extra/serialization.cc
@@ -201,9 +201,9 @@ class ObjectGraphSerializer {
     if (obj->IsInstance<IntEnumObj>()) return static_cast<const 
EnumObj*>(obj)->_int_index;
     if (obj->IsInstance<EnumObj>()) return static_cast<const 
EnumObj*>(obj)->_str_index;
     static reflection::TypeAttrColumn data_to_json =
-        reflection::TypeAttrColumn(reflection::type_attr::kDataToJson);
-    if (data_to_json[value.type_index()] != nullptr) {
-      return data_to_json[value.type_index()].cast<Function>()(value);
+        reflection::TypeAttrColumn(type_attr::kDataToJson);
+    if (auto hook = 
type_attr::kDataToJson.ValueFrom(data_to_json[value.type_index()])) {
+      return (*hook)(value);
     }
     // NOTE: invariant: lhs and rhs are already the same type
     const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(value.type_index());
@@ -394,9 +394,9 @@ class ObjectGraphDeserializer {
       return EnumObj::_GetByStrIndex(type_index, data.cast<String>());
     }
     static reflection::TypeAttrColumn data_from_json =
-        reflection::TypeAttrColumn(reflection::type_attr::kDataFromJson);
-    if (data_from_json[type_index] != nullptr) {
-      return data_from_json[type_index].cast<Function>()(data);
+        reflection::TypeAttrColumn(type_attr::kDataFromJson);
+    if (auto hook = 
type_attr::kDataFromJson.ValueFrom(data_from_json[type_index])) {
+      return (*hook)(data);
     }
     // otherwise, we go over the fields and create the data.
     const TVMFFITypeInfo* type_info = TVMFFIGetTypeInfo(type_index);
@@ -489,8 +489,8 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def("ffi.ToJSONGraphString", ToJSONGraphString)
       .def("ffi.FromJSONGraph", FromJSONGraph)
       .def("ffi.FromJSONGraphString", FromJSONGraphString);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kDataToJson);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kDataFromJson);
+  refl::EnsureTypeAttrColumn(type_attr::kDataToJson);
+  refl::EnsureTypeAttrColumn(type_attr::kDataFromJson);
 }
 
 }  // namespace ffi
diff --git a/src/ffi/extra/structural_equal.cc 
b/src/ffi/extra/structural_equal.cc
index 21934b39..d61ab456 100644
--- a/src/ffi/extra/structural_equal.cc
+++ b/src/ffi/extra/structural_equal.cc
@@ -213,10 +213,11 @@ class StructEqualHandler {
   // clamp — the caller (CompareObject) handles that for the FreeVar case.
   bool CompareFields(const ObjectRef& lhs, const ObjectRef& rhs, const 
TVMFFITypeInfo* type_info) {
     static reflection::TypeAttrColumn custom_s_equal =
-        reflection::TypeAttrColumn(reflection::type_attr::kSEqual);
+        reflection::TypeAttrColumn(type_attr::kSEqual);
 
     bool success = true;
-    if (custom_s_equal[type_info->type_index] == nullptr) {
+    auto hook = 
type_attr::kSEqual.ValueFrom(custom_s_equal[type_info->type_index]);
+    if (!hook) {
       // We recursively compare the fields the object
       reflection::ForEachFieldInfoWithEarlyStop(type_info, [&](const 
TVMFFIFieldInfo* field_info) {
         // skip fields that are marked as structural eq hash ignore
@@ -291,9 +292,7 @@ class StructEqualHandler {
               return sub_success;
             });
       }
-      success = custom_s_equal[type_info->type_index]
-                    .cast<ffi::Function>()(lhs, rhs, s_equal_callback_)
-                    .cast<bool>();
+      success = (*hook)(lhs.get(), rhs.get(), s_equal_callback_);
     }
     return success;
   }
@@ -456,7 +455,7 @@ class StructEqualHandler {
   std::vector<reflection::AccessStep>* mismatch_lhs_reverse_path_ = nullptr;
   std::vector<reflection::AccessStep>* mismatch_rhs_reverse_path_ = nullptr;
   // lazily initialize custom equal function
-  ffi::Function s_equal_callback_ = nullptr;
+  ffi::TypedFunction<bool(AnyView, AnyView, int, AnyView)> s_equal_callback_ = 
nullptr;
   // map from lhs to rhs
   std::unordered_map<ObjectRef, ObjectRef, ObjectPtrHash, ObjectPtrEqual> 
equal_map_lhs_;
   // map from rhs to lhs
@@ -499,7 +498,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def("ffi.StructuralEqual", StructuralEqual::Equal)
       .def("ffi.GetFirstStructuralMismatch", 
StructuralEqual::GetFirstMismatch);
   // ensure the type attribute column is presented in the system even if it is 
empty.
-  refl::EnsureTypeAttrColumn(refl::type_attr::kSEqual);
+  refl::EnsureTypeAttrColumn(type_attr::kSEqual);
 }
 
 }  // namespace ffi
diff --git a/src/ffi/extra/structural_hash.cc b/src/ffi/extra/structural_hash.cc
index 34d1c0b5..ced299d3 100644
--- a/src/ffi/extra/structural_hash.cc
+++ b/src/ffi/extra/structural_hash.cc
@@ -179,10 +179,10 @@ class StructuralHashHandler {
   // callback). Does not touch the FreeVar def-region clamp — that lives
   // inline in HashObject's FreeVar branch, which wraps this helper.
   uint64_t HashFields(const ObjectRef& obj, const TVMFFITypeInfo* type_info, 
uint64_t init_hash) {
-    static reflection::TypeAttrColumn custom_s_hash =
-        reflection::TypeAttrColumn(reflection::type_attr::kSHash);
+    static reflection::TypeAttrColumn custom_s_hash = 
reflection::TypeAttrColumn(type_attr::kSHash);
 
-    if (custom_s_hash[type_info->type_index] == nullptr) {
+    auto hook = 
type_attr::kSHash.ValueFrom(custom_s_hash[type_info->type_index]);
+    if (!hook) {
       // go over the content and hash the fields
       reflection::ForEachFieldInfo(type_info, [&](const TVMFFIFieldInfo* 
field_info) {
         // skip fields that are marked as structural eq hash ignore
@@ -233,9 +233,8 @@ class StructuralHashHandler {
               return 
static_cast<int64_t>(details::StableHashCombine(inner_init, hv));
             });
       }
-      init_hash = custom_s_hash[type_info->type_index]
-                      .cast<ffi::Function>()(obj, 
static_cast<int64_t>(init_hash), s_hash_callback_)
-                      .cast<uint64_t>();
+      init_hash = static_cast<uint64_t>(
+          (*hook)(obj.get(), static_cast<int64_t>(init_hash), 
s_hash_callback_));
     }
     return init_hash;
   }
@@ -382,7 +381,7 @@ class StructuralHashHandler {
   // graph node counter.
   uint32_t graph_node_counter_{0};
   // lazily initialize custom hash function
-  ffi::Function s_hash_callback_ = nullptr;
+  ffi::TypedFunction<int64_t(AnyView, int64_t, int)> s_hash_callback_ = 
nullptr;
   // map from lhs to rhs
   std::unordered_map<ObjectRef, uint64_t, ObjectPtrHash, ObjectPtrEqual> 
hash_memo_;
 };
@@ -405,7 +404,7 @@ static int64_t FFIStructuralHash(const Any& value, bool 
map_free_vars, bool skip
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::GlobalDef().def("ffi.StructuralHash", FFIStructuralHash);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kSHash);
+  refl::EnsureTypeAttrColumn(type_attr::kSHash);
 }
 
 }  // namespace ffi
diff --git a/src/ffi/extra/structural_mutate.cc 
b/src/ffi/extra/structural_mutate.cc
index f3361bc4..56bc3b18 100644
--- a/src/ffi/extra/structural_mutate.cc
+++ b/src/ffi/extra/structural_mutate.cc
@@ -433,43 +433,29 @@ TVM_FFI_STATIC_INIT_BLOCK() {
               const Array<Tuple<int32_t, Function, bool>>& callbacks) -> Any {
              return details::StructuralMutateExpected(std::move(root), 
callbacks).value();
            });
-  refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMutate);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMaybeInplaceMutate);
+  refl::EnsureTypeAttrColumn(type_attr::kStructuralMutate);
+  refl::EnsureTypeAttrColumn(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<details::BigIntObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
+      
.native_fn_attr<&details::MutateImmutableLeaf>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MutateImmutableLeaf>(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<details::StringObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
+      
.native_fn_attr<&details::MutateImmutableLeaf>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MutateImmutableLeaf>(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<details::BytesObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
+      
.native_fn_attr<&details::MutateImmutableLeaf>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MutateImmutableLeaf>(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<ArrayObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateArray>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            
FStructuralMutate::FromNative<&details::MaybeInplaceMutateArray>());
+      .native_fn_attr<&details::MutateArray>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MaybeInplaceMutateArray>(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<ListObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateList>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            FStructuralMutate::FromNative<&details::MaybeInplaceMutateList>());
+      .native_fn_attr<&details::MutateList>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MaybeInplaceMutateList>(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<MapObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateMap>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            FStructuralMutate::FromNative<&details::MaybeInplaceMutateMap>());
+      .native_fn_attr<&details::MutateMap>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MaybeInplaceMutateMap>(type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<DictObj>()
-      .attr(refl::type_attr::kStructuralMutate,
-            FStructuralMutate::FromNative<&details::MutateDict>())
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            FStructuralMutate::FromNative<&details::MaybeInplaceMutateDict>());
+      .native_fn_attr<&details::MutateDict>(type_attr::kStructuralMutate)
+      
.native_fn_attr<&details::MaybeInplaceMutateDict>(type_attr::kStructuralMaybeInplaceMutate);
 }
 
 }  // namespace ffi
diff --git a/src/ffi/extra/structural_visit.cc 
b/src/ffi/extra/structural_visit.cc
index 729a4a7c..0d2124e9 100644
--- a/src/ffi/extra/structural_visit.cc
+++ b/src/ffi/extra/structural_visit.cc
@@ -169,19 +169,15 @@ TVM_FFI_STATIC_INIT_BLOCK() {
               const Array<Tuple<int32_t, Function>>& callbacks) -> 
Optional<VisitInterrupt> {
              return details::StructuralVisitExpected(root, callbacks).value();
            });
-  refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
-  refl::TypeAttrDef<ArrayObj>().attr(
-      refl::type_attr::kStructuralVisit,
-      FStructuralVisit::FromNative<&details::VisitSeqContainer<ArrayObj>>());
-  refl::TypeAttrDef<ListObj>().attr(
-      refl::type_attr::kStructuralVisit,
-      FStructuralVisit::FromNative<&details::VisitSeqContainer<ListObj>>());
-  refl::TypeAttrDef<MapObj>().attr(
-      refl::type_attr::kStructuralVisit,
-      FStructuralVisit::FromNative<&details::VisitMapContainer<MapObj>>());
-  refl::TypeAttrDef<DictObj>().attr(
-      refl::type_attr::kStructuralVisit,
-      FStructuralVisit::FromNative<&details::VisitMapContainer<DictObj>>());
+  refl::EnsureTypeAttrColumn(type_attr::kStructuralVisit);
+  
refl::TypeAttrDef<ArrayObj>().native_fn_attr<&details::VisitSeqContainer<ArrayObj>>(
+      type_attr::kStructuralVisit);
+  
refl::TypeAttrDef<ListObj>().native_fn_attr<&details::VisitSeqContainer<ListObj>>(
+      type_attr::kStructuralVisit);
+  
refl::TypeAttrDef<MapObj>().native_fn_attr<&details::VisitMapContainer<MapObj>>(
+      type_attr::kStructuralVisit);
+  
refl::TypeAttrDef<DictObj>().native_fn_attr<&details::VisitMapContainer<DictObj>>(
+      type_attr::kStructuralVisit);
 }
 
 }  // namespace ffi
diff --git a/src/ffi/object.cc b/src/ffi/object.cc
index 324fe1fd..004e5c5d 100644
--- a/src/ffi/object.cc
+++ b/src/ffi/object.cc
@@ -686,7 +686,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def_ro("value", &ffi::EnumObj::_str_index, "Canonical string index.", 
refl::init(false),
               refl::AttachFieldFlag::SEqHashIgnore())
       .def_convert<ffi::StrEnum>();
-  refl::EnsureTypeAttrColumn(refl::type_attr::kEnumState);
+  refl::EnsureTypeAttrColumn(ffi::type_attr::kEnumState);
   refl::GlobalDef()
       .def_method("ffi.GetRegisteredTypeKeys",
                   []() -> ffi::Array<ffi::String> {
diff --git a/src/ffi/testing/testing.cc b/src/ffi/testing/testing.cc
index 319c8347..31f1b9a4 100644
--- a/src/ffi/testing/testing.cc
+++ b/src/ffi/testing/testing.cc
@@ -30,6 +30,7 @@
 #include <tvm/ffi/dtype.h>
 #include <tvm/ffi/enum.h>
 #include <tvm/ffi/extra/c_env_api.h>
+#include <tvm/ffi/extra/dataclass.h>
 #include <tvm/ffi/extra/structural_visit.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/optional.h>
@@ -113,7 +114,8 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   // silence clang-tidy's bugprone-unused-raii since the RAII finalisation is 
the point.
   refl::ObjectDef<TestEnumVariantObj>(refl::init(false));  // 
NOLINT(bugprone-unused-raii)
   refl::TypeAttrDef<TestEnumVariantObj>().def_convert<TestEnumVariant>();
-  refl::EnumDef<TestEnumVariantObj>("Alpha").set_attr("code", int64_t{10});
+  constexpr refl::AttrKey<int64_t> code{"code"};
+  refl::EnumDef<TestEnumVariantObj>("Alpha").set_attr(code, int64_t{10});
   refl::EnumDef<TestEnumVariantObj>("Beta").set_attr("code", int64_t{20});
 }
 
@@ -176,16 +178,17 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def_ro("pattern", &TestVisitRegionsObj::pattern, 
refl::AttachFieldFlag::SEqHashDefPattern())
       .def_ro("ordinary", &TestVisitRegionsObj::ordinary);
   refl::ObjectDef<TestVisitHookObj>().def_ro("child", 
&TestVisitHookObj::child);
-  refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
-  refl::TypeAttrDef<TestVisitHookObj>().def(
-      refl::type_attr::kStructuralVisit,
-      [](StructuralVisitor visitor, const TestVisitHookObj* self) {
+  refl::EnsureTypeAttrColumn(type_attr::kStructuralVisit);
+  TypedFunction<Optional<VisitInterrupt>(StructuralVisitorObj*, AnyView)> hook 
=
+      [](StructuralVisitorObj* visitor, AnyView value) {
+        const auto* self = value.as_or_throw<const TestVisitHookObj*>();
         auto result = visitor->WithDefRegionKind(kTVMFFIDefRegionKindSimple,
                                                  [&] { return 
visitor->Visit(self->child); });
         if (result.has_value()) return result;
         // This sibling observes the region restored after the scoped child 
visit.
         return visitor->Visit(int64_t{4});
-      });
+      };
+  refl::TypeAttrDef<TestVisitHookObj>().attr(type_attr::kStructuralVisit, 
FStructuralVisit(hook));
   refl::GlobalDef().def("testing.make_visit_region_graph", [](bool with_hook) 
-> Any {
     ObjectRef fields(make_object<TestVisitRegionsObj>());
     if (with_hook) return ObjectRef(make_object<TestVisitHookObj>(fields));
@@ -1021,27 +1024,26 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   // Register __ffi_hash__ for TestCustomHash: only hashes `key`, ignores 
`label`.
   refl::TypeAttrDef<TestCustomHashObj>().def(
-      refl::type_attr::kHash, [](const Object* self, const Function& fn_hash) 
-> int64_t {
+      type_attr::kHash.name, [](const Object* self, const Function& fn_hash) 
-> int64_t {
         auto* obj = static_cast<const TestCustomHashObj*>(self);
         return fn_hash(AnyView(obj->key)).cast<int64_t>();
       });
   // Register __ffi_hash__ for TestCustomCompare: only hashes `key`, 
consistent with eq/compare.
   refl::TypeAttrDef<TestCustomCompareObj>().def(
-      refl::type_attr::kHash, [](const Object* self, const Function& fn_hash) 
-> int64_t {
+      type_attr::kHash.name, [](const Object* self, const Function& fn_hash) 
-> int64_t {
         auto* obj = static_cast<const TestCustomCompareObj*>(self);
         return fn_hash(AnyView(obj->key)).cast<int64_t>();
       });
   // Register __ffi_eq__ for TestCustomCompare: compares only `key`.
   refl::TypeAttrDef<TestCustomCompareObj>().def(
-      refl::type_attr::kEq,
-      [](const Object* lhs, const Object* rhs, const Function& fn_eq) -> bool {
+      type_attr::kEq.name, [](const Object* lhs, const Object* rhs, const 
Function& fn_eq) -> bool {
         auto* a = static_cast<const TestCustomCompareObj*>(lhs);
         auto* b = static_cast<const TestCustomCompareObj*>(rhs);
         return fn_eq(AnyView(a->key), AnyView(b->key)).cast<bool>();
       });
   // Register __ffi_compare__ for TestCustomCompare: three-way ordering on 
`key`.
   refl::TypeAttrDef<TestCustomCompareObj>().def(
-      refl::type_attr::kCompare,
+      type_attr::kCompare.name,
       [](const Object* lhs, const Object* rhs, const Function& fn_cmp) -> 
int32_t {
         auto* a = static_cast<const TestCustomCompareObj*>(lhs);
         auto* b = static_cast<const TestCustomCompareObj*>(rhs);
@@ -1050,8 +1052,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   // Register __ffi_eq__ for TestEqWithoutHash: deliberately no __ffi_hash__.
   // This exercises the RecursiveHash guard that rejects eq-without-hash types.
   refl::TypeAttrDef<TestEqWithoutHashObj>().def(
-      refl::type_attr::kEq,
-      [](const Object* lhs, const Object* rhs, const Function& fn_eq) -> bool {
+      type_attr::kEq.name, [](const Object* lhs, const Object* rhs, const 
Function& fn_eq) -> bool {
         auto* a = static_cast<const TestEqWithoutHashObj*>(lhs);
         auto* b = static_cast<const TestEqWithoutHashObj*>(rhs);
         return fn_eq(AnyView(a->key), AnyView(b->key)).cast<bool>();
diff --git a/tests/cpp/extra/test_serialization.cc 
b/tests/cpp/extra/test_serialization.cc
index 4cf086ba..e130e1a2 100644
--- a/tests/cpp/extra/test_serialization.cc
+++ b/tests/cpp/extra/test_serialization.cc
@@ -74,11 +74,15 @@ class SerializationStrEnum : public StrEnum {
                                                 SerializationStrEnumObj);
 };
 
+constexpr reflection::AttrKey<reflection::NativeFunctionView<int(int)>> 
kEnumIncrement{
+    "test.enum.increment"};
+int EnumIncrement(int value) { return value + 1; }
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::ObjectDef<SerializationEnumObj>(refl::init(false));  // 
NOLINT(bugprone-unused-raii)
   refl::TypeAttrDef<SerializationEnumObj>().def_convert<SerializationEnum>();
-  refl::EnumDef<SerializationEnumObj>("Alpha");  // 
NOLINT(bugprone-unused-raii)
+  
refl::EnumDef<SerializationEnumObj>("Alpha").set_native_fn_attr<&EnumIncrement>(kEnumIncrement);
 
   
refl::ObjectDef<SerializationIntEnumObj>(refl::init(false)).def_convert<SerializationIntEnum>();
   refl::EnumDef<SerializationIntEnumObj>("Seven", 7);  // 
NOLINT(bugprone-unused-raii)
@@ -100,6 +104,13 @@ TEST(Serialization, EnumSingletonIdentity) {
   EXPECT_TRUE(StructuralEqual()(ToJSONGraph(original), expected));
   Enum restored = FromJSONGraph(expected).cast<Enum>();
   EXPECT_TRUE(restored.same_as(original));
+  reflection::TypeAttrColumn states(type_attr::kEnumState);
+  EnumState state = states[original->type_index()].as_or_throw<EnumState>();
+  auto hook = 
kEnumIncrement.ValueFrom(state->attrs.at(kEnumIncrement.name).at(restored));
+  EXPECT_TRUE(hook.has_value());
+  if (hook) {
+    EXPECT_EQ((*hook)(4), 5);
+  }
 }
 
 TEST(Serialization, PayloadEnumSingletonIdentity) {
diff --git a/tests/cpp/extra/test_structural_mutate.cc 
b/tests/cpp/extra/test_structural_mutate.cc
index 348f94be..6973eb94 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -298,13 +298,12 @@ class TNestedMapHookObj : public Object {
   static void RegisterReflection() {
     namespace refl = tvm::ffi::reflection;
     refl::ObjectDef<TNestedMapHookObj>().def_rw("field", 
&TNestedMapHookObj::field);
-    refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMutate);
-    refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMaybeInplaceMutate);
+    refl::EnsureTypeAttrColumn(type_attr::kStructuralMutate);
+    refl::EnsureTypeAttrColumn(type_attr::kStructuralMaybeInplaceMutate);
     refl::TypeAttrDef<TNestedMapHookObj>()
-        .attr(refl::type_attr::kStructuralMutate,
-              
FStructuralMutate::FromNative<&TNestedMapHookObj::StructuralMutate>())
-        .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-              
FStructuralMutate::FromNative<&TNestedMapHookObj::MaybeInplaceMutate>());
+        
.native_fn_attr<&TNestedMapHookObj::StructuralMutate>(type_attr::kStructuralMutate)
+        .native_fn_attr<&TNestedMapHookObj::MaybeInplaceMutate>(
+            type_attr::kStructuralMaybeInplaceMutate);
   }
 
   static constexpr bool _type_mutable = true;
diff --git a/tests/cpp/test_reflection.cc b/tests/cpp/test_reflection.cc
index 2ea12f98..6fd600c9 100644
--- a/tests/cpp/test_reflection.cc
+++ b/tests/cpp/test_reflection.cc
@@ -25,6 +25,7 @@
 #include <tvm/ffi/reflection/access_path.h>
 #include <tvm/ffi/reflection/accessor.h>
 #include <tvm/ffi/reflection/creator.h>
+#include <tvm/ffi/reflection/enum_def.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ffi/string.h>
 
@@ -34,7 +35,7 @@
 
 /*! \brief Look up __ffi_init__ from the TypeAttrColumn (not the method 
table). */
 static tvm::ffi::Function GetInitAttr(const char* type_key) {
-  static tvm::ffi::reflection::TypeAttrColumn 
col(tvm::ffi::reflection::type_attr::kInit);
+  static tvm::ffi::reflection::TypeAttrColumn col(tvm::ffi::type_attr::kInit);
   return col[tvm::ffi::TypeKeyToIndex(type_key)].cast<tvm::ffi::Function>();
 }
 
@@ -43,6 +44,8 @@ namespace {
 using namespace tvm::ffi;
 using namespace tvm::ffi::testing;
 
+constexpr reflection::AttrKey<bool> kFlag{"test.attr.typed.flag"};
+
 struct TestObjA : public Object {
   int64_t x;
   int64_t y;
@@ -94,6 +97,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       .def_type_attr("test.attr.object.int", int64_t{7})
       .def_type_attr("test.attr.object.bool", true)
       .def_type_attr("test.attr.object.func", [](int64_t value) -> int64_t { 
return value + 1; });
+  refl::TypeAttrDef<TestObjA>().attr(kFlag, false);
   refl::ObjectDef<TestObjADerived>()
       .def(refl::init<int64_t, int64_t, int64_t>())
       .def_ro("z", &TestObjADerived::z);
@@ -328,6 +332,23 @@ TEST(Reflection, TypeAttrColumn) {
   EXPECT_EQ(size_attr[TIntObj::RuntimeTypeIndex()].cast<int>(), 
sizeof(TIntObj));
 }
 
+TEST(Reflection, AttrKey) {
+  namespace refl = reflection;
+  refl::TypeAttrColumn column(kFlag);
+  auto flag = kFlag.ValueFrom(column[TestObjA::RuntimeTypeIndex()]);
+  EXPECT_EQ(flag, std::optional<bool>(false));
+  EXPECT_FALSE(kFlag.ValueFrom(column[-1]).has_value());
+  EXPECT_THROW(kFlag.ValueFrom(AnyView(1)), Error);
+
+  // Raw names retain dynamic value storage alongside typed registration.
+  constexpr refl::AttrKey<int64_t> count{"test.attr.typed.count"};
+  refl::TypeAttrDef<TestObjA>().attr(count, int64_t{7});
+  refl::TypeAttrDef<TestObjADerived>().attr(count.name, Any("dynamic"));
+  refl::TypeAttrColumn counts(count);
+  EXPECT_EQ(count.ValueFrom(counts[TestObjA::RuntimeTypeIndex()]), 7);
+  EXPECT_THROW(count.ValueFrom(counts[TestObjADerived::RuntimeTypeIndex()]), 
Error);
+}
+
 TEST(Reflection, TypeAttrColumnBeginIndex) {
   // Get the column and verify begin_index
   TVMFFIByteArray attr_name = {"test.size", 
std::char_traits<char>::length("test.size")};
@@ -377,7 +398,7 @@ TEST(Reflection, TypeAttrDefDirectValues) {
 }
 
 TEST(Reflection, ObjectDefPublishesTypeFinal) {
-  reflection::TypeAttrColumn final_attr(reflection::type_attr::kTypeFinal);
+  reflection::TypeAttrColumn final_attr(type_attr::kTypeFinal);
 
   EXPECT_FALSE(final_attr[TestObjA::RuntimeTypeIndex()].cast<bool>());
   EXPECT_TRUE(final_attr[TestObjADerived::RuntimeTypeIndex()].cast<bool>());
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index ad01c4a6..d301e309 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -25,6 +25,9 @@
 #include <tvm/ffi/container/map.h>
 #include <tvm/ffi/device.h>
 #include <tvm/ffi/dtype.h>
+#include <tvm/ffi/extra/serialization.h>
+#include <tvm/ffi/extra/structural_equal.h>
+#include <tvm/ffi/extra/structural_hash.h>
 #include <tvm/ffi/extra/structural_mutate.h>
 #include <tvm/ffi/extra/structural_visit.h>
 #include <tvm/ffi/memory.h>
@@ -100,18 +103,17 @@ inline void TIntObj::RegisterReflection() {
   refl::TypeAttrDef<TIntObj>()
       .def("test.GetValue", &TIntObj::GetValue)
       .attr("test.size", sizeof(TIntObj))
-      .attr(refl::type_attr::kAnyHash,
-            refl::NativeFunction<int64_t(const 
Any&)>::FromNative<&TInt::CustomAnyHash>())
-      .attr(
-          refl::type_attr::kAnyEqual,
-          refl::NativeFunction<bool(const Any&, const 
Any&)>::FromNative<&TInt::CustomAnyEqual>());
+      .native_fn_attr<&TInt::CustomAnyHash>(type_attr::kAnyHash)
+      .native_fn_attr<&TInt::CustomAnyEqual>(type_attr::kAnyEqual);
   // custom json serialization
   refl::TypeAttrDef<TIntObj>()
-      .def(refl::type_attr::kDataToJson,
-           [](const TIntObj* self) -> Map<String, Any> {
+      .def(type_attr::kDataToJson.name,
+           [](AnyView value) -> Any {
+             const auto* self = value.as_or_throw<const TIntObj*>();
              return Map<String, Any>{{"value", self->value}};
            })
-      .def(refl::type_attr::kDataFromJson, [](Map<String, Any> json_obj) -> 
TInt {
+      .def(type_attr::kDataFromJson.name, [](AnyView value) -> Any {
+        auto json_obj = value.as_or_throw<Map<String, Any>>();
         return TInt(json_obj["value"].cast<int64_t>());
       });
 }
@@ -152,8 +154,8 @@ inline void TFloatObj::RegisterReflection() {
       .def("sub", [](const TFloatObj* self, double other) -> double { return 
self->value - other; })
       .def("add", &TFloatObj::Add, "add method");
   refl::TypeAttrDef<TFloatObj>()
-      .def(refl::type_attr::kAnyHash, &TFloat::CustomAnyHash)
-      .def(refl::type_attr::kAnyEqual, &TFloat::CustomAnyEqual);
+      .def(type_attr::kAnyHash.name, &TFloat::CustomAnyHash)
+      .def(type_attr::kAnyEqual.name, &TFloat::CustomAnyEqual);
 }
 
 class TPrimExprObj : public Object {
@@ -276,10 +278,9 @@ class TMutatePairObj : public Object {
     refl::ObjectDef<TMutatePairObj>()
         .def_ro("lhs", &TMutatePairObj::lhs)
         .def_ro("rhs", &TMutatePairObj::rhs);
-    refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMutate);
-    refl::TypeAttrDef<TMutatePairObj>().attr(
-        refl::type_attr::kStructuralMutate,
-        
FStructuralMutate::FromNative<&TMutatePairObj::StructuralMutateHook>());
+    refl::EnsureTypeAttrColumn(type_attr::kStructuralMutate);
+    
refl::TypeAttrDef<TMutatePairObj>().native_fn_attr<&TMutatePairObj::StructuralMutateHook>(
+        type_attr::kStructuralMutate);
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
@@ -432,10 +433,9 @@ class TFuncObj : public Object {
         .def_ro("params", &TFuncObj::params, 
refl::AttachFieldFlag::SEqHashDefPattern())
         .def_ro("body", &TFuncObj::body)
         .def_ro("comment", &TFuncObj::comment, 
refl::AttachFieldFlag::SEqHashIgnore());
-    refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
-    refl::TypeAttrDef<TFuncObj>().attr(
-        refl::type_attr::kStructuralVisit,
-        FStructuralVisit::FromNative<&TFuncObj::StructuralVisitHook>());
+    refl::EnsureTypeAttrColumn(type_attr::kStructuralVisit);
+    
refl::TypeAttrDef<TFuncObj>().native_fn_attr<&TFuncObj::StructuralVisitHook>(
+        type_attr::kStructuralVisit);
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
@@ -461,7 +461,7 @@ class TCustomFuncObj : public Object {
       : params(params), body(body), comment(comment) {}
 
   bool SEqual(const TCustomFuncObj* other,
-              ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> cmp) 
const {
+              const ffi::TypedFunction<bool(AnyView, AnyView, int, AnyView)>& 
cmp) const {
     if (!cmp(params, other->params, true, "params")) {
       return false;
     }
@@ -471,7 +471,8 @@ class TCustomFuncObj : public Object {
     return true;
   }
 
-  int64_t SHash(int64_t init_hash, ffi::TypedFunction<int64_t(AnyView, 
int64_t, bool)> hash) const {
+  int64_t SHash(int64_t init_hash,
+                const ffi::TypedFunction<int64_t(AnyView, int64_t, int)>& 
hash) const {
     int64_t hash_value = init_hash;
     hash_value = hash(params, hash_value, true);
     hash_value = hash(body, hash_value, false);
@@ -485,8 +486,8 @@ class TCustomFuncObj : public Object {
         .def_ro("body", &TCustomFuncObj::body)
         .def_ro("comment", &TCustomFuncObj::comment);
     refl::TypeAttrDef<TCustomFuncObj>()
-        .def(refl::type_attr::kSEqual, &TCustomFuncObj::SEqual)
-        .def(refl::type_attr::kSHash, &TCustomFuncObj::SHash);
+        .def(type_attr::kSEqual.name, &TCustomFuncObj::SEqual)
+        .def(type_attr::kSHash.name, &TCustomFuncObj::SHash);
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;

Reply via email to