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