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 39d593e7 [FFI][REFACTOR] Introduce UnchangedOr for StructuralMutate 
(#768)
39d593e7 is described below

commit 39d593e716a5b437a57ea3241aff8850112caf2e
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Sep 8 18:22:59 2026 -0400

    [FFI][REFACTOR] Introduce UnchangedOr for StructuralMutate (#768)
    
    This change refactors StructuralMutate to introduce `UnchangedOr<T>`,
    which can indicate that a mutation did not change the input value; if a
    value is changed, it is held in the container. Previously, Mutate simply
    returned a fresh copy of the original value when unchanged.
    Performance-wise, this results in a refcount bump and decref for every
    node examined, even when there is no change. Depending on the hardware
    platform, the overhead can be visible. This change introduces a special
    `UnchangedOr<T>`, which holds the `kTVMFFIUnchanged` tag to indicate
    that a mutation did not result in a change, while a changed value is
    propagated through the container. The canonical mutation hook pattern
    still works well, in a cleaner form:
    
    ```c++
    TVMFFIAny FooMutate(StructuralMutatorObj* mutator, AnyView value) noexcept {
      const FooNode* self =
          details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
FooNode>(value);
      TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Expr>, a,
                                        mutator->MutateExpected(self->a));
      TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Expr>, b,
                                        mutator->MutateExpected(self->b));
      if (a.UnchangedOrSameAs(self->a) && b.UnchangedOrSameAs(self->b)) {
        TVM_FFI_S_MUTATE_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)));
    }
    ```
    
    The engine is updated to be aware of the new convention.
---
 docs/concepts/structural_eq_hash.rst         |  12 +-
 include/tvm/ffi/c_api.h                      |   2 +
 include/tvm/ffi/expected.h                   |  44 +-
 include/tvm/ffi/extra/structural_mutate.h    | 579 +++++++++++++++++++++------
 include/tvm/ffi/extra/structural_visit.h     |  14 +-
 include/tvm/ffi/type_traits.h                |   2 +
 python/tvm_ffi/cython/base.pxi               |   1 +
 rust/tvm-ffi-sys/src/c_api.rs                |   2 +
 rust/tvm-ffi/src/extra/structural_mutate.rs  |  14 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs |  10 +
 src/ffi/extra/structural_mutate.cc           | 129 +++---
 src/ffi/object.cc                            |   1 +
 tests/cpp/extra/test_structural_mutate.cc    | 249 ++++++++++--
 tests/cpp/testing_object.h                   |  19 +-
 tests/python/test_structural.py              |   8 +
 15 files changed, 842 insertions(+), 244 deletions(-)

diff --git a/docs/concepts/structural_eq_hash.rst 
b/docs/concepts/structural_eq_hash.rst
index b013f11b..74efd998 100644
--- a/docs/concepts/structural_eq_hash.rst
+++ b/docs/concepts/structural_eq_hash.rst
@@ -1230,6 +1230,11 @@ structural child, and returns an interrupt if one occurs:
 
 A custom ``__s_mutate__`` hook similarly receives the active mutator.  It 
should
 recursively call ``mutator.mutate`` and return a new value only when needed.
+In C++, a hook can return ``Unchanged()`` when it produces no new value, or use
+``UnchangedOr<T>`` to carry either that marker or a replacement.  The mutator
+propagates the marker through recursive callback-facing entry points.  The
+top-level ``StructuralMap`` and ``StructuralMutate`` functions resolve it to 
the
+original value, so it never escapes as a mapped value.
 An optional ``__s_maybe_inplace_mutate__`` hook may implement an in-place
 optimization.  The structural-map engine dispatches it only when the input is
 safe to mutate, so the optional hook may rely on that ownership guarantee.  A
@@ -1287,8 +1292,9 @@ subtree. An unmatched value uses default descent:
          return visitor->VisitExpected(pair->lhs);
        });
 
-Walk callbacks return ``Expected<WalkResult>``.  Map callbacks return
-``Expected<Any>`` and must obey the same non-in-place callback contract as the
-Python API.  For ``Map`` and ``Dict``, both APIs process values and skip keys.
+Walk callbacks return ``Expected<WalkResult>``.  Map callbacks may return a 
bare
+replacement, ``Unchanged``, or ``Expected<Any>``, and must obey the same
+non-in-place callback contract as the Python API.
+For ``Map`` and ``Dict``, both APIs process values and skip keys.
 ``StructuralWalk``, ``StructuralVisit`` and ``StructuralMap`` are the
 corresponding throwing convenience forms.
diff --git a/include/tvm/ffi/c_api.h b/include/tvm/ffi/c_api.h
index 019bd665..0444a3c5 100644
--- a/include/tvm/ffi/c_api.h
+++ b/include/tvm/ffi/c_api.h
@@ -136,6 +136,8 @@ typedef enum {
   kTVMFFISmallStr = 11,
   /*! \brief Small bytes on stack */
   kTVMFFISmallBytes = 12,
+  /*! \brief Structural-mutation marker indicating that no new value was 
produced */
+  kTVMFFIUnchanged = 13,
   /*! \brief Start of statically defined objects. */
   kTVMFFIStaticObjectBegin = 64,
   /*!
diff --git a/include/tvm/ffi/expected.h b/include/tvm/ffi/expected.h
index ffe91a25..77ba7b39 100644
--- a/include/tvm/ffi/expected.h
+++ b/include/tvm/ffi/expected.h
@@ -459,20 +459,46 @@ struct ExpectedUnsafe {
 };
 
 /*!
- * \brief Return proxy used by early-return macros in raw or typed functions.
+ * \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 either the raw ``TVMFFIAny`` conversion used by
- * hooks or the same ``Expected<T>`` type used by typed helpers. Both
- * conversions are rvalue-qualified because handing off the stored payload 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.
+ * 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 MaybeReturnHelper {
+class ExpectedReturnHelper {
  public:
-  TVM_FFI_INLINE explicit MaybeReturnHelper(Expected<T>&& value) noexcept
+  TVM_FFI_INLINE explicit ExpectedReturnHelper(Expected<T>&& value) noexcept
       : value_(std::move(value)) {}
 
   // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
diff --git a/include/tvm/ffi/extra/structural_mutate.h 
b/include/tvm/ffi/extra/structural_mutate.h
index d6dac496..73659ae8 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -50,13 +50,15 @@ namespace tvm {
 namespace ffi {
 
 class StructuralMutatorObj;
+template <typename T>
+class UnchangedOr;
 
 /*!
  * \brief ABI callback type for structural mutation.
  *
  * \param mutator The active structural mutator.
  * \param value The borrowed value to mutate.
- * \return Raw ``TVMFFIAny`` containing the mutated value or an Error.
+ * \return Raw ``TVMFFIAny`` containing a replacement, the unchanged marker, 
or an Error.
  *
  * \note The hook is exception-free. Representable failures must be returned 
as an Error. Hook
  *       implementations should use non-throwing accessors when the engine's 
type dispatch has
@@ -103,7 +105,7 @@ struct StructuralMutatorVTable {
    *
    * \param mutator The active structural mutator.
    * \param value The borrowed value to mutate.
-   * \return Raw ``TVMFFIAny`` carrying the mutated value or Error.
+   * \return Raw ``TVMFFIAny`` carrying a replacement, the unchanged marker, 
or Error.
    */
   FStructuralMutate mutate = nullptr;
   /*!
@@ -111,7 +113,7 @@ struct StructuralMutatorVTable {
    *
    * \param mutator The active structural mutator.
    * \param value The borrowed value to mutate.
-   * \return Raw ``TVMFFIAny`` carrying the mutated value or Error.
+   * \return Raw ``TVMFFIAny`` carrying a replacement, the unchanged marker, 
or Error.
    *
    * The returned value may refer to the same object as \p value when the 
implementation mutates
    * that object in place.
@@ -136,6 +138,173 @@ struct StructuralMutatorVTable {
   FStructuralVarRemapSet var_remap_set = nullptr;
 };
 
+namespace details {
+struct UnchangedOrUnsafe;
+}  // namespace details
+
+/*! \brief Tag for a mutation result that produced no new value. */
+struct Unchanged {
+  /*!
+   * \brief Copy this tag to its raw marker representation.
+   * \return Raw ``TVMFFIAny`` carrying the reserved unchanged type index.
+   */
+  TVM_FFI_INLINE TVMFFIAny CopyToTVMFFIAny() const noexcept {
+    // The marker needs a reserved type index because every ordinary index is 
a legal mutation
+    // result. In particular, kTVMFFINone is a valid replacement and cannot 
double as the marker.
+    TVMFFIAny raw;
+    raw.type_index = TypeIndex::kTVMFFIUnchanged;
+    // invariance: always set the union padding part to 0
+    raw.zero_padding = 0;
+    raw.v_int64 = 0;
+    return raw;
+  }
+
+  /*!
+   * \brief Convert this tag to its owning marker representation.
+   * \return An owning ``Any`` carrying the reserved unchanged type index.
+   */
+  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+  TVM_FFI_INLINE operator Any() const noexcept {
+    TVMFFIAny raw = CopyToTVMFFIAny();
+    return details::AnyUnsafe::MoveTVMFFIAnyToAny(&raw);
+  }
+};
+
+/*!
+ * \brief A structural-mutation result containing a replacement or no new 
value.
+ *
+ * \tparam T The replacement value type.
+ * \note ``UnchangedOr`` is deliberately designed to only have 
rvalue-qualified value accessors,
+ *       so the compiler forces a value to leave the container exactly once, 
via a move.
+ *
+ * \code{.cpp}
+ * // resolves to the original when the descent reported unchanged
+ * copy->a = std::move(a).ValueOrUnchanged(std::move(copy->a));
+ * // already known to be changed, so no original is needed
+ * copy->b = std::move(b).ValueUnchecked();
+ * \endcode
+ */
+template <typename T>
+class UnchangedOr {
+ public:
+  static_assert(!std::is_base_of_v<Error, std::remove_cv_t<T>>,
+                "UnchangedOr<Error> is not supported");
+
+  /*!
+   * \brief Construct an unchanged result from its tag.
+   * \param unchanged The unchanged tag.
+   */
+  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+  TVM_FFI_INLINE UnchangedOr(Unchanged unchanged) noexcept : 
data_(static_cast<Any>(unchanged)) {}
+
+  /*!
+   * \brief Construct a changed result from a replacement value.
+   * \param value The replacement value.
+   */
+  // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+  TVM_FFI_INLINE UnchangedOr(T value) : data_(Any(std::move(value))) {}
+
+  /// \cond Doxygen_Suppress
+  TVM_FFI_INLINE UnchangedOr(const UnchangedOr&) = default;
+  TVM_FFI_INLINE UnchangedOr(UnchangedOr&&) noexcept = default;
+  /// \endcond
+  TVM_FFI_INLINE ~UnchangedOr() = default;
+  TVM_FFI_INLINE UnchangedOr& operator=(const UnchangedOr&) = default;
+  TVM_FFI_INLINE UnchangedOr& operator=(UnchangedOr&&) noexcept = default;
+
+  /*!
+   * \brief Whether this result asks the caller to preserve the original value.
+   * \return Whether the result is unchanged.
+   */
+  TVM_FFI_INLINE bool IsUnchanged() const& noexcept {
+    return data_.type_index() == TypeIndex::kTVMFFIUnchanged;
+  }
+
+  /*!
+   * \brief Whether this result is unchanged or contains the original object 
identity.
+   * \param original The original value.
+   * \return Whether the original identity may be reused.
+   */
+  TVM_FFI_INLINE bool UnchangedOrSameAs(const T& original) const& noexcept {
+    return IsUnchanged() || data_.same_as(original);
+  }
+
+  /*!
+   * \brief Move the replacement, or move \p original when unchanged.
+   * \param original The owned original value.
+   * \return The replacement or original value.
+   * \note Passing a named lvalue transfers ownership and may leave it 
moved-from.
+   */
+  TVM_FFI_INLINE T ValueOrUnchanged(T& original) && {
+    return IsUnchanged() ? std::move(original)
+                         : 
details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
+  }
+
+  /*!
+   * \brief Move the replacement, or move \p original when unchanged.
+   * \param original The owned original value.
+   * \return The replacement or original value.
+   */
+  TVM_FFI_INLINE T ValueOrUnchanged(T&& original) && {
+    return IsUnchanged() ? std::move(original)
+                         : 
details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
+  }
+
+  /*!
+   * \brief Move the replacement, or materialize \p original when unchanged.
+   * \tparam U The replacement type, constrained to ``Any``.
+   * \param original The borrowed original value.
+   * \return The replacement or original value.
+   */
+  template <typename U = T,
+            typename = std::enable_if_t<std::is_same_v<T, Any> && 
std::is_same_v<U, T>>>
+  TVM_FFI_INLINE Any ValueOrUnchanged(AnyView original) && {
+    return IsUnchanged() ? Any(original)
+                         : 
details::AnyUnsafe::MoveFromAnyAfterCheck<Any>(std::move(data_));
+  }
+
+  /*!
+   * \brief Move the known-changed replacement without checking its state.
+   * \return The replacement value.
+   * \pre The result is not unchanged.
+   */
+  TVM_FFI_INLINE T ValueUnchecked() && {
+    return details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
+  }
+
+ private:
+  friend struct details::UnchangedOrUnsafe;
+  template <typename, typename>
+  friend struct TypeTraits;
+  struct UnsafeInit {};
+  TVM_FFI_INLINE explicit UnchangedOr(UnsafeInit, Any data) noexcept : 
data_(std::move(data)) {}
+  Any data_;
+};
+
+namespace details {
+/*! \brief Unsafe moves between UnchangedOr and its single Any storage. */
+struct UnchangedOrUnsafe {
+  template <typename T>
+  TVM_FFI_INLINE static TVMFFIAny MoveToTVMFFIAny(UnchangedOr<T>&& result) 
noexcept {
+    return AnyUnsafe::MoveAnyToTVMFFIAny(std::move(result.data_));
+  }
+};
+
+}  // namespace details
+
+namespace details {
+// Out of line so its strings and Error construction stay out of the hot path 
of whatever hook
+// body TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN expands into. Same reason as
+// BadStructuralMutateHookError.
+// 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", "")));
+}
+}  // namespace details
+
 /*!
  * \brief Object node of a structural mutator.
  */
