facet_path/walk.rs
1//! Shape-only visitor API for deterministic traversal of [`Shape`] trees.
2//!
3//! This module provides a visitor pattern for walking the static type structure
4//! described by [`Shape`], emitting [`Path`] context at each node. It traverses
5//! only the type metadata — no values are inspected.
6//!
7//! # Traversal order
8//!
9//! - **Depth-first, declaration order.** Struct fields are visited in the order
10//! they appear in the source. Enum variants are visited in declaration order,
11//! and within each variant, fields follow the same rule.
12//! - `enter` is called **before** children; `leave` is called **after** children
13//! (only if `enter` returned [`VisitDecision::Recurse`]).
14//!
15//! # Traversal control
16//!
17//! [`VisitDecision`] returned from [`ShapeVisitor::enter`] controls descent:
18//!
19//! | Decision | Effect |
20//! |-----------------|-------------------------------------------------|
21//! | `Recurse` | Visit children, then call `leave`. |
22//! | `SkipChildren` | Skip descendants of this node; `leave` is still called. |
23//! | `Stop` | Terminate the entire walk immediately. |
24//!
25//! # Cycle handling
26//!
27//! Recursive types (e.g. a tree node whose children are `Vec<Node>`) would cause
28//! infinite descent. The walker tracks ancestor types by [`ConstTypeId`] and
29//! **does not recurse** into a type that is already on the current path. When a
30//! cycle is detected the node is still reported to `enter`/`leave`, but its
31//! children are skipped automatically — the visitor does not need to handle this.
32
33use alloc::vec::Vec;
34
35use facet_core::{ConstTypeId, Def, Shape, Type, UserType};
36
37use crate::{Path, PathStep};
38
39/// Decision returned by [`ShapeVisitor::enter`] to control traversal.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum VisitDecision {
43 /// Descend into this node's children, then call [`ShapeVisitor::leave`].
44 Recurse,
45 /// Skip this node's descendants. [`ShapeVisitor::leave`] is still called.
46 SkipChildren,
47 /// Stop the entire walk immediately. No further callbacks are made.
48 Stop,
49}
50
51/// Outcome of [`walk_shape`].
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum WalkStatus {
54 /// The walk visited every reachable node.
55 Completed,
56 /// The walk was terminated early by [`VisitDecision::Stop`].
57 Stopped,
58}
59
60/// Visitor trait for shape-only traversal.
61///
62/// Implement this trait to receive callbacks as [`walk_shape`] descends through
63/// a [`Shape`] tree.
64pub trait ShapeVisitor {
65 /// Called when the walker enters a node, **before** visiting children.
66 ///
67 /// Return a [`VisitDecision`] to control whether children are visited.
68 fn enter(&mut self, path: &Path, shape: &'static Shape) -> VisitDecision;
69
70 /// Called when the walker leaves a node, **after** visiting children (or
71 /// after skipping them if `enter` returned [`VisitDecision::SkipChildren`]).
72 ///
73 /// Not called if `enter` returned [`VisitDecision::Stop`].
74 fn leave(&mut self, path: &Path, shape: &'static Shape) {
75 let _ = (path, shape);
76 }
77}
78
79/// Walk a [`Shape`] tree depth-first, calling `visitor` at each node.
80///
81/// See the [module docs](self) for traversal order and control semantics.
82pub fn walk_shape(shape: &'static Shape, visitor: &mut impl ShapeVisitor) -> WalkStatus {
83 let mut path = Path::new(shape);
84 let mut ancestors: Vec<ConstTypeId> = Vec::new();
85 if walk_recursive(shape, visitor, &mut path, &mut ancestors) {
86 WalkStatus::Stopped
87 } else {
88 WalkStatus::Completed
89 }
90}
91
92/// Returns `true` if the walk was stopped.
93fn walk_recursive(
94 shape: &'static Shape,
95 visitor: &mut impl ShapeVisitor,
96 path: &mut Path,
97 ancestors: &mut Vec<ConstTypeId>,
98) -> bool {
99 // Cycle detection: if this type is already an ancestor, don't recurse
100 let is_cycle = ancestors.contains(&shape.id);
101
102 let decision = visitor.enter(path, shape);
103 match decision {
104 VisitDecision::Stop => return true,
105 VisitDecision::SkipChildren => {
106 visitor.leave(path, shape);
107 return false;
108 }
109 VisitDecision::Recurse => {
110 if is_cycle {
111 // Cycle detected — treat as SkipChildren automatically
112 visitor.leave(path, shape);
113 return false;
114 }
115 }
116 }
117
118 // Push onto ancestor stack for cycle detection
119 ancestors.push(shape.id);
120
121 let stopped = walk_children(shape, visitor, path, ancestors);
122
123 ancestors.pop();
124
125 if !stopped {
126 visitor.leave(path, shape);
127 }
128
129 stopped
130}
131
132/// Walk the children of a shape. Returns `true` if stopped.
133///
134/// A shape can describe its children through two orthogonal axes:
135///
136/// - `shape.ty` — structural type information (struct fields, enum variants)
137/// - `shape.def` — semantic container information (list element, map key/value, etc.)
138///
139/// Some types use both (e.g. `Option<T>` is an enum in `ty` and has `Def::Option`),
140/// so we must avoid double-visiting. The rule:
141///
142/// 1. `Def::Option` takes priority — Option is a container, not an enum to iterate.
143/// 2. If `ty` is `Struct` or `Enum`, walk children through `ty` (fields/variants).
144/// 3. Otherwise, walk children through `def` (container element shapes).
145/// 4. Walk `shape.inner` only if none of the above provided child access.
146fn walk_children(
147 shape: &'static Shape,
148 visitor: &mut impl ShapeVisitor,
149 path: &mut Path,
150 ancestors: &mut Vec<ConstTypeId>,
151) -> bool {
152 // Track whether we found children via ty or def, to avoid
153 // double-visiting through `inner`.
154 let mut has_structural_children = false;
155
156 // Option<T> is both UserType::Enum and Def::Option. We handle it via
157 // Def::Option (using OptionSome) because Option is semantically a
158 // container — the None/Some distinction is a runtime concern, not
159 // something a shape-only walk should iterate as enum variants.
160 if let Def::Option(od) = shape.def {
161 path.push(PathStep::OptionSome);
162 if walk_recursive(od.t(), visitor, path, ancestors) {
163 return true;
164 }
165 path.pop();
166 // Skip the Enum branch below — we already handled Option's children.
167 return false;
168 }
169
170 match shape.ty {
171 Type::User(UserType::Struct(st)) => {
172 has_structural_children = true;
173 for (i, field) in st.fields.iter().enumerate() {
174 path.push(PathStep::Field(i as u32));
175 if walk_recursive(field.shape(), visitor, path, ancestors) {
176 return true;
177 }
178 path.pop();
179 }
180 }
181 Type::User(UserType::Enum(et)) => {
182 has_structural_children = true;
183 for (vi, variant) in et.variants.iter().enumerate() {
184 path.push(PathStep::Variant(vi as u32));
185
186 for (fi, field) in variant.data.fields.iter().enumerate() {
187 path.push(PathStep::Field(fi as u32));
188 if walk_recursive(field.shape(), visitor, path, ancestors) {
189 return true;
190 }
191 path.pop();
192 }
193
194 path.pop();
195 }
196 }
197 _ => {}
198 }
199
200 // For types without structural children (Opaque, Primitive, Sequence, Pointer),
201 // descend through the semantic definition.
202 if !has_structural_children {
203 match shape.def {
204 Def::List(ld) => {
205 has_structural_children = true;
206 path.push(PathStep::Index(0));
207 if walk_recursive(ld.t(), visitor, path, ancestors) {
208 return true;
209 }
210 path.pop();
211 }
212 Def::Array(ad) => {
213 has_structural_children = true;
214 path.push(PathStep::Index(0));
215 if walk_recursive(ad.t(), visitor, path, ancestors) {
216 return true;
217 }
218 path.pop();
219 }
220 Def::Slice(sd) => {
221 has_structural_children = true;
222 path.push(PathStep::Index(0));
223 if walk_recursive(sd.t(), visitor, path, ancestors) {
224 return true;
225 }
226 path.pop();
227 }
228 Def::NdArray(nd) => {
229 has_structural_children = true;
230 path.push(PathStep::Index(0));
231 if walk_recursive(nd.t(), visitor, path, ancestors) {
232 return true;
233 }
234 path.pop();
235 }
236 Def::Set(sd) => {
237 has_structural_children = true;
238 path.push(PathStep::Index(0));
239 if walk_recursive(sd.t(), visitor, path, ancestors) {
240 return true;
241 }
242 path.pop();
243 }
244 Def::Map(md) => {
245 has_structural_children = true;
246 path.push(PathStep::MapKey(0));
247 if walk_recursive(md.k(), visitor, path, ancestors) {
248 return true;
249 }
250 path.pop();
251
252 path.push(PathStep::MapValue(0));
253 if walk_recursive(md.v(), visitor, path, ancestors) {
254 return true;
255 }
256 path.pop();
257 }
258 Def::Option(od) => {
259 has_structural_children = true;
260 path.push(PathStep::OptionSome);
261 if walk_recursive(od.t(), visitor, path, ancestors) {
262 return true;
263 }
264 path.pop();
265 }
266 Def::Result(rd) => {
267 has_structural_children = true;
268 // Result<T, E> is Opaque in ty, so we walk both T and E here.
269 // Use Variant(0) for Ok, Variant(1) for Err to match Result's
270 // semantic structure.
271 path.push(PathStep::Variant(0));
272 path.push(PathStep::Field(0));
273 if walk_recursive(rd.t(), visitor, path, ancestors) {
274 return true;
275 }
276 path.pop();
277 path.pop();
278
279 path.push(PathStep::Variant(1));
280 path.push(PathStep::Field(0));
281 if walk_recursive(rd.e(), visitor, path, ancestors) {
282 return true;
283 }
284 path.pop();
285 path.pop();
286 }
287 Def::Pointer(pd) => {
288 if let Some(pointee) = pd.pointee() {
289 has_structural_children = true;
290 path.push(PathStep::Deref);
291 if walk_recursive(pointee, visitor, path, ancestors) {
292 return true;
293 }
294 path.pop();
295 }
296 }
297 _ => {}
298 }
299 }
300
301 // Transparent inner type — only if nothing above already provided children.
302 // Types like Vec<T> set both Def::List and inner, and we don't want to
303 // visit T twice.
304 if !has_structural_children && let Some(inner) = shape.inner {
305 path.push(PathStep::Inner);
306 if walk_recursive(inner, visitor, path, ancestors) {
307 return true;
308 }
309 path.pop();
310 }
311
312 false
313}