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 b98be1bf [FEAT][RUST] Add rust structural_visit and structural_walk.
(#693)
b98be1bf is described below
commit b98be1bf4062b85a95a7330327645ffd86f99b05
Author: Linzhang Li <[email protected]>
AuthorDate: Sun Aug 2 21:37:11 2026 -0400
[FEAT][RUST] Add rust structural_visit and structural_walk. (#693)
A refreshed summary of the API as it now stands, since it has moved
since the description above was written.
`structural_walk(root, walker, order)` walks a value graph and
dispatches typed handlers in pre- or post-order; handlers steer the
traversal through the returned `WalkResult` (`Advance`, `Skip`,
`Interrupt`/`InterruptWith(payload)`) and may return
`Result<WalkResult>` to propagate errors with `?`. The walker can be:
- a `&mut` `#[dispatch(visit)]` visitor (or a hand-written
`VisitDispatch`) — its `visit_*` methods dispatch on argument type in
source order;
- a single closure over an FFI value type `T` (value cast), a borrowed
object node `&N` (refcount-free subtype check), or the `&VisitValue`
catch-all;
- a tuple of up to 8 such callbacks — closures and `&mut` visitors mixed
freely — tried in order with the first matching argument type winning,
the analog of the variadic C++ `StructuralWalk(root, callbacks...)`
chain.
Every handler shape may declare a trailing `DefRegionKind` argument to
receive the definition-region state:
```rust
use tvm_ffi::{structural_walk, Array, DefRegionKind, Object, WalkOrder,
WalkResult};
let values = Array::new(vec![10_i64, 2]);
let mut total = 0_i64;
let mut objects = 0;
assert!(structural_walk(
&values,
(
|value: i64| {
total += value;
WalkResult::Advance
},
|_object: &Object, _kind: DefRegionKind| {
objects += 1;
WalkResult::Advance
},
),
WalkOrder::PreOrder,
)
.unwrap()
.is_none());
assert_eq!((total, objects), (12, 1));
```
`structural_visit(root, visitor)` drives a hand-written
`StructuralVisitor` when recursion itself is part of the analysis,
mirroring a C++ `StructuralVisitorObj`: `visit` runs for each value and
controls all descent through `default_visit_children` or
`visit_child(child, kind)`, whose explicit kind argument plays the role
of `WithDefRegionKind`. Both entry points return
`Result<Option<VisitInterrupt>>`: `Ok(None)` means the whole graph was
visited, and an interrupting handler's payload comes back in
`Ok(Some(interrupt))`:
```rust
use tvm_ffi::{
structural_visit, Array, DefRegionKind, Result, StructuralVisitor,
VisitInterrupt, VisitValue,
};
#[derive(Default)]
struct Depth {
max: usize,
current: usize,
}
impl StructuralVisitor for Depth {
fn visit(
&mut self,
value: &VisitValue,
def_region_kind: DefRegionKind,
) -> Result<Option<VisitInterrupt>> {
self.current += 1;
self.max = self.max.max(self.current);
let interrupt = self.default_visit_children(value,
def_region_kind)?;
self.current -= 1;
Ok(interrupt)
}
}
let values = Array::new(vec![1_i64, 2]);
let mut depth = Depth::default();
structural_visit(&values, &mut depth)?;
assert_eq!(depth.max, 2);
```
## Performance
Re-measured on the current branch with the shared benchmark: Rust
`structural_visit` (pre-order) and `structural_walk` (post-order)
against the equivalent C++ `StructuralWalk` traversals on identical
object graphs, all language x style combinations checksum-asserted
before timing. Medians of 30 pinned reps.
Rust / C++ time ratio (lower is better; < 1 means Rust is faster):
| shape | structural_visit | structural_walk |
|---|---|---|
| reflected-object tree (196k values) | 0.98x | 1.04x |
| nested arrays (87k values) | 1.10x | 1.16x |
| map trees, small & dense layouts (31k values) | 0.90x | 0.96x |
Every combination is within 1.16x of C++, at an absolute cost of ~7-21
ns per visited value, matching C++.
Signed-off-by: yuchuan <[email protected]>
Co-authored-by: tlopex <[email protected]>
---
docs/guides/rust_lang_guide.md | 148 ++
rust/tvm-ffi-macros/src/lib.rs | 9 +
rust/tvm-ffi-macros/src/visit.rs | 279 ++++
rust/tvm-ffi-sys/src/c_api.rs | 58 +
rust/tvm-ffi/src/any.rs | 23 +
rust/tvm-ffi/src/extra/dispatch.rs | 114 ++
rust/tvm-ffi/src/extra/mod.rs | 2 +
rust/tvm-ffi/src/extra/structural_visit.rs | 1750 ++++++++++++++++++++
rust/tvm-ffi/src/lib.rs | 7 +-
rust/tvm-ffi/tests/test_structural_visit.rs | 873 ++++++++++
.../tests/test_structural_visitor_alignment.rs | 133 ++
11 files changed, 3395 insertions(+), 1 deletion(-)
diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index f4c19e78..f2ae5eff 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -180,6 +180,154 @@ fn may_fail(value: i32) -> Result<()> {
}
```
+### Structural Walk and Visit
+
+Rust provides equivalents of the C++ `StructuralWalk`/`StructuralVisitor`
+APIs. Put `#[dispatch(visit)]` on an impl to turn its `visit_*` methods into
+typed handlers, then pass it to `structural_walk`; each handler returns a
+`WalkResult` (`Advance`, `Skip`, or `Interrupt`) to steer the traversal.
+Handlers dispatch on their argument type and may take an optional trailing
+`DefRegionKind` argument:
+
+```rust
+use tvm_ffi::{dispatch, structural_walk, Array, DefRegionKind, WalkOrder,
WalkResult};
+
+#[derive(Default)]
+struct Probe {
+ total: i64,
+ floats: usize,
+}
+
+#[dispatch(visit)]
+impl Probe {
+ fn visit_integer(&mut self, value: i64) -> WalkResult {
+ self.total += value;
+ WalkResult::Advance
+ }
+
+ fn visit_float(&mut self, _value: f64, _kind: DefRegionKind) -> WalkResult
{
+ self.floats += 1;
+ WalkResult::Advance
+ }
+}
+
+let values = Array::new(vec![1_i64, 2, 3]);
+let mut probe = Probe::default();
+structural_walk(&values, &mut probe, WalkOrder::PreOrder)?;
+assert_eq!(probe.total, 6);
+```
+
+Lambdas also work — pass a single typed lambda, or a tuple of them (up to 8)
+tried in order with the first matching argument type winning, like the
+variadic C++ `StructuralWalk(root, callbacks...)` chain. Unmatched values
+simply advance; a `&VisitValue` lambda acts as a catch-all and must come
+last, since links after an always-matching one never run. Each lambda may
+take a trailing `DefRegionKind` argument:
+
+```rust
+use tvm_ffi::{structural_walk, Array, DefRegionKind, Object, WalkOrder,
WalkResult};
+
+let values = Array::new(vec![1_i64, 2, 3]);
+
+let mut total = 0;
+structural_walk(
+ &values,
+ |value: i64| {
+ total += value;
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+)?;
+assert_eq!(total, 6);
+
+let mut evens = 0;
+let mut objects = 0;
+structural_walk(
+ &values,
+ (
+ |value: i64| {
+ if value % 2 == 0 {
+ evens += 1;
+ }
+ WalkResult::Advance
+ },
+ |_object: &Object, _kind: DefRegionKind| {
+ objects += 1;
+ WalkResult::Advance
+ },
+ ),
+ WalkOrder::PreOrder,
+)?;
+assert_eq!((evens, objects), (1, 1));
+```
+
+Both entry points return `Result<Option<VisitInterrupt>>`: `Ok(None)` means
+the whole graph was visited, and a handler stops the walk early by returning
+`WalkResult::interrupt_with(payload)`, which comes back to the caller as
+`Ok(Some(interrupt))`. Handlers may also return `Result<WalkResult>` and
+propagate errors with `?`:
+
+```rust
+use tvm_ffi::{structural_walk, Array, WalkOrder, WalkResult};
+
+let values = Array::new(vec![1_i64, 2, 3]);
+let found = structural_walk(
+ &values,
+ |value: i64| {
+ if value == 2 {
+ return WalkResult::interrupt_with(value);
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+)?;
+assert_eq!(found.map(|i| i64::try_from(i.value).unwrap()), Some(2));
+```
+
+To drive recursion yourself, implement `StructuralVisitor` and call
+`structural_visit`; `visit` runs for each value and descends through
+`default_visit_children`, or through `visit_child`, which visits one
+selected child and can override the def-region state for it (e.g.
+`DefRegionKind::Recursive` when descending into a binder's parameters):
+
+```rust
+use tvm_ffi::{
+ structural_visit, Array, DefRegionKind, Result, StructuralVisitor,
VisitInterrupt, VisitValue,
+};
+
+#[derive(Default)]
+struct Depth {
+ max: usize,
+ current: usize,
+}
+
+impl StructuralVisitor for Depth {
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ self.current += 1;
+ self.max = self.max.max(self.current);
+ let interrupt = self.default_visit_children(value, def_region_kind)?;
+ self.current -= 1;
+ Ok(interrupt)
+ }
+}
+
+let values = Array::new(vec![1_i64, 2]);
+let mut depth = Depth::default();
+structural_visit(&values, &mut depth)?;
+assert_eq!(depth.max, 2);
+```
+
+Two safety notes: mutable `List`/`Dict` contents are snapshotted before
+callbacks run, so mutation during traversal cannot invalidate the walk; and
+a non-container type with a foreign `__s_visit__` hook is rejected rather
+than silently walked through reflection — visit such a type's children
+explicitly from a `StructuralVisitor`, or skip it with a pre-order
+`WalkResult::Skip`.
+
## Examples
The repository includes a complete example in
`rust/tvm-ffi/examples/load_library.rs`.
diff --git a/rust/tvm-ffi-macros/src/lib.rs b/rust/tvm-ffi-macros/src/lib.rs
index 03ecb050..ada75d41 100644
--- a/rust/tvm-ffi-macros/src/lib.rs
+++ b/rust/tvm-ffi-macros/src/lib.rs
@@ -23,6 +23,15 @@ use proc_macro_error::proc_macro_error;
mod match_any;
mod object_macros;
mod utils;
+mod visit;
+
+/// Generate typed structural-visit dispatch from the `visit_*` methods in an
+/// inherent implementation.
+#[proc_macro_error]
+#[proc_macro_attribute]
+pub fn dispatch(attr: TokenStream, item: TokenStream) -> TokenStream {
+ visit::dispatch(attr, item)
+}
/// Match object-backed values carried by an Any-compatible scrutinee.
///
diff --git a/rust/tvm-ffi-macros/src/visit.rs b/rust/tvm-ffi-macros/src/visit.rs
new file mode 100644
index 00000000..4bad37db
--- /dev/null
+++ b/rust/tvm-ffi-macros/src/visit.rs
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, quote_spanned};
+use syn::{parse_macro_input, FnArg, ImplItem, ImplItemMethod, ItemImpl, Meta,
NestedMeta, Type};
+
+use crate::utils::get_tvm_ffi_crate;
+
+pub(crate) fn dispatch(attr: TokenStream, item: TokenStream) -> TokenStream {
+ let _args = parse_macro_input!(attr as DispatchArgs);
+ let item_impl = parse_macro_input!(item as ItemImpl);
+
+ match expand(&item_impl) {
+ Ok(generated) => quote!(#item_impl #generated).into(),
+ Err(error) => {
+ let error = error.to_compile_error();
+ quote!(#item_impl #error).into()
+ }
+ }
+}
+
+struct DispatchArgs;
+
+impl syn::parse::Parse for DispatchArgs {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let mode: syn::Ident = input.parse()?;
+ if mode != "visit" {
+ return Err(syn::Error::new(mode.span(), "expected
`dispatch(visit)`"));
+ }
+ if !input.is_empty() {
+ return Err(input.error(
+ "`dispatch(visit)` takes no further arguments; a handler that
needs the \
+ definition-region state declares a trailing `DefRegionKind`
argument",
+ ));
+ }
+ Ok(DispatchArgs)
+ }
+}
+
+fn expand(item_impl: &ItemImpl) -> syn::Result<TokenStream2> {
+ if item_impl.trait_.is_some() {
+ return Err(syn::Error::new_spanned(
+ item_impl,
+ "`dispatch(visit)` requires an inherent impl",
+ ));
+ }
+
+ let handlers = item_impl
+ .items
+ .iter()
+ .filter_map(|item| match item {
+ ImplItem::Method(method) if
method.sig.ident.to_string().starts_with("visit_") => {
+ Some(parse_handler(method))
+ }
+ _ => None,
+ })
+ .collect::<syn::Result<Vec<_>>>()?;
+
+ if handlers.is_empty() {
+ return Err(syn::Error::new_spanned(
+ item_impl,
+ "`dispatch(visit)` found no `visit_*` methods",
+ ));
+ }
+ let tvm_ffi = get_tvm_ffi_crate();
+
+ let links = handlers.iter().map(|handler| {
+ let method = &handler.method;
+ let attrs = &handler.cfg_attrs;
+ // A handler opts into the definition-region state by declaring a
+ // trailing argument; the generated dispatch forwards by arity, like
+ // the C++ StructuralWalk callback overloads.
+ let kind_arg = if handler.wants_def_region {
+ quote!(, def_region_kind)
+ } else {
+ quote!()
+ };
+ let invoke = match &handler.argument {
+ HandlerArgument::Value => quote! {
+ return Some(
+
#tvm_ffi::extra::structural_visit::IntoVisitResult::into_visit_result(
+ self.#method(value #kind_arg)
+ )
+ );
+ },
+ HandlerArgument::BorrowedNode(node_type) => quote! {
+ if let Some(node) = value.as_node::<#node_type>() {
+ return Some(
+
#tvm_ffi::extra::structural_visit::IntoVisitResult::into_visit_result(
+ self.#method(node #kind_arg)
+ )
+ );
+ }
+ },
+ HandlerArgument::Owned(value_type) => quote! {
+ if let Some(node) = value.cast::<#value_type>() {
+ return Some(
+
#tvm_ffi::extra::structural_visit::IntoVisitResult::into_visit_result(
+ self.#method(node #kind_arg)
+ )
+ );
+ }
+ },
+ };
+ quote! {
+ #(#[#attrs])*
+ {
+ #invoke
+ }
+ }
+ });
+ let self_type = &item_impl.self_ty;
+ let (impl_generics, _, where_clause) = item_impl.generics.split_for_impl();
+ let impl_cfg_attrs = presence_attrs(&item_impl.attrs)?;
+ let ordering_errors = handlers
+ .iter()
+ .enumerate()
+ .filter(|(_, handler)| matches!(&handler.argument,
HandlerArgument::Value))
+ .flat_map(|(index, handler)| {
+ handlers[index + 1..].iter().map(|later| {
+ let span = handler.method.span();
+ let handler_attrs = &handler.cfg_attrs;
+ let later_attrs = &later.cfg_attrs;
+ quote_spanned! {span=>
+ #(#[#impl_cfg_attrs])*
+ #(#[#handler_attrs])*
+ #(#[#later_attrs])*
+ compile_error!(
+ "the `&VisitValue` catch-all handler must be last
among enabled handlers"
+ );
+ }
+ })
+ });
+
+ Ok(quote! {
+ #(#ordering_errors)*
+
+ #(#[#impl_cfg_attrs])*
+ impl #impl_generics #tvm_ffi::extra::structural_visit::VisitDispatch
+ for #self_type #where_clause
+ {
+ #[allow(unreachable_code, unused_variables)]
+ fn dispatch_visit(
+ &mut self,
+ value: &#tvm_ffi::extra::structural_visit::VisitValue,
+ def_region_kind:
#tvm_ffi::extra::structural_visit::DefRegionKind,
+ ) -> Option<#tvm_ffi::extra::structural_visit::VisitResult> {
+ #(#links)*
+ None
+ }
+ }
+ })
+}
+
+struct Handler {
+ method: syn::Ident,
+ argument: HandlerArgument,
+ /// The handler declared a trailing `DefRegionKind` argument.
+ wants_def_region: bool,
+ cfg_attrs: Vec<Meta>,
+}
+
+enum HandlerArgument {
+ Value,
+ BorrowedNode(Type),
+ Owned(Type),
+}
+
+fn parse_handler(method: &ImplItemMethod) -> syn::Result<Handler> {
+ let inputs = &method.sig.inputs;
+ let receiver_is_mut = matches!(
+ inputs.first(),
+ Some(FnArg::Receiver(receiver))
+ if receiver.reference.is_some() && receiver.mutability.is_some()
+ );
+ if !receiver_is_mut || !(inputs.len() == 2 || inputs.len() == 3) {
+ return Err(syn::Error::new_spanned(
+ &method.sig,
+ "visit handlers must take `&mut self`, a node, and optionally a
trailing \
+ `DefRegionKind` argument",
+ ));
+ }
+ let wants_def_region = inputs.len() == 3;
+
+ let value_type = match inputs.iter().nth(1) {
+ Some(FnArg::Typed(value)) => (*value.ty).clone(),
+ _ => unreachable!("the second argument cannot be a receiver"),
+ };
+ let argument = match &value_type {
+ Type::Reference(reference) if reference.mutability.is_none() => {
+ if is_visit_value(reference.elem.as_ref()) {
+ HandlerArgument::Value
+ } else {
+ HandlerArgument::BorrowedNode((*reference.elem).clone())
+ }
+ }
+ Type::Reference(_) => {
+ return Err(syn::Error::new_spanned(
+ value_type,
+ "visit handler values cannot be mutable references",
+ ));
+ }
+ _ => HandlerArgument::Owned(value_type),
+ };
+ let cfg_attrs = presence_attrs(&method.attrs)?;
+ Ok(Handler {
+ method: method.sig.ident.clone(),
+ argument,
+ wants_def_region,
+ cfg_attrs,
+ })
+}
+
+fn presence_attrs(attrs: &[syn::Attribute]) -> syn::Result<Vec<Meta>> {
+ attrs
+ .iter()
+ .filter(|attr| attr.path.is_ident("cfg") ||
attr.path.is_ident("cfg_attr"))
+ .map(|attr| attr.parse_meta().map(presence_meta))
+ .filter_map(Result::transpose)
+ .collect()
+}
+
+fn presence_meta(meta: Meta) -> Option<Meta> {
+ if meta.path().is_ident("cfg") {
+ return Some(meta);
+ }
+ let Meta::List(mut list) = meta else {
+ return None;
+ };
+ if !list.path.is_ident("cfg_attr") {
+ return None;
+ }
+
+ let mut items = list.nested.into_iter();
+ let condition = items.next()?;
+ let mut retained = syn::punctuated::Punctuated::new();
+ retained.push(condition);
+ for item in items {
+ if let NestedMeta::Meta(meta) = item {
+ if let Some(meta) = presence_meta(meta) {
+ retained.push(NestedMeta::Meta(meta));
+ }
+ }
+ }
+ if retained.len() == 1 {
+ None
+ } else {
+ list.nested = retained;
+ Some(Meta::List(list))
+ }
+}
+
+fn is_visit_value(value_type: &Type) -> bool {
+ let Type::Path(path) = value_type else {
+ return false;
+ };
+ path.path
+ .segments
+ .last()
+ .is_some_and(|segment| segment.ident == "VisitValue")
+}
diff --git a/rust/tvm-ffi-sys/src/c_api.rs b/rust/tvm-ffi-sys/src/c_api.rs
index 910a0505..203446c8 100644
--- a/rust/tvm-ffi-sys/src/c_api.rs
+++ b/rust/tvm-ffi-sys/src/c_api.rs
@@ -81,6 +81,16 @@ pub enum TVMFFITypeIndex {
kTVMFFIModule = 73,
/// Opaque python object.
kTVMFFIOpaquePyObject = 74,
+ /// Mutable list object.
+ kTVMFFIList = 75,
+ /// Mutable dict object.
+ kTVMFFIDict = 76,
+ /// Structural visit interrupt object.
+ kTVMFFIVisitInterrupt = 77,
+ /// End of the statically allocated object type-index range.
+ kTVMFFIStaticObjectEnd = 78,
+ /// Start of dynamically allocated object type indices.
+ kTVMFFIDynObjectBegin = 128,
}
#[repr(i32)]
@@ -91,6 +101,46 @@ pub enum TVMFFIObjectDeleterFlagBitMask {
kTVMFFIObjectDeleterFlagBitMaskBoth = (1 << 0) | (1 << 1),
}
+/// Bit flags attached to reflected fields.
+#[repr(i32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum TVMFFIFieldFlagBitMask {
+ kTVMFFIFieldFlagBitMaskWritable = 1 << 0,
+ kTVMFFIFieldFlagBitMaskHasDefault = 1 << 1,
+ kTVMFFIFieldFlagBitMaskIsStaticMethod = 1 << 2,
+ kTVMFFIFieldFlagBitMaskSEqHashIgnore = 1 << 3,
+ kTVMFFIFieldFlagBitMaskSEqHashDefRecursive = 1 << 4,
+ kTVMFFIFieldFlagBitMaskDefaultFromFactory = 1 << 5,
+ kTVMFFIFieldFlagBitMaskReprOff = 1 << 6,
+ kTVMFFIFieldFlagBitMaskCompareOff = 1 << 7,
+ kTVMFFIFieldFlagBitMaskHashOff = 1 << 8,
+ kTVMFFIFieldFlagBitMaskInitOff = 1 << 9,
+ kTVMFFIFieldFlagBitMaskKwOnly = 1 << 10,
+ kTVMFFIFieldFlagBitSetterIsFunctionObj = 1 << 11,
+ kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive = 1 << 12,
+}
+
+/// Definition-region mode used by structural traversal.
+#[repr(i32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum TVMFFIDefRegionKind {
+ kTVMFFIDefRegionKindNone = 0,
+ kTVMFFIDefRegionKindRecursive = 1,
+ kTVMFFIDefRegionKindNonRecursive = 2,
+}
+
+/// Structural equality/hash participation kind stored in type metadata.
+#[repr(i32)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum TVMFFISEqHashKind {
+ kTVMFFISEqHashKindUnsupported = 0,
+ kTVMFFISEqHashKindTreeNode = 1,
+ kTVMFFISEqHashKindFreeVar = 2,
+ kTVMFFISEqHashKindDAGNode = 3,
+ kTVMFFISEqHashKindConstTreeNode = 4,
+ kTVMFFISEqHashKindUniqueInstance = 5,
+}
+
/// Handle to Object from C API's pov
pub type TVMFFIObjectHandle = *mut c_void;
pub type TVMFFIObjectDeleter = unsafe extern "C" fn(self_ptr: *mut c_void,
flags: i32);
@@ -410,6 +460,14 @@ unsafe extern "C" {
pub fn TVMFFISetCustomAllocator(allocator: *mut TVMFFICustomAllocator) ->
i32;
pub fn TVMFFITypeKeyToIndex(type_key: *const TVMFFIByteArray, out_tindex:
*mut i32) -> i32;
+ pub fn TVMFFITypeRegisterAttr(
+ type_index: i32,
+ attr_name: *const TVMFFIByteArray,
+ attr_value: *const TVMFFIAny,
+ ) -> i32;
+ pub fn TVMFFIGetTypeAttrColumn(
+ attr_name: *const TVMFFIByteArray,
+ ) -> *const TVMFFITypeAttrColumn;
pub fn TVMFFIFunctionGetGlobal(
name: *const TVMFFIByteArray,
out: *mut TVMFFIObjectHandle,
diff --git a/rust/tvm-ffi/src/any.rs b/rust/tvm-ffi/src/any.rs
index 47379f47..1ebf017c 100644
--- a/rust/tvm-ffi/src/any.rs
+++ b/rust/tvm-ffi/src/any.rs
@@ -53,6 +53,25 @@ impl<'a> AnyView<'a> {
self.data.type_index
}
+ #[inline]
+ pub(crate) fn as_raw_ffi_any(&self) -> &TVMFFIAny {
+ &self.data
+ }
+
+ /// Construct a borrowed view from its ABI representation.
+ ///
+ /// # Safety
+ ///
+ /// The caller must keep every resource referenced by `data` alive for the
+ /// returned view's complete lifetime.
+ #[inline]
+ pub(crate) unsafe fn from_raw_ffi_any(data: TVMFFIAny) -> Self {
+ Self {
+ data,
+ _phantom: std::marker::PhantomData,
+ }
+ }
+
/// More strict version than try_from/try_into
///
/// This function will not try to cast the type
@@ -151,6 +170,10 @@ impl Any {
pub fn type_index(&self) -> i32 {
self.data.type_index
}
+ #[inline]
+ pub(crate) fn as_raw_ffi_any(&self) -> &TVMFFIAny {
+ &self.data
+ }
/// Try to query if stored typed in Any exactly matches the type T
///
/// This function is fast in the case of failure and can be used to check
diff --git a/rust/tvm-ffi/src/extra/dispatch.rs
b/rust/tvm-ffi/src/extra/dispatch.rs
new file mode 100644
index 00000000..2b3a49f0
--- /dev/null
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Typed visitor dispatch for [`super::structural_visit::structural_walk`]:
+//! the [`VisitDispatch`] trait targeted by `#[dispatch(visit)]`, and the
+//! walker adapter that runs such a visitor at the phase selected by the walk
+//! order. The traversal engine, closure walkers, and tuple chains live in
+//! [`super::structural_visit`], which re-exports these items to keep its
+//! public paths stable.
+
+use crate::error::Result;
+
+use super::structural_visit::{
+ DefRegionKind, IntoWalker, NativeVisit, VisitResult, VisitValue,
WalkOrder, WalkResult,
+};
+
+/// Typed dispatch implemented by a walk-layer observer.
+///
+/// [`crate::dispatch`] tests the implementation's `visit_*` methods in source
+/// order. Borrowed node arguments use refcount-free subtype checks, owned
+/// FFI-compatible arguments use exact value casts, and `&VisitValue` is a
+/// catch-all. `None` reports that no handler matched: a standalone walk then
+/// advances normally, while a tuple chain hands the value to the next link —
+/// so a spliced visitor that "handled" a value must not return `None`.
+///
+/// This is the observer layer, mirroring C++ `StructuralWalk` callbacks: the
+/// walker owns recursion, and a handler steers it only through the returned
+/// [`WalkResult`]. A traversal that must visit children itself — selected
+/// children, custom orders, explicit definition-region overrides — belongs in
+/// a [`super::structural_visit::StructuralVisitor`] instead.
+///
+/// The definition-region state active at the dispatched value arrives as the
+/// `def_region_kind` argument. A `#[dispatch(visit)]` handler opts into it by
+/// declaring a trailing `DefRegionKind` parameter — the analog of a C++
+/// `StructuralWalk` callback accepting `(value, def_region_kind)` instead of
+/// `(value)`.
+pub trait VisitDispatch: Sized {
+ fn dispatch_visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult>;
+}
+
+impl<V: VisitDispatch> VisitDispatch for &mut V {
+ #[inline]
+ fn dispatch_visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ (**self).dispatch_visit(value, def_region_kind)
+ }
+}
+
+#[doc(hidden)]
+pub enum ByDispatch {}
+
+impl<'a, V: VisitDispatch> IntoWalker<ByDispatch> for &'a mut V {
+ type Walker = DispatchVisitor<&'a mut V>;
+ fn into_walker(self, order: WalkOrder) -> Self::Walker {
+ DispatchVisitor {
+ visitor: self,
+ order,
+ }
+ }
+}
+
+/// Owns its walker so a closure's state stays inline and a `&mut` visitor
+/// keeps a single level of indirection. Public only as an
+/// [`IntoWalker::Walker`] projection.
+#[doc(hidden)]
+pub struct DispatchVisitor<V> {
+ visitor: V,
+ order: WalkOrder,
+}
+
+impl<V: VisitDispatch> NativeVisit for DispatchVisitor<V> {
+ fn enter(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) ->
Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => self
+ .visitor
+ .dispatch_visit(value, def_region_kind)
+ .unwrap_or(Ok(WalkResult::Advance)),
+ WalkOrder::PostOrder => Ok(WalkResult::Advance),
+ }
+ }
+
+ fn exit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) ->
Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => Ok(WalkResult::Advance),
+ WalkOrder::PostOrder => self
+ .visitor
+ .dispatch_visit(value, def_region_kind)
+ .unwrap_or(Ok(WalkResult::Advance)),
+ }
+ }
+}
diff --git a/rust/tvm-ffi/src/extra/mod.rs b/rust/tvm-ffi/src/extra/mod.rs
index 2a4c01d0..0489fe27 100644
--- a/rust/tvm-ffi/src/extra/mod.rs
+++ b/rust/tvm-ffi/src/extra/mod.rs
@@ -16,4 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
+pub mod dispatch;
pub mod module;
+pub mod structural_visit;
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs
b/rust/tvm-ffi/src/extra/structural_visit.rs
new file mode 100644
index 00000000..1d15efd8
--- /dev/null
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -0,0 +1,1750 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Native Rust structural visiting.
+//!
+//! Two public layers mirror the C++ API split:
+//!
+//! * [`StructuralVisitor`] + [`structural_visit`] — the visitor drives
+//! recursion itself, like a hand-written C++ `StructuralVisitorObj`:
+//! [`StructuralVisitor::visit`] runs once per reached value and descends
+//! only where it calls [`StructuralVisitor::default_visit_children`] or
+//! [`StructuralVisitor::visit_child`].
+//! * [`VisitDispatch`] + [`structural_walk`] — observer callbacks, like C++
+//! `StructuralWalk`: the walker recurses on its own and callbacks steer it
+//! through the returned [`WalkResult`] (advance, skip, interrupt).
+//!
+//! Both layers thread the definition-region state explicitly: walk handlers
+//! opt in with a trailing [`DefRegionKind`] argument, and a visitor receives
+//! and forwards it when descending.
+//!
+//! Underneath both, [`VisitValue`] provides borrowed matching for typed Rust
+//! dispatch and the stateless recursion engine (`visit_raw` and the
+//! `visit_*` helpers below) owns iteration over containers and reflected
+//! fields.
+//!
+//! The runtime object registry is open, so the walker uses the stable tvm-ffi
+//! reflection ABI for arbitrary registered object types. That ABI is only the
+//! object-description boundary: traversal, control flow, typed dispatch,
+//! visitor state, and definition-region propagation remain in Rust.
+//!
+//! Mutable `List`/`Dict` contents are snapshotted before callbacks run, so a
+//! callback mutating the container it was reached through cannot invalidate
+//! the traversal; the walk sees the pre-mutation contents.
+//!
+//! No C++ `ffi.StructuralVisitor` is constructed and no C++ default-visit
+//! function is called. A non-container type with a foreign `__s_visit__` hook
+//! is rejected instead of silently substituting reflection with potentially
+//! different semantics; visit such a type's children explicitly from a
+//! [`StructuralVisitor`], or skip the value in a walk.
+
+use std::marker::PhantomData;
+use std::ops::ControlFlow;
+use std::os::raw::c_void;
+use std::ptr::NonNull;
+use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
+
+use crate::any::{Any, AnyView};
+use crate::error::{Error, Result, RUNTIME_ERROR, TYPE_ERROR};
+use crate::function::Function;
+use crate::object::ObjectCore;
+use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
+ kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive,
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive,
+ kTVMFFIFieldFlagBitMaskSEqHashIgnore,
+};
+use crate::tvm_ffi_sys::{
+ TVMFFIAny, TVMFFIByteArray, TVMFFIDefRegionKind, TVMFFIFieldInfo,
TVMFFIGetTypeAttrColumn,
+ TVMFFIGetTypeInfo, TVMFFIObject, TVMFFISEqHashKind, TVMFFITypeAttrColumn,
TVMFFITypeIndex,
+};
+
+const STRUCTURAL_VISIT_ATTR: &str = "__s_visit__";
+const FLAG_SEQ_HASH_IGNORE: i64 = kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
+const FLAG_SEQ_HASH_DEF_RECURSIVE: i64 =
kTVMFFIFieldFlagBitMaskSEqHashDefRecursive as i64;
+const FLAG_SEQ_HASH_DEF_NON_RECURSIVE: i64 =
kTVMFFIFieldFlagBitMaskSEqHashDefNonRecursive as i64;
+
+/// What a callback asks the Rust walker to do with the current value.
+pub enum WalkResult {
+ /// Continue and visit this value's children.
+ Advance,
+ /// Continue without visiting this value's children or firing its exit
hook.
+ Skip,
+ /// Halt the entire traversal.
+ Interrupt,
+ /// Halt the entire traversal and return a payload to the caller.
+ InterruptWith(Any),
+}
+
+impl WalkResult {
+ /// Halt traversal with an FFI-compatible payload.
+ pub fn interrupt_with<T: Into<Any>>(payload: T) -> Self {
+ Self::InterruptWith(payload.into())
+ }
+}
+
+/// Convert either an infallible or fallible typed handler result.
+///
+/// This keeps simple handlers terse while allowing a handler to return
+/// `tvm_ffi::Result<WalkResult>` and use `?`.
+pub trait IntoVisitResult {
+ fn into_visit_result(self) -> Result<WalkResult>;
+}
+
+impl IntoVisitResult for WalkResult {
+ fn into_visit_result(self) -> Result<WalkResult> {
+ Ok(self)
+ }
+}
+
+impl IntoVisitResult for Result<WalkResult> {
+ fn into_visit_result(self) -> Result<WalkResult> {
+ self
+ }
+}
+
+/// Callback order for [`structural_walk`].
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum WalkOrder {
+ /// Run the typed handler before the current value's children.
+ #[default]
+ PreOrder,
+ /// Run the typed handler after the current value's children.
+ PostOrder,
+}
+
+/// Definition-region state active at the current value.
+///
+/// Reflected fields marked `SEqHashDefRecursive` or
+/// `SEqHashDefNonRecursive` override the inherited state for that field's
+/// complete recursive visit.
+#[repr(i32)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum DefRegionKind {
+ /// The value is outside a definition region.
+ #[default]
+ None = 0,
+ /// Definitions apply recursively through the visited value.
+ Recursive = 1,
+ /// Definitions apply to the visited value using non-recursive semantics.
+ NonRecursive = 2,
+}
+
+const _: () = {
+ assert!(DefRegionKind::None as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindNone as i32);
+ assert!(
+ DefRegionKind::Recursive as i32
+ == TVMFFIDefRegionKind::kTVMFFIDefRegionKindRecursive as i32
+ );
+ assert!(
+ DefRegionKind::NonRecursive as i32
+ == TVMFFIDefRegionKind::kTVMFFIDefRegionKindNonRecursive as i32
+ );
+};
+
+/// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
+///
+/// Entry points and visitor-layer calls return
+/// `Result<Option<VisitInterrupt>>`: `Ok(None)` means the (sub)graph was
+/// visited completely, `Ok(Some(..))` means a handler halted the traversal
+/// with this interrupt, and `Err` means it failed.
+pub struct VisitInterrupt {
+ /// Payload returned with the interrupt, or FFI `None` for no payload.
+ pub value: Any,
+}
+
+impl VisitInterrupt {
+ /// Interrupt carrying an FFI-compatible payload.
+ pub fn with<T: Into<Any>>(payload: T) -> Self {
+ Self {
+ value: payload.into(),
+ }
+ }
+}
+
+/// Fallible result returned by generated typed dispatch.
+#[doc(hidden)]
+pub type VisitResult = Result<WalkResult>;
+
+/// A borrowed view of a raw tvm-ffi value.
+///
+/// Generated visitors match this value without taking ownership: borrowed
+/// object-node handlers use [`VisitValue::as_node`], while POD or object-ref
+/// value handlers use [`VisitValue::cast`].
+#[repr(transparent)]
+pub struct VisitValue(TVMFFIAny);
+
+impl VisitValue {
+ #[inline]
+ fn from_raw(raw: TVMFFIAny) -> Self {
+ VisitValue(raw)
+ }
+
+ /// Convert the value into an owned typed handle.
+ #[inline]
+ pub fn cast<R: crate::type_traits::AnyCompatible>(&self) -> Option<R> {
+ unsafe {
+ if R::check_any_strict(&self.0) {
+ Some(R::copy_from_any_view_after_check(&self.0))
+ } else {
+ None
+ }
+ }
+ }
+
+ /// Runtime type index stored in this value.
+ #[inline]
+ pub fn type_index(&self) -> i32 {
+ self.0.type_index
+ }
+
+ /// Borrow the value as node type `N` if it is an instance of that type.
+ #[inline]
+ pub fn as_node<N: ObjectCore>(&self) -> Option<&N> {
+ if self.0.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as
i32 {
+ return None;
+ }
+ let base_type_index = N::type_index();
+ if self.0.type_index != base_type_index {
+ // A final type has no registered subtype, so a differing index can
+ // never match: reject with the integer compare alone, mirroring
the
+ // `_type_final` fast path of C++ `IsObjectInstance`.
+ if N::TYPE_FINAL {
+ return None;
+ }
+ if !is_instance_at_depth(self.0.type_index, base_type_index,
N::TYPE_DEPTH) {
+ return None;
+ }
+ }
+ Some(unsafe { &*(self.0.data_union.v_obj as *const N) })
+ }
+}
+
+enum NativeHalt {
+ Interrupt(Any),
+ Error(Error),
+}
+
+impl From<Error> for NativeHalt {
+ fn from(error: Error) -> Self {
+ NativeHalt::Error(error)
+ }
+}
+
+type NativeResult = std::result::Result<(), NativeHalt>;
+
+// The typed-dispatch layer (`VisitDispatch`, its walker adapter, and the
+// `&mut V` IntoWalker form) lives in `super::dispatch`; re-exported here so
+// the module's public paths — which `#[dispatch(visit)]`-generated code
+// names — stay stable.
+pub use super::dispatch::{ByDispatch, DispatchVisitor, VisitDispatch};
+
+/// Conversion into the walker argument of [`structural_walk`].
+///
+/// The `Marker` parameter lets one entry point accept several handler
+/// shapes without overlapping implementations — the Rust equivalent of the
+/// C++ `StructuralWalk` callback overload set:
+///
+/// * `&mut V` where `V: VisitDispatch` — a stateful typed visitor
+/// (`#[dispatch(visit)]` or hand-written).
+/// * A bare closure in any [`WalkChainLink`] shape — catch-all
+/// `FnMut(&VisitValue)`, typed `FnMut(T)`, node `FnMut(&N)`, each with an
+/// optional trailing [`DefRegionKind`] argument — the analog of a single
+/// C++ callback. Values a typed closure does not match advance normally.
+/// * A tuple of typed links `(link1, link2, ...)`, up to 8 — the analog of
+/// the C++ variadic callback chain; see [`WalkChainLink`] for the
+/// accepted link shapes. Larger handler sets belong in one
+/// `#[dispatch(visit)]` visitor, which itself splices into a tuple as a
+/// single link.
+///
+/// Closure arguments usually need explicit type annotations
+/// (`|value: &VisitValue| ...`) for the marker to be inferred.
+#[diagnostic::on_unimplemented(
+ message = "`{Self}` is not a supported `structural_walk` walker",
+ note = "accepted walkers: `&mut V` where `V: VisitDispatch`; a closure
over `&VisitValue`, \
+ an FFI value type `T`, or `&N` of an object node type (`N:
ObjectCore`, e.g. \
+ `&Object`), optionally with a trailing `DefRegionKind` argument;
or a tuple of \
+ up to 8 such links",
+ note = "closure arguments need explicit type annotations; ObjectRef
wrappers like `String` \
+ or `Array<T>` are FFI value types — take them by value, not by
reference"
+)]
+pub trait IntoWalker<Marker> {
+ #[doc(hidden)]
+ type Walker: NativeVisit;
+ #[doc(hidden)]
+ fn into_walker(self, order: WalkOrder) -> Self::Walker;
+}
+
+/// Runs a catch-all closure at the phase selected by `order` — the closure
+/// analog of `DispatchVisitor`, without the `Option<VisitResult>`
+/// no-handler-matched layer a dispatch chain needs. (Routing closures
+/// through `DispatchVisitor` instead measures ~10-20% slower on the bare
+/// closure walk: the wrapped-and-unwrapped `Option<Result<..>>` does not
+/// fold away.)
+#[doc(hidden)]
+pub struct ClosureWalker<F> {
+ callback: F,
+ order: WalkOrder,
+}
+
+impl<F, O> NativeVisit for ClosureWalker<F>
+where
+ F: FnMut(&VisitValue) -> O,
+ O: IntoVisitResult,
+{
+ fn enter(&mut self, value: &VisitValue, _def_region_kind: DefRegionKind)
-> Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => (self.callback)(value).into_visit_result(),
+ WalkOrder::PostOrder => Ok(WalkResult::Advance),
+ }
+ }
+
+ fn exit(&mut self, value: &VisitValue, _def_region_kind: DefRegionKind) ->
Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => Ok(WalkResult::Advance),
+ WalkOrder::PostOrder => (self.callback)(value).into_visit_result(),
+ }
+ }
+}
+
+#[doc(hidden)]
+pub enum ByValueClosure {}
+
+impl<F, O> IntoWalker<ByValueClosure> for F
+where
+ F: FnMut(&VisitValue) -> O,
+ O: IntoVisitResult,
+{
+ type Walker = ClosureWalker<F>;
+ fn into_walker(self, order: WalkOrder) -> Self::Walker {
+ ClosureWalker {
+ callback: self,
+ order,
+ }
+ }
+}
+
+/// `ClosureWalker` variant whose callback also receives the definition-region
+/// state.
+#[doc(hidden)]
+pub struct ClosureKindWalker<F> {
+ callback: F,
+ order: WalkOrder,
+}
+
+impl<F, O> NativeVisit for ClosureKindWalker<F>
+where
+ F: FnMut(&VisitValue, DefRegionKind) -> O,
+ O: IntoVisitResult,
+{
+ fn enter(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) ->
Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => (self.callback)(value,
def_region_kind).into_visit_result(),
+ WalkOrder::PostOrder => Ok(WalkResult::Advance),
+ }
+ }
+
+ fn exit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) ->
Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => Ok(WalkResult::Advance),
+ WalkOrder::PostOrder => (self.callback)(value,
def_region_kind).into_visit_result(),
+ }
+ }
+}
+
+#[doc(hidden)]
+pub enum ByValueKindClosure {}
+
+impl<F, O> IntoWalker<ByValueKindClosure> for F
+where
+ F: FnMut(&VisitValue, DefRegionKind) -> O,
+ O: IntoVisitResult,
+{
+ type Walker = ClosureKindWalker<F>;
+ fn into_walker(self, order: WalkOrder) -> Self::Walker {
+ ClosureKindWalker {
+ callback: self,
+ order,
+ }
+ }
+}
+
+/// One typed link of a tuple walker — a single callback of the C++ variadic
+/// `StructuralWalk(root, callbacks...)` chain.
+///
+/// A tuple of up to 8 links passed to [`structural_walk`] is tried in order
+/// and the first link whose argument type matches the value runs, exactly
+/// like the C++ callback chain. (Python's `structural_walk` differs on one
+/// point: it keeps `callbacks` and `with_def_region_kind` as two separately
+/// ordered groups, trying every plain entry before any kind-taking entry,
+/// so a mixed Rust tuple's single interleaved order has no exact Python
+/// equivalent.) Accepted link shapes mirror `#[dispatch(visit)]` handlers:
+///
+/// * `FnMut(T) -> impl IntoVisitResult` for an FFI-convertible `T` — value
+/// cast via [`VisitValue::cast`], which matches on the FFI type tag: a
+/// numeric link claims every `Int` (or `Float`) regardless of width and
+/// converts with `as` semantics, so prefer `i64`/`f64` links unless a
+/// deliberate narrowing is wanted.
+/// * `FnMut(&N) -> impl IntoVisitResult` for an object node `N` —
+/// refcount-free subtype check via [`VisitValue::as_node`].
+/// * `FnMut(&VisitValue) -> impl IntoVisitResult` — catch-all.
+/// * `&mut V` where `V: VisitDispatch` — splice a typed visitor into the
+/// chain; it claims every value one of its handlers matches.
+///
+/// Links after one that matches every value never run: place a catch-all
+/// closure — or a spliced visitor whose own chain ends in a `&VisitValue`
+/// handler — last. Unlike the in-visitor ordering check, misordering a
+/// tuple is not a compile error.
+///
+/// Every closure shape may declare a trailing [`DefRegionKind`] argument,
+/// and a single typed closure may also be passed to [`structural_walk`]
+/// bare, without the tuple. Closure arguments need explicit type
+/// annotations for the marker to be inferred. Borrow rules apply per link,
+/// so state shared across links goes through a `Cell`/`RefCell` — or in a
+/// single `#[dispatch(visit)]` visitor, which shares `&mut self` between
+/// its handlers.
+///
+/// This trait is sealed: the link shapes above are the complete set, and
+/// the dispatch method is an internal detail.
+pub trait WalkChainLink<Marker>: sealed::SealedLink<Marker> {
+ /// Run this link if `value` matches its argument type; `None` hands the
+ /// value to the next link.
+ #[doc(hidden)]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult>;
+}
+
+mod sealed {
+ use super::{DefRegionKind, IntoVisitResult, ObjectCore, VisitDispatch,
VisitValue};
+
+ /// Seal for [`super::WalkChainLink`]: one impl per accepted link shape,
+ /// mirroring the `WalkChainLink` impl set exactly.
+ pub trait SealedLink<Marker> {}
+
+ impl<F, T, O> SealedLink<super::ByOwnedLink<T>> for F
+ where
+ F: FnMut(T) -> O,
+ O: IntoVisitResult,
+ {
+ }
+ impl<F, T, O> SealedLink<super::ByOwnedKindLink<T>> for F
+ where
+ F: FnMut(T, DefRegionKind) -> O,
+ O: IntoVisitResult,
+ {
+ }
+ impl<F, N: ObjectCore, O> SealedLink<super::ByNodeLink<N>> for F
+ where
+ F: for<'a> FnMut(&'a N) -> O,
+ O: IntoVisitResult,
+ {
+ }
+ impl<F, N: ObjectCore, O> SealedLink<super::ByNodeKindLink<N>> for F
+ where
+ F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
+ O: IntoVisitResult,
+ {
+ }
+ impl<F, O> SealedLink<super::ByCatchAllLink> for F
+ where
+ F: for<'a> FnMut(&'a VisitValue) -> O,
+ O: IntoVisitResult,
+ {
+ }
+ impl<F, O> SealedLink<super::ByCatchAllKindLink> for F
+ where
+ F: for<'a> FnMut(&'a VisitValue, DefRegionKind) -> O,
+ O: IntoVisitResult,
+ {
+ }
+ impl<V: VisitDispatch> SealedLink<super::ByDispatchLink> for &mut V {}
+}
+
+#[doc(hidden)]
+pub struct ByOwnedLink<T>(PhantomData<T>);
+
+impl<F, T, O> WalkChainLink<ByOwnedLink<T>> for F
+where
+ F: FnMut(T) -> O,
+ T: crate::type_traits::AnyCompatible,
+ O: IntoVisitResult,
+{
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ _def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ value
+ .cast::<T>()
+ .map(|typed| self(typed).into_visit_result())
+ }
+}
+
+#[doc(hidden)]
+pub struct ByOwnedKindLink<T>(PhantomData<T>);
+
+impl<F, T, O> WalkChainLink<ByOwnedKindLink<T>> for F
+where
+ F: FnMut(T, DefRegionKind) -> O,
+ T: crate::type_traits::AnyCompatible,
+ O: IntoVisitResult,
+{
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ value
+ .cast::<T>()
+ .map(|typed| self(typed, def_region_kind).into_visit_result())
+ }
+}
+
+#[doc(hidden)]
+pub struct ByNodeLink<N>(PhantomData<N>);
+
+impl<F, N, O> WalkChainLink<ByNodeLink<N>> for F
+where
+ F: for<'a> FnMut(&'a N) -> O,
+ N: ObjectCore,
+ O: IntoVisitResult,
+{
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ _def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ value
+ .as_node::<N>()
+ .map(|node| self(node).into_visit_result())
+ }
+}
+
+#[doc(hidden)]
+pub struct ByNodeKindLink<N>(PhantomData<N>);
+
+impl<F, N, O> WalkChainLink<ByNodeKindLink<N>> for F
+where
+ F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
+ N: ObjectCore,
+ O: IntoVisitResult,
+{
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ value
+ .as_node::<N>()
+ .map(|node| self(node, def_region_kind).into_visit_result())
+ }
+}
+
+#[doc(hidden)]
+pub enum ByCatchAllLink {}
+
+impl<F, O> WalkChainLink<ByCatchAllLink> for F
+where
+ F: for<'a> FnMut(&'a VisitValue) -> O,
+ O: IntoVisitResult,
+{
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ _def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ Some(self(value).into_visit_result())
+ }
+}
+
+#[doc(hidden)]
+pub enum ByCatchAllKindLink {}
+
+impl<F, O> WalkChainLink<ByCatchAllKindLink> for F
+where
+ F: for<'a> FnMut(&'a VisitValue, DefRegionKind) -> O,
+ O: IntoVisitResult,
+{
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ Some(self(value, def_region_kind).into_visit_result())
+ }
+}
+
+#[doc(hidden)]
+pub enum ByDispatchLink {}
+
+impl<V: VisitDispatch> WalkChainLink<ByDispatchLink> for &mut V {
+ #[inline]
+ fn try_call(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Option<VisitResult> {
+ self.dispatch_visit(value, def_region_kind)
+ }
+}
+
+/// Runs a tuple of [`WalkChainLink`]s at the phase selected by `order`,
+/// trying links in order and short-circuiting on the first whose type
+/// matches — the Rust analog of C++ `StructuralWalkCallbackChain`. Static
+/// dispatch throughout: each link's type test inlines to the same code the
+/// `#[dispatch(visit)]` macro generates for a `visit_*` chain.
+#[doc(hidden)]
+pub struct ChainWalker<Links, Markers> {
+ links: Links,
+ order: WalkOrder,
+ markers: PhantomData<fn(Markers)>,
+}
+
+macro_rules! impl_chain_walker {
+ ($(($F:ident, $M:ident, $idx:tt)),+) => {
+ impl<$($F, $M,)+> ChainWalker<($($F,)+), ($($M,)+)>
+ where
+ $($F: WalkChainLink<$M>,)+
+ {
+ #[inline]
+ fn dispatch(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<WalkResult> {
+ $(
+ if let Some(result) = self.links.$idx.try_call(value,
def_region_kind) {
+ return result;
+ }
+ )+
+ Ok(WalkResult::Advance)
+ }
+ }
+
+ impl<$($F, $M,)+> NativeVisit for ChainWalker<($($F,)+), ($($M,)+)>
+ where
+ $($F: WalkChainLink<$M>,)+
+ {
+ fn enter(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => self.dispatch(value,
def_region_kind),
+ WalkOrder::PostOrder => Ok(WalkResult::Advance),
+ }
+ }
+
+ fn exit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<WalkResult> {
+ match self.order {
+ WalkOrder::PreOrder => Ok(WalkResult::Advance),
+ WalkOrder::PostOrder => self.dispatch(value,
def_region_kind),
+ }
+ }
+ }
+
+ impl<$($F, $M,)+> IntoWalker<($($M,)+)> for ($($F,)+)
+ where
+ $($F: WalkChainLink<$M>,)+
+ {
+ type Walker = ChainWalker<($($F,)+), ($($M,)+)>;
+ fn into_walker(self, order: WalkOrder) -> Self::Walker {
+ ChainWalker {
+ links: self,
+ order,
+ markers: PhantomData,
+ }
+ }
+ }
+ };
+}
+
+impl_chain_walker!((F0, M0, 0));
+impl_chain_walker!((F0, M0, 0), (F1, M1, 1));
+impl_chain_walker!((F0, M0, 0), (F1, M1, 1), (F2, M2, 2));
+impl_chain_walker!((F0, M0, 0), (F1, M1, 1), (F2, M2, 2), (F3, M3, 3));
+impl_chain_walker!(
+ (F0, M0, 0),
+ (F1, M1, 1),
+ (F2, M2, 2),
+ (F3, M3, 3),
+ (F4, M4, 4)
+);
+impl_chain_walker!(
+ (F0, M0, 0),
+ (F1, M1, 1),
+ (F2, M2, 2),
+ (F3, M3, 3),
+ (F4, M4, 4),
+ (F5, M5, 5)
+);
+impl_chain_walker!(
+ (F0, M0, 0),
+ (F1, M1, 1),
+ (F2, M2, 2),
+ (F3, M3, 3),
+ (F4, M4, 4),
+ (F5, M5, 5),
+ (F6, M6, 6)
+);
+impl_chain_walker!(
+ (F0, M0, 0),
+ (F1, M1, 1),
+ (F2, M2, 2),
+ (F3, M3, 3),
+ (F4, M4, 4),
+ (F5, M5, 5),
+ (F6, M6, 6),
+ (F7, M7, 7)
+);
+
+// A bare typed closure — `FnMut(T)` or `FnMut(&N)`, optionally with a
+// trailing `DefRegionKind` — walks as a single-link chain, so a lone typed
+// handler needs no tuple wrapping; values that do not match its argument
+// type advance normally. `&VisitValue` catch-all closures keep their
+// dedicated `ClosureWalker`/`ClosureKindWalker` path above.
+macro_rules! impl_bare_link_walker {
+ ($(($marker:ident, $($fn_args:ty),+)),+ $(,)?) => {
+ $(
+ impl<F, T, O> IntoWalker<$marker<T>> for F
+ where
+ F: FnMut($($fn_args),+) -> O,
+ Self: WalkChainLink<$marker<T>>,
+ O: IntoVisitResult,
+ {
+ type Walker = ChainWalker<(F,), ($marker<T>,)>;
+ fn into_walker(self, order: WalkOrder) -> Self::Walker {
+ ChainWalker {
+ links: (self,),
+ order,
+ markers: PhantomData,
+ }
+ }
+ }
+ )+
+ };
+}
+
+impl_bare_link_walker!(
+ (ByOwnedLink, T),
+ (ByOwnedKindLink, T, DefRegionKind),
+ (ByNodeLink, &T),
+ (ByNodeKindLink, &T, DefRegionKind),
+);
+
+/// A visitor that drives recursion itself, mirroring C++
+/// `StructuralVisitorObj`.
+///
+/// [`structural_visit`] calls [`StructuralVisitor::visit`] for the root;
+/// after that the visitor is in control, exactly like a C++ visitor whose
+/// vtable `visit` runs per value. A `visit` implementation descends only
+/// where it chooses:
+///
+/// * [`StructuralVisitor::default_visit_children`] delegates the default
+/// child recursion — the analog of C++
+/// `StructuralVisitorObj::DefaultVisitExpected`.
+/// * [`StructuralVisitor::visit_child`] visits one selected child — the
+/// analog of C++ `visitor->Visit(child)`, with the explicit
+/// `def_region_kind` argument playing the role of `WithDefRegionKind`.
+///
+/// Returning without descending skips the value's children. There is no
+/// [`WalkResult`] at this layer: control flow is what the implementation
+/// visits, and `Ok(Some(interrupt))` halts the traversal — the analog of
+/// returning a C++ `VisitInterrupt`. Nested `visit_child` and
+/// `default_visit_children` calls report a nested interrupt through their
+/// return value; propagate it (and errors, via `?`) upward instead of
+/// dropping the result.
+///
+/// The definition-region state is threaded explicitly, exactly like walk
+/// handlers that declare the trailing argument: `visit` receives the state
+/// active at the value and forwards it — or an override — when descending.
+/// Reflected-field annotations override the forwarded state automatically
+/// inside `default_visit_children`.
+pub trait StructuralVisitor: Sized {
+ /// Visit one value under the definition-region state active at it.
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>>;
+
+ /// Visit `child` now under `def_region_kind`, dispatching back into
+ /// [`StructuralVisitor::visit`]. An FFI `None` child is skipped without
+ /// a callback, matching the walk layer.
+ #[inline]
+ fn visit_child<T>(
+ &mut self,
+ child: &T,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>>
+ where
+ for<'x> AnyView<'x>: From<&'x T>,
+ {
+ let raw = raw_of(AnyView::from(child));
+ if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+ return Ok(None);
+ }
+ self.visit(&VisitValue::from_raw(raw), def_region_kind)
+ }
+
+ /// Visit `value`'s children — not `value` itself — with the default
+ /// rules, dispatching each child back into [`StructuralVisitor::visit`].
+ ///
+ /// Children are container contents for `Array`/`List`/`Map`/`Dict` and
+ /// reflected structural fields otherwise. Field annotations override
+ /// `def_region_kind` for that field's recursive visit exactly like the
+ /// walk layer.
+ #[inline]
+ fn default_visit_children(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ let result = visit_children_raw(
+ value.0,
+ &mut UserChildren { visitor: self },
+ def_region_kind,
+ )
+ .map_err(|halt| with_value_context(halt, value.0));
+ finish(result)
+ }
+}
+
+/// Internal per-value protocol driven by the recursion engine. Public only
+/// as the bound of [`IntoWalker::Walker`]; not meant to be implemented
+/// outside this crate.
+#[doc(hidden)]
+pub trait NativeVisit {
+ fn enter(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) ->
Result<WalkResult>;
+
+ fn exit(&mut self, _value: &VisitValue, _def_region_kind: DefRegionKind)
-> Result<WalkResult> {
+ Ok(WalkResult::Advance)
+ }
+}
+
+/// Per-child action invoked by the shared child-iteration engine.
+///
+/// The engine owns *finding* the children (container contents, reflected
+/// fields) and computing each child's definition-region state; this trait
+/// decides what happens at a child. The walk layer recurses
+/// ([`WalkChildren`]); the visitor layer hands the child straight to user
+/// code ([`UserChildren`]).
+trait ChildVisit {
+ fn visit_child(&mut self, child: TVMFFIAny, def_region_kind:
DefRegionKind) -> NativeResult;
+}
+
+/// Walk-layer recursion: every child re-enters [`visit_raw`].
+struct WalkChildren<'a, V> {
+ visitor: &'a mut V,
+}
+
+impl<V: NativeVisit> ChildVisit for WalkChildren<'_, V> {
+ fn visit_child(&mut self, child: TVMFFIAny, def_region_kind:
DefRegionKind) -> NativeResult {
+ visit_raw(child, self.visitor, def_region_kind)
+ }
+}
+
+/// Visitor-layer dispatch: every child goes back into the user-driven
+/// [`StructuralVisitor::visit`], which controls further descent itself.
+struct UserChildren<'a, V> {
+ visitor: &'a mut V,
+}
+
+impl<V: StructuralVisitor> ChildVisit for UserChildren<'_, V> {
+ #[inline]
+ fn visit_child(&mut self, child: TVMFFIAny, def_region_kind:
DefRegionKind) -> NativeResult {
+ if child.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+ return Ok(());
+ }
+ match self
+ .visitor
+ .visit(&VisitValue::from_raw(child), def_region_kind)
+ {
+ Ok(None) => Ok(()),
+ Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
+ Err(error) => Err(NativeHalt::Error(error)),
+ }
+ }
+}
+
+/// Recurse into `value` on behalf of `visitor`: fire its enter hook, walk the
+/// children, fire its exit hook. The engine below is stateless — these are
+/// free functions, with the only shared piece (the `__s_visit__` attribute
+/// column) cached process-wide.
+fn visit_raw<V: NativeVisit>(
+ value: TVMFFIAny,
+ visitor: &mut V,
+ def_region_kind: DefRegionKind,
+) -> NativeResult {
+ if value.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+ return Ok(());
+ }
+
+ let visit_value = VisitValue::from_raw(value);
+ // Single by-value matches: splitting the Result match from the
+ // WalkResult match leaves a partially-moved temporary whose drop glue
+ // the compiler cannot fold away (measurably so on the container fast
+ // path).
+ match visitor.enter(&visit_value, def_region_kind) {
+ Ok(WalkResult::Advance) => {}
+ Ok(WalkResult::Skip) => return Ok(()),
+ Ok(WalkResult::Interrupt) => return
Err(NativeHalt::Interrupt(Any::new())),
+ Ok(WalkResult::InterruptWith(payload)) => return
Err(NativeHalt::Interrupt(payload)),
+ Err(error) => return Err(with_value_context(error.into(), value)),
+ }
+
+ let children = &mut WalkChildren {
+ visitor: &mut *visitor,
+ };
+ if let Err(halt) = visit_children_raw(value, children, def_region_kind) {
+ return Err(with_value_context(halt, value));
+ }
+
+ match visitor.exit(&visit_value, def_region_kind) {
+ Ok(WalkResult::Interrupt) => Err(NativeHalt::Interrupt(Any::new())),
+ Ok(WalkResult::InterruptWith(payload)) =>
Err(NativeHalt::Interrupt(payload)),
+ Ok(WalkResult::Advance | WalkResult::Skip) => Ok(()),
+ Err(error) => Err(with_value_context(error.into(), value)),
+ }
+}
+
+#[inline]
+fn visit_children_raw<C: ChildVisit>(
+ value: TVMFFIAny,
+ visitor: &mut C,
+ def_region_kind: DefRegionKind,
+) -> NativeResult {
+ match value.type_index {
+ x if x == TVMFFITypeIndex::kTVMFFIArray as i32
+ || x == TVMFFITypeIndex::kTVMFFIList as i32 =>
+ {
+ return visit_sequence(value, visitor, def_region_kind);
+ }
+ x if x == TVMFFITypeIndex::kTVMFFIMap as i32
+ || x == TVMFFITypeIndex::kTVMFFIDict as i32 =>
+ {
+ // Fast path: read the MapBaseObj storage layout directly, like
+ // the SeqPrefix path for arrays — zero FFI calls per entry.
+ // Dict entries are snapshotted first to keep the re-entrant
+ // mutation guard. If the one-time layout validation fails
+ // (e.g. an ABI-debug build), fall back to the packed-functor
+ // iteration protocol.
+ if map_layout_usable(value) {
+ let snapshot = x == TVMFFITypeIndex::kTVMFFIDict as i32;
+ return visit_map_layout(value, visitor, def_region_kind,
snapshot);
+ }
+ return visit_map(value, visitor, def_region_kind);
+ }
+ _ => {}
+ }
+
+ reject_foreign_structural_visit(value.type_index)?;
+ if value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ Ok(())
+ } else {
+ visit_reflected_fields(value, visitor, def_region_kind)
+ }
+}
+
+#[inline(never)]
+fn visit_sequence<C: ChildVisit>(
+ value: TVMFFIAny,
+ visitor: &mut C,
+ def_region_kind: DefRegionKind,
+) -> NativeResult {
+ let seq = unsafe { &*(value.data_union.v_obj as *const SeqPrefix) };
+ if seq.size < 0 {
+ return Err(runtime_error("native visitor: sequence reports a negative
size").into());
+ }
+ if seq.data.is_null() && seq.size != 0 {
+ return Err(
+ runtime_error("native visitor: non-empty sequence has a null data
pointer").into(),
+ );
+ }
+ let size = usize::try_from(seq.size)
+ .map_err(|_| runtime_error("native visitor: sequence size does not fit
usize"))?;
+ if size == 0 {
+ return Ok(());
+ }
+
+ if value.type_index == TVMFFITypeIndex::kTVMFFIList as i32 {
+ // List storage may be invalidated by a re-entrant callback. Own a
+ // snapshot before running the first callback.
+ let children: Vec<Any> = {
+ let cells = unsafe { std::slice::from_raw_parts(seq.data, size) };
+ cells
+ .iter()
+ .map(|cell| Any::from(unsafe { view_of(cell) }))
+ .collect()
+ };
+ for (index, child) in children.into_iter().enumerate() {
+ let raw = raw_of_owned(&child);
+ visitor
+ .visit_child(raw, def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("sequence
item [{index}]")))?;
+ }
+ return Ok(());
+ }
+
+ // Array is immutable, so its element cells remain stable throughout
+ // recursive callbacks and need no refcounted snapshot.
+ let cells = unsafe { std::slice::from_raw_parts(seq.data, size) };
+ for (index, child) in cells.iter().enumerate() {
+ visitor
+ .visit_child(*child, def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("sequence item
[{index}]")))?;
+ }
+ Ok(())
+}
+
+/// Walk map/dict entries by reading the `MapBaseObj` storage directly —
+/// the map analog of the `SeqPrefix` array fast path. `snapshot` first
+/// takes owned copies of all entries (Dict re-entrant mutation guard).
+#[inline(never)]
+fn visit_map_layout<C: ChildVisit>(
+ value: TVMFFIAny,
+ visitor: &mut C,
+ def_region_kind: DefRegionKind,
+ snapshot: bool,
+) -> NativeResult {
+ let map = unsafe { &*(value.data_union.v_obj as *const MapPrefix) };
+ let size = map.size as usize;
+ if size == 0 {
+ return Ok(());
+ }
+ let mut cursor = unsafe { MapCursor::new(map) };
+
+ if snapshot {
+ let mut entries: Vec<(Any, Any)> = Vec::with_capacity(size);
+ for _ in 0..size {
+ let Some((key, val)) = (unsafe { cursor.next() }) else {
+ return Err(runtime_error("native visitor: map iteration ended
early").into());
+ };
+ entries.push((
+ Any::from(unsafe { view_of(&key) }),
+ Any::from(unsafe { view_of(&val) }),
+ ));
+ }
+ for (index, (key, val)) in entries.into_iter().enumerate() {
+ visitor
+ .visit_child(raw_of_owned(&key), def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("dict key
[{index}]")))?;
+ visitor
+ .visit_child(raw_of_owned(&val), def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("dict value
[{index}]")))?;
+ }
+ return Ok(());
+ }
+
+ // Immutable map: entry cells stay stable throughout recursive
+ // callbacks, so visit them in place. The `size` bound also guards the
+ // dense iteration list against corruption-induced cycles.
+ for index in 0..size {
+ let Some((key, val)) = (unsafe { cursor.next() }) else {
+ return Err(runtime_error("native visitor: map iteration ended
early").into());
+ };
+ visitor
+ .visit_child(key, def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("map key
[{index}]")))?;
+ visitor
+ .visit_child(val, def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("map value
[{index}]")))?;
+ }
+ Ok(())
+}
+
+/// Cold fallback used when the mirrored layout fails validation (e.g. an
+/// ABI-debug build): iterate through the public packed functors. Map storage
+/// is private C++; the Rust binding itself uses these iterator functors, so
+/// no structural visiting or traversal control leaves Rust. Entries are
+/// snapshotted before user callbacks run — required for Dict, whose mutation
+/// invalidates the iterator, and harmless for immutable Map on this
+/// non-performance path.
+fn visit_map<C: ChildVisit>(
+ value: TVMFFIAny,
+ visitor: &mut C,
+ def_region_kind: DefRegionKind,
+) -> NativeResult {
+ let is_dict = value.type_index == TVMFFITypeIndex::kTVMFFIDict as i32;
+ let (size_name, iter_name, kind) = if is_dict {
+ ("ffi.DictSize", "ffi.DictForwardIterFunctor", "dict")
+ } else {
+ ("ffi.MapSize", "ffi.MapForwardIterFunctor", "map")
+ };
+ let size = Function::get_global(size_name)?
+ .call_packed(&[unsafe { view_of(&value) }])
+ .and_then(i64::try_from)?;
+ if size < 0 {
+ return Err(runtime_error("native visitor: map reports a negative
size").into());
+ }
+ let size = usize::try_from(size)
+ .map_err(|_| runtime_error("native visitor: map size does not fit
usize"))?;
+ if size == 0 {
+ return Ok(());
+ }
+
+ let iter_any = Function::get_global(iter_name)?.call_packed(&[unsafe {
view_of(&value) }])?;
+ let iter = Function::try_from(iter_any)?;
+
+ let mut entries = Vec::with_capacity(size);
+ for index in 0..size {
+ let key = iter.call_packed(&[AnyView::from(&0i64)])?;
+ let map_value = iter.call_packed(&[AnyView::from(&1i64)])?;
+ entries.push((key, map_value));
+ if index + 1 != size {
+ iter.call_packed(&[AnyView::from(&2i64)])?;
+ }
+ }
+
+ for (index, (key, map_value)) in entries.into_iter().enumerate() {
+ visitor
+ .visit_child(raw_of_owned(&key), def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("{kind} key
[{index}]")))?;
+ visitor
+ .visit_child(raw_of_owned(&map_value), def_region_kind)
+ .map_err(|halt| with_error_context(halt, &format!("{kind} value
[{index}]")))?;
+ }
+ Ok(())
+}
+
+#[inline]
+fn visit_reflected_fields<C: ChildVisit>(
+ value: TVMFFIAny,
+ visitor: &mut C,
+ def_region_kind: DefRegionKind,
+) -> NativeResult {
+ let type_info = unsafe { TVMFFIGetTypeInfo(value.type_index) };
+ if type_info.is_null() {
+ return Err(runtime_error(&format!(
+ "native visitor: unregistered type index {}",
+ value.type_index
+ ))
+ .into());
+ }
+ let seq_hash_kind = unsafe {
+ let metadata = (*type_info).metadata;
+ if metadata.is_null() {
+ TVMFFISEqHashKind::kTVMFFISEqHashKindUnsupported as i32
+ } else {
+ (*metadata).structural_eq_hash_kind
+ }
+ };
+ let def_region_kind = free_var_child_region(def_region_kind,
seq_hash_kind);
+ let object = unsafe { value.data_union.v_obj } as *mut u8;
+ let halted = unsafe {
+ for_each_field(value.type_index, |field| {
+ match visit_reflected_field(object, field, visitor,
def_region_kind) {
+ Ok(()) => ControlFlow::Continue(()),
+ Err(halt) => ControlFlow::Break(halt),
+ }
+ })
+ };
+ halted.map_or(Ok(()), Err)
+}
+
+unsafe fn visit_reflected_field<C: ChildVisit>(
+ object: *mut u8,
+ field: &TVMFFIFieldInfo,
+ visitor: &mut C,
+ inherited_region: DefRegionKind,
+) -> NativeResult {
+ if field.flags & FLAG_SEQ_HASH_IGNORE != 0 {
+ return Ok(());
+ }
+
+ let Some(getter) = field.getter else {
+ return Err(NativeHalt::Error(runtime_error(&format!(
+ "native visitor: reflected field `{}` has no getter",
+ field.name.as_str()
+ ))));
+ };
+ let address = object.offset(field.offset as isize) as *mut c_void;
+ let mut child_raw = TVMFFIAny::new();
+ if getter(address, &mut child_raw) != 0 {
+ return Err(with_error_context(
+ NativeHalt::Error(Error::from_raised()),
+ &format!("field `{}`", field.name.as_str()),
+ ));
+ }
+
+ // A reflection getter returns an owned Any. Keep it alive while the
+ // recursive walk borrows its raw cell.
+ let child = Any::from_raw_ffi_any(child_raw);
+ let borrowed = raw_of_owned(&child);
+ let child_region = field_def_region(field, inherited_region);
+ visitor
+ .visit_child(borrowed, child_region)
+ .map_err(|halt| with_error_context(halt, &format!("field `{}`",
field.name.as_str())))
+}
+
+// Runs once per visited value: keep the no-hook fast path small enough to
+// actually inline (one cached-column load and a tag compare) and the error
+// formatting out of line — with the cold body inside, the `#[inline]` hint
+// was declined and the call cost ~20% of the container fast path.
+#[inline]
+fn reject_foreign_structural_visit(type_index: i32) -> Result<()> {
+ let Some(attr) = structural_visit_column().and_then(|column|
column.get(type_index)) else {
+ return Ok(());
+ };
+ if attr.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+ return Ok(());
+ }
+ reject_foreign_structural_visit_cold(type_index, attr.type_index)
+}
+
+#[cold]
+#[inline(never)]
+fn reject_foreign_structural_visit_cold(type_index: i32, attr_type_index: i32)
-> Result<()> {
+ if attr_type_index == TVMFFITypeIndex::kTVMFFIOpaquePtr as i32
+ || attr_type_index == TVMFFITypeIndex::kTVMFFIFunction as i32
+ {
+ let value_type = if type_index <
TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ format!("type index {type_index}")
+ } else {
+ format!("type `{}`", type_key_of(type_index))
+ };
+ Err(runtime_error(&format!(
+ "native visitor: {value_type} registers foreign
`{STRUCTURAL_VISIT_ATTR}`; \
+ visit its children explicitly from a `StructuralVisitor` \
+ (`structural_visit`), or skip it with a pre-order
`WalkResult::Skip` \
+ handler"
+ )))
+ } else {
+ Err(Error::new(
+ TYPE_ERROR,
+ &format!("{STRUCTURAL_VISIT_ATTR} must be an opaque function
pointer or ffi.Function"),
+ "",
+ ))
+ }
+}
+
+fn with_value_context(halt: NativeHalt, value: TVMFFIAny) -> NativeHalt {
+ if value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ halt
+ } else {
+ with_error_context(halt, &format!("object `{}`",
type_key_of(value.type_index)))
+ }
+}
+
+/// Visit `root` with a user-driven [`StructuralVisitor`].
+///
+/// The visitor's [`StructuralVisitor::visit`] runs for the root under
+/// [`DefRegionKind::None`] and controls all further recursion itself. This is
+/// the Rust analog of constructing a C++ `StructuralVisitorObj` and calling
+/// `visitor->Visit(root)`. An FFI `None` root completes immediately.
+pub fn structural_visit<R, V>(root: &R, visitor: &mut V) ->
Result<Option<VisitInterrupt>>
+where
+ V: StructuralVisitor,
+ for<'x> AnyView<'x>: From<&'x R>,
+{
+ visitor.visit_child(root, DefRegionKind::None)
+}
+
+/// Walk `root` with an observer, the Rust analog of C++
+/// `StructuralWalk<order>(root, callbacks...)`.
+///
+/// `walker` is anything implementing [`IntoWalker`]: a `&mut` reference to a
+/// stateful [`VisitDispatch`] visitor (`#[dispatch(visit)]`), a bare closure
+/// in any [`WalkChainLink`] shape (catch-all `&VisitValue`, typed, or node,
+/// with an optional trailing [`DefRegionKind`]), or a tuple of such
+/// callbacks tried in order — the C++ callback overloads and variadic
+/// chain. The walker owns recursion: the handler runs once per value,
+/// before or after the value's children according to `order`, and steers
+/// traversal through the returned [`WalkResult`].
+pub fn structural_walk<R, M, H>(
+ root: &R,
+ walker: H,
+ order: WalkOrder,
+) -> Result<Option<VisitInterrupt>>
+where
+ H: IntoWalker<M>,
+ for<'x> AnyView<'x>: From<&'x R>,
+{
+ let mut dispatch = walker.into_walker(order);
+ finish(visit_raw(
+ raw_of(AnyView::from(root)),
+ &mut dispatch,
+ DefRegionKind::None,
+ ))
+}
+
+fn finish(result: NativeResult) -> Result<Option<VisitInterrupt>> {
+ match result {
+ Ok(()) => Ok(None),
+ Err(NativeHalt::Error(error)) => Err(error),
+ Err(NativeHalt::Interrupt(payload)) => Ok(Some(VisitInterrupt { value:
payload })),
+ }
+}
+
+#[inline]
+fn field_def_region(field: &TVMFFIFieldInfo, inherited: DefRegionKind) ->
DefRegionKind {
+ if field.flags & FLAG_SEQ_HASH_DEF_NON_RECURSIVE != 0 {
+ DefRegionKind::NonRecursive
+ } else if field.flags & FLAG_SEQ_HASH_DEF_RECURSIVE != 0 {
+ DefRegionKind::Recursive
+ } else {
+ inherited
+ }
+}
+
+/// A non-recursive definition applies to a FreeVar value itself, but not to
+/// the FreeVar's own reflected children: nested free vars there must resolve
+/// against an outer binding instead of rebinding. Mirrors C++
+/// `VisitReflectedFieldsExpected`.
+#[inline]
+fn free_var_child_region(inherited: DefRegionKind, structural_eq_hash_kind:
i32) -> DefRegionKind {
+ if inherited == DefRegionKind::NonRecursive
+ && structural_eq_hash_kind ==
TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32
+ {
+ DefRegionKind::None
+ } else {
+ inherited
+ }
+}
+
+fn with_error_context(halt: NativeHalt, frame: &str) -> NativeHalt {
+ match halt {
+ NativeHalt::Error(error) =>
NativeHalt::Error(Error::with_appended_backtrace(
+ error,
+ &format!("[native structural visit] {frame}\n"),
+ )),
+ interrupt => interrupt,
+ }
+}
+
+fn runtime_error(message: &str) -> Error {
+ Error::new(RUNTIME_ERROR, message, "")
+}
+
+/// Layout prefix shared by the C++ `MapObj` and `DictObj` (`MapBaseObj`,
+/// release ABI without `TVM_FFI_DEBUG_WITH_ABI_CHANGE`).
+#[repr(C)]
+struct MapPrefix {
+ _header: TVMFFIObject,
+ data: *mut u8,
+ size: u64,
+ slots: u64,
+ _data_deleter: Option<unsafe extern "C" fn(*mut c_void)>,
+}
+
+/// Dense-layout extension of the prefix (`DenseMapBaseObj`).
+#[repr(C)]
+struct DenseMapPrefix {
+ base: MapPrefix,
+ fib_shift: u32,
+ iter_list_head: u64,
+ iter_list_tail: u64,
+}
+
+const _: () = {
+ assert!(std::mem::offset_of!(MapPrefix, data) == 24);
+ assert!(std::mem::offset_of!(MapPrefix, size) == 32);
+ assert!(std::mem::offset_of!(MapPrefix, slots) == 40);
+ assert!(std::mem::offset_of!(MapPrefix, _data_deleter) == 48);
+ assert!(std::mem::offset_of!(DenseMapPrefix, fib_shift) == 56);
+ assert!(std::mem::offset_of!(DenseMapPrefix, iter_list_head) == 64);
+};
+
+/// MSB tag on `slots_` marking the small (inline KV array) layout.
+const MAP_SMALL_TAG: u64 = 1 << 63;
+/// `kInvalidIndex`: terminator of the dense iteration list.
+const MAP_INVALID_INDEX: u64 = u64::MAX;
+/// `kBlockCap`: entries per dense block.
+const MAP_BLOCK_CAP: u64 = 16;
+/// `sizeof(ItemType)`: KV pair (32 bytes) + prev/next indices (16 bytes).
+const MAP_ITEM_SIZE: usize = 48;
+/// `sizeof(Block)`: `kBlockCap` metadata bytes + `kBlockCap` items.
+const MAP_BLOCK_SIZE: usize = 16 + 16 * MAP_ITEM_SIZE;
+/// Byte offset of `ItemType::next` (after the 32-byte KV pair and `prev`).
+const MAP_ITEM_NEXT_OFFSET: usize = 40;
+
+/// Borrowed traversal cursor over either map storage layout, yielding entries
+/// in the same order as the C++ iterator.
+enum MapCursor {
+ Small {
+ kv: *const TVMFFIAny,
+ index: usize,
+ size: usize,
+ },
+ Dense {
+ data: *const u8,
+ index: u64,
+ },
+}
+
+impl MapCursor {
+ #[inline]
+ unsafe fn new(map: &MapPrefix) -> MapCursor {
+ if map.slots & MAP_SMALL_TAG != 0 {
+ MapCursor::Small {
+ kv: map.data as *const TVMFFIAny,
+ index: 0,
+ size: map.size as usize,
+ }
+ } else {
+ let dense = &*(map as *const MapPrefix as *const DenseMapPrefix);
+ MapCursor::Dense {
+ data: map.data,
+ index: dense.iter_list_head,
+ }
+ }
+ }
+
+ #[inline]
+ unsafe fn next(&mut self) -> Option<(TVMFFIAny, TVMFFIAny)> {
+ match self {
+ MapCursor::Small { kv, index, size } => {
+ if *index >= *size {
+ return None;
+ }
+ let pair = kv.add(*index * 2);
+ *index += 1;
+ Some((*pair, *pair.add(1)))
+ }
+ MapCursor::Dense { data, index } => {
+ if *index == MAP_INVALID_INDEX {
+ return None;
+ }
+ let block = data.add((*index / MAP_BLOCK_CAP) as usize *
MAP_BLOCK_SIZE);
+ let item = block.add(
+ MAP_BLOCK_CAP as usize + (*index % MAP_BLOCK_CAP) as usize
* MAP_ITEM_SIZE,
+ );
+ let key = *(item as *const TVMFFIAny);
+ let val = *(item.add(16) as *const TVMFFIAny);
+ *index = *(item.add(MAP_ITEM_NEXT_OFFSET) as *const u64);
+ Some((key, val))
+ }
+ }
+ }
+}
+
+/// Process-wide result of the one-time map layout validation:
+/// 0 = unknown, 1 = usable, 2 = unusable.
+static MAP_LAYOUT_STATE: AtomicU8 = AtomicU8::new(0);
+
+#[inline]
+fn map_layout_usable(value: TVMFFIAny) -> bool {
+ match MAP_LAYOUT_STATE.load(Ordering::Relaxed) {
+ 1 => true,
+ 2 => false,
+ _ => {
+ let usable = validate_map_layout(value);
+ MAP_LAYOUT_STATE.store(if usable { 1 } else { 2 },
Ordering::Relaxed);
+ usable
+ }
+ }
+}
+
+/// Cross-check the mirrored `MapBaseObj` layout against the public size
+/// functor once per process. An ABI-debug build inserts a state marker that
+/// shifts every field by 8 bytes, which this detects: offset 32 then holds a
+/// pointer value that cannot equal the reported entry count.
+fn validate_map_layout(value: TVMFFIAny) -> bool {
+ let expected = (|| -> Result<i64> {
+ let is_dict = value.type_index == TVMFFITypeIndex::kTVMFFIDict as i32;
+ let name = if is_dict {
+ "ffi.DictSize"
+ } else {
+ "ffi.MapSize"
+ };
+ Function::get_global(name)?
+ .call_packed(&[unsafe { view_of(&value) }])
+ .and_then(i64::try_from)
+ })();
+ let Ok(expected) = expected else {
+ return false;
+ };
+ let map = unsafe { &*(value.data_union.v_obj as *const MapPrefix) };
+ expected >= 0 && map.size == expected as u64
+}
+
+/// Layout prefix shared by the C++ `ArrayObj` and `ListObj`.
+#[repr(C)]
+struct SeqPrefix {
+ _header: TVMFFIObject,
+ data: *const TVMFFIAny,
+ size: i64,
+}
+
+const _: () = {
+ assert!(std::mem::offset_of!(SeqPrefix, data) == 24);
+ assert!(std::mem::offset_of!(SeqPrefix, size) == 32);
+};
+
+#[derive(Clone, Copy)]
+struct TypeAttrColumn(NonNull<TVMFFITypeAttrColumn>);
+
+impl TypeAttrColumn {
+ /// Copy one borrowed cell; ownership remains with the registry.
+ fn get(self, type_index: i32) -> Option<TVMFFIAny> {
+ unsafe {
+ let column = self.0.as_ref();
+ let index = type_index - column.begin_index;
+ if index < 0 || index >= column.size || column.data.is_null() {
+ None
+ } else {
+ Some(*column.data.offset(index as isize))
+ }
+ }
+ }
+}
+
+fn type_attr_column(attr_name: &str) -> Option<TypeAttrColumn> {
+ unsafe {
+ let attr_name = TVMFFIByteArray::from_str(attr_name);
+
NonNull::new(TVMFFIGetTypeAttrColumn(&attr_name).cast_mut()).map(TypeAttrColumn)
+ }
+}
+
+/// Cached `__s_visit__` column pointer (0 = not seen yet). A registry column
+/// is stable once created — C++ `DefaultVisitExpected` caches the same
+/// pointer in a function-local static — while an absent column is re-queried
+/// because a later attr registration may create it. The cache keeps the
+/// per-value foreign-hook check free of FFI lookups.
+static STRUCTURAL_VISIT_COLUMN: AtomicUsize = AtomicUsize::new(0);
+
+#[inline]
+fn structural_visit_column() -> Option<TypeAttrColumn> {
+ let cached = STRUCTURAL_VISIT_COLUMN.load(Ordering::Relaxed);
+ if cached != 0 {
+ let pointer = cached as *mut TVMFFITypeAttrColumn;
+ return Some(TypeAttrColumn(unsafe { NonNull::new_unchecked(pointer)
}));
+ }
+ let column = type_attr_column(STRUCTURAL_VISIT_ATTR)?;
+ STRUCTURAL_VISIT_COLUMN.store(column.0.as_ptr() as usize,
Ordering::Relaxed);
+ Some(column)
+}
+
+fn type_key_of(type_index: i32) -> String {
+ unsafe {
+ let info = TVMFFIGetTypeInfo(type_index);
+ if info.is_null() {
+ format!("<type_index {type_index}>")
+ } else {
+ (*info).type_key.as_str().to_string()
+ }
+ }
+}
+
+/// Subtype check with the base's inheritance depth supplied by the caller
+/// (`ObjectCore::TYPE_DEPTH`), so only the object's type info is fetched.
+#[inline]
+fn is_instance_at_depth(object_type_index: i32, base_type_index: i32,
base_depth: i32) -> bool {
+ if object_type_index == base_type_index {
+ return true;
+ }
+ unsafe {
+ let info = TVMFFIGetTypeInfo(object_type_index);
+ if info.is_null() {
+ return false;
+ }
+ if (*info).type_depth <= base_depth {
+ return false;
+ }
+ let ancestors = (*info).type_acenstors;
+ if ancestors.is_null() {
+ return false;
+ }
+ let ancestor = *ancestors.offset(base_depth as isize);
+ !ancestor.is_null() && (*ancestor).type_index == base_type_index
+ }
+}
+
+/// Visit every reflected field of `type_index` and its ancestors in the same
+/// parent-to-child order as C++ `ForEachFieldInfoWithEarlyStop`.
+///
+/// # Safety
+///
+/// `type_index` must be a registered type index.
+unsafe fn for_each_field<B>(
+ type_index: i32,
+ mut callback: impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
+) -> Option<B> {
+ let info = TVMFFIGetTypeInfo(type_index);
+ if info.is_null() {
+ return None;
+ }
+
+ // Ancestor slot 0 is the root Object. C++ starts at slot 1, walks toward
+ // the immediate parent, then visits the concrete type's own fields.
+ for depth in 1..(*info).type_depth {
+ let ancestor = *(*info).type_acenstors.offset(depth as isize);
+ if let Some(value) = visit_field_level(ancestor, &mut callback) {
+ return Some(value);
+ }
+ }
+ visit_field_level(info, &mut callback)
+}
+
+unsafe fn visit_field_level<B>(
+ info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
+ callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
+) -> Option<B> {
+ if info.is_null() || (*info).fields.is_null() {
+ return None;
+ }
+ let fields = std::slice::from_raw_parts((*info).fields, (*info).num_fields
as usize);
+ for field in fields {
+ // C reflection tables are immortal once registered.
+ let field: &'static TVMFFIFieldInfo = &*(field as *const
TVMFFIFieldInfo);
+ if let ControlFlow::Break(value) = callback(field) {
+ return Some(value);
+ }
+ }
+ None
+}
+
+#[inline]
+fn raw_of(view: AnyView<'_>) -> TVMFFIAny {
+ *view.as_raw_ffi_any()
+}
+
+#[inline]
+fn raw_of_owned(any: &Any) -> TVMFFIAny {
+ *any.as_raw_ffi_any()
+}
+
+#[inline]
+unsafe fn view_of(raw: &TVMFFIAny) -> AnyView<'_> {
+ unsafe { AnyView::from_raw_ffi_any(*raw) }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::Array;
+
+ struct RegionProbe(Vec<DefRegionKind>);
+
+ impl NativeVisit for RegionProbe {
+ fn enter(
+ &mut self,
+ _value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<WalkResult> {
+ self.0.push(def_region_kind);
+ Ok(WalkResult::Advance)
+ }
+ }
+
+ #[derive(Default)]
+ struct TypedRegionProbe {
+ seen: Vec<DefRegionKind>,
+ }
+
+ #[crate::dispatch(visit)]
+ impl TypedRegionProbe {
+ fn visit_integer(&mut self, _value: i64, def_region_kind:
DefRegionKind) -> WalkResult {
+ self.seen.push(def_region_kind);
+ WalkResult::Advance
+ }
+ }
+
+ unsafe extern "C" fn clone_any_field(field: *mut c_void, result: *mut
TVMFFIAny) -> i32 {
+ let value = &*(field as *const Any);
+ *result = Any::into_raw_ffi_any(value.clone());
+ 0
+ }
+
+ #[test]
+ fn def_region_is_inherited_through_containers() {
+ let root = Array::new(vec![1i64, 2]);
+ let mut probe = RegionProbe(Vec::new());
+ assert!(visit_raw(
+ raw_of(AnyView::from(&root)),
+ &mut probe,
+ DefRegionKind::Recursive,
+ )
+ .is_ok());
+ assert_eq!(probe.0, vec![DefRegionKind::Recursive; 3]);
+ }
+
+ #[test]
+ fn reflected_field_def_region_reaches_typed_handler() {
+ let mut probe = TypedRegionProbe::default();
+ let mut dispatch = (&mut probe).into_walker(WalkOrder::PreOrder);
+ let mut value = Any::from(7i64);
+ let mut field: TVMFFIFieldInfo = unsafe { std::mem::zeroed() };
+ field.name = unsafe { TVMFFIByteArray::from_str("value") };
+ field.getter = Some(clone_any_field);
+ let object = (&mut value as *mut Any).cast::<u8>();
+
+ let mut children = WalkChildren {
+ visitor: &mut dispatch,
+ };
+ for flags in [
+ FLAG_SEQ_HASH_DEF_RECURSIVE,
+ 0,
+ FLAG_SEQ_HASH_DEF_NON_RECURSIVE,
+ FLAG_SEQ_HASH_DEF_NON_RECURSIVE | FLAG_SEQ_HASH_DEF_RECURSIVE,
+ FLAG_SEQ_HASH_IGNORE,
+ ] {
+ field.flags = flags;
+ assert!(unsafe {
+ visit_reflected_field(object, &field, &mut children,
DefRegionKind::None)
+ }
+ .is_ok());
+ }
+ assert_eq!(
+ probe.seen,
+ vec![
+ DefRegionKind::Recursive,
+ DefRegionKind::None,
+ DefRegionKind::NonRecursive,
+ DefRegionKind::NonRecursive,
+ ]
+ );
+ }
+
+ #[test]
+ fn non_recursive_region_is_clamped_for_free_var_children_only() {
+ use TVMFFISEqHashKind::{kTVMFFISEqHashKindFreeVar,
kTVMFFISEqHashKindTreeNode};
+
+ let free_var = kTVMFFISEqHashKindFreeVar as i32;
+ let tree_node = kTVMFFISEqHashKindTreeNode as i32;
+ assert_eq!(
+ free_var_child_region(DefRegionKind::NonRecursive, free_var),
+ DefRegionKind::None
+ );
+ assert_eq!(
+ free_var_child_region(DefRegionKind::Recursive, free_var),
+ DefRegionKind::Recursive
+ );
+ assert_eq!(
+ free_var_child_region(DefRegionKind::None, free_var),
+ DefRegionKind::None
+ );
+ assert_eq!(
+ free_var_child_region(DefRegionKind::NonRecursive, tree_node),
+ DefRegionKind::NonRecursive
+ );
+ }
+}
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 12492308..86579857 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -46,13 +46,18 @@ pub use crate::error::{
ATTRIBUTE_ERROR, INDEX_ERROR, KEY_ERROR, RUNTIME_ERROR, TYPE_ERROR,
VALUE_ERROR,
};
pub use crate::extra::module::Module;
+pub use crate::extra::structural_visit::{
+ structural_visit, structural_walk, DefRegionKind, IntoVisitResult,
IntoWalker,
+ StructuralVisitor, VisitDispatch, VisitInterrupt, VisitValue,
WalkChainLink, WalkOrder,
+ WalkResult,
+};
pub use crate::function::Function;
pub use crate::object::ObjectRefCast;
pub use crate::object::{Object, ObjectArc, ObjectCore,
ObjectCoreWithExtraItems, ObjectRefCore};
pub use crate::optional::Optional;
pub use crate::string::{Bytes, String};
pub use crate::type_traits::AnyCompatible;
-pub use tvm_ffi_macros::match_any;
+pub use tvm_ffi_macros::{dispatch, match_any};
pub use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
pub use tvm_ffi_sys::{
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs
b/rust/tvm-ffi/tests/test_structural_visit.rs
new file mode 100644
index 00000000..723de807
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -0,0 +1,873 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use tvm_ffi::tvm_ffi_sys::{TVMFFIByteArray, TVMFFITypeIndex,
TVMFFITypeRegisterAttr};
+use tvm_ffi::{
+ dispatch, structural_visit, structural_walk, Any, AnyView, Array,
DefRegionKind, Error,
+ Function, Map, Object, Result, Shape, String as FfiString,
StructuralVisitor, VisitInterrupt,
+ VisitValue, WalkOrder, WalkResult, RUNTIME_ERROR,
+};
+
+fn runtime_error(message: &str) -> Error {
+ Error::new(RUNTIME_ERROR, message, "")
+}
+
+#[test]
+fn plain_walk_uses_native_sequence_fallback() {
+ let root = Array::new(vec![1i64, 2, 3]);
+ let mut integers = 0;
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if value.cast::<i64>().is_some() {
+ integers += 1;
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(integers, 3);
+}
+
+#[test]
+fn plain_walk_uses_native_map_fallback() {
+ let root: Map<FfiString, i64> = [(FfiString::from("a"), 1i64),
(FfiString::from("b"), 2i64)]
+ .into_iter()
+ .collect();
+ let mut integers = 0;
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if value.cast::<i64>().is_some() {
+ integers += 1;
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(integers, 2);
+}
+
+#[derive(Default)]
+struct SkipForeignShape {}
+
+#[dispatch(visit)]
+impl SkipForeignShape {
+ fn visit_shape(&mut self, _shape: Shape) -> WalkResult {
+ WalkResult::Skip
+ }
+}
+
+/// Visitor-layer handling of the foreign type: `visit` enumerates the
+/// children itself (none, for a shape) instead of the default recursion.
+#[derive(Default)]
+struct ForeignShapeVisitor {
+ shapes: usize,
+}
+
+impl StructuralVisitor for ForeignShapeVisitor {
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ if value.cast::<Shape>().is_some() {
+ self.shapes += 1;
+ return Ok(None);
+ }
+ self.default_visit_children(value, def_region_kind)
+ }
+}
+
+#[test]
+fn foreign_structural_visit_requires_explicit_rust_override() {
+ let hook = Function::get_global("ffi.ArraySize").unwrap();
+ let attr_name = unsafe { TVMFFIByteArray::from_str("__s_visit__") };
+ let mut attr_value = Any::from(hook);
+ assert_eq!(
+ unsafe {
+ TVMFFITypeRegisterAttr(
+ TVMFFITypeIndex::kTVMFFIShape as i32,
+ &attr_name,
+ Any::as_data_ptr(&mut attr_value),
+ )
+ },
+ 0
+ );
+
+ let root = Shape::from([2i64, 3]);
+ let error = match structural_walk(
+ &root,
+ |_value: &VisitValue| WalkResult::Advance,
+ WalkOrder::PreOrder,
+ ) {
+ Err(error) => error,
+ Ok(_) => panic!("foreign structural visit unexpectedly used
reflection"),
+ };
+ assert!(error.message().contains("registers foreign `__s_visit__`"));
+ assert!(error.message().contains("StructuralVisitor"));
+
+ // Walk layer: a pre-order handler skips the foreign type.
+ assert!(
+ structural_walk(&root, &mut SkipForeignShape::default(),
WalkOrder::PreOrder)
+ .unwrap()
+ .is_none()
+ );
+
+ // Visitor layer: take over the type's children explicitly instead.
+ let mut takeover = ForeignShapeVisitor::default();
+ assert!(structural_visit(&root, &mut takeover).unwrap().is_none());
+ assert_eq!(takeover.shapes, 1);
+}
+
+#[test]
+fn mutable_list_is_snapshotted_before_callbacks() {
+ let root = Function::get_global("ffi.List")
+ .unwrap()
+ .call_packed(&[AnyView::from(&1i64), AnyView::from(&2i64)])
+ .unwrap();
+ let captured = root.clone();
+ let append = Function::get_global("ffi.ListAppend").unwrap();
+ let mut appended = false;
+ let mut integers = Vec::new();
+
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if let Some(integer) = value.cast::<i64>() {
+ integers.push(integer);
+ if !appended {
+ append
+ .call_packed(&[AnyView::from(&captured),
AnyView::from(&3i64)])
+ .unwrap();
+ appended = true;
+ }
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+
+ assert_eq!(integers, vec![1, 2]);
+ let size = Function::get_global("ffi.ListSize")
+ .unwrap()
+ .call_packed(&[AnyView::from(&root)])
+ .and_then(i64::try_from)
+ .unwrap();
+ assert_eq!(size, 3);
+}
+
+#[test]
+fn mutable_dict_is_snapshotted_before_callbacks() {
+ let root = Function::get_global("ffi.Dict")
+ .unwrap()
+ .call_packed(&[
+ AnyView::from(&FfiString::from("a")),
+ AnyView::from(&1i64),
+ AnyView::from(&FfiString::from("b")),
+ AnyView::from(&2i64),
+ ])
+ .unwrap();
+ let captured = root.clone();
+ let set_item = Function::get_global("ffi.DictSetItem").unwrap();
+ let mut inserted = false;
+ let mut integers = Vec::new();
+
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if let Some(integer) = value.cast::<i64>() {
+ integers.push(integer);
+ if !inserted {
+ set_item
+ .call_packed(&[
+ AnyView::from(&captured),
+ AnyView::from(&FfiString::from("c")),
+ AnyView::from(&3i64),
+ ])
+ .unwrap();
+ inserted = true;
+ }
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+
+ integers.sort_unstable();
+ assert_eq!(integers, vec![1, 2]);
+ let size = Function::get_global("ffi.DictSize")
+ .unwrap()
+ .call_packed(&[AnyView::from(&root)])
+ .and_then(i64::try_from)
+ .unwrap();
+ assert_eq!(size, 3);
+}
+
+#[test]
+fn dense_map_layout_is_traversed_completely() {
+ // More than 4 entries forces the dense (block + iteration list) layout.
+ let root: Map<FfiString, i64> = (0..9)
+ .map(|i| (FfiString::from(format!("k{i}")), i as i64))
+ .collect();
+ let mut sum = 0;
+ let mut strings = 0;
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if let Some(integer) = value.cast::<i64>() {
+ sum += integer;
+ } else if value.cast::<FfiString>().is_some() {
+ strings += 1;
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(sum, (0..9).sum::<i64>());
+ assert_eq!(strings, 9);
+}
+
+#[test]
+fn interrupt_payload_crosses_map_traversal() {
+ let root: Map<FfiString, i64> = [(FfiString::from("a"), 1i64),
(FfiString::from("b"), 2i64)]
+ .into_iter()
+ .collect();
+ let outcome = structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if value.cast::<i64>().is_some() {
+ return WalkResult::interrupt_with(99i64);
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap();
+ let Some(interrupt) = outcome else {
+ panic!("map walk unexpectedly completed");
+ };
+ assert_eq!(i64::try_from(interrupt.value).unwrap(), 99);
+}
+
+#[test]
+fn handler_error_crosses_map_traversal() {
+ let root: Map<FfiString, i64> = [(FfiString::from("a"),
1i64)].into_iter().collect();
+ let error = match structural_walk(
+ &root,
+ |value: &VisitValue| -> Result<WalkResult> {
+ if value.cast::<i64>().is_some() {
+ Err(runtime_error("map handler failed"))
+ } else {
+ Ok(WalkResult::Advance)
+ }
+ },
+ WalkOrder::PreOrder,
+ ) {
+ Err(error) => error,
+ Ok(_) => panic!("map handler unexpectedly succeeded"),
+ };
+ assert_eq!(error.message(), "map handler failed");
+ assert!(error.backtrace().contains("object `ffi.Map`"));
+}
+
+#[test]
+fn interrupt_stops_without_running_remaining_callbacks() {
+ let root = Array::new(vec![1i64, 2, 3]);
+ let mut integers = 0;
+ let outcome = structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if value.cast::<i64>().is_some() {
+ integers += 1;
+ return WalkResult::Interrupt;
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap();
+ assert!(outcome.is_some());
+ assert_eq!(integers, 1);
+}
+
+/// Visitor-layer traversal that overrides the def-region for one child and
+/// inherits it for the next, mirroring a C++ visitor using
+/// `WithDefRegionKind`.
+#[derive(Default)]
+struct ManualRegionVisitor {
+ seen: Vec<DefRegionKind>,
+}
+
+impl StructuralVisitor for ManualRegionVisitor {
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ if let Some(array) = value.cast::<Array<i64>>() {
+ // Override the state for exactly this child's subtree...
+ let overridden = array.get(0).unwrap();
+ if let Some(interrupt) = self.visit_child(&overridden,
DefRegionKind::NonRecursive)? {
+ return Ok(Some(interrupt));
+ }
+ // ...and forward the received state to inherit it.
+ let inherited = array.get(1).unwrap();
+ return self.visit_child(&inherited, def_region_kind);
+ }
+ if value.cast::<i64>().is_some() {
+ self.seen.push(def_region_kind);
+ }
+ Ok(None)
+ }
+}
+
+#[test]
+fn manual_child_visit_can_override_def_region() {
+ let root = Array::new(vec![7i64, 8]);
+ let mut probe = ManualRegionVisitor::default();
+ assert!(structural_visit(&root, &mut probe).unwrap().is_none());
+ assert_eq!(
+ probe.seen,
+ vec![DefRegionKind::NonRecursive, DefRegionKind::None]
+ );
+}
+
+#[derive(Default)]
+struct GenericDispatchProbe {
+ integers: Vec<i64>,
+ objects: usize,
+ catch_all: usize,
+}
+
+#[dispatch(visit)]
+impl GenericDispatchProbe {
+ fn visit_integer(&mut self, value: i64) -> WalkResult {
+ self.integers.push(value);
+ WalkResult::Advance
+ }
+
+ // Trailing DefRegionKind: handlers may mix arities within one impl.
+ fn visit_object(&mut self, _value: &tvm_ffi::Object, kind: DefRegionKind)
-> WalkResult {
+ assert_eq!(kind, DefRegionKind::None);
+ self.objects += 1;
+ WalkResult::Advance
+ }
+
+ fn visit_any(&mut self, _value: &VisitValue) -> WalkResult {
+ self.catch_all += 1;
+ WalkResult::Advance
+ }
+}
+
+#[test]
+fn generated_dispatch_supports_pod_and_ordered_catch_all() {
+ let root = Array::new(vec![1i64, 2]);
+ let mut probe = GenericDispatchProbe::default();
+ assert!(structural_walk(&root, &mut probe, WalkOrder::PreOrder)
+ .unwrap()
+ .is_none());
+ assert_eq!(probe.integers, vec![1, 2]);
+ assert_eq!(probe.objects, 1);
+
+ let floats = Array::new(vec![1.0f64, 2.0]);
+ assert!(structural_walk(&floats, &mut probe, WalkOrder::PreOrder)
+ .unwrap()
+ .is_none());
+ assert_eq!(probe.objects, 2);
+ assert_eq!(probe.catch_all, 2);
+}
+
+/// Visitor-layer enter/exit straddling: run enter logic, delegate the
+/// default child recursion, then run exit logic with the same locals in
+/// scope — the C++ `DefaultVisitExpected` pattern.
+#[derive(Default)]
+struct StraddleVisitor {
+ events: Vec<String>,
+}
+
+impl StructuralVisitor for StraddleVisitor {
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ let label = match value.cast::<i64>() {
+ Some(integer) => format!("int:{integer}"),
+ None => "node".to_string(),
+ };
+ self.events.push(format!("enter:{label}"));
+ if let Some(interrupt) = self.default_visit_children(value,
def_region_kind)? {
+ return Ok(Some(interrupt));
+ }
+ self.events.push(format!("exit:{label}"));
+ Ok(None)
+ }
+}
+
+#[test]
+fn visitor_can_straddle_default_children() {
+ let root = Array::new(vec![1i64, 2]);
+ let mut probe = StraddleVisitor::default();
+ assert!(structural_visit(&root, &mut probe).unwrap().is_none());
+ assert_eq!(
+ probe.events,
+ vec![
+ "enter:node",
+ "enter:int:1",
+ "exit:int:1",
+ "enter:int:2",
+ "exit:int:2",
+ "exit:node",
+ ]
+ );
+}
+
+#[derive(Default)]
+struct OrderProbe {
+ events: Vec<String>,
+}
+
+#[dispatch(visit)]
+impl OrderProbe {
+ fn visit_array(&mut self, _array: Array<i64>) -> WalkResult {
+ self.events.push("array".to_string());
+ WalkResult::Advance
+ }
+
+ fn visit_integer(&mut self, value: i64) -> WalkResult {
+ self.events.push(format!("int:{value}"));
+ WalkResult::Advance
+ }
+}
+
+#[test]
+fn stateful_structural_walk_supports_post_order() {
+ let root = Array::new(vec![1i64, 2]);
+ let mut probe = OrderProbe::default();
+ assert!(structural_walk(&root, &mut probe, WalkOrder::PostOrder)
+ .unwrap()
+ .is_none());
+ assert_eq!(probe.events, vec!["int:1", "int:2", "array"]);
+}
+
+#[test]
+fn interrupt_payload_is_returned_to_the_caller() {
+ let root = Array::new(vec![1i64, 2]);
+ let outcome = structural_walk(
+ &root,
+ |value: &VisitValue| {
+ if value.cast::<i64>() == Some(1) {
+ return WalkResult::interrupt_with(42i64);
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap();
+ let Some(interrupt) = outcome else {
+ panic!("walk unexpectedly completed");
+ };
+ assert_eq!(i64::try_from(interrupt.value).unwrap(), 42);
+}
+
+#[test]
+fn handler_errors_include_native_visit_path() {
+ let root = Array::new(vec![1i64]);
+ let error = match structural_walk(
+ &root,
+ |value: &VisitValue| -> Result<WalkResult> {
+ if value.cast::<i64>().is_some() {
+ Err(runtime_error("handler failed"))
+ } else {
+ Ok(WalkResult::Advance)
+ }
+ },
+ WalkOrder::PreOrder,
+ ) {
+ Err(error) => error,
+ Ok(_) => panic!("handler unexpectedly succeeded"),
+ };
+ assert_eq!(error.message(), "handler failed");
+ assert!(error.backtrace().contains("sequence item [0]"));
+ assert!(error.backtrace().contains("object `ffi.Array`"));
+}
+
+#[test]
+fn visitor_errors_include_native_visit_path() {
+ struct FailingVisitor;
+
+ impl StructuralVisitor for FailingVisitor {
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ if value.cast::<i64>().is_some() {
+ return Err(runtime_error("visitor failed"));
+ }
+ self.default_visit_children(value, def_region_kind)
+ }
+ }
+
+ let root = Array::new(vec![1i64]);
+ let error = match structural_visit(&root, &mut FailingVisitor) {
+ Err(error) => error,
+ Ok(_) => panic!("visitor unexpectedly succeeded"),
+ };
+ assert_eq!(error.message(), "visitor failed");
+ assert!(error.backtrace().contains("sequence item [0]"));
+ assert!(error.backtrace().contains("object `ffi.Array`"));
+}
+
+#[test]
+fn visitor_interrupt_propagates_through_default_children() {
+ struct InterruptingVisitor;
+
+ impl StructuralVisitor for InterruptingVisitor {
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ if value.cast::<i64>() == Some(2) {
+ return Ok(Some(VisitInterrupt::with(7i64)));
+ }
+ self.default_visit_children(value, def_region_kind)
+ }
+ }
+
+ let root = Array::new(vec![1i64, 2, 3]);
+ let outcome = structural_visit(&root, &mut InterruptingVisitor).unwrap();
+ let Some(interrupt) = outcome else {
+ panic!("visitor traversal unexpectedly completed");
+ };
+ assert_eq!(i64::try_from(interrupt.value).unwrap(), 7);
+}
+
+#[test]
+fn closure_walk_receives_def_region_kind() {
+ // C++: StructuralWalk<kPreOrder>(root,
+ // [&](const TVarObj* var, TVMFFIDefRegionKind kind) { ... })
+ let root = Array::new(vec![1i64, 2]);
+ let mut kinds = Vec::new();
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue, kind: DefRegionKind| {
+ if value.cast::<i64>().is_some() {
+ kinds.push(kind);
+ }
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(kinds, vec![DefRegionKind::None; 2]);
+}
+
+#[test]
+fn closure_walk_supports_post_order_and_skip() {
+ let root = Array::new(vec![1i64, 2]);
+ let mut order_probe = Vec::new();
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ order_probe.push(value.cast::<i64>());
+ WalkResult::Advance
+ },
+ WalkOrder::PostOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(order_probe, vec![Some(1), Some(2), None]);
+
+ let mut visited = 0;
+ assert!(structural_walk(
+ &root,
+ |value: &VisitValue| {
+ visited += 1;
+ if value.cast::<i64>().is_none() {
+ WalkResult::Skip
+ } else {
+ WalkResult::Advance
+ }
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(visited, 1);
+}
+
+// ---------------------------------------------------------------------------
+// Tuple walkers: structural_walk(root, (link1, link2, ...), order) — links
+// are tried in order and the first whose argument type matches the value
+// runs, the Rust analog of the variadic C++ StructuralWalk callback chain.
+// ---------------------------------------------------------------------------
+#[test]
+fn chain_accepts_owned_object_ref_links() {
+ let root = Array::new(vec![Array::new(vec![1i64]), Array::new(vec![2i64,
3])]);
+ let mut lengths = Vec::new();
+ assert!(structural_walk(
+ &root,
+ (
+ |array: Array<i64>| {
+ lengths.push(array.len());
+ WalkResult::Advance
+ },
+ |_value: i64| WalkResult::Advance,
+ ),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ // The outer Array<Array<i64>> fails the strict element check and falls
+ // through the chain; only the inner arrays match the typed link.
+ assert_eq!(lengths, vec![1, 2]);
+}
+
+#[test]
+fn chain_links_may_mix_def_region_arity() {
+ // Like #[dispatch(visit)] handlers, each link independently opts into
+ // the trailing DefRegionKind argument.
+ let root = Array::new(vec![1i64, 2]);
+ let mut kinds = Vec::new();
+ let mut objects = 0;
+ assert!(structural_walk(
+ &root,
+ (
+ |_value: i64, kind: DefRegionKind| {
+ kinds.push(kind);
+ WalkResult::Advance
+ },
+ |_value: &VisitValue, kind: DefRegionKind| {
+ assert_eq!(kind, DefRegionKind::None);
+ objects += 1;
+ WalkResult::Advance
+ },
+ ),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(kinds, vec![DefRegionKind::None; 2]);
+ assert_eq!(objects, 1);
+}
+
+#[test]
+fn chain_links_can_skip_children() {
+ let root = Array::new(vec![Array::new(vec![1i64]),
Array::new(vec![2i64])]);
+ let mut arrays = 0;
+ let mut integers = 0;
+ assert!(structural_walk(
+ &root,
+ (
+ |_array: Array<i64>| {
+ arrays += 1;
+ WalkResult::Skip
+ },
+ |_value: i64| {
+ integers += 1;
+ WalkResult::Advance
+ },
+ ),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(arrays, 2);
+ assert_eq!(integers, 0); // both inner arrays were skipped
+}
+
+#[test]
+fn chain_link_errors_include_native_visit_path() {
+ let root = Array::new(vec![1i64]);
+ let error = match structural_walk(
+ &root,
+ (
+ |_value: i64| -> Result<WalkResult> { Err(runtime_error("link
failed")) },
+ |_value: &VisitValue| WalkResult::Advance,
+ ),
+ WalkOrder::PreOrder,
+ ) {
+ Err(error) => error,
+ Ok(_) => panic!("link unexpectedly succeeded"),
+ };
+ assert_eq!(error.message(), "link failed");
+ assert!(error.backtrace().contains("sequence item [0]"));
+ assert!(error.backtrace().contains("object `ffi.Array`"));
+}
+
+#[test]
+fn chain_supports_post_order() {
+ // Rust borrow rules apply per link: state shared across links goes
+ // through a RefCell (or a single #[dispatch(visit)] visitor).
+ let root = Array::new(vec![1i64, 2]);
+ let events = std::cell::RefCell::new(Vec::new());
+ assert!(structural_walk(
+ &root,
+ (
+ |value: i64| {
+ events.borrow_mut().push(format!("int:{value}"));
+ WalkResult::Advance
+ },
+ |_object: &Object| {
+ events.borrow_mut().push("array".to_string());
+ WalkResult::Advance
+ },
+ ),
+ WalkOrder::PostOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(events.into_inner(), vec!["int:1", "int:2", "array"]);
+}
+
+#[derive(Default)]
+struct ObjectCounter {
+ objects: usize,
+}
+
+#[dispatch(visit)]
+impl ObjectCounter {
+ fn visit_object(&mut self, _value: &Object) -> WalkResult {
+ self.objects += 1;
+ WalkResult::Advance
+ }
+}
+
+#[test]
+fn chain_splices_dispatch_visitors_between_closures() {
+ // A `&mut` typed visitor participates in the chain like any other link,
+ // keeping its own no-match fall-through semantics.
+ let root = Array::new(vec![1i64, 2]);
+ let mut counter = ObjectCounter::default();
+ let mut integers = 0;
+ assert!(structural_walk(
+ &root,
+ (&mut counter, |_value: i64| {
+ integers += 1;
+ WalkResult::Advance
+ },),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(counter.objects, 1);
+ assert_eq!(integers, 2);
+}
+
+#[test]
+fn chain_supports_full_arity() {
+ // Doubles as the first-match ordering probe: earlier misses fall
+ // through, the first matching link claims the value, later links
+ // never run.
+ let root = Array::new(vec![1i64, 2, 3]);
+ let mut integers = Vec::new();
+ let mut objects = 0;
+ let mut others = 0;
+ assert!(structural_walk(
+ &root,
+ (
+ |_value: f64| WalkResult::Advance,
+ |_value: bool| WalkResult::Advance,
+ |_value: tvm_ffi::String| WalkResult::Advance,
+ |_value: Array<f64>| WalkResult::Advance,
+ |value: i64| {
+ integers.push(value);
+ WalkResult::Advance
+ },
+ |_object: &Object, _kind: DefRegionKind| {
+ objects += 1;
+ WalkResult::Advance
+ },
+ |_value: &VisitValue, _kind: DefRegionKind| {
+ others += 1;
+ WalkResult::Advance
+ },
+ |_value: &VisitValue| WalkResult::Advance,
+ ),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(integers, vec![1, 2, 3]);
+ assert_eq!(objects, 1); // the array itself; integers matched earlier
+ assert_eq!(others, 0); // every value matched an earlier link
+}
+
+#[test]
+fn typed_lambda_walks_bare_and_as_single_link_tuple() {
+ // A lone typed handler needs no tuple: unmatched values (the array
+ // itself) advance normally. The 1-tuple spelling routes through the
+ // chain impls instead and must agree.
+ let root = Array::new(vec![1i64, 2, 3]);
+ let mut bare = 0;
+ assert!(structural_walk(
+ &root,
+ |value: i64| {
+ bare += value;
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ let mut tupled = 0;
+ assert!(structural_walk(
+ &root,
+ (|value: i64| {
+ tupled += value;
+ WalkResult::Advance
+ },),
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!((bare, tupled), (6, 6));
+}
+
+#[test]
+fn bare_node_lambda_takes_def_region_kind() {
+ let root = Array::new(vec![1i64, 2]);
+ let mut objects = 0;
+ assert!(structural_walk(
+ &root,
+ |_object: &Object, kind: DefRegionKind| {
+ assert_eq!(kind, DefRegionKind::None);
+ objects += 1;
+ WalkResult::Advance
+ },
+ WalkOrder::PreOrder,
+ )
+ .unwrap()
+ .is_none());
+ assert_eq!(objects, 1);
+}
diff --git a/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
b/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
new file mode 100644
index 00000000..2d964bdf
--- /dev/null
+++ b/rust/tvm-ffi/tests/test_structural_visitor_alignment.rs
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Rust mirror of the C++ visitor example.
+//!
+//! `RecordingVisitor` lines up member-for-member with the C++
+//! `TestVisitorObj` (tests/cpp/extra/test_structural_visit.cc): its `visit`
+//! plays the role of the C++ vtable `VisitImpl`, and the array arm plays the
+//! role of the C++ `TFuncObj::StructuralVisit` hook
+//! (tests/cpp/testing_object.h): the first element is visited as a recursive
+//! definition region, the rest inherit the surrounding state.
+
+use tvm_ffi::{
+ structural_visit, Array, DefRegionKind, Result, String as FfiString,
StructuralVisitor,
+ VisitInterrupt, VisitValue,
+};
+
+/// C++: class TestVisitorObj : public StructuralVisitorObj
+#[derive(Default)]
+struct RecordingVisitor {
+ /// C++: `std::vector<ObjectRef> visited;`
+ visited: Vec<String>,
+ /// C++: `std::vector<TVMFFIDefRegionKind> modes;`
+ modes: Vec<DefRegionKind>,
+ /// C++: `ObjectRef interrupt_on;`
+ interrupt_on: Option<i64>,
+}
+
+impl StructuralVisitor for RecordingVisitor {
+ /// C++ analog: `TestVisitorObj::VisitImpl` — record every value together
+ /// with the active def-region state, optionally interrupt with a payload,
+ /// otherwise delegate recursion explicitly. The array arm mirrors
+ /// `TFuncObj::StructuralVisit`.
+ fn visit(
+ &mut self,
+ value: &VisitValue,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Option<VisitInterrupt>> {
+ let integer = value.cast::<i64>();
+ let label = match integer {
+ Some(integer) => integer.to_string(),
+ None if value.cast::<Array<i64>>().is_some() =>
"array".to_string(),
+ None => "obj".to_string(),
+ };
+ // C++: visited.push_back(value_ref);
+ // modes.push_back(def_region_mode_);
+ self.visited.push(label);
+ self.modes.push(def_region_kind);
+
+ // C++: if (value_ref.same_as(interrupt_on))
+ // return VisitInterrupt(String("stop"));
+ if self.interrupt_on.is_some() && integer == self.interrupt_on {
+ return Ok(Some(VisitInterrupt::with(FfiString::from("stop"))));
+ }
+
+ // C++ analog: `TFuncObj::StructuralVisit` — visit "params"
+ // (element 0) under a recursive definition region, then the "body"
+ // (element 1) under the inherited state.
+ if let Some(array) = value.cast::<Array<i64>>() {
+ // C++: visitor->WithDefRegionKind(kTVMFFIDefRegionKindRecursive,
+ // [&] { return visitor->VisitExpected(self->params); })
+ let params = array.get(0).unwrap();
+ if let Some(interrupt) = self.visit_child(¶ms,
DefRegionKind::Recursive)? {
+ return Ok(Some(interrupt));
+ }
+ // C++: visitor->VisitExpected(self->body) (inherits the state)
+ let body = array.get(1).unwrap();
+ return self.visit_child(&body, def_region_kind);
+ }
+
+ // C++: return DefaultVisitExpected(value);
+ self.default_visit_children(value, def_region_kind)
+ }
+}
+
+/// C++ analog: TEST(StructuralVisitor, TraversesFunction) — the def-region
+/// state flips to Recursive under "params" and back to None for the "body".
+#[test]
+fn records_values_and_def_region_modes() {
+ let root = Array::new(vec![10i64, 20]);
+ let mut visitor = RecordingVisitor::default();
+
+ let outcome = structural_visit(&root, &mut visitor).unwrap();
+
+ assert!(outcome.is_none());
+ assert_eq!(visitor.visited, vec!["array", "10", "20"]);
+ assert_eq!(
+ visitor.modes,
+ vec![
+ DefRegionKind::None, // the array itself
+ DefRegionKind::Recursive, // element 0: the "params" position
+ DefRegionKind::None, // element 1: the "body" position
+ ]
+ );
+}
+
+/// C++ analog: TEST(StructuralVisitor, StopsOnInterrupt) — the traversal
+/// halts at the marked value and the payload reaches the caller.
+#[test]
+fn stops_on_interrupt_with_payload() {
+ let root = Array::new(vec![10i64, 20]);
+ let mut visitor = RecordingVisitor {
+ interrupt_on: Some(20),
+ ..RecordingVisitor::default()
+ };
+
+ let outcome = structural_visit(&root, &mut visitor).unwrap();
+
+ let Some(interrupt) = outcome else {
+ panic!("traversal unexpectedly completed");
+ };
+ assert_eq!(
+ FfiString::try_from(interrupt.value).unwrap().as_str(),
+ "stop"
+ );
+ assert_eq!(visitor.visited, vec!["array", "10", "20"]);
+}