@@ -148,69 +317,104 @@ class StructuralMutatorObj : public Object {
    * \brief Mutate a value through the mutator vtable.
    *
    * \param value The value to mutate.
-   * \return The mutated owning value.
+   * \tparam T The declared replacement type.
+   * \return The replacement or unchanged marker.
    * \throws Error if mutation fails.
    *
    * This entry point never intentionally mutates \p value in place. Recursive 
mutations
    * also use \ref Mutate.
+   *
+   * \code{.cpp}
+   * Expr new_node = 
mutator->Mutate<Expr>(node).ValueOrUnchanged(std::move(node));
+   * \endcode
    */
-  TVM_FFI_INLINE Any Mutate(AnyView value) { return 
MutateExpected(value).value(); }
+  template <typename T = Any>
+  TVM_FFI_INLINE UnchangedOr<T> Mutate(AnyView value) {
+    return std::move(MutateExpected<T>(value)).value();
+  }
 
   /*!
    * \brief Exception-free form of \ref Mutate.
    *
    * \param value The value to mutate.
-   * \return The mutated owning value, or an Error if mutation failed.
+   * \tparam T The declared replacement type.
+   * \return The replacement or unchanged marker, or an Error if mutation 
failed.
    */
-  TVM_FFI_INLINE Expected<Any> MutateExpected(AnyView value) noexcept {
-    return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>((*vtable_->mutate)(this, 
value));
+  template <typename T = Any>
+  TVM_FFI_INLINE Expected<UnchangedOr<T>> MutateExpected(AnyView value) 
noexcept {
+    if constexpr (std::is_same_v<T, Any>) {
+      return details::ExpectedUnsafe::MoveFromTVMFFIAny<UnchangedOr<T>>(
+          (*vtable_->mutate)(this, value));
+    } else {
+      TVMFFIAny result = (*vtable_->mutate)(this, value);
+      if 
(TVM_FFI_PREDICT_FALSE(!TypeTraits<Expected<UnchangedOr<T>>>::CheckAnyStrict(&result)))
 {
+        (void)details::AnyUnsafe::MoveTVMFFIAnyToAny(&result);
+        return details::SMutateDeclaredTypeError();
+      }
+      return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<UnchangedOr<T>>(result);
+    }
   }
 
   /*!
    * \brief Mutate a value, permitting an in-place implementation when it is 
safe.
    *
    * \param value The borrowed value to mutate.
-   * \return The mutated owning value.
+   * \tparam T The declared replacement type.
+   * \return The replacement or unchanged marker.
    * \throws Error if mutation fails.
    *
    * The returned value may refer to the same object as \p value. Callers must 
use the return value
    * as the result of the mutation rather than assuming that the input object 
was reused.
    */
-  TVM_FFI_INLINE Any MaybeInplaceMutate(AnyView value) {
-    return MaybeInplaceMutateExpected(value).value();
+  template <typename T = Any>
+  TVM_FFI_INLINE UnchangedOr<T> MaybeInplaceMutate(AnyView value) {
+    return std::move(MaybeInplaceMutateExpected<T>(value)).value();
   }
 
   /*!
    * \brief Exception-free form of \ref MaybeInplaceMutate.
    *
    * \param value The borrowed value to mutate.
-   * \return The mutated owning value, or an Error if mutation failed.
+   * \tparam T The declared replacement type.
+   * \return The replacement or unchanged marker, or an Error if mutation 
failed.
    *
    * \note Call only from a ``__s_maybe_inplace_mutate__`` hook, which is 
dispatched
    *       only for a value whose entire path from the root is uniquely owned.
    */
-  TVM_FFI_INLINE Expected<Any> MaybeInplaceMutateExpected(AnyView value) 
noexcept {
-    return details::ExpectedUnsafe::MoveFromTVMFFIAny<Any>(
-        (*vtable_->maybe_inplace_mutate)(this, value));
+  template <typename T = Any>
+  TVM_FFI_INLINE Expected<UnchangedOr<T>> MaybeInplaceMutateExpected(AnyView 
value) noexcept {
+    if constexpr (std::is_same_v<T, Any>) {
+      return details::ExpectedUnsafe::MoveFromTVMFFIAny<UnchangedOr<T>>(
+          (*vtable_->maybe_inplace_mutate)(this, value));
+    } else {
+      TVMFFIAny result = (*vtable_->maybe_inplace_mutate)(this, value);
+      if 
(TVM_FFI_PREDICT_FALSE(!TypeTraits<Expected<UnchangedOr<T>>>::CheckAnyStrict(&result)))
 {
+        (void)details::AnyUnsafe::MoveTVMFFIAnyToAny(&result);
+        return details::SMutateDeclaredTypeError();
+      }
+      return 
details::ExpectedUnsafe::MoveFromTVMFFIAny<UnchangedOr<T>>(result);
+    }
   }
 
   /*!
    * \brief Mutate a value, using in-place mutation only for a uniquely owned 
object.
-   *
+   * \tparam T The declared replacement type.
    * \param value The borrowed value to mutate.
-   * \return The mutated owning value, or an Error if mutation failed.
+   * \return The replacement or unchanged marker, or an Error if mutation 
failed.
    *
    * \note The caller must already know the entire path from the root is 
uniquely
    *       owned, either through an owning moved-in root or while handling a
    *       ``__s_maybe_inplace_mutate__`` hook. This method checks only \p 
value
    *       itself, not its ancestors.
    */
-  TVM_FFI_INLINE Expected<Any> MaybeInplaceMutateIfUniqueExpected(AnyView 
value) noexcept {
+  template <typename T = Any>
+  TVM_FFI_INLINE Expected<UnchangedOr<T>> MaybeInplaceMutateIfUniqueExpected(
+      AnyView value) noexcept {
     const Object* obj = value.as<Object>();
     if (obj != nullptr && obj->unique()) {
-      return MaybeInplaceMutateExpected(value);
+      return MaybeInplaceMutateExpected<T>(value);
     }
-    return MutateExpected(value);
+    return MutateExpected<T>(value);
   }
 
   /*!
@@ -335,16 +539,16 @@ class StructuralMutatorObj : public Object {
         Error("TypeError", "__s_mutate__ must be an opaque function pointer or 
ffi.Function", ""));
   }
 
-  // Convention: the ABI boundary is a raw TVMFFIAny; everything inside a 
callback or hook body
-  // works in Expected<Any> and moves out to TVMFFIAny at that boundary.
+  // Convention: the ABI boundary is a raw TVMFFIAny; mutation results inside 
a callback or hook
+  // body use Expected<Any> and move out to TVMFFIAny at that boundary. 
Unchanged converts to an
+  // Any carrying kTVMFFIUnchanged.
   //
   // The Raw forms below exist because that boundary is also the default path. 
A hook is a C-ABI
   // function pointer returning TVMFFIAny, a 16-byte POD that stays in 
registers; wrapping the
-  // result in Expected<Any> would force it to memory, since Expected<Any> is 
not trivially
-  // destructible and is therefore classified MEMORY. Descent through an 
unmatched node calls a
-  // hook and returns its result unchanged, so keeping that path raw removes 
the round trip
-  // entirely. Only a matched callback pays for an Expected<Any>, and it 
materializes an Any
-  // anyway to record its remap entry.
+  // result in Expected<Any> would force it to memory because the C++ wrapper 
is not
+  // trivially destructible and is therefore classified MEMORY. Descent 
through an unmatched node
+  // calls a hook and returns its result unchanged, so keeping that path raw 
removes the round trip
+  // entirely. Only a matched callback pays for the Expected wrapper.
   //
   // Engine-internal: subclasses call the Expected forms above.
   /*! \brief Raw default mutation: attr lookup then hook, favouring the fn-ptr 
case. */
@@ -400,9 +604,9 @@ class StructuralMutatorObj : public Object {
     // reflected walk runs under an identity remap.
     const bool remappable = IsRemappableIdentity(value.type_index());
     if (remappable) {
+      // Only None means no cached remap; every other result is returned 
directly.
       Expected<Any> mapped = VarRemapGetExpected(value);
-      if (TVM_FFI_PREDICT_FALSE(mapped.is_err()) ||
-          details::ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
+      if (details::ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
         return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(mapped));
       }
     }
@@ -411,8 +615,10 @@ class StructuralMutatorObj : public Object {
       return details::ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
     }
     if (remappable) {
-      Expected<void> set_result =
-          VarRemapSetExpected(value, details::ExpectedUnsafe::GetData(result));
+      const Any& result_value = details::ExpectedUnsafe::GetData(result);
+      AnyView value_to_store =
+          result_value.type_index() == TypeIndex::kTVMFFIUnchanged ? value : 
AnyView(result_value);
+      Expected<void> set_result = VarRemapSetExpected(value, value_to_store);
       if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
         return details::ExpectedUnsafe::MoveToTVMFFIAny(
             Expected<Any>(Unexpected(std::move(set_result).error())));
@@ -546,7 +752,8 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
             return true;
           }
 
-          Expected<Any> mutated_field = [&]() -> Expected<Any> {
+          // Reflected fields use the same unchanged-or-value descent protocol.
+          Expected<UnchangedOr<Any>> mutated_field = [&]() -> 
Expected<UnchangedOr<Any>> {
             if (field_info->flags & kTVMFFIFieldFlagBitMaskSEqHashDefSimple) {
               return mutator->WithDefRegionKind(kTVMFFIDefRegionKindSimple, 
[&]() {
                 return mutator->MutateExpected(field_value);
@@ -563,8 +770,11 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
             result = Unexpected(std::move(mutated_field).error());
             return true;
           }
-          const Any& new_field = 
details::ExpectedUnsafe::GetData(mutated_field);
-          if (field_value.same_as(new_field)) {
+          const Any& mutated_field_data = 
details::ExpectedUnsafe::GetData(mutated_field);
+          // Unchanged first: it is the common case, and it is one type-index 
test where the
+          // resolved form ran a full same_as against a value it had just been 
handed back.
+          if (mutated_field_data.type_index() == TypeIndex::kTVMFFIUnchanged ||
+              field_value.same_as(mutated_field_data)) {
             return false;
           }
 
@@ -579,8 +789,8 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
             return true;
           }
 
-          ret_code = reflection::CallFieldSetter(field_info, field_addr,
-                                                 reinterpret_cast<const 
TVMFFIAny*>(&new_field));
+          ret_code = reflection::CallFieldSetter(
+              field_info, field_addr, reinterpret_cast<const 
TVMFFIAny*>(&mutated_field_data));
           if (TVM_FFI_PREDICT_FALSE(ret_code != 0)) {
             result = Unexpected(details::MoveFromSafeCallRaised());
             return true;
@@ -603,7 +813,7 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
     return result;
   }
   if (!field_changed) {
-    return Any(value);
+    return Unchanged();
   }
   return result;
 }
@@ -615,36 +825,27 @@ TVM_FFI_INLINE static Expected<Any> 
MutateReflectedFieldsExpected(StructuralMuta
 // ---------------------------------------------------------------------------
 
 namespace details {
-
 /// \cond Doxygen_Suppress
 // Return from the current raw or same-T Expected mutation function if Result 
is an Error.
-// The rvalue-only proxy 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::MaybeReturnHelper(::std::move(tvm_ffi_res_)); \
-    }                                                                          
 \
+// 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())); \
+    }                                                                 \
   } while (0)
 
 /// \endcond
 
-// Out of line so its strings and Error construction stay out of the hot path 
of whatever hook
-// body TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN expands into. Same reason as
-// BadStructuralMutateHookError.
-TVM_FFI_COLD_CODE inline Expected<Any> SMutateDeclaredTypeError() noexcept {
-  return Unexpected(
-      Error("TypeError", "structural mutate result does not match the declared 
type", ""));
-}
-
 /// \cond Doxygen_Suppress
 #define TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN_IMPL_(Result, Type, Name, 
ResultExpr)    \
   auto Result = (ResultExpr); /* NOLINT(bugprone-macro-parentheses) */         
    \
   TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result);                                 
    \
   if 
(TVM_FFI_PREDICT_FALSE(!::tvm::ffi::details::AnyUnsafe::CheckAnyStrict<Type>( \
           ::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))) {            
    \
-    return ::tvm::ffi::details::MaybeReturnHelper(                             
    \
-        ::tvm::ffi::details::SMutateDeclaredTypeError());                      
    \
+    return ::tvm::ffi::details::SMutateDeclaredTypeError();                    
    \
   }                                                                            
    \
   Type Name = /* NOLINT(bugprone-macro-parentheses) */                         
    \
       ::tvm::ffi::details::AnyUnsafe::MoveFromAnyAfterCheck<Type>(             
    \
@@ -657,23 +858,65 @@ TVM_FFI_COLD_CODE inline Expected<Any> 
SMutateDeclaredTypeError() noexcept {
  * ``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,
  * reported with a fixed string so a correct hook pays only one 
predicted-not-taken branch per
- * field. Its early returns work from either a raw ``TVMFFIAny`` hook or a 
same-T ``Expected<T>``
- * helper. This macro declares ``Name`` into the enclosing scope and must be 
used in a braced
- * block, never as an unbraced control-flow body.
+ * field. Its early returns work from either a raw ``TVMFFIAny`` hook or an
+ * ``Expected<UnchangedOr<Any>>`` helper. 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}
- * TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, child, 
mutator->MutateExpected(self->child));
+ * TVMFFIAny FooMutate(StructuralMutatorObj* mutator, AnyView value) noexcept {
+ *   const FooNode* self =
+ *       details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
FooNode>(value);
+ *   TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Expr>, a,
+ *                                     mutator->MutateExpected(self->a));
+ *   TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Expr>, b,
+ *                                     mutator->MutateExpected(self->b));
+ *   if (a.UnchangedOrSameAs(self->a) && b.UnchangedOrSameAs(self->b)) {
+ *     return Unchanged().CopyToTVMFFIAny();
+ *   }
+ *   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)));
+ * }
  * \endcode
  *
- * \param Type The concrete type of the successful value.
+ * Keep one statement per traversed field. A field skipped intentionally must 
be guarded and carry
+ * a ``// skips:`` note.
+ *
+ * \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
  */
 #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__), \
                                           Type, Name, ResultExpr)
 
+/// \cond Doxygen_Suppress
+#define TVM_FFI_S_MUTATE_UNSAFE_ASSIGN_OR_RETURN_UNCHECKED_IMPL_(Result, Type, 
Name, ResultExpr) \
+  auto Result = (ResultExpr); /* NOLINT(bugprone-macro-parentheses) */         
                  \
+  TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(Result);                                 
                  \
+  Type Name = /* NOLINT(bugprone-macro-parentheses) */                         
                  \
+      ::tvm::ffi::details::AnyUnsafe::MoveFromAnyAfterCheck<Type>(             
                  \
+          ::std::move(::tvm::ffi::details::ExpectedUnsafe::GetData(Result)))
+/// \endcond
+
+/*!
+ * \brief Unwrap a successful mutation result when the caller guarantees it to 
be ``Type``.
+ *
+ * This is an unsafe form that can only be used when the mutation contract 
guarantees the result
+ * type.
+ *
+ * \param Type The guaranteed concrete type of the successful value.
+ * \param Name The name of the value declared in the enclosing scope.
+ * \param ResultExpr An expression producing the ``Expected`` value to unwrap.
+ * \sa TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN
+ */
+#define TVM_FFI_S_MUTATE_UNSAFE_ASSIGN_OR_RETURN_UNCHECKED(Type, Name, 
ResultExpr) \
+  TVM_FFI_S_MUTATE_UNSAFE_ASSIGN_OR_RETURN_UNCHECKED_IMPL_(                    
    \
+      TVM_FFI_STR_CONCAT(tvm_ffi_mutate_result_, __COUNTER__), Type, Name, 
ResultExpr)
+
 /// \cond Doxygen_Suppress
 #define TVM_FFI_UNSAFE_S_MUTATE_ASSIGN_OR_RETURN_SKIP_CHECK_IMPL_(Result, 
