This is an automated email from the ASF dual-hosted git repository.

tlopex 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 874c85c5 [FEAT][Rust] Add composable policies for structural visit and 
walk (#794)
874c85c5 is described below

commit 874c85c537fc6638dafe584224a770bb008b7c8f
Author: Shushi Hong <[email protected]>
AuthorDate: Wed Sep 16 18:10:09 2026 -0400

    [FEAT][Rust] Add composable policies for structural visit and walk (#794)
    
    Add `VisitPolicy<State>` to customize default recursion independently of
    callbacks, corresponding to the C++ Parent extension mechanism. Attach
    policies through `VisitCallbacks::with_policy` or `WalkWithPolicy`;
    nested tuples compose policies sharing the callback state.
    
    Add `VisitContext::default_visit_children(value, kind)` to continue the
    policy chain with an explicit value and definition region, bypassing the
    target's callback while its children re-enter full dispatch. Preserve
    Pattern precedence and restore the enclosing region on return, error, or
    interrupt. Keep `visit_children()` as the current-value/current-region
    shorthand.
    
    Tests cover composition, shared state, generated walk dispatch, callback
    ordering, Skip, policy interrupts/errors, retargeted descent, and region
    propagation through reflected fields and Function-valued hooks.
---
 docs/guides/rust_lang_guide.md                    |   4 +
 rust/tvm-ffi/src/extra/structural_visit.rs        | 175 +++++--
 rust/tvm-ffi/src/extra/structural_visit/policy.rs | 272 ++++++++++
 rust/tvm-ffi/src/lib.rs                           |   7 +-
 rust/tvm-ffi/tests/test_structural_visit.rs       | 602 +++++++++++++++++++++-
 src/ffi/testing/testing.cc                        |  44 ++
 6 files changed, 1063 insertions(+), 41 deletions(-)

diff --git a/docs/guides/rust_lang_guide.md b/docs/guides/rust_lang_guide.md
index 3d700085..2bc2764c 100644
--- a/docs/guides/rust_lang_guide.md
+++ b/docs/guides/rust_lang_guide.md
@@ -366,6 +366,10 @@ Callbacks are `Fn`; mutable data belongs in the visitor 
state. A catch-all
 callback must call `visit_children()` explicitly, and interrupt values must be
 returned explicitly because `?` only propagates errors.
 
+`VisitCallbacks::with_policy` and `WalkWithPolicy` customize default recursion
+with reusable `VisitPolicy<State>` policies. See the `VisitPolicy` API
+documentation for policy composition and state access.
+
 For a named implementation, `#[dispatch(visit)]` generates
 `StructuralVisitor` from `visit_*` methods. Matching handlers own recursion;
 unmatched values use default child traversal:
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs 
b/rust/tvm-ffi/src/extra/structural_visit.rs
index b6cef1c8..0fc337ad 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -57,7 +57,7 @@ use crate::function::Function;
 use crate::object::{Object, ObjectArc, ObjectCore};
 use crate::reflection::TypeAttrColumn;
 use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
-    kTVMFFIFieldFlagBitMaskSEqHashDefSimple, 
kTVMFFIFieldFlagBitMaskSEqHashDefPattern,
+    kTVMFFIFieldFlagBitMaskSEqHashDefPattern, 
kTVMFFIFieldFlagBitMaskSEqHashDefSimple,
     kTVMFFIFieldFlagBitMaskSEqHashIgnore,
 };
 use crate::tvm_ffi_sys::{
@@ -141,13 +141,9 @@ pub enum DefRegionKind {
 const _: () = {
     assert!(DefRegionKind::None as i32 == 
TVMFFIDefRegionKind::kTVMFFIDefRegionKindNone as i32);
     assert!(
-        DefRegionKind::Pattern as i32
-            == TVMFFIDefRegionKind::kTVMFFIDefRegionKindPattern as i32
-    );
-    assert!(
-        DefRegionKind::Simple as i32
-            == TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32
+        DefRegionKind::Pattern as i32 == 
TVMFFIDefRegionKind::kTVMFFIDefRegionKindPattern as i32
     );
+    assert!(DefRegionKind::Simple as i32 == 
TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32);
 };
 
 /// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
@@ -228,6 +224,9 @@ impl From<Error> for NativeHalt {
 
 type NativeResult = std::result::Result<(), NativeHalt>;
 
+mod policy;
+pub use policy::{DefaultVisitPolicy, VisitPolicy, WalkWithPolicy};
+
 /// State and recursive operations available to a visit callback.
 ///
 /// A matched callback owns traversal of its value. Recursive operations
@@ -299,11 +298,51 @@ impl<State> VisitContext<'_, State> {
         self.driver.visit_raw(raw, def_region_kind)
     }
 
-    /// Visit the current value's children using registered hooks or reflected
-    /// structural fields. The current value itself is not dispatched again.
+    /// Apply default descent without dispatching the current value again.
+    /// A callback enters its configured policy; within a policy this continues
+    /// with the next policy, then registered hooks or reflected fields.
     pub fn visit_children(&mut self) -> Result<Option<VisitInterrupt>> {
-        self.driver
-            .visit_children_raw(self.current.raw(), self.def_region_kind)
+        self.default_visit_children_raw(self.current.raw(), 
self.def_region_kind)
+    }
+
+    /// Apply default descent to `value` under an explicit definition region.
+    ///
+    /// Like [`Self::visit_children`], this continues with the next policy (or
+    /// enters the configured policy from a callback), then registered hooks or
+    /// reflected fields. It does not dispatch callbacks for `value` itself;
+    /// its children re-enter the full callback engine.
+    ///
+    /// An enclosing [`DefRegionKind::Pattern`] cannot be downgraded. The 
current
+    /// value and region of this context are unchanged after the call, 
including
+    /// when descent returns an error or interrupt.
+    pub fn default_visit_children<T>(
+        &mut self,
+        value: &T,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>>
+    where
+        for<'x> AnyView<'x>: From<&'x T>,
+    {
+        self.default_visit_children_raw(raw_of(AnyView::from(value)), 
def_region_kind)
+    }
+
+    fn default_visit_children_raw(
+        &mut self,
+        raw: TVMFFIAny,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+            return Ok(None);
+        }
+        let kind = if self.def_region_kind == DefRegionKind::Pattern {
+            DefRegionKind::Pattern
+        } else {
+            def_region_kind
+        };
+        let active = active_structural_visitor()?;
+        // Keep the ABI visitor in sync while policies run, not only inside
+        // hooks: a subsequent visit_with must also preserve a pattern region.
+        with_visitor_def_region(active, kind, || 
self.driver.visit_children_raw(raw, kind))
     }
 }
 
@@ -473,7 +512,8 @@ macro_rules! impl_visit_chain_link {
 impl_callback_chain_tuple_arities!(impl_visit_chain_link);
 
 /// A reusable callback visitor with shared user state.
-pub struct VisitCallbacks<State, Link, Marker> {
+pub struct VisitCallbacks<State, Link, Marker, Policy = DefaultVisitPolicy> {
+    policy: Option<Rc<Policy>>,
     state: State,
     callbacks: Rc<Link>,
     _marker: PhantomData<fn(Marker)>,
@@ -488,12 +528,27 @@ where
         Self {
             state,
             callbacks: Rc::new(callbacks),
+            policy: None,
             _marker: PhantomData,
         }
     }
 }
 
-impl<State, Link, Marker> VisitCallbacks<State, Link, Marker> {
+impl<State, Link, Marker, Policy> VisitCallbacks<State, Link, Marker, Policy> {
+    /// Set the default-recursion policy while retaining the callbacks and 
state.
+    /// A matched callback enters the policy only when it calls 
`visit_children()`.
+    pub fn with_policy<P: VisitPolicy<State>>(
+        self,
+        policy: P,
+    ) -> VisitCallbacks<State, Link, Marker, P> {
+        VisitCallbacks {
+            state: self.state,
+            callbacks: self.callbacks,
+            policy: Some(Rc::new(policy)),
+            _marker: PhantomData,
+        }
+    }
+
     /// Shared access to the callback state.
     pub fn state(&self) -> &State {
         &self.state
@@ -521,7 +576,9 @@ trait VisitCallbackState<State> {
     fn callback_state_mut(&mut self) -> &mut State;
 }
 
-impl<State, Link, Marker> VisitCallbackState<State> for VisitCallbacks<State, 
Link, Marker> {
+impl<State, Link, Marker, Policy> VisitCallbackState<State>
+    for VisitCallbacks<State, Link, Marker, Policy>
+{
     fn callback_state(&self) -> &State {
         &self.state
     }
@@ -979,19 +1036,23 @@ pub trait StructuralVisitor: Sized {
         value: &VisitValue,
         def_region_kind: DefRegionKind,
     ) -> Result<Option<VisitInterrupt>> {
-        let raw = value.raw();
-        let context = std::ptr::from_mut(&mut *self).cast::<c_void>();
-        let result = visit_children_raw(
-            raw,
-            &mut UserChildren { visitor: self },
-            context,
-            def_region_kind,
-        )
-        .map_err(|halt| with_value_context(halt, raw));
-        finish(result)
+        default_user_visit_children(self, value, def_region_kind)
     }
 }
 
+fn default_user_visit_children<V: StructuralVisitor>(
+    visitor: &mut V,
+    value: &VisitValue,
+    def_region_kind: DefRegionKind,
+) -> Result<Option<VisitInterrupt>> {
+    let raw = value.raw();
+    let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
+    finish(
+        visit_children_raw(raw, &mut UserChildren { visitor }, context, 
def_region_kind)
+            .map_err(|halt| with_value_context(halt, raw)),
+    )
+}
+
 fn try_visit_callbacks<State, Link, Marker>(
     driver: &mut impl VisitContextDriver<State>,
     callback_ptr: *const Link,
@@ -1015,9 +1076,10 @@ where
     }
 }
 
-impl<State, Link, Marker> StructuralVisitor for VisitCallbacks<State, Link, 
Marker>
+impl<State, Link, Marker, Policy> StructuralVisitor for VisitCallbacks<State, 
Link, Marker, Policy>
 where
     Link: VisitChainLink<State, Marker>,
+    Policy: VisitPolicy<State>,
 {
     fn visit(
         &mut self,
@@ -1027,6 +1089,22 @@ where
         let callback_ptr = Rc::as_ptr(&self.callbacks);
         try_visit_callbacks::<State, Link, Marker>(self, callback_ptr, value, 
def_region_kind)
     }
+
+    fn default_visit_children(
+        &mut self,
+        value: &VisitValue,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        let Some(policy) = self.policy.as_ref().map(Rc::clone) else {
+            return default_user_visit_children(self, value, def_region_kind);
+        };
+        policy::visit_with_policy(
+            &mut policy::VisitDescent { visitor: self },
+            &*policy,
+            value,
+            def_region_kind,
+        )
+    }
 }
 
 impl<Link, Marker> StructuralVisitor for DirectVisitCallbacks<'_, Link, Marker>
@@ -1085,8 +1163,32 @@ where
 
 /// Internal callback protocol used by [`IntoWalker`].
 #[doc(hidden)]
-pub trait NativeVisit {
+pub trait NativeVisit: Sized {
+    const CUSTOM_DESCENT: bool = false;
+
     fn visit(&mut self, value: &VisitValue, def_region_kind: DefRegionKind) -> 
Result<WalkResult>;
+
+    fn default_visit_children<const PRE_ORDER: bool>(
+        &mut self,
+        value: &VisitValue,
+        def_region_kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        default_walk_children::<Self, PRE_ORDER>(self, value, def_region_kind)
+    }
+}
+
+fn default_walk_children<V: NativeVisit, const PRE_ORDER: bool>(
+    visitor: &mut V,
+    value: &VisitValue,
+    def_region_kind: DefRegionKind,
+) -> Result<Option<VisitInterrupt>> {
+    let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
+    finish(visit_children_raw(
+        value.raw(),
+        &mut WalkChildren::<V, PRE_ORDER> { visitor },
+        context,
+        def_region_kind,
+    ))
 }
 
 /// Action applied to each child found by the shared traversal.
@@ -1147,12 +1249,19 @@ fn visit_raw<V: NativeVisit, const PRE_ORDER: bool>(
         }
     }
 
-    let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
-    let children = &mut WalkChildren::<V, PRE_ORDER> {
-        visitor: &mut *visitor,
-    };
-    if let Err(halt) = visit_children_raw(value, children, context, 
def_region_kind) {
-        return Err(with_value_context(halt, value));
+    if V::CUSTOM_DESCENT {
+        match visitor.default_visit_children::<PRE_ORDER>(&visit_value, 
def_region_kind) {
+            Ok(None) => {}
+            Ok(Some(interrupt)) => return 
Err(NativeHalt::Interrupt(interrupt.value)),
+            Err(error) => return Err(with_value_context(error.into(), value)),
+        }
+    } else {
+        // Preserve the raw-result path for ordinary walkers.
+        let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
+        let children = &mut WalkChildren::<V, PRE_ORDER> { visitor };
+        if let Err(halt) = visit_children_raw(value, children, context, 
def_region_kind) {
+            return Err(with_value_context(halt, value));
+        }
     }
 
     if PRE_ORDER {
@@ -1567,7 +1676,7 @@ unsafe fn runtime_walk<V: NativeVisit, const PRE_ORDER: 
bool>(
     if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
         return Ok(());
     }
-    if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+    if !V::CUSTOM_DESCENT && raw.type_index < 
TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
         let visitor = &mut *context.cast::<V>();
         if PRE_ORDER {
             match visitor.visit(&VisitValue::from_raw(raw), def_region_kind) {
diff --git a/rust/tvm-ffi/src/extra/structural_visit/policy.rs 
b/rust/tvm-ffi/src/extra/structural_visit/policy.rs
new file mode 100644
index 00000000..e2372ee2
--- /dev/null
+++ b/rust/tvm-ffi/src/extra/structural_visit/policy.rs
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+//! Reusable customization of default structural descent.
+
+use super::*;
+
+/// A reusable default-recursion policy sharing state with traversal callbacks.
+///
+/// `visit_children()` on this policy's context continues with the next policy
+/// (or the built-in hooks and reflected fields). `visit()` re-enters the full
+/// callback engine for a child. A tuple `(outer, inner)` composes two 
policies;
+/// tuples may nest. Policies are shared during recursive calls, so mutable 
data
+/// belongs in the context's state.
+pub trait VisitPolicy<State> {
+    /// Customize default descent for the current value.
+    ///
+    /// Return interrupts explicitly, and restore any scoped state before
+    /// returning an interrupt or error. Walk invokes this between its pre- and
+    /// post-order callback positions; visit invokes it only on callback miss
+    /// or when a matched callback requests default descent.
+    fn default_visit(
+        &self,
+        value: &VisitValue,
+        visitor: &mut VisitContext<'_, State>,
+    ) -> Result<Option<VisitInterrupt>>;
+}
+
+/// Default descent through registered hooks or reflected structural fields.
+pub struct DefaultVisitPolicy;
+
+impl<State> VisitPolicy<State> for DefaultVisitPolicy {
+    fn default_visit(
+        &self,
+        _value: &VisitValue,
+        visitor: &mut VisitContext<'_, State>,
+    ) -> Result<Option<VisitInterrupt>> {
+        visitor.visit_children()
+    }
+}
+
+impl<State, Outer: VisitPolicy<State>, Inner: VisitPolicy<State>> 
VisitPolicy<State>
+    for (Outer, Inner)
+{
+    fn default_visit(
+        &self,
+        value: &VisitValue,
+        visitor: &mut VisitContext<'_, State>,
+    ) -> Result<Option<VisitInterrupt>> {
+        let kind = visitor.def_region_kind();
+        visit_with_policy(
+            &mut NextPolicy {
+                driver: &mut *visitor.driver,
+                policy: &self.1,
+            },
+            &self.0,
+            value,
+            kind,
+        )
+    }
+}
+
+pub(super) fn visit_with_policy<State>(
+    driver: &mut dyn VisitContextDriver<State>,
+    policy: &impl VisitPolicy<State>,
+    value: &VisitValue,
+    def_region_kind: DefRegionKind,
+) -> Result<Option<VisitInterrupt>> {
+    policy.default_visit(
+        value,
+        &mut VisitContext {
+            driver,
+            current: VisitValue::from_raw(value.raw()),
+            def_region_kind,
+            _not_send_sync: PhantomData,
+        },
+    )
+}
+
+struct NextPolicy<'a, State, Policy> {
+    driver: &'a mut dyn VisitContextDriver<State>,
+    policy: &'a Policy,
+}
+
+impl<State, Policy: VisitPolicy<State>> VisitContextDriver<State>
+    for NextPolicy<'_, State, Policy>
+{
+    fn state(&self) -> &State {
+        self.driver.state()
+    }
+    fn state_mut(&mut self) -> &mut State {
+        self.driver.state_mut()
+    }
+    fn visit_raw(&mut self, raw: TVMFFIAny, kind: DefRegionKind) -> 
Result<Option<VisitInterrupt>> {
+        self.driver.visit_raw(raw, kind)
+    }
+    fn visit_children_raw(
+        &mut self,
+        raw: TVMFFIAny,
+        kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        visit_with_policy(self.driver, self.policy, 
&VisitValue::from_raw(raw), kind)
+    }
+}
+
+pub(super) struct VisitDescent<'a, V> {
+    pub(super) visitor: &'a mut V,
+}
+
+impl<State, V: StructuralVisitor + VisitCallbackState<State>> 
VisitContextDriver<State>
+    for VisitDescent<'_, V>
+{
+    fn state(&self) -> &State {
+        self.visitor.callback_state()
+    }
+    fn state_mut(&mut self) -> &mut State {
+        self.visitor.callback_state_mut()
+    }
+    fn visit_raw(&mut self, raw: TVMFFIAny, kind: DefRegionKind) -> 
Result<Option<VisitInterrupt>> {
+        VisitContextDriver::visit_raw(self.visitor, raw, kind)
+    }
+    fn visit_children_raw(
+        &mut self,
+        raw: TVMFFIAny,
+        kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        // Bypass the current policy; children still re-enter the complete 
visitor.
+        default_user_visit_children(self.visitor, &VisitValue::from_raw(raw), 
kind)
+    }
+}
+
+/// A walk dispatcher combined with a reusable default-recursion policy.
+///
+/// The dispatcher is also the state visible through the policy's context. Use
+/// `#[dispatch(walk)]` or implement [`WalkDispatch`] to define its callbacks.
+/// Run repeatedly with [`Self::walk`], or pass this value to 
[`structural_walk`].
+pub struct WalkWithPolicy<Walker, Policy> {
+    walker: Walker,
+    policy: Rc<Policy>,
+}
+
+impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>> WalkWithPolicy<Walker, 
Policy> {
+    /// Combine a dispatcher and a default-recursion policy.
+    pub fn new(walker: Walker, policy: Policy) -> Self {
+        Self {
+            walker,
+            policy: Rc::new(policy),
+        }
+    }
+
+    /// Access the dispatcher and its traversal state.
+    pub fn state(&self) -> &Walker {
+        &self.walker
+    }
+
+    /// Mutably access the dispatcher outside an active walk.
+    pub fn state_mut(&mut self) -> &mut Walker {
+        &mut self.walker
+    }
+
+    /// Recover the dispatcher and its state.
+    pub fn into_state(self) -> Walker {
+        self.walker
+    }
+
+    /// Walk a root using this dispatcher and policy.
+    pub fn walk<R>(&mut self, root: &R, order: WalkOrder) -> 
Result<Option<VisitInterrupt>>
+    where
+        for<'x> AnyView<'x>: From<&'x R>,
+    {
+        let raw = raw_of(AnyView::from(root));
+        finish(match order {
+            WalkOrder::PreOrder => {
+                run_structural_visitor(raw, self, walk_runtime_vtable::<Self, 
true>())
+            }
+            WalkOrder::PostOrder => {
+                run_structural_visitor(raw, self, walk_runtime_vtable::<Self, 
false>())
+            }
+        })
+    }
+}
+
+#[doc(hidden)]
+pub enum ByPolicyWalk {}
+
+impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>> 
IntoWalker<ByPolicyWalk>
+    for WalkWithPolicy<Walker, Policy>
+{
+    type Walker = Self;
+    fn into_walker(self) -> Self {
+        self
+    }
+}
+
+impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>> NativeVisit
+    for WalkWithPolicy<Walker, Policy>
+{
+    const CUSTOM_DESCENT: bool = true;
+
+    fn visit(&mut self, value: &VisitValue, kind: DefRegionKind) -> 
Result<WalkResult> {
+        self.walker
+            .dispatch_walk(value, kind)
+            .unwrap_or(Ok(WalkResult::Advance))
+    }
+
+    fn default_visit_children<const PRE_ORDER: bool>(
+        &mut self,
+        value: &VisitValue,
+        kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        let policy = Rc::clone(&self.policy);
+        let active = active_structural_visitor()?;
+        // Reflected children arrive directly from the Rust walker. Synchronize
+        // the ABI region before the policy can re-enter callback dispatch.
+        with_visitor_def_region(active, kind, || {
+            visit_with_policy(
+                &mut WalkDescent::<_, _, PRE_ORDER> { visitor: self },
+                &*policy,
+                value,
+                kind,
+            )
+        })
+    }
+}
+
+struct WalkDescent<'a, Walker, Policy, const PRE_ORDER: bool> {
+    visitor: &'a mut WalkWithPolicy<Walker, Policy>,
+}
+
+impl<Walker: WalkDispatch, Policy: VisitPolicy<Walker>, const PRE_ORDER: bool>
+    VisitContextDriver<Walker> for WalkDescent<'_, Walker, Policy, PRE_ORDER>
+{
+    fn state(&self) -> &Walker {
+        &self.visitor.walker
+    }
+    fn state_mut(&mut self) -> &mut Walker {
+        &mut self.visitor.walker
+    }
+    fn visit_raw(&mut self, raw: TVMFFIAny, kind: DefRegionKind) -> 
Result<Option<VisitInterrupt>> {
+        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+            return Ok(None);
+        }
+        let active = active_structural_visitor()?;
+        let context = std::ptr::from_mut(&mut *self.visitor).cast::<c_void>();
+        finish(with_current_visitor_context(active, context, || {
+            call_visitor(active, raw, kind)
+        }))
+    }
+    fn visit_children_raw(
+        &mut self,
+        raw: TVMFFIAny,
+        kind: DefRegionKind,
+    ) -> Result<Option<VisitInterrupt>> {
+        default_walk_children::<_, PRE_ORDER>(self.visitor, 
&VisitValue::from_raw(raw), kind)
+    }
+}
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 107399ff..dc44cedc 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -54,9 +54,10 @@ pub use crate::extra::structural_mutate::{
     MutateContext, MutateDispatch, Mutator, StructuralMutator, 
StructuralVarRemap,
 };
 pub use crate::extra::structural_visit::{
-    structural_visit, structural_walk, DefRegionKind, IntoVisitor, 
IntoWalkResult, IntoWalker,
-    StructuralVisitor, VisitCallbacks, VisitChainLink, VisitContext, 
VisitInterrupt, VisitValue,
-    WalkChainLink, WalkDispatch, WalkOrder, WalkResult,
+    structural_visit, structural_walk, DefRegionKind, DefaultVisitPolicy, 
IntoVisitor,
+    IntoWalkResult, IntoWalker, StructuralVisitor, VisitCallbacks, 
VisitChainLink, VisitContext,
+    VisitInterrupt, VisitPolicy, VisitValue, WalkChainLink, WalkDispatch, 
WalkOrder, WalkResult,
+    WalkWithPolicy,
 };
 pub use crate::extra::unchanged::{Unchanged, UnchangedOr};
 pub use crate::function::Function;
diff --git a/rust/tvm-ffi/tests/test_structural_visit.rs 
b/rust/tvm-ffi/tests/test_structural_visit.rs
index f2a67f47..9e73b206 100644
--- a/rust/tvm-ffi/tests/test_structural_visit.rs
+++ b/rust/tvm-ffi/tests/test_structural_visit.rs
@@ -23,13 +23,608 @@ use tvm_ffi::{
     dispatch, get_type_attr, structural_visit, structural_walk, Any, Array, 
DLDataType,
     DLDataTypeCode, DefRegionKind, Error, FieldGetter, Function, Map, Object, 
ObjectRefCore,
     Result, String as FfiString, StructuralVisitor, TypeIndex, VisitCallbacks, 
VisitContext,
-    VisitInterrupt, VisitValue, WalkOrder, WalkResult, RUNTIME_ERROR,
+    VisitInterrupt, VisitPolicy, VisitValue, WalkOrder, WalkResult, 
WalkWithPolicy, RUNTIME_ERROR,
 };
 
 fn runtime_error(message: &str) -> Error {
     Error::new(RUNTIME_ERROR, message, "")
 }
 
+#[test]
+fn composed_policies_share_array_scope_with_visit_and_walk_callbacks() {
+    #[derive(Default)]
+    struct CollectIntegers {
+        depth: usize,
+        integers: Vec<(i64, usize)>,
+        descent_depths: Vec<usize>,
+    }
+
+    impl CollectIntegers {
+        fn record(&mut self, value: i64) {
+            self.integers.push((value, self.depth));
+        }
+    }
+
+    // The outer policy establishes a scope around an array's children.
+    // It does not know what the callbacks will do with the current depth.
+    struct ArrayScope;
+
+    impl VisitPolicy<CollectIntegers> for ArrayScope {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            visitor: &mut VisitContext<'_, CollectIntegers>,
+        ) -> Result<Option<VisitInterrupt>> {
+            if value.cast::<Array<Any>>().is_none() {
+                return visitor.visit_children();
+            }
+            let outer_depth = visitor.state().depth;
+            let depth = outer_depth + 1;
+            visitor.state_mut().depth = depth;
+
+            // Continue to RecordDescent, not directly to the children.
+            let result = visitor.visit_children();
+
+            visitor.state_mut().depth = outer_depth;
+            result
+        }
+    }
+
+    // The inner policy observes the scope set by ArrayScope. Its continuation
+    // reaches the built-in Array hook, whose children re-enter the full 
engine.
+    struct RecordDescent;
+
+    impl VisitPolicy<CollectIntegers> for RecordDescent {
+        fn default_visit(
+            &self,
+            _value: &VisitValue,
+            visitor: &mut VisitContext<'_, CollectIntegers>,
+        ) -> Result<Option<VisitInterrupt>> {
+            let state = visitor.state_mut();
+            state.descent_depths.push(state.depth);
+            visitor.visit_children()
+        }
+    }
+
+    // [1, [2, 3], 4]: the final sibling must see the restored outer depth.
+    let root = Array::new(vec![
+        Any::from(1_i64),
+        Any::from(Array::new(vec![2_i64, 3])),
+        Any::from(4_i64),
+    ]);
+    let expected = vec![(1, 1), (2, 2), (3, 2), (4, 1)];
+
+    let mut visitor = VisitCallbacks::new(
+        CollectIntegers::default(),
+        |value: i64, visitor: &mut VisitContext<'_, CollectIntegers>| {
+            visitor.state_mut().record(value);
+            // Matched visit callbacks own recursion: an integer needs no 
descent.
+        },
+    )
+    .with_policy((ArrayScope, RecordDescent));
+    assert!(structural_visit(&root, &mut visitor).unwrap().is_none());
+    assert_eq!(visitor.state().integers, expected);
+    assert_eq!(visitor.state().depth, 0);
+    // Only unmatched arrays enter the default policies in this visit.
+    assert_eq!(visitor.state().descent_depths, vec![1, 2]);
+
+    #[dispatch(walk)]
+    impl CollectIntegers {
+        fn walk_integer(&mut self, value: i64) -> WalkResult {
+            self.record(value);
+            WalkResult::Advance
+        }
+    }
+
+    // Reuse the policies with a macro-generated walk dispatcher. Walk manages
+    // recursion, so default policies also run for matched integer leaves.
+    for order in [WalkOrder::PreOrder, WalkOrder::PostOrder] {
+        let mut walker =
+            WalkWithPolicy::new(CollectIntegers::default(), (ArrayScope, 
RecordDescent));
+        assert!(walker.walk(&root, order).unwrap().is_none());
+        assert_eq!(walker.state().integers, expected);
+        assert_eq!(walker.state().depth, 0);
+        assert_eq!(walker.state().descent_depths, vec![1, 1, 2, 2, 2, 1]);
+    }
+}
+
+// Enter Simple, then Pattern, then attempt to downgrade to None. Verify each
+// enclosing region is restored even when a child interrupts or returns an 
error.
+#[test]
+fn policy_continuation_scopes_regions_and_restores_after_halts() {
+    #[derive(Clone, Copy)]
+    enum Outcome {
+        Finish,
+        Interrupt,
+        Error,
+    }
+    struct Probe {
+        outcome: Outcome,
+        seen: Vec<(i64, DefRegionKind)>,
+        regions: Vec<DefRegionKind>,
+    }
+    impl Probe {
+        fn record(&mut self, value: i64, kind: DefRegionKind) -> 
Result<Option<VisitInterrupt>> {
+            self.seen.push((value, kind));
+            if value == 1 {
+                match self.outcome {
+                    Outcome::Interrupt => return 
Ok(Some(VisitInterrupt::with(42_i64))),
+                    Outcome::Error => return Err(runtime_error("continuation 
failed")),
+                    Outcome::Finish => {}
+                }
+            }
+            Ok(None)
+        }
+    }
+    #[dispatch(walk)]
+    impl Probe {
+        fn walk_integer(&mut self, value: i64, kind: DefRegionKind) -> 
Result<WalkResult> {
+            Ok(match self.record(value, kind)? {
+                Some(interrupt) => WalkResult::InterruptWith(interrupt.value),
+                None => WalkResult::Advance,
+            })
+        }
+    }
+    struct Scope(DefRegionKind, i64);
+    impl VisitPolicy<Probe> for Scope {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            ctx: &mut VisitContext<'_, Probe>,
+        ) -> Result<Option<VisitInterrupt>> {
+            let Some(array) = value.cast::<Array<i64>>() else {
+                return ctx.visit_children();
+            };
+            let outer = ctx.def_region_kind();
+            ctx.state_mut().regions.push(outer);
+            let result = ctx.default_visit_children(&array, self.0);
+            assert_eq!(ctx.def_region_kind(), outer);
+            // Re-dispatch checks the restored ABI region, not only the 
context.
+            assert!(ctx.visit(&self.1)?.is_none());
+            result
+        }
+    }
+    use DefRegionKind::{None as Use, Pattern, Simple};
+    let root = Array::new(vec![1_i64, 5]);
+    for outcome in [Outcome::Finish, Outcome::Interrupt, Outcome::Error] {
+        for order in [None, Some(WalkOrder::PreOrder), 
Some(WalkOrder::PostOrder)] {
+            let state = Probe {
+                outcome,
+                seen: vec![],
+                regions: vec![],
+            };
+            let policies = (Scope(Simple, 4), (Scope(Pattern, 3), Scope(Use, 
2)));
+            let (result, state) = if let Some(order) = order {
+                let mut walker = WalkWithPolicy::new(state, policies);
+                let result = walker.walk(&root, order);
+                (result, walker.into_state())
+            } else {
+                let mut visitor =
+                    VisitCallbacks::new(state, |value: i64, ctx: &mut 
VisitContext<'_, Probe>| {
+                        let kind = ctx.def_region_kind();
+                        ctx.state_mut().record(value, kind)
+                    })
+                    .with_policy(policies);
+                let result = structural_visit(&root, &mut visitor);
+                (result, visitor.into_state())
+            };
+            match outcome {
+                Outcome::Finish => assert!(result.unwrap().is_none()),
+                Outcome::Interrupt => {
+                    
assert_eq!(i64::try_from(result.unwrap().unwrap().value).unwrap(), 42)
+                }
+                Outcome::Error => assert!(result
+                    .err()
+                    .unwrap()
+                    .to_string()
+                    .contains("continuation failed")),
+            }
+            assert_eq!(state.regions, vec![Use, Simple, Pattern]);
+            let mut expected = vec![(1, Pattern)];
+            if matches!(outcome, Outcome::Finish) {
+                expected.push((5, Pattern)); // Halts must skip the remaining 
child.
+            }
+            // Unwind the innermost, middle, and outer scopes.
+            expected.extend([(2, Pattern), (3, Simple), (4, Use)]);
+            assert_eq!(state.seen, expected);
+        }
+    }
+}
+
+fn visit_region_graph(with_hook: bool) -> Any {
+    assert_eq!(
+        unsafe { tvm_ffi::tvm_ffi_sys::TVMFFITestingDummyTarget() },
+        0
+    );
+    Function::get_global("testing.make_visit_region_graph")
+        .unwrap()
+        .call_tuple((with_hook,))
+        .unwrap()
+}
+
+#[derive(Default)]
+struct PolicyRegionTrace(Vec<(i64, DefRegionKind)>);
+#[dispatch(walk)]
+impl PolicyRegionTrace {
+    fn walk_integer(&mut self, value: i64, kind: DefRegionKind) -> WalkResult {
+        self.0.push((value, kind));
+        WalkResult::Advance
+    }
+}
+
+// Default descent may target a child container without dispatching its own
+// callback, while keeping subsequent policies and child callback dispatch.
+#[test]
+fn policy_continuation_retargets_without_dispatching_the_container() {
+    #[derive(Default)]
+    struct Probe(Vec<(&'static str, i64, DefRegionKind)>, bool);
+
+    #[dispatch(walk)]
+    impl Probe {
+        fn walk_array(&mut self, value: Array<Any>, kind: DefRegionKind) -> 
WalkResult {
+            self.0.push(("array callback", value.len() as i64, kind));
+            if self.1 {
+                WalkResult::Skip
+            } else {
+                WalkResult::Advance
+            }
+        }
+        fn walk_integer(&mut self, value: i64, kind: DefRegionKind) -> 
WalkResult {
+            self.0.push(("integer callback", value, kind));
+            WalkResult::Advance
+        }
+    }
+
+    struct Redirect;
+    impl VisitPolicy<Probe> for Redirect {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            ctx: &mut VisitContext<'_, Probe>,
+        ) -> Result<Option<VisitInterrupt>> {
+            let Some(array) = value.cast::<Array<Any>>() else {
+                return ctx.visit_children();
+            };
+            assert_eq!(
+                array.len(),
+                2,
+                "the redirected array must not restart this policy"
+            );
+            let kind = ctx.def_region_kind();
+            ctx.state_mut().0.push(("before", 2, kind));
+            let target = array.get(0).unwrap();
+            let result = ctx.default_visit_children(&target, 
DefRegionKind::Pattern);
+            assert_eq!(ctx.current().cast::<Array<Any>>().unwrap().len(), 2);
+            assert_eq!(ctx.def_region_kind(), DefRegionKind::None);
+            let kind = ctx.def_region_kind();
+            ctx.state_mut().0.push(("after", 2, kind));
+            result
+        }
+    }
+
+    struct Observe;
+    impl VisitPolicy<Probe> for Observe {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            ctx: &mut VisitContext<'_, Probe>,
+        ) -> Result<Option<VisitInterrupt>> {
+            if let Some(array) = value.cast::<Array<Any>>() {
+                assert_eq!(ctx.current().cast::<Array<Any>>().unwrap().len(), 
1);
+                let kind = ctx.def_region_kind();
+                ctx.state_mut()
+                    .0
+                    .push(("next policy", array.len() as i64, kind));
+            } else if value.cast::<i64>().is_none() {
+                let kind = ctx.def_region_kind();
+                ctx.state_mut().0.push(("reflected policy", 0, kind));
+            }
+            ctx.visit_children()
+        }
+    }
+
+    let root = Array::new(vec![Any::from(Array::new(vec![7_i64])), 
Any::from(99_i64)]);
+    let descent = vec![
+        ("before", 2, DefRegionKind::None),
+        ("next policy", 1, DefRegionKind::Pattern),
+        ("integer callback", 7, DefRegionKind::Pattern),
+        ("after", 2, DefRegionKind::None),
+    ];
+    for order in [WalkOrder::PreOrder, WalkOrder::PostOrder] {
+        for skip in [false, true] {
+            let mut walker = WalkWithPolicy::new(Probe(vec![], skip), 
(Redirect, Observe));
+            assert!(walker.walk(&root, order).unwrap().is_none());
+            let mut expected = descent.clone();
+            let array_callback = ("array callback", 2, DefRegionKind::None);
+            match order {
+                WalkOrder::PreOrder => {
+                    if skip {
+                        expected.clear(); // Skip prevents policy entry as 
well as child traversal.
+                    }
+                    expected.insert(0, array_callback);
+                }
+                WalkOrder::PostOrder => expected.push(array_callback),
+            }
+            assert_eq!(walker.state().0, expected);
+        }
+    }
+    let mut visitor = VisitCallbacks::new(
+        Probe::default(),
+        |value: &VisitValue, ctx: &mut VisitContext<'_, Probe>| {
+            let kind = ctx.def_region_kind();
+            if let Some(array) = value.cast::<Array<Any>>() {
+                ctx.state_mut()
+                    .0
+                    .push(("array callback", array.len() as i64, kind));
+                ctx.visit_children()
+            } else {
+                ctx.state_mut()
+                    .0
+                    .push(("integer callback", value.cast::<i64>().unwrap(), 
kind));
+                Ok(None)
+            }
+        },
+    )
+    .with_policy((Redirect, Observe));
+    assert!(structural_visit(&root, &mut visitor).unwrap().is_none());
+    let mut expected = descent;
+    expected.insert(0, ("array callback", 2, DefRegionKind::None));
+    assert_eq!(visitor.state().0, expected);
+
+    // A callback can also retarget default descent, entering the configured
+    // policy. This target uses reflection instead of the Array hook above.
+    let target = visit_region_graph(false);
+    let mut visitor = VisitCallbacks::new(
+        Probe::default(),
+        |value: &VisitValue, ctx: &mut VisitContext<'_, Probe>| {
+            if value.cast::<i64>() == Some(-1) {
+                assert!(ctx
+                    .default_visit_children(&Any::new(), 
DefRegionKind::Pattern)?
+                    .is_none());
+                let result = ctx.default_visit_children(&target, 
DefRegionKind::Pattern);
+                assert_eq!(ctx.current().cast::<i64>(), Some(-1));
+                assert_eq!(ctx.def_region_kind(), DefRegionKind::None);
+                return result;
+            }
+            // A callback on the reflected target itself would fail this cast.
+            let integer = value.cast::<i64>().unwrap();
+            let kind = ctx.def_region_kind();
+            ctx.state_mut().0.push(("integer callback", integer, kind));
+            Ok(None)
+        },
+    )
+    .with_policy(Observe);
+    assert!(structural_visit(&-1_i64, &mut visitor).unwrap().is_none());
+    assert_eq!(
+        visitor.state().0,
+        vec![
+            ("reflected policy", 0, DefRegionKind::Pattern),
+            ("integer callback", 1, DefRegionKind::Pattern),
+            ("integer callback", 2, DefRegionKind::Pattern),
+            ("integer callback", 3, DefRegionKind::Pattern),
+        ]
+    );
+}
+
+#[test]
+fn policy_halts_skip_remaining_policies_and_restore_outer_state() {
+    #[derive(Default)]
+    struct Probe {
+        events: Vec<&'static str>,
+        depth: usize,
+    }
+    #[dispatch(walk)]
+    impl Probe {
+        fn walk_any(&mut self, value: &VisitValue, kind: DefRegionKind) -> 
WalkResult {
+            assert!(
+                value.cast::<Array<i64>>().is_some(),
+                "children must not be visited"
+            );
+            assert_eq!(kind, DefRegionKind::None);
+            self.events.push("callback");
+            WalkResult::Advance
+        }
+    }
+    struct Scope;
+    impl VisitPolicy<Probe> for Scope {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            ctx: &mut VisitContext<'_, Probe>,
+        ) -> Result<Option<VisitInterrupt>> {
+            ctx.state_mut().events.push("enter");
+            ctx.state_mut().depth += 1;
+            let result = ctx.default_visit_children(&value.to_owned(), 
DefRegionKind::Pattern);
+            assert_eq!(ctx.def_region_kind(), DefRegionKind::None);
+            ctx.state_mut().depth -= 1;
+            ctx.state_mut().events.push("exit");
+            result
+        }
+    }
+    struct Stop(bool);
+    impl VisitPolicy<Probe> for Stop {
+        fn default_visit(
+            &self,
+            _: &VisitValue,
+            ctx: &mut VisitContext<'_, Probe>,
+        ) -> Result<Option<VisitInterrupt>> {
+            assert_eq!(ctx.def_region_kind(), DefRegionKind::Pattern);
+            assert_eq!(ctx.state().depth, 1);
+            ctx.state_mut().events.push("stop");
+            if self.0 {
+                Err(runtime_error("policy failed"))
+            } else {
+                Ok(Some(VisitInterrupt::with(42_i64)))
+            }
+        }
+    }
+    struct Unreachable;
+    impl VisitPolicy<Probe> for Unreachable {
+        fn default_visit(
+            &self,
+            _: &VisitValue,
+            _: &mut VisitContext<'_, Probe>,
+        ) -> Result<Option<VisitInterrupt>> {
+            panic!("the policy after Stop must not run")
+        }
+    }
+    let root = Array::new(vec![1_i64]);
+    for error in [false, true] {
+        for order in [None, Some(WalkOrder::PreOrder), 
Some(WalkOrder::PostOrder)] {
+            let policies = (Scope, (Stop(error), Unreachable));
+            let (result, state) = if let Some(order) = order {
+                let mut walker = WalkWithPolicy::new(Probe::default(), 
policies);
+                let result = walker.walk(&root, order);
+                (result, walker.into_state())
+            } else {
+                let mut visitor = VisitCallbacks::new(
+                    Probe::default(),
+                    |value: &VisitValue, ctx: &mut VisitContext<'_, Probe>| {
+                        let kind = ctx.def_region_kind();
+                        ctx.state_mut().walk_any(value, kind);
+                        ctx.visit_children()
+                    },
+                )
+                .with_policy(policies);
+                let result = structural_visit(&root, &mut visitor);
+                (result, visitor.into_state())
+            };
+            if error {
+                assert!(result.err().unwrap().to_string().contains("policy 
failed"));
+            } else {
+                
assert_eq!(i64::try_from(result.unwrap().unwrap().value).unwrap(), 42);
+            }
+            let expected = if order == Some(WalkOrder::PostOrder) {
+                vec!["enter", "stop", "exit"]
+            } else {
+                vec!["callback", "enter", "stop", "exit"]
+            };
+            assert_eq!(state.events, expected);
+            assert_eq!(state.depth, 0);
+        }
+    }
+}
+
+#[test]
+fn policy_regions_compose_with_field_flags_and_function_hooks() {
+    struct SetRootRegion(i32, DefRegionKind);
+    impl VisitPolicy<PolicyRegionTrace> for SetRootRegion {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            ctx: &mut VisitContext<'_, PolicyRegionTrace>,
+        ) -> Result<Option<VisitInterrupt>> {
+            if value.type_index() == self.0 {
+                ctx.default_visit_children(&value.to_owned(), self.1)
+            } else {
+                ctx.visit_children()
+            }
+        }
+    }
+    use DefRegionKind::{None as Use, Pattern, Simple};
+    for with_hook in [false, true] {
+        let root = visit_region_graph(with_hook);
+        if with_hook {
+            // Exercise the Function-valued __s_visit__ path, not an opaque 
pointer hook.
+            assert!(
+                Function::try_from(get_type_attr(root.type_index(), 
"__s_visit__").unwrap())
+                    .is_ok()
+            );
+        }
+        for region in [Use, Simple, Pattern] {
+            let ordinary = if with_hook && region == Use {
+                Simple
+            } else {
+                region
+            };
+            let mut expected = vec![
+                (1, if region == Pattern { Pattern } else { Simple }),
+                (2, Pattern),
+                (3, ordinary),
+            ];
+            if with_hook {
+                expected.push((4, region));
+            }
+            for order in [None, Some(WalkOrder::PreOrder), 
Some(WalkOrder::PostOrder)] {
+                let policy = SetRootRegion(root.type_index(), region);
+                let state = if let Some(order) = order {
+                    let mut walker = 
WalkWithPolicy::new(PolicyRegionTrace::default(), policy);
+                    assert!(walker.walk(&root, order).unwrap().is_none());
+                    walker.into_state()
+                } else {
+                    let mut visitor = VisitCallbacks::new(
+                        PolicyRegionTrace::default(),
+                        |value: i64, ctx: &mut VisitContext<'_, 
PolicyRegionTrace>| {
+                            let kind = ctx.def_region_kind();
+                            ctx.state_mut().0.push((value, kind));
+                        },
+                    )
+                    .with_policy(policy);
+                    assert!(structural_visit(&root, &mut 
visitor).unwrap().is_none());
+                    visitor.into_state()
+                };
+                assert_eq!(state.0, expected);
+            }
+        }
+    }
+}
+
+#[test]
+fn walk_policy_preserves_reflected_pattern_before_default_descent() {
+    let root = visit_region_graph(false);
+    struct Reenter(DefRegionKind);
+    impl VisitPolicy<PolicyRegionTrace> for Reenter {
+        fn default_visit(
+            &self,
+            value: &VisitValue,
+            ctx: &mut VisitContext<'_, PolicyRegionTrace>,
+        ) -> Result<Option<VisitInterrupt>> {
+            match value.cast::<i64>() {
+                Some(2) => {
+                    assert_eq!(ctx.def_region_kind(), DefRegionKind::Pattern);
+                    // Re-dispatch immediately: visit_children() must not be 
needed
+                    // to synchronize the ABI visitor with the reflected field 
region.
+                    ctx.visit_with(&99_i64, self.0)
+                }
+                Some(3) => {
+                    assert_eq!(ctx.def_region_kind(), DefRegionKind::None);
+                    // The preceding field's Pattern scope must not leak into 
a sibling.
+                    ctx.visit(&100_i64)
+                }
+                _ => ctx.visit_children(),
+            }
+        }
+    }
+    use DefRegionKind::{None as Use, Pattern, Simple};
+    for requested in [Use, Simple] {
+        for order in [WalkOrder::PreOrder, WalkOrder::PostOrder] {
+            let mut walker = WalkWithPolicy::new(PolicyRegionTrace::default(), 
Reenter(requested));
+            assert!(walker.walk(&root, order).unwrap().is_none());
+            let expected = match order {
+                WalkOrder::PreOrder => vec![
+                    (1, Simple),
+                    (2, Pattern),
+                    (99, Pattern),
+                    (3, Use),
+                    (100, Use),
+                ],
+                WalkOrder::PostOrder => vec![
+                    (1, Simple),
+                    (99, Pattern),
+                    (2, Pattern),
+                    (100, Use),
+                    (3, Use),
+                ],
+            };
+            assert_eq!(
+                walker.state().0,
+                expected,
+                "order={order:?}, requested={requested:?}"
+            );
+        }
+    }
+}
+
 #[test]
 fn public_reflection_access_uses_registered_field_and_type_attr() {
     // Keep the existing C++ test library linked so its startup registrations
@@ -328,10 +923,7 @@ 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::Simple, DefRegionKind::None]
-    );
+    assert_eq!(probe.seen, vec![DefRegionKind::Simple, DefRegionKind::None]);
 }
 
 #[derive(Default)]
