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 0af62893 [FEAT] Add typed native reflection hooks with ABI dispatch 
(#821)
0af62893 is described below

commit 0af62893f8033b65c3f424a0dbfd3138b5cbe5d4
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Sep 27 21:14:34 2026 -0400

    [FEAT] Add typed native reflection hooks with ABI dispatch (#821)
    
    Add `NativeFunctionView<Expected<R>(Args...)>` for reflection hooks with
    a context-free native ABI adapter and a borrowed packed-function
    fallback. Add an `Any`-backed owning `NativeFunction` with TypeTraits
    support.
    
    Use typed native hooks for structural mutation and visitation while
    retaining the raw `TVMFFIAny` vtable ABI. Carry `Expected` results
    through the sequence, map, and registered hook bodies.
    
    Native targets bind at compile time with `FromNative<&Fn>()`; packed
    hooks remain available for frontend registration.
---
 include/tvm/ffi/any.h                        |   3 +-
 include/tvm/ffi/expected.h                   |  55 -----
 include/tvm/ffi/extra/structural_mutate.h    |  61 +++---
 include/tvm/ffi/extra/structural_visit.h     |  36 ++--
 include/tvm/ffi/reflection/native_function.h | 310 +++++++++++++++++++++++++++
 include/tvm/ffi/reflection/registry.h        |   2 +-
 src/ffi/extra/structural_mutate.cc           | 119 +++++-----
 src/ffi/extra/structural_visit.cc            |  46 ++--
 tests/cpp/extra/test_structural_mutate.cc    |  17 +-
 tests/cpp/test_expected_checks.cc            |  17 +-
 tests/cpp/test_native_function_view.cc       | 100 +++++++++
 tests/cpp/testing_object.h                   |  18 +-
 12 files changed, 561 insertions(+), 223 deletions(-)

diff --git a/include/tvm/ffi/any.h b/include/tvm/ffi/any.h
index bd077b24..6226c749 100644
--- a/include/tvm/ffi/any.h
+++ b/include/tvm/ffi/any.h
@@ -337,7 +337,7 @@ class Any {
    * \tparam T The value type of the other
    */
   template <typename T, typename = 
std::enable_if_t<TypeTraits<T>::convert_enabled>>
-  Any(T other) {  // NOLINT(*)
+  TVM_FFI_INLINE Any(T other) {  // NOLINT(*)
     TypeTraits<T>::MoveToAny(std::move(other), &data_);
   }
   /*!
@@ -562,6 +562,7 @@ static_assert(sizeof(Any) == sizeof(TVMFFIAny));
 // or non-trivial destructor on AnyView would silently regress this
 // calling convention — catch it here at compile time.
 static_assert(std::is_trivially_copyable_v<AnyView>, "AnyView must be 
trivially copyable.");
+static_assert(std::is_standard_layout_v<AnyView>, "AnyView must have standard 
layout.");
 
 namespace details {
 
diff --git a/include/tvm/ffi/expected.h b/include/tvm/ffi/expected.h
index 420e0d29..f96de25b 100644
--- a/include/tvm/ffi/expected.h
+++ b/include/tvm/ffi/expected.h
@@ -513,61 +513,6 @@ struct ExpectedUnsafe {
   }
 };
 
-/*!
- * \brief Error-only return proxy used by mutation early-return macros.
- *
- * An error has the same shape in every mutation return type, so this proxy
- * converts to the raw ``TVMFFIAny`` used by hooks or to any ``Expected<T>``.
- */
-class UnexpectedReturnHelper {
- public:
-  TVM_FFI_INLINE explicit UnexpectedReturnHelper(Unexpected<Error>&& value) 
noexcept
-      : value_(std::move(value)) {}
-
-  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
-  TVM_FFI_INLINE operator TVMFFIAny() && noexcept {
-    return ExpectedUnsafe::MoveToTVMFFIAny(Expected<Any>(std::move(value_)));
-  }
-
-  template <typename T>
-  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
-  TVM_FFI_INLINE operator Expected<T>() && noexcept {
-    return std::move(value_);
-  }
-
- private:
-  Unexpected<Error> value_;
-};
-
-/*!
- * \brief Expected-value return proxy used by visit early-return macros.
- * \tparam T The success type, fixed when the proxy stores its ``Expected<T>``.
- *
- * A return statement selects the raw ``TVMFFIAny`` conversion used by hooks or
- * the same ``Expected<T>`` type used by typed helpers. The payload-bearing
- * conversions are rvalue-qualified because handing it off is a single move; an
- * lvalue helper cannot accidentally transfer it twice. As with other 
moved-from
- * values, deliberately converting ``std::move(helper)`` twice remains caller
- * error.
- */
-template <typename T>
-class ExpectedReturnHelper {
- public:
-  TVM_FFI_INLINE explicit ExpectedReturnHelper(Expected<T>&& value) noexcept
-      : value_(std::move(value)) {}
-
-  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
-  TVM_FFI_INLINE operator TVMFFIAny() && noexcept {
-    return ExpectedUnsafe::MoveToTVMFFIAny(std::move(value_));
-  }
-
-  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
-  TVM_FFI_INLINE operator Expected<T>() && noexcept { return 
std::move(value_); }
-
- private:
-  Expected<T> value_;
-};
-
 }  // namespace details
 
 // TypeTraits specialization for Expected<T>
diff --git a/include/tvm/ffi/extra/structural_mutate.h 
b/include/tvm/ffi/extra/structural_mutate.h
index e0ae09a1..7bd0ef9b 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -91,7 +91,7 @@ class StructuralMutateEngine;
  *       already established the type; allocation failure and violated 
container invariants
  *       remain fatal.
  */
-using FStructuralMutate = TVMFFIAny (*)(StructuralMutatorObj* mutator, AnyView 
value) noexcept;
+using FStructuralMutateABI = TVMFFIAny (*)(StructuralMutatorObj* mutator, 
AnyView value) noexcept;
 
 /*!
  * \brief ABI callback type for looking up an identity substitution.
@@ -133,7 +133,7 @@ struct StructuralMutatorVTable {
    * \param value The borrowed value to mutate.
    * \return Raw ``TVMFFIAny`` carrying a replacement, the unchanged marker, 
or Error.
    */
-  FStructuralMutate mutate = nullptr;
+  FStructuralMutateABI mutate = nullptr;
   /*!
    * \brief Mutate a value, permitting an in-place implementation when it is 
safe.
    *
@@ -144,7 +144,7 @@ struct StructuralMutatorVTable {
    * The returned value may refer to the same object as \p value when the 
implementation mutates
    * that object in place.
    */
-  FStructuralMutate maybe_inplace_mutate = nullptr;
+  FStructuralMutateABI maybe_inplace_mutate = nullptr;
   /*!
    * \brief Look up the replacement for a variable identity.
    *
@@ -164,6 +164,10 @@ struct StructuralMutatorVTable {
   FStructuralVarRemapSet var_remap_set = nullptr;
 };
 
+/*! \brief Borrowed structural mutation hook at attribute lookup and call. */
+using FStructuralMutate =
+    
reflection::NativeFunctionView<Expected<UnchangedOr<Any>>(StructuralMutatorObj*,
 AnyView)>;
+
 namespace details {
 template <typename Parent>
 class StructuralMutateDynEngine;
@@ -423,9 +427,9 @@ namespace details {
 // Takes nothing on purpose. Naming the offending type in the message would 
keep the result live
 // across the predicted-not-taken guard in the hot path. The declared type is 
already present in
 // the source line to which the diagnostic points.
-TVM_FFI_COLD_CODE inline UnexpectedReturnHelper SMutateDeclaredTypeError() 
noexcept {
-  return UnexpectedReturnHelper(Unexpected(
-      Error("TypeError", "structural mutate result does not match the declared 
type", "")));
+TVM_FFI_COLD_CODE inline Unexpected<Error> SMutateDeclaredTypeError() noexcept 
{
+  return Unexpected(
+      Error("TypeError", "structural mutate result does not match the declared 
type", ""));
 }
 }  // namespace details
 
@@ -448,7 +452,7 @@ class StructuralMutatorObj : public Object {
    *       only when inplace_mode is InplaceMode::kAllow and the current value 
is uniquely owned.
    *       See \ref MutateExpected for the full permission and error semantics.
    *
-   * Use \ref TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN in Expected-returning helpers 
or raw hooks to
+   * Use \ref TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN in Expected-returning hooks 
and helpers to
    * preserve checked typed mutation results and the fixed mismatch 
diagnostic. At a throwing
    * boundary, an explicit cast may instead report its own TypeError:
    * \code{.cpp}
@@ -599,7 +603,7 @@ class StructuralMutatorObj : public Object {
    */
   TVM_FFI_COLD_CODE static Expected<Any> BadStructuralMutateHookError() 
noexcept {
     return Unexpected(
-        Error("TypeError", "__s_mutate__ must be an opaque function pointer or 
ffi.Function", ""));
+        Error("TypeError", "__s_mutate__ must be a direct hook pointer or 
ffi.Function", ""));
   }
 
   // Convention: the ABI boundary is a raw TVMFFIAny; mutation results inside 
a callback or hook
@@ -622,7 +626,7 @@ class StructuralMutatorObj : public Object {
     // dispatching into `value`, so both exits below name it here and nowhere 
else.
     TVMFFIAny result;
     if (TVM_FFI_PREDICT_TRUE(attr.type_index() == 
TypeIndex::kTVMFFIOpaquePtr)) {
-      result = 
(*reinterpret_cast<FStructuralMutate>(attr.cast<void*>()))(this, value);
+      result = 
(*reinterpret_cast<FStructuralMutateABI>(attr.cast<void*>()))(this, value);
     } else {
       result = DefaultMutateRawTail(value, attr);
     }
@@ -642,7 +646,7 @@ class StructuralMutatorObj : public Object {
   /*! \brief The cold remainder of DefaultMutateRaw: an ffi.Function hook, or 
no hook at all. */
   TVMFFIAny DefaultMutateRawTail(AnyView value, AnyView attr) noexcept {
     if (attr.type_index() != TypeIndex::kTVMFFINone) {
-      // Registered, but as an ffi.Function rather than an opaque pointer.
+      // Registered as a typed function pointer or ffi.Function.
       if (attr.type_index() == TypeIndex::kTVMFFIFunction) {
         return details::ExpectedUnsafe::MoveToTVMFFIAny(
             attr.cast<Function>().CallExpected<Any>(this, value));
@@ -707,7 +711,7 @@ class StructuralMutatorObj : public Object {
       // This is the engine dispatching into `value`; hooks propagate errors 
untouched, so the
       // node is named here. The fall-through re-dispatches the same node 
through
       // DefaultMutateRaw, which names it there instead -- exactly one frame 
either way.
-      TVMFFIAny result = 
(*reinterpret_cast<FStructuralMutate>(attr.cast<void*>()))(this, value);
+      TVMFFIAny result = 
(*reinterpret_cast<FStructuralMutateABI>(attr.cast<void*>()))(this, value);
       if (TVM_FFI_PREDICT_FALSE(result.type_index == TypeIndex::kTVMFFIError)) 
{
         return AttachVisitErrorContextRaw(result, value);
       }
@@ -896,15 +900,13 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
 
 namespace details {
 /// \cond Doxygen_Suppress
-// Return an error from the current raw or Expected mutation function.
-// The rvalue-only helper lets the enclosing return type select the 
representation.
-#define TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result)                   \
-  do {                                                                \
-    auto&& tvm_ffi_res_ = (Result);                                   \
-    if (TVM_FFI_PREDICT_FALSE(tvm_ffi_res_.is_err())) {               \
-      return ::tvm::ffi::details::UnexpectedReturnHelper(             \
-          ::tvm::ffi::Unexpected(::std::move(tvm_ffi_res_).error())); \
-    }                                                                 \
+// Return an error from the current Expected-returning mutation function.
+#define TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result)                     \
+  do {                                                                  \
+    auto&& tvm_ffi_res_ = (Result);                                     \
+    if (TVM_FFI_PREDICT_FALSE(tvm_ffi_res_.is_err())) {                 \
+      return ::tvm::ffi::Unexpected(::std::move(tvm_ffi_res_).error()); \
+    }                                                                   \
   } while (0)
 
 /// \endcond
@@ -928,16 +930,16 @@ namespace details {
  * \brief Unwrap a successful mutation result into a newly declared value or 
return its error.
  *
  * ``Type`` must be concrete; use a type alias when it contains a top-level 
comma. A type mismatch
- * returns ``TypeError`` through the surrounding raw or ``Expected`` function 
without throwing,
+ * returns ``TypeError`` through the surrounding ``Expected`` function without 
throwing,
  * reported with a fixed string. The check is omitted when the declared type 
subsumes the result's
- * success type. Its early returns work from either a raw ``TVMFFIAny`` hook 
or an
- * ``Expected<T>`` helper, including one with a different success type. This 
macro declares
+ * success type. Its early returns work from an ``Expected<T>`` hook or 
helper, including one
+ * with a different success type. This macro declares
  * ``Name`` into the enclosing scope and must be used in a braced block, never 
as an unbraced
  * control-flow body.
  *
  * Example:
  * \code{.cpp}
- * TVMFFIAny FooMutate(StructuralMutatorObj* mutator, AnyView value) noexcept {
+ * Expected<UnchangedOr<Any>> FooMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
  *   const FooNode* self =
  *       details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
FooNode>(value);
  *   constexpr InplaceMode inplace_mode = InplaceMode::kDisallow;
@@ -946,12 +948,12 @@ namespace details {
  *   TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Expr>, b,
  *                                     mutator->MutateExpected(self->b, 
inplace_mode));
  *   if (a.UnchangedOrSameAs(self->a) && b.UnchangedOrSameAs(self->b)) {
- *     return Unchanged().CopyToTVMFFIAny();
+ *     return Unchanged();
  *   }
  *   ObjectPtr<FooNode> copy = make_object<FooNode>(*self);
  *   copy->a = std::move(a).ValueOrUnchanged(std::move(copy->a));
  *   copy->b = std::move(b).ValueOrUnchanged(std::move(copy->b));
- *   return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(copy)));
+ *   return copy;
  * }
  * \endcode
  *
@@ -961,7 +963,7 @@ namespace details {
  * \param Type The concrete successful value type.
  * \param Name The name of the value declared in the enclosing scope.
  * \param ResultExpr An expression producing the ``Expected`` value to unwrap.
- * \sa Unchanged::CopyToTVMFFIAny
+ * \sa Unchanged
  */
 #define TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Type, Name, ResultExpr)              
                    \
   
TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN_IMPL_(TVM_FFI_STR_CONCAT(tvm_ffi_mutate_result_,
 __COUNTER__), \
@@ -1027,9 +1029,8 @@ class StructuralMapEngineBase : public 
StructuralMutatorObj {
     return Unexpected(Error("TypeError", "structural mutate: descent changed 
the node type", ""));
   }
 
-  TVM_FFI_COLD_CODE static details::UnexpectedReturnHelper 
VarRemapKeyTypeError() noexcept {
-    return details::UnexpectedReturnHelper(
-        Unexpected(Error("TypeError", "Variable-remap key must be an 
object-backed value", "")));
+  TVM_FFI_COLD_CODE static Unexpected<Error> VarRemapKeyTypeError() noexcept {
+    return Unexpected(Error("TypeError", "Variable-remap key must be an 
object-backed value", ""));
   }
   /// \endcond
 
diff --git a/include/tvm/ffi/extra/structural_visit.h 
b/include/tvm/ffi/extra/structural_visit.h
index d6d2ced7..ac638f8a 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -35,6 +35,7 @@
 #include <tvm/ffi/function_details.h>
 #include <tvm/ffi/optional.h>
 #include <tvm/ffi/reflection/accessor.h>
+#include <tvm/ffi/reflection/native_function.h>
 
 #include <cstddef>
 #include <exception>
@@ -99,7 +100,7 @@ class StructuralVisitorObj;
  * ``AnyView``. It returns a raw ``TVMFFIAny`` storing
  * ``Expected<Optional<VisitInterrupt>>``.
  */
-using FStructuralVisit = TVMFFIAny (*)(StructuralVisitorObj* visitor, AnyView 
value) noexcept;
+using FStructuralVisitABI = TVMFFIAny (*)(StructuralVisitorObj* visitor, 
AnyView value) noexcept;
 
 namespace details {
 
@@ -124,9 +125,13 @@ struct StructuralVisitorVTable {
    * failure, the returned ``TVMFFIAny`` stores ``Error``; on success, it 
stores
    * either None or ``VisitInterrupt``.
    */
-  FStructuralVisit visit = nullptr;
+  FStructuralVisitABI visit = nullptr;
 };
 
+/*! \brief Borrowed typed structural visit hook at attribute lookup and call. 
*/
+using FStructuralVisit = 
reflection::NativeFunctionView<Expected<Optional<VisitInterrupt>>(
+    StructuralVisitorObj*, AnyView)>;
+
 /*!
  * \brief Object node of a structural visitor.
  *
@@ -236,7 +241,7 @@ class StructuralVisitorObj : public Object {
     static reflection::TypeAttrColumn 
column(reflection::type_attr::kStructuralVisit);
     AnyView attr = column[value.type_index()];
     if (TVM_FFI_PREDICT_TRUE(attr.type_index() == 
TypeIndex::kTVMFFIOpaquePtr)) {
-      return (*reinterpret_cast<FStructuralVisit>(attr.cast<void*>()))(this, 
value);
+      return 
(*reinterpret_cast<FStructuralVisitABI>(attr.cast<void*>()))(this, value);
     }
     return DefaultVisitRawTail(value, attr);
   }
@@ -306,19 +311,6 @@ class StructuralVisitor : public ObjectRef {
 
 namespace details {
 
-/*!
- * \brief Return an optional visit result directly or wrap an Expected for 
return conversion.
- */
-template <typename T>
-TVM_FFI_INLINE auto VisitReturnHelper(T&& result) {
-  if constexpr (std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>,
-                               Optional<VisitInterrupt>>) {
-    return std::forward<T>(result);
-  } else {
-    return ExpectedReturnHelper(std::forward<T>(result));
-  }
-}
-
 /*!
  * \brief Return true when \p result carries a traversal-stopping interrupt.
  */
@@ -522,21 +514,21 @@ namespace details {
  * \brief Return from a visit hook if \p Result stops traversal.
  *
  * Propagates an ``Error`` or a ``VisitInterrupt`` out of the enclosing 
function
- * and otherwise falls through. An ``Optional<VisitInterrupt>`` result is 
returned
- * directly. An ``Expected`` result uses an rvalue-only proxy to select the 
return
- * representation for raw ``TVMFFIAny`` hooks or typed ``Expected`` helpers.
+ * and otherwise falls through. The stopping result is moved directly into the
+ * enclosing Optional or Expected return type.
  *
  * A registered ``__s_visit__`` hook is one line per traversed field followed 
by
  * the terminal return. A field skipped on purpose is guarded by a condition 
and
  * carries a ``// skips:`` note saying why.
  *
  * \code{.cpp}
- * TVMFFIAny FooVisit(StructuralVisitorObj* visitor, AnyView value) noexcept {
+ * Expected<Optional<VisitInterrupt>> FooVisit(StructuralVisitorObj* visitor,
+ *                                             AnyView value) noexcept {
  *   const FooNode* self =
  *       details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
FooNode>(value);
  *   TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->a));
  *   TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->b));
- *   return AnyView(nullptr).CopyToTVMFFIAny();
+ *   return Optional<VisitInterrupt>(std::nullopt);
  * }
  * \endcode
  *
@@ -547,7 +539,7 @@ namespace details {
     auto&& tvm_ffi_res_ = (Result);                                            
   \
     if (TVM_FFI_PREDICT_FALSE(                                                 
   \
             
::tvm::ffi::details::StructuralVisitNeedEarlyReturn(tvm_ffi_res_))) { \
-      return 
::tvm::ffi::details::VisitReturnHelper(::std::move(tvm_ffi_res_));   \
+      return ::std::move(tvm_ffi_res_);                                        
   \
     }                                                                          
   \
   } while (0)
 
diff --git a/include/tvm/ffi/reflection/native_function.h 
b/include/tvm/ffi/reflection/native_function.h
new file mode 100644
index 00000000..d42562f0
--- /dev/null
+++ b/include/tvm/ffi/reflection/native_function.h
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*!
+ * \file tvm/ffi/reflection/native_function.h
+ * \brief Borrowed and owning typed hooks with native ABI dispatch and packed 
fallback.
+ */
+#ifndef TVM_FFI_REFLECTION_NATIVE_FUNCTION_H_
+#define TVM_FFI_REFLECTION_NATIVE_FUNCTION_H_
+
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/expected.h>
+#include <tvm/ffi/function.h>
+
+#include <type_traits>
+#include <utility>
+
+namespace tvm {
+namespace ffi {
+namespace reflection {
+
+template <typename Signature>
+class NativeFunctionView;
+
+/*!
+ * \brief Borrowed typed function for reflection attributes with native ABI 
dispatch.
+ *
+ * Bind a named `Expected<R>(Args...) noexcept` native target with 
`FromNative<&Fn>()`. The view
+ * stores its C ABI-compatible `TVMFFIAny(Args...) noexcept` adapter pointer.
+ * A borrowed `ffi::Function` supports frontend registration and packed 
fallback. This approach
+ * provides efficient native calls for most natively registered hooks while 
preserving flexibility
+ * in how functions are registered.
+ *
+ * \code{.cpp}
+ * TVM_FFI_INLINE tvm::ffi::Expected<int> Increment(int value) noexcept { 
return value + 1; }
+ * using FIncrement = 
tvm::ffi::reflection::NativeFunctionView<tvm::ffi::Expected<int>(int)>;
+ * FIncrement native = FIncrement::FromNative<&Increment>();
+ * tvm::ffi::Expected<int> result = native(3);
+ * \endcode
+ *
+ * \note The original function must remain alive while a borrowed view is used.
+ */
+template <typename R, typename... Args>
+class NativeFunctionView<Expected<R>(Args...)> {
+ public:
+  static_assert(((std::is_lvalue_reference_v<Args> ||
+                  (std::is_standard_layout_v<Args> && 
std::is_trivially_copyable_v<Args>)) &&
+                 ...),
+                "NativeFunctionView parameters require lvalue references or 
standard-layout, "
+                "trivially-copyable values");
+  static_assert((ffi::details::ArgSupported<Args> && ...) && 
ffi::details::RetSupported<R>,
+                "NativeFunctionView signature must be supported by 
TypedFunction");
+  /*! \brief Raw C ABI-compatible function pointer stored for native hook 
dispatch. */
+  using ABIType = TVMFFIAny (*)(Args...) noexcept;
+
+  /*! \brief A hook view cannot be empty. */
+  NativeFunctionView() = delete;
+  /*! \brief A null pointer is not a callable hook. */
+  NativeFunctionView(std::nullptr_t) = delete;
+  /*!
+   * \brief Borrow a non-null typed packed function without retaining it.
+   *
+   * \param packed The function that must outlive this view.
+   */
+  NativeFunctionView(const TypedFunction<Expected<R>(Args...)>& packed)  // 
NOLINT(*)
+      : data_(AnyView(packed.packed()).CopyToTVMFFIAny()) {
+    TVM_FFI_CHECK(packed != nullptr, ValueError)
+        << "NativeFunctionView requires a non-null typed function";
+  }
+  /*! \brief Reject a temporary packed function, which would leave a dangling 
view. */
+  NativeFunctionView(const TypedFunction<Expected<R>(Args...)>&&) = delete;
+
+  /*!
+   * \brief Bind a named noexcept native function through a context-free ABI 
adapter.
+   *
+   * \tparam Fn The native hook function.
+   * \return A borrowed view of its static adapter.
+   * \note Declare the native target `Fn` with `TVM_FFI_INLINE` when its 
definition is visible
+   * to the adapter. This gives the compiler an opportunity to inline it into 
the adapter;
+   * calls and result handling inside `Fn` may still remain.
+   */
+  template <Expected<R> (*Fn)(Args...) noexcept>
+  static NativeFunctionView FromNative() {
+    TVMFFIAny data;
+    data.type_index = TypeIndex::kTVMFFIOpaquePtr;
+    data.zero_padding = 0;
+    data.v_ptr = reinterpret_cast<void*>(&NativeABIFuncPtr<Fn>);
+    return NativeFunctionView(UnsafeInit{}, data);
+  }
+
+  /*!
+   * \brief Invoke the hook and return its declared Expected result.
+   *
+   * \param args The typed hook arguments.
+   * \return A value or an error.
+   */
+  TVM_FFI_INLINE Expected<R> operator()(Args... args) const noexcept {
+    return 
ffi::details::ExpectedUnsafe::MoveFromTVMFFIAny<R>(CallABI(std::forward<Args>(args)...));
+  }
+
+ private:
+  explicit NativeFunctionView(UnsafeInit, TVMFFIAny data) : data_(data) {}
+
+  template <Expected<R> (*Fn)(Args...) noexcept>
+  static TVMFFIAny NativeABIFuncPtr(Args... args) noexcept {
+    return 
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Fn(std::forward<Args>(args)...));
+  }
+
+  TVM_FFI_INLINE TVMFFIAny CallABI(Args... args) const noexcept {
+    if (TVM_FFI_PREDICT_TRUE(data_.type_index == TypeIndex::kTVMFFIOpaquePtr)) 
{
+      return 
reinterpret_cast<ABIType>(data_.v_obj)(std::forward<Args>(args)...);
+    }
+    return CallABITail(std::forward<Args>(args)...);
+  }
+
+  TVM_FFI_COLD_CODE TVMFFIAny CallABITail(Args... args) const noexcept {
+    auto* cell = TVMFFIFunctionGetCellPtr(data_.v_obj);
+    AnyView packed_args[sizeof...(Args) > 0 ? sizeof...(Args) : 1];
+    PackedArgs::Fill(packed_args, std::forward<Args>(args)...);
+    Any result;
+    // The function cell follows the object header in the C ABI allocation.
+    // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound)
+    int code = cell->safe_call(data_.v_obj, reinterpret_cast<const 
TVMFFIAny*>(packed_args),
+                               sizeof...(Args), 
reinterpret_cast<TVMFFIAny*>(&result));
+    if (TVM_FFI_PREDICT_FALSE(code != 0)) {
+      return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(
+          Expected<R>(Unexpected(ffi::details::MoveFromSafeCallRaised())));
+    }
+    if constexpr (std::is_same_v<R, Any>) {
+      return 
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<R>(std::move(result)));
+    } else if constexpr (std::is_void_v<R>) {
+      if (result.type_index() == TypeIndex::kTVMFFINone) {
+        return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<void>());
+      }
+    } else if (auto value = result.template try_cast<R>()) {
+      return 
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<R>(*std::move(value)));
+    }
+    return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(
+        Expected<R>(Unexpected(Error("TypeError", "Packed hook result type 
mismatch", ""))));
+  }
+
+  template <typename, typename>
+  friend struct ::tvm::ffi::TypeTraits;
+  template <typename>
+  friend class NativeFunction;
+
+  TVMFFIAny data_;
+};
+
+template <typename Signature>
+class NativeFunction;
+
+/*!
+ * \brief Owning counterpart of NativeFunctionView for stored hook values.
+ *
+ * Construct a borrowed view, then copy it into this owner. A packed function 
remains valid after
+ * the original TypedFunction is destroyed; native adapters retain their 
static function address.
+ * The owner stores one `ffi::Any`, which manages the lifetime of packed 
function objects.
+ *
+ * \code{.cpp}
+ * tvm::ffi::Expected<int> Increment(int value) noexcept { return value + 1; }
+ * using FIncrement = 
tvm::ffi::reflection::NativeFunction<tvm::ffi::Expected<int>(int)>;
+ * FIncrement owned = FIncrement::FromNative<&Increment>();
+ * tvm::ffi::Expected<int> result = owned(3);
+ * \endcode
+ */
+template <typename R, typename... Args>
+class NativeFunction<Expected<R>(Args...)> {
+ public:
+  /*! \brief The borrowed view with the same callable signature. */
+  using View = NativeFunctionView<Expected<R>(Args...)>;
+
+  /*!
+   * \brief Retain a copy of a borrowed native or packed hook.
+   *
+   * \param view The borrowed view to copy.
+   */
+  explicit NativeFunction(View view) : data_(AnyView(view)) {}
+
+  /*!
+   * \brief Retain a non-null typed packed function.
+   *
+   * \param packed The typed function to copy.
+   */
+  NativeFunction(const TypedFunction<Expected<R>(Args...)>& packed)  // 
NOLINT(*)
+      : NativeFunction(View(packed)) {}
+
+  /*!
+   * \brief Copy a borrowed hook into an owning hook.
+   *
+   * \param view The borrowed view to retain.
+   * \return An independent owner of the hook.
+   */
+  static NativeFunction From(View view) { return NativeFunction(view); }
+
+  /*!
+   * \brief Own a named native hook bound through the same ABI adapter as View.
+   *
+   * \tparam Fn The noexcept native hook function.
+   * \return An owner of the generated static adapter.
+   * \note As with `View::FromNative`, declaring `Fn` with `TVM_FFI_INLINE` 
helps the compiler
+   * inline a visible target into the adapter, without guaranteeing complete 
call elimination.
+   */
+  template <Expected<R> (*Fn)(Args...) noexcept>
+  static NativeFunction FromNative() {
+    return From(View::template FromNative<Fn>());
+  }
+
+  /*!
+   * \brief Invoke the hook and return its declared Expected result.
+   *
+   * \param args The typed hook arguments.
+   * \return A value or an error.
+   */
+  TVM_FFI_INLINE Expected<R> operator()(Args... args) const noexcept {
+    return View(UnsafeInit{}, 
AnyView(data_).CopyToTVMFFIAny())(std::forward<Args>(args)...);
+  }
+
+ private:
+  explicit NativeFunction(UnsafeInit, Any data) : data_(std::move(data)) {}
+
+  template <typename, typename>
+  friend struct ::tvm::ffi::TypeTraits;
+
+  Any data_;
+};
+
+}  // namespace reflection
+
+template <typename Signature>
+inline constexpr bool 
use_default_type_traits_v<reflection::NativeFunctionView<Signature>> = false;
+
+template <typename Signature>
+inline constexpr bool 
use_default_type_traits_v<reflection::NativeFunction<Signature>> = false;
+
+template <typename Signature>
+struct TypeTraits<reflection::NativeFunctionView<Signature>> : public 
TypeTraitsBase {
+  using View = reflection::NativeFunctionView<Signature>;
+  static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny;
+
+  static void CopyToAnyView(const View& src, TVMFFIAny* result) { *result = 
src.data_; }
+  static void MoveToAny(View src, TVMFFIAny* result) {
+    *result = 
details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(AnyView::CopyFromTVMFFIAny(src.data_)));
+  }
+  static bool CheckAnyStrict(const TVMFFIAny* src) {
+    return (src->type_index == TypeIndex::kTVMFFIOpaquePtr ||
+            src->type_index == TypeIndex::kTVMFFIFunction);
+  }
+  static View CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { return 
View(UnsafeInit{}, *src); }
+  static View MoveFromAnyAfterCheck(TVMFFIAny* src) { return 
CopyFromAnyViewAfterCheck(src); }
+  static std::optional<View> TryCastFromAnyView(const TVMFFIAny* src) {
+    if (CheckAnyStrict(src)) return CopyFromAnyViewAfterCheck(src);
+    return std::nullopt;
+  }
+  static std::string TypeStr() {
+    return "Variant<" + TypeTraits<void*>::TypeStr() + ", " +
+           TypeTraits<TypedFunction<Signature>>::TypeStr() + ">";
+  }
+  static std::string TypeSchema() {
+    return R"({"type":"Variant","args":[)" + TypeTraits<void*>::TypeSchema() + 
"," +
+           TypeTraits<TypedFunction<Signature>>::TypeSchema() + "]}";
+  }
+};
+
+template <typename Signature>
+struct TypeTraits<reflection::NativeFunction<Signature>> : public 
TypeTraitsBase {
+  using Owner = reflection::NativeFunction<Signature>;
+  using View = typename Owner::View;
+  static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny;
+
+  static void CopyToAnyView(const Owner& src, TVMFFIAny* result) {
+    *result = AnyView(src.data_).CopyToTVMFFIAny();
+  }
+  static void MoveToAny(Owner src, TVMFFIAny* result) {
+    *result = details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(src.data_));
+  }
+  static bool CheckAnyStrict(const TVMFFIAny* src) { return 
TypeTraits<View>::CheckAnyStrict(src); }
+  static Owner CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
+    return Owner(TypeTraits<View>::CopyFromAnyViewAfterCheck(src));
+  }
+  static Owner MoveFromAnyAfterCheck(TVMFFIAny* src) {
+    return Owner(UnsafeInit{}, details::AnyUnsafe::MoveTVMFFIAnyToAny(src));
+  }
+  static std::optional<Owner> TryCastFromAnyView(const TVMFFIAny* src) {
+    if (CheckAnyStrict(src)) return CopyFromAnyViewAfterCheck(src);
+    return std::nullopt;
+  }
+  static std::string TypeStr() { return TypeTraits<View>::TypeStr(); }
+  static std::string TypeSchema() { return TypeTraits<View>::TypeSchema(); }
+};
+
+}  // namespace ffi
+}  // namespace tvm
+
+#endif  // TVM_FFI_REFLECTION_NATIVE_FUNCTION_H_
diff --git a/include/tvm/ffi/reflection/registry.h 
b/include/tvm/ffi/reflection/registry.h
index 612e5a38..6b9f8be4 100644
--- a/include/tvm/ffi/reflection/registry.h
+++ b/include/tvm/ffi/reflection/registry.h
@@ -1145,7 +1145,7 @@ class TypeAttrDef : public ReflectionDefBase {
    * \return The TypeAttrDef object.
    */
   template <typename T>
-  TypeAttrDef& attr(const char* name, T value) {
+  TypeAttrDef& attr(const char* name, T value) {  // 
NOLINT(performance-unnecessary-value-param)
     RegisterTypeAttrValue(type_index_, name, std::move(value));
     return *this;
   }
diff --git a/src/ffi/extra/structural_mutate.cc 
b/src/ffi/extra/structural_mutate.cc
index 3764322e..f3361bc4 100644
--- a/src/ffi/extra/structural_mutate.cc
+++ b/src/ffi/extra/structural_mutate.cc
@@ -171,9 +171,10 @@ Expected<Any> StructuralMutateExpected(
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-TVM_FFI_INLINE TVMFFIAny MutateSeqContainerChanged(StructuralMutatorObj* 
mutator,
-                                                   const SeqObj* self, int64_t 
index,
-                                                   Any first) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MutateSeqContainerChanged(StructuralMutatorObj* mutator,
+                                                                    const 
SeqObj* self,
+                                                                    int64_t 
index,
+                                                                    Any first) 
noexcept {
   int64_t size = static_cast<int64_t>(self->size());
   const Any* items = self->begin();
   ObjectPtr<SeqObj> output = SeqObj::CreateRepeated(size, Any());
@@ -186,7 +187,7 @@ TVM_FFI_INLINE TVMFFIAny 
MutateSeqContainerChanged(StructuralMutatorObj* mutator
                                       mutator->MutateExpected(item, 
InplaceMode::kDisallow));
     output->SetItemAfterCheck(i, 
std::move(mapped_value).ValueOrUnchanged(AnyView(item)));
   }
-  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
+  return output;
 }
 
 /*!
@@ -198,7 +199,8 @@ TVM_FFI_INLINE TVMFFIAny 
MutateSeqContainerChanged(StructuralMutatorObj* mutator
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* mutator, const SeqObj* 
self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MutateSeqContainerRaw(StructuralMutatorObj* mutator,
+                                                                const SeqObj* 
self) noexcept {
   int64_t size = static_cast<int64_t>(self->size());
   const Any* items = self->begin();
 
@@ -210,7 +212,7 @@ TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* 
mutator, const SeqObj* sel
       return MutateSeqContainerChanged(mutator, self, i, 
std::move(mapped_value).ValueUnchecked());
     }
   }
-  return Unchanged().CopyToTVMFFIAny();
+  return Unchanged();
 }
 
 /*!
@@ -222,7 +224,8 @@ TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* 
mutator, const SeqObj* sel
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-TVMFFIAny MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, 
SeqObj* self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MaybeInplaceMutateSeqContainerRaw(
+    StructuralMutatorObj* mutator, SeqObj* self) noexcept {
   for (int64_t i = 0; i < static_cast<int64_t>(self->size()); ++i) {
     const Any& item = self->begin()[i];
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped_value,
@@ -231,7 +234,7 @@ TVMFFIAny 
MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, SeqOb
       self->SetItemAfterCheck(i, std::move(mapped_value).ValueUnchecked());
     }
   }
-  return Unchanged().CopyToTVMFFIAny();
+  return Unchanged();
 }
 
 /*!
@@ -246,10 +249,10 @@ TVMFFIAny 
MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, SeqOb
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-TVM_FFI_INLINE TVMFFIAny MutateMapValuesChanged(StructuralMutatorObj* mutator,
-                                                const MapObjType* self,
-                                                MapBaseObj::iterator 
source_it, size_t index,
-                                                Any first) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MutateMapValuesChanged(StructuralMutatorObj* mutator,
+                                                                 const 
MapObjType* self,
+                                                                 
MapBaseObj::iterator source_it,
+                                                                 size_t index, 
Any first) noexcept {
   ObjectPtr<Object> output = MapObjType::ShallowCopy(self);
   auto output_it = static_cast<MapBaseObj*>(output.get())->begin();
   for (size_t i = 0; i < index; ++i) {
@@ -267,7 +270,7 @@ TVM_FFI_INLINE TVMFFIAny 
MutateMapValuesChanged(StructuralMutatorObj* mutator,
       output_it->second = std::move(new_value).ValueUnchecked();
     }
   }
-  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
+  return output;
 }
 
 /*!
@@ -279,7 +282,8 @@ TVM_FFI_INLINE TVMFFIAny 
MutateMapValuesChanged(StructuralMutatorObj* mutator,
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator, const MapObjType* 
self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MutateMapValuesRaw(StructuralMutatorObj* mutator,
+                                                             const MapObjType* 
self) noexcept {
   size_t index = 0;
   for (auto source_it = self->begin(); source_it != self->end(); ++source_it, 
++index) {
     const Any& old_value = source_it->second;
@@ -290,7 +294,7 @@ TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator, 
const MapObjType* se
                                     std::move(new_value).ValueUnchecked());
     }
   }
-  return Unchanged().CopyToTVMFFIAny();
+  return Unchanged();
 }
 
 /*!
@@ -302,7 +306,8 @@ TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator, 
const MapObjType* se
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-TVMFFIAny MaybeInplaceMutateMapValuesRaw(StructuralMutatorObj* mutator, 
MapObjType* self) noexcept {
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MaybeInplaceMutateMapValuesRaw(
+    StructuralMutatorObj* mutator, MapObjType* self) noexcept {
   for (auto it = self->begin(); it != self->end(); ++it) {
     const Any& old_value = it->second;
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, new_value,
@@ -311,61 +316,71 @@ TVMFFIAny 
MaybeInplaceMutateMapValuesRaw(StructuralMutatorObj* mutator, MapObjTy
       it->second = std::move(new_value).ValueUnchecked();
     }
   }
-  return Unchanged().CopyToTVMFFIAny();
+  return Unchanged();
 }
 
 /*! \brief Identity structural mutation hook for immutable String and Bytes 
leaves. */
-TVMFFIAny MutateImmutableLeaf(StructuralMutatorObj*, AnyView) noexcept {
-  return Unchanged().CopyToTVMFFIAny();
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MutateImmutableLeaf(StructuralMutatorObj*,
+                                                              AnyView) 
noexcept {
+  return Unchanged();
 }
 
-/*! \brief Structural mutation hook for ArrayObj. */
-TVMFFIAny MutateArray(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for ArrayObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateArray(StructuralMutatorObj* 
mutator,
+                                                      AnyView value) noexcept {
   return MutateSeqContainerRaw(
       mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
ArrayObj>(value));
 }
 
-/*! \brief Maybe-in-place structural mutation hook for ArrayObj. */
-TVMFFIAny MaybeInplaceMutateArray(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
+/*! \brief Typed structural mutation hook for ArrayObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MaybeInplaceMutateArray(StructuralMutatorObj* mutator,
+                                                                  AnyView 
value) noexcept {
   return MaybeInplaceMutateSeqContainerRaw(
       mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ArrayObj>(value));
 }
 
-/*! \brief Structural mutation hook for ListObj. */
-TVMFFIAny MutateList(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for ListObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateList(StructuralMutatorObj* 
mutator,
+                                                     AnyView value) noexcept {
   return MutateSeqContainerRaw(
       mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
ListObj>(value));
 }
 
-/*! \brief Maybe-in-place structural mutation hook for ListObj. */
-TVMFFIAny MaybeInplaceMutateList(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
+/*! \brief Typed structural mutation hook for ListObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MaybeInplaceMutateList(StructuralMutatorObj* mutator,
+                                                                 AnyView 
value) noexcept {
   return MaybeInplaceMutateSeqContainerRaw(
       mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ListObj>(value));
 }
 
-/*! \brief Structural mutation hook for MapObj. */
-TVMFFIAny MutateMap(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for MapObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateMap(StructuralMutatorObj* 
mutator,
+                                                    AnyView value) noexcept {
   return MutateMapValuesRaw(
       mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
MapObj>(value));
 }
 
-/*! \brief Maybe-in-place structural mutation hook for MapObj. */
-TVMFFIAny MaybeInplaceMutateMap(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
+/*! \brief Typed structural mutation hook for MapObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MaybeInplaceMutateMap(StructuralMutatorObj* mutator,
+                                                                AnyView value) 
noexcept {
   return MaybeInplaceMutateMapValuesRaw(
       mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<MapObj>(value));
 }
 
-/*! \brief Structural mutation hook for DictObj. */
-TVMFFIAny MutateDict(StructuralMutatorObj* mutator, AnyView value) noexcept {
+/*! \brief Typed structural mutation hook for DictObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> MutateDict(StructuralMutatorObj* 
mutator,
+                                                     AnyView value) noexcept {
   return MutateMapValuesRaw(
       mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
DictObj>(value));
 }
 
-/*! \brief Maybe-in-place structural mutation hook for DictObj. */
-TVMFFIAny MaybeInplaceMutateDict(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
+/*! \brief Typed structural mutation hook for DictObj. */
+TVM_FFI_INLINE Expected<UnchangedOr<Any>> 
MaybeInplaceMutateDict(StructuralMutatorObj* mutator,
+                                                                 AnyView 
value) noexcept {
   return MaybeInplaceMutateMapValuesRaw(
       mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<DictObj>(value));
 }
+
 }  // namespace details
 
 // ---------------------------------------------------------------------------
@@ -422,43 +437,39 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMaybeInplaceMutate);
   refl::TypeAttrDef<details::BigIntObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)))
+            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)));
+            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
   refl::TypeAttrDef<details::StringObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)))
+            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)));
+            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
   refl::TypeAttrDef<details::BytesObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)))
+            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateImmutableLeaf)));
+            FStructuralMutate::FromNative<&details::MutateImmutableLeaf>());
   refl::TypeAttrDef<ArrayObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateArray)))
+            FStructuralMutate::FromNative<&details::MutateArray>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            reinterpret_cast<void*>(
-                
static_cast<FStructuralMutate>(&details::MaybeInplaceMutateArray)));
+            
FStructuralMutate::FromNative<&details::MaybeInplaceMutateArray>());
   refl::TypeAttrDef<ListObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateList)))
+            FStructuralMutate::FromNative<&details::MutateList>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            reinterpret_cast<void*>(
-                
static_cast<FStructuralMutate>(&details::MaybeInplaceMutateList)));
+            FStructuralMutate::FromNative<&details::MaybeInplaceMutateList>());
   refl::TypeAttrDef<MapObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateMap)))
-      .attr(
-          refl::type_attr::kStructuralMaybeInplaceMutate,
-          
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MaybeInplaceMutateMap)));
+            FStructuralMutate::FromNative<&details::MutateMap>())
+      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
+            FStructuralMutate::FromNative<&details::MaybeInplaceMutateMap>());
   refl::TypeAttrDef<DictObj>()
       .attr(refl::type_attr::kStructuralMutate,
-            
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&details::MutateDict)))
+            FStructuralMutate::FromNative<&details::MutateDict>())
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            reinterpret_cast<void*>(
-                
static_cast<FStructuralMutate>(&details::MaybeInplaceMutateDict)));
+            FStructuralMutate::FromNative<&details::MaybeInplaceMutateDict>());
 }
 
 }  // namespace ffi