Type, Name, ResultExpr) \
   auto Result = (ResultExpr); /* NOLINT(bugprone-macro-parentheses) */         
                   \
@@ -905,7 +1148,7 @@ class StructuralMapEngine : public Parent {
     using FuncInfo = details::FunctionInfo<std::decay_t<Callback>>;
     static_assert(std::is_convertible_v<typename FuncInfo::RetType, 
Expected<Any>>,
                   "StructuralMap callbacks must return a replacement value, 
Error, Unexpected, "
-                  "or Expected<U> implicitly convertible to Expected<Any>");
+                  "unchanged marker, or Expected<Any>");
     static_assert(
         FuncInfo::num_args == 1 + sizeof...(Is) || FuncInfo::num_args == 2 + 
sizeof...(Is),
         "StructuralMap callback takes (value, state...) with an optional 
trailing "
@@ -983,14 +1226,9 @@ class StructuralMapEngine : public Parent {
       return this->IsRemappableIdentity(value.type_index());
     }();
 
-    // A FreeVar or DAG node maps once and every later occurrence reuses that 
result, so if
-    // this node already has a cached remap entry, return it instead of 
mutating it again.
     if (remappable) {
+      // Only None means no cached remap; every other result is returned 
directly.
       Expected<Any> mapped = this->VarRemapGetExpected(value);
-      if (mapped.is_err()) {
-        *out = std::move(mapped);
-        return true;
-      }
       if (ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
         *out = std::move(mapped);
         return true;
@@ -1016,29 +1254,32 @@ class StructuralMapEngine : public Parent {
         *out = std::move(callback_result);
         return true;
       }
-      // Own the callback's result: it is the only reference from here on, and 
moving it out
-      // beats holding a reference into an Expected that stays alive across 
the descent below.
-      Any mapped_value = ExpectedUnsafe::GetData(callback_result);
+      Any mapped_value = std::move(ExpectedUnsafe::GetData(callback_result));
+      const AnyView descent_view =
+          mapped_value.type_index() == TypeIndex::kTVMFFIUnchanged ? value : 
AnyView(mapped_value);
       // Each descent names the node it actually ran on in the error context.
       *out = [&]() -> Expected<Any> {
         if constexpr (kMaybeInplace) {
           // A pre-order result can be mutated in place if unchanged or 
uniquely owned.
-          const TVMFFIAny* mapped_data = 
AnyUnsafe::TVMFFIAnyPtrFromAny(mapped_value);
+          const TVMFFIAny descent_data = descent_view.CopyToTVMFFIAny();
           const TVMFFIAny input_data = value.CopyToTVMFFIAny();
-          if (mapped_data->type_index == input_data.type_index &&
-              mapped_data->zero_padding == input_data.zero_padding &&
-              mapped_data->v_int64 == input_data.v_int64) {
+          if (descent_data.type_index == input_data.type_index &&
+              descent_data.zero_padding == input_data.zero_padding &&
+              descent_data.v_int64 == input_data.v_int64) {
             return this->DefaultMaybeInplaceMutateExpected(value);
           }
-          const Object* mapped_obj = mapped_value.as<Object>();
+          const Object* mapped_obj = descent_view.as<Object>();
           bool can_inplace = mapped_obj != nullptr && mapped_obj->unique();
-          return can_inplace ? 
this->DefaultMaybeInplaceMutateExpected(mapped_value)
-                             : this->DefaultMutateExpected(mapped_value);
+          return can_inplace ? 
this->DefaultMaybeInplaceMutateExpected(descent_view)
+                             : this->DefaultMutateExpected(descent_view);
         } else {
-          return this->DefaultMutateExpected(mapped_value);
+          return this->DefaultMutateExpected(descent_view);
         }
       }();
       if (TVM_FFI_PREDICT_FALSE(out->is_err())) return true;
+      if (ExpectedUnsafe::GetData(*out).type_index() == 
TypeIndex::kTVMFFIUnchanged) {
+        *out = std::move(mapped_value);
+      }
     } else {
       // Post-order: children are mapped first and the callback sees the 
rebuilt node, so it
       // observes its operands already substituted.
@@ -1048,20 +1289,21 @@ class StructuralMapEngine : public Parent {
         *out = std::move(descended);
         return true;
       }
-      // Held by reference, not moved out: the error path below names this 
node, so it has to
-      // survive the callback. Only a storage-enabled TSub could gain from a 
move, and that is
-      // exactly the case whose move would empty it.
-      const Any& mapped_value = ExpectedUnsafe::GetData(descended);
+      // Descended unchanged uses the original view; otherwise the callback 
sees the replacement.
+      const Any& descended_value = ExpectedUnsafe::GetData(descended);
+      const AnyView mapped_view = descended_value.type_index() == 
TypeIndex::kTVMFFIUnchanged
+                                      ? value
+                                      : AnyView(descended_value);
       *out = [&]() -> Expected<Any> {
         if constexpr (std::is_same_v<TSub, AnyView>) {
-          return InvokeTypedCallbackLink(callback, AnyView(mapped_value), 
StateIndices{});
+          return InvokeTypedCallbackLink(callback, mapped_view, 
StateIndices{});
         } else if constexpr (std::is_same_v<TSub, Any>) {
-          return InvokeTypedCallbackLink(callback, Any(mapped_value), 
StateIndices{});
+          return InvokeTypedCallbackLink(callback, Any(mapped_view), 
StateIndices{});
         } else {
           // Re-converted rather than reusing the match: the callback is 
invoked on the node
           // descent handed back, and must only see the type it asked for. 
Default mutation is
           // required to preserve the type, so failing here means some hook 
broke that.
-          std::optional<TSub> descended_sub = mapped_value.template as<TSub>();
+          std::optional<TSub> descended_sub = mapped_view.template as<TSub>();
           if (TVM_FFI_PREDICT_FALSE(!descended_sub.has_value())) {
             return this->SMutateDescentTypeError();
           }
@@ -1069,14 +1311,25 @@ class StructuralMapEngine : public Parent {
         }
       }();
       if (TVM_FFI_PREDICT_FALSE(out->is_err())) {
-        this->UpdateVisitErrorContext(*out, mapped_value);
+        this->UpdateVisitErrorContext(*out, mapped_view);
         return true;
       }
     }
 
-    // Bind this node's identity to its final result, so every later 
occurrence reuses it.
+    // An unchanged result binds this node's original value for later 
occurrences.
+    if (ExpectedUnsafe::GetData(*out).type_index() == 
TypeIndex::kTVMFFIUnchanged) {
+      // Bind this node's identity to its final result, so every later 
occurrence reuses it.
+      if (remappable) {
+        Expected<void> set_result = this->VarRemapSetExpected(value, value);
+        if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
+          *out = Unexpected(std::move(set_result).error());
+        }
+      }
+      return true;
+    }
     if (remappable) {
-      Expected<void> set_result = this->VarRemapSetExpected(value, 
ExpectedUnsafe::GetData(*out));
+      Expected<void> set_result =
+          this->VarRemapSetExpected(value, 
AnyView(ExpectedUnsafe::GetData(*out)));
       if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
         *out = Unexpected(std::move(set_result).error());
       }
@@ -1241,11 +1494,8 @@ class StructuralMapDynEngine : public Parent {
     // A FreeVar or DAG node maps once and every later occurrence reuses that 
result.
     const bool remappable = this->IsRemappableIdentity(value.type_index());
     if (remappable) {
+      // Only None means no cached remap; every other result is returned 
directly.
       Expected<Any> mapped = this->VarRemapGetExpected(value);
-      if (mapped.is_err()) {
-        *out = std::move(mapped);
-        return true;
-      }
       if (ExpectedUnsafe::GetData(mapped).type_index() != 
TypeIndex::kTVMFFINone) {
         *out = std::move(mapped);
         return true;
@@ -1262,25 +1512,30 @@ class StructuralMapDynEngine : public Parent {
         *out = std::move(callback_result);
         return true;
       }
-      Any mapped_value = ExpectedUnsafe::GetData(callback_result);
+      Any mapped_value = std::move(ExpectedUnsafe::GetData(callback_result));
+      const AnyView descent_view =
+          mapped_value.type_index() == TypeIndex::kTVMFFIUnchanged ? value : 
AnyView(mapped_value);
       *out = [&]() -> Expected<Any> {
         if constexpr (kMaybeInplace) {
-          const TVMFFIAny* mapped_data = 
AnyUnsafe::TVMFFIAnyPtrFromAny(mapped_value);
+          const TVMFFIAny descent_data = descent_view.CopyToTVMFFIAny();
           const TVMFFIAny input_data = value.CopyToTVMFFIAny();
-          if (mapped_data->type_index == input_data.type_index &&
-              mapped_data->zero_padding == input_data.zero_padding &&
-              mapped_data->v_int64 == input_data.v_int64) {
+          if (descent_data.type_index == input_data.type_index &&
+              descent_data.zero_padding == input_data.zero_padding &&
+              descent_data.v_int64 == input_data.v_int64) {
             return this->DefaultMaybeInplaceMutateExpected(value);
           }
-          const Object* mapped_obj = mapped_value.as<Object>();
+          const Object* mapped_obj = descent_view.as<Object>();
           bool can_inplace = mapped_obj != nullptr && mapped_obj->unique();
-          return can_inplace ? 
this->DefaultMaybeInplaceMutateExpected(mapped_value)
-                             : this->DefaultMutateExpected(mapped_value);
+          return can_inplace ? 
this->DefaultMaybeInplaceMutateExpected(descent_view)
+                             : this->DefaultMutateExpected(descent_view);
         } else {
-          return this->DefaultMutateExpected(mapped_value);
+          return this->DefaultMutateExpected(descent_view);
         }
       }();
       if (TVM_FFI_PREDICT_FALSE(out->is_err())) return true;
+      if (ExpectedUnsafe::GetData(*out).type_index() == 
TypeIndex::kTVMFFIUnchanged) {
+        *out = std::move(mapped_value);
+      }
     } else {
       // Post-order: children are mapped first, so the callback sees the 
rebuilt node.
       Expected<Any> descended = kMaybeInplace ? 
this->DefaultMaybeInplaceMutateExpected(value)
@@ -1289,28 +1544,43 @@ class StructuralMapDynEngine : public Parent {
         *out = std::move(descended);
         return true;
       }
-      const Any& mapped_value = ExpectedUnsafe::GetData(descended);
+      // See the typed engine: a borrowed view of the original, never an 
owning copy of it.
+      const Any& descended_value = ExpectedUnsafe::GetData(descended);
+      const AnyView mapped_view = descended_value.type_index() == 
TypeIndex::kTVMFFIUnchanged
+                                      ? value
+                                      : AnyView(descended_value);
       // Selection used the input node. Recheck the descended node against 
that same registered
       // target before invoking the saved link.
       if (TVM_FFI_PREDICT_FALSE(
-              !details::RuntimeTypeIndexMatch(mapped_value.type_index(), 
link_type_index))) {
+              !details::RuntimeTypeIndexMatch(mapped_view.type_index(), 
link_type_index))) {
         *out = this->SMutateDescentTypeError();
-        this->UpdateVisitErrorContext(*out, mapped_value);
+        this->UpdateVisitErrorContext(*out, mapped_view);
         return true;
       }
       // WithDefRegionKind restores its state through RAII, so this late read 
is equivalent to
       // the typed engine's invocation-time read even after recursive descent.
-      *out = InvokeLink(*matched, with_kind, mapped_value, 
this->def_region_kind());
+      *out = InvokeLink(*matched, with_kind, mapped_view, 
this->def_region_kind());
       if (TVM_FFI_PREDICT_FALSE(out->is_err())) {
-        this->UpdateVisitErrorContext(*out, mapped_value);
+        this->UpdateVisitErrorContext(*out, mapped_view);
         return true;
       }
     }
 
-    // --- identity remap, exit half ------------------------------------------
-    // Bind this node's identity to its final result for later occurrences.
+    // An unchanged result binds this node's original value for later 
occurrences.
+    if (ExpectedUnsafe::GetData(*out).type_index() == 
TypeIndex::kTVMFFIUnchanged) {
+      // --- identity remap, exit half ----------------------------------------
+      // Bind this node's identity to its final result for later occurrences.
+      if (remappable) {
+        Expected<void> set_result = this->VarRemapSetExpected(value, value);
+        if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
+          *out = Unexpected(std::move(set_result).error());
+        }
+      }
+      return true;
+    }
     if (remappable) {
-      Expected<void> set_result = this->VarRemapSetExpected(value, 
ExpectedUnsafe::GetData(*out));
+      Expected<void> set_result =
+          this->VarRemapSetExpected(value, 
AnyView(ExpectedUnsafe::GetData(*out)));
       if (TVM_FFI_PREDICT_FALSE(set_result.is_err())) {
         *out = Unexpected(std::move(set_result).error());
       }
@@ -1484,12 +1754,12 @@ class StructuralMutateEngine : public Parent {
  * argument is borrowed and must not be retained after the callback returns.
  *
  * Each callback should follow map semantics: it must not mutate the input in 
place and should
- * return a bare Any-convertible replacement or ``Expected<U>`` where ``U`` is 
Any-convertible.
- * A callback may instead return an error value or throw ``Error`` to stop the 
mapping. In
- * pre-order, an unchanged input or uniquely owned replacement may
- * continue through ``MaybeInplaceMutate``; a shared replacement uses 
``Mutate``. In post-order,
- * the callback runs after the node's optional in-place mutation. In-place 
mutation is available
- * only through an explicit ``__s_maybe_inplace_mutate__`` hook.
+ * return a bare Any-convertible replacement, ``Unchanged``, 
``UnchangedOr<U>``, or
+ * ``Expected<U>`` where ``U`` is a supported result type. A callback may 
instead return an error
+ * value or throw ``Error`` to stop the mapping. In pre-order, an unchanged 
input or uniquely owned
+ * replacement may continue through ``MaybeInplaceMutate``; a shared 
replacement uses ``Mutate``.
+ * In post-order, the callback runs after the node's optional in-place 
mutation. In-place mutation
+ * is available only through an explicit ``__s_maybe_inplace_mutate__`` hook.
  *
  * Objects marked ``kTVMFFISEqHashKindFreeVar`` or 
``kTVMFFISEqHashKindDAGNode`` are
  * identity-substituted. A callback is invoked only for the first occurrence 
of each identity; its
@@ -1519,11 +1789,11 @@ class StructuralMutateEngine : public Parent {
  * \tparam Callbacks Callback types whose first parameters select matching 
values.
  * \param root The owning root value to map.
  * \param callbacks Callbacks tested in declaration order. Each accepts 
``(value)`` or
- *        ``(value, def_region_kind)`` and returns a bare Any-convertible 
replacement,
- *        ``Expected<U>`` where ``U`` is Any-convertible, or an error value.
+ *        ``(value, def_region_kind)`` and returns a replacement, 
``Unchanged``, or
+ *        ``Expected<Any>``.
  * \return The mapped owning value, or an Error if mapping or a callback fails.
  *
- * \note Returning ``Expected<U>`` expresses errors as values; throwing 
``Error`` is also
+ * \note Returning ``Expected<Any>`` expresses errors as values; throwing 
``Error`` is also
  *       supported and is converted to the error state.
  * \note Pass an owned root with ``std::move(root)`` to permit root reuse. In a
  *       ``__s_maybe_inplace_mutate__`` hook, a nested owned field follows the 
idiom
@@ -1536,7 +1806,11 @@ Expected<Any> StructuralMapExpected(
   static_assert(sizeof...(Callbacks) != 0, "StructuralMap requires at least 
one callback");
   using Mutator = StructuralMapEngine<StructuralMapEngineBase, order, 
std::decay_t<Callbacks>...>;
   StructuralMutator 
mutator(make_object<Mutator>(std::forward<Callbacks>(callbacks)...));
-  return mutator->MaybeInplaceMutateIfUniqueExpected(root);
+  auto result = mutator->MaybeInplaceMutateIfUniqueExpected(root);
+  if (TVM_FFI_PREDICT_FALSE(result.is_err())) return 
Unexpected(std::move(result).error());
+  UnchangedOr<Any> mapped = 
details::AnyUnsafe::MoveFromAnyAfterCheck<UnchangedOr<Any>>(
+      std::move(details::ExpectedUnsafe::GetData(result)));
+  return std::move(mapped).ValueOrUnchanged(std::move(root));
 }
 
 /*!
@@ -1549,12 +1823,12 @@ Expected<Any> StructuralMapExpected(
  * \tparam Callbacks Callback types whose first parameters select matching 
values.
  * \param root The owning root value to map.
  * \param callbacks Callbacks tested in declaration order. Each accepts 
``(value)`` or
- *        ``(value, def_region_kind)`` and returns a bare Any-convertible 
replacement,
- *        ``Expected<U>`` where ``U`` is Any-convertible, or an error value.
+ *        ``(value, def_region_kind)`` and returns a replacement, 
``Unchanged``, or
+ *        ``Expected<Any>``.
  * \return The mapped owning value.
  * \throws Error if mapping or a callback fails.
  *
- * \note Returning ``Expected<U>`` expresses errors as values; throwing 
``Error`` is also
+ * \note Returning ``Expected<Any>`` expresses errors as values; throwing 
``Error`` is also
  *       supported and is rethrown by this interface.
  * \note Pass an owned root with ``std::move(root)`` to permit root reuse.
  */
@@ -1569,15 +1843,14 @@ Any StructuralMap(Any root,
 /*!
  * \brief Mutate a structured value with callbacks that own recursion.
  *
- * A callback takes one of two signatures:
+ * A callback takes one of two forms, where ``R`` is a supported return type:
  *
- * - ``Expected<Any>(const T& value, StructuralMutatorObj* mutator)``
- * - ``Expected<Any>(const T& value, StructuralMutatorObj* mutator, bool 
allow_inplace)``
+ * - ``R(const T& value, StructuralMutatorObj* mutator)``
+ * - ``R(const T& value, StructuralMutatorObj* mutator, bool allow_inplace)``
  *
- * The returned ``Any`` is the replacement for ``value``; an ``Error`` fails 
the
- * mutation. The first argument selects by FFI type; callbacks are tried in
- * declaration order and the first match owns mutation -- it drives its own
- * recursion through the mutator and sets any variable remapping. An unmatched
+ * A callback returns a replacement, ``Unchanged``, or ``Expected<Any>``. The 
first argument
+ * selects by FFI type; callbacks are tried in declaration order and the first 
match owns mutation
+ * -- it drives its own recursion through the mutator and sets any variable 
remapping. An unmatched
  * value takes registered or reflected default mutation.
  *
  * \param root The owning root value to mutate.
@@ -1599,7 +1872,11 @@ Expected<Any> StructuralMutateExpected(
   static_assert(sizeof...(Callbacks) != 0, "StructuralMutate requires at least 
one callback");
   using Mutator = StructuralMutateEngine<StructuralMapEngineBase, 
std::decay_t<Callbacks>...>;
   StructuralMutator 
mutator(make_object<Mutator>(std::forward<Callbacks>(callbacks)...));
-  return mutator->MaybeInplaceMutateIfUniqueExpected(root);
+  auto result = mutator->MaybeInplaceMutateIfUniqueExpected(root);
+  if (TVM_FFI_PREDICT_FALSE(result.is_err())) return 
Unexpected(std::move(result).error());
+  UnchangedOr<Any> mapped = 
details::AnyUnsafe::MoveFromAnyAfterCheck<UnchangedOr<Any>>(
+      std::move(details::ExpectedUnsafe::GetData(result)));
+  return std::move(mapped).ValueOrUnchanged(std::move(root));
 }
 
 /*!
@@ -1614,6 +1891,48 @@ Any StructuralMutate(Any root,
   return StructuralMutateExpected(std::move(root), 
std::forward<Callbacks>(callbacks)...).value();
 }
 
+template <typename T>
+inline constexpr bool use_default_type_traits_v<UnchangedOr<T>> = false;
+
+template <typename T>
+struct TypeTraits<UnchangedOr<T>> : public TypeTraitsBase {
+  TVM_FFI_INLINE static void CopyToAnyView(const UnchangedOr<T>& src, 
TVMFFIAny* result) {
+    *result = src.data_.CopyToTVMFFIAny();
+  }
+
+  TVM_FFI_INLINE static void MoveToAny(UnchangedOr<T> src, TVMFFIAny* result) {
+    *result = details::UnchangedOrUnsafe::MoveToTVMFFIAny(std::move(src));
+  }
+
+  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
+    if constexpr (std::is_same_v<T, Any>) {
+      return src->type_index != TypeIndex::kTVMFFIError;
+    } else {
+      return src->type_index == TypeIndex::kTVMFFIUnchanged || 
TypeTraits<T>::CheckAnyStrict(src);
+    }
+  }
+
+  TVM_FFI_INLINE static UnchangedOr<T> CopyFromAnyViewAfterCheck(const 
TVMFFIAny* src) {
+    if (src->type_index == TypeIndex::kTVMFFIUnchanged) return Unchanged();
+    if constexpr (std::is_same_v<T, Any>) {
+      return UnchangedOr<T>(Any(AnyView::CopyFromTVMFFIAny(*src)));
+    } else {
+      return UnchangedOr<T>(TypeTraits<T>::CopyFromAnyViewAfterCheck(src));
+    }
+  }
+
+  TVM_FFI_INLINE static UnchangedOr<T> MoveFromAnyAfterCheck(TVMFFIAny* src) {
+    return UnchangedOr<T>(typename UnchangedOr<T>::UnsafeInit{},
+                          details::AnyUnsafe::MoveTVMFFIAnyToAny(src));
+  }
+  TVM_FFI_INLINE static std::string TypeStr() {
+    return "UnchangedOr<" + details::Type2Str<T>::v() + ">";
+  }
+  TVM_FFI_INLINE static std::string TypeSchema() {
+    return R"({"type":"UnchangedOr","args":[)" + details::TypeSchema<T>::v() + 
"]}";
+  }
+};
+
 }  // namespace ffi
 }  // namespace tvm
 
diff --git a/include/tvm/ffi/extra/structural_visit.h 
b/include/tvm/ffi/extra/structural_visit.h
index 93fa6cb5..8d83b1d3 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -517,13 +517,13 @@ namespace details {
  *
  * \param Result An expression yielding the descent result to inspect.
  */
-#define TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Result)                             
   \
-  do {                                                                         
   \
-    auto&& tvm_ffi_res_ = (Result);                                            
   \
-    if (TVM_FFI_PREDICT_FALSE(                                                 
   \
-            
::tvm::ffi::details::StructuralVisitNeedEarlyReturn(tvm_ffi_res_))) { \
-      return 
::tvm::ffi::details::MaybeReturnHelper(::std::move(tvm_ffi_res_));   \
-    }                                                                          
   \
+#define TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Result)                             
    \
+  do {                                                                         
    \
+    auto&& tvm_ffi_res_ = (Result);                                            
    \
+    if (TVM_FFI_PREDICT_FALSE(                                                 
    \
+            
::tvm::ffi::details::StructuralVisitNeedEarlyReturn(tvm_ffi_res_))) {  \
+      return 
::tvm::ffi::details::ExpectedReturnHelper(::std::move(tvm_ffi_res_)); \
+    }                                                                          
    \
   } while (0)
 
 }  // namespace details
diff --git a/include/tvm/ffi/type_traits.h b/include/tvm/ffi/type_traits.h
index 45e8749d..ae08213e 100644
--- a/include/tvm/ffi/type_traits.h
+++ b/include/tvm/ffi/type_traits.h
@@ -77,6 +77,8 @@ struct StaticTypeKey {
   static constexpr const char* kTVMFFISmallStr = "ffi.SmallStr";
   /*! \brief The type key for SmallBytes */
   static constexpr const char* kTVMFFISmallBytes = "ffi.SmallBytes";
+  /*! \brief The type key for Unchanged */
+  static constexpr const char* kTVMFFIUnchanged = "ffi.Unchanged";
   /*! \brief The type key for Error */
   static constexpr const char* kTVMFFIError = "ffi.Error";
   /*! \brief The type key for Bytes */
diff --git a/python/tvm_ffi/cython/base.pxi b/python/tvm_ffi/cython/base.pxi
index da727199..a40da30f 100644
--- a/python/tvm_ffi/cython/base.pxi
+++ b/python/tvm_ffi/cython/base.pxi
@@ -139,6 +139,7 @@ cdef extern from "tvm/ffi/c_api.h":
         kTVMFFIObjectRValueRef = 10
         kTVMFFISmallStr = 11
         kTVMFFISmallBytes = 12
+        kTVMFFIUnchanged = 13
         kTVMFFIStaticObjectBegin = 64
         kTVMFFIObject = 64
         kTVMFFIStr = 65
diff --git a/rust/tvm-ffi-sys/src/c_api.rs b/rust/tvm-ffi-sys/src/c_api.rs
index 50ace68c..3fa37e89 100644
--- a/rust/tvm-ffi-sys/src/c_api.rs
+++ b/rust/tvm-ffi-sys/src/c_api.rs
@@ -56,6 +56,8 @@ pub enum TVMFFITypeIndex {
     kTVMFFISmallStr = 11,
     /// Small bytes on stack
     kTVMFFISmallBytes = 12,
+    /// Structural-mutation marker indicating that no new value was produced
+    kTVMFFIUnchanged = 13,
     /// Start of statically defined objects.
     kTVMFFIStaticObjectBegin = 64,
     /// String object, layout = { TVMFFIObject, TVMFFIByteArray, ... }
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 1ba0986b..effebda0 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -2239,7 +2239,12 @@ fn call_mutator(
     };
     with_mutator_def_region(mutator, def_region_kind, || unsafe {
         let view = AnyView::from_raw_ffi_any(raw);
-        result_from_raw(callback(mutator, view))
+        let result = result_from_raw(callback(mutator, view))?;
+        if result.type_index() == TVMFFITypeIndex::kTVMFFIUnchanged as i32 {
+            owned_from_raw(raw)
+        } else {
+            Ok(result)
+        }
     })
 }
 
@@ -2279,7 +2284,7 @@ fn call_structural_mutate_hook(
     attr: TVMFFIAny,
 ) -> Result<Any> {
     with_mutator_def_region(mutator, def_region_kind, || unsafe {
-        match attr.type_index {
+        let result = match attr.type_index {
             x if x == TVMFFITypeIndex::kTVMFFIOpaquePtr as i32 => {
                 let pointer = attr.data_union.v_ptr;
                 if pointer.is_null() {
@@ -2303,6 +2308,11 @@ fn call_structural_mutate_hook(
                 "__s_mutate__ must be an opaque function pointer or 
ffi.Function",
                 "",
             )),
+        }?;
+        if result.type_index() == TVMFFITypeIndex::kTVMFFIUnchanged as i32 {
+            owned_from_raw(raw)
+        } else {
+            Ok(result)
         }
     })
 }
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index feadbebb..0e951bba 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -366,6 +366,16 @@ fn reflected_no_change_returns_original() {
     assert_eq!(any_object_pointer(&mapped), any_object_pointer(&source));
 }
 
+#[test]
+fn native_unchanged_hook_returns_original_without_exposing_marker() {
+    let source = Any::from(FfiString::from("unchanged"));
+    let source_pointer = any_object_pointer(&source);
+    let mutated = structural_mutate(source, &mut 
ManualIncrement::default()).unwrap();
+
+    assert_eq!(any_object_pointer(&mutated), source_pointer);
+    assert_ne!(mutated.type_index(), TypeIndex::kTVMFFIUnchanged as i32);
+}
+
 #[test]
 fn reflected_object_without_shallow_copy_is_rejected_even_when_unchanged() {
     // Keep the C++ test library linked for its startup registrations.
diff --git a/src/ffi/extra/structural_mutate.cc 
b/src/ffi/extra/structural_mutate.cc
index 6060fb5e..9dff14f7 100644
--- a/src/ffi/extra/structural_mutate.cc
+++ b/src/ffi/extra/structural_mutate.cc
@@ -52,15 +52,20 @@ Expected<Any> StructuralMapExpected(
     Any root,  // NOLINT(performance-unnecessary-value-param)
     const Array<Tuple<int32_t, Function>>& callbacks,
     const Array<Tuple<int32_t, Function>>& callbacks_with_def_region_kind, int 
order) noexcept {
-  if (order == static_cast<int>(WalkOrder::kPreOrder)) {
-    using Mutator = StructuralMapDynEngine<StructuralMapEngineBase, 
WalkOrder::kPreOrder>;
-    StructuralMutator mutator(make_object<Mutator>(callbacks, 
callbacks_with_def_region_kind));
-    return mutator->MaybeInplaceMutateIfUniqueExpected(root);
-  } else {
+  Expected<UnchangedOr<Any>> result = [&]() -> Expected<UnchangedOr<Any>> {
+    if (order == static_cast<int>(WalkOrder::kPreOrder)) {
+      using Mutator = StructuralMapDynEngine<StructuralMapEngineBase, 
WalkOrder::kPreOrder>;
+      StructuralMutator mutator(make_object<Mutator>(callbacks, 
callbacks_with_def_region_kind));
+      return mutator->MaybeInplaceMutateIfUniqueExpected(root);
+    }
     using Mutator = StructuralMapDynEngine<StructuralMapEngineBase, 
WalkOrder::kPostOrder>;
     StructuralMutator mutator(make_object<Mutator>(callbacks, 
callbacks_with_def_region_kind));
     return mutator->MaybeInplaceMutateIfUniqueExpected(root);
-  }
+  }();
+  if (TVM_FFI_PREDICT_FALSE(result.is_err())) return 
Unexpected(std::move(result).error());
+  UnchangedOr<Any> mapped = AnyUnsafe::MoveFromAnyAfterCheck<UnchangedOr<Any>>(
+      std::move(ExpectedUnsafe::GetData(result)));
+  return std::move(mapped).ValueOrUnchanged(std::move(root));
 }
 
 /*! \brief Runtime counterpart of the typed callback-owned mutate engine. */
@@ -142,7 +147,11 @@ Expected<Any> StructuralMutateExpected(
     const Array<Tuple<int32_t, Function, bool>>& callbacks) noexcept {
   using Mutator = StructuralMutateDynEngine<StructuralMapEngineBase>;
   StructuralMutator mutator(make_object<Mutator>(callbacks));
-  return mutator->MaybeInplaceMutateIfUniqueExpected(root);
+  auto result = mutator->MaybeInplaceMutateIfUniqueExpected(root);
+  if (TVM_FFI_PREDICT_FALSE(result.is_err())) return 
Unexpected(std::move(result).error());
+  UnchangedOr<Any> mapped = AnyUnsafe::MoveFromAnyAfterCheck<UnchangedOr<Any>>(
+      std::move(ExpectedUnsafe::GetData(result)));
+  return std::move(mapped).ValueOrUnchanged(std::move(root));
 }
 
 // ---------------------------------------------------------------------------
@@ -171,8 +180,9 @@ TVM_FFI_INLINE TVMFFIAny 
MutateSeqContainerChanged(StructuralMutatorObj* mutator
 
   for (int64_t i = index + 1; i < size; ++i) {
     const Any& item = items[i];
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, mapped_value, 
mutator->MutateExpected(item));
-    output->SetItemAfterCheck(i, std::move(mapped_value));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped_value,
+                                      mutator->MutateExpected(item));
+    output->SetItemAfterCheck(i, 
std::move(mapped_value).ValueOrUnchanged(AnyView(item)));
   }
   return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
 }
@@ -182,24 +192,23 @@ TVM_FFI_INLINE TVMFFIAny 
MutateSeqContainerChanged(StructuralMutatorObj* mutator
  *
  * \tparam SeqObj The underlying sequence object type.
  * \param mutator The active structural mutator.
- * \param value The borrowed sequence container.
- * \param self The sequence object stored in \p value.
+ * \param self The source sequence object.
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* mutator, AnyView value,
-                                const SeqObj* self) noexcept {
+TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* mutator, const SeqObj* 
self) noexcept {
   int64_t size = static_cast<int64_t>(self->size());
   const Any* items = self->begin();
 
   for (int64_t i = 0; i < size; ++i) {
     const Any& item = items[i];
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, mapped_value, 
mutator->MutateExpected(item));
-    if (!item.same_as(mapped_value)) {
-      return MutateSeqContainerChanged(mutator, self, i, 
std::move(mapped_value));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped_value,
+                                      mutator->MutateExpected(item));
+    if (!mapped_value.UnchangedOrSameAs(item)) {
+      return MutateSeqContainerChanged(mutator, self, i, 
std::move(mapped_value).ValueUnchecked());
     }
   }
-  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+  return Unchanged().CopyToTVMFFIAny();
 }
 
 /*!
@@ -207,23 +216,20 @@ TVMFFIAny MutateSeqContainerRaw(StructuralMutatorObj* 
mutator, AnyView value,
  *
  * \tparam SeqObj The underlying sequence object type.
  * \param mutator The active structural mutator.
- * \param value The borrowed sequence container, which must be safe to mutate 
in place.
- * \param self The sequence object stored in \p value.
+ * \param self The sequence object, which must be safe to mutate in place.
  * \return The mutated sequence, or an Error.
  */
 template <typename SeqObj>
-TVMFFIAny MaybeInplaceMutateSeqContainerRaw(StructuralMutatorObj* mutator, 
AnyView value,
-                                            SeqObj* self) noexcept {
+TVMFFIAny 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(Any, mapped_value,
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped_value,
                                       
mutator->MaybeInplaceMutateIfUniqueExpected(item));
-
-    if (!item.same_as(mapped_value)) {
-      self->SetItemAfterCheck(i, std::move(mapped_value));
+    if (!mapped_value.UnchangedOrSameAs(item)) {
+      self->SetItemAfterCheck(i, std::move(mapped_value).ValueUnchecked());
     }
   }
-  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+  return Unchanged().CopyToTVMFFIAny();
 }
 
 /*!
@@ -253,9 +259,10 @@ TVM_FFI_INLINE TVMFFIAny 
MutateMapValuesChanged(StructuralMutatorObj* mutator,
 
   for (; source_it != self->end(); ++source_it, ++output_it) {
     const Any& old_value = source_it->second;
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, new_value, 
mutator->MutateExpected(old_value));
-    if (!old_value.same_as(new_value)) {
-      output_it->second = std::move(new_value);
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, new_value,
+                                      mutator->MutateExpected(old_value));
+    if (!new_value.UnchangedOrSameAs(old_value)) {
+      output_it->second = std::move(new_value).ValueUnchecked();
     }
   }
   return AnyUnsafe::MoveAnyToTVMFFIAny(Any(std::move(output)));
@@ -266,22 +273,22 @@ TVM_FFI_INLINE TVMFFIAny 
MutateMapValuesChanged(StructuralMutatorObj* mutator,
  *
  * \tparam MapObjType The underlying map object type.
  * \param mutator The active structural mutator.
- * \param value The borrowed map container.
- * \param self The map object stored in \p value.
+ * \param self The source map object.
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* mutator, AnyView value,
-                             const MapObjType* self) noexcept {
+TVMFFIAny 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;
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, new_value, 
mutator->MutateExpected(old_value));
-    if (!old_value.same_as(new_value)) {
-      return MutateMapValuesChanged(mutator, self, source_it, index, 
std::move(new_value));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, new_value,
+                                      mutator->MutateExpected(old_value));
+    if (!new_value.UnchangedOrSameAs(old_value)) {
+      return MutateMapValuesChanged(mutator, self, source_it, index,
+                                    std::move(new_value).ValueUnchecked());
     }
   }
-  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+  return Unchanged().CopyToTVMFFIAny();
 }
 
 /*!
@@ -289,77 +296,73 @@ TVMFFIAny MutateMapValuesRaw(StructuralMutatorObj* 
mutator, AnyView value,
  *
  * \tparam MapObjType The underlying map object type.
  * \param mutator The active structural mutator.
- * \param value The borrowed map container, which must be safe to mutate in 
place.
- * \param self The map object stored in \p value.
+ * \param self The map object, which must be safe to mutate in place.
  * \return The mutated map, or an Error.
  */
 template <typename MapObjType>
-TVMFFIAny MaybeInplaceMutateMapValuesRaw(StructuralMutatorObj* mutator, 
AnyView value,
-                                         MapObjType* self) noexcept {
+TVMFFIAny 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(Any, new_value,
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, new_value,
                                       
mutator->MaybeInplaceMutateIfUniqueExpected(old_value));
-
-    if (!old_value.same_as(new_value)) {
-      it->second = std::move(new_value);
+    if (!new_value.UnchangedOrSameAs(old_value)) {
+      it->second = std::move(new_value).ValueUnchecked();
     }
   }
-  return AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+  return Unchanged().CopyToTVMFFIAny();
 }
 
 /*! \brief Identity structural mutation hook for immutable String and Bytes 
leaves. */
-TVMFFIAny MutateImmutableLeaf(StructuralMutatorObj*, AnyView value) noexcept {
-  Expected<Any> result = Any(value);
-  return ExpectedUnsafe::MoveToTVMFFIAny(std::move(result));
+TVMFFIAny MutateImmutableLeaf(StructuralMutatorObj*, AnyView) noexcept {
+  return Unchanged().CopyToTVMFFIAny();
 }
 
 /*! \brief Structural mutation hook for ArrayObj. */
 TVMFFIAny MutateArray(StructuralMutatorObj* mutator, AnyView value) noexcept {
   return MutateSeqContainerRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const ArrayObj>(value));
+      mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
ArrayObj>(value));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for ArrayObj. */
 TVMFFIAny MaybeInplaceMutateArray(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
   return MaybeInplaceMutateSeqContainerRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ArrayObj>(value));
+      mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ArrayObj>(value));
 }
 
 /*! \brief Structural mutation hook for ListObj. */
 TVMFFIAny MutateList(StructuralMutatorObj* mutator, AnyView value) noexcept {
   return MutateSeqContainerRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const ListObj>(value));
+      mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
ListObj>(value));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for ListObj. */
 TVMFFIAny MaybeInplaceMutateList(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
   return MaybeInplaceMutateSeqContainerRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ListObj>(value));
+      mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<ListObj>(value));
 }
 
 /*! \brief Structural mutation hook for MapObj. */
 TVMFFIAny MutateMap(StructuralMutatorObj* mutator, AnyView value) noexcept {
   return MutateMapValuesRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const MapObj>(value));
+      mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
MapObj>(value));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for MapObj. */
 TVMFFIAny MaybeInplaceMutateMap(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
   return MaybeInplaceMutateMapValuesRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<MapObj>(value));
+      mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<MapObj>(value));
 }
 
 /*! \brief Structural mutation hook for DictObj. */
 TVMFFIAny MutateDict(StructuralMutatorObj* mutator, AnyView value) noexcept {
   return MutateMapValuesRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const DictObj>(value));
+      mutator, details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
DictObj>(value));
 }
 
 /*! \brief Maybe-in-place structural mutation hook for DictObj. */
 TVMFFIAny MaybeInplaceMutateDict(StructuralMutatorObj* mutator, AnyView value) 