diff --git a/src/ffi/testing/testing.cc b/src/ffi/testing/testing.cc
index 742ca9d0..cda1078e 100644
--- a/src/ffi/testing/testing.cc
+++ b/src/ffi/testing/testing.cc
@@ -30,6 +30,7 @@
 #include <tvm/ffi/dtype.h>
 #include <tvm/ffi/enum.h>
 #include <tvm/ffi/extra/c_env_api.h>
+#include <tvm/ffi/extra/structural_visit.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/optional.h>
 #include <tvm/ffi/reflection/accessor.h>
@@ -146,6 +147,49 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   
refl::ObjectDef<TestCxxStrEnumObj>(refl::init(false)).def_convert<TestCxxStrEnum>();
 }
 
+// Structural traversal fixtures shared by language binding tests.
+class TestVisitRegionsObj : public Object {
+ public:
+  int64_t simple = 1;
+  int64_t pattern = 2;
+  int64_t ordinary = 3;
+
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestVisitRegions", 
TestVisitRegionsObj, Object);
+};
+
+class TestVisitHookObj : public Object {
+ public:
+  ObjectRef child;
+
+  explicit TestVisitHookObj(ObjectRef child) : child(std::move(child)) {}
+
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("testing.TestVisitHook", TestVisitHookObj, 
Object);
+};
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  refl::ObjectDef<TestVisitRegionsObj>()
+      .def_ro("simple", &TestVisitRegionsObj::simple, 
refl::AttachFieldFlag::SEqHashDefSimple())
+      .def_ro("pattern", &TestVisitRegionsObj::pattern, 
refl::AttachFieldFlag::SEqHashDefPattern())
+      .def_ro("ordinary", &TestVisitRegionsObj::ordinary);
+  refl::ObjectDef<TestVisitHookObj>().def_ro("child", 
&TestVisitHookObj::child);
+  refl::EnsureTypeAttrColumn(refl::type_attr::kStructuralVisit);
+  refl::TypeAttrDef<TestVisitHookObj>().def(
+      refl::type_attr::kStructuralVisit,
+      [](StructuralVisitor visitor, const TestVisitHookObj* self) {
+        auto result = visitor->WithDefRegionKind(kTVMFFIDefRegionKindSimple,
+                                                 [&] { return 
visitor->Visit(self->child); });
+        if (result.has_value()) return result;
+        // This sibling observes the region restored after the scoped child 
visit.
+        return visitor->Visit(int64_t{4});
+      });
+  refl::GlobalDef().def("testing.make_visit_region_graph", [](bool with_hook) 
-> Any {
+    ObjectRef fields(make_object<TestVisitRegionsObj>());
+    if (with_hook) return ObjectRef(make_object<TestVisitHookObj>(fields));
+    return fields;
+  });
+}
+
 class TestObjectBase : public Object {
  public:
   int64_t v_i64;

Reply via email to