use crate::leaptypes::*; use std::collections::HashSet; pub struct PropRecursionCheck<'a> { spec: &'a LeapSpec, start_name: String, visited: HashSet, } impl<'a> PropRecursionCheck<'a> { pub fn is_recursive(spec: &'a LeapSpec, leap_type: &LeapType, prop: &Prop) -> bool { let mut check = Self { spec, start_name: leap_type.name().get().to_owned(), visited: HashSet::new(), }; check.is_recursive_check(&prop.prop_type) } fn is_recursive_check(&mut self, next: &ValueType) -> bool { if self.start_name == next.name() { return true; } if self.visited.contains(next) { return false; } self.visited.insert(next.clone()); // get type if it is .struct or .enum let next_t = if let Some(t) = self.spec.get_type_by_name(&next.name()) { t } else { return false; }; let next_t = next_t.apply_args(&next.args()); match next_t { LeapType::Struct(s) => { for Prop { prop_type, .. } in &s.props { if self.is_recursive_check(prop_type) { return true; } } } LeapType::Enum(e) => { for Prop { prop_type, .. } in &e.variants { if self.is_recursive_check(prop_type) { return true; } } } } false } } #[cfg(test)] mod test { use super::*; use crate::{parser::parser::Parser, stdtypes::STD_TYPES}; #[test] fn test_simple1() { let spec_text = " .struct s1 a: str "; let spec = LeapSpec::new(Parser::parse(spec_text).unwrap()); let t = spec.get_type_by_name("s1").unwrap(); let s = t.as_struct().unwrap(); assert!(!PropRecursionCheck::is_recursive(&spec, t, &s.props[0])); } #[test] fn test_simple2() { let spec_text = " .struct s1 a: s1 "; let spec = LeapSpec::new(Parser::parse(spec_text).unwrap()); let t = spec.get_type_by_name("s1").unwrap(); let s = t.as_struct().unwrap(); assert!(PropRecursionCheck::is_recursive(&spec, t, &s.props[0])); } #[test] fn test_simple3() { let spec_text = " .struct s1 a: e .struct s2 a: float .enum e s1 s2 "; let spec = LeapSpec::new(Parser::parse(spec_text).unwrap()); let t = spec.get_type_by_name("e").unwrap(); let e = t.as_enum().unwrap(); assert!(PropRecursionCheck::is_recursive(&spec, t, &e.variants[0])); assert!(!PropRecursionCheck::is_recursive(&spec, t, &e.variants[1])); } #[test] fn test_complex() { let spec_text = " .struct s1 a: s2[option[s3]] b: s4 .struct s2[t] a: t .struct s3 a: s1 .struct s4 a: option[s5] .struct s5 a: s4 "; let mut spec = LeapSpec::new(Parser::parse(spec_text).unwrap()); spec.join(LeapSpec::new(Parser::parse(STD_TYPES).unwrap())); let t = spec.get_type_by_name("s1").unwrap(); let s = t.as_struct().unwrap(); assert!(PropRecursionCheck::is_recursive(&spec, t, &s.props[0])); assert!(!PropRecursionCheck::is_recursive(&spec, t, &s.props[1])); let t = spec.get_type_by_name("s4").unwrap(); let s = t.as_struct().unwrap(); assert!(PropRecursionCheck::is_recursive(&spec, t, &s.props[0])); let t = spec.get_type_by_name("s5").unwrap(); let s = t.as_struct().unwrap(); assert!(PropRecursionCheck::is_recursive(&spec, t, &s.props[0])); } }