use alloc::vec::Vec;
use facet_core::{ConstTypeId, Def, Shape, Type, UserType};
use crate::{Path, PathStep};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VisitDecision {
Recurse,
SkipChildren,
Stop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WalkStatus {
Completed,
Stopped,
}
pub trait ShapeVisitor {
fn enter(&mut self, path: &Path, shape: &'static Shape) -> VisitDecision;
fn leave(&mut self, path: &Path, shape: &'static Shape) {
let _ = (path, shape);
}
}
pub fn walk_shape(shape: &'static Shape, visitor: &mut impl ShapeVisitor) -> WalkStatus {
let mut path = Path::new(shape);
let mut ancestors: Vec<ConstTypeId> = Vec::new();
if walk_recursive(shape, visitor, &mut path, &mut ancestors) {
WalkStatus::Stopped
} else {
WalkStatus::Completed
}
}
fn walk_recursive(
shape: &'static Shape,
visitor: &mut impl ShapeVisitor,
path: &mut Path,
ancestors: &mut Vec<ConstTypeId>,
) -> bool {
let is_cycle = ancestors.contains(&shape.id);
let decision = visitor.enter(path, shape);
match decision {
VisitDecision::Stop => return true,
VisitDecision::SkipChildren => {
visitor.leave(path, shape);
return false;
}
VisitDecision::Recurse => {
if is_cycle {
visitor.leave(path, shape);
return false;
}
}
}
ancestors.push(shape.id);
let stopped = walk_children(shape, visitor, path, ancestors);
ancestors.pop();
if !stopped {
visitor.leave(path, shape);
}
stopped
}
fn walk_children(
shape: &'static Shape,
visitor: &mut impl ShapeVisitor,
path: &mut Path,
ancestors: &mut Vec<ConstTypeId>,
) -> bool {
let mut has_structural_children = false;
if let Def::Option(od) = shape.def {
path.push(PathStep::OptionSome);
if walk_recursive(od.t(), visitor, path, ancestors) {
return true;
}
path.pop();
return false;
}
match shape.ty {
Type::User(UserType::Struct(st)) => {
has_structural_children = true;
for (i, field) in st.fields.iter().enumerate() {
path.push(PathStep::Field(i as u32));
if walk_recursive(field.shape(), visitor, path, ancestors) {
return true;
}
path.pop();
}
}
Type::User(UserType::Enum(et)) => {
has_structural_children = true;
for (vi, variant) in et.variants.iter().enumerate() {
path.push(PathStep::Variant(vi as u32));
for (fi, field) in variant.data.fields.iter().enumerate() {
path.push(PathStep::Field(fi as u32));
if walk_recursive(field.shape(), visitor, path, ancestors) {
return true;
}
path.pop();
}
path.pop();
}
}
_ => {}
}
if !has_structural_children {
match shape.def {
Def::List(ld) => {
has_structural_children = true;
path.push(PathStep::Index(0));
if walk_recursive(ld.t(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::Array(ad) => {
has_structural_children = true;
path.push(PathStep::Index(0));
if walk_recursive(ad.t(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::Slice(sd) => {
has_structural_children = true;
path.push(PathStep::Index(0));
if walk_recursive(sd.t(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::NdArray(nd) => {
has_structural_children = true;
path.push(PathStep::Index(0));
if walk_recursive(nd.t(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::Set(sd) => {
has_structural_children = true;
path.push(PathStep::Index(0));
if walk_recursive(sd.t(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::Map(md) => {
has_structural_children = true;
path.push(PathStep::MapKey(0));
if walk_recursive(md.k(), visitor, path, ancestors) {
return true;
}
path.pop();
path.push(PathStep::MapValue(0));
if walk_recursive(md.v(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::Option(od) => {
has_structural_children = true;
path.push(PathStep::OptionSome);
if walk_recursive(od.t(), visitor, path, ancestors) {
return true;
}
path.pop();
}
Def::Result(rd) => {
has_structural_children = true;
path.push(PathStep::Variant(0));
path.push(PathStep::Field(0));
if walk_recursive(rd.t(), visitor, path, ancestors) {
return true;
}
path.pop();
path.pop();
path.push(PathStep::Variant(1));
path.push(PathStep::Field(0));
if walk_recursive(rd.e(), visitor, path, ancestors) {
return true;
}
path.pop();
path.pop();
}
Def::Pointer(pd) => {
if let Some(pointee) = pd.pointee() {
has_structural_children = true;
path.push(PathStep::Deref);
if walk_recursive(pointee, visitor, path, ancestors) {
return true;
}
path.pop();
}
}
_ => {}
}
}
if !has_structural_children && let Some(inner) = shape.inner {
path.push(PathStep::Inner);
if walk_recursive(inner, visitor, path, ancestors) {
return true;
}
path.pop();
}
false
}