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();