noexcept {
   return MaybeInplaceMutateMapValuesRaw(
-      mutator, value, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<DictObj>(value));
+      mutator, 
details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<DictObj>(value));
 }
 }  // namespace details
 
@@ -371,10 +374,16 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::ObjectDef<StructuralMutatorObj>();  // NOLINT(bugprone-unused-raii)
   refl::GlobalDef()
-      .def_method("ffi.StructuralMutatorMutate", &StructuralMutatorObj::Mutate)
-      .def_method("ffi.StructuralMutatorDefaultMutate",
+      .def_method("ffi.StructuralMutatorMutate",
                   [](const StructuralMutator& mutator, AnyView value) {
-                    return mutator->DefaultMutateExpected(value).value();
+                    return 
std::move(mutator->Mutate(value)).ValueOrUnchanged(value);
+                  })
+      .def_method("ffi.StructuralMutatorDefaultMutate",
+                  [](const StructuralMutator& mutator, AnyView value) -> Any {
+                    UnchangedOr<Any> result =
+                        
details::AnyUnsafe::MoveFromAnyAfterCheck<UnchangedOr<Any>>(
+                            
std::move(mutator->DefaultMutateExpected(value)).value());
+                    return std::move(result).ValueOrUnchanged(value);
                   })
       .def_method("ffi.StructuralMutatorVarRemapGet",
                   [](const StructuralMutator& mutator, AnyView var) {
diff --git a/src/ffi/object.cc b/src/ffi/object.cc
index 4daa9652..5cb84974 100644
--- a/src/ffi/object.cc
+++ b/src/ffi/object.cc
@@ -414,6 +414,7 @@ class TypeTable {
                             TypeIndex::kTVMFFIObjectRValueRef);
     ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFISmallStr, 
TypeIndex::kTVMFFISmallStr);
     ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFISmallBytes, 
TypeIndex::kTVMFFISmallBytes);
+    ReserveBuiltinTypeIndex(StaticTypeKey::kTVMFFIUnchanged, 
TypeIndex::kTVMFFIUnchanged);
     // reserved static type indices for depth 1 object types
     ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIStr, 
TypeIndex::kTVMFFIStr);
     ReserveDepthOneObjectTypeIndex(StaticTypeKey::kTVMFFIBytes, 
TypeIndex::kTVMFFIBytes);
diff --git a/tests/cpp/extra/test_structural_mutate.cc 
b/tests/cpp/extra/test_structural_mutate.cc
index b99b0a3c..5ad0c89d 100644
--- a/tests/cpp/extra/test_structural_mutate.cc
+++ b/tests/cpp/extra/test_structural_mutate.cc
@@ -40,6 +40,160 @@ using namespace tvm::ffi::testing;
 using AnyArray = Array<Any>;
 using StringMap = Map<String, Any>;
 
