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