diff --git a/src/ffi/extra/structural_visit.cc 
b/src/ffi/extra/structural_visit.cc
index aaa0f804..729a4a7c 100644
--- a/src/ffi/extra/structural_visit.cc
+++ b/src/ffi/extra/structural_visit.cc
@@ -111,43 +111,25 @@ Expected<Optional<VisitInterrupt>> 
StructuralVisitExpected(
 }
 
 /*! \brief Visit entries in a sequence container. */
-TVMFFIAny VisitSeqContainer(StructuralVisitorObj* visitor, const SeqBaseObj* 
self) noexcept {
+template <typename SeqObj>
+TVM_FFI_INLINE Expected<Optional<VisitInterrupt>> 
VisitSeqContainer(StructuralVisitorObj* visitor,
+                                                                    AnyView 
value) noexcept {
+  const auto* self = value.cast<const SeqObj*>();
   for (const Any& item : *self) {
     TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(item));
   }
-  return AnyView(nullptr).CopyToTVMFFIAny();
+  return Optional<VisitInterrupt>(std::nullopt);
 }
 
 /*! \brief Visit values in a map container while treating keys as structural 
anchors. */
-TVMFFIAny VisitMapContainer(StructuralVisitorObj* visitor, const MapBaseObj* 
self) noexcept {
+template <typename MapObjType>
+TVM_FFI_INLINE Expected<Optional<VisitInterrupt>> 
VisitMapContainer(StructuralVisitorObj* visitor,
+                                                                    AnyView 
value) noexcept {
+  const auto* self = value.cast<const MapObjType*>();
   for (const auto& kv : *self) {
     TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(kv.second));
   }
-  return AnyView(nullptr).CopyToTVMFFIAny();
-}
-
-/*! \brief Structural visit hook for ArrayObj. */
-TVMFFIAny VisitArray(StructuralVisitorObj* visitor, AnyView value) noexcept {
-  const auto* array = value.cast<const ArrayObj*>();
-  return VisitSeqContainer(visitor, array);
-}
-
-/*! \brief Structural visit hook for ListObj. */
-TVMFFIAny VisitList(StructuralVisitorObj* visitor, AnyView value) noexcept {
-  const auto* list = value.cast<const ListObj*>();
-  return VisitSeqContainer(visitor, list);
-}
-
-/*! \brief Structural visit hook for MapObj. */
-TVMFFIAny VisitMap(StructuralVisitorObj* visitor, AnyView value) noexcept {
-  const auto* map = value.cast<const MapObj*>();
-  return VisitMapContainer(visitor, map);
-}
-
-/*! \brief Structural visit hook for DictObj. */
-TVMFFIAny VisitDict(StructuralVisitorObj* visitor, AnyView value) noexcept {
-  const auto* dict = value.cast<const DictObj*>();
-  return VisitMapContainer(visitor, dict);
+  return Optional<VisitInterrupt>(std::nullopt);
 }
 
 }  // namespace details
