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,