Skip to main content

tvm_ffi/extra/
structural_visit.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20//! Native Rust structural visiting.
21//!
22//! Two public layers mirror the C++ API split:
23//!
24//! * [`StructuralVisitor`] + [`structural_visit`] — the visitor drives
25//!   recursion itself, like a hand-written C++ `StructuralVisitorObj`:
26//!   [`StructuralVisitor::visit`] runs once per reached value and descends
27//!   only where it calls [`StructuralVisitor::default_visit_children`] or
28//!   [`StructuralVisitor::visit_child`]. `#[dispatch(visit)]` generates this
29//!   trait from typed `visit_*` methods.
30//! * [`WalkDispatch`] + [`structural_walk`] — observer callbacks, like C++
31//!   `StructuralWalk`: the walker recurses on its own and callbacks steer it
32//!   through the returned [`WalkResult`] (advance, skip, interrupt).
33//!
34//! Both layers thread the definition-region state explicitly: walk handlers
35//! opt in with a trailing [`DefRegionKind`] argument, and a visitor receives
36//! and forwards it when descending.
37//!
38//! Underneath both, [`StructuralView`] provides borrowed matching for typed Rust
39//! dispatch. Rust supplies a temporary `ffi.StructuralVisitor` ABI object so
40//! every type's registered `__s_visit__` hook can enumerate its children and
41//! call back into the active Rust visitor. Types without a hook fall back to
42//! reflected structural fields, matching the C++ protocol.
43
44use std::cell::Cell;
45use std::marker::PhantomData;
46use std::ops::ControlFlow;
47use std::os::raw::c_void;
48use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
49use std::ptr::NonNull;
50use std::rc::Rc;
51use std::sync::atomic::{AtomicUsize, Ordering};
52use std::sync::LazyLock;
53
54use crate::any::{Any, AnyView};
55use crate::error::{Error, Result, RUNTIME_ERROR, TYPE_ERROR};
56use crate::function::Function;
57use crate::object::{Object, ObjectArc, ObjectCore};
58use crate::reflection::TypeAttrColumn;
59use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
60    kTVMFFIFieldFlagBitMaskSEqHashDefPattern, kTVMFFIFieldFlagBitMaskSEqHashDefSimple,
61    kTVMFFIFieldFlagBitMaskSEqHashIgnore,
62};
63use crate::tvm_ffi_sys::{
64    TVMFFIAny, TVMFFIByteArray, TVMFFIDefRegionKind, TVMFFIFieldInfo, TVMFFIGetTypeInfo,
65    TVMFFIObject, TVMFFISEqHashKind, TVMFFITypeAttrColumn, TVMFFITypeIndex, TVMFFITypeKeyToIndex,
66};
67
68use super::structural_common::{
69    impl_callback_chain_tuple_arities, with_structural_error_context, with_visit_error_context,
70};
71
72const STRUCTURAL_VISIT_ATTR: &str = "__s_visit__";
73const FLAG_SEQ_HASH_IGNORE: i64 = kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
74const FLAG_SEQ_HASH_DEF_PATTERN: i64 = kTVMFFIFieldFlagBitMaskSEqHashDefPattern as i64;
75const FLAG_SEQ_HASH_DEF_SIMPLE: i64 = kTVMFFIFieldFlagBitMaskSEqHashDefSimple as i64;
76
77/// What a callback asks the Rust walker to do with the current value.
78pub enum WalkResult {
79    /// Continue and visit this value's children.
80    Advance,
81    /// Continue without visiting this value's children or firing its exit hook.
82    Skip,
83    /// Halt the entire traversal.
84    Interrupt,
85    /// Halt the entire traversal and return a payload to the caller.
86    InterruptWith(Any),
87}
88
89impl WalkResult {
90    /// Halt traversal with an FFI-compatible payload.
91    pub fn interrupt_with<T: Into<Any>>(payload: T) -> Self {
92        Self::InterruptWith(payload.into())
93    }
94}
95
96/// Convert either an infallible or fallible typed handler result.
97///
98/// This keeps simple handlers terse while allowing a handler to return
99/// `tvm_ffi::Result<WalkResult>` and use `?`.
100pub trait IntoWalkResult {
101    fn into_walk_result(self) -> Result<WalkResult>;
102}
103
104impl IntoWalkResult for WalkResult {
105    fn into_walk_result(self) -> Result<WalkResult> {
106        Ok(self)
107    }
108}
109
110impl IntoWalkResult for Result<WalkResult> {
111    fn into_walk_result(self) -> Result<WalkResult> {
112        self
113    }
114}
115
116/// Callback order for [`structural_walk`].
117#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
118pub enum WalkOrder {
119    /// Run the typed handler before the current value's children.
120    #[default]
121    PreOrder,
122    /// Run the typed handler after the current value's children.
123    PostOrder,
124}
125
126/// Definition-region state active at the current value.
127///
128/// Reflected fields marked `SEqHashDefPattern` or
129/// `SEqHashDefSimple` override the inherited state for that field's
130/// complete recursive visit.
131#[repr(i32)]
132#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
133pub enum DefRegionKind {
134    /// The value is outside a definition region.
135    #[default]
136    None = 0,
137    /// Definitions apply recursively through the visited value.
138    Pattern = 1,
139    /// Definitions apply to the visited value alone; its type is walked as uses.
140    Simple = 2,
141}
142
143const _: () = {
144    assert!(DefRegionKind::None as i32 == TVMFFIDefRegionKind::kTVMFFIDefRegionKindNone as i32);
145    assert!(
146        DefRegionKind::Pattern as i32 == TVMFFIDefRegionKind::kTVMFFIDefRegionKindPattern as i32
147    );
148    assert!(DefRegionKind::Simple as i32 == TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32);
149};
150
151/// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
152///
153/// Entry points and visitor-layer calls return
154/// `Result<Option<VisitInterrupt>>`: `Ok(None)` means the (sub)graph was
155/// visited completely, `Ok(Some(..))` means a handler halted the traversal
156/// with this interrupt, and `Err` means it failed.
157pub struct VisitInterrupt {
158    /// Payload returned with the interrupt, or FFI `None` for no payload.
159    pub value: Any,
160}
161
162impl VisitInterrupt {
163    /// Interrupt carrying an FFI-compatible payload.
164    pub fn with<T: Into<Any>>(payload: T) -> Self {
165        Self {
166            value: payload.into(),
167        }
168    }
169}
170
171/// Convert a callback result into structural-visit completion state.
172#[doc(hidden)]
173pub trait IntoVisitResult {
174    fn into_visit_result(self) -> Result<Option<VisitInterrupt>>;
175}
176
177impl IntoVisitResult for () {
178    #[inline]
179    fn into_visit_result(self) -> Result<Option<VisitInterrupt>> {
180        Ok(None)
181    }
182}
183
184impl IntoVisitResult for Result<()> {
185    #[inline]
186    fn into_visit_result(self) -> Result<Option<VisitInterrupt>> {
187        self.map(|()| None)
188    }
189}
190
191impl IntoVisitResult for Option<VisitInterrupt> {
192    #[inline]
193    fn into_visit_result(self) -> Result<Option<VisitInterrupt>> {
194        Ok(self)
195    }
196}
197
198impl IntoVisitResult for Result<Option<VisitInterrupt>> {
199    #[inline]
200    fn into_visit_result(self) -> Result<Option<VisitInterrupt>> {
201        self
202    }
203}
204
205pub use super::StructuralView;
206
207enum NativeHalt {
208    Interrupt(Any),
209    Error(Error),
210}
211
212impl From<Error> for NativeHalt {
213    fn from(error: Error) -> Self {
214        NativeHalt::Error(error)
215    }
216}
217
218type NativeResult = std::result::Result<(), NativeHalt>;
219
220mod policy;
221pub use policy::{ContextPolicy, DefaultContextPolicy, WalkWithContextPolicy};
222
223/// State and recursive operations available to a visit callback.
224///
225/// A matched callback owns traversal of its value. Recursive operations
226/// reborrow the visitor, so mutable state cannot remain borrowed across them.
227pub struct VisitContext<'a, State> {
228    driver: &'a mut dyn VisitContextDriver<State>,
229    current: StructuralView,
230    def_region_kind: DefRegionKind,
231    _not_send_sync: PhantomData<Rc<()>>,
232}
233
234trait VisitContextDriver<State> {
235    fn state(&self) -> &State;
236    fn state_mut(&mut self) -> &mut State;
237    fn visit_raw(
238        &mut self,
239        raw: TVMFFIAny,
240        def_region_kind: DefRegionKind,
241    ) -> Result<Option<VisitInterrupt>>;
242    fn visit_children_raw(
243        &mut self,
244        raw: TVMFFIAny,
245        def_region_kind: DefRegionKind,
246    ) -> Result<Option<VisitInterrupt>>;
247}
248
249impl<State> VisitContext<'_, State> {
250    /// User state shared by every callback in this traversal.
251    pub fn state(&self) -> &State {
252        self.driver.state()
253    }
254
255    /// Mutably borrow the user state.
256    pub fn state_mut(&mut self) -> &mut State {
257        self.driver.state_mut()
258    }
259
260    /// Complete borrowed value active at this callback.
261    pub fn current(&self) -> &StructuralView {
262        &self.current
263    }
264
265    /// Definition-region state active at the callback's current value.
266    pub fn def_region_kind(&self) -> DefRegionKind {
267        self.def_region_kind
268    }
269
270    /// Visit `child` using the current definition-region state.
271    pub fn visit<T>(&mut self, child: &T) -> Result<Option<VisitInterrupt>>
272    where
273        for<'x> AnyView<'x>: From<&'x T>,
274    {
275        self.visit_with(child, self.def_region_kind)
276    }
277
278    /// Visit `child` using an explicit definition-region state.
279    pub fn visit_with<T>(
280        &mut self,
281        child: &T,
282        def_region_kind: DefRegionKind,
283    ) -> Result<Option<VisitInterrupt>>
284    where
285        for<'x> AnyView<'x>: From<&'x T>,
286    {
287        let raw = raw_of(AnyView::from(child));
288        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
289            return Ok(None);
290        }
291        self.driver.visit_raw(raw, def_region_kind)
292    }
293
294    /// Apply default descent without dispatching the current value again.
295    /// A callback enters its configured policy; within a policy this continues
296    /// with the next policy, then registered hooks or reflected fields.
297    pub fn visit_children(&mut self) -> Result<Option<VisitInterrupt>> {
298        self.default_visit_children_raw(self.current.raw(), self.def_region_kind)
299    }
300
301    /// Apply default descent to `value` under an explicit definition region.
302    ///
303    /// Like [`Self::visit_children`], this continues with the next policy (or
304    /// enters the configured policy from a callback), then registered hooks or
305    /// reflected fields. It does not dispatch callbacks for `value` itself;
306    /// its children re-enter the full callback engine.
307    ///
308    /// An enclosing [`DefRegionKind::Pattern`] cannot be downgraded. The current
309    /// value and region of this context are unchanged after the call, including
310    /// when descent returns an error or interrupt.
311    pub fn default_visit_children<T>(
312        &mut self,
313        value: &T,
314        def_region_kind: DefRegionKind,
315    ) -> Result<Option<VisitInterrupt>>
316    where
317        for<'x> AnyView<'x>: From<&'x T>,
318    {
319        self.default_visit_children_raw(raw_of(AnyView::from(value)), def_region_kind)
320    }
321
322    fn default_visit_children_raw(
323        &mut self,
324        raw: TVMFFIAny,
325        def_region_kind: DefRegionKind,
326    ) -> Result<Option<VisitInterrupt>> {
327        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
328            return Ok(None);
329        }
330        let kind = if self.def_region_kind == DefRegionKind::Pattern {
331            DefRegionKind::Pattern
332        } else {
333            def_region_kind
334        };
335        let active = active_structural_visitor()?;
336        // Keep the ABI visitor in sync while policies run, not only inside
337        // hooks: a subsequent visit_with must also preserve a pattern region.
338        with_visitor_def_region(active, kind, || self.driver.visit_children_raw(raw, kind))
339    }
340}
341
342/// Conversion into the visitor argument accepted by [`structural_visit`].
343///
344/// Accepts a mutable [`StructuralVisitor`] or a first-match callback chain.
345/// Use [`VisitCallbacks`] when the chain needs mutable state.
346#[diagnostic::on_unimplemented(
347    message = "`{Self}` is not a supported `structural_visit` visitor",
348    note = "accepted visitors: `&mut V` where `V: StructuralVisitor`; an `Fn` callback over an FFI value type `T`, `&N` of an object node type, or `&StructuralView`, followed by `&mut VisitContext<'_, ()>`; or a tuple of up to 12 such callbacks (tuples may nest)",
349    note = "callback arguments need explicit type annotations; use `VisitCallbacks::new(state, callbacks)` for ordinary mutable callback state"
350)]
351pub trait IntoVisitor<Marker> {
352    #[doc(hidden)]
353    fn visit_root(self, root: TVMFFIAny) -> Result<Option<VisitInterrupt>>;
354}
355
356impl<V: StructuralVisitor> IntoVisitor<V> for &mut V {
357    fn visit_root(self, root: TVMFFIAny) -> Result<Option<VisitInterrupt>> {
358        finish(run_structural_visitor(
359            root,
360            self,
361            user_runtime_vtable::<V>(),
362        ))
363    }
364}
365
366/// One link in a first-match visitor callback chain.
367pub trait VisitChainLink<State, Marker>: visit_sealed::SealedLink<State, Marker> {
368    #[doc(hidden)]
369    fn try_visit(
370        &self,
371        value: &StructuralView,
372        visitor: &mut VisitContext<'_, State>,
373    ) -> Option<Result<Option<VisitInterrupt>>>;
374}
375
376mod visit_sealed {
377    use super::{IntoVisitResult, ObjectCore, StructuralView, VisitContext};
378
379    pub trait SealedLink<State, Marker> {}
380
381    impl<F, State, T, O> SealedLink<State, super::ByVisitOwnedLink<T>> for F
382    where
383        F: for<'visitor, 'driver> Fn(T, &'visitor mut VisitContext<'driver, State>) -> O,
384        O: IntoVisitResult,
385    {
386    }
387
388    impl<F, State, N: ObjectCore, O> SealedLink<State, super::ByVisitNodeLink<N>> for F
389    where
390        F: for<'value, 'visitor, 'driver> Fn(
391            &'value N,
392            &'visitor mut VisitContext<'driver, State>,
393        ) -> O,
394        O: IntoVisitResult,
395    {
396    }
397
398    impl<F, State, O> SealedLink<State, super::ByVisitCatchAllLink> for F
399    where
400        F: for<'value, 'visitor, 'driver> Fn(
401            &'value StructuralView,
402            &'visitor mut VisitContext<'driver, State>,
403        ) -> O,
404        O: IntoVisitResult,
405    {
406    }
407}
408
409#[doc(hidden)]
410pub struct ByVisitOwnedLink<T>(PhantomData<T>);
411
412impl<F, State, T, O> VisitChainLink<State, ByVisitOwnedLink<T>> for F
413where
414    F: for<'visitor, 'driver> Fn(T, &'visitor mut VisitContext<'driver, State>) -> O,
415    T: crate::type_traits::AnyCompatible,
416    O: IntoVisitResult,
417{
418    fn try_visit(
419        &self,
420        value: &StructuralView,
421        visitor: &mut VisitContext<'_, State>,
422    ) -> Option<Result<Option<VisitInterrupt>>> {
423        value
424            .cast::<T>()
425            .map(|typed| self(typed, visitor).into_visit_result())
426    }
427}
428
429#[doc(hidden)]
430pub struct ByVisitNodeLink<N>(PhantomData<N>);
431
432impl<F, State, N, O> VisitChainLink<State, ByVisitNodeLink<N>> for F
433where
434    F: for<'value, 'visitor, 'driver> Fn(
435        &'value N,
436        &'visitor mut VisitContext<'driver, State>,
437    ) -> O,
438    N: ObjectCore,
439    O: IntoVisitResult,
440{
441    fn try_visit(
442        &self,
443        value: &StructuralView,
444        visitor: &mut VisitContext<'_, State>,
445    ) -> Option<Result<Option<VisitInterrupt>>> {
446        value
447            .as_node::<N>()
448            .map(|node| self(node, visitor).into_visit_result())
449    }
450}
451
452#[doc(hidden)]
453pub enum ByVisitCatchAllLink {}
454
455impl<F, State, O> VisitChainLink<State, ByVisitCatchAllLink> for F
456where
457    F: for<'value, 'visitor, 'driver> Fn(
458        &'value StructuralView,
459        &'visitor mut VisitContext<'driver, State>,
460    ) -> O,
461    O: IntoVisitResult,
462{
463    fn try_visit(
464        &self,
465        value: &StructuralView,
466        visitor: &mut VisitContext<'_, State>,
467    ) -> Option<Result<Option<VisitInterrupt>>> {
468        Some(self(value, visitor).into_visit_result())
469    }
470}
471
472#[doc(hidden)]
473pub struct ByVisitChainLink<Markers>(PhantomData<fn(Markers)>);
474
475macro_rules! impl_visit_chain_link {
476    ($(($F:ident, $M:ident, $idx:tt)),+) => {
477        impl<State, $($F, $M,)+>
478            visit_sealed::SealedLink<State, ByVisitChainLink<($($M,)+)>> for ($($F,)+)
479        where
480            $($F: VisitChainLink<State, $M>,)+
481        {
482        }
483
484        impl<State, $($F, $M,)+> VisitChainLink<State, ByVisitChainLink<($($M,)+)>>
485            for ($($F,)+)
486        where
487            $($F: VisitChainLink<State, $M>,)+
488        {
489            fn try_visit(
490                &self,
491                value: &StructuralView,
492                visitor: &mut VisitContext<'_, State>,
493            ) -> Option<Result<Option<VisitInterrupt>>> {
494                $(
495                    if let Some(result) = self.$idx.try_visit(value, visitor) {
496                        return Some(result);
497                    }
498                )+
499                None
500            }
501        }
502    };
503}
504
505impl_callback_chain_tuple_arities!(impl_visit_chain_link);
506
507/// A reusable callback visitor with shared user state.
508pub struct VisitCallbacks<State, Link, Marker, Policy = DefaultContextPolicy> {
509    policy: Option<Rc<Policy>>,
510    state: State,
511    callbacks: Rc<Link>,
512    _marker: PhantomData<fn(Marker)>,
513}
514
515impl<State, Link, Marker> VisitCallbacks<State, Link, Marker>
516where
517    Link: VisitChainLink<State, Marker>,
518{
519    /// Construct a stateful callback visitor.
520    pub fn new(state: State, callbacks: Link) -> Self {
521        Self {
522            state,
523            callbacks: Rc::new(callbacks),
524            policy: None,
525            _marker: PhantomData,
526        }
527    }
528}
529
530impl<State, Link, Marker, Policy> VisitCallbacks<State, Link, Marker, Policy> {
531    /// Set the default-recursion policy while retaining the callbacks and state.
532    /// A matched callback enters the policy only when it calls `visit_children()`.
533    pub fn with_policy<P: ContextPolicy<State>>(
534        self,
535        policy: P,
536    ) -> VisitCallbacks<State, Link, Marker, P> {
537        VisitCallbacks {
538            state: self.state,
539            callbacks: self.callbacks,
540            policy: Some(Rc::new(policy)),
541            _marker: PhantomData,
542        }
543    }
544
545    /// Shared access to the callback state.
546    pub fn state(&self) -> &State {
547        &self.state
548    }
549
550    /// Mutable access to the callback state outside an active recursive call.
551    pub fn state_mut(&mut self) -> &mut State {
552        &mut self.state
553    }
554
555    /// Consume the visitor and return its state.
556    pub fn into_state(self) -> State {
557        self.state
558    }
559}
560
561struct DirectVisitCallbacks<'a, Link, Marker> {
562    state: (),
563    callbacks: &'a Link,
564    _marker: PhantomData<fn(Marker)>,
565}
566
567#[doc(hidden)]
568pub trait VisitCallbackState<State> {
569    fn callback_state(&self) -> &State;
570    fn callback_state_mut(&mut self) -> &mut State;
571}
572
573impl<State, Link, Marker, Policy> VisitCallbackState<State>
574    for VisitCallbacks<State, Link, Marker, Policy>
575{
576    fn callback_state(&self) -> &State {
577        &self.state
578    }
579
580    fn callback_state_mut(&mut self) -> &mut State {
581        &mut self.state
582    }
583}
584
585impl<Link, Marker> VisitCallbackState<()> for DirectVisitCallbacks<'_, Link, Marker> {
586    fn callback_state(&self) -> &() {
587        &self.state
588    }
589
590    fn callback_state_mut(&mut self) -> &mut () {
591        &mut self.state
592    }
593}
594
595#[doc(hidden)]
596pub struct ByVisitCallbacks<Marker>(PhantomData<fn(Marker)>);
597
598impl<Link, Marker> IntoVisitor<ByVisitCallbacks<Marker>> for Link
599where
600    Link: VisitChainLink<(), Marker>,
601{
602    fn visit_root(self, root: TVMFFIAny) -> Result<Option<VisitInterrupt>> {
603        let callbacks = self;
604        let mut visitor = DirectVisitCallbacks::<Link, Marker> {
605            state: (),
606            callbacks: &callbacks,
607            _marker: PhantomData,
608        };
609        finish(run_structural_visitor(
610            root,
611            &mut visitor,
612            user_runtime_vtable::<DirectVisitCallbacks<Link, Marker>>(),
613        ))
614    }
615}
616
617// Keep the generated walk-dispatch paths stable.
618pub use super::dispatch::{ByWalkDispatch, DispatchWalker, WalkDispatch};
619
620/// Conversion into the walker argument of [`structural_walk`].
621///
622/// Accepts a mutable [`WalkDispatch`], a typed callback, a nested callback
623/// tuple, or an owned or borrowed [`WalkWithContextPolicy`].
624/// `Marker` distinguishes the supported callback shapes.
625#[diagnostic::on_unimplemented(
626    message = "`{Self}` is not a supported `structural_walk` walker",
627    note = "accepted walkers: `&mut V` where `V: WalkDispatch`; a closure over `&StructuralView`, \
628            an FFI value type `T`, or `&N` of an object node type (`N: ObjectCore`, e.g. \
629            `&Object`), optionally with a trailing `DefRegionKind` argument; or a tuple of \
630            up to 12 such links (tuples nest, so `(a, (b, c))` chains more); \
631            or a `WalkWithContextPolicy` passed by value or mutable reference",
632    note = "closure arguments need explicit type annotations; ObjectRef wrappers like `String` \
633            or `Array<T>` are FFI value types — take them by value, not by reference"
634)]
635pub trait IntoWalker<Marker> {
636    #[doc(hidden)]
637    type Walker: NativeVisit;
638    #[doc(hidden)]
639    fn into_walker(self) -> Self::Walker;
640}
641
642impl<F, O> IntoWalker<ByCatchAllLink> for F
643where
644    F: for<'a> FnMut(&'a StructuralView) -> O,
645    O: IntoWalkResult,
646{
647    type Walker = ChainWalker<F, ByCatchAllLink>;
648    fn into_walker(self) -> Self::Walker {
649        ChainWalker::new(self)
650    }
651}
652
653impl<F, O> IntoWalker<ByCatchAllKindLink> for F
654where
655    F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
656    O: IntoWalkResult,
657{
658    type Walker = ChainWalker<F, ByCatchAllKindLink>;
659    fn into_walker(self) -> Self::Walker {
660        ChainWalker::new(self)
661    }
662}
663
664/// One link in a first-match [`structural_walk`] callback chain.
665///
666/// Supported links are typed values, borrowed object nodes, `&StructuralView`,
667/// and mutable [`WalkDispatch`] implementations, optionally followed by
668/// [`DefRegionKind`]. Tuples hold up to 12 links and may be nested.
669pub trait WalkChainLink<Marker>: sealed::SealedLink<Marker> {
670    /// Run this link if `value` matches its argument type; `None` hands the
671    /// value to the next link.
672    #[doc(hidden)]
673    fn try_call(
674        &mut self,
675        value: &StructuralView,
676        def_region_kind: DefRegionKind,
677    ) -> Option<Result<WalkResult>>;
678}
679
680mod sealed {
681    use super::{DefRegionKind, IntoWalkResult, ObjectCore, StructuralView, WalkDispatch};
682
683    pub trait SealedLink<Marker> {}
684
685    impl<F, T, O> SealedLink<super::ByOwnedLink<T>> for F
686    where
687        F: FnMut(T) -> O,
688        O: IntoWalkResult,
689    {
690    }
691    impl<F, T, O> SealedLink<super::ByOwnedKindLink<T>> for F
692    where
693        F: FnMut(T, DefRegionKind) -> O,
694        O: IntoWalkResult,
695    {
696    }
697    impl<F, N: ObjectCore, O> SealedLink<super::ByNodeLink<N>> for F
698    where
699        F: for<'a> FnMut(&'a N) -> O,
700        O: IntoWalkResult,
701    {
702    }
703    impl<F, N: ObjectCore, O> SealedLink<super::ByNodeKindLink<N>> for F
704    where
705        F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
706        O: IntoWalkResult,
707    {
708    }
709    impl<F, O> SealedLink<super::ByCatchAllLink> for F
710    where
711        F: for<'a> FnMut(&'a StructuralView) -> O,
712        O: IntoWalkResult,
713    {
714    }
715    impl<F, O> SealedLink<super::ByCatchAllKindLink> for F
716    where
717        F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
718        O: IntoWalkResult,
719    {
720    }
721    impl<V: WalkDispatch> SealedLink<super::ByWalkDispatchLink> for &mut V {}
722}
723
724#[doc(hidden)]
725pub struct ByOwnedLink<T>(PhantomData<T>);
726
727impl<F, T, O> WalkChainLink<ByOwnedLink<T>> for F
728where
729    F: FnMut(T) -> O,
730    T: crate::type_traits::AnyCompatible,
731    O: IntoWalkResult,
732{
733    #[inline]
734    fn try_call(
735        &mut self,
736        value: &StructuralView,
737        _def_region_kind: DefRegionKind,
738    ) -> Option<Result<WalkResult>> {
739        value
740            .cast::<T>()
741            .map(|typed| self(typed).into_walk_result())
742    }
743}
744
745#[doc(hidden)]
746pub struct ByOwnedKindLink<T>(PhantomData<T>);
747
748impl<F, T, O> WalkChainLink<ByOwnedKindLink<T>> for F
749where
750    F: FnMut(T, DefRegionKind) -> O,
751    T: crate::type_traits::AnyCompatible,
752    O: IntoWalkResult,
753{
754    #[inline]
755    fn try_call(
756        &mut self,
757        value: &StructuralView,
758        def_region_kind: DefRegionKind,
759    ) -> Option<Result<WalkResult>> {
760        value
761            .cast::<T>()
762            .map(|typed| self(typed, def_region_kind).into_walk_result())
763    }
764}
765
766#[doc(hidden)]
767pub struct ByNodeLink<N>(PhantomData<N>);
768
769impl<F, N, O> WalkChainLink<ByNodeLink<N>> for F
770where
771    F: for<'a> FnMut(&'a N) -> O,
772    N: ObjectCore,
773    O: IntoWalkResult,
774{
775    #[inline]
776    fn try_call(
777        &mut self,
778        value: &StructuralView,
779        _def_region_kind: DefRegionKind,
780    ) -> Option<Result<WalkResult>> {
781        value
782            .as_node::<N>()
783            .map(|node| self(node).into_walk_result())
784    }
785}
786
787#[doc(hidden)]
788pub struct ByNodeKindLink<N>(PhantomData<N>);
789
790impl<F, N, O> WalkChainLink<ByNodeKindLink<N>> for F
791where
792    F: for<'a> FnMut(&'a N, DefRegionKind) -> O,
793    N: ObjectCore,
794    O: IntoWalkResult,
795{
796    #[inline]
797    fn try_call(
798        &mut self,
799        value: &StructuralView,
800        def_region_kind: DefRegionKind,
801    ) -> Option<Result<WalkResult>> {
802        value
803            .as_node::<N>()
804            .map(|node| self(node, def_region_kind).into_walk_result())
805    }
806}
807
808#[doc(hidden)]
809pub enum ByCatchAllLink {}
810
811impl<F, O> WalkChainLink<ByCatchAllLink> for F
812where
813    F: for<'a> FnMut(&'a StructuralView) -> O,
814    O: IntoWalkResult,
815{
816    #[inline]
817    fn try_call(
818        &mut self,
819        value: &StructuralView,
820        _def_region_kind: DefRegionKind,
821    ) -> Option<Result<WalkResult>> {
822        Some(self(value).into_walk_result())
823    }
824}
825
826#[doc(hidden)]
827pub enum ByCatchAllKindLink {}
828
829impl<F, O> WalkChainLink<ByCatchAllKindLink> for F
830where
831    F: for<'a> FnMut(&'a StructuralView, DefRegionKind) -> O,
832    O: IntoWalkResult,
833{
834    #[inline]
835    fn try_call(
836        &mut self,
837        value: &StructuralView,
838        def_region_kind: DefRegionKind,
839    ) -> Option<Result<WalkResult>> {
840        Some(self(value, def_region_kind).into_walk_result())
841    }
842}
843
844#[doc(hidden)]
845pub struct ByChainLink<Markers>(PhantomData<fn(Markers)>);
846
847#[doc(hidden)]
848pub enum ByWalkDispatchLink {}
849
850impl<V: WalkDispatch> WalkChainLink<ByWalkDispatchLink> for &mut V {
851    #[inline]
852    fn try_call(
853        &mut self,
854        value: &StructuralView,
855        def_region_kind: DefRegionKind,
856    ) -> Option<Result<WalkResult>> {
857        self.dispatch_walk(value, def_region_kind)
858    }
859}
860
861/// Adapter from a [`WalkChainLink`] to the native traversal callback.
862#[doc(hidden)]
863pub struct ChainWalker<Link, Marker> {
864    link: Link,
865    marker: PhantomData<fn(Marker)>,
866}
867
868impl<Link, Marker> ChainWalker<Link, Marker> {
869    #[inline]
870    fn new(link: Link) -> Self {
871        ChainWalker {
872            link,
873            marker: PhantomData,
874        }
875    }
876}
877
878impl<Link, Marker> NativeVisit for ChainWalker<Link, Marker>
879where
880    Link: WalkChainLink<Marker>,
881{
882    #[inline]
883    fn visit(
884        &mut self,
885        value: &StructuralView,
886        def_region_kind: DefRegionKind,
887    ) -> Result<WalkResult> {
888        self.link
889            .try_call(value, def_region_kind)
890            .unwrap_or_else(|| Ok(WalkResult::Advance))
891    }
892}
893
894macro_rules! impl_chain_link {
895    ($(($F:ident, $M:ident, $idx:tt)),+) => {
896        impl<$($F, $M,)+> sealed::SealedLink<ByChainLink<($($M,)+)>> for ($($F,)+)
897        where
898            $($F: WalkChainLink<$M>,)+
899        {
900        }
901
902        impl<$($F, $M,)+> WalkChainLink<ByChainLink<($($M,)+)>> for ($($F,)+)
903        where
904            $($F: WalkChainLink<$M>,)+
905        {
906            #[inline]
907            fn try_call(
908                &mut self,
909                value: &StructuralView,
910                def_region_kind: DefRegionKind,
911            ) -> Option<Result<WalkResult>> {
912                $(
913                    if let Some(result) = self.$idx.try_call(value, def_region_kind) {
914                        return Some(result);
915                    }
916                )+
917                None
918            }
919        }
920
921        impl<$($F, $M,)+> IntoWalker<($($M,)+)> for ($($F,)+)
922        where
923            $($F: WalkChainLink<$M>,)+
924        {
925            type Walker = ChainWalker<($($F,)+), ByChainLink<($($M,)+)>>;
926            fn into_walker(self) -> Self::Walker {
927                ChainWalker::new(self)
928            }
929        }
930    };
931}
932
933impl_callback_chain_tuple_arities!(impl_chain_link);
934
935macro_rules! impl_bare_link_walker {
936    ($(($marker:ident, $($fn_args:ty),+)),+ $(,)?) => {
937        $(
938            impl<F, T, O> IntoWalker<$marker<T>> for F
939            where
940                F: FnMut($($fn_args),+) -> O,
941                Self: WalkChainLink<$marker<T>>,
942                O: IntoWalkResult,
943            {
944                type Walker = ChainWalker<F, $marker<T>>;
945                fn into_walker(self) -> Self::Walker {
946                    ChainWalker::new(self)
947                }
948            }
949        )+
950    };
951}
952
953impl_bare_link_walker!(
954    (ByOwnedLink, T),
955    (ByOwnedKindLink, T, DefRegionKind),
956    (ByNodeLink, &T),
957    (ByNodeKindLink, &T, DefRegionKind),
958);
959
960/// A visitor that controls its own recursion.
961///
962/// Implementations descend with [`Self::visit_child`] or
963/// [`Self::default_visit_children`]. `#[dispatch(visit)]` generates this trait
964/// from typed `visit_*` methods.
965pub trait StructuralVisitor: Sized {
966    /// Visit one value under the definition-region state active at it.
967    fn visit(
968        &mut self,
969        value: &StructuralView,
970        def_region_kind: DefRegionKind,
971    ) -> Result<Option<VisitInterrupt>>;
972
973    /// Visit `child` under `def_region_kind`.
974    #[inline]
975    fn visit_child<T>(
976        &mut self,
977        child: &T,
978        def_region_kind: DefRegionKind,
979    ) -> Result<Option<VisitInterrupt>>
980    where
981        for<'x> AnyView<'x>: From<&'x T>,
982    {
983        let raw = raw_of(AnyView::from(child));
984        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
985            return Ok(None);
986        }
987        let active = active_structural_visitor()?;
988        let context = std::ptr::from_mut(self).cast::<c_void>();
989        finish(with_current_visitor_context(active, context, || {
990            call_visitor(active, raw, def_region_kind)
991        }))
992    }
993
994    /// Visit `value`'s children with the default structural rules.
995    #[inline]
996    fn default_visit_children(
997        &mut self,
998        value: &StructuralView,
999        def_region_kind: DefRegionKind,
1000    ) -> Result<Option<VisitInterrupt>> {
1001        default_user_visit_children(self, value, def_region_kind)
1002    }
1003}
1004
1005fn default_user_visit_children<V: StructuralVisitor>(
1006    visitor: &mut V,
1007    value: &StructuralView,
1008    def_region_kind: DefRegionKind,
1009) -> Result<Option<VisitInterrupt>> {
1010    let raw = value.raw();
1011    let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
1012    finish(
1013        visit_children_raw(raw, &mut UserChildren { visitor }, context, def_region_kind)
1014            .map_err(|halt| with_value_context(halt, raw)),
1015    )
1016}
1017
1018#[doc(hidden)]
1019pub fn default_visit_with_policy<V: StructuralVisitor + VisitCallbackState<V>>(
1020    visitor: &mut V,
1021    policy: &impl ContextPolicy<V>,
1022    value: &StructuralView,
1023    kind: DefRegionKind,
1024) -> Result<Option<VisitInterrupt>> {
1025    policy::visit_with_policy(&mut policy::VisitDescent { visitor }, policy, value, kind)
1026}
1027
1028fn try_visit_callbacks<State, Link, Marker>(
1029    driver: &mut impl VisitContextDriver<State>,
1030    callback_ptr: *const Link,
1031    value: &StructuralView,
1032    def_region_kind: DefRegionKind,
1033) -> Result<Option<VisitInterrupt>>
1034where
1035    Link: VisitChainLink<State, Marker>,
1036{
1037    let mut visitor = VisitContext {
1038        driver,
1039        current: StructuralView::from_raw(value.raw()),
1040        def_region_kind,
1041        _not_send_sync: PhantomData,
1042    };
1043    // SAFETY: The owning `Rc` or the direct callback's stack slot remains live
1044    // and is never modified through the driver during recursive reentry.
1045    match unsafe { (&*callback_ptr).try_visit(value, &mut visitor) } {
1046        Some(outcome) => outcome,
1047        None => visitor.visit_children(),
1048    }
1049}
1050
1051impl<State, Link, Marker, Policy> StructuralVisitor for VisitCallbacks<State, Link, Marker, Policy>
1052where
1053    Link: VisitChainLink<State, Marker>,
1054    Policy: ContextPolicy<State>,
1055{
1056    fn visit(
1057        &mut self,
1058        value: &StructuralView,
1059        def_region_kind: DefRegionKind,
1060    ) -> Result<Option<VisitInterrupt>> {
1061        let callback_ptr = Rc::as_ptr(&self.callbacks);
1062        try_visit_callbacks::<State, Link, Marker>(self, callback_ptr, value, def_region_kind)
1063    }
1064
1065    fn default_visit_children(
1066        &mut self,
1067        value: &StructuralView,
1068        def_region_kind: DefRegionKind,
1069    ) -> Result<Option<VisitInterrupt>> {
1070        let Some(policy) = self.policy.as_ref().map(Rc::clone) else {
1071            return default_user_visit_children(self, value, def_region_kind);
1072        };
1073        policy::visit_with_policy(
1074            &mut policy::VisitDescent { visitor: self },
1075            &*policy,
1076            value,
1077            def_region_kind,
1078        )
1079    }
1080}
1081
1082impl<Link, Marker> StructuralVisitor for DirectVisitCallbacks<'_, Link, Marker>
1083where
1084    Link: VisitChainLink<(), Marker>,
1085{
1086    fn visit(
1087        &mut self,
1088        value: &StructuralView,
1089        def_region_kind: DefRegionKind,
1090    ) -> Result<Option<VisitInterrupt>> {
1091        let callback_ptr = std::ptr::from_ref(self.callbacks);
1092        try_visit_callbacks::<(), Link, Marker>(self, callback_ptr, value, def_region_kind)
1093    }
1094}
1095
1096impl<State, Driver> VisitContextDriver<State> for Driver
1097where
1098    Driver: StructuralVisitor + VisitCallbackState<State>,
1099{
1100    fn state(&self) -> &State {
1101        self.callback_state()
1102    }
1103
1104    fn state_mut(&mut self) -> &mut State {
1105        self.callback_state_mut()
1106    }
1107
1108    fn visit_raw(
1109        &mut self,
1110        raw: TVMFFIAny,
1111        def_region_kind: DefRegionKind,
1112    ) -> Result<Option<VisitInterrupt>> {
1113        if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
1114            return Ok(None);
1115        }
1116        let active = active_structural_visitor()?;
1117        let context = std::ptr::from_mut(self).cast::<c_void>();
1118        finish(with_current_visitor_context(active, context, || {
1119            call_visitor(active, raw, def_region_kind)
1120        }))
1121    }
1122
1123    fn visit_children_raw(
1124        &mut self,
1125        raw: TVMFFIAny,
1126        def_region_kind: DefRegionKind,
1127    ) -> Result<Option<VisitInterrupt>> {
1128        <Self as StructuralVisitor>::default_visit_children(
1129            self,
1130            &StructuralView::from_raw(raw),
1131            def_region_kind,
1132        )
1133    }
1134}
1135
1136/// Internal callback protocol used by [`IntoWalker`].
1137#[doc(hidden)]
1138pub trait NativeVisit: Sized {
1139    const CUSTOM_DESCENT: bool = false;
1140
1141    fn walk_root(&mut self, root: AnyView<'_>, order: WalkOrder) -> Result<Option<VisitInterrupt>> {
1142        let root = raw_of(root);
1143        finish(match order {
1144            WalkOrder::PreOrder => {
1145                run_structural_visitor(root, self, walk_runtime_vtable::<Self, true>())
1146            }
1147            WalkOrder::PostOrder => {
1148                run_structural_visitor(root, self, walk_runtime_vtable::<Self, false>())
1149            }
1150        })
1151    }
1152
1153    fn visit(
1154        &mut self,
1155        value: &StructuralView,
1156        def_region_kind: DefRegionKind,
1157    ) -> Result<WalkResult>;
1158
1159    fn default_visit_children<const PRE_ORDER: bool>(
1160        &mut self,
1161        value: &StructuralView,
1162        def_region_kind: DefRegionKind,
1163    ) -> Result<Option<VisitInterrupt>> {
1164        default_walk_children::<Self, PRE_ORDER>(self, value, def_region_kind)
1165    }
1166}
1167
1168impl<V: NativeVisit> NativeVisit for &mut V {
1169    const CUSTOM_DESCENT: bool = V::CUSTOM_DESCENT;
1170
1171    fn walk_root(&mut self, root: AnyView<'_>, order: WalkOrder) -> Result<Option<VisitInterrupt>> {
1172        // Register the actual visitor as the active context, not this reference's
1173        // stack slot: policy continuations validate that identity when reentering.
1174        (**self).walk_root(root, order)
1175    }
1176
1177    fn visit(
1178        &mut self,
1179        value: &StructuralView,
1180        def_region_kind: DefRegionKind,
1181    ) -> Result<WalkResult> {
1182        (**self).visit(value, def_region_kind)
1183    }
1184
1185    fn default_visit_children<const PRE_ORDER: bool>(
1186        &mut self,
1187        value: &StructuralView,
1188        def_region_kind: DefRegionKind,
1189    ) -> Result<Option<VisitInterrupt>> {
1190        (**self).default_visit_children::<PRE_ORDER>(value, def_region_kind)
1191    }
1192}
1193
1194fn default_walk_children<V: NativeVisit, const PRE_ORDER: bool>(
1195    visitor: &mut V,
1196    value: &StructuralView,
1197    def_region_kind: DefRegionKind,
1198) -> Result<Option<VisitInterrupt>> {
1199    let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
1200    finish(visit_children_raw(
1201        value.raw(),
1202        &mut WalkChildren::<V, PRE_ORDER> { visitor },
1203        context,
1204        def_region_kind,
1205    ))
1206}
1207
1208/// Action applied to each child found by the shared traversal.
1209trait ChildVisit {
1210    fn visit_child(
1211        &mut self,
1212        child: &StructuralView,
1213        def_region_kind: DefRegionKind,
1214    ) -> NativeResult;
1215}
1216
1217struct WalkChildren<'a, V, const PRE_ORDER: bool> {
1218    visitor: &'a mut V,
1219}
1220
1221impl<V: NativeVisit, const PRE_ORDER: bool> ChildVisit for WalkChildren<'_, V, PRE_ORDER> {
1222    #[inline(always)]
1223    fn visit_child(
1224        &mut self,
1225        child: &StructuralView,
1226        def_region_kind: DefRegionKind,
1227    ) -> NativeResult {
1228        visit_raw::<V, PRE_ORDER>(child.raw(), self.visitor, def_region_kind)
1229    }
1230}
1231
1232struct UserChildren<'a, V> {
1233    visitor: &'a mut V,
1234}
1235
1236impl<V: StructuralVisitor> ChildVisit for UserChildren<'_, V> {
1237    #[inline]
1238    fn visit_child(
1239        &mut self,
1240        child: &StructuralView,
1241        def_region_kind: DefRegionKind,
1242    ) -> NativeResult {
1243        if child.type_index() == TVMFFITypeIndex::kTVMFFINone as i32 {
1244            return Ok(());
1245        }
1246        match with_visit_region(def_region_kind, |kind| self.visitor.visit(child, kind)) {
1247            Ok(None) => Ok(()),
1248            Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
1249            Err(error) => Err(with_value_context(NativeHalt::Error(error), child.raw())),
1250        }
1251    }
1252}
1253
1254// Every registered container child re-enters this hot path.
1255#[inline(always)]
1256fn visit_raw<V: NativeVisit, const PRE_ORDER: bool>(
1257    value: TVMFFIAny,
1258    visitor: &mut V,
1259    def_region_kind: DefRegionKind,
1260) -> NativeResult {
1261    if value.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
1262        return Ok(());
1263    }
1264    let visit_value = StructuralView::from_raw(value);
1265    if PRE_ORDER {
1266        let action = visitor
1267            .visit(&visit_value, def_region_kind)
1268            .map_err(|error| with_value_context(error.into(), value))?;
1269        match action {
1270            WalkResult::Advance => {}
1271            WalkResult::Skip => return Ok(()),
1272            WalkResult::Interrupt => return Err(NativeHalt::Interrupt(Any::new())),
1273            WalkResult::InterruptWith(payload) => return Err(NativeHalt::Interrupt(payload)),
1274        }
1275    }
1276
1277    if V::CUSTOM_DESCENT {
1278        match visitor.default_visit_children::<PRE_ORDER>(&visit_value, def_region_kind) {
1279            Ok(None) => {}
1280            Ok(Some(interrupt)) => return Err(NativeHalt::Interrupt(interrupt.value)),
1281            Err(error) => return Err(with_value_context(error.into(), value)),
1282        }
1283    } else if value.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32
1284        || has_registered_visit_hook(value.type_index)
1285    {
1286        // Preserve the raw-result path for ordinary walkers.
1287        let context = std::ptr::from_mut(&mut *visitor).cast::<c_void>();
1288        let children = &mut WalkChildren::<V, PRE_ORDER> { visitor };
1289        if let Err(halt) = visit_children_raw(value, children, context, def_region_kind) {
1290            return Err(with_value_context(halt, value));
1291        }
1292    }
1293
1294    if PRE_ORDER {
1295        Ok(())
1296    } else {
1297        match visitor.visit(&visit_value, def_region_kind) {
1298            Ok(WalkResult::Advance | WalkResult::Skip) => Ok(()),
1299            Ok(WalkResult::Interrupt) => Err(NativeHalt::Interrupt(Any::new())),
1300            Ok(WalkResult::InterruptWith(payload)) => Err(NativeHalt::Interrupt(payload)),
1301            Err(error) => Err(with_value_context(error.into(), value)),
1302        }
1303    }
1304}
1305
1306#[inline]
1307fn visit_children_raw<C: ChildVisit>(
1308    value: TVMFFIAny,
1309    visitor: &mut C,
1310    driver_context: *mut c_void,
1311    def_region_kind: DefRegionKind,
1312) -> NativeResult {
1313    if let Some(attr) =
1314        structural_visit_column().and_then(|column| column.get_raw(value.type_index))
1315    {
1316        if attr.type_index != TVMFFITypeIndex::kTVMFFINone as i32 {
1317            let active = active_structural_visitor()?;
1318            return with_current_visitor_context(active, driver_context, || {
1319                call_structural_visit_hook(active, value, def_region_kind, attr)
1320            });
1321        }
1322    }
1323
1324    if value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
1325        Ok(())
1326    } else {
1327        visit_reflected_fields(value, visitor, def_region_kind)
1328    }
1329}
1330
1331#[inline]
1332fn visit_reflected_fields<C: ChildVisit>(
1333    value: TVMFFIAny,
1334    visitor: &mut C,
1335    def_region_kind: DefRegionKind,
1336) -> NativeResult {
1337    let type_info = unsafe { TVMFFIGetTypeInfo(value.type_index) };
1338    if type_info.is_null() {
1339        return Err(runtime_error(&format!(
1340            "native visitor: unregistered type index {}",
1341            value.type_index
1342        ))
1343        .into());
1344    }
1345    let seq_hash_kind = unsafe {
1346        let metadata = (*type_info).metadata;
1347        if metadata.is_null() {
1348            TVMFFISEqHashKind::kTVMFFISEqHashKindUnsupported as i32
1349        } else {
1350            (*metadata).structural_eq_hash_kind
1351        }
1352    };
1353    let def_region_kind = free_var_child_region(def_region_kind, seq_hash_kind);
1354    let object = unsafe { value.data_union.v_obj } as *mut u8;
1355    let halted = unsafe {
1356        for_each_field_info(type_info, &mut |field| match visit_reflected_field(
1357            object,
1358            field,
1359            visitor,
1360            def_region_kind,
1361        ) {
1362            Ok(()) => ControlFlow::Continue(()),
1363            Err(halt) => ControlFlow::Break(halt),
1364        })
1365    };
1366    halted.map_or(Ok(()), Err)
1367}
1368
1369unsafe fn visit_reflected_field<C: ChildVisit>(
1370    object: *mut u8,
1371    field: &TVMFFIFieldInfo,
1372    visitor: &mut C,
1373    inherited_region: DefRegionKind,
1374) -> NativeResult {
1375    if field.flags & FLAG_SEQ_HASH_IGNORE != 0 {
1376        return Ok(());
1377    }
1378
1379    let Some(getter) = field.getter else {
1380        return Err(NativeHalt::Error(runtime_error(&format!(
1381            "native visitor: reflected field `{}` has no getter",
1382            field.name.as_str()
1383        ))));
1384    };
1385    let address = object.offset(field.offset as isize) as *mut c_void;
1386    // Own the getter result so partial writes and recursive borrows drop safely.
1387    let mut child = Any::new();
1388    if getter(address, Any::as_data_ptr(&mut child)) != 0 {
1389        return Err(with_error_context(
1390            NativeHalt::Error(Error::from_raised()),
1391            &format!("field `{}`", field.name.as_str()),
1392        ));
1393    }
1394
1395    let borrowed = StructuralView::from_any(&child);
1396    let child_region = field_def_region(field, inherited_region);
1397    visitor
1398        .visit_child(borrowed, child_region)
1399        .map_err(|halt| with_error_context(halt, &format!("field `{}`", field.name.as_str())))
1400}
1401
1402type StructuralVisitorHandle = *mut RuntimeStructuralVisitorObj;
1403type FStructuralVisit =
1404    unsafe extern "C" fn(StructuralVisitorHandle, AnyView<'static>) -> TVMFFIAny;
1405
1406/// Rust mirror of the C++ `StructuralVisitorVTable` ABI.
1407#[repr(C)]
1408struct StructuralVisitorVTable {
1409    visit: FStructuralVisit,
1410}
1411
1412/// Rust visitor object with the C++ `StructuralVisitorObj` prefix.
1413#[repr(C)]
1414struct RuntimeStructuralVisitorObj {
1415    base: Object,
1416    vtable: *const StructuralVisitorVTable,
1417    def_region_mode: i32,
1418    // The live context remains in traversal-local TLS.
1419    context_identity: *mut c_void,
1420    owner_thread: std::thread::ThreadId,
1421    panic: Option<Box<dyn std::any::Any + Send>>,
1422}
1423
1424/// Rust layout used to create and read the ABI `ffi.VisitInterrupt` object.
1425#[repr(C)]
1426struct RuntimeVisitInterruptObj {
1427    base: Object,
1428    value: Any,
1429}
1430
1431const _: () = {
1432    assert!(
1433        std::mem::offset_of!(RuntimeStructuralVisitorObj, vtable)
1434            == std::mem::size_of::<TVMFFIObject>()
1435    );
1436    assert!(
1437        std::mem::offset_of!(RuntimeStructuralVisitorObj, def_region_mode)
1438            == std::mem::size_of::<TVMFFIObject>() + std::mem::size_of::<*const c_void>()
1439    );
1440    assert!(
1441        std::mem::offset_of!(RuntimeVisitInterruptObj, value)
1442            == std::mem::size_of::<TVMFFIObject>()
1443    );
1444};
1445
1446// SAFETY: the `repr(C)` prefix and assertions above match
1447// `StructuralVisitorObj`; the runtime type is registered by the C++ extra.
1448unsafe impl ObjectCore for RuntimeStructuralVisitorObj {
1449    const TYPE_KEY: &'static str = "ffi.StructuralVisitor";
1450    const TYPE_DEPTH: i32 = Object::TYPE_DEPTH + 1;
1451
1452    fn type_index() -> i32 {
1453        static TYPE_INDEX: LazyLock<i32> = LazyLock::new(|| unsafe {
1454            let key = TVMFFIByteArray::from_str(RuntimeStructuralVisitorObj::TYPE_KEY);
1455            let mut type_index = 0;
1456            let return_code = TVMFFITypeKeyToIndex(&key, &mut type_index);
1457            if return_code != 0 {
1458                panic!(
1459                    "ffi.StructuralVisitor is not registered: {}",
1460                    Error::from_raised()
1461                );
1462            }
1463            type_index
1464        });
1465        *TYPE_INDEX
1466    }
1467
1468    unsafe fn object_header_mut(this: &mut Self) -> &mut TVMFFIObject {
1469        Object::object_header_mut(&mut this.base)
1470    }
1471}
1472
1473// SAFETY: `VisitInterruptObj` is final and consists of `Object` followed by
1474// one `Any`, exactly matching `RuntimeVisitInterruptObj`.
1475unsafe impl ObjectCore for RuntimeVisitInterruptObj {
1476    const TYPE_KEY: &'static str = "ffi.VisitInterrupt";
1477    const TYPE_DEPTH: i32 = Object::TYPE_DEPTH + 1;
1478    const TYPE_FINAL: bool = true;
1479
1480    fn type_index() -> i32 {
1481        TVMFFITypeIndex::kTVMFFIVisitInterrupt as i32
1482    }
1483
1484    unsafe fn object_header_mut(this: &mut Self) -> &mut TVMFFIObject {
1485        Object::object_header_mut(&mut this.base)
1486    }
1487}
1488
1489// Use a direct ABI entry for each concrete visitor type.
1490fn walk_runtime_vtable<V: NativeVisit, const PRE_ORDER: bool>() -> &'static StructuralVisitorVTable
1491{
1492    &StructuralVisitorVTable {
1493        visit: rust_vtable_walk::<V, PRE_ORDER>,
1494    }
1495}
1496
1497fn user_runtime_vtable<V: StructuralVisitor>() -> &'static StructuralVisitorVTable {
1498    &StructuralVisitorVTable {
1499        visit: rust_vtable_user::<V>,
1500    }
1501}
1502
1503struct RuntimeContextGuard {
1504    active: *mut ActiveStructuralVisitor,
1505    context: *mut c_void,
1506}
1507
1508impl Drop for RuntimeContextGuard {
1509    fn drop(&mut self) {
1510        // SAFETY: `active` points to the traversal-local state installed in
1511        // TLS, which outlives every callback guard created during that run.
1512        unsafe { (*self.active).context = self.context };
1513    }
1514}
1515
1516/// Traversal-local state exposed through TLS on the owner thread.
1517struct ActiveStructuralVisitor {
1518    visitor: StructuralVisitorHandle,
1519    context: *mut c_void,
1520    context_identity: *mut c_void,
1521}
1522
1523/// Take the Rust callback context while one vtable call is active.
1524///
1525/// # Safety
1526///
1527/// `visitor` must be null or point to a live [`RuntimeStructuralVisitorObj`].
1528#[inline(always)]
1529unsafe fn take_runtime_context(visitor: StructuralVisitorHandle) -> Result<RuntimeContextGuard> {
1530    let active = active_structural_visitor_state(visitor)
1531        .ok_or_else(|| inactive_structural_visitor_error(visitor, "callback"))?;
1532    let context = (*active).context;
1533    if context.is_null() {
1534        return Err(runtime_error(
1535            "structural visitor may only be called by its active registered hook",
1536        ));
1537    }
1538    (*active).context = std::ptr::null_mut();
1539    Ok(RuntimeContextGuard { active, context })
1540}
1541
1542unsafe extern "C" fn rust_vtable_walk<V: NativeVisit, const PRE_ORDER: bool>(
1543    visitor: StructuralVisitorHandle,
1544    value: AnyView<'static>,
1545) -> TVMFFIAny {
1546    rust_vtable_visit_impl(visitor, value, |context, raw, kind| {
1547        runtime_walk::<V, PRE_ORDER>(context, raw, kind)
1548    })
1549}
1550
1551unsafe extern "C" fn rust_vtable_user<V: StructuralVisitor>(
1552    visitor: StructuralVisitorHandle,
1553    value: AnyView<'static>,
1554) -> TVMFFIAny {
1555    rust_vtable_visit_impl(visitor, value, |context, raw, kind| {
1556        runtime_user_visit::<V>(context, raw, kind)
1557    })
1558}
1559
1560#[inline(always)]
1561unsafe fn rust_vtable_visit_impl(
1562    visitor: StructuralVisitorHandle,
1563    value: AnyView<'static>,
1564    callback: impl FnOnce(*mut c_void, TVMFFIAny, DefRegionKind) -> NativeResult,
1565) -> TVMFFIAny {
1566    let context_guard = match take_runtime_context(visitor) {
1567        Ok(guard) => guard,
1568        Err(error) => return native_result_into_raw(Err(NativeHalt::Error(error))),
1569    };
1570    let context = context_guard.context;
1571    let raw = *value.as_raw_ffi_any();
1572    let outcome = catch_unwind(AssertUnwindSafe(
1573        #[inline(always)]
1574        || {
1575            let result = def_region_from_raw((*visitor).def_region_mode)
1576                .map_err(NativeHalt::Error)
1577                .and_then(|kind| callback(context, raw, kind));
1578            native_result_into_raw(result)
1579        },
1580    ));
1581    match outcome {
1582        Ok(result) => result,
1583        Err(payload) => visit_panic_result(visitor, payload),
1584    }
1585}
1586
1587#[cold]
1588unsafe fn visit_panic_result(
1589    visitor: StructuralVisitorHandle,
1590    payload: Box<dyn std::any::Any + Send>,
1591) -> TVMFFIAny {
1592    (*visitor).panic = Some(payload);
1593    native_result_into_raw(Err(NativeHalt::Error(runtime_error(
1594        "panic in structural visitor callback",
1595    ))))
1596}
1597
1598thread_local! {
1599    static ACTIVE_STRUCTURAL_VISITOR: Cell<*mut ActiveStructuralVisitor> = const {
1600        Cell::new(std::ptr::null_mut())
1601    };
1602}
1603
1604fn with_active_structural_visitor<T>(
1605    active_state: &mut ActiveStructuralVisitor,
1606    callback: impl FnOnce() -> T,
1607) -> T {
1608    ACTIVE_STRUCTURAL_VISITOR.with(|active| {
1609        let previous = active.replace(std::ptr::from_mut(active_state));
1610        struct Restore<'a> {
1611            active: &'a Cell<*mut ActiveStructuralVisitor>,
1612            previous: *mut ActiveStructuralVisitor,
1613        }
1614        impl Drop for Restore<'_> {
1615            fn drop(&mut self) {
1616                self.active.set(self.previous);
1617            }
1618        }
1619        let _restore = Restore { active, previous };
1620        callback()
1621    })
1622}
1623
1624fn active_structural_visitor() -> Result<StructuralVisitorHandle> {
1625    ACTIVE_STRUCTURAL_VISITOR.with(|active| {
1626        let state = active.get();
1627        if state.is_null() {
1628            Err(runtime_error(
1629                "structural visitor helper called outside structural_visit or structural_walk",
1630            ))
1631        } else {
1632            Ok(unsafe { (*state).visitor })
1633        }
1634    })
1635}
1636
1637#[inline(always)]
1638fn active_structural_visitor_state(
1639    handle: StructuralVisitorHandle,
1640) -> Option<*mut ActiveStructuralVisitor> {
1641    ACTIVE_STRUCTURAL_VISITOR.with(|active| {
1642        let state = active.get();
1643        if state.is_null() || unsafe { (*state).visitor != handle } {
1644            None
1645        } else {
1646            Some(state)
1647        }
1648    })
1649}
1650
1651#[cold]
1652fn inactive_structural_visitor_error(visitor: StructuralVisitorHandle, operation: &str) -> Error {
1653    if visitor.is_null() {
1654        return runtime_error("null active structural visitor");
1655    }
1656    // This branch is outside the hot path. The immutable owner id lets us
1657    // reject a foreign-thread call before reading context fields that the
1658    // owner thread may be updating.
1659    unsafe {
1660        if (*visitor).owner_thread != std::thread::current().id() {
1661            return runtime_error(&format!(
1662                "structural visitor {operation} invoked from a different thread"
1663            ));
1664        }
1665        if (*visitor).context_identity.is_null() {
1666            runtime_error("structural visitor was retained after its active call")
1667        } else {
1668            runtime_error(&format!(
1669                "structural visitor {operation} may only be used by its active registered hook"
1670            ))
1671        }
1672    }
1673}
1674
1675/// Expose the current Rust visitor only while a registered hook may call its
1676/// vtable. The callback hides it again before returning to Rust user code.
1677fn with_current_visitor_context(
1678    visitor: StructuralVisitorHandle,
1679    context: *mut c_void,
1680    callback: impl FnOnce() -> NativeResult,
1681) -> NativeResult {
1682    let active = checked_visitor_context(visitor, context)?;
1683    unsafe {
1684        (*active).context = context;
1685        struct HideContext {
1686            active: *mut ActiveStructuralVisitor,
1687        }
1688        impl Drop for HideContext {
1689            fn drop(&mut self) {
1690                unsafe { (*self.active).context = std::ptr::null_mut() };
1691            }
1692        }
1693        let _hide = HideContext { active };
1694        callback()
1695    }
1696}
1697
1698fn checked_visitor_context(
1699    visitor: StructuralVisitorHandle,
1700    context: *mut c_void,
1701) -> Result<*mut ActiveStructuralVisitor> {
1702    let active = active_structural_visitor_state(visitor)
1703        .ok_or_else(|| inactive_structural_visitor_error(visitor, "helper"))?;
1704    unsafe {
1705        if (*active).context_identity != context {
1706            return Err(runtime_error(
1707                "structural visitor helper called on a non-active visitor",
1708            ));
1709        }
1710        if !(*active).context.is_null() {
1711            return Err(runtime_error(
1712                "structural visitor context is already exposed",
1713            ));
1714        }
1715        Ok(active)
1716    }
1717}
1718
1719#[inline(always)]
1720unsafe fn runtime_walk<V: NativeVisit, const PRE_ORDER: bool>(
1721    context: *mut c_void,
1722    raw: TVMFFIAny,
1723    def_region_kind: DefRegionKind,
1724) -> NativeResult {
1725    visit_raw::<V, PRE_ORDER>(raw, &mut *context.cast::<V>(), def_region_kind)
1726}
1727
1728#[inline(always)]
1729unsafe fn runtime_user_visit<V: StructuralVisitor>(
1730    context: *mut c_void,
1731    raw: TVMFFIAny,
1732    def_region_kind: DefRegionKind,
1733) -> NativeResult {
1734    if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
1735        return Ok(());
1736    }
1737    match (&mut *context.cast::<V>()).visit(&StructuralView::from_raw(raw), def_region_kind) {
1738        Ok(None) => Ok(()),
1739        Ok(Some(interrupt)) => Err(NativeHalt::Interrupt(interrupt.value)),
1740        Err(error) => Err(with_value_context(NativeHalt::Error(error), raw)),
1741    }
1742}
1743
1744fn run_structural_visitor<D>(
1745    root: TVMFFIAny,
1746    driver: &mut D,
1747    vtable: &'static StructuralVisitorVTable,
1748) -> NativeResult {
1749    let context = std::ptr::from_mut(driver).cast::<c_void>();
1750    let mut active = ObjectArc::new(RuntimeStructuralVisitorObj {
1751        base: Object::new(),
1752        vtable,
1753        def_region_mode: DefRegionKind::None as i32,
1754        context_identity: context,
1755        owner_thread: std::thread::current().id(),
1756        panic: None,
1757    });
1758    let handle = unsafe { ObjectArc::as_raw_mut(&mut active) };
1759    let mut active_state = ActiveStructuralVisitor {
1760        visitor: handle,
1761        context,
1762        context_identity: context,
1763    };
1764    // Keep all borrow-sensitive Rust state in this traversal's stack frame.
1765    // Nested callbacks validate and temporarily take it through TLS without
1766    // repeatedly touching the heap-allocated FFI object.
1767    let result = with_active_structural_visitor(&mut active_state, || {
1768        call_visitor(handle, root, DefRegionKind::None)
1769    });
1770    unsafe {
1771        (*handle).context_identity = std::ptr::null_mut();
1772    }
1773    let panic = unsafe { (*handle).panic.take() };
1774    if let Some(payload) = panic {
1775        drop(result);
1776        resume_unwind(payload);
1777    }
1778    result
1779}
1780
1781fn call_visitor(
1782    visitor: StructuralVisitorHandle,
1783    raw: TVMFFIAny,
1784    def_region_kind: DefRegionKind,
1785) -> NativeResult {
1786    if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
1787        return Ok(());
1788    }
1789    if visitor.is_null() {
1790        return Err(runtime_error("no active structural visitor").into());
1791    }
1792    let callback = unsafe { (*(*visitor).vtable).visit };
1793    with_visitor_def_region(visitor, def_region_kind, || unsafe {
1794        let value = AnyView::from_raw_ffi_any(raw);
1795        visit_result_from_raw(callback(visitor, value))
1796    })
1797}
1798
1799fn call_structural_visit_hook(
1800    visitor: StructuralVisitorHandle,
1801    raw: TVMFFIAny,
1802    def_region_kind: DefRegionKind,
1803    attr: TVMFFIAny,
1804) -> NativeResult {
1805    with_visitor_def_region(visitor, def_region_kind, || unsafe {
1806        match attr.type_index {
1807            x if x == TVMFFITypeIndex::kTVMFFIOpaquePtr as i32 => {
1808                let pointer = attr.data_union.v_ptr;
1809                if pointer.is_null() {
1810                    return Err(runtime_error("structural visit hook is null").into());
1811                }
1812                // The `__s_visit__` protocol stores exactly an
1813                // `FStructuralVisit` in an opaque-pointer attribute.
1814                let hook: FStructuralVisit = std::mem::transmute(pointer);
1815                let value = AnyView::from_raw_ffi_any(raw);
1816                visit_result_from_raw(hook(visitor, value))
1817            }
1818            x if x == TVMFFITypeIndex::kTVMFFIFunction as i32 => {
1819                let function = Function::try_from(AnyView::from_raw_ffi_any(attr))?;
1820                let visitor_value = borrowed_visitor_view(visitor);
1821                let value = AnyView::from_raw_ffi_any(raw);
1822                visit_result_from_any(function.call_packed(&[visitor_value, value])?)
1823            }
1824            _ => Err(Error::new(
1825                TYPE_ERROR,
1826                "__s_visit__ must be an opaque function pointer or ffi.Function",
1827                "",
1828            )
1829            .into()),
1830        }
1831    })
1832}
1833
1834unsafe fn borrowed_visitor_view<'a>(visitor: StructuralVisitorHandle) -> AnyView<'a> {
1835    let object = visitor.cast::<TVMFFIObject>();
1836    let mut raw = TVMFFIAny::new();
1837    raw.type_index = (*object).type_index;
1838    raw.small_str_len = 0;
1839    raw.data_union.v_obj = object;
1840    AnyView::from_raw_ffi_any(raw)
1841}
1842
1843#[inline(always)]
1844fn native_result_into_raw(result: NativeResult) -> TVMFFIAny {
1845    match result {
1846        Ok(()) => TVMFFIAny::new(),
1847        Err(NativeHalt::Error(error)) => unsafe { Any::into_raw_ffi_any(Any::from(error)) },
1848        Err(NativeHalt::Interrupt(payload)) => {
1849            let interrupt = ObjectArc::new(RuntimeVisitInterruptObj {
1850                base: Object::new(),
1851                value: payload,
1852            });
1853            let object = unsafe { ObjectArc::into_raw(interrupt) }.cast_mut();
1854            let mut raw = TVMFFIAny::new();
1855            raw.type_index = TVMFFITypeIndex::kTVMFFIVisitInterrupt as i32;
1856            raw.data_union.v_obj = object.cast::<TVMFFIObject>();
1857            raw
1858        }
1859    }
1860}
1861
1862unsafe fn visit_result_from_raw(raw: TVMFFIAny) -> NativeResult {
1863    // None is the overwhelmingly common success result. Avoid constructing
1864    // and dropping an owning Any unless the hook actually stopped or failed.
1865    if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
1866        Ok(())
1867    } else {
1868        visit_result_from_any(Any::from_raw_ffi_any(raw))
1869    }
1870}
1871
1872fn visit_result_from_any(value: Any) -> NativeResult {
1873    match value.type_index() {
1874        x if x == TVMFFITypeIndex::kTVMFFINone as i32 => Ok(()),
1875        x if x == TVMFFITypeIndex::kTVMFFIError as i32 => match Error::try_from(value) {
1876            Ok(error) | Err(error) => Err(NativeHalt::Error(error)),
1877        },
1878        x if x == TVMFFITypeIndex::kTVMFFIVisitInterrupt as i32 => {
1879            let raw = *value.as_raw_ffi_any();
1880            let object = unsafe { raw.data_union.v_obj };
1881            if object.is_null() {
1882                return Err(runtime_error("structural visit returned a null interrupt").into());
1883            }
1884            let payload = unsafe { (*object.cast::<RuntimeVisitInterruptObj>()).value.clone() };
1885            Err(NativeHalt::Interrupt(payload))
1886        }
1887        _ => Err(Error::new(
1888            TYPE_ERROR,
1889            "structural visit hook must return None or ffi.VisitInterrupt",
1890            "",
1891        )
1892        .into()),
1893    }
1894}
1895
1896#[inline(always)]
1897fn with_visit_region<T>(
1898    kind: DefRegionKind,
1899    callback: impl FnOnce(DefRegionKind) -> Result<T>,
1900) -> Result<T> {
1901    let active = active_structural_visitor()?;
1902    with_visitor_def_region(
1903        active,
1904        kind,
1905        #[inline(always)]
1906        || {
1907            // SAFETY: the active invocation keeps this thread's ABI visitor alive.
1908            let kind = def_region_from_raw(unsafe { (*active).def_region_mode })?;
1909            callback(kind)
1910        },
1911    )
1912}
1913
1914#[inline(always)]
1915fn with_visitor_def_region<T>(
1916    visitor: StructuralVisitorHandle,
1917    kind: DefRegionKind,
1918    callback: impl FnOnce() -> T,
1919) -> T {
1920    unsafe {
1921        let previous = (*visitor).def_region_mode;
1922        // Precedence: a pattern region propagates; entering any kind inside it has no effect.
1923        if previous != DefRegionKind::Pattern as i32 {
1924            (*visitor).def_region_mode = kind as i32;
1925        }
1926        struct Restore {
1927            visitor: StructuralVisitorHandle,
1928            previous: i32,
1929        }
1930        impl Drop for Restore {
1931            fn drop(&mut self) {
1932                if self.previous != DefRegionKind::Pattern as i32 {
1933                    unsafe { (*self.visitor).def_region_mode = self.previous };
1934                }
1935            }
1936        }
1937        let _restore = Restore { visitor, previous };
1938        callback()
1939    }
1940}
1941
1942#[inline(always)]
1943fn def_region_from_raw(kind: i32) -> Result<DefRegionKind> {
1944    match kind {
1945        x if x == DefRegionKind::None as i32 => Ok(DefRegionKind::None),
1946        x if x == DefRegionKind::Pattern as i32 => Ok(DefRegionKind::Pattern),
1947        x if x == DefRegionKind::Simple as i32 => Ok(DefRegionKind::Simple),
1948        _ => Err(runtime_error("invalid structural definition-region kind")),
1949    }
1950}
1951
1952#[cold]
1953fn with_value_context(halt: NativeHalt, value: TVMFFIAny) -> NativeHalt {
1954    if value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
1955        halt
1956    } else {
1957        let halt = match halt {
1958            NativeHalt::Error(error) => NativeHalt::Error(with_visit_error_context(error, value)),
1959            interrupt => interrupt,
1960        };
1961        with_error_context(halt, &format!("object `{}`", type_key_of(value.type_index)))
1962    }
1963}
1964
1965/// Visit `root` with a [`StructuralVisitor`] or typed callback chain.
1966///
1967/// A matching callback owns recursion; unmatched values use default child
1968/// traversal. Use [`VisitCallbacks`] to attach mutable state to the chain.
1969pub fn structural_visit<R, M>(
1970    root: &R,
1971    visitor: impl IntoVisitor<M>,
1972) -> Result<Option<VisitInterrupt>>
1973where
1974    for<'x> AnyView<'x>: From<&'x R>,
1975{
1976    visitor.visit_root(raw_of(AnyView::from(root)))
1977}
1978
1979/// Walk `root` with an observer, the Rust analog of C++
1980/// `StructuralWalk<order>(root, callbacks...)`.
1981///
1982/// `walker` is anything implementing [`IntoWalker`]: a `&mut` reference to a
1983/// stateful [`WalkDispatch`] walker (`#[dispatch(walk)]`), a bare closure
1984/// in any [`WalkChainLink`] shape (catch-all `&StructuralView`, typed, or node,
1985/// with an optional trailing [`DefRegionKind`]), or a tuple of such
1986/// callbacks tried in order — the C++ callback overloads and variadic
1987/// chain. A [`WalkWithContextPolicy`] may be passed by value or mutable reference.
1988/// The walker owns recursion: the handler runs once per value,
1989/// before or after the value's children according to `order`, and steers
1990/// traversal through the returned [`WalkResult`].
1991pub fn structural_walk<R, M, H>(
1992    root: &R,
1993    walker: H,
1994    order: WalkOrder,
1995) -> Result<Option<VisitInterrupt>>
1996where
1997    H: IntoWalker<M>,
1998    for<'x> AnyView<'x>: From<&'x R>,
1999{
2000    walker.into_walker().walk_root(AnyView::from(root), order)
2001}
2002
2003fn finish(result: NativeResult) -> Result<Option<VisitInterrupt>> {
2004    match result {
2005        Ok(()) => Ok(None),
2006        Err(NativeHalt::Error(error)) => Err(error),
2007        Err(NativeHalt::Interrupt(payload)) => Ok(Some(VisitInterrupt { value: payload })),
2008    }
2009}
2010
2011#[inline]
2012pub(crate) fn field_def_region(field: &TVMFFIFieldInfo, inherited: DefRegionKind) -> DefRegionKind {
2013    // Precedence: a pattern region propagates; entering any kind inside it has no effect.
2014    if inherited == DefRegionKind::Pattern {
2015        DefRegionKind::Pattern
2016    } else if field.flags & FLAG_SEQ_HASH_DEF_SIMPLE != 0 {
2017        DefRegionKind::Simple
2018    } else if field.flags & FLAG_SEQ_HASH_DEF_PATTERN != 0 {
2019        DefRegionKind::Pattern
2020    } else {
2021        inherited
2022    }
2023}
2024
2025/// A simple definition applies to a FreeVar value itself, but not to
2026/// the FreeVar's own reflected children: nested free vars there must resolve
2027/// against an outer binding instead of rebinding. Mirrors C++
2028/// `VisitReflectedFieldsExpected`.
2029#[inline]
2030pub(crate) fn free_var_child_region(
2031    inherited: DefRegionKind,
2032    structural_eq_hash_kind: i32,
2033) -> DefRegionKind {
2034    if inherited == DefRegionKind::Simple
2035        && structural_eq_hash_kind == TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32
2036    {
2037        DefRegionKind::None
2038    } else {
2039        inherited
2040    }
2041}
2042
2043#[cold]
2044fn with_error_context(halt: NativeHalt, frame: &str) -> NativeHalt {
2045    match halt {
2046        NativeHalt::Error(error) => {
2047            NativeHalt::Error(with_structural_error_context(error, "visit", frame))
2048        }
2049        interrupt => interrupt,
2050    }
2051}
2052
2053fn runtime_error(message: &str) -> Error {
2054    Error::new(RUNTIME_ERROR, message, "")
2055}
2056
2057pub(crate) fn type_attr_column(attr_name: &str) -> Option<TypeAttrColumn> {
2058    TypeAttrColumn::new(attr_name)
2059}
2060
2061/// Cached `__s_visit__` column pointer (0 = not seen yet). A registry column
2062/// is stable once created — C++ `DefaultVisitExpected` caches the same
2063/// pointer in a function-local static — while an absent column is re-queried
2064/// because a later attr registration may create it.
2065static STRUCTURAL_VISIT_COLUMN: AtomicUsize = AtomicUsize::new(0);
2066
2067#[inline]
2068fn structural_visit_column() -> Option<TypeAttrColumn> {
2069    let cached = STRUCTURAL_VISIT_COLUMN.load(Ordering::Relaxed);
2070    if cached != 0 {
2071        let pointer = cached as *mut TVMFFITypeAttrColumn;
2072        return Some(unsafe { TypeAttrColumn::from_non_null(NonNull::new_unchecked(pointer)) });
2073    }
2074    initialize_structural_visit_column()
2075}
2076
2077#[inline]
2078fn has_registered_visit_hook(type_index: i32) -> bool {
2079    structural_visit_column()
2080        .and_then(|column| column.get_raw(type_index))
2081        .is_some_and(|attr| attr.type_index != TVMFFITypeIndex::kTVMFFINone as i32)
2082}
2083
2084#[cold]
2085#[inline(never)]
2086fn initialize_structural_visit_column() -> Option<TypeAttrColumn> {
2087    let column = type_attr_column(STRUCTURAL_VISIT_ATTR)?;
2088    STRUCTURAL_VISIT_COLUMN.store(column.as_ptr() as usize, Ordering::Relaxed);
2089    Some(column)
2090}
2091
2092pub(crate) fn type_key_of(type_index: i32) -> String {
2093    unsafe {
2094        let info = TVMFFIGetTypeInfo(type_index);
2095        if info.is_null() {
2096            format!("<type_index {type_index}>")
2097        } else {
2098            (*info).type_key.as_str().to_string()
2099        }
2100    }
2101}
2102
2103/// Visit every reflected field described by `info` and its ancestors in the
2104/// same parent-to-child order as C++ `ForEachFieldInfoWithEarlyStop`.
2105///
2106/// # Safety
2107///
2108/// `info` must point to an immortal registered type-info record.
2109pub(crate) unsafe fn for_each_field_info<B>(
2110    info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
2111    callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
2112) -> Option<B> {
2113    // Ancestor slot 0 is the root Object. C++ starts at slot 1, walks toward
2114    // the immediate parent, then visits the concrete type's own fields.
2115    for depth in 1..(*info).type_depth {
2116        let ancestor = *(*info).type_acenstors.offset(depth as isize);
2117        if let Some(value) = visit_field_level(ancestor, callback) {
2118            return Some(value);
2119        }
2120    }
2121    visit_field_level(info, callback)
2122}
2123
2124unsafe fn visit_field_level<B>(
2125    info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
2126    callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
2127) -> Option<B> {
2128    if info.is_null() || (*info).fields.is_null() {
2129        return None;
2130    }
2131    let fields = std::slice::from_raw_parts((*info).fields, (*info).num_fields as usize);
2132    for field in fields {
2133        // C reflection tables are immortal once registered.
2134        let field: &'static TVMFFIFieldInfo = &*(field as *const TVMFFIFieldInfo);
2135        if let ControlFlow::Break(value) = callback(field) {
2136            return Some(value);
2137        }
2138    }
2139    None
2140}
2141
2142#[inline]
2143fn raw_of(view: AnyView<'_>) -> TVMFFIAny {
2144    *view.as_raw_ffi_any()
2145}