@@ -190,16 +172,16 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
   refl::TypeAttrDef<ArrayObj>().attr(
       refl::type_attr::kStructuralVisit,
-      
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitArray)));
+      FStructuralVisit::FromNative<&details::VisitSeqContainer<ArrayObj>>());
   refl::TypeAttrDef<ListObj>().attr(
       refl::type_attr::kStructuralVisit,
-      
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitList)));
+      FStructuralVisit::FromNative<&details::VisitSeqContainer<ListObj>>());
   refl::TypeAttrDef<MapObj>().attr(
       refl::type_attr::kStructuralVisit,
-      
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitMap)));
+      FStructuralVisit::FromNative<&details::VisitMapContainer<MapObj>>());
   refl::TypeAttrDef<DictObj>().attr(
       refl::type_attr::kStructuralVisit,
-      
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&details::VisitDict)));
+      FStructuralVisit::FromNative<&details::VisitMapContainer<DictObj>>());
 }
 
 }  // namespace ffi
diff --git a/tests/cpp/extra/test_structural_mutate.cc 
b/tests/cpp/extra/test_structural_mutate.cc
index 1debe194..1cc31e75 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -270,20 +270,21 @@ class TNestedMapHookObj : public Object {
 
   explicit TNestedMapHookObj(AnyArray field) : field(std::move(field)) {}
 
-  static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
+  TVM_FFI_INLINE static Expected<UnchangedOr<Any>> 
StructuralMutate(StructuralMutatorObj* mutator,
+                                                                    AnyView 
value) noexcept {
     const auto* self = value.cast<const TNestedMapHookObj*>();
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped,
                                       mutator->MutateExpected(self->field, 
InplaceMode::kDisallow));
     if (mapped.UnchangedOrSameAs(Any(self->field))) {
-      return Unchanged().CopyToTVMFFIAny();
+      return Unchanged();
     }
     Any mapped_value = std::move(mapped).ValueOrUnchanged(Any(self->field));
     AnyArray mapped_field = mapped_value.cast<AnyArray>();
-    return details::AnyUnsafe::MoveAnyToTVMFFIAny(
-        Any(make_object<TNestedMapHookObj>(std::move(mapped_field))));
+    return make_object<TNestedMapHookObj>(std::move(mapped_field));
   }
 
