1use alloc::collections::{BTreeMap, BTreeSet};
11use alloc::vec::Vec;
12
13use crate::type_identifier::{EquivalenceHash, TypeIdentifier};
14use crate::type_object::minimal::MinimalTypeObject;
15use crate::type_object::{CompleteTypeObject, TypeObject};
16
17pub const DEFAULT_MAX_RESOLVE_DEPTH: usize = 64;
21
22pub const DEFAULT_MAX_RESOLVE_NODES: usize = 4_096;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum ResolveError {
32 DepthExceeded {
34 limit: usize,
36 },
37 NodeLimitExceeded {
39 limit: usize,
41 },
42 Unknown {
44 hash: EquivalenceHash,
46 },
47 Cycle,
49}
50
51#[derive(Debug, Clone, Default)]
55pub struct TypeRegistry {
56 minimals: BTreeMap<EquivalenceHash, MinimalTypeObject>,
57 completes: BTreeMap<EquivalenceHash, CompleteTypeObject>,
58}
59
60impl TypeRegistry {
61 #[must_use]
63 pub fn new() -> Self {
64 Self::default()
65 }
66
67 pub fn insert_minimal(&mut self, hash: EquivalenceHash, t: MinimalTypeObject) {
69 self.minimals.insert(hash, t);
70 }
71
72 pub fn insert_complete(&mut self, hash: EquivalenceHash, t: CompleteTypeObject) {
74 self.completes.insert(hash, t);
75 }
76
77 #[must_use]
79 pub fn get_minimal(&self, hash: &EquivalenceHash) -> Option<&MinimalTypeObject> {
80 self.minimals.get(hash)
81 }
82
83 #[must_use]
85 pub fn get_complete(&self, hash: &EquivalenceHash) -> Option<&CompleteTypeObject> {
86 self.completes.get(hash)
87 }
88
89 #[must_use]
91 pub fn len(&self) -> (usize, usize) {
92 (self.minimals.len(), self.completes.len())
93 }
94
95 #[must_use]
97 pub fn is_empty(&self) -> bool {
98 self.minimals.is_empty() && self.completes.is_empty()
99 }
100
101 pub fn iter_minimals(
103 &self,
104 ) -> alloc::collections::btree_map::Iter<'_, EquivalenceHash, MinimalTypeObject> {
105 self.minimals.iter()
106 }
107
108 pub fn iter_completes(
110 &self,
111 ) -> alloc::collections::btree_map::Iter<'_, EquivalenceHash, CompleteTypeObject> {
112 self.completes.iter()
113 }
114
115 #[must_use]
121 pub fn dependencies_of(&self, hash: &EquivalenceHash) -> Vec<EquivalenceHash> {
122 let to = if let Some(m) = self.minimals.get(hash) {
123 TypeObject::Minimal(m.clone())
124 } else if let Some(c) = self.completes.get(hash) {
125 TypeObject::Complete(c.clone())
126 } else {
127 return Vec::new();
128 };
129 collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap_or_default()
130 }
131
132 #[must_use]
136 pub fn transitive_dependencies(
137 &self,
138 hash: &EquivalenceHash,
139 max_nodes: usize,
140 ) -> Vec<EquivalenceHash> {
141 let mut out: Vec<EquivalenceHash> = Vec::new();
142 let mut seen: BTreeSet<EquivalenceHash> = BTreeSet::new();
143 let mut queue: Vec<EquivalenceHash> = self.dependencies_of(hash);
144 while let Some(h) = queue.pop() {
145 if !seen.insert(h) {
146 continue;
147 }
148 if out.len() >= max_nodes {
149 break;
150 }
151 out.push(h);
152 for child in self.dependencies_of(&h) {
153 if !seen.contains(&child) {
154 queue.push(child);
155 }
156 }
157 }
158 out
159 }
160}
161
162pub fn resolve_alias_chain(
170 start: &TypeIdentifier,
171 registry: &TypeRegistry,
172 max_depth: usize,
173) -> Result<TypeIdentifier, ResolveError> {
174 let mut current = start.clone();
175 let mut visited: BTreeSet<EquivalenceHash> = BTreeSet::new();
176 for _ in 0..max_depth {
177 let hash = match ¤t {
178 TypeIdentifier::EquivalenceHashMinimal(h) => *h,
179 TypeIdentifier::EquivalenceHashComplete(h) => *h,
180 _ => return Ok(current), };
182 if !visited.insert(hash) {
183 return Err(ResolveError::Cycle);
184 }
185
186 let next_ti = if matches!(current, TypeIdentifier::EquivalenceHashMinimal(_)) {
187 match registry.get_minimal(&hash) {
188 Some(MinimalTypeObject::Alias(a)) => a.body.common.related_type.clone(),
189 Some(_) => return Ok(current), None => return Err(ResolveError::Unknown { hash }),
191 }
192 } else {
193 match registry.get_complete(&hash) {
194 Some(CompleteTypeObject::Alias(a)) => a.body.related_type.clone(),
195 Some(_) => return Ok(current),
196 None => return Err(ResolveError::Unknown { hash }),
197 }
198 };
199 current = next_ti;
200 }
201 Err(ResolveError::DepthExceeded { limit: max_depth })
202}
203
204pub fn collect_referenced_hashes(
214 root: &TypeObject,
215 max_depth: usize,
216) -> Result<Vec<EquivalenceHash>, ResolveError> {
217 let mut out = Vec::new();
218 let mut seen = Vec::new();
219 collect_from_type_object(root, max_depth, 0, &mut out, &mut seen)?;
220 Ok(out)
221}
222
223fn collect_from_type_object(
224 to: &TypeObject,
225 max_depth: usize,
226 depth: usize,
227 out: &mut Vec<EquivalenceHash>,
228 seen: &mut Vec<EquivalenceHash>,
229) -> Result<(), ResolveError> {
230 if depth >= max_depth {
231 return Err(ResolveError::DepthExceeded { limit: max_depth });
232 }
233 match to {
234 TypeObject::Minimal(m) => collect_from_minimal(m, max_depth, depth + 1, out, seen),
235 TypeObject::Complete(c) => collect_from_complete(c, max_depth, depth + 1, out, seen),
236 }
237}
238
239fn collect_from_minimal(
240 m: &MinimalTypeObject,
241 max_depth: usize,
242 depth: usize,
243 out: &mut Vec<EquivalenceHash>,
244 seen: &mut Vec<EquivalenceHash>,
245) -> Result<(), ResolveError> {
246 match m {
247 MinimalTypeObject::Struct(s) => {
248 collect_from_ti(&s.header.base_type, max_depth, depth, out, seen)?;
249 for mem in &s.member_seq {
250 collect_from_ti(&mem.common.member_type_id, max_depth, depth, out, seen)?;
251 }
252 }
253 MinimalTypeObject::Union(u) => {
254 collect_from_ti(&u.discriminator.common.type_id, max_depth, depth, out, seen)?;
255 for mem in &u.member_seq {
256 collect_from_ti(&mem.common.type_id, max_depth, depth, out, seen)?;
257 }
258 }
259 MinimalTypeObject::Alias(a) => {
260 collect_from_ti(&a.body.common.related_type, max_depth, depth, out, seen)?;
261 }
262 MinimalTypeObject::Sequence(s) => {
263 collect_from_ti(&s.element.common.type_id, max_depth, depth, out, seen)?;
264 }
265 MinimalTypeObject::Array(a) => {
266 collect_from_ti(&a.element.common.type_id, max_depth, depth, out, seen)?;
267 }
268 MinimalTypeObject::Map(m) => {
269 collect_from_ti(&m.key.common.type_id, max_depth, depth, out, seen)?;
270 collect_from_ti(&m.element.common.type_id, max_depth, depth, out, seen)?;
271 }
272 _ => {} }
274 Ok(())
275}
276
277fn collect_from_complete(
278 c: &CompleteTypeObject,
279 max_depth: usize,
280 depth: usize,
281 out: &mut Vec<EquivalenceHash>,
282 seen: &mut Vec<EquivalenceHash>,
283) -> Result<(), ResolveError> {
284 match c {
285 CompleteTypeObject::Struct(s) => {
286 collect_from_ti(&s.header.base_type, max_depth, depth, out, seen)?;
287 for mem in &s.member_seq {
288 collect_from_ti(&mem.common.member_type_id, max_depth, depth, out, seen)?;
289 }
290 }
291 CompleteTypeObject::Union(u) => {
292 collect_from_ti(&u.discriminator.common.type_id, max_depth, depth, out, seen)?;
293 for mem in &u.member_seq {
294 collect_from_ti(&mem.common.type_id, max_depth, depth, out, seen)?;
295 }
296 }
297 CompleteTypeObject::Alias(a) => {
298 collect_from_ti(&a.body.related_type, max_depth, depth, out, seen)?;
299 }
300 CompleteTypeObject::Sequence(s) => {
301 collect_from_ti(&s.element.common.type_id, max_depth, depth, out, seen)?;
302 }
303 CompleteTypeObject::Array(a) => {
304 collect_from_ti(&a.element.common.type_id, max_depth, depth, out, seen)?;
305 }
306 CompleteTypeObject::Map(m) => {
307 collect_from_ti(&m.key.common.type_id, max_depth, depth, out, seen)?;
308 collect_from_ti(&m.element.common.type_id, max_depth, depth, out, seen)?;
309 }
310 _ => {}
311 }
312 Ok(())
313}
314
315fn collect_from_ti(
321 ti: &TypeIdentifier,
322 _max_depth: usize,
323 _depth: usize,
324 out: &mut Vec<EquivalenceHash>,
325 seen: &mut Vec<EquivalenceHash>,
326) -> Result<(), ResolveError> {
327 match ti {
328 TypeIdentifier::EquivalenceHashMinimal(h) | TypeIdentifier::EquivalenceHashComplete(h) => {
329 if !seen.contains(h) {
330 if seen.len() >= DEFAULT_MAX_RESOLVE_NODES {
331 return Err(ResolveError::NodeLimitExceeded {
332 limit: DEFAULT_MAX_RESOLVE_NODES,
333 });
334 }
335 seen.push(*h);
336 out.push(*h);
337 }
338 }
339 TypeIdentifier::PlainSequenceSmall { element, .. }
340 | TypeIdentifier::PlainSequenceLarge { element, .. }
341 | TypeIdentifier::PlainArraySmall { element, .. }
342 | TypeIdentifier::PlainArrayLarge { element, .. } => {
343 collect_from_ti(element, _max_depth, _depth, out, seen)?;
344 }
345 TypeIdentifier::PlainMapSmall { element, key, .. }
346 | TypeIdentifier::PlainMapLarge { element, key, .. } => {
347 collect_from_ti(element, _max_depth, _depth, out, seen)?;
348 collect_from_ti(key, _max_depth, _depth, out, seen)?;
349 }
350 _ => {}
351 }
352 Ok(())
353}
354
355#[cfg(test)]
360#[allow(clippy::unwrap_used, clippy::panic)]
361mod tests {
362 use super::*;
363 use crate::builder::TypeObjectBuilder;
364 use crate::hash::compute_minimal_hash;
365 use crate::type_identifier::PrimitiveKind;
366 use crate::type_object::minimal::MinimalAliasType;
367
368 fn make_alias(related: TypeIdentifier) -> MinimalAliasType {
369 use crate::type_object::flags::{AliasMemberFlag, AliasTypeFlag};
370 use crate::type_object::minimal::{CommonAliasBody, MinimalAliasBody};
371 MinimalAliasType {
372 alias_flags: AliasTypeFlag::default(),
373 body: MinimalAliasBody {
374 common: CommonAliasBody {
375 related_flags: AliasMemberFlag::default(),
376 related_type: related,
377 },
378 },
379 }
380 }
381
382 #[test]
383 fn resolve_returns_primitive_unchanged() {
384 let reg = TypeRegistry::new();
385 let p = TypeIdentifier::Primitive(PrimitiveKind::Int64);
386 let resolved = resolve_alias_chain(&p, ®, 8).unwrap();
387 assert_eq!(resolved, p);
388 }
389
390 #[test]
391 fn resolve_follows_single_alias_to_target() {
392 let mut reg = TypeRegistry::new();
393 let target = TypeIdentifier::Primitive(PrimitiveKind::UInt64);
394 let alias = MinimalTypeObject::Alias(make_alias(target.clone()));
395 let alias_hash = compute_minimal_hash(&alias).unwrap();
396 reg.insert_minimal(alias_hash, alias);
397
398 let start = TypeIdentifier::EquivalenceHashMinimal(alias_hash);
399 let resolved = resolve_alias_chain(&start, ®, 8).unwrap();
400 assert_eq!(resolved, target);
401 }
402
403 #[test]
404 fn resolve_chained_aliases_two_deep() {
405 let mut reg = TypeRegistry::new();
406 let final_target = TypeIdentifier::Primitive(PrimitiveKind::Int8);
407 let inner = MinimalTypeObject::Alias(make_alias(final_target.clone()));
408 let inner_hash = compute_minimal_hash(&inner).unwrap();
409 reg.insert_minimal(inner_hash, inner);
410
411 let outer = MinimalTypeObject::Alias(make_alias(TypeIdentifier::EquivalenceHashMinimal(
412 inner_hash,
413 )));
414 let outer_hash = compute_minimal_hash(&outer).unwrap();
415 reg.insert_minimal(outer_hash, outer);
416
417 let resolved =
418 resolve_alias_chain(&TypeIdentifier::EquivalenceHashMinimal(outer_hash), ®, 8)
419 .unwrap();
420 assert_eq!(resolved, final_target);
421 }
422
423 #[test]
424 fn resolve_cycle_detected() {
425 let mut reg = TypeRegistry::new();
426 let self_hash = EquivalenceHash([0x99; 14]);
431 let self_alias = MinimalTypeObject::Alias(make_alias(
432 TypeIdentifier::EquivalenceHashMinimal(self_hash),
433 ));
434 reg.insert_minimal(self_hash, self_alias);
435
436 let err = resolve_alias_chain(&TypeIdentifier::EquivalenceHashMinimal(self_hash), ®, 8)
437 .unwrap_err();
438 assert_eq!(err, ResolveError::Cycle);
439 }
440
441 #[test]
442 fn resolve_unknown_hash_fails() {
443 let reg = TypeRegistry::new();
444 let err = resolve_alias_chain(
445 &TypeIdentifier::EquivalenceHashMinimal(EquivalenceHash([0xAA; 14])),
446 ®,
447 8,
448 )
449 .unwrap_err();
450 assert!(matches!(err, ResolveError::Unknown { .. }));
451 }
452
453 #[test]
454 fn collect_referenced_hashes_picks_up_nested_struct_members() {
455 let inner_hash = EquivalenceHash([0x01; 14]);
456 let st = TypeObjectBuilder::struct_type("::X")
457 .member(
458 "ref_a",
459 TypeIdentifier::EquivalenceHashMinimal(inner_hash),
460 |m| m,
461 )
462 .member("p", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
463 .build_minimal();
464 let to = TypeObject::Minimal(MinimalTypeObject::Struct(st));
465 let refs = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap();
466 assert_eq!(refs, alloc::vec![inner_hash]);
467 }
468
469 #[test]
470 fn collect_referenced_hashes_from_union_cases() {
471 use crate::type_object::common::CommonUnionMember;
472 use crate::type_object::flags::{UnionDiscriminatorFlag, UnionMemberFlag, UnionTypeFlag};
473 use crate::type_object::minimal::{
474 CommonDiscriminatorMember, MinimalDiscriminatorMember, MinimalUnionMember,
475 MinimalUnionType,
476 };
477 let h_disc = EquivalenceHash([0x01; 14]);
478 let h_case = EquivalenceHash([0x02; 14]);
479 let u = MinimalUnionType {
480 union_flags: UnionTypeFlag::default(),
481 discriminator: MinimalDiscriminatorMember {
482 common: CommonDiscriminatorMember {
483 member_flags: UnionDiscriminatorFlag::default(),
484 type_id: TypeIdentifier::EquivalenceHashMinimal(h_disc),
485 },
486 },
487 member_seq: alloc::vec![MinimalUnionMember {
488 common: CommonUnionMember {
489 member_id: 1,
490 member_flags: UnionMemberFlag::default(),
491 type_id: TypeIdentifier::EquivalenceHashMinimal(h_case),
492 label_seq: alloc::vec![1],
493 },
494 detail: crate::type_object::common::NameHash([0; 4]),
495 }],
496 };
497 let to = TypeObject::Minimal(MinimalTypeObject::Union(u));
498 let refs = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap();
499 assert!(refs.contains(&h_disc));
500 assert!(refs.contains(&h_case));
501 }
502
503 #[test]
504 fn collect_referenced_hashes_from_sequence_and_array_and_map() {
505 use crate::builder::TypeObjectBuilder;
506 let h_elem = EquivalenceHash([0x10; 14]);
507
508 let seq = TypeObjectBuilder::sequence(TypeIdentifier::EquivalenceHashMinimal(h_elem), 50)
509 .build_minimal();
510 let refs = collect_referenced_hashes(
511 &TypeObject::Minimal(MinimalTypeObject::Sequence(seq)),
512 DEFAULT_MAX_RESOLVE_DEPTH,
513 )
514 .unwrap();
515 assert_eq!(refs, alloc::vec![h_elem]);
516
517 let arr = TypeObjectBuilder::array(
518 TypeIdentifier::EquivalenceHashMinimal(h_elem),
519 alloc::vec![2, 3],
520 )
521 .build_minimal();
522 let refs = collect_referenced_hashes(
523 &TypeObject::Minimal(MinimalTypeObject::Array(arr)),
524 DEFAULT_MAX_RESOLVE_DEPTH,
525 )
526 .unwrap();
527 assert_eq!(refs, alloc::vec![h_elem]);
528
529 let h_key = EquivalenceHash([0x20; 14]);
530 let map = TypeObjectBuilder::map(
531 TypeIdentifier::EquivalenceHashMinimal(h_key),
532 TypeIdentifier::EquivalenceHashMinimal(h_elem),
533 10,
534 )
535 .build_minimal();
536 let refs = collect_referenced_hashes(
537 &TypeObject::Minimal(MinimalTypeObject::Map(map)),
538 DEFAULT_MAX_RESOLVE_DEPTH,
539 )
540 .unwrap();
541 assert!(refs.contains(&h_key));
542 assert!(refs.contains(&h_elem));
543 }
544
545 #[test]
546 fn collect_referenced_hashes_from_complete_typeobject() {
547 use crate::builder::TypeObjectBuilder;
548 let h = EquivalenceHash([0x44; 14]);
549 let st = TypeObjectBuilder::struct_type("::X")
550 .member("f", TypeIdentifier::EquivalenceHashMinimal(h), |m| m)
551 .build_complete();
552 let to = TypeObject::Complete(CompleteTypeObject::Struct(st));
553 let refs = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap();
554 assert_eq!(refs, alloc::vec![h]);
555 }
556
557 #[test]
558 fn collect_referenced_hashes_from_alias() {
559 use crate::builder::TypeObjectBuilder;
560 let h = EquivalenceHash([0x55; 14]);
561 let a = TypeObjectBuilder::alias("::A", TypeIdentifier::EquivalenceHashMinimal(h))
562 .build_minimal();
563 let to = TypeObject::Minimal(MinimalTypeObject::Alias(a));
564 let refs = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap();
565 assert_eq!(refs, alloc::vec![h]);
566 }
567
568 #[test]
569 fn collect_referenced_hashes_nested_plain_sequence_in_plain_map() {
570 let h = EquivalenceHash([0x66; 14]);
572 let inner_map = TypeIdentifier::PlainMapSmall {
573 header: crate::type_identifier::PlainCollectionHeader::default(),
574 bound: 1,
575 element: alloc::boxed::Box::new(TypeIdentifier::EquivalenceHashMinimal(h)),
576 key_flags: crate::type_identifier::CollectionElementFlag(0),
577 key: alloc::boxed::Box::new(TypeIdentifier::Primitive(
578 crate::type_identifier::PrimitiveKind::Int32,
579 )),
580 };
581 let outer_seq = TypeIdentifier::PlainSequenceSmall {
582 header: crate::type_identifier::PlainCollectionHeader::default(),
583 bound: 3,
584 element: alloc::boxed::Box::new(inner_map),
585 };
586 use crate::builder::TypeObjectBuilder;
587 let st = TypeObjectBuilder::struct_type("::Nested")
588 .member("data", outer_seq, |m| m)
589 .build_minimal();
590 let to = TypeObject::Minimal(MinimalTypeObject::Struct(st));
591 let refs = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap();
592 assert_eq!(refs, alloc::vec![h]);
593 }
594
595 #[test]
596 fn resolve_depth_exceeded_with_long_alias_chain() {
597 let mut reg = TypeRegistry::new();
599 let mut last_hash = EquivalenceHash([0x00; 14]);
600 for i in 0..10u8 {
601 let target = if i == 0 {
602 TypeIdentifier::Primitive(crate::type_identifier::PrimitiveKind::Int32)
603 } else {
604 TypeIdentifier::EquivalenceHashMinimal(last_hash)
605 };
606 let alias = MinimalTypeObject::Alias(make_alias(target));
607 let h = crate::hash::compute_minimal_hash(&alias).unwrap();
608 reg.insert_minimal(h, alias);
609 last_hash = h;
610 }
611 let err = resolve_alias_chain(
612 &TypeIdentifier::EquivalenceHashMinimal(last_hash),
613 ®,
614 3, )
616 .unwrap_err();
617 assert_eq!(err, ResolveError::DepthExceeded { limit: 3 });
618 }
619
620 #[test]
621 fn collect_referenced_hashes_skips_duplicates() {
622 let h = EquivalenceHash([0x22; 14]);
623 let st = TypeObjectBuilder::struct_type("::Y")
624 .member("a", TypeIdentifier::EquivalenceHashMinimal(h), |m| m)
625 .member("b", TypeIdentifier::EquivalenceHashMinimal(h), |m| m)
626 .build_minimal();
627 let to = TypeObject::Minimal(MinimalTypeObject::Struct(st));
628 let refs = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap();
629 assert_eq!(refs.len(), 1);
630 }
631
632 #[test]
633 fn collect_referenced_hashes_node_limit_exceeded_on_wide_struct() {
634 let mut builder = TypeObjectBuilder::struct_type("::Wide");
637 for i in 0..(DEFAULT_MAX_RESOLVE_NODES as u32 + 1) {
638 let mut h_bytes = [0u8; 14];
639 h_bytes[..4].copy_from_slice(&i.to_le_bytes());
640 let h = EquivalenceHash(h_bytes);
641 builder = builder.member(
642 alloc::format!("m_{i}").as_str(),
643 TypeIdentifier::EquivalenceHashMinimal(h),
644 |m| m,
645 );
646 }
647 let st = builder.build_minimal();
648 let to = TypeObject::Minimal(MinimalTypeObject::Struct(st));
649 let err = collect_referenced_hashes(&to, DEFAULT_MAX_RESOLVE_DEPTH).unwrap_err();
650 assert_eq!(
651 err,
652 ResolveError::NodeLimitExceeded {
653 limit: DEFAULT_MAX_RESOLVE_NODES,
654 }
655 );
656 }
657
658 fn struct_with_member_ref(name: &str, ref_hash: EquivalenceHash) -> MinimalTypeObject {
669 MinimalTypeObject::Struct(
670 TypeObjectBuilder::struct_type(name)
671 .member(
672 "next",
673 TypeIdentifier::EquivalenceHashMinimal(ref_hash),
674 |m| m,
675 )
676 .build_minimal(),
677 )
678 }
679
680 #[test]
681 fn scc_three_element_cycle_resolves_correctly() {
682 let h_a = EquivalenceHash([0x0A; 14]);
684 let h_b = EquivalenceHash([0x0B; 14]);
685 let h_c = EquivalenceHash([0x0C; 14]);
686 let mut reg = TypeRegistry::new();
687 reg.insert_minimal(h_a, struct_with_member_ref("A", h_b));
688 reg.insert_minimal(h_b, struct_with_member_ref("B", h_c));
689 reg.insert_minimal(h_c, struct_with_member_ref("C", h_a));
690
691 let deps = reg.transitive_dependencies(&h_a, DEFAULT_MAX_RESOLVE_NODES);
692 assert!(deps.contains(&h_a));
693 assert!(deps.contains(&h_b));
694 assert!(deps.contains(&h_c));
695 let mut sorted = deps.clone();
698 sorted.sort();
699 sorted.dedup();
700 assert_eq!(sorted.len(), 3);
701 }
702
703 #[test]
704 fn scc_four_element_cycle_resolves_correctly() {
705 let h_a = EquivalenceHash([0x1A; 14]);
707 let h_b = EquivalenceHash([0x1B; 14]);
708 let h_c = EquivalenceHash([0x1C; 14]);
709 let h_d = EquivalenceHash([0x1D; 14]);
710 let mut reg = TypeRegistry::new();
711 reg.insert_minimal(h_a, struct_with_member_ref("A", h_b));
712 reg.insert_minimal(h_b, struct_with_member_ref("B", h_c));
713 reg.insert_minimal(h_c, struct_with_member_ref("C", h_d));
714 reg.insert_minimal(h_d, struct_with_member_ref("D", h_a));
715
716 let deps = reg.transitive_dependencies(&h_a, DEFAULT_MAX_RESOLVE_NODES);
717 assert!(deps.contains(&h_a));
718 assert!(deps.contains(&h_b));
719 assert!(deps.contains(&h_c));
720 assert!(deps.contains(&h_d));
721 let mut sorted = deps.clone();
722 sorted.sort();
723 sorted.dedup();
724 assert_eq!(sorted.len(), 4);
725 }
726
727 #[test]
728 fn scc_diamond_with_cycle_resolves_finitely() {
729 let h_a = EquivalenceHash([0x2A; 14]);
734 let h_b = EquivalenceHash([0x2B; 14]);
735 let h_c = EquivalenceHash([0x2C; 14]);
736 let h_d = EquivalenceHash([0x2D; 14]);
737 let mut reg = TypeRegistry::new();
738 let st_a = MinimalTypeObject::Struct(
740 TypeObjectBuilder::struct_type("A")
741 .member("to_b", TypeIdentifier::EquivalenceHashMinimal(h_b), |m| m)
742 .member("to_c", TypeIdentifier::EquivalenceHashMinimal(h_c), |m| m)
743 .build_minimal(),
744 );
745 reg.insert_minimal(h_a, st_a);
746 reg.insert_minimal(h_b, struct_with_member_ref("B", h_d));
747 reg.insert_minimal(h_c, struct_with_member_ref("C", h_a));
748 let st_d = MinimalTypeObject::Struct(
750 TypeObjectBuilder::struct_type("D")
751 .member("x", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
752 .build_minimal(),
753 );
754 reg.insert_minimal(h_d, st_d);
755
756 let deps = reg.transitive_dependencies(&h_a, DEFAULT_MAX_RESOLVE_NODES);
757 let mut sorted = deps.clone();
758 sorted.sort();
759 sorted.dedup();
760 assert!(sorted.contains(&h_a));
761 assert!(sorted.contains(&h_b));
762 assert!(sorted.contains(&h_c));
763 assert!(sorted.contains(&h_d));
764 assert_eq!(sorted.len(), 4);
765 }
766
767 #[test]
770 fn external_forward_decl_roundtrip() {
771 use crate::type_identifier::PrimitiveKind;
774 use crate::type_object::TypeObject;
775 use crate::type_object::flags::StructMemberFlag;
776 use crate::type_object::minimal::MinimalTypeObject;
777 let h_self = EquivalenceHash([0x4A; 14]);
778 let mut a = TypeObjectBuilder::struct_type("::A")
779 .member(
780 "next",
781 TypeIdentifier::EquivalenceHashMinimal(h_self),
782 |m| m.external().id(1),
783 )
784 .member(
785 "value",
786 TypeIdentifier::Primitive(PrimitiveKind::Int32),
787 |m| m.id(2),
788 )
789 .build_minimal();
790 a.member_seq[0].common.member_flags = StructMemberFlag(StructMemberFlag::IS_EXTERNAL);
792
793 let mut reg = TypeRegistry::new();
794 reg.insert_minimal(h_self, MinimalTypeObject::Struct(a.clone()));
795
796 let to = TypeObject::Minimal(MinimalTypeObject::Struct(a.clone()));
798 let bytes = to.to_bytes_le().unwrap();
799 let decoded = TypeObject::from_bytes_le(&bytes).unwrap();
800 if let TypeObject::Minimal(MinimalTypeObject::Struct(b)) = decoded {
801 assert!(
802 b.member_seq[0]
803 .common
804 .member_flags
805 .has(StructMemberFlag::IS_EXTERNAL)
806 );
807 assert_eq!(b.member_seq[0].common.member_id, 1);
808 } else {
809 panic!("expected MinimalStruct");
810 }
811
812 let deps = reg.transitive_dependencies(&h_self, DEFAULT_MAX_RESOLVE_NODES);
814 assert!(deps.contains(&h_self));
815 }
816
817 #[test]
818 fn external_diamond_resolves_through_both_paths() {
819 use crate::type_identifier::PrimitiveKind;
825 use crate::type_object::flags::StructMemberFlag;
826 use crate::type_object::minimal::MinimalTypeObject;
827 let h_a = EquivalenceHash([0x5A; 14]);
828 let h_b = EquivalenceHash([0x5B; 14]);
829 let h_c = EquivalenceHash([0x5C; 14]);
830 let h_d = EquivalenceHash([0x5D; 14]);
831
832 let mut a = TypeObjectBuilder::struct_type("A")
833 .member("to_b", TypeIdentifier::EquivalenceHashMinimal(h_b), |m| {
834 m.external().id(1)
835 })
836 .member("to_c", TypeIdentifier::EquivalenceHashMinimal(h_c), |m| {
837 m.external().id(2)
838 })
839 .build_minimal();
840 for m in &mut a.member_seq {
842 m.common.member_flags = StructMemberFlag(StructMemberFlag::IS_EXTERNAL);
843 }
844
845 let b = TypeObjectBuilder::struct_type("B")
846 .member("to_d", TypeIdentifier::EquivalenceHashMinimal(h_d), |m| {
847 m.external().id(1)
848 })
849 .build_minimal();
850 let c = TypeObjectBuilder::struct_type("C")
851 .member("to_d", TypeIdentifier::EquivalenceHashMinimal(h_d), |m| {
852 m.external().id(1)
853 })
854 .build_minimal();
855 let d = TypeObjectBuilder::struct_type("D")
856 .member("v", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
857 m.id(1)
858 })
859 .build_minimal();
860
861 let mut reg = TypeRegistry::new();
862 reg.insert_minimal(h_a, MinimalTypeObject::Struct(a));
863 reg.insert_minimal(h_b, MinimalTypeObject::Struct(b));
864 reg.insert_minimal(h_c, MinimalTypeObject::Struct(c));
865 reg.insert_minimal(h_d, MinimalTypeObject::Struct(d));
866
867 let deps = reg.transitive_dependencies(&h_a, DEFAULT_MAX_RESOLVE_NODES);
868 let mut sorted = deps.clone();
869 sorted.sort();
870 sorted.dedup();
871 assert!(sorted.contains(&h_b));
872 assert!(sorted.contains(&h_c));
873 assert!(sorted.contains(&h_d));
874 assert_eq!(sorted.len(), 3);
876 }
877
878 #[test]
879 fn scc_self_loop_does_not_explode_node_count() {
880 let h_a = EquivalenceHash([0x3A; 14]);
882 let mut reg = TypeRegistry::new();
883 reg.insert_minimal(h_a, struct_with_member_ref("A", h_a));
884
885 let deps = reg.transitive_dependencies(&h_a, DEFAULT_MAX_RESOLVE_NODES);
886 assert_eq!(deps, alloc::vec![h_a]);
889 }
890}