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 b1936099 [Rust] Decouple structural mutation dispatch from recursion 
control (#723)
b1936099 is described below

commit b1936099423fb9199e3691e59a7c558a3cb05716
Author: Shushi Hong <[email protected]>
AuthorDate: Tue Sep 1 08:13:31 2026 -0400

    [Rust] Decouple structural mutation dispatch from recursion control (#723)
    
    This PR separates typed mutation dispatch from structural-mutation
    recursion control.
    
    `#[dispatch(mutate)]` now implements `MutateDispatch` and passes a
    `MutateContext` to each typed handler. Dispatch is responsible only for
    selecting the matching handler, while `MutateContext` provides
    recursion, default mutation, state access, variable remapping, and the
    current definition region.
    
    This enables callbacks such as:
    
    ```rust
    fn mutate_add(
        &mut self,
        value: Add,
        mutator: &mut MutateContext<'_, State>,
    ) -> Result<Any> {
        let a = mutator.mutate(&value.a)?;
        let b = mutator.mutate(&value.b)?;
        // Build or reuse the result.
    }
---
 docs/guides/rust_lang_guide.md               |  44 ++--
 rust/tvm-ffi-macros/src/dispatch.rs          | 290 ++++++++++++++++++++++-----
 rust/tvm-ffi/src/extra/structural_mutate.rs  | 234 ++++++++++++++++++---
 rust/tvm-ffi/src/lib.rs                      |   2 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs | 258 +++++++++++++++---------
 5 files changed, 643 insertions(+), 185 deletions(-)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index f63a07e2..3edfd737 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -496,12 +496,12 @@ completed before a later error are not rolled back, and 
the consumed root is
 not returned on error.
 
 `structural_mutate` accepts typed callbacks in addition to a
-`StructuralMutator`. Callbacks receive `MutateContext`; `MutateCallbacks` adds
+`StructuralMutator`. Callbacks receive a `Mutator`; `MutateCallbacks` adds
 state shared by the callback chain:
 
 ```rust
 use tvm_ffi::{
-    structural_mutate, Array, MapValue, MutateCallbacks, MutateContext,
+    structural_mutate, Array, MapValue, MutateCallbacks, Mutator,
 };
 
 #[derive(Default)]
@@ -512,11 +512,11 @@ struct Stats {
 let mut mutator = MutateCallbacks::new(
     Stats::default(),
     (
-        |value: i64, mutator: &mut MutateContext<'_, Stats>| {
+        |value: i64, mutator: &mut Mutator<Stats>| {
             mutator.state_mut().integers += 1;
             value + 1
         },
-        |_value: &MapValue, mutator: &mut MutateContext<'_, Stats>| {
+        |_value: &MapValue, mutator: &mut Mutator<Stats>| {
             mutator.default_mutate()
         },
     ),
@@ -527,41 +527,53 @@ assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2, 
3]);
 assert_eq!(mutator.state().integers, 2);
 ```
 
-`MutateContext::mutate` uses the copy path for a borrowed value, while
+`Mutator::mutate` uses the copy path for a borrowed value, while
 `maybe_inplace_mutate` preserves the reuse opportunity of an owned value.
 Callbacks are `Fn`; mutable data belongs in the mutator state.
 
-For ordinary mutable state, `#[dispatch(mutate)]` generates a
-`StructuralMutator` from `mutate_*` methods. A matching handler returns the
-current value's final result and may recursively call `self.mutate()`;
-an unmatched value follows default mutation with its current in-place permit:
+`#[dispatch(mutate)]` groups typed `mutate_*` callbacks. Dispatch only selects
+the first matching callback; `Mutator` supplies recursion, the current
+definition region, and mutable state. `mutator.mutate(child)` inherits the
+current region, while `mutate_with` is available for an explicit override. An
+unmatched value follows default mutation with its current in-place permit:
 
 ```rust
-use tvm_ffi::{dispatch, structural_mutate, Array, DefRegionKind};
+use tvm_ffi::{
+    dispatch, structural_mutate, Any, Array, MutateCallbacks, Mutator,
+};
 
 #[derive(Default)]
-struct Increment {
+struct IncrementState {
     integers: usize,
 }
 
+struct Increment;
+
 #[dispatch(mutate)]
 impl Increment {
-    fn mutate_integer(&mut self, value: i64, _kind: DefRegionKind) -> i64 {
-        self.integers += 1;
-        value + 1
+    fn mutate_integer(
+        &self,
+        value: i64,
+        mutator: &mut Mutator<IncrementState>,
+    ) -> Any {
+        mutator.state_mut().integers += 1;
+        Any::from(value + 1)
     }
 }
 
-let mut increment = Increment::default();
+let mut increment = MutateCallbacks::new(IncrementState::default(), Increment);
 let mutated = structural_mutate(
     Array::new(vec![1_i64, 2]),
     &mut increment,
 )?;
 let mutated = Array::<i64>::try_from(mutated)?;
 assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2, 3]);
-assert_eq!(increment.integers, 2);
+assert_eq!(increment.state().integers, 2);
 ```
 
+A generated dispatch whose context state is `()` can be passed directly to
+`structural_mutate`, without `MutateCallbacks`.
+
 For a named custom recursion policy, implement `StructuralMutator` and pass
 `&mut` it to `structural_mutate`. `InplaceValue` is an engine-issued
 capability: callers cannot construct it from a read-only `MapValue`. Override
diff --git a/rust/tvm-ffi-macros/src/dispatch.rs 
b/rust/tvm-ffi-macros/src/dispatch.rs
index 21c6ccb4..243e4916 100644
--- a/rust/tvm-ffi-macros/src/dispatch.rs
+++ b/rust/tvm-ffi-macros/src/dispatch.rs
@@ -19,17 +19,28 @@
 
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, quote_spanned};
-use syn::{parse_macro_input, FnArg, ImplItem, ImplItemMethod, ItemImpl, Meta, 
NestedMeta, Type};
+use quote::{format_ident, quote, quote_spanned};
+use syn::{
+    parse_macro_input, FnArg, GenericArgument, ImplItem, ImplItemMethod, 
ItemImpl, Meta,
+    NestedMeta, PathArguments, Type,
+};
 
 use crate::utils::get_tvm_ffi_crate;
 
 pub(crate) fn dispatch(attr: TokenStream, item: TokenStream) -> TokenStream {
     let args = parse_macro_input!(attr as DispatchArgs);
-    let item_impl = parse_macro_input!(item as ItemImpl);
+    let mut item_impl = parse_macro_input!(item as ItemImpl);
 
     match expand(&item_impl, args.mode) {
-        Ok(generated) => quote!(#item_impl #generated).into(),
+        Ok(generated) => {
+            if matches!(args.mode, DispatchMode::Mutate) {
+                if let Err(error) = specialize_mutate_handlers(&mut item_impl) 
{
+                    let error = error.to_compile_error();
+                    return quote!(#item_impl #error).into();
+                }
+            }
+            quote!(#item_impl #generated).into()
+        }
         Err(error) => {
             let error = error.to_compile_error();
             quote!(#item_impl #error).into()
@@ -37,6 +48,81 @@ pub(crate) fn dispatch(attr: TokenStream, item: TokenStream) 
-> TokenStream {
     }
 }
 
+fn specialize_mutate_handlers(item_impl: &mut ItemImpl) -> syn::Result<()> {
+    let tvm_ffi = get_tvm_ffi_crate();
+    for item in &mut item_impl.items {
+        let ImplItem::Method(method) = item else {
+            continue;
+        };
+        if !method.sig.ident.to_string().starts_with("mutate_") {
+            continue;
+        }
+
+        let handler = parse_handler(method, DispatchMode::Mutate)?;
+        let state = handler
+            .mutate_state
+            .expect("mutate handlers always record their context state");
+        let driver = format_ident!("__TvmFfiMutateDriver");
+        if method
+            .sig
+            .generics
+            .type_params()
+            .any(|param| param.ident == driver)
+        {
+            return Err(syn::Error::new_spanned(
+                &method.sig.generics,
+                "reserved mutate-handler generic name is already in use",
+            ));
+        }
+
+        method.sig.generics.params.push(syn::parse_quote!(#driver));
+        method
+            .sig
+            .generics
+            .make_where_clause()
+            .predicates
+            .push(syn::parse_quote!(
+                #driver: 
#tvm_ffi::extra::structural_mutate::MutateContextDriver<#state> + ?Sized
+            ));
+
+        let context = match method.sig.inputs.iter_mut().nth(2) {
+            Some(FnArg::Typed(context)) => context,
+            _ => unreachable!("the third mutate-handler argument cannot be a 
receiver"),
+        };
+        let Type::Reference(reference) = context.ty.as_mut() else {
+            unreachable!("parse_handler already validated the mutate context");
+        };
+        let Type::Path(path) = reference.elem.as_mut() else {
+            unreachable!("parse_handler already validated the mutate context 
path");
+        };
+        let segment = path
+            .path
+            .segments
+            .last_mut()
+            .expect("a parsed Rust type path always has a segment");
+        if matches!(segment.arguments, PathArguments::None) {
+            let arguments: syn::AngleBracketedGenericArguments =
+                syn::parse_quote!(<#state, #driver>);
+            segment.arguments = PathArguments::AngleBracketed(arguments);
+            continue;
+        }
+        let PathArguments::AngleBracketed(arguments) = &mut segment.arguments 
else {
+            unreachable!("parse_handler already rejected parenthesized 
arguments");
+        };
+        if !arguments
+            .args
+            .iter()
+            .any(|argument| matches!(argument, GenericArgument::Type(_)))
+        {
+            arguments.args.push(GenericArgument::Type(state.clone()));
+        }
+        arguments
+            .args
+            .push(GenericArgument::Type(syn::parse_quote!(#driver)));
+    }
+    Ok(())
+}
+
 struct DispatchArgs {
     mode: DispatchMode,
 }
@@ -77,8 +163,8 @@ impl DispatchMode {
 
     fn result_is_optional(self) -> bool {
         match self {
-            Self::Walk | Self::Map => true,
-            Self::Visit | Self::Mutate => false,
+            Self::Walk | Self::Map | Self::Mutate => true,
+            Self::Visit => false,
         }
     }
 }
@@ -102,11 +188,18 @@ impl syn::parse::Parse for DispatchArgs {
             ));
         };
         if !input.is_empty() {
-            return Err(input.error(format!(
-                "`dispatch({})` takes no further arguments; a handler that 
needs the \
-                 definition-region state declares a trailing `DefRegionKind` 
argument",
-                mode.name()
-            )));
+            let message = if matches!(mode, DispatchMode::Mutate) {
+                "`dispatch(mutate)` takes no further arguments; the definition 
region is \
+                 available through `Mutator::region()`"
+                    .to_owned()
+            } else {
+                format!(
+                    "`dispatch({})` takes no further arguments; a handler that 
needs the \
+                     definition-region state declares a trailing 
`DefRegionKind` argument",
+                    mode.name()
+                )
+            };
+            return Err(input.error(message));
         }
         Ok(DispatchArgs { mode })
     }
@@ -159,6 +252,16 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
             
#tvm_ffi::extra::structural_mutate::IntoMutateResult::into_mutate_result
         },
     };
+    let mutate_state = if matches!(mode, DispatchMode::Mutate) {
+        Some(
+            handlers[0]
+                .mutate_state
+                .as_ref()
+                .expect("mutate handlers always record their context state"),
+        )
+    } else {
+        None
+    };
     let links = expand_links(&handlers, mode, &into_result, quote!(value));
     let self_type = &item_impl.self_ty;
     let (impl_generics, _, where_clause) = item_impl.generics.split_for_impl();
@@ -236,34 +339,30 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) -> 
syn::Result<TokenStream2>
             }
         },
         DispatchMode::Mutate => {
-            let inplace_links =
-                expand_links(&handlers, mode, &into_result, 
quote!(value.as_value()));
+            let state = mutate_state.expect("mutate dispatch has a context 
state");
             quote! {
-                impl #impl_generics 
#tvm_ffi::extra::structural_mutate::StructuralMutator
+                impl #impl_generics 
#tvm_ffi::extra::structural_mutate::MutateDispatch
                     for #self_type #where_clause
                 {
-                    #[inline]
+                    type State = #state;
+
+                    #[inline(always)]
                     #[allow(unreachable_code, unused_variables)]
-                    fn dispatch_mutate(
-                        &mut self,
+                    fn dispatch_mutate<__TvmFfiMutateDriver>(
+                        &self,
                         value: &#tvm_ffi::extra::structural_mutate::MapValue,
-                        def_region_kind: 
#tvm_ffi::extra::structural_visit::DefRegionKind,
-                    ) -> #tvm_ffi::Result<#tvm_ffi::Any> {
+                        mutator: &mut 
#tvm_ffi::extra::structural_mutate::Mutator<
+                            Self::State,
+                            __TvmFfiMutateDriver,
+                        >,
+                    ) -> 
Option<#tvm_ffi::extra::structural_mutate::MutateResult>
+                    where
+                        __TvmFfiMutateDriver:
+                            
#tvm_ffi::extra::structural_mutate::MutateContextDriver<Self::State>
+                                + ?Sized,
+                    {
                         #(#links)*
-                        <Self as 
#tvm_ffi::extra::structural_mutate::StructuralMutator>::
-                            default_mutate(self, value, def_region_kind)
-                    }
-
-                    #[inline]
-                    #[allow(unreachable_code, unused_variables)]
-                    fn dispatch_maybe_inplace_mutate(
-                        &mut self,
-                        value: 
#tvm_ffi::extra::structural_mutate::InplaceValue<'_>,
-                        def_region_kind: 
#tvm_ffi::extra::structural_visit::DefRegionKind,
-                    ) -> #tvm_ffi::Result<#tvm_ffi::Any> {
-                        #(#inplace_links)*
-                        <Self as 
#tvm_ffi::extra::structural_mutate::StructuralMutator>::
-                            default_maybe_inplace_mutate(self, value, 
def_region_kind)
+                        None
                     }
                 }
             }
@@ -289,10 +388,10 @@ fn expand_links(
         .map(|handler| {
             let method = &handler.method;
             let attrs = &handler.cfg_attrs;
-            let kind_arg = if handler.wants_def_region {
-                quote!(, def_region_kind)
-            } else {
-                quote!()
+            let trailing_arg = match mode {
+                DispatchMode::Mutate => quote!(, mutator),
+                _ if handler.wants_def_region => quote!(, def_region_kind),
+                _ => quote!(),
             };
             let wrap_result = |result: TokenStream2| {
                 if mode.result_is_optional() {
@@ -304,7 +403,7 @@ fn expand_links(
             let invoke = match &handler.argument {
                 HandlerArgument::Value => {
                     let result = wrap_result(quote! {
-                        #into_result(self.#method(#value #kind_arg))
+                        #into_result(self.#method(#value #trailing_arg))
                     });
                     quote! {
                         return #result;
@@ -312,7 +411,7 @@ fn expand_links(
                 }
                 HandlerArgument::BorrowedNode(node_type) => {
                     let result = wrap_result(quote! {
-                        #into_result(self.#method(node #kind_arg))
+                        #into_result(self.#method(node #trailing_arg))
                     });
                     quote! {
                         if let Some(node) = #value.as_node::<#node_type>() {
@@ -322,7 +421,7 @@ fn expand_links(
                 }
                 HandlerArgument::Owned(value_type) => {
                     let result = wrap_result(quote! {
-                        #into_result(self.#method(typed #kind_arg))
+                        #into_result(self.#method(typed #trailing_arg))
                     });
                     quote! {
                         if let Some(typed) = #value.cast::<#value_type>() {
@@ -345,6 +444,7 @@ struct Handler {
     method: syn::Ident,
     argument: HandlerArgument,
     wants_def_region: bool,
+    mutate_state: Option<Type>,
     cfg_attrs: Vec<Meta>,
 }
 
@@ -356,22 +456,42 @@ enum HandlerArgument {
 
 fn parse_handler(method: &ImplItemMethod, mode: DispatchMode) -> 
syn::Result<Handler> {
     let inputs = &method.sig.inputs;
-    let receiver_is_mut = matches!(
-        inputs.first(),
-        Some(FnArg::Receiver(receiver))
-            if receiver.reference.is_some() && receiver.mutability.is_some()
-    );
-    if !receiver_is_mut || !(inputs.len() == 2 || inputs.len() == 3) {
-        return Err(syn::Error::new_spanned(
-            &method.sig,
+    let receiver_is_expected = match (mode, inputs.first()) {
+        (DispatchMode::Mutate, Some(FnArg::Receiver(receiver))) => {
+            receiver.reference.is_some() && receiver.mutability.is_none()
+        }
+        (_, Some(FnArg::Receiver(receiver))) => {
+            receiver.reference.is_some() && receiver.mutability.is_some()
+        }
+        _ => false,
+    };
+    let arity_is_expected = if matches!(mode, DispatchMode::Mutate) {
+        inputs.len() == 3
+    } else {
+        inputs.len() == 2 || inputs.len() == 3
+    };
+    if !receiver_is_expected || !arity_is_expected {
+        let message = if matches!(mode, DispatchMode::Mutate) {
+            "mutate handlers must take `&self`, a node, and `&mut 
Mutator<State>`".to_owned()
+        } else {
             format!(
                 "{} handlers must take `&mut self`, a node, and optionally a 
trailing \
                  `DefRegionKind` argument",
                 mode.name()
-            ),
-        ));
+            )
+        };
+        return Err(syn::Error::new_spanned(&method.sig, message));
     }
-    let wants_def_region = inputs.len() == 3;
+    let wants_def_region = !matches!(mode, DispatchMode::Mutate) && 
inputs.len() == 3;
+    let mutate_state = if matches!(mode, DispatchMode::Mutate) {
+        let context_type = match inputs.iter().nth(2) {
+            Some(FnArg::Typed(context)) => context.ty.as_ref(),
+            _ => unreachable!("the third argument cannot be a receiver"),
+        };
+        Some(parse_mutator_state(context_type)?)
+    } else {
+        None
+    };
 
     let value_type = match inputs.iter().nth(1) {
         Some(FnArg::Typed(value)) => (*value.ty).clone(),
@@ -401,10 +521,78 @@ fn parse_handler(method: &ImplItemMethod, mode: 
DispatchMode) -> syn::Result<Han
         method: method.sig.ident.clone(),
         argument,
         wants_def_region,
+        mutate_state,
         cfg_attrs,
     })
 }
 
+fn parse_mutator_state(context_type: &Type) -> syn::Result<Type> {
+    let Type::Reference(reference) = context_type else {
+        return Err(syn::Error::new_spanned(
+            context_type,
+            "the mutator must be `&mut Mutator<State>`",
+        ));
+    };
+    if reference.mutability.is_none() {
+        return Err(syn::Error::new_spanned(
+            context_type,
+            "the mutate context must be a mutable reference",
+        ));
+    }
+    let Type::Path(path) = reference.elem.as_ref() else {
+        return Err(syn::Error::new_spanned(
+            context_type,
+            "expected `&mut Mutator<State>`",
+        ));
+    };
+    let Some(segment) = path.path.segments.last() else {
+        return Err(syn::Error::new_spanned(
+            context_type,
+            "expected `&mut Mutator<State>`",
+        ));
+    };
+    let is_short_name = segment.ident == "Mutator";
+    if !is_short_name && segment.ident != "MutateContext" {
+        return Err(syn::Error::new_spanned(
+            context_type,
+            "expected `&mut Mutator<State>`",
+        ));
+    }
+    let arguments = match &segment.arguments {
+        PathArguments::AngleBracketed(arguments) => Some(arguments),
+        PathArguments::None if is_short_name => None,
+        _ => {
+            return Err(syn::Error::new_spanned(
+                context_type,
+                "`Mutator` accepts one optional state type",
+            ));
+        }
+    };
+    let mut state_types = arguments.into_iter().flat_map(|arguments| {
+        arguments.args.iter().filter_map(|argument| match argument {
+            GenericArgument::Type(state) => Some(state.clone()),
+            _ => None,
+        })
+    });
+    let state = match state_types.next() {
+        Some(state) => state,
+        None if is_short_name => syn::parse_quote!(()),
+        None => {
+            return Err(syn::Error::new_spanned(
+                context_type,
+                "`MutateContext` requires a state type",
+            ));
+        }
+    };
+    if state_types.next().is_some() {
+        return Err(syn::Error::new_spanned(
+            context_type,
+            "`Mutator` accepts at most one state type",
+        ));
+    }
+    Ok(state)
+}
+
 fn presence_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<Meta>> {
     attrs
         .iter()
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index ecd62ae0..e69f312b 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -108,14 +108,30 @@ impl<T: Into<Any>> IntoMapResult for Result<T> {
 ///
 /// A matched callback owns mutation of its value. Recursive operations
 /// reborrow the mutator, so mutable state cannot remain borrowed across them.
-pub struct MutateContext<'a, State> {
-    driver: &'a mut dyn MutateContextDriver<State>,
+pub struct MutateContext<'a, State, Driver: ?Sized = dyn 
MutateContextDriver<State> + 'a> {
+    driver: &'a mut Driver,
     current: MapValue,
     def_region_kind: DefRegionKind,
+    _state: PhantomData<fn() -> State>,
     _not_send_sync: PhantomData<Rc<()>>,
 }
 
-trait MutateContextDriver<State> {
+/// Recursive mutation operations passed to structural-mutation callbacks.
+///
+/// This is the concise callback-facing name for [`MutateContext`]. The
+/// borrow lifetime is inferred in function parameters, so stateful callbacks
+/// can write `&mut Mutator<State>` and stateless callbacks can write
+/// `&mut Mutator`.
+pub type Mutator<'a, State = (), Driver = dyn MutateContextDriver<State> + 'a> 
=
+    MutateContext<'a, State, Driver>;
+
+#[doc(hidden)]
+/// Internal operations used by [`MutateContext`].
+///
+/// The dispatch macro keeps the concrete implementor visible to the compiler
+/// so recursive `mutate` calls can be inlined. This is not a user extension
+/// point.
+pub trait MutateContextDriver<State> {
     fn state(&self) -> &State;
     fn state_mut(&mut self) -> &mut State;
     fn mutate_raw(
@@ -130,29 +146,43 @@ trait MutateContextDriver<State> {
     fn var_remap_set_raw(&mut self, raw: TVMFFIAny, mutated_value: &Any) -> 
Result<()>;
 }
 
-impl<State> MutateContext<'_, State> {
+impl<State, Driver> MutateContext<'_, State, Driver>
+where
+    Driver: MutateContextDriver<State> + ?Sized,
+{
     /// User state shared by every callback in this mutation.
+    #[inline(always)]
     pub fn state(&self) -> &State {
         self.driver.state()
     }
 
     /// Mutably borrow the user state.
+    #[inline(always)]
     pub fn state_mut(&mut self) -> &mut State {
         self.driver.state_mut()
     }
 
     /// Complete borrowed value active at this callback.
+    #[inline(always)]
     pub fn current(&self) -> &MapValue {
         &self.current
     }
 
     /// Definition-region state active at the callback's current value.
+    #[inline(always)]
     pub fn def_region_kind(&self) -> DefRegionKind {
         self.def_region_kind
     }
 
+    /// Definition region active at the callback's current value.
+    #[inline]
+    pub fn region(&self) -> DefRegionKind {
+        self.def_region_kind
+    }
+
     /// Mutate a borrowed value through the same callback chain. The value and
     /// its descendants begin on the non-in-place path.
+    #[inline(always)]
     pub fn mutate<T>(&mut self, value: &T) -> Result<Any>
     where
         for<'x> AnyView<'x>: From<&'x T>,
@@ -161,6 +191,7 @@ impl<State> MutateContext<'_, State> {
     }
 
     /// Mutate a borrowed value under an explicit definition-region state.
+    #[inline(always)]
     pub fn mutate_with<T>(&mut self, value: &T, def_region_kind: 
DefRegionKind) -> Result<Any>
     where
         for<'x> AnyView<'x>: From<&'x T>,
@@ -172,11 +203,13 @@ impl<State> MutateContext<'_, State> {
 
     /// Mutate an owned value, allowing an in-place attempt when it remains
     /// uniquely owned and no matched callback borrows it.
+    #[inline(always)]
     pub fn maybe_inplace_mutate<T: Into<Any>>(&mut self, value: T) -> 
Result<Any> {
         self.maybe_inplace_mutate_with(value, self.def_region_kind)
     }
 
     /// Mutate an owned value under an explicit definition-region state.
+    #[inline(always)]
     pub fn maybe_inplace_mutate_with<T: Into<Any>>(
         &mut self,
         value: T,
@@ -194,17 +227,20 @@ impl<State> MutateContext<'_, State> {
     ///
     /// This operation always uses the copy path because a callback may still
     /// hold a shared borrow of the current value. It may be called repeatedly.
+    #[inline(always)]
     pub fn default_mutate(&mut self) -> Result<Any> {
         self.driver
             .default_mutate_raw(self.current.raw(), self.def_region_kind)
     }
 
     /// Look up an invocation-local identity substitution.
+    #[inline(always)]
     pub fn var_remap_get(&mut self, var: &MapValue) -> Result<Option<Any>> {
         self.driver.var_remap_get_raw(var.raw())
     }
 
     /// Store an invocation-local identity substitution.
+    #[inline(always)]
     pub fn var_remap_set(&mut self, var: &MapValue, mutated_value: &Any) -> 
Result<()> {
         self.driver.var_remap_set_raw(var.raw(), mutated_value)
     }
@@ -212,11 +248,12 @@ impl<State> MutateContext<'_, State> {
 
 /// Conversion into the mutator argument accepted by [`structural_mutate`].
 ///
-/// Accepts a mutable [`StructuralMutator`] or a first-match callback chain.
-/// Use [`MutateCallbacks`] when the chain needs mutable state.
+/// Accepts a mutable low-level [`StructuralMutator`], a generated
+/// [`MutateDispatch`], or a first-match callback chain. Use 
[`MutateCallbacks`]
+/// when typed dispatch or a callback chain needs mutable state.
 #[diagnostic::on_unimplemented(
     message = "`{Self}` is not a supported `structural_mutate` mutator",
-    note = "accepted mutators: `&mut U` where `U: StructuralMutator`; an `Fn` 
callback over an FFI value type `T`, `&N` of an object node type, or 
`&MapValue`, followed by `&mut MutateContext<'_, ()>`; or a tuple of up to 12 
such callbacks (tuples may nest)",
+    note = "accepted mutators: `&mut U` where `U: StructuralMutator`; a 
generated `MutateDispatch<State = ()>`; an `Fn` callback over an FFI value type 
`T`, `&N` of an object node type, or `&MapValue`, followed by `&mut 
Mutator<State>`; or a tuple of up to 12 such callbacks (tuples may nest)",
     note = "callback arguments need explicit type annotations; use 
`MutateCallbacks::new(state, callbacks)` for ordinary mutable callback state"
 )]
 pub trait IntoMutator<Marker> {
@@ -256,8 +293,19 @@ impl<T: Into<Any>> IntoMutateResult for Result<T> {
 #[doc(hidden)]
 pub type MutateResult = Result<Any>;
 
+#[doc(hidden)]
+/// Callback tuples use a type-erased mutation driver.
+pub enum DynamicMutateCallbacks {}
+
+#[doc(hidden)]
+/// Generated dispatch keeps the concrete mutation driver for inlining.
+pub enum StaticMutateDispatch {}
+
 /// One typed callback in a callback-driven structural mutator.
 pub trait MutateChainLink<State, Marker>: mutate_sealed::SealedLink<State, 
Marker> {
+    #[doc(hidden)]
+    type Strategy;
+
     #[doc(hidden)]
     fn try_mutate(
         &self,
@@ -266,6 +314,25 @@ pub trait MutateChainLink<State, Marker>: 
mutate_sealed::SealedLink<State, Marke
     ) -> Option<MutateResult>;
 }
 
+/// Ordered typed callback dispatch for [`structural_mutate`].
+///
+/// `None` means no handler matched, so structural mutation applies its default
+/// behavior. A generated `#[dispatch(mutate)]` implementation tests
+/// `mutate_*` methods in source order and passes the same [`Mutator`] to
+/// the first match.
+pub trait MutateDispatch: Sized {
+    /// Mutable state shared by the dispatched callbacks.
+    type State;
+
+    fn dispatch_mutate<Driver>(
+        &self,
+        value: &MapValue,
+        mutator: &mut Mutator<Self::State, Driver>,
+    ) -> Option<MutateResult>
+    where
+        Driver: MutateContextDriver<Self::State> + ?Sized;
+}
+
 mod mutate_sealed {
     use super::{IntoMutateResult, MapValue, MutateContext, ObjectCore};
 
@@ -297,6 +364,27 @@ mod mutate_sealed {
         O: IntoMutateResult,
     {
     }
+
+    impl<D> SealedLink<D::State, super::ByMutateDispatch> for D where D: 
super::MutateDispatch {}
+}
+
+#[doc(hidden)]
+pub enum ByMutateDispatch {}
+
+impl<D> MutateChainLink<D::State, ByMutateDispatch> for D
+where
+    D: MutateDispatch,
+{
+    type Strategy = StaticMutateDispatch;
+
+    #[inline(always)]
+    fn try_mutate(
+        &self,
+        value: &MapValue,
+        mutator: &mut MutateContext<'_, D::State>,
+    ) -> Option<MutateResult> {
+        self.dispatch_mutate(value, mutator)
+    }
 }
 
 #[doc(hidden)]
@@ -308,6 +396,8 @@ where
     T: crate::type_traits::AnyCompatible,
     O: IntoMutateResult,
 {
+    type Strategy = DynamicMutateCallbacks;
+
     fn try_mutate(
         &self,
         value: &MapValue,
@@ -331,6 +421,8 @@ where
     N: ObjectCore,
     O: IntoMutateResult,
 {
+    type Strategy = DynamicMutateCallbacks;
+
     fn try_mutate(
         &self,
         value: &MapValue,
@@ -353,6 +445,8 @@ where
     ) -> O,
     O: IntoMutateResult,
 {
+    type Strategy = DynamicMutateCallbacks;
+
     fn try_mutate(
         &self,
         value: &MapValue,
@@ -379,6 +473,8 @@ macro_rules! impl_mutate_chain_link {
         where
             $($F: MutateChainLink<State, $M>,)+
         {
+            type Strategy = DynamicMutateCallbacks;
+
             fn try_mutate(
                 &self,
                 value: &MapValue,
@@ -397,7 +493,7 @@ macro_rules! impl_mutate_chain_link {
 
 impl_callback_chain_tuple_arities!(impl_mutate_chain_link);
 
-/// A reusable callback mutator with shared user state.
+/// A reusable typed-dispatch or callback mutator with shared user state.
 pub struct MutateCallbacks<State, Link, Marker> {
     state: State,
     callbacks: Rc<Link>,
@@ -472,6 +568,7 @@ pub struct 
ByMutateCallbacks<Marker>(PhantomData<fn(Marker)>);
 impl<Link, Marker> IntoMutator<ByMutateCallbacks<Marker>> for Link
 where
     Link: MutateChainLink<(), Marker>,
+    Link::Strategy: MutateCallbackStrategy<(), Link, Marker>,
 {
     fn mutate_root(self, root: Any) -> Result<Any> {
         let callbacks = self;
@@ -900,10 +997,11 @@ impl StructuralVarRemap {
     }
 }
 
-/// A mutator that controls its own recursion.
+/// A low-level mutator that controls its own recursion.
 ///
 /// Implementations descend with the `mutate` or `default_*` helpers.
-/// `#[dispatch(mutate)]` generates this trait from typed `mutate_*` methods.
+/// Prefer mutation callbacks or `#[dispatch(mutate)]` for typed dispatch with
+/// recursion supplied through [`Mutator`].
 pub trait StructuralMutator: Sized {
     /// Dispatch one borrowed value without modifying its source storage.
     ///
@@ -996,33 +1094,104 @@ pub trait StructuralMutator: Sized {
     }
 }
 
-fn try_mutate_callbacks<State, Link, Marker>(
-    driver: &mut impl MutateContextDriver<State>,
+// A plain closure has a fixed `&mut Mutator<State>` signature, while a
+// macro-generated dispatch method can be generic over the concrete driver.
+// Select the matching representation without changing the public callback API.
+trait MutateCallbackStrategy<State, Link, Marker> {
+    fn try_mutate<Driver>(
+        driver: &mut Driver,
+        callback_ptr: *const Link,
+        value: &MapValue,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MutateResult>
+    where
+        Driver: MutateContextDriver<State>;
+}
+
+impl<State, Link, Marker> MutateCallbackStrategy<State, Link, Marker> for 
DynamicMutateCallbacks
+where
+    Link: MutateChainLink<State, Marker>,
+{
+    #[inline(always)]
+    fn try_mutate<Driver>(
+        driver: &mut Driver,
+        callback_ptr: *const Link,
+        value: &MapValue,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MutateResult>
+    where
+        Driver: MutateContextDriver<State>,
+    {
+        let mut mutator = MutateContext::<State, dyn 
MutateContextDriver<State>> {
+            driver,
+            current: MapValue::from_raw(value.raw()),
+            def_region_kind,
+            _state: PhantomData,
+            _not_send_sync: PhantomData,
+        };
+        // SAFETY: The owning `Rc` or the direct callback's stack slot remains 
live
+        // and is never modified through the driver during recursive reentry.
+        unsafe { (&*callback_ptr).try_mutate(value, &mut mutator) }
+    }
+}
+
+impl<State, Dispatch> MutateCallbackStrategy<State, Dispatch, ByMutateDispatch>
+    for StaticMutateDispatch
+where
+    Dispatch: MutateDispatch<State = State>,
+{
+    #[inline(always)]
+    fn try_mutate<Driver>(
+        driver: &mut Driver,
+        callback_ptr: *const Dispatch,
+        value: &MapValue,
+        def_region_kind: DefRegionKind,
+    ) -> Option<MutateResult>
+    where
+        Driver: MutateContextDriver<State>,
+    {
+        let mut mutator = MutateContext::<State, Driver> {
+            driver,
+            current: MapValue::from_raw(value.raw()),
+            def_region_kind,
+            _state: PhantomData,
+            _not_send_sync: PhantomData,
+        };
+        // SAFETY: The dispatch value is held by the owning `Rc` or by the
+        // direct callback's stack slot and is only borrowed immutably.
+        unsafe { (&*callback_ptr).dispatch_mutate(value, &mut mutator) }
+    }
+}
+
+#[inline(always)]
+fn try_mutate_callbacks<State, Link, Marker, Driver>(
+    driver: &mut Driver,
     callback_ptr: *const Link,
     value: &MapValue,
     def_region_kind: DefRegionKind,
 ) -> Option<MutateResult>
 where
     Link: MutateChainLink<State, Marker>,
+    Link::Strategy: MutateCallbackStrategy<State, Link, Marker>,
+    Driver: MutateContextDriver<State>,
 {
-    let mut mutator = MutateContext {
+    <Link::Strategy as MutateCallbackStrategy<State, Link, 
Marker>>::try_mutate(
         driver,
-        current: MapValue::from_raw(value.raw()),
+        callback_ptr,
+        value,
         def_region_kind,
-        _not_send_sync: PhantomData,
-    };
-    // SAFETY: The owning `Rc` or the direct callback's stack slot remains live
-    // and is never modified through the driver during recursive reentry.
-    unsafe { (&*callback_ptr).try_mutate(value, &mut mutator) }
+    )
 }
 
 impl<State, Link, Marker> StructuralMutator for MutateCallbacks<State, Link, 
Marker>
 where
     Link: MutateChainLink<State, Marker>,
+    Link::Strategy: MutateCallbackStrategy<State, Link, Marker>,
 {
+    #[inline(always)]
     fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any> {
         let callback_ptr = Rc::as_ptr(&self.callbacks);
-        match try_mutate_callbacks::<State, Link, Marker>(
+        match try_mutate_callbacks::<State, Link, Marker, _>(
             self,
             callback_ptr,
             value,
@@ -1033,13 +1202,14 @@ where
         }
     }
 
+    #[inline(always)]
     fn dispatch_maybe_inplace_mutate(
         &mut self,
         value: InplaceValue<'_>,
         def_region_kind: DefRegionKind,
     ) -> Result<Any> {
         let callback_ptr = Rc::as_ptr(&self.callbacks);
-        match try_mutate_callbacks::<State, Link, Marker>(
+        match try_mutate_callbacks::<State, Link, Marker, _>(
             self,
             callback_ptr,
             value.as_value(),
@@ -1054,22 +1224,30 @@ where
 impl<Link, Marker> StructuralMutator for DirectMutateCallbacks<'_, Link, 
Marker>
 where
     Link: MutateChainLink<(), Marker>,
+    Link::Strategy: MutateCallbackStrategy<(), Link, Marker>,
 {
+    #[inline(always)]
     fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind: 
DefRegionKind) -> Result<Any> {
         let callback_ptr = std::ptr::from_ref(self.callbacks);
-        match try_mutate_callbacks::<(), Link, Marker>(self, callback_ptr, 
value, def_region_kind) {
+        match try_mutate_callbacks::<(), Link, Marker, _>(
+            self,
+            callback_ptr,
+            value,
+            def_region_kind,
+        ) {
             Some(result) => result,
             None => self.default_mutate(value, def_region_kind),
         }
     }
 
+    #[inline(always)]
     fn dispatch_maybe_inplace_mutate(
         &mut self,
         value: InplaceValue<'_>,
         def_region_kind: DefRegionKind,
     ) -> Result<Any> {
         let callback_ptr = std::ptr::from_ref(self.callbacks);
-        match try_mutate_callbacks::<(), Link, Marker>(
+        match try_mutate_callbacks::<(), Link, Marker, _>(
             self,
             callback_ptr,
             value.as_value(),
@@ -1085,14 +1263,17 @@ impl<State, Driver> MutateContextDriver<State> for 
Driver
 where
     Driver: StructuralMutator + MutateCallbackState<State>,
 {
+    #[inline(always)]
     fn state(&self) -> &State {
         self.callback_state()
     }
 
+    #[inline(always)]
     fn state_mut(&mut self) -> &mut State {
         self.callback_state_mut()
     }
 
+    #[inline(always)]
     fn mutate_raw(
         &mut self,
         raw: TVMFFIAny,
@@ -1102,6 +1283,7 @@ where
         dispatch_user_raw(self, raw, def_region_kind, permit)
     }
 
+    #[inline(always)]
     fn default_mutate_raw(
         &mut self,
         raw: TVMFFIAny,
@@ -1110,17 +1292,20 @@ where
         default_mutate_driver(self, raw, def_region_kind, Permit::Copy)
     }
 
+    #[inline(always)]
     fn var_remap_get_raw(&mut self, raw: TVMFFIAny) -> Result<Option<Any>> {
         <Self as StructuralMutator>::var_remap_get(self, 
&MapValue::from_raw(raw))
     }
 
+    #[inline(always)]
     fn var_remap_set_raw(&mut self, raw: TVMFFIAny, mutated_value: &Any) -> 
Result<()> {
         <Self as StructuralMutator>::var_remap_set(self, 
&MapValue::from_raw(raw), mutated_value)
     }
 }
 
+#[doc(hidden)]
 #[derive(Clone, Copy, PartialEq, Eq)]
-enum Permit {
+pub enum Permit {
     Copy,
     MaybeInPlace,
 }
@@ -2089,6 +2274,7 @@ fn with_mutator_def_region<T>(
     }
 }
 
+#[inline(always)]
 fn dispatch_user_raw<U: StructuralMutator>(
     mutator: &mut U,
     raw: TVMFFIAny,
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 8be52bbe..67f96dcc 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -51,7 +51,7 @@ pub use crate::extra::module::Module;
 pub use crate::extra::structural_mutate::{
     structural_map, structural_mutate, InplaceValue, IntoMapResult, 
IntoMapper, IntoMutator,
     MapChainLink, MapDispatch, MapValue, MutateCallbacks, MutateChainLink, 
MutateContext,
-    StructuralMutator, StructuralVarRemap,
+    MutateDispatch, Mutator, StructuralMutator, StructuralVarRemap,
 };
 pub use crate::extra::structural_visit::{
     structural_visit, structural_walk, DefRegionKind, IntoVisitor, 
IntoWalkResult, IntoWalker,
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index cf364a56..b38c29ce 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -29,7 +29,7 @@ use tvm_ffi::tvm_ffi_sys::{
 };
 use tvm_ffi::{
     dispatch, structural_map, structural_mutate, Any, AnyView, Array, 
DefRegionKind, Error,
-    Function, InplaceValue, Map, MapDispatch, MapValue, MutateCallbacks, 
MutateContext, Object,
+    Function, InplaceValue, Map, MapDispatch, MapValue, MutateCallbacks, 
Mutator, Object,
     ObjectArc, ObjectCore, ObjectRefCore, Result, String as FfiString, 
StructuralMutator,
     StructuralVarRemap, TypeIndex, WalkOrder, RUNTIME_ERROR,
 };
@@ -1087,7 +1087,7 @@ fn 
callback_errors_preserve_message_and_add_object_context() {
 
     let error = match structural_mutate(
         Array::new(vec![1i64]),
-        |_integer: i64, _mutator: &mut MutateContext<'_, ()>| -> Result<i64> {
+        |_integer: i64, _mutator: &mut Mutator| -> Result<i64> {
             Err(Error::new(
                 RUNTIME_ERROR,
                 "callback mutator failed",
@@ -1133,10 +1133,9 @@ fn registered_mutation_hooks_receive_the_rust_mutator() {
     assert!(error.message().contains("retained after its active call"));
 
     let mutate_calls_before = REGISTERED_MUTATE_CALLS.load(Ordering::Relaxed);
-    let mutated = structural_mutate(
-        source.clone(),
-        |value: i64, _mutator: &mut MutateContext<'_, ()>| Any::from(value + 
1),
-    )
+    let mutated = structural_mutate(source.clone(), |value: i64, _mutator: 
&mut Mutator| {
+        Any::from(value + 1)
+    })
     .and_then(i64::try_from)
     .unwrap();
     assert_eq!(mutated, 2);
@@ -1377,23 +1376,45 @@ fn 
generated_map_dispatch_supports_kind_and_ordered_catch_all() {
 }
 
 #[derive(Default)]
-struct GeneratedLeafMutator {
+struct GeneratedLeafState {
     integers: Vec<(i64, DefRegionKind)>,
 }
 
+struct GeneratedLeafDispatch;
+
 #[dispatch(mutate)]
-impl GeneratedLeafMutator {
-    fn mutate_integer(&mut self, value: i64, kind: DefRegionKind) -> Any {
-        self.integers.push((value, kind));
+impl GeneratedLeafDispatch {
+    fn mutate_integer(&self, value: i64, mutator: &mut 
Mutator<GeneratedLeafState>) -> Any {
+        let region = mutator.region();
+        mutator.state_mut().integers.push((value, region));
         Any::from(value + 1)
     }
 }
 
+struct GeneratedStatelessDispatch;
+
+#[dispatch(mutate)]
+impl GeneratedStatelessDispatch {
+    fn mutate_integer(&self, value: i64, _mutator: &mut Mutator) -> i64 {
+        value + 1
+    }
+}
+
+#[test]
+fn generated_stateless_mutate_dispatch_is_a_direct_callback() {
+    assert_eq!(
+        structural_mutate(1i64, GeneratedStatelessDispatch)
+            .and_then(i64::try_from)
+            .unwrap(),
+        2
+    );
+}
+
 #[test]
-fn generated_mutator_defaults_unmatched_values_and_preserves_inplace_permit() {
+fn 
generated_mutate_dispatch_defaults_unmatched_values_and_preserves_inplace_permit()
 {
     let root = Array::new(vec![1i64, 2]);
     let root_pointer = array_pointer(&root);
-    let mut mutator = GeneratedLeafMutator::default();
+    let mut mutator = MutateCallbacks::new(GeneratedLeafState::default(), 
GeneratedLeafDispatch);
     let mutated = structural_mutate(root, &mut mutator)
         .and_then(Array::<i64>::try_from)
         .unwrap();
@@ -1401,126 +1422,188 @@ fn 
generated_mutator_defaults_unmatched_values_and_preserves_inplace_permit() {
     assert_eq!(array_pointer(&mutated), root_pointer);
     assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2, 3]);
     assert_eq!(
-        mutator.integers,
+        mutator.state().integers,
         vec![(1, DefRegionKind::None), (2, DefRegionKind::None)]
     );
 }
 
 #[test]
-fn generated_mutator_default_remap_crosses_registered_hooks() {
+fn generated_mutate_dispatch_default_remap_crosses_registered_hooks() {
     ensure_test_types_registered();
     let _guard = REGISTERED_HOOK_TEST_LOCK.lock().unwrap();
     RETAINED_MUTATOR.with(|retained| {
         retained.take();
     });
 
-    let mut mutator = GeneratedLeafMutator::default();
+    let mut mutator = MutateCallbacks::new(GeneratedLeafState::default(), 
GeneratedLeafDispatch);
     let mutated = structural_mutate(rust_hook_node(), &mut mutator)
         .and_then(i64::try_from)
         .unwrap();
     assert_eq!(mutated, 2);
-    assert_eq!(mutator.integers, vec![(1, DefRegionKind::None)]);
+    assert_eq!(mutator.state().integers, vec![(1, DefRegionKind::None)]);
     RETAINED_MUTATOR.with(|retained| {
         retained.take();
     });
 }
 
 #[derive(Default)]
-struct GeneratedRecursiveMutator {
-    integers: Vec<i64>,
+struct GeneratedRecursiveState {
+    arrays: Vec<DefRegionKind>,
+    integers: Vec<(i64, DefRegionKind)>,
 }
 
+struct GeneratedRecursiveDispatch;
+
 #[dispatch(mutate)]
-impl GeneratedRecursiveMutator {
-    fn mutate_array(&mut self, array: Array<i64>, kind: DefRegionKind) -> 
Result<Array<i64>> {
+impl GeneratedRecursiveDispatch {
+    fn mutate_array(
+        &self,
+        array: Array<i64>,
+        mutator: &mut Mutator<GeneratedRecursiveState>,
+    ) -> Result<Array<i64>> {
+        let region = mutator.region();
+        mutator.state_mut().arrays.push(region);
         let mut mutated = Vec::with_capacity(array.len());
         for value in array.iter() {
-            mutated.push(i64::try_from(self.mutate(&value, kind)?)?);
+            mutated.push(i64::try_from(mutator.mutate(&value)?)?);
         }
         Ok(Array::new(mutated))
     }
 
-    fn mutate_integer(&mut self, value: i64) -> Any {
-        self.integers.push(value);
+    fn mutate_integer(&self, value: i64, mutator: &mut 
Mutator<GeneratedRecursiveState>) -> Any {
+        let region = mutator.region();
+        mutator.state_mut().integers.push((value, region));
         Any::from(value + 10)
     }
 }
 
 #[test]
-fn generated_mutator_can_drive_recursion_through_mut_self() {
-    let mut mutator = GeneratedRecursiveMutator::default();
+fn generated_mutate_dispatch_recurses_through_context() {
+    let mut mutator = MutateCallbacks::new(
+        GeneratedRecursiveState::default(),
+        GeneratedRecursiveDispatch,
+    );
     let mutated = structural_mutate(Array::new(vec![1i64, 2]), &mut mutator)
         .and_then(Array::<i64>::try_from)
         .unwrap();
 
     assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![11, 12]);
-    assert_eq!(mutator.integers, vec![1, 2]);
+    assert_eq!(mutator.state().arrays, vec![DefRegionKind::None]);
+    assert_eq!(
+        mutator.state().integers,
+        vec![(1, DefRegionKind::None), (2, DefRegionKind::None)]
+    );
+}
+
+#[test]
+fn generated_mutate_dispatch_inherits_region_during_explicit_recursion() {
+    ensure_test_types_registered();
+    let _guard = REFLECTED_TEST_LOCK.lock().unwrap();
+    let root = rust_pair(Array::new(vec![1i64]), Any::new());
+    let mut mutator = MutateCallbacks::new(
+        GeneratedRecursiveState::default(),
+        GeneratedRecursiveDispatch,
+    );
+
+    let mutated = structural_mutate(root, &mut mutator)
+        .and_then(RustPair::try_from)
+        .unwrap();
+    let first = Array::<i64>::try_from(mutated.data.first.clone()).unwrap();
+
+    assert_eq!(first.iter().collect::<Vec<_>>(), vec![11]);
+    assert_eq!(mutator.state().arrays, vec![DefRegionKind::Recursive]);
+    assert_eq!(
+        mutator.state().integers,
+        vec![(1, DefRegionKind::Recursive)]
+    );
 }
 
 #[derive(Default)]
-struct GeneratedDefaultingMutator {
+struct GeneratedDefaultingState {
     arrays: usize,
     integers: Vec<i64>,
 }
 
+struct GeneratedDefaultingDispatch;
+
 #[dispatch(mutate)]
-impl GeneratedDefaultingMutator {
-    fn mutate_array(&mut self, array: Array<i64>, kind: DefRegionKind) -> 
Result<Any> {
-        self.arrays += 1;
-        self.default_mutate_value(&array, kind)
+impl GeneratedDefaultingDispatch {
+    fn mutate_array(
+        &self,
+        _array: Array<i64>,
+        mutator: &mut Mutator<GeneratedDefaultingState>,
+    ) -> Result<Any> {
+        mutator.state_mut().arrays += 1;
+        mutator.default_mutate()
     }
 
-    fn mutate_integer(&mut self, value: i64) -> Any {
-        self.integers.push(value);
+    fn mutate_integer(&self, value: i64, mutator: &mut 
Mutator<GeneratedDefaultingState>) -> Any {
+        mutator.state_mut().integers.push(value);
         Any::from(value + 1)
     }
 }
 
 #[test]
-fn generated_mutator_can_default_recurse_from_a_typed_handler() {
-    let mut mutator = GeneratedDefaultingMutator::default();
+fn generated_mutate_dispatch_can_default_recurse_from_a_typed_handler() {
+    let mut mutator = MutateCallbacks::new(
+        GeneratedDefaultingState::default(),
+        GeneratedDefaultingDispatch,
+    );
     let mutated = structural_mutate(Array::new(vec![1i64, 2]), &mut mutator)
         .and_then(Array::<i64>::try_from)
         .unwrap();
 
     assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2, 3]);
-    assert_eq!(mutator.arrays, 1);
-    assert_eq!(mutator.integers, vec![1, 2]);
+    assert_eq!(mutator.state().arrays, 1);
+    assert_eq!(mutator.state().integers, vec![1, 2]);
 }
 
-struct GeneratedRemappingMutator {
+struct GeneratedRemappingState {
     type_index: i32,
     calls: usize,
 }
 
+struct GeneratedRemappingDispatch;
+
 #[dispatch(mutate)]
-impl GeneratedRemappingMutator {
-    fn mutate_dag_node(&mut self, _value: &RustDagNodeObj) -> Any {
+impl GeneratedRemappingDispatch {
+    fn mutate_dag_node(
+        &self,
+        _value: &RustDagNodeObj,
+        _mutator: &mut Mutator<GeneratedRemappingState>,
+    ) -> Any {
         Any::from(42i64)
     }
 
-    fn mutate_any(&mut self, value: &MapValue, kind: DefRegionKind) -> 
Result<Any> {
-        if value.type_index() != self.type_index {
-            return self.default_mutate(value, kind);
+    fn mutate_any(
+        &self,
+        value: &MapValue,
+        mutator: &mut Mutator<GeneratedRemappingState>,
+    ) -> Result<Any> {
+        if value.type_index() != mutator.state().type_index {
+            return mutator.default_mutate();
         }
-        if let Some(mutated) = self.var_remap_get(value)? {
+        if let Some(mutated) = mutator.var_remap_get(value)? {
             return Ok(mutated);
         }
-        self.calls += 1;
+        mutator.state_mut().calls += 1;
         let mutated = Any::from(41i64);
-        self.var_remap_set(value, &mutated)?;
+        mutator.var_remap_set(value, &mutated)?;
         Ok(mutated)
     }
 }
 
 #[test]
-fn generated_mutator_uses_fresh_invocation_local_var_remap() {
+fn generated_mutate_dispatch_uses_fresh_invocation_local_var_remap() {
     ensure_test_types_registered();
     let var = rust_free_var();
-    let mut mutator = GeneratedRemappingMutator {
-        type_index: RustFreeVarObj::type_index(),
-        calls: 0,
-    };
+    let mut mutator = MutateCallbacks::new(
+        GeneratedRemappingState {
+            type_index: RustFreeVarObj::type_index(),
+            calls: 0,
+        },
+        GeneratedRemappingDispatch,
+    );
 
     for expected_calls in [1, 2] {
         let root = call_global(
@@ -1528,7 +1611,7 @@ fn 
generated_mutator_uses_fresh_invocation_local_var_remap() {
             &[Any::from(var.clone()), Any::from(var.clone())],
         );
         let mutated = structural_mutate(root, &mut mutator).unwrap();
-        assert_eq!(mutator.calls, expected_calls);
+        assert_eq!(mutator.state().calls, expected_calls);
         assert_eq!(i64::try_from(array_item(&mutated, 0)).unwrap(), 41);
         assert_eq!(i64::try_from(array_item(&mutated, 1)).unwrap(), 41);
     }
@@ -1622,7 +1705,7 @@ fn callbacks_return_values_convertible_into_any() {
 
     let mutated = structural_mutate(
         Array::new(vec![1i64, 2]),
-        |integer: i64, _mutator: &mut MutateContext<'_, ()>| integer * 2,
+        |integer: i64, _mutator: &mut Mutator| integer * 2,
     )
     .and_then(Array::<i64>::try_from)
     .unwrap();
@@ -1781,7 +1864,7 @@ fn 
callback_mutate_defaults_unmatched_values_and_preserves_root_permit() {
     ensure_test_types_registered();
     let root = Array::new(vec![1i64, 2]);
     let root_pointer = array_pointer(&root);
-    let mutated = structural_mutate(root, |value: i64, _mutator: &mut 
MutateContext<'_, ()>| {
+    let mutated = structural_mutate(root, |value: i64, _mutator: &mut Mutator| 
{
         Any::from(value + 1)
     })
     .and_then(Array::<i64>::try_from)
@@ -1796,17 +1879,14 @@ struct CallbackMutateStats {
     defaults: usize,
 }
 
-fn stateful_mutate_integer(
-    value: i64,
-    mutator: &mut MutateContext<'_, CallbackMutateStats>,
-) -> Any {
+fn stateful_mutate_integer(value: i64, mutator: &mut 
Mutator<CallbackMutateStats>) -> Any {
     mutator.state_mut().integers.push(value);
     Any::from(value + 1)
 }
 
 fn stateful_mutate_default(
     _value: &MapValue,
-    mutator: &mut MutateContext<'_, CallbackMutateStats>,
+    mutator: &mut Mutator<CallbackMutateStats>,
 ) -> Result<Any> {
     mutator.state_mut().defaults += 1;
     mutator.default_mutate()
@@ -1846,7 +1926,7 @@ struct CallbackMutateDepth {
 
 fn stateful_mutate_recursive(
     value: &MapValue,
-    mutator: &mut MutateContext<'_, CallbackMutateDepth>,
+    mutator: &mut Mutator<CallbackMutateDepth>,
 ) -> Result<Any> {
     assert_eq!(mutator.current().type_index(), value.type_index());
     {
@@ -1891,8 +1971,8 @@ fn 
callback_mutate_current_default_is_repeatable_copy_path() {
     let mutated = structural_mutate(
         root,
         (
-            |value: i64, _mutator: &mut MutateContext<'_, ()>| Any::from(value 
+ 1),
-            |_value: &MapValue, mutator: &mut MutateContext<'_, ()>| -> 
Result<Any> {
+            |value: i64, _mutator: &mut Mutator| Any::from(value + 1),
+            |_value: &MapValue, mutator: &mut Mutator| -> Result<Any> {
                 defaults.set(defaults.get() + 1);
                 let first = mutator.default_mutate()?;
                 let second = mutator.default_mutate()?;
@@ -1915,10 +1995,8 @@ fn 
callback_mutate_match_is_final_and_same_fn_can_reenter() {
     let mutated = structural_mutate(
         Array::new(vec![1i64]),
         (
-            |_array: Array<i64>, _mutator: &mut MutateContext<'_, ()>| {
-                Any::from(Array::new(vec![10i64]))
-            },
-            |value: i64, _mutator: &mut MutateContext<'_, ()>| {
+            |_array: Array<i64>, _mutator: &mut Mutator| 
Any::from(Array::new(vec![10i64])),
+            |value: i64, _mutator: &mut Mutator| {
                 integer_calls.set(integer_calls.get() + 1);
                 Any::from(value + 1)
             },
@@ -1932,7 +2010,7 @@ fn 
callback_mutate_match_is_final_and_same_fn_can_reenter() {
     let calls = Cell::new(0);
     let mutated = structural_mutate(
         Array::new(vec![1i64, 2]),
-        |_value: &MapValue, mutator: &mut MutateContext<'_, ()>| {
+        |_value: &MapValue, mutator: &mut Mutator| {
             calls.set(calls.get() + 1);
             mutator.default_mutate()
         },
@@ -1956,10 +2034,10 @@ fn 
callback_mutate_supports_node_links_nested_tuples_and_reflection() {
         root,
         (
             (
-                |_value: f64, _mutator: &mut MutateContext<'_, ()>| Any::new(),
-                |_node: &RustDagNodeObj, _mutator: &mut MutateContext<'_, ()>| 
Any::from(7i64),
+                |_value: f64, _mutator: &mut Mutator| Any::new(),
+                |_node: &RustDagNodeObj, _mutator: &mut Mutator| 
Any::from(7i64),
             ),
-            |value: i64, mutator: &mut MutateContext<'_, ()>| {
+            |value: i64, mutator: &mut Mutator| {
                 regions.borrow_mut().push(mutator.def_region_kind());
                 Any::from(value + 1)
             },
@@ -1982,8 +2060,8 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
     let mutated = structural_mutate(
         true,
         (
-            |_value: bool, mutator: &mut MutateContext<'_, ()>| 
mutator.mutate(&borrowed_child),
-            |value: i64, _mutator: &mut MutateContext<'_, ()>| Any::from(value 
+ 1),
+            |_value: bool, mutator: &mut Mutator| 
mutator.mutate(&borrowed_child),
+            |value: i64, _mutator: &mut Mutator| Any::from(value + 1),
         ),
     )
     .and_then(Array::<i64>::try_from)
@@ -1996,12 +2074,12 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
     let mutated = structural_mutate(
         true,
         (
-            |_value: bool, mutator: &mut MutateContext<'_, ()>| {
+            |_value: bool, mutator: &mut Mutator| {
                 let child = Array::new(vec![1i64]);
                 owned_pointer.set(array_pointer(&child) as usize);
                 mutator.maybe_inplace_mutate(child)
             },
-            |value: i64, _mutator: &mut MutateContext<'_, ()>| Any::from(value 
+ 1),
+            |value: i64, _mutator: &mut Mutator| Any::from(value + 1),
         ),
     )
     .and_then(Array::<i64>::try_from)
@@ -2018,7 +2096,7 @@ fn 
callback_mutate_can_use_its_invocation_local_var_remap() {
     let type_index = RustFreeVarObj::type_index();
     let mut mutator = MutateCallbacks::new(
         (),
-        |value: &MapValue, mutator: &mut MutateContext<'_, ()>| -> Result<Any> 
{
+        |value: &MapValue, mutator: &mut Mutator| -> Result<Any> {
             if value.type_index() != type_index {
                 return mutator.default_mutate();
             }
@@ -2046,20 +2124,16 @@ fn 
callback_mutate_can_use_its_invocation_local_var_remap() {
 
 #[test]
 fn nested_callback_mutate_restores_the_outer_active_mutator() {
-    let mutated = structural_mutate(
-        1i64,
-        |value: i64, mutator: &mut MutateContext<'_, ()>| -> Result<Any> {
-            if value != 1 {
-                return Ok(Any::from(value + 1));
-            }
-            let inner =
-                structural_mutate(2i64, |value: i64, _mutator: &mut 
MutateContext<'_, ()>| {
-                    Any::from(value + 10)
-                })?;
-            assert_eq!(i64::try_from(inner).unwrap(), 12);
-            mutator.mutate(&3i64)
-        },
-    )
+    let mutated = structural_mutate(1i64, |value: i64, mutator: &mut Mutator| 
-> Result<Any> {
+        if value != 1 {
+            return Ok(Any::from(value + 1));
+        }
+        let inner = structural_mutate(2i64, |value: i64, _mutator: &mut 
Mutator| {
+            Any::from(value + 10)
+        })?;
+        assert_eq!(i64::try_from(inner).unwrap(), 12);
+        mutator.mutate(&3i64)
+    })
     .and_then(i64::try_from)
     .unwrap();
     assert_eq!(mutated, 4);
@@ -2070,9 +2144,7 @@ fn 
callback_mutate_panics_resume_and_leave_the_next_run_usable() {
     let panic = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| 
{
         structural_mutate(
             Array::new(vec![1i64]),
-            |_value: i64, _mutator: &mut MutateContext<'_, ()>| -> Any {
-                panic!("callback mutator panic")
-            },
+            |_value: i64, _mutator: &mut Mutator| -> Any { panic!("callback 
mutator panic") },
         )
     })) {
         Err(panic) => panic,
@@ -2085,7 +2157,7 @@ fn 
callback_mutate_panics_resume_and_leave_the_next_run_usable() {
 
     let mutated = structural_mutate(
         Array::new(vec![1i64]),
-        |value: i64, _mutator: &mut MutateContext<'_, ()>| Any::from(value + 
1),
+        |value: i64, _mutator: &mut Mutator| Any::from(value + 1),
     )
     .and_then(Array::<i64>::try_from)
     .unwrap();

Reply via email to