-  static TVMFFIAny MaybeInplaceMutate(StructuralMutatorObj*, AnyView value) 
noexcept {
+  TVM_FFI_INLINE static Expected<UnchangedOr<Any>> 
MaybeInplaceMutate(StructuralMutatorObj*,
+                                                                      AnyView 
value) noexcept {
     auto* self = value.cast<TNestedMapHookObj*>();
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(
         Any, mapped,
@@ -291,7 +292,7 @@ class TNestedMapHookObj : public Object {
             Any(std::move(self->field)),
             [](int64_t item) -> Expected<Any> { return Any(item + 1); }));
     self->field = mapped.cast<AnyArray>();
-    return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+    return Any(value);
   }
 
   static void RegisterReflection() {
@@ -301,9 +302,9 @@ class TNestedMapHookObj : public Object {
     refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMaybeInplaceMutate);
     refl::TypeAttrDef<TNestedMapHookObj>()
         .attr(refl::type_attr::kStructuralMutate,
-              
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&StructuralMutate)))
+              
FStructuralMutate::FromNative<&TNestedMapHookObj::StructuralMutate>())
         .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-              
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&MaybeInplaceMutate)));
+              
FStructuralMutate::FromNative<&TNestedMapHookObj::MaybeInplaceMutate>());
   }
 
   static constexpr bool _type_mutable = true;
