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 29c7495a [FEAT][Rust] Declare dispatch arguments and return types 
explicitly (#900)
29c7495a is described below

commit 29c7495aa52e138f25578abf22d23f1cf41146d4
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Nov 3 10:02:57 2026 -0500

    [FEAT][Rust] Declare dispatch arguments and return types explicitly (#900)
    
    Make the dispatch input tuple and return type explicit with
    `#[dispatch(prefix, args_as_tuple, ret)]`. The declared contract
    replaces longest-handler signature deduction while preserving typed
    first-argument matching and handlers that consume an initial argument
    prefix.
    
    Convert the first matched handler's result through standard `Into<Ret>`
    before wrapping it in `Some`; outer `None` continues to mean no match.
    Preserve borrowed results, shared generic bounds, aliases, and
    conditional impls, and migrate the existing call sites and API
    documentation.
---
 docs/guides/rust_lang_guide.md                     |  61 ++++--
 rust/tvm-ffi-macros/src/dispatch.rs                | 216 ++++++++-------------
 rust/tvm-ffi-macros/src/lib.rs                     |  41 ++--
 rust/tvm-ffi/src/extra/dispatch.rs                 |  19 +-
 rust/tvm-ffi/src/extra/structural_mutate.rs        |   4 +-
 rust/tvm-ffi/src/extra/structural_visit.rs         |   2 +-
 rust/tvm-ffi/tests/test_big_int.rs                 |   6 +-
 rust/tvm-ffi/tests/test_match_any_ast_lookup.rs    |   8 +-
 rust/tvm-ffi/tests/test_structural_mutate.rs       |  76 ++++----
 rust/tvm-ffi/tests/test_structural_visit.rs        | 170 ++++++++++++----
 .../tests/test_structural_visitor_alignment.rs     |   6 +-
 11 files changed, 354 insertions(+), 255 deletions(-)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index 84ee8f95..8b784f32 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -457,11 +457,28 @@ fn may_fail(value: i32) -> Result<()> {
 ### Structural Walk and Visit
 
 Rust provides equivalents of the C++ `StructuralWalk`/`StructuralVisitor`
-APIs. An inherent `#[dispatch(walk)]` impl groups `walk_*` handlers for
+APIs. An inherent `#[dispatch(walk, (AnyView<'_>, DefRegionKind), 
Result<WalkResult>)]`
+impl groups `walk_*` handlers for
 `structural_walk`. The named handlers return the common `Result<WalkResult>`
 type to advance, skip children, or interrupt traversal.
-Handlers dispatch on their argument type and may take an optional trailing
-`DefRegionKind` argument:
+The attribute always declares `#[dispatch(prefix, args_as_tuple, ret)]`:
+`args_as_tuple` is a nonempty tuple whose first element is the match carrier,
+`AnyView<'_>` or `MutationView<'_>`, followed by the arguments passed to 
handlers.
+Each handler consumes its typed first value and an initial prefix of the
+remaining arguments. The declared signature does not depend on the longest
+handler. `dispatch` returns `Option<ret>`: the first matched handler's result
+converts through standard `Into<ret>` and is wrapped in `Some`; outer `None`
+means no match. A matched error or inner `None` stops dispatch. This conversion
+does not recursively convert results or wrap scalar values in `Ok`.
+
+Elided input lifetimes are independent and elided output lifetimes borrow
+`self`. Use a shared named lifetime in the tuple and return type for a result
+borrowing an input. Names come from the enclosing impl or common handler 
generic
+declarations; those declarations and bounds must agree across handlers, except
+for unbounded pattern-only lifetimes. `Self` denotes the implementor. Carrier
+and handler type aliases are supported.
+
+Walk handlers may take an optional trailing `DefRegionKind` argument:
 
 ```rust
 use tvm_ffi::{dispatch, structural_walk, Array, DefRegionKind, Result, 
WalkOrder, WalkResult};
@@ -472,7 +489,7 @@ struct Probe {
     floats: usize,
 }
 
-#[dispatch(walk)]
+#[dispatch(walk, (tvm_ffi::AnyView<'_>, DefRegionKind), Result<WalkResult>)]
 impl Probe {
     fn walk_integer(&mut self, value: i64) -> Result<WalkResult> {
         self.total += value;
@@ -577,7 +594,11 @@ struct Stats {
     total: i64,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (tvm_ffi::AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl Stats {
     fn visit_integer(&mut self, value: i64, _visitor: &mut 
StructuralVisitorView<'_, Self>)
         -> Result<Option<VisitInterrupt>> {
@@ -601,8 +622,8 @@ does not need a region scope. Calls reborrow the same 
dispatcher, so state can b
 read and updated before and after recursion. Interrupt values must be
 returned explicitly because `?` only propagates errors.
 
-An inherent `#[dispatch(visit)]` impl groups typed `visit_*` methods. It 
supplies
-the engine signature directly, without a separate forwarding trait:
+An inherent `#[dispatch]` impl groups typed `visit_*` methods. Its declared
+argument tuple and return type supply the engine signature directly:
 
 ```rust
 use tvm_ffi::{
@@ -615,7 +636,11 @@ struct Depth {
     current: usize,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (tvm_ffi::AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl Depth {
     fn visit_any(&mut self, value: AnyView<'_>, visitor: &mut 
StructuralVisitorView<'_, Self>)
         -> Result<Option<VisitInterrupt>> {
@@ -653,8 +678,8 @@ permission.
 ### Structural Mapping and Mutation
 
 `structural_map` is the transforming counterpart to `structural_walk`. An
-inherent `#[dispatch(map)]` impl groups `map_*` methods returning the common
-engine result `Result<Any>`. Methods are tested in source order, the
+inherent `#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]` impl 
groups
+`map_*` methods returning the engine result `Result<Any>`. Methods are tested 
in source order, the
 first matching argument type wins, and an unmatched value is preserved. A
 method may take an optional trailing `DefRegionKind`; an `AnyView<'_>` method 
is a
 catch-all and should therefore come last:
@@ -669,7 +694,7 @@ struct Increment {
     integers: usize,
 }
 
-#[dispatch(map)]
+#[dispatch(map, (tvm_ffi::AnyView<'_>, DefRegionKind), Result<Any>)]
 impl Increment {
     fn map_integer(&mut self, value: i64, _kind: DefRegionKind) -> Result<Any> 
{
         self.integers += 1;
@@ -756,7 +781,7 @@ struct Stats {
     integers: usize,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
 impl Stats {
     fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
         self.integers += 1;
@@ -799,10 +824,10 @@ writeback: preserve `Unchanged` or reconstruct the parent 
from replacement resul
 only copy permission. Read permission from `value.inplace_mode()`; see
 `MutationView` for ownership requirements.
 
-An inherent `#[dispatch(mutate, MutationView)]` impl supplies the engine's
-required signature directly. An unmatched value follows default mutation with
-its current permission. All handlers return `Result<Any>`; a handler may omit
-the context argument when another handler declares it:
+An inherent `#[dispatch]` impl declares the engine's required mutation tuple
+and `Result<Any>` return type. An unmatched value follows default mutation with
+its current permission. A handler may omit the context argument even when no
+other handler consumes it:
 
 ```rust
 use tvm_ffi::{
@@ -814,7 +839,7 @@ struct Increment {
     integers: usize,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
 impl Increment {
     fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
         self.integers += 1;
@@ -841,7 +866,7 @@ use tvm_ffi::{
 
 struct Increment;
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (tvm_ffi::MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
 impl Increment {
     fn mutate_integer(&mut self, value: i64, _mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
         Ok(Any::from(value + 1))
diff --git a/rust/tvm-ffi-macros/src/dispatch.rs 
b/rust/tvm-ffi-macros/src/dispatch.rs
index 8cefc239..b6701b7e 100644
--- a/rust/tvm-ffi-macros/src/dispatch.rs
+++ b/rust/tvm-ffi-macros/src/dispatch.rs
@@ -22,7 +22,7 @@ use proc_macro2::{Span, TokenStream as TokenStream2, 
TokenTree};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
 use syn::{
     parse_macro_input, FnArg, GenericParam, ImplItem, ImplItemMethod, 
ItemImpl, Meta, NestedMeta,
-    ReturnType, Token, Type,
+    Token, Type,
 };
 
 use crate::utils::get_tvm_ffi_crate;
@@ -40,49 +40,39 @@ pub(crate) fn dispatch(attr: TokenStream, item: 
TokenStream) -> TokenStream {
     }
 }
 
-#[derive(Clone, Copy, PartialEq, Eq)]
-enum Carrier {
-    AnyView,
-    MutationView,
-}
-
 struct DispatchArgs {
     prefix: syn::Ident,
-    carrier: Option<Carrier>,
+    arguments: syn::TypeTuple,
+    output: Type,
 }
 
 impl syn::parse::Parse for DispatchArgs {
     fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
-        let mut args = Self {
-            prefix: input.parse()?,
-            carrier: None,
+        let prefix = input.parse()?;
+        input.parse::<Token![,]>()?;
+        let arguments = match input.parse::<Type>()? {
+            Type::Tuple(tuple) if !tuple.elems.is_empty() => tuple,
+            ty => return Err(syn::Error::new_spanned(
+                ty,
+                "dispatch requires a nonempty argument tuple, starting with 
AnyView or MutationView",
+            )),
         };
-        while !input.is_empty() {
-            input.parse::<Token![,]>()?;
-            if input.is_empty() {
-                break;
-            }
-            let option: syn::Ident = input.parse()?;
-            if option == "AnyView" || option == "MutationView" {
-                if args.carrier.is_some() {
-                    return Err(syn::Error::new(
-                        option.span(),
-                        "duplicate dispatch input carrier",
-                    ));
-                }
-                args.carrier = Some(if option == "MutationView" {
-                    Carrier::MutationView
-                } else {
-                    Carrier::AnyView
-                });
-                continue;
-            }
-            return Err(syn::Error::new(
-                option.span(),
-                "expected `AnyView` or `MutationView`",
+        input.parse::<Token![,]>()?;
+        let output = input.parse::<Type>()?;
+        if matches!(output, Type::Infer(_) | Type::ImplTrait(_)) {
+            return Err(syn::Error::new_spanned(
+                output,
+                "dispatch requires an explicit return type",
             ));
         }
-        Ok(args)
+        if !input.is_empty() {
+            input.parse::<Token![,]>()?;
+        }
+        Ok(Self {
+            prefix,
+            arguments,
+            output,
+        })
     }
 }
 
@@ -110,15 +100,12 @@ fn expand(item: &ItemImpl, args: &DispatchArgs) -> 
syn::Result<TokenStream2> {
             ImplItem::Method(method)
                 if method.sig.ident.to_string().starts_with(&method_prefix) =>
             {
-                Some(parse_handler(method))
+                Some(parse_handler(method, args))
             }
             _ => None,
         })
         .collect::<syn::Result<Vec<_>>>()?;
-    let Some(anchor) = handlers
-        .iter()
-        .min_by_key(|handler| std::cmp::Reverse(handler.trailing))
-    else {
+    let Some(first) = handlers.first() else {
         return Err(syn::Error::new_spanned(
             item,
             format!("no `{method_prefix}*` handlers"),
@@ -136,41 +123,19 @@ fn expand(item: &ItemImpl, args: &DispatchArgs) -> 
syn::Result<TokenStream2> {
             ));
         }
     }
-    // Preserve the input lifetime independently of the longest trailing list.
-    let view_lifetime = handlers
-        .iter()
-        .filter(|handler| match args.carrier.unwrap_or(Carrier::AnyView) {
-            Carrier::AnyView => matches!(handler.argument, Argument::Value),
-            Carrier::MutationView => handler.capability.is_some(),
-        })
-        .find_map(|handler| {
-            let FnArg::Typed(arg) = &handler.method.sig.inputs[1] else {
-                unreachable!()
-            };
-            value_lifetime(&arg.ty)
-                .filter(|lifetime| 
!handler.local_lifetimes.contains(&lifetime.ident))
-        });
-    expand_impl(item, args, &handlers, anchor, view_lifetime)
-}
-
-fn expand_impl(
-    item: &ItemImpl,
-    args: &DispatchArgs,
-    handlers: &[Handler<'_>],
-    anchor: &Handler<'_>,
-    view_lifetime: Option<syn::Lifetime>,
-) -> syn::Result<TokenStream2> {
     let tvm_ffi = get_tvm_ffi_crate();
+    // Only binder declarations and the receiver are shared between handlers.
+    // The attribute alone supplies the argument and result contract.
     let mut generics = item.generics.clone();
-    generics.params.extend(anchor.generics.params.clone());
-    if let Some(method_where) = &anchor.generics.where_clause {
+    generics.params.extend(first.generics.params.clone());
+    if let Some(method_where) = &first.generics.where_clause {
         generics
             .make_where_clause()
             .predicates
             .extend(method_where.predicates.clone());
     }
     let mut lifetimes = Lifetimes::new(&generics);
-    let FnArg::Receiver(receiver) = &anchor.method.sig.inputs[0] else {
+    let FnArg::Receiver(receiver) = &first.method.sig.inputs[0] else {
         unreachable!()
     };
     let receiver_lifetime = receiver
@@ -179,40 +144,28 @@ fn expand_impl(
         .and_then(|(_, l)| l.clone())
         .filter(|l| l.ident != "_")
         .unwrap_or_else(|| lifetimes.fresh());
-    let view_lifetime = view_lifetime.unwrap_or_else(|| lifetimes.fresh());
-    let trailing: Vec<_> = anchor
-        .method
-        .sig
-        .inputs
-        .iter()
-        .skip(2)
-        .map(|arg| {
-            let FnArg::Typed(arg) = arg else {
-                unreachable!()
-            };
-            &arg.ty
-        })
-        .collect();
-    let output = &anchor.method.sig.output;
-    let carrier = match args.carrier.unwrap_or(Carrier::AnyView) {
-        Carrier::AnyView => quote!(#tvm_ffi::AnyView),
-        Carrier::MutationView => quote!(#tvm_ffi::MutationView),
-    };
-    let mut signature: syn::TypeBareFn =
-        syn::parse_quote!(fn(#carrier<#view_lifetime> #(, #trailing)*) 
#output);
-    let consuming = args.carrier == Some(Carrier::MutationView);
-    let source = if consuming {
-        quote!(#tvm_ffi::match_any_internal::CapabilitySource<#view_lifetime>)
-    } else {
-        quote!(#tvm_ffi::match_any_internal::ViewSource<#view_lifetime>)
-    };
+    let mut arguments = args.arguments.clone();
+    for ty in &mut arguments.elems {
+        lifetimes.normalize(ty, None);
+    }
+    let mut output = args.output.clone();
+    // As in an ordinary method, result lifetime elision borrows the receiver.
+    lifetimes.normalize(&mut output, Some(&receiver_lifetime));
+    let carrier = &arguments.elems[0];
+    generics
+        .make_where_clause()
+        .predicates
+        .push(syn::parse_quote!(
+            #carrier: #tvm_ffi::extra::dispatch::DispatchInput
+        ));
+    let source = quote!(<#carrier as 
#tvm_ffi::extra::dispatch::DispatchInput>::Source);
     let mut borrow_name = "__tvm_ffi_match_borrow".to_owned();
     while lifetimes.used.contains(&borrow_name) {
         borrow_name.push('_');
     }
     let borrow = syn::Lifetime::new(&format!("'{borrow_name}"), 
Span::mixed_site());
     let mut patterns = Vec::new();
-    for handler in handlers {
+    for handler in &handlers {
         let FnArg::Typed(value) = &handler.method.sig.inputs[1] else {
             unreachable!()
         };
@@ -222,7 +175,7 @@ fn expand_impl(
         ))?;
         if matches!(handler.argument, Argument::Owned)
             && handler.capability.is_none()
-            && value_lifetime(&ty).is_some()
+            && has_pattern_lifetime(&ty)
         {
             lifetimes.normalize(&mut ty, None);
             // Fix recovery independently of the arm-local mutable source 
borrow.
@@ -234,14 +187,6 @@ fn expand_impl(
         }
         patterns.push(ty);
     }
-    for input in &mut signature.inputs {
-        lifetimes.normalize(&mut input.ty, None);
-        input.name = None;
-    }
-    if let ReturnType::Type(_, output) = &mut signature.output {
-        // Handler return elision is bound to the receiver.
-        lifetimes.normalize(output, Some(&receiver_lifetime));
-    }
     generics.params.extend(
         lifetimes
             .added
@@ -252,10 +197,10 @@ fn expand_impl(
     let mut params: Vec<_> = generics.params.into_iter().collect();
     params.sort_by_key(|param| !matches!(param, GenericParam::Lifetime(_)));
     generics.params = params.into_iter().collect();
-    // Self in a handler signature means the original implementor, not the
-    // reference type on which the generated Dispatcher is implemented.
-    let signature: syn::TypeBareFn =
-        syn::parse2(replace_self(signature.into_token_stream(), 
&item.self_ty))?;
+    // Self in the contract means the implementor, not its receiver reference.
+    let arguments: syn::TypeTuple =
+        syn::parse2(replace_self(arguments.into_token_stream(), 
&item.self_ty))?;
+    let output: Type = syn::parse2(replace_self(output.into_token_stream(), 
&item.self_ty))?;
     let where_clause = generics
         .where_clause
         .take()
@@ -263,7 +208,7 @@ fn expand_impl(
         .transpose()?;
     generics = syn::parse2(replace_self(generics.into_token_stream(), 
&item.self_ty))?;
     generics.where_clause = where_clause;
-    let mut constrained = quote!(#signature).into_iter().collect::<Vec<_>>();
+    let mut constrained = quote!(#arguments 
#output).into_iter().collect::<Vec<_>>();
     constrained.extend(item.self_ty.to_token_stream());
     for param in &generics.params {
         let ident = match param {
@@ -281,27 +226,21 @@ fn expand_impl(
     let mutable = &receiver.mutability;
     let value = format_ident!("__tvm_ffi_value", span = Span::mixed_site());
     let bound = format_ident!("__tvm_ffi_matched", span = Span::mixed_site());
-    let names: Vec<_> = (0..signature.inputs.len() - 1)
+    let names: Vec<_> = (0..arguments.elems.len() - 1)
         .map(|i| format_ident!("__tvm_ffi_arg_{i}", span = Span::mixed_site()))
         .collect();
-    let types: Vec<_> = signature.inputs.iter().map(|arg| &arg.ty).collect();
-    let output: Type = match &signature.output {
-        ReturnType::Default => syn::parse_quote!(()),
-        ReturnType::Type(_, output) => (**output).clone(),
-    };
+    let types: Vec<_> = arguments.elems.iter().collect();
     let dispatch_self = format_ident!("self", span = Span::mixed_site());
     let mut errors = Vec::new();
     let mut arms = Vec::new();
     for (handler, pattern_type) in handlers.iter().zip(&patterns) {
         let method = &handler.method.sig.ident;
-        let error = if anchor.generics != handler.generics
-            || anchor.method.sig.inputs[0] != handler.method.sig.inputs[0]
+        let error = if first.generics != handler.generics
+            || first.method.sig.inputs[0] != handler.method.sig.inputs[0]
         {
             Some("dispatch handlers must use the same receiver and method 
generics")
         } else if handler.trailing > names.len() {
             Some("dispatch handler has more trailing arguments than the 
dispatch signature")
-        } else if handler.capability.is_some() && !consuming {
-            Some("MutationView handlers require `#[dispatch(prefix, 
MutationView)]`")
         } else {
             None
         };
@@ -334,7 +273,7 @@ fn expand_impl(
             quote!(::<#(#type_args),*>)
         };
         let invocation = quote_spanned! {method.span()=> 
#dispatch_self.#method #type_args (#bound #(, #forwarded)*) };
-        let call = quote_spanned! {method.span()=> 
::core::option::Option::Some(#invocation) };
+        let call = quote_spanned! {method.span()=> 
::core::option::Option::Some(::core::convert::Into::<#output>::into(#invocation))
 };
         let pattern = match &handler.argument {
             Argument::BorrowedNode => {
                 let Type::Reference(reference) = pattern_type else {
@@ -419,7 +358,7 @@ fn expand_impl(
     })
 }
 
-fn parse_handler(method: &ImplItemMethod) -> syn::Result<Handler<'_>> {
+fn parse_handler<'a>(method: &'a ImplItemMethod, args: &DispatchArgs) -> 
syn::Result<Handler<'a>> {
     if let Some(attribute) = method
         .attrs
         .iter()
@@ -463,17 +402,18 @@ fn parse_handler(method: &ImplItemMethod) -> 
syn::Result<Handler<'_>> {
         None
     };
     let receiver = &signature.inputs[0];
-    let trailing = signature.inputs.iter().skip(2);
-    let output = &signature.output;
+    let arguments = &args.arguments;
+    let output = &args.output;
     let where_clause = &signature.generics.where_clause;
-    let mut visible = quote!(#receiver #(#trailing)* #output #where_clause);
+    let mut visible = quote!(#receiver #arguments #output #where_clause);
     for param in &signature.generics.params {
         match param {
             GenericParam::Lifetime(param) => param.bounds.to_tokens(&mut 
visible),
             param => param.to_tokens(&mut visible),
         }
     }
-    // Each handler infers its own input-only lifetimes at the call site.
+    // Lifetimes absent from the explicit contract and shared bounds stay local
+    // to each handler's matching pattern.
     let local_lifetimes: Vec<_> = signature
         .generics
         .lifetimes()
@@ -504,23 +444,19 @@ fn is_capability(ty: &Type) -> bool {
     matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|s| 
s.ident == "MutationView"))
 }
 
-fn value_lifetime(ty: &Type) -> Option<syn::Lifetime> {
-    let Type::Path(path) = ty else { return None };
+fn has_pattern_lifetime(ty: &Type) -> bool {
+    let Type::Path(path) = ty else { return false };
     path.qself
         .as_ref()
-        .and_then(|qualified| value_lifetime(&qualified.ty))
-        .or_else(|| {
-            path.path.segments.iter().find_map(|segment| {
-                let syn::PathArguments::AngleBracketed(args) = 
&segment.arguments else {
-                    return None;
-                };
-                args.args.iter().find_map(|arg| match arg {
-                    syn::GenericArgument::Lifetime(lifetime) if lifetime.ident 
!= "_" => {
-                        Some(lifetime.clone())
-                    }
-                    syn::GenericArgument::Type(ty) => value_lifetime(ty),
-                    _ => None,
-                })
+        .is_some_and(|qualified| has_pattern_lifetime(&qualified.ty))
+        || path.path.segments.iter().any(|segment| {
+            let syn::PathArguments::AngleBracketed(args) = &segment.arguments 
else {
+                return false;
+            };
+            args.args.iter().any(|arg| match arg {
+                syn::GenericArgument::Lifetime(lifetime) => lifetime.ident != 
"_",
+                syn::GenericArgument::Type(ty) => has_pattern_lifetime(ty),
+                _ => false,
             })
         })
 }
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index dad4b823..20be4b00 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -29,26 +29,33 @@ mod utils;
 
 /// Generate source-ordered runtime type dispatch from `prefix_*` handlers.
 ///
-/// `#[dispatch(prefix)]` implements `Dispatcher<(AnyView<'_>, ...), 
Option<R>>`
-/// for the receiver reference (`&T` or `&mut T`). `#[dispatch(prefix, 
AnyView)]`
-/// selects that carrier explicitly; `#[dispatch(prefix, MutationView)]` 
selects
-/// a consuming mutation view. The inherent `.dispatch(value, ...)` method owns
-/// matching; the trait's `.dispatch_tuple((value, ...))` unpacks and forwards 
to it.
-/// Both methods always inline. The result is `Option<R>`, preserving the 
complete handler outcome. Only
-/// the outer `None` means no match; matched errors and inner `None` are final.
-/// The receiver reference is consumed, allowing results to borrow it safely.
+/// `#[dispatch(prefix, (Carrier, Arg, ...), Ret)]` declares the complete input
+/// tuple and result type. The first input is `AnyView<'_>` or 
`MutationView<'_>`
+/// (including aliases); subsequent inputs are passed to matching handlers.
+/// The macro implements `Dispatcher<(Carrier, Arg, ...), Option<Ret>>` for the
+/// receiver reference (`&T` or `&mut T`). The inherent `.dispatch(value, ...)`
+/// method owns matching; `.dispatch_tuple((value, ...))` unpacks and forwards.
+/// Both methods always inline. A matched result is converted with `Into<Ret>`
+/// and wrapped in `Some`. Only the outer `None` means no match; matched errors
+/// and inner `None` are final. Conversion uses ordinary trait implementations,
+/// without recursively converting results or wrapping values in `Ok`.
 ///
 /// Handlers match an owned FFI value, a borrowed object node, or an optional
-/// by-value `AnyView` catch-all, which must be last.
-/// The longest handler determines trailing arguments and output;
-/// other handlers may consume a compatible prefix without cloning. Method
-/// generics must be constrained by the receiver, argument tuple or return 
type.
+/// by-value `AnyView` catch-all, which must be last. Each handler receives its
+/// typed first value and may consume an initial prefix of the remaining 
declared
+/// arguments without cloning. Neither arguments nor results are deduced from a
+/// handler signature, and no handler needs to consume every declared argument.
 ///
-/// Handlers share a common return type, with any normalization written 
explicitly
-/// in their bodies. Handler-level `cfg`/`cfg_attr` attributes are unsupported;
-/// configure the whole impl instead.
-/// A consuming `MutationView` retains mutation permission; its typed misses
-/// recover the same handle, and `AnyView` cannot grant it.
+/// Handlers share the same receiver and method generic declarations and 
bounds.
+/// Pattern-only, unbounded lifetimes may differ between handlers. Shared 
method
+/// generics must occur in the receiver, declared tuple or declared return 
type.
+/// Named lifetimes come from those shared declarations or the enclosing impl;
+/// use the same name in input and output to return a borrow of that input.
+/// Elided input lifetimes are independent; elided output lifetimes borrow the
+/// receiver. `Self` in the declared types denotes the original implementor.
+/// Handler-level `cfg`/`cfg_attr` attributes are unsupported; configure the 
whole
+/// impl instead. A consuming `MutationView` retains mutation permission; its
+/// typed misses recover the same handle, and `AnyView` cannot grant it.
 ///
 /// Matching invokes the public `match_any!` macro and its exact-final-type
 /// lookup for eligible larger handler sets. Dispatch also matches scalar 
payloads
diff --git a/rust/tvm-ffi/src/extra/dispatch.rs 
b/rust/tvm-ffi/src/extra/dispatch.rs
index 6610eca8..34258564 100644
--- a/rust/tvm-ffi/src/extra/dispatch.rs
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -21,7 +21,7 @@
 
 /// Runtime type dispatch with a complete handler outcome.
 ///
-/// Implementations generated by `#[dispatch(prefix)]` use a borrowed receiver
+/// Implementations generated by `#[dispatch(prefix, Args, R)]` use a borrowed 
receiver
 /// (`&T` or `&mut T`) as `Self` and use `Ret = Option<R>`. Only the outer
 /// `None` denotes no match: a matched error or inner `None` remains 
`Some(result)`
 /// and does not try another handler. Consuming the receiver reference 
preserves
@@ -29,7 +29,7 @@
 /// The argument tuple and complete return type select an implementation.
 #[diagnostic::on_unimplemented(
     message = "this receiver does not implement Dispatcher for the requested 
argument tuple",
-    note = "use #[dispatch(prefix)] on typed handlers with compatible 
arguments and results"
+    note = "use #[dispatch(prefix, (Carrier, ...), Ret)] with compatible typed 
handlers"
 )]
 pub trait Dispatcher<Args, Ret> {
     fn dispatch_tuple(self, args: Args) -> Ret;
@@ -37,6 +37,21 @@ pub trait Dispatcher<Args, Ret> {
 
 use crate::{error::Result, AnyView};
 
+/// Compile-time source metadata for the supported dispatch carriers.
+#[doc(hidden)]
+#[diagnostic::on_unimplemented(
+    message = "the first dispatch argument must be AnyView or MutationView"
+)]
+pub trait DispatchInput {
+    type Source;
+}
+impl<'a> DispatchInput for AnyView<'a> {
+    type Source = crate::match_any_internal::ViewSource<'a>;
+}
+impl<'a, T> DispatchInput for crate::MutationView<'a, T> {
+    type Source = crate::match_any_internal::CapabilitySource<'a>;
+}
+
 // Generated alias bounds must not make recovery depend on a temporary borrow.
 #[doc(hidden)]
 pub trait DispatchRecovery<S> {
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs 
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 77153b1b..8aed719c 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -154,7 +154,7 @@ impl InplaceMode {
 /// ```compile_fail,E0505
 /// use tvm_ffi::{dispatch, structural_mutate, Any, MutationView, 
StructuralMutatorView, Result};
 /// struct Inner<'a>(Option<MutationView<'a>>);
-/// #[dispatch(mutate, MutationView)]
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
 /// impl<'a> Inner<'a> {
 ///     fn mutate_bool(&mut self, _: bool, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
 ///         let pending = self.0.take().unwrap();
@@ -173,7 +173,7 @@ impl InplaceMode {
 /// ```compile_fail,E0505
 /// use tvm_ffi::{dispatch, Any, MutationView, StructuralMutatorView, Result, 
Unchanged};
 /// struct Pass;
-/// #[dispatch(mutate, MutationView)]
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
 /// impl Pass {
 ///     fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut 
StructuralMutatorView<'_, Self>) -> Result<Any> {
 ///         let view = value.as_any_view();
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs 
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 8685c9ad..9c78e500 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -1565,7 +1565,7 @@ where
 /// `StructuralWalk<order>(root, callbacks...)`.
 ///
 /// `walker` is anything implementing [`IntoWalker`]: a `&mut` reference to a
-/// stateful [`Dispatcher`](crate::Dispatcher) walker (`#[dispatch(walk)]`), a 
bare closure
+/// stateful [`Dispatcher`](crate::Dispatcher) walker (declared with 
`#[dispatch]`), a bare closure
 /// in any [`WalkChainLink`] shape (catch-all `AnyView<'_>`, typed, or node,
 /// with an optional trailing [`DefRegionKind`]), or a tuple of such
 /// callbacks tried in order — the C++ callback overloads and variadic
diff --git a/rust/tvm-ffi/tests/test_big_int.rs 
b/rust/tvm-ffi/tests/test_big_int.rs
index 9310d16e..fcca28bf 100644
--- a/rust/tvm-ffi/tests/test_big_int.rs
+++ b/rust/tvm-ffi/tests/test_big_int.rs
@@ -517,7 +517,11 @@ fn test_big_int_structural_traversal() {
     // A BigInt callback also accepts inline integers, mirroring the C++ cast 
rule.
     #[derive(Default)]
     struct Integers(Vec<BigInt>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Integers {
         fn visit_integer(
             &mut self,
diff --git a/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs 
b/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
index a89e44a5..e459c254 100644
--- a/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
+++ b/rust/tvm-ffi/tests/test_match_any_ast_lookup.rs
@@ -71,25 +71,25 @@ macro_rules! define_expr_leaves {
         )+
 
         struct OwnedClassifier;
-        #[dispatch(classify)]
+        #[dispatch(classify, (AnyView<'_>,), &'static str)]
         impl OwnedClassifier {
             $(fn $method(&self, _value: $handle) -> &'static str { $label })+
         }
 
         struct BorrowedClassifier;
-        #[dispatch(classify)]
+        #[dispatch(classify, (AnyView<'_>,), &'static str)]
         impl BorrowedClassifier {
             $(fn $method(&self, _value: &$object) -> &'static str { $label })+
         }
 
         struct CapabilityClassifier;
-        #[dispatch(classify, MutationView)]
+        #[dispatch(classify, (MutationView<'_>,), &'static str)]
         impl CapabilityClassifier {
             $(fn $method(&self, _value: MutationView<'_, $handle>) -> &'static 
str { $label })+
         }
 
         struct MixedClassifier;
-        #[dispatch(classify)]
+        #[dispatch(classify, (AnyView<'_>,), &'static str)]
         impl MixedClassifier {
             $(fn $method(&self, _value: $handle) -> &'static str { $label })+
             fn classify_integer_array(&self, _value: Array<i64>) -> &'static 
str {
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs 
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 7a1ec4d6..75a76289 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -163,13 +163,13 @@ impl<'s, 'v, 'c, 'r>
         })
     }
 }
-#[dispatch(map)]
+#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
 impl ManualIncrement {
     fn map_integer(&mut self, value: i64, _: DefRegionKind) -> Result<Any> {
         Ok(Any::from(value + 1))
     }
 }
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl ReplaceNone {
     fn mutate_any(
         &mut self,
@@ -184,7 +184,7 @@ impl ReplaceNone {
         }
     }
 }
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl RecursiveEntryMutator {
     fn mutate_any(
         &mut self,
@@ -493,7 +493,7 @@ fn 
user_mutator_recursive_entries_reenter_the_same_mutator() {
     struct BoundDispatcher {
         visits: usize,
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl BoundDispatcher {
         fn mutate_bool(
             &mut self,
@@ -554,7 +554,7 @@ fn 
default_mutation_mode_preserves_ownership_and_unchanged_results() {
         increment: bool,
     }
 
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Controlled {
         fn mutate_integer(&mut self, value: i64) -> Result<Any> {
             Ok(if self.increment {
@@ -599,7 +599,7 @@ fn 
default_mutation_mode_preserves_ownership_and_unchanged_results() {
 #[test]
 fn borrowed_capabilities_cannot_enable_low_level_default_reuse() {
     struct BorrowedEntry;
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl BorrowedEntry {
         fn mutate_any(
             &mut self,
@@ -642,7 +642,7 @@ fn 
borrowed_capabilities_keep_their_mode_in_dispatch_adapters() {
         source: &'a Array<i64>,
         result: Option<Any>,
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Inspect<'a> {
         fn mutate_bool(
             &mut self,
@@ -689,7 +689,7 @@ fn owned_entry_mode_is_forwarded_by_generated_callbacks() {
         increment: bool,
         shared: bool,
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Entry {
         fn mutate_bool(
             &mut self,
@@ -793,7 +793,7 @@ fn 
consuming_callbacks_forward_permissions_without_temporary_owners() {
             drop(temporary);
         }
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Forward {
         fn mutate_string(&mut self, _: MutationView<'_, FfiString>) -> 
Result<Any> {
             panic!("typed miss must preserve the capability for the next 
handler")
@@ -899,7 +899,7 @@ fn 
consuming_default_descent_transfers_between_contexts_without_node_borrows() {
         value: Option<MutationView<'a>>,
         result: Any,
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Inner<'a> {
         fn mutate_bool(
             &mut self,
@@ -934,7 +934,7 @@ fn 
consuming_default_descent_transfers_between_contexts_without_node_borrows() {
     struct Outer {
         retain: bool,
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Outer {
         fn mutate_any(
             &mut self,
@@ -965,7 +965,7 @@ fn 
consuming_default_descent_transfers_between_contexts_without_node_borrows() {
 #[test]
 fn consuming_default_descent_preserves_unchanged_and_propagates_errors() {
     struct Preserve(bool);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Preserve {
         fn mutate_any(
             &mut self,
@@ -1073,7 +1073,11 @@ fn 
reflected_object_without_shallow_copy_is_rejected_even_when_unchanged() {
 fn callback_errors_preserve_message_and_add_object_context() {
     struct Delegate(Error);
     struct VisitDelegate(Error);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl VisitDelegate {
         fn visit_any(
             &mut self,
@@ -1087,7 +1091,7 @@ fn 
callback_errors_preserve_message_and_add_object_context() {
         }
     }
 
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Delegate {
         fn mutate_any(
             &mut self,
@@ -1462,7 +1466,7 @@ struct GeneratedMapper {
     catch_all: usize,
 }
 
-#[dispatch(map)]
+#[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
 impl GeneratedMapper {
     fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> Result<Any> {
         self.integers.push((value, kind));
@@ -1495,7 +1499,7 @@ struct GeneratedLeafDispatch {
     integers: Vec<(i64, DefRegionKind)>,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl GeneratedLeafDispatch {
     fn mutate_integer(
         &mut self,
@@ -1510,7 +1514,7 @@ impl GeneratedLeafDispatch {
 
 struct GeneratedStatelessDispatch;
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl GeneratedStatelessDispatch {
     fn mutate_integer(
         &mut self,
@@ -1554,7 +1558,7 @@ struct GeneratedRecursiveDispatch {
     integers: Vec<(i64, DefRegionKind)>,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl GeneratedRecursiveDispatch {
     fn mutate_array(
         &mut self,
@@ -1606,7 +1610,7 @@ struct GeneratedDefaultingDispatch {
     integers: Vec<i64>,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl GeneratedDefaultingDispatch {
     fn mutate_array(
         &mut self,
@@ -1745,7 +1749,7 @@ fn callbacks_return_values_convertible_into_any() {
     assert_eq!(mapped.iter().collect::<Vec<_>>(), vec![21, 22]);
 
     struct Double;
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Double {
         fn mutate_integer(
             &mut self,
@@ -1764,7 +1768,7 @@ fn callbacks_return_values_convertible_into_any() {
 #[test]
 fn recursive_mutate_returns_unchanged_or_a_replacement() {
     struct ClampNegativeIntegers;
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl ClampNegativeIntegers {
         fn mutate_any(
             &mut self,
@@ -2034,7 +2038,7 @@ struct CallbackMutateStats {
     defaults: usize,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl CallbackMutateStats {
     fn mutate_integer(&mut self, value: i64) -> Result<Any> {
         self.integers.push(value);
@@ -2080,7 +2084,7 @@ struct CallbackMutateDepth {
     exits: usize,
 }
 
-#[dispatch(mutate, MutationView)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
 impl CallbackMutateDepth {
     fn mutate_any(
         &mut self,
@@ -2119,7 +2123,7 @@ fn 
callback_mutate_explicit_default_is_repeatable_copy_path() {
     let root_pointer = array_pointer(&root);
     let defaults = Cell::new(0);
     struct Repeat<'a>(&'a Cell<i32>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Repeat<'a> {
         fn mutate_integer(&mut self, value: i64) -> Result<Any> {
             Ok(Any::from(value + 1))
@@ -2152,7 +2156,7 @@ fn 
callback_mutate_explicit_default_is_repeatable_copy_path() {
 fn callback_mutate_match_is_final_and_same_fn_can_reenter() {
     let integer_calls = Cell::new(0);
     struct First<'a>(&'a Cell<i32>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> First<'a> {
         fn mutate_array(
             &mut self,
@@ -2174,7 +2178,7 @@ fn 
callback_mutate_match_is_final_and_same_fn_can_reenter() {
 
     let calls = Cell::new(0);
     struct Reenter<'a>(&'a Cell<i32>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Reenter<'a> {
         fn mutate_any(
             &mut self,
@@ -2205,7 +2209,7 @@ fn 
callback_mutate_supports_node_links_nested_tuples_and_reflection() {
     );
     let regions = RefCell::new(Vec::new());
     struct Nodes<'a>(&'a RefCell<Vec<DefRegionKind>>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Nodes<'a> {
         fn mutate_bool(&mut self, _: bool) -> Result<Any> {
             Ok(Any::new())
@@ -2240,7 +2244,7 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
     let borrowed_child = Array::new(vec![1i64]);
     let borrowed_pointer = array_pointer(&borrowed_child);
     struct Borrowed<'a>(&'a Array<i64>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Borrowed<'a> {
         fn mutate_bool(
             &mut self,
@@ -2265,7 +2269,7 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
 
     let owned_pointer = Cell::new(0usize);
     struct Owned<'a>(&'a Cell<usize>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Owned<'a> {
         fn mutate_bool(
             &mut self,
@@ -2292,7 +2296,7 @@ fn 
callback_mutate_distinguishes_borrowed_and_owned_children() {
 #[test]
 fn nested_callback_mutate_restores_the_outer_active_mutator() {
     struct Inner;
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Inner {
         fn mutate_integer(
             &mut self,
@@ -2304,7 +2308,7 @@ fn 
nested_callback_mutate_restores_the_outer_active_mutator() {
     }
     #[repr(align(128))]
     struct Outer<'a>(&'a Cell<usize>);
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl<'a> Outer<'a> {
         fn mutate_integer(
             &mut self,
@@ -2339,7 +2343,7 @@ fn 
nested_callback_mutate_restores_the_outer_active_mutator() {
 #[test]
 fn callback_mutate_panics_resume_and_leave_the_next_run_usable() {
     struct Panic;
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Panic {
         fn mutate_integer(
             &mut self,
@@ -2430,13 +2434,13 @@ fn mutation_descent_preserves_ownership_and_markers() {
         }
     }
     struct MapOwnership(Ownership);
-    #[dispatch(map)]
+    #[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
     impl MapOwnership {
         fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<Any> {
             self.0.map_integer(value, kind)
         }
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Ownership {
         fn mutate_integer(&mut self, value: i64) -> Result<Any> {
             self.map_integer(value, DefRegionKind::None)
@@ -2532,7 +2536,7 @@ fn mutation_regions_retargeting_and_error_restore() {
     }
     #[derive(Default)]
     struct MapRegions(Regions);
-    #[dispatch(map)]
+    #[dispatch(map, (AnyView<'_>, DefRegionKind), Result<Any>)]
     impl MapRegions {
         fn map_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<Any> {
             self.0.map_integer(value, kind)
@@ -2541,7 +2545,7 @@ fn mutation_regions_retargeting_and_error_restore() {
             self.0.map_array(value, kind)
         }
     }
-    #[dispatch(mutate, MutationView)]
+    #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, 
Self>), Result<Any>)]
     impl Regions {
         fn mutate_any(
             &mut self,
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs 
b/rust/tvm-ffi/tests/test_structural_visit.rs
index f723f246..75968052 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -90,7 +90,11 @@ fn default_descent_scopes_regions_and_restores_after_halts() 
{
         seen: Vec<(i64, DefRegionKind)>,
         regions: Vec<DefRegionKind>,
     }
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Probe {
         fn visit_any(
             &mut self,
@@ -195,7 +199,11 @@ fn visit_region_graph(with_hook: bool) -> Any {
 fn default_descent_retargets_without_dispatching_the_container() {
     #[derive(Default)]
     struct Probe(Vec<(&'static str, i64, DefRegionKind)>, Option<Any>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Probe {
         fn visit_any(
             &mut self,
@@ -267,7 +275,11 @@ fn regions_compose_with_field_flags_and_function_hooks() {
         region: DefRegionKind,
         seen: Vec<(i64, DefRegionKind)>,
     }
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Trace {
         fn visit_any(
             &mut self,
@@ -342,7 +354,11 @@ fn regions_compose_with_field_flags_and_function_hooks() {
 #[test]
 fn reflected_pattern_precedes_default_descent() {
     struct Trace(Vec<(i64, DefRegionKind)>, DefRegionKind);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Trace {
         fn visit_any(
             &mut self,
@@ -671,7 +687,11 @@ struct ManualRegionVisitor {
     seen: Vec<DefRegionKind>,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl ManualRegionVisitor {
     fn visit_any(
         &mut self,
@@ -714,7 +734,11 @@ struct GeneratedLeafVisitor {
     integers: Vec<(i64, DefRegionKind)>,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl GeneratedLeafVisitor {
     fn visit_integer(
         &mut self,
@@ -760,7 +784,11 @@ struct GeneratedRecursiveVisitor {
     events: Vec<String>,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl GeneratedRecursiveVisitor {
     fn visit_array(
         &mut self,
@@ -806,7 +834,7 @@ struct GenericDispatchProbe {
     catch_all: usize,
 }
 
-#[dispatch(walk)]
+#[dispatch(walk, (AnyView<'_>, DefRegionKind), Result<WalkResult>)]
 impl GenericDispatchProbe {
     fn walk_integer(&mut self, value: i64) -> Result<WalkResult> {
         self.integers.push(value);
@@ -829,7 +857,7 @@ impl GenericDispatchProbe {
 struct DispatchLabel(String);
 struct DescribeDispatchProbe;
 
-#[dispatch(describe)]
+#[dispatch(describe, (AnyView<'_>, DispatchLabel, &str), String)]
 impl DescribeDispatchProbe {
     fn describe_integer(&mut self, value: i64, prefix: DispatchLabel) -> 
String {
         format!("{}{value}", prefix.0)
@@ -852,7 +880,7 @@ impl DescribeDispatchProbe {
     fn dispatch_describe(&self) {}
 }
 
-#[dispatch(describe)]
+#[dispatch(describe, (AnyView<'_>, MissingType), MissingType)]
 #[cfg_attr(all(), cfg_attr(all(), cfg(any())))]
 impl MissingWholeImpl {
     fn describe_disabled(&mut self, _: MissingType, _: MissingType) -> 
MissingType {
@@ -915,7 +943,11 @@ struct StraddleVisitor {
     events: Vec<String>,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl StraddleVisitor {
     fn visit_any(
         &mut self,
@@ -961,7 +993,7 @@ struct OrderProbe {
     events: Vec<String>,
 }
 
-#[dispatch(walk)]
+#[dispatch(walk, (AnyView<'_>, DefRegionKind), Result<WalkResult>)]
 impl OrderProbe {
     fn walk_array(&mut self, _array: Array<i64>, _kind: DefRegionKind) -> 
Result<WalkResult> {
         self.events.push("array".to_string());
@@ -1066,7 +1098,11 @@ fn handler_errors_include_native_visit_path() {
 fn visitor_errors_include_native_visit_path() {
     struct FailingVisitor;
 
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl FailingVisitor {
         fn visit_any(
             &mut self,
@@ -1092,7 +1128,11 @@ fn visitor_errors_include_native_visit_path() {
     assert!(error.backtrace().contains("object `ffi.Array`"));
 
     struct FailingLeaf;
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl FailingLeaf {
         fn visit_integer(
             &mut self,
@@ -1134,7 +1174,11 @@ fn 
callback_panics_resume_after_the_registered_hook_returns() {
 fn visitor_interrupt_propagates_through_default_children() {
     struct InterruptingVisitor;
 
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl InterruptingVisitor {
         fn visit_any(
             &mut self,
@@ -1338,7 +1382,7 @@ struct ObjectCounter {
     objects: usize,
 }
 
-#[dispatch(walk)]
+#[dispatch(walk, (AnyView<'_>, DefRegionKind), Result<WalkResult>)]
 impl ObjectCounter {
     fn walk_object(&mut self, _value: &Object, _kind: DefRegionKind) -> 
Result<WalkResult> {
         self.objects += 1;
@@ -1459,7 +1503,11 @@ struct InheritedRegionProbe {
     seen: Vec<DefRegionKind>,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl InheritedRegionProbe {
     fn visit_any(
         &mut self,
@@ -1619,7 +1667,11 @@ fn nested_tuple_first_match_order_is_flattened() {
 #[test]
 fn callback_visit_defaults_only_when_no_link_matches() {
     struct Integers<'a>(&'a Cell<i64>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Integers<'a> {
         fn visit_integer(
             &mut self,
@@ -1631,7 +1683,11 @@ fn callback_visit_defaults_only_when_no_link_matches() {
         }
     }
     struct SkipArray<'a>(&'a Cell<i64>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> SkipArray<'a> {
         fn visit_array(
             &mut self,
@@ -1678,7 +1734,11 @@ struct StatefulVisitStats {
     integer_sum: i64,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl StatefulVisitStats {
     fn visit_array(
         &mut self,
@@ -1716,7 +1776,11 @@ struct StatefulVisitDepth {
     calls: usize,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl StatefulVisitDepth {
     fn visit_any(
         &mut self,
@@ -1747,7 +1811,11 @@ fn 
stateful_callback_visit_reborrows_visitor_during_recursion() {
 #[test]
 fn callback_visit_can_reenter_the_same_fn_through_visitor() {
     struct Reenter<'a>(&'a Cell<i32>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Reenter<'a> {
         fn visit_any(
             &mut self,
@@ -1768,7 +1836,11 @@ fn 
callback_visit_can_reenter_the_same_fn_through_visitor() {
 fn callback_visit_tuple_is_first_match_and_can_interrupt() {
     type View<'value> = AnyView<'value>;
     struct Stop<'a>(&'a Cell<i32>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Stop<'a> {
         fn visit_integer(
             &mut self,
@@ -1796,7 +1868,11 @@ fn 
callback_visit_tuple_is_first_match_and_can_interrupt() {
 #[test]
 fn callback_visit_supports_node_links_and_nested_tuples() {
     struct Nodes<'a>(&'a RefCell<Vec<(i64, DefRegionKind)>>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Nodes<'a> {
         fn visit_float(
             &mut self,
@@ -1837,7 +1913,11 @@ fn 
callback_visit_supports_node_links_and_nested_tuples() {
 #[test]
 fn callback_visit_with_overrides_child_def_region() {
     struct Regions<'a>(&'a RefCell<Vec<(i64, DefRegionKind)>>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Regions<'a> {
         fn visit_array(
             &mut self,
@@ -1880,7 +1960,11 @@ fn 
nested_callback_visit_restores_the_outer_active_visitor() {
         checked: bool,
         calls: usize,
     }
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Aligned {
         fn visit_any(
             &mut self,
@@ -1958,7 +2042,11 @@ fn 
nested_callback_visit_restores_the_outer_active_visitor() {
     }
     assert_eq!(defaults.get(), 12);
     struct Inner<'a>(&'a RefCell<Vec<i64>>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Inner<'a> {
         fn visit_integer(
             &mut self,
@@ -1976,7 +2064,11 @@ fn 
nested_callback_visit_restores_the_outer_active_visitor() {
         inner_values: &'a RefCell<Vec<i64>>,
         depth: &'a Cell<usize>,
     }
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Outer<'a> {
         fn visit_any(
             &mut self,
@@ -2023,7 +2115,11 @@ fn 
nested_callback_visit_restores_the_outer_active_visitor() {
 #[test]
 fn callback_visit_panics_resume_and_leave_the_next_run_usable() {
     struct Panic<'a>(&'a Cell<bool>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Panic<'a> {
         fn visit_integer(
             &mut self,
@@ -2035,7 +2131,11 @@ fn 
callback_visit_panics_resume_and_leave_the_next_run_usable() {
         }
     }
     struct Count<'a>(&'a Cell<i32>);
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl<'a> Count<'a> {
         fn visit_integer(
             &mut self,
@@ -2084,7 +2184,7 @@ fn 
copied_views_forward_borrows_and_retain_only_explicit_owners() {
     let root = Any::from(Array::new(vec![1_i64, 2]));
     let view = AnyView::from(&root);
     struct Identity;
-    #[dispatch(identity)]
+    #[dispatch(identity, (AnyView<'a>,), AnyView<'a>)]
     impl Identity {
         fn identity_any<'a>(&self, value: AnyView<'a>) -> AnyView<'a> {
             value
@@ -2117,7 +2217,11 @@ fn 
copied_views_forward_borrows_and_retain_only_explicit_owners() {
     )
     .unwrap();
     struct Descend;
-    #[dispatch(visit)]
+    #[dispatch(
+        visit,
+        (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+        Result<Option<VisitInterrupt>>
+    )]
     impl Descend {
         fn visit_array(
             &mut self,
@@ -2143,7 +2247,7 @@ fn 
copied_views_forward_borrows_and_retain_only_explicit_owners() {
 #[test]
 fn generic_dispatch_distinguishes_no_match_from_handler_outcomes() {
     struct Outcomes;
-    #[dispatch(walk, AnyView)]
+    #[dispatch(walk, (AnyView<'_>,), std::result::Result<Option<i64>, &'static 
str>)]
     impl Outcomes {
         fn walk_integer(&self, _: i64) -> std::result::Result<Option<i64>, 
&'static str> {
             Err("matched error")
diff --git a/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs 
b/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
index 3ea6fc29..ff17dd5f 100644
--- a/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
+++ b/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
@@ -42,7 +42,11 @@ struct RecordingVisitor {
     interrupt_on: Option<i64>,
 }
 
-#[dispatch(visit)]
+#[dispatch(
+    visit,
+    (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
+    Result<Option<VisitInterrupt>>
+)]
 impl RecordingVisitor {
     /// C++ analog: `TestVisitorObj::VisitImpl` — record every value together
     /// with the active def-region state, optionally interrupt with a payload,

Reply via email to