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 8682539f [REFACTOR][RUST] Clarify structural mutator dispatch and
recursion APIs (#714)
8682539f is described below
commit 8682539fda858409ab4705cd4b02e726f453f8d2
Author: Shushi Hong <[email protected]>
AuthorDate: Sat Aug 29 14:40:12 2026 -0400
[REFACTOR][RUST] Clarify structural mutator dispatch and recursion APIs
(#714)
This PR clarifies the Rust structural-mutation API by separating engine
dispatch hooks from user-controlled recursive mutation.
The engine-facing hooks are renamed to:
- `mutate` → `dispatch_mutate`
- `maybe_inplace_mutate` → `dispatch_maybe_inplace_mutate`
The user-facing recursive methods are renamed to:
- `mutate_child` → `mutate`
- `maybe_inplace_mutate_child` → `maybe_inplace_mutate`
This makes the correspondence with the C++ protocol clearer:
- C++ vtable dispatch → Rust `dispatch_mutate`
- Recursive mutation → Rust `mutate`
- Default mutation of the current value → Rust `default_mutate`
This PR also adds `default_mutate_value`, allowing a typed
`#[dispatch(mutate)]` handler to apply default recursion to its current
value before performing a post-order rewrite.
The dispatch macro, documentation, and tests are updated accordingly.
All Rust workspace tests pass.
---
docs/guides/rust_lang_guide.md | 16 ++---
rust/tvm-ffi-macros/src/dispatch.rs | 4 +-
rust/tvm-ffi/src/extra/structural_mutate.rs | 101 +++++++++++++++------------
rust/tvm-ffi/tests/test_structural_mutate.rs | 87 +++++++++++++++--------
4 files changed, 126 insertions(+), 82 deletions(-)
diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index cae1c758..92f2d059 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -525,13 +525,13 @@ assert_eq!(mutated.iter().collect::<Vec<_>>(), vec![2,
3]);
assert_eq!(mutator.state().integers, 2);
```
-`MutateContext::mutate` uses the copy path for a borrowed child, while
-`maybe_inplace_mutate` preserves the reuse opportunity of an owned child.
+`MutateContext::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_child()`;
+current value's final result and may recursively call `self.mutate()`;
an unmatched value follows default mutation with its current in-place permit:
```rust
@@ -563,10 +563,10 @@ assert_eq!(increment.integers, 2);
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
-`maybe_inplace_mutate` to opt into default container reuse;
+`dispatch_maybe_inplace_mutate` to opt into default container reuse;
`default_maybe_inplace_mutate` rechecks uniqueness before writing. Borrowed
-children can be re-entered with `mutate_child`, while owned children can use
-`maybe_inplace_mutate_child`:
+values can be re-entered with `mutate`, while owned values can use
+`maybe_inplace_mutate`:
```rust
use tvm_ffi::{
@@ -578,14 +578,14 @@ use tvm_ffi::{
struct Increment;
impl StructuralMutator for Increment {
- fn mutate(&mut self, value: &MapValue, kind: DefRegionKind) -> Result<Any>
{
+ fn dispatch_mutate(&mut self, value: &MapValue, kind: DefRegionKind) ->
Result<Any> {
match value.cast::<i64>() {
Some(value) => Ok(Any::from(value + 1)),
None => self.default_mutate(value, kind),
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
kind: DefRegionKind,
diff --git a/rust/tvm-ffi-macros/src/dispatch.rs
b/rust/tvm-ffi-macros/src/dispatch.rs
index 0e692c3d..21c6ccb4 100644
--- a/rust/tvm-ffi-macros/src/dispatch.rs
+++ b/rust/tvm-ffi-macros/src/dispatch.rs
@@ -244,7 +244,7 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) ->
syn::Result<TokenStream2>
{
#[inline]
#[allow(unreachable_code, unused_variables)]
- fn mutate(
+ fn dispatch_mutate(
&mut self,
value: &#tvm_ffi::extra::structural_mutate::MapValue,
def_region_kind:
#tvm_ffi::extra::structural_visit::DefRegionKind,
@@ -256,7 +256,7 @@ fn expand(item_impl: &ItemImpl, mode: DispatchMode) ->
syn::Result<TokenStream2>
#[inline]
#[allow(unreachable_code, unused_variables)]
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value:
#tvm_ffi::extra::structural_mutate::InplaceValue<'_>,
def_region_kind:
#tvm_ffi::extra::structural_visit::DefRegionKind,
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 1c6b0c3e..7d5b607a 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -135,40 +135,40 @@ impl<State> MutateContext<'_, State> {
self.def_region_kind
}
- /// Mutate a borrowed child through the same callback chain. The child and
+ /// Mutate a borrowed value through the same callback chain. The value and
/// its descendants begin on the non-in-place path.
- pub fn mutate<T>(&mut self, child: &T) -> Result<Any>
+ pub fn mutate<T>(&mut self, value: &T) -> Result<Any>
where
for<'x> AnyView<'x>: From<&'x T>,
{
- self.mutate_with(child, self.def_region_kind)
+ self.mutate_with(value, self.def_region_kind)
}
- /// Mutate a borrowed child under an explicit definition-region state.
- pub fn mutate_with<T>(&mut self, child: &T, def_region_kind:
DefRegionKind) -> Result<Any>
+ /// Mutate a borrowed value under an explicit definition-region state.
+ pub fn mutate_with<T>(&mut self, value: &T, def_region_kind:
DefRegionKind) -> Result<Any>
where
for<'x> AnyView<'x>: From<&'x T>,
{
- let view = AnyView::from(child);
+ let view = AnyView::from(value);
self.driver
.mutate_raw(*view.as_raw_ffi_any(), def_region_kind, Permit::Copy)
}
- /// Mutate an owned child, allowing an in-place attempt when it remains
+ /// Mutate an owned value, allowing an in-place attempt when it remains
/// uniquely owned and no matched callback borrows it.
- pub fn maybe_inplace_mutate<T: Into<Any>>(&mut self, child: T) ->
Result<Any> {
- self.maybe_inplace_mutate_with(child, self.def_region_kind)
+ 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 child under an explicit definition-region state.
+ /// Mutate an owned value under an explicit definition-region state.
pub fn maybe_inplace_mutate_with<T: Into<Any>>(
&mut self,
- child: T,
+ value: T,
def_region_kind: DefRegionKind,
) -> Result<Any> {
- let child = child.into();
+ let value = value.into();
self.driver.mutate_raw(
- *child.as_raw_ffi_any(),
+ *value.as_raw_ffi_any(),
def_region_kind,
Permit::MaybeInPlace,
)
@@ -883,49 +883,47 @@ impl StructuralVarRemap {
/// A mutator that controls its own recursion.
///
-/// Implementations descend with the child or `default_*` helpers.
+/// Implementations descend with the `mutate` or `default_*` helpers.
/// `#[dispatch(mutate)]` generates this trait from typed `mutate_*` methods.
pub trait StructuralMutator: Sized {
- /// Mutate one borrowed value without modifying its source storage.
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any>;
+ /// Dispatch one borrowed value without modifying its source storage.
+ ///
+ /// The structural-mutation engine calls this hook for each value.
+ fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind:
DefRegionKind) -> Result<Any>;
- /// Mutate one value for which the engine permits an in-place attempt.
+ /// Dispatch one value for which the engine permits an in-place attempt.
///
- /// The default delegates to [`Self::mutate`] and therefore remains
+ /// The default delegates to [`Self::dispatch_mutate`] and therefore
remains
/// non-in-place. Override this method to opt into the default container
/// reuse path.
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
) -> Result<Any> {
- self.mutate(value.as_value(), def_region_kind)
+ self.dispatch_mutate(value.as_value(), def_region_kind)
}
- /// Re-enter this mutator for a borrowed child. The child and all of its
+ /// Re-enter this mutator for a borrowed value. The value and all of its
/// descendants use the non-in-place path.
- fn mutate_child<T>(&mut self, child: &T, def_region_kind: DefRegionKind)
-> Result<Any>
+ fn mutate<T>(&mut self, value: &T, def_region_kind: DefRegionKind) ->
Result<Any>
where
for<'x> AnyView<'x>: From<&'x T>,
{
- let view = AnyView::from(child);
+ let view = AnyView::from(value);
dispatch_user_raw(self, *view.as_raw_ffi_any(), def_region_kind,
Permit::Copy)
}
- /// Re-enter this mutator for an owned child, permitting reuse only when
+ /// Re-enter this mutator for an owned value, permitting reuse only when
/// the converted value remains uniquely owned.
- fn maybe_inplace_mutate_child<T>(
- &mut self,
- child: T,
- def_region_kind: DefRegionKind,
- ) -> Result<Any>
+ fn maybe_inplace_mutate<T>(&mut self, value: T, def_region_kind:
DefRegionKind) -> Result<Any>
where
T: Into<Any>,
{
- let child = child.into();
+ let value = value.into();
dispatch_user_raw(
self,
- *child.as_raw_ffi_any(),
+ *value.as_raw_ffi_any(),
def_region_kind,
Permit::MaybeInPlace,
)
@@ -936,6 +934,20 @@ pub trait StructuralMutator: Sized {
user_default_mutate(self, value.raw(), def_region_kind, Permit::Copy)
}
+ /// Apply default non-in-place mutation to a borrowed typed value.
+ ///
+ /// Unlike [Self::mutate], this bypasses dispatch for the value
+ /// itself while its children still re-enter this mutator. This lets a
+ /// typed structural-mutate handler recurse through its current node
+ /// before applying a post-order rewrite.
+ fn default_mutate_value<T>(&mut self, value: &T, def_region_kind:
DefRegionKind) -> Result<Any>
+ where
+ for<'x> AnyView<'x>: From<&'x T>,
+ {
+ let view = AnyView::from(value);
+ user_default_mutate(self, *view.as_raw_ffi_any(), def_region_kind,
Permit::Copy)
+ }
+
/// Apply the default mutation under an engine-issued in-place capability.
///
/// Uniqueness is checked again here because user code may have retained
@@ -989,7 +1001,7 @@ impl<State, Link, Marker> StructuralMutator for
MutateCallbacks<State, Link, Mar
where
Link: MutateChainLink<State, Marker>,
{
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+ 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>(
self,
@@ -1002,7 +1014,7 @@ where
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -1024,7 +1036,7 @@ impl<Link, Marker> StructuralMutator for
DirectMutateCallbacks<'_, Link, Marker>
where
Link: MutateChainLink<(), Marker>,
{
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+ 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) {
Some(result) => result,
@@ -1032,7 +1044,7 @@ where
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -1407,13 +1419,13 @@ struct StructuralMutatorVTable {
var_remap_set: FStructuralVarRemapSet,
}
-type RuntimeMutateCallback =
+type RuntimeDispatchMutateCallback =
unsafe fn(*mut c_void, TVMFFIAny, DefRegionKind, Permit) -> Result<Any>;
type RuntimeVarRemapGetCallback = unsafe fn(*mut c_void, TVMFFIAny) ->
Result<Option<Any>>;
type RuntimeVarRemapSetCallback = unsafe fn(*mut c_void, TVMFFIAny, &Any) ->
Result<()>;
struct RuntimeMutatorCallbacks {
- mutate: RuntimeMutateCallback,
+ dispatch_mutate: RuntimeDispatchMutateCallback,
var_remap_get: RuntimeVarRemapGetCallback,
var_remap_set: RuntimeVarRemapSetCallback,
}
@@ -1561,7 +1573,7 @@ unsafe fn rust_vtable_mutate_impl(
Err(error) => return result_into_raw(Err(error)),
};
let context = context_guard.context;
- let callback = (*mutator).callbacks.mutate;
+ let callback = (*mutator).callbacks.dispatch_mutate;
let raw = *value.as_raw_ffi_any();
let outcome = catch_unwind(AssertUnwindSafe(|| {
let kind = def_region_from_raw((*mutator).def_region_mode)?;
@@ -1822,7 +1834,7 @@ impl<U: StructuralMutator> MutationDriver for U {
///
/// `context` must come from the current mutable reborrow of a live `D`; the
/// runtime object hides that pointer until this call returns.
-unsafe fn runtime_mutate<D: MutationDriver>(
+unsafe fn runtime_dispatch_mutate<D: MutationDriver>(
context: *mut c_void,
raw: TVMFFIAny,
def_region_kind: DefRegionKind,
@@ -1835,7 +1847,7 @@ unsafe fn runtime_mutate<D: MutationDriver>(
///
/// # Safety
///
-/// `context` must satisfy the same requirements as [`runtime_mutate`].
+/// `context` must satisfy the same requirements as
[`runtime_dispatch_mutate`].
unsafe fn runtime_var_remap_get<D: MutationDriver>(
context: *mut c_void,
raw: TVMFFIAny,
@@ -1847,7 +1859,7 @@ unsafe fn runtime_var_remap_get<D: MutationDriver>(
///
/// # Safety
///
-/// `context` must satisfy the same requirements as [`runtime_mutate`], and
+/// `context` must satisfy the same requirements as
[`runtime_dispatch_mutate`], and
/// `replacement` must remain alive for this call.
unsafe fn runtime_var_remap_set<D: MutationDriver>(
context: *mut c_void,
@@ -1860,7 +1872,7 @@ unsafe fn runtime_var_remap_set<D: MutationDriver>(
fn run_structural_mutator<D: MutationDriver>(root: Any, driver: &mut D) ->
Result<Any> {
let context = std::ptr::from_mut(driver).cast::<c_void>();
let callbacks = RuntimeMutatorCallbacks {
- mutate: runtime_mutate::<D>,
+ dispatch_mutate: runtime_dispatch_mutate::<D>,
var_remap_get: runtime_var_remap_get::<D>,
var_remap_set: runtime_var_remap_set::<D>,
};
@@ -2066,9 +2078,10 @@ fn dispatch_user_raw<U: StructuralMutator>(
) -> Result<Any> {
let result = if permit == Permit::MaybeInPlace && object_is_unique(raw) {
let mut scoped_raw = raw;
- mutator.maybe_inplace_mutate(InplaceValue::from_raw(&mut scoped_raw),
def_region_kind)
+ mutator
+ .dispatch_maybe_inplace_mutate(InplaceValue::from_raw(&mut
scoped_raw), def_region_kind)
} else {
- mutator.mutate(&MapValue::from_raw(raw), def_region_kind)
+ mutator.dispatch_mutate(&MapValue::from_raw(raw), def_region_kind)
};
result.map_err(|error| with_value_context(error, raw))
}
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 0e4fea5e..11934875 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -550,7 +550,7 @@ struct ManualIncrement {
}
impl StructuralMutator for ManualIncrement {
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+ fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind:
DefRegionKind) -> Result<Any> {
if let Some(integer) = value.cast::<i64>() {
Ok(Any::from(integer + 1))
} else {
@@ -558,7 +558,7 @@ impl StructuralMutator for ManualIncrement {
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -582,7 +582,7 @@ struct ReplaceNone {
}
impl StructuralMutator for ReplaceNone {
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+ fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind:
DefRegionKind) -> Result<Any> {
if value.type_index() == TypeIndex::kTVMFFINone as i32 {
self.calls += 1;
Ok(Any::from(8i64))
@@ -591,7 +591,7 @@ impl StructuralMutator for ReplaceNone {
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -615,7 +615,7 @@ struct RemappingFreeVar {
}
impl StructuralMutator for RemappingFreeVar {
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+ fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind:
DefRegionKind) -> Result<Any> {
if value.type_index() == self.type_index {
if let Some(mutated) = self.remap.get(value)? {
return Ok(mutated);
@@ -629,7 +629,7 @@ impl StructuralMutator for RemappingFreeVar {
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -646,21 +646,21 @@ impl StructuralMutator for RemappingFreeVar {
}
}
-struct ChildHelperMutator {
+struct RecursiveEntryMutator {
remap: StructuralVarRemap,
- use_owned_child: bool,
- owned_child_pointer: Option<usize>,
+ use_owned_value: bool,
+ owned_value_pointer: Option<usize>,
}
-impl StructuralMutator for ChildHelperMutator {
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+impl StructuralMutator for RecursiveEntryMutator {
+ fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind:
DefRegionKind) -> Result<Any> {
if value.type_index() == TypeIndex::kTVMFFINone as i32 {
- if self.use_owned_child {
- let child = Array::new(vec![1i64]);
- self.owned_child_pointer = Some(array_pointer(&child) as
usize);
- self.maybe_inplace_mutate_child(child, def_region_kind)
+ if self.use_owned_value {
+ let value = Array::new(vec![1i64]);
+ self.owned_value_pointer = Some(array_pointer(&value) as
usize);
+ self.maybe_inplace_mutate(value, def_region_kind)
} else {
- self.mutate_child(&1i64, def_region_kind)
+ self.mutate(&1i64, def_region_kind)
}
} else if let Some(integer) = value.cast::<i64>() {
Ok(Any::from(integer + 1))
@@ -669,7 +669,7 @@ impl StructuralMutator for ChildHelperMutator {
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -693,7 +693,7 @@ struct RejectAliasedReentry {
}
impl StructuralMutator for RejectAliasedReentry {
- fn mutate(&mut self, value: &MapValue, def_region_kind: DefRegionKind) ->
Result<Any> {
+ fn dispatch_mutate(&mut self, value: &MapValue, def_region_kind:
DefRegionKind) -> Result<Any> {
if let Some(integer) = value.cast::<i64>() {
let retained = RETAINED_MUTATOR.with(|slot|
slot.borrow().as_ref().unwrap().clone());
let error = match
Function::get_global("ffi.StructuralMutatorMutate")
@@ -713,7 +713,7 @@ impl StructuralMutator for RejectAliasedReentry {
}
}
- fn maybe_inplace_mutate(
+ fn dispatch_maybe_inplace_mutate(
&mut self,
value: InplaceValue<'_>,
def_region_kind: DefRegionKind,
@@ -878,28 +878,28 @@ fn user_mutator_can_store_a_changed_free_var_result() {
}
#[test]
-fn user_mutator_child_helpers_reenter_the_same_mutator() {
- let mut borrowed = ChildHelperMutator {
+fn user_mutator_recursive_entries_reenter_the_same_mutator() {
+ let mut borrowed = RecursiveEntryMutator {
remap: StructuralVarRemap::default(),
- use_owned_child: false,
- owned_child_pointer: None,
+ use_owned_value: false,
+ owned_value_pointer: None,
};
let mutated = structural_mutate(Any::new(), &mut borrowed)
.and_then(i64::try_from)
.unwrap();
assert_eq!(mutated, 2);
- let mut owned = ChildHelperMutator {
+ let mut owned = RecursiveEntryMutator {
remap: StructuralVarRemap::default(),
- use_owned_child: true,
- owned_child_pointer: None,
+ use_owned_value: true,
+ owned_value_pointer: None,
};
let mutated = structural_mutate(Any::new(), &mut owned)
.and_then(Array::<i64>::try_from)
.unwrap();
assert_eq!(
array_pointer(&mutated) as usize,
- owned.owned_child_pointer.unwrap()
+ owned.owned_value_pointer.unwrap()
);
assert_eq!(mutated.get(0).unwrap(), 2);
}
@@ -1435,7 +1435,7 @@ impl GeneratedRecursiveMutator {
fn mutate_array(&mut self, array: Array<i64>, kind: DefRegionKind) ->
Result<Any> {
let mut mutated = Vec::with_capacity(array.len());
for value in array.iter() {
- mutated.push(i64::try_from(self.mutate_child(&value, kind)?)?);
+ mutated.push(i64::try_from(self.mutate(&value, kind)?)?);
}
Ok(Any::from(Array::new(mutated)))
}
@@ -1457,6 +1457,37 @@ fn
generated_mutator_can_drive_recursion_through_mut_self() {
assert_eq!(mutator.integers, vec![1, 2]);
}
+#[derive(Default)]
+struct GeneratedDefaultingMutator {
+ arrays: usize,
+ integers: Vec<i64>,
+}
+
+#[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)
+ }
+
+ fn mutate_integer(&mut self, value: i64) -> Any {
+ self.integers.push(value);
+ Any::from(value + 1)
+ }
+}
+
+#[test]
+fn generated_mutator_can_default_recurse_from_a_typed_handler() {
+ let mut mutator = GeneratedDefaultingMutator::default();
+ 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]);
+}
+
struct GeneratedRemappingMutator {
type_index: i32,
calls: usize,