diff --git a/tests/cpp/test_expected_checks.cc 
b/tests/cpp/test_expected_checks.cc
index 6797652e..b6f408ac 100644
--- a/tests/cpp/test_expected_checks.cc
+++ b/tests/cpp/test_expected_checks.cc
@@ -98,28 +98,23 @@ TEST(ExpectedChecks, ReturnChecks) {
   // NOLINTEND(google-readability-braces-around-statements)
 }
 
-template <typename Return>
-Return UnsafeMutateAssign(Expected<Any> result, int* evaluations) noexcept {
+Expected<int> UnsafeMutateAssign(Expected<Any> result, int* evaluations) 
noexcept {
   TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN(int, value, (++*evaluations, 
std::move(result)));
-  if constexpr (std::is_same_v<Return, TVMFFIAny>) {
-    return AnyView(value + 1).CopyToTVMFFIAny();
-  } else {
-    return value + 1;
-  }
+  return value + 1;
 }
 
 TEST(ExpectedChecks, UnsafeAssignment) {
   int evaluations = 0;
-  EXPECT_EQ(UnsafeMutateAssign<Expected<int>>(Any(41), &evaluations).value(), 
42);
+  EXPECT_EQ(UnsafeMutateAssign(Any(41), &evaluations).value(), 42);
   auto raw_ok = details::ExpectedUnsafe::MoveFromTVMFFIAny<int>(
-      UnsafeMutateAssign<TVMFFIAny>(Any(41), &evaluations));
+      details::ExpectedUnsafe::MoveToTVMFFIAny(UnsafeMutateAssign(Any(41), 
&evaluations)));
   EXPECT_EQ(raw_ok.value(), 42);
   Error error("ValueError", "unsafe assign", "");
-  auto typed_error = UnsafeMutateAssign<Expected<int>>(error, &evaluations);
+  auto typed_error = UnsafeMutateAssign(error, &evaluations);
   ASSERT_TRUE(typed_error.is_err());
   EXPECT_EQ(typed_error.error().message(), "unsafe assign");
   auto raw_error = details::ExpectedUnsafe::MoveFromTVMFFIAny<int>(
-      UnsafeMutateAssign<TVMFFIAny>(error, &evaluations));
+      details::ExpectedUnsafe::MoveToTVMFFIAny(UnsafeMutateAssign(error, 
&evaluations)));
   ASSERT_TRUE(raw_error.is_err());
   EXPECT_EQ(raw_error.error().message(), "unsafe assign");
   EXPECT_EQ(evaluations, 4);