+// ---------------------------------------------------------------------------
+// Unchanged result protocol.
+// ---------------------------------------------------------------------------
+
+Expected<UnchangedOr<String>> ReturnTypedUnchangedExpected() noexcept { return 
Unchanged(); }
+
+TEST(UnchangedOr, ErrorRoundTrip) {
+  static_assert(std::is_copy_constructible_v<UnchangedOr<String>>);
+
+  UnchangedOr<String> original = String("unchanged-or special-member value");
+  UnchangedOr<String> copied_value(original);
+  UnchangedOr<String> copy_assigned = Unchanged();
+  copy_assigned = original;
+  EXPECT_EQ(std::move(copied_value).ValueUnchecked(), "unchanged-or 
special-member value");
+  EXPECT_EQ(std::move(copy_assigned).ValueUnchecked(), "unchanged-or 
special-member value");
+
+  UnchangedOr<String> moved_value(std::move(original));
+  UnchangedOr<String> move_source = String("unchanged-or move-assignment 
value");
+  UnchangedOr<String> move_assigned = Unchanged();
+  move_assigned = std::move(move_source);
+  EXPECT_EQ(std::move(moved_value).ValueUnchecked(), "unchanged-or 
special-member value");
+  EXPECT_EQ(std::move(move_assigned).ValueUnchecked(), "unchanged-or 
move-assignment value");
+
+  Expected<UnchangedOr<Any>> failure = Error("ValueError", "expected failure", 
"");
+  const Any copied_storage(failure);
+  Expected<UnchangedOr<Any>> copied =
+      
details::AnyUnsafe::CopyFromAnyViewAfterCheck<Expected<UnchangedOr<Any>>>(copied_storage);
+
+  ASSERT_TRUE(copied.is_err());
+  EXPECT_EQ(copied.error().kind(), "ValueError");
+  EXPECT_EQ(copied.error().message(), "expected failure");
+
+  Any moved_storage(failure);
+  Expected<UnchangedOr<Any>> moved =
+      details::AnyUnsafe::MoveFromAnyAfterCheck<Expected<UnchangedOr<Any>>>(
+          std::move(moved_storage));
+
+  ASSERT_TRUE(moved.is_err());
+  EXPECT_EQ(moved.error().kind(), "ValueError");
+  EXPECT_EQ(moved.error().message(), "expected failure");
+
+  Expected<Any> source_error = Error("TypeError", "converted failure", "");
+  Expected<UnchangedOr<Any>> converted_error = std::move(source_error);
+  ASSERT_TRUE(converted_error.is_err());
+  EXPECT_EQ(converted_error.error().kind(), "TypeError");
+  EXPECT_EQ(converted_error.error().message(), "converted failure");
+
+  Expected<Any> unexpected_error = Unexpected(Error("IndexError", "unexpected 
failure", ""));
+  ASSERT_TRUE(unexpected_error.is_err());
+  EXPECT_EQ(unexpected_error.error().kind(), "IndexError");
+  EXPECT_EQ(unexpected_error.error().message(), "unexpected failure");
+
+  Expected<UnchangedOr<Any>> unexpected_result =
+      Unexpected(Error("RuntimeError", "unchanged-or unexpected failure", ""));
+  ASSERT_TRUE(unexpected_result.is_err());
+  EXPECT_EQ(unexpected_result.error().kind(), "RuntimeError");
+  EXPECT_EQ(unexpected_result.error().message(), "unchanged-or unexpected 
failure");
+}
+
+TEST(StructuralMutate, UnchangedProtocolResolvesAtThrowingEntryPoints) {
+  Expected<Any> raw_tag = []() -> Expected<Any> { return Unchanged(); }();
+  ASSERT_TRUE(raw_tag.is_ok());
+  EXPECT_EQ(details::ExpectedUnsafe::GetData(raw_tag).type_index(), 
TypeIndex::kTVMFFIUnchanged);
+  EXPECT_TRUE(ReturnTypedUnchangedExpected().value().IsUnchanged());
+  auto never_matches = [](int64_t, StructuralMutatorObj*) -> Expected<Any> { 
return Any(); };
+  using Mutator = StructuralMutateEngine<StructuralMapEngineBase, 
decltype(never_matches)>;
+  StructuralMutator mutator(make_object<Mutator>(std::move(never_matches)));
+  String value("value longer than small-string storage");
+
+  auto untyped = mutator->MutateExpected(AnyView(value));
+  ASSERT_TRUE(untyped.is_ok());
+  EXPECT_TRUE(std::move(untyped).value().IsUnchanged());
+  auto untyped_throwing = mutator->Mutate(AnyView(value));
+  EXPECT_TRUE(untyped_throwing.IsUnchanged());
+  
EXPECT_TRUE(std::move(untyped_throwing).ValueOrUnchanged(AnyView(value)).same_as(value));
+
+  auto typed = mutator->MutateExpected<String>(value);
+  ASSERT_TRUE(typed.is_ok());
+  EXPECT_TRUE(std::move(typed).value().IsUnchanged());
+  auto typed_throwing = mutator->Mutate<String>(value);
+  EXPECT_TRUE(typed_throwing.IsUnchanged());
+  String moved_value = value;
+  String moved_value_alias = moved_value;
+  
EXPECT_EQ(std::move(typed_throwing).ValueOrUnchanged(std::move(moved_value)), 
moved_value_alias);
+  EXPECT_TRUE(mutator->MaybeInplaceMutate(AnyView(value)).IsUnchanged());
+  EXPECT_TRUE(mutator->MaybeInplaceMutate<String>(value).IsUnchanged());
+  auto unique = mutator->MaybeInplaceMutateIfUniqueExpected(AnyView(value));
+  ASSERT_TRUE(unique.is_ok());
+  EXPECT_TRUE(std::move(unique).value().IsUnchanged());
+  auto typed_unique = 
mutator->MaybeInplaceMutateIfUniqueExpected<String>(value);
+  ASSERT_TRUE(typed_unique.is_ok());
+  EXPECT_TRUE(std::move(typed_unique).value().IsUnchanged());
+  EXPECT_TRUE(StructuralMutateExpected(Any(value), 
never_matches).value().same_as(value));
+  EXPECT_TRUE(StructuralMapExpected<WalkOrder::kPostOrder>(
+                  Any(value), [](int64_t item) -> Expected<Any> { return 
Any(item + 1); })
+                  .value()
+                  .same_as(value));
+
+  auto replace_int_with_string = [](int64_t value, StructuralMutatorObj*) -> 
Expected<Any> {
+    if (value == -1) return Unexpected(Error("ValueError", "direct-forward 
failure", ""));
+    return String("wrong replacement uses heap storage");
+  };
+  using WrongTypeMutator =
+      StructuralMutateEngine<StructuralMapEngineBase, 
decltype(replace_int_with_string)>;
+  StructuralMutator wrong_type_mutator(
+      make_object<WrongTypeMutator>(std::move(replace_int_with_string)));
+
+  auto any_error = wrong_type_mutator->MutateExpected(int64_t{-1});
+  ASSERT_TRUE(any_error.is_err());
+  EXPECT_EQ(any_error.error().message(), "direct-forward failure");
+  auto any_inplace_error = 
wrong_type_mutator->MaybeInplaceMutateExpected(int64_t{-1});
+  ASSERT_TRUE(any_inplace_error.is_err());
+  EXPECT_EQ(any_inplace_error.error().message(), "direct-forward failure");
+  auto typed_error = wrong_type_mutator->MutateExpected<int64_t>(int64_t{-1});
+  ASSERT_TRUE(typed_error.is_err());
+  EXPECT_EQ(typed_error.error().message(), "direct-forward failure");
+  auto typed_inplace_error = 
wrong_type_mutator->MaybeInplaceMutateExpected<int64_t>(int64_t{-1});
+  ASSERT_TRUE(typed_inplace_error.is_err());
+  EXPECT_EQ(typed_inplace_error.error().message(), "direct-forward failure");
+
+  Expected<UnchangedOr<int64_t>> result = 
wrong_type_mutator->MutateExpected<int64_t>(int64_t{1});
+  ASSERT_TRUE(result.is_err());
+  EXPECT_EQ(result.error().kind(), "TypeError");
+  Expected<UnchangedOr<int64_t>> inplace_result =
+      wrong_type_mutator->MaybeInplaceMutateExpected<int64_t>(int64_t{1});
+  ASSERT_TRUE(inplace_result.is_err());
+  EXPECT_EQ(inplace_result.error().kind(), "TypeError");
+  Expected<UnchangedOr<int64_t>> unique_result =
+      
wrong_type_mutator->MaybeInplaceMutateIfUniqueExpected<int64_t>(int64_t{1});
+  ASSERT_TRUE(unique_result.is_err());
+  EXPECT_EQ(unique_result.error().kind(), "TypeError");
+
+  for (WalkOrder order : {WalkOrder::kPreOrder, WalkOrder::kPostOrder}) {
+    TVar root("n");
+    TVar unchanged = (order == WalkOrder::kPreOrder
+                          ? StructuralMap<WalkOrder::kPreOrder>(
+                                root, [](const TVar&) -> Expected<Any> { 
return Unchanged(); })
+                          : StructuralMap<WalkOrder::kPostOrder>(
+                                root, [](const TVar&) -> Expected<Any> { 
return Unchanged(); }))
+                         .cast<TVar>();
+    EXPECT_TRUE(unchanged.same_as(root));
+  }
+
+  Function dynamic_unchanged = Function::FromTyped([](int64_t) -> Any { return 
Unchanged(); });
+  Array<Tuple<int32_t, Function>> callbacks{
+      Tuple<int32_t, Function>(TypeIndex::kTVMFFIInt, dynamic_unchanged)};
+  Function structural_map = Function::GetGlobalRequired("ffi.StructuralMap");
+  for (WalkOrder order : {WalkOrder::kPreOrder, WalkOrder::kPostOrder}) {
+    Any unchanged = structural_map(int64_t{1}, callbacks, Array<Tuple<int32_t, 
Function>>(),
+                                   static_cast<int32_t>(order));
+    EXPECT_EQ(unchanged.cast<int64_t>(), 1);
+  }
+}
+
 class TNestedMapHookObj : public Object {
  public:
   AnyArray field;
@@ -48,11 +202,13 @@ class TNestedMapHookObj : public Object {
 
   static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
     const auto* self = value.cast<const TNestedMapHookObj*>();
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, mapped, 
mutator->MutateExpected(self->field));
-    AnyArray mapped_field = mapped.cast<AnyArray>();
-    if (mapped_field.same_as(self->field)) {
-      return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, mapped,
+                                      mutator->MutateExpected(self->field));
+    if (mapped.UnchangedOrSameAs(Any(self->field))) {
+      return Unchanged().CopyToTVMFFIAny();
     }
+    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))));
   }
@@ -198,7 +354,8 @@ TEST(StructuralMap, 
ParentLayerOwnsBothDescentsAndProvidesState) {
 
   TVar var("n");
   AnyArray repeated{var, var};
-  AnyArray mapped = mutator->MutateExpected(repeated).value().cast<AnyArray>();
+  AnyArray mapped =
+      
std::move(mutator->Mutate<AnyArray>(repeated)).ValueOrUnchanged(std::move(repeated));
   EXPECT_EQ(var_callback_count, 1);
   EXPECT_TRUE(mapped[0].cast<TVar>().same_as(mapped[1].cast<TVar>()));
 }
@@ -207,7 +364,11 @@ TEST(StructuralMutate, 
CallbackOwnsMutationAndErrorsStayExpected) {
   std::vector<int64_t> trace;
   auto mutate_array = [&](const AnyArray& value, StructuralMutateLayer* 
mutator) -> Expected<Any> {
     EXPECT_EQ(mutator->callback_tag(), 23);
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, first, 
mutator->MutateExpected(value[0]));
+    auto first_result = mutator->MutateExpected(value[0]);
+    if (TVM_FFI_PREDICT_FALSE(first_result.is_err())) {
+      return Unexpected(std::move(first_result).error());
+    }
+    Any first = 
std::move(first_result).value().ValueOrUnchanged(AnyView(value[0]));
     return Any(AnyArray{std::move(first), int64_t{10}});
   };
   auto mutate_int = [&](int64_t value, StructuralMutateLayer*) -> 
Expected<Any> {
@@ -218,8 +379,8 @@ TEST(StructuralMutate, 
CallbackOwnsMutationAndErrorsStayExpected) {
       StructuralMutateEngine<StructuralMutateLayer, decltype(mutate_array), 
decltype(mutate_int)>;
   StructuralMutator mutator(make_object<Mutator>(std::move(mutate_array), 
std::move(mutate_int)));
 
-  AnyArray mapped =
-      mutator->MutateExpected(AnyArray{int64_t{1}, 
int64_t{2}}).value().cast<AnyArray>();
+  AnyArray root{int64_t{1}, int64_t{2}};
+  AnyArray mapped = 
std::move(mutator->Mutate<AnyArray>(root)).ValueOrUnchanged(std::move(root));
   ASSERT_EQ(mapped.size(), 2U);
   EXPECT_EQ(mapped[0].cast<int64_t>(), 2);
   EXPECT_EQ(mapped[1].cast<int64_t>(), 10);
@@ -257,8 +418,11 @@ TEST(StructuralMutate, CallbackControlsRecursion) {
       StructuralMutate(
           root,
           [](const TPair& pair, StructuralMutatorObj* mutator) -> 
Expected<Any> {
-            TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, lhs, 
mutator->MutateExpected(pair->lhs));
-            return Any(TPair(lhs.cast<ObjectRef>(), pair->rhs));
+            TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<ObjectRef>, 
lhs_result,
+                                              
mutator->MutateExpected<ObjectRef>(pair->lhs));
+            ObjectRef original_lhs = pair->lhs;
+            ObjectRef lhs = 
std::move(lhs_result).ValueOrUnchanged(std::move(original_lhs));
+            return Any(TPair(std::move(lhs), pair->rhs));
           },
           [](const TInt& value, StructuralMutatorObj*) -> Expected<Any> {
             return Any(TInt(value->value + 100));
@@ -350,21 +514,23 @@ TEST(StructuralMutate, MatchedVarOwnsRemapConsistency) {
   int callback_count = 0;
 
   AnyArray mapped =
-      StructuralMutate(
-          root,
-          [&](const TVar& value, StructuralMutatorObj* mutator) -> 
Expected<Any> {
-            ++callback_count;
-            TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(Any, remapped, 
mutator->VarRemapGetExpected(value));
-            if (remapped.type_index() != TypeIndex::kTVMFFINone) {
-              return remapped;
-            }
-            Any replacement(TVar(value->name + "-mapped"));
-            Expected<void> set_result = mutator->VarRemapSetExpected(value, 
replacement);
-            if (set_result.is_err()) {
-              return Unexpected(std::move(set_result).error());
-            }
-            return replacement;
-          })
+      StructuralMutate(root,
+                       [&](const TVar& value, StructuralMutatorObj* mutator) 
-> Expected<Any> {
+                         ++callback_count;
+                         TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<Any>, 
remapped_result,
+                                                           
mutator->VarRemapGetExpected(value));
+                         Any remapped = 
std::move(remapped_result).ValueUnchecked();
+                         if (remapped.type_index() != TypeIndex::kTVMFFINone) {
+                           return remapped;
+                         }
+                         Any replacement(TVar(value->name + "-mapped"));
+                         Expected<void> set_result =
+                             mutator->VarRemapSetExpected(value, replacement);
+                         if (set_result.is_err()) {
+                           return Unexpected(std::move(set_result).error());
+                         }
+                         return replacement;
+                       })
           .cast<AnyArray>();
 
   EXPECT_EQ(callback_count, 2);
@@ -610,6 +776,22 @@ TEST(StructuralMap, PreOrderRecursivelyMapsCallbackResult) 
{
   EXPECT_FALSE(mapped_value.same_as(replacement));
   EXPECT_EQ(replacement[0].cast<int64_t>(), 10);
   EXPECT_EQ(mapped_value[0].cast<int64_t>(), 11);
+
+  // The changed callback result remains owned while its leaf descent reports 
unchanged.
+  String replacement_leaf("replacement longer than small-string storage");
+  String retained =
+      StructuralMap<WalkOrder::kPreOrder>(TVar("n"), [&](const TVar&) -> 
Expected<Any> {
+        return Any(replacement_leaf);
+      }).cast<String>();
+  EXPECT_EQ(retained, replacement_leaf);
+
+  // An unchanged pre-order callback still descends the original node.
+  AnyArray unchanged_root{int64_t{1}};
+  AnyArray descended_original =
+      StructuralMap<WalkOrder::kPreOrder>(
+          unchanged_root, [](const AnyArray&) -> Expected<Any> { return 
Unchanged(); }, Increment)
+          .cast<AnyArray>();
+  EXPECT_EQ(descended_original[0].cast<int64_t>(), 2);
 }
 
 TEST(StructuralMap, AcceptsExpectedCallbackReturnTypes) {
@@ -811,6 +993,20 @@ Any CallDynStructuralMap(AnyView root, const 
Array<Tuple<int32_t, Function>>& ca
   return fn(root, callbacks, Array<Tuple<int32_t, Function>>(), 
static_cast<int32_t>(order));
 }
 
+TEST(StructuralMapDyn, PreOrderDescendsOriginalAfterUnchangedCallback) {
+  AnyArray root{int64_t{1}};
+  Function unchanged = Function::FromTyped([](const AnyArray&) -> Any { return 
Unchanged(); });
+  Function increment = Function::FromTyped([](int64_t value) -> Any { return 
Any(value + 1); });
+
+  AnyArray mapped =
+      CallDynStructuralMap(root,
+                           {Tuple<int32_t, Function>(TypeIndex::kTVMFFIArray, 
unchanged),
+                            Tuple<int32_t, Function>(TypeIndex::kTVMFFIInt, 
increment)},
+                           WalkOrder::kPreOrder)
+          .cast<AnyArray>();
+  EXPECT_EQ(mapped[0].cast<int64_t>(), 2);
+}
+
 TEST(StructuralMapDyn, ReusesRemapResultForRepeatedVar) {
   // A FreeVar maps once and every later occurrence reuses that result. Both 
mutators share this
   // half of the walk, so it must hold identically here.
@@ -841,7 +1037,8 @@ void CheckDynamicParentLayer() {
       Array<Tuple<int32_t, Function>>());
   StructuralMutator mutator(engine);
 
-  AnyArray mapped = mutator->Mutate(AnyArray{int64_t{1}}).cast<AnyArray>();
+  AnyArray root{int64_t{1}};
+  AnyArray mapped = 
std::move(mutator->Mutate<AnyArray>(root)).ValueOrUnchanged(std::move(root));
   EXPECT_EQ(mapped[0].cast<int64_t>(), 2);
   EXPECT_EQ(calls, 1);
   EXPECT_GT(engine->count().value, 0);
diff --git a/tests/cpp/testing_object.h b/tests/cpp/testing_object.h
index 4d4cc65a..5553c6c3 100644
--- a/tests/cpp/testing_object.h
+++ b/tests/cpp/testing_object.h
@@ -251,14 +251,19 @@ class TMutatePairObj : public Object {
 
   static TVMFFIAny StructuralMutate(StructuralMutatorObj* mutator, AnyView 
value) noexcept {
     ++StructuralMutateCallCount();
-    const auto* self = value.cast<const TMutatePairObj*>();
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, lhs, 
mutator->MutateExpected(self->lhs));
-    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ObjectRef, rhs, 
mutator->MutateExpected(self->rhs));
-    if (lhs.same_as(self->lhs) && rhs.same_as(self->rhs)) {
-      return details::AnyUnsafe::MoveAnyToTVMFFIAny(Any(value));
+    const TMutatePairObj* self =
+        details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
TMutatePairObj>(value);
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<ObjectRef>, lhs,
+                                      mutator->MutateExpected(self->lhs));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(UnchangedOr<ObjectRef>, rhs,
+                                      mutator->MutateExpected(self->rhs));
+    if (lhs.UnchangedOrSameAs(self->lhs) && rhs.UnchangedOrSameAs(self->rhs)) {
+      return Unchanged().CopyToTVMFFIAny();
     }
-    return details::AnyUnsafe::MoveAnyToTVMFFIAny(
-        Any(make_object<TMutatePairObj>(std::move(lhs), std::move(rhs))));
+    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)));
   }
 
   static void RegisterReflection() {
diff --git a/tests/python/test_structural.py b/tests/python/test_structural.py
index 96873830..2d01511d 100644
--- a/tests/python/test_structural.py
+++ b/tests/python/test_structural.py
@@ -413,6 +413,14 @@ def 
test_structural_mutate_callback_owned_recursion_and_errors() -> None:
     assert nested_trace == [1, 2]
 
 
+def test_structural_mutate_callback_resolves_unchanged() -> None:
+    unchanged = tvm_ffi.structural_mutate(
+        tvm_ffi.Array(["unchanged"]),
+        (tvm_ffi.Array, lambda value, mutator: mutator.mutate(value[0])),
+    )
+    assert unchanged == "unchanged"
+
+
 def test_structural_walk_nested_containers_and_skips_map_keys() -> None:
     root = tvm_ffi.Array(
         [

Reply via email to