diff --git a/tests/cpp/test_native_function_view.cc 
b/tests/cpp/test_native_function_view.cc
new file mode 100644
index 00000000..09eb195c
--- /dev/null
+++ b/tests/cpp/test_native_function_view.cc
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+#include <gtest/gtest.h>
+#include <tvm/ffi/reflection/native_function.h>
+
+#include <optional>
+#include <string>
+#include <type_traits>
+
+namespace {
+
+using namespace tvm::ffi;
+using tvm::ffi::reflection::NativeFunction;
+using tvm::ffi::reflection::NativeFunctionView;
+
+static_assert(sizeof(NativeFunctionView<Expected<int>(int)>) == 
sizeof(TVMFFIAny));
+static_assert(sizeof(NativeFunction<Expected<int>(int)>) == sizeof(Any));
+static_assert(!std::is_default_constructible_v<NativeFunctionView<Expected<int>(int)>>);
+static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>, 
std::nullptr_t>);
+static_assert(std::is_convertible_v<TypedFunction<Expected<int>(int)>&,
+                                    NativeFunctionView<Expected<int>(int)>>);
+static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>,
+                                       TypedFunction<Expected<int>(int)>&&>);
+static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>, 
AnyView>);
+static_assert(std::is_same_v<decltype(std::declval<NativeFunctionView<Expected<int>(int)>>()(0)),
+                             Expected<int>>);
+
+Expected<int> IncrementHook(int value) noexcept { return value + 1; }
+Expected<int> FailHook(int) noexcept { return Unexpected(Error("ValueError", 
"hook failed", "")); }
+
+TEST(NativeFunctionView, NativeAndBorrowedPacked) {
+  auto native = 
NativeFunctionView<Expected<int>(int)>::FromNative<&IncrementHook>();
+  EXPECT_EQ(native(3).value(), 4);
+  EXPECT_EQ(AnyView(native).type_index(), TypeIndex::kTVMFFIOpaquePtr);
+  
EXPECT_EQ(Any(native).cast<NativeFunctionView<Expected<int>(int)>>()(3).value(),
 4);
+  
EXPECT_EQ(TypeTraits<NativeFunctionView<Expected<int>(int)>>::TypeStr().find("Variant<"),
 0);
+  EXPECT_NE(
+      
TypeTraits<NativeFunctionView<Expected<int>(int)>>::TypeSchema().find("\"type\":\"Variant\""),
+      std::string::npos);
+
+  TypedFunction<Expected<int>(int)> packed(
+      Function::FromTyped([](int x) -> Expected<int> { return x + 2; }));
+  auto* obj = packed.packed().get();
+  auto before = obj->use_count();
+  NativeFunctionView<Expected<int>(int)> borrowed = packed;
+  EXPECT_EQ(obj->use_count(), before);
+  EXPECT_EQ(borrowed(3).value(), 5);
+}
+
+TEST(NativeFunctionView, ErrorAndNullRejection) {
+  auto native = 
NativeFunctionView<Expected<int>(int)>::FromNative<&FailHook>();
+  auto failed = native(0);
+  ASSERT_TRUE(failed.is_err());
+  EXPECT_EQ(failed.error().kind(), "ValueError");
+
+  TypedFunction<Expected<int>(int)> empty(nullptr);
+  EXPECT_ANY_THROW(NativeFunctionView<Expected<int>(int)> invalid(empty));
+  
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunctionView<Expected<int>(int)>>().has_value());
+  
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunction<Expected<int>(int)>>().has_value());
+}
+
+TEST(NativeFunctionView, OwningLifetimeAndAnyRoundtrip) {
+  std::optional<NativeFunction<Expected<int>(int)>> owned;
+  {
+    TypedFunction<Expected<int>(int)> packed(
+        Function::FromTyped([](int value) -> Expected<int> { return value + 2; 
}));
+    auto* obj = packed.packed().get();
+    auto before = obj->use_count();
+    owned.emplace(NativeFunctionView<Expected<int>(int)>(packed));
+    EXPECT_EQ(obj->use_count(), before + 1);
+    NativeFunction<Expected<int>(int)> copy(*owned);
+    EXPECT_EQ(obj->use_count(), before + 2);
+    NativeFunction<Expected<int>(int)> moved(std::move(copy));
+    EXPECT_EQ(moved(3).value(), 5);
+  }
+  EXPECT_EQ((*owned)(3).value(), 5);
+  Any attr = *owned;
+  EXPECT_EQ(attr.cast<NativeFunction<Expected<int>(int)>>()(3).value(), 5);
+  Any moved_attr = std::move(*owned);
+  EXPECT_EQ(moved_attr.cast<NativeFunction<Expected<int>(int)>>()(3).value(), 
5);
+  
EXPECT_EQ(NativeFunction<Expected<int>(int)>::FromNative<&IncrementHook>()(3).value(),
 4);
+}
+
+}  // namespace
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index 366bb96a..0b8881c3 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -249,7 +249,8 @@ class TMutatePairObj : public Object {
     return count;
   }
 
-  static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
+  TVM_FFI_INLINE static Expected<UnchangedOr<Any>> StructuralMutateHook(
+      StructuralMutatorObj* mutator, AnyView value) noexcept {
     ++StructuralMutateCallCount();
     const TMutatePairObj* self =
         details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
TMutatePairObj>(value);
@@ -258,12 +259,12 @@ class TMutatePairObj : public Object {
     TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<ObjectRef>, rhs,
                                       mutator->MutateExpected(self->rhs, 
InplaceMode::kDisallow));
     if (lhs.UnchangedOrSameAs(self->lhs) && rhs.UnchangedOrSameAs(self->rhs)) {
-      return Unchanged().CopyToTVMFFIAny();
+      return Unchanged();
     }
     ObjectPtr<TMutatePairObj> copy = make_object<TMutatePairObj>(*self);
     copy->lhs = std::move(lhs).ValueOrUnchanged(std::move(copy->lhs));
     copy->rhs = std::move(rhs).ValueOrUnchanged(std::move(copy->rhs));
-    return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(copy)));
+    return copy;
   }
 
   static void RegisterReflection() {
@@ -274,7 +275,7 @@ class TMutatePairObj : public Object {
     refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralMutate);
     refl::TypeAttrDef<TMutatePairObj>().attr(
         refl::type_attr::kStructuralMutate,
-        
reinterpret_cast<void*>(static_cast<FStructuralMutate>(&TMutatePairObj::StructuralMutate)));
+        
FStructuralMutate::FromNative<&TMutatePairObj::StructuralMutateHook>());
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
@@ -411,15 +412,14 @@ class TFuncObj : public Object {
   TFuncObj(Array<TVar> params, Array<ObjectRef> body, Optional<String> comment)
       : params(params), body(body), comment(comment) {}
 
-  static TVMFFIAny StructuralVisit(StructuralVisitorObj* visitor, AnyView 
value) noexcept {
+  TVM_FFI_INLINE static Expected<Optional<VisitInterrupt>> StructuralVisitHook(
+      StructuralVisitorObj* visitor, AnyView value) noexcept {
     const auto* self = value.cast<const TFuncObj*>();
 
     TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
         kTVMFFIDefRegionKindPattern, [&]() { return 
visitor->VisitExpected(self->params); }));
 
-    auto body_result = visitor->VisitExpected(self->body);
-    TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(body_result);
-    return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(body_result));
+    return visitor->VisitExpected(self->body);
   }
 
   static void RegisterReflection() {
@@ -431,7 +431,7 @@ class TFuncObj : public Object {
     refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
     refl::TypeAttrDef<TFuncObj>().attr(
         refl::type_attr::kStructuralVisit,
-        
reinterpret_cast<void*>(static_cast<FStructuralVisit>(&TFuncObj::StructuralVisit)));
+        FStructuralVisit::FromNative<&TFuncObj::StructuralVisitHook>());
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;

Reply via email to