1use crate::resolve::{TypeRegistry, resolve_alias_chain};
16use crate::type_identifier::{PrimitiveKind, TypeIdentifier};
17use crate::type_object::flags::StructTypeFlag;
18use crate::type_object::minimal::{MinimalStructType, MinimalTypeObject};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum InheritanceError {
23 UnknownBase {
26 hash: crate::type_identifier::EquivalenceHash,
28 },
29 BaseNotAStruct,
32 Cycle,
34 DepthExceeded {
36 limit: usize,
38 },
39 InheritanceConflict {
42 member_id: u32,
44 reason: &'static str,
46 },
47}
48
49pub fn flatten_inheritance(
59 s: &MinimalStructType,
60 registry: &TypeRegistry,
61 max_depth: usize,
62) -> Result<MinimalStructType, InheritanceError> {
63 use alloc::collections::BTreeSet;
64
65 let mut visited: BTreeSet<crate::type_identifier::EquivalenceHash> = BTreeSet::new();
66 let mut chain: alloc::vec::Vec<MinimalStructType> = alloc::vec::Vec::new();
67 let mut current = s.clone();
68 for _ in 0..max_depth {
69 let base_ti = current.header.base_type.clone();
70 chain.push(current.clone());
71 match base_ti {
72 TypeIdentifier::None => break,
73 TypeIdentifier::EquivalenceHashMinimal(h)
74 | TypeIdentifier::EquivalenceHashComplete(h) => {
75 if !visited.insert(h) {
76 return Err(InheritanceError::Cycle);
77 }
78 let to = match registry.get_minimal(&h) {
79 Some(MinimalTypeObject::Struct(b)) => b.clone(),
80 Some(_) => return Err(InheritanceError::BaseNotAStruct),
81 None => return Err(InheritanceError::UnknownBase { hash: h }),
82 };
83 current = to;
84 }
85 _ => return Err(InheritanceError::BaseNotAStruct),
86 }
87 }
88 if chain
89 .last()
90 .is_none_or(|c| c.header.base_type != TypeIdentifier::None)
91 {
92 return Err(InheritanceError::DepthExceeded { limit: max_depth });
93 }
94
95 let mut flat_members: alloc::vec::Vec<crate::type_object::minimal::MinimalStructMember> =
98 alloc::vec::Vec::new();
99 let mut seen_ids: BTreeSet<u32> = BTreeSet::new();
100 let mut seen_names: BTreeSet<crate::type_object::common::NameHash> = BTreeSet::new();
101 for st in chain.iter().rev() {
102 for m in &st.member_seq {
103 if !seen_ids.insert(m.common.member_id) {
104 return Err(InheritanceError::InheritanceConflict {
105 member_id: m.common.member_id,
106 reason: "member_id collides between base and derived",
107 });
108 }
109 if !seen_names.insert(m.detail) {
110 return Err(InheritanceError::InheritanceConflict {
111 member_id: m.common.member_id,
112 reason: "member name_hash collides between base and derived",
113 });
114 }
115 flat_members.push(m.clone());
116 }
117 }
118
119 let Some(derived) = chain.first().cloned() else {
122 return Err(InheritanceError::DepthExceeded { limit: max_depth });
123 };
124 let mut flat = derived;
125 flat.header.base_type = TypeIdentifier::None;
126 flat.member_seq = flat_members;
127 Ok(flat)
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub struct AssignabilityConfig {
137 pub allow_type_coercion: bool,
139 pub ignore_sequence_bounds: bool,
141 pub ignore_string_bounds: bool,
143 pub ignore_member_names: bool,
146 pub ignore_literal_names: bool,
151 pub max_depth: usize,
153}
154
155impl Default for AssignabilityConfig {
156 fn default() -> Self {
157 Self {
158 allow_type_coercion: false,
159 ignore_sequence_bounds: true,
160 ignore_string_bounds: true,
161 ignore_member_names: false,
162 ignore_literal_names: false,
163 max_depth: crate::resolve::DEFAULT_MAX_RESOLVE_DEPTH,
164 }
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum Assignable {
171 Yes,
173 No(&'static str),
175}
176
177impl Assignable {
178 #[must_use]
180 pub const fn is_yes(&self) -> bool {
181 matches!(self, Self::Yes)
182 }
183}
184
185pub fn is_assignable(
199 w: &TypeIdentifier,
200 r: &TypeIdentifier,
201 registry: &TypeRegistry,
202 cfg: &AssignabilityConfig,
203) -> Assignable {
204 if w == r {
213 return Assignable::Yes;
214 }
215
216 let Ok(w) = resolve_alias_chain(w, registry, cfg.max_depth) else {
218 return Assignable::No("writer alias resolution failed");
219 };
220 let Ok(r) = resolve_alias_chain(r, registry, cfg.max_depth) else {
221 return Assignable::No("reader alias resolution failed");
222 };
223
224 check_direct(&w, &r, registry, cfg)
225}
226
227fn check_direct(
233 w: &TypeIdentifier,
234 r: &TypeIdentifier,
235 registry: &TypeRegistry,
236 cfg: &AssignabilityConfig,
237) -> Assignable {
238 if w == r {
240 return Assignable::Yes;
241 }
242
243 match (w, r) {
244 (TypeIdentifier::Primitive(wp), TypeIdentifier::Primitive(rp)) => {
246 primitive_compatible(*wp, *rp, cfg)
247 }
248 (
251 TypeIdentifier::String8Small { .. } | TypeIdentifier::String8Large { .. },
252 TypeIdentifier::String8Small { .. } | TypeIdentifier::String8Large { .. },
253 ) => {
254 let (wb, rb) = (string_bound_u32_s8(w), string_bound_u32_s8(r));
255 if !cfg.ignore_string_bounds && rb != 0 && wb > rb {
256 Assignable::No("writer string8 bound exceeds reader bound")
257 } else {
258 Assignable::Yes
259 }
260 }
261 (
262 TypeIdentifier::String16Small { .. } | TypeIdentifier::String16Large { .. },
263 TypeIdentifier::String16Small { .. } | TypeIdentifier::String16Large { .. },
264 ) => {
265 let (wb, rb) = (string_bound_u32_s16(w), string_bound_u32_s16(r));
266 if !cfg.ignore_string_bounds && rb != 0 && wb > rb {
267 Assignable::No("writer string16 bound exceeds reader bound")
268 } else {
269 Assignable::Yes
270 }
271 }
272
273 (
277 TypeIdentifier::PlainSequenceSmall { .. } | TypeIdentifier::PlainSequenceLarge { .. },
278 TypeIdentifier::PlainSequenceSmall { .. } | TypeIdentifier::PlainSequenceLarge { .. },
279 ) => {
280 let (we, wb) = sequence_parts(w);
281 let (re, rb) = sequence_parts(r);
282 if !cfg.ignore_sequence_bounds && rb != 0 && wb > rb {
283 return Assignable::No("writer sequence bound exceeds reader bound");
284 }
285 is_assignable(we, re, registry, cfg)
286 }
287
288 (
293 TypeIdentifier::PlainArraySmall { .. } | TypeIdentifier::PlainArrayLarge { .. },
294 TypeIdentifier::PlainArraySmall { .. } | TypeIdentifier::PlainArrayLarge { .. },
295 ) => {
296 let (we, wb) = array_parts(w);
297 let (re, rb) = array_parts(r);
298 if wb != rb {
299 return Assignable::No("array bounds differ");
300 }
301 is_assignable(we, re, registry, cfg)
302 }
303
304 (
306 TypeIdentifier::PlainMapSmall { .. } | TypeIdentifier::PlainMapLarge { .. },
307 TypeIdentifier::PlainMapSmall { .. } | TypeIdentifier::PlainMapLarge { .. },
308 ) => {
309 let (we, wk, wb) = map_parts(w);
310 let (re, rk, rb) = map_parts(r);
311 if !cfg.ignore_sequence_bounds && rb != 0 && wb > rb {
312 return Assignable::No("writer map bound exceeds reader bound");
313 }
314 match is_assignable(wk, rk, registry, cfg) {
315 Assignable::Yes => is_assignable(we, re, registry, cfg),
316 e => e,
317 }
318 }
319
320 (
322 TypeIdentifier::EquivalenceHashMinimal(wh),
323 TypeIdentifier::EquivalenceHashMinimal(rh),
324 ) => {
325 if wh == rh {
326 return Assignable::Yes;
327 }
328 match (registry.get_minimal(wh), registry.get_minimal(rh)) {
330 (Some(wobj), Some(robj)) => check_minimal_types(wobj, robj, registry, cfg),
331 _ => Assignable::No("unknown type objects for hash comparison"),
332 }
333 }
334 (
342 TypeIdentifier::EquivalenceHashComplete(wh),
343 TypeIdentifier::EquivalenceHashComplete(rh),
344 ) => {
345 if wh == rh {
346 return Assignable::Yes;
347 }
348 match (registry.get_minimal(wh), registry.get_minimal(rh)) {
351 (Some(wobj), Some(robj)) => check_minimal_types(wobj, robj, registry, cfg),
352 _ => Assignable::No("unknown complete type objects for hash comparison"),
353 }
354 }
355
356 _ => Assignable::No("kinds do not match"),
357 }
358}
359
360fn sequence_parts(ti: &TypeIdentifier) -> (&TypeIdentifier, u32) {
361 match ti {
362 TypeIdentifier::PlainSequenceSmall { element, bound, .. } => (element, u32::from(*bound)),
363 TypeIdentifier::PlainSequenceLarge { element, bound, .. } => (element, *bound),
364 _ => (ti, 0),
365 }
366}
367
368fn array_parts(ti: &TypeIdentifier) -> (&TypeIdentifier, alloc::vec::Vec<u32>) {
369 match ti {
370 TypeIdentifier::PlainArraySmall {
371 element,
372 array_bounds,
373 ..
374 } => (
375 element,
376 array_bounds.iter().map(|b| u32::from(*b)).collect(),
377 ),
378 TypeIdentifier::PlainArrayLarge {
379 element,
380 array_bounds,
381 ..
382 } => (element, array_bounds.clone()),
383 _ => (ti, alloc::vec::Vec::new()),
384 }
385}
386
387fn map_parts(ti: &TypeIdentifier) -> (&TypeIdentifier, &TypeIdentifier, u32) {
388 match ti {
389 TypeIdentifier::PlainMapSmall {
390 element,
391 key,
392 bound,
393 ..
394 } => (element, key, u32::from(*bound)),
395 TypeIdentifier::PlainMapLarge {
396 element,
397 key,
398 bound,
399 ..
400 } => (element, key, *bound),
401 _ => (ti, ti, 0),
402 }
403}
404
405fn string_bound_u32_s8(ti: &TypeIdentifier) -> u32 {
406 match ti {
407 TypeIdentifier::String8Small { bound } => u32::from(*bound),
408 TypeIdentifier::String8Large { bound } => *bound,
409 _ => 0,
410 }
411}
412
413fn string_bound_u32_s16(ti: &TypeIdentifier) -> u32 {
414 match ti {
415 TypeIdentifier::String16Small { bound } => u32::from(*bound),
416 TypeIdentifier::String16Large { bound } => *bound,
417 _ => 0,
418 }
419}
420
421fn primitive_compatible(
422 w: PrimitiveKind,
423 r: PrimitiveKind,
424 cfg: &AssignabilityConfig,
425) -> Assignable {
426 if w == r {
427 return Assignable::Yes;
428 }
429 if !cfg.allow_type_coercion {
430 return Assignable::No("primitive kinds differ (no coercion allowed)");
431 }
432 use PrimitiveKind::*;
437 let ok = matches!(
438 (w, r),
439 (Int8 | UInt8 | Byte, Int16 | Int32 | Int64)
440 | (Int16 | UInt16, Int32 | Int64)
441 | (Int32 | UInt32, Int64)
442 | (UInt8 | Byte, UInt16 | UInt32 | UInt64)
443 | (UInt16, UInt32 | UInt64)
444 | (UInt32, UInt64)
445 | (Float32, Float64)
446 );
447 if ok {
448 Assignable::Yes
449 } else {
450 Assignable::No("primitive coercion not widening-safe")
451 }
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457enum StructExt {
458 Final,
459 Appendable,
460 Mutable,
461}
462
463fn struct_extensibility(flags: StructTypeFlag) -> StructExt {
464 if flags.has(StructTypeFlag::IS_FINAL) {
465 StructExt::Final
466 } else if flags.has(StructTypeFlag::IS_MUTABLE) {
467 StructExt::Mutable
468 } else {
469 StructExt::Appendable
470 }
471}
472
473fn check_minimal_types(
474 w: &MinimalTypeObject,
475 r: &MinimalTypeObject,
476 registry: &TypeRegistry,
477 cfg: &AssignabilityConfig,
478) -> Assignable {
479 match (w, r) {
480 (MinimalTypeObject::Struct(ws), MinimalTypeObject::Struct(rs)) => {
481 let w_ext = struct_extensibility(ws.struct_flags);
487 let r_ext = struct_extensibility(rs.struct_flags);
488 if w_ext != r_ext {
489 return Assignable::No("extensibility mismatch");
490 }
491 let w_final = matches!(w_ext, StructExt::Final);
492 let w_mut = matches!(w_ext, StructExt::Mutable);
493
494 if w_final {
495 if ws.member_seq.len() != rs.member_seq.len() {
497 return Assignable::No("final struct member count mismatch");
498 }
499 for (wm, rm) in ws.member_seq.iter().zip(rs.member_seq.iter()) {
500 if !cfg.ignore_member_names && wm.detail != rm.detail {
501 return Assignable::No("final struct member name-hash differs");
502 }
503 match is_assignable(
504 &wm.common.member_type_id,
505 &rm.common.member_type_id,
506 registry,
507 cfg,
508 ) {
509 Assignable::Yes => {}
510 e => return e,
511 }
512 }
513 Assignable::Yes
514 } else if w_mut {
515 for rm in &rs.member_seq {
520 let rm_optional = rm
521 .common
522 .member_flags
523 .has(crate::type_object::flags::StructMemberFlag::IS_OPTIONAL);
524 match ws
525 .member_seq
526 .iter()
527 .find(|wm| wm.common.member_id == rm.common.member_id)
528 {
529 Some(wm) => {
530 if !cfg.ignore_member_names && wm.detail != rm.detail {
531 return Assignable::No(
532 "mutable: member name-hash differs despite id match",
533 );
534 }
535 match is_assignable(
536 &wm.common.member_type_id,
537 &rm.common.member_type_id,
538 registry,
539 cfg,
540 ) {
541 Assignable::Yes => {}
542 e => return e,
543 }
544 }
545 None if rm_optional => {}
546 None => return Assignable::No("mutable: reader member missing in writer"),
547 }
548 }
549 Assignable::Yes
550 } else {
551 if ws.member_seq.len() < rs.member_seq.len() {
554 return Assignable::No("appendable: writer has fewer members than reader");
555 }
556 for (wm, rm) in ws.member_seq.iter().zip(rs.member_seq.iter()) {
557 if !cfg.ignore_member_names && wm.detail != rm.detail {
558 return Assignable::No("appendable: member name-hash differs");
559 }
560 match is_assignable(
561 &wm.common.member_type_id,
562 &rm.common.member_type_id,
563 registry,
564 cfg,
565 ) {
566 Assignable::Yes => {}
567 e => return e,
568 }
569 }
570 Assignable::Yes
571 }
572 }
573 (MinimalTypeObject::Enumerated(we), MinimalTypeObject::Enumerated(re)) => {
574 if we.header.common.bit_bound != re.header.common.bit_bound {
579 return Assignable::No("enum bit_bound mismatch");
580 }
581 let ignore_names = cfg.ignore_literal_names
584 || we
585 .enum_flags
586 .has(crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES)
587 || re
588 .enum_flags
589 .has(crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES);
590 for wl in &we.literal_seq {
591 let found = re.literal_seq.iter().any(|rl| {
592 rl.common.value == wl.common.value && (ignore_names || rl.detail == wl.detail)
593 });
594 if !found {
595 return Assignable::No("enum writer literal unknown in reader");
596 }
597 }
598 Assignable::Yes
599 }
600 _ => Assignable::No("type kinds do not match"),
601 }
602}
603
604#[cfg(test)]
605#[allow(clippy::unwrap_used)]
606mod tests {
607 use super::*;
608 use crate::builder::{Extensibility, TypeObjectBuilder};
609 use crate::hash::compute_minimal_hash;
610 use crate::type_object::TypeObject;
611
612 #[test]
613 fn primitive_same_kind_is_assignable() {
614 let reg = TypeRegistry::new();
615 let a = is_assignable(
616 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
617 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
618 ®,
619 &AssignabilityConfig::default(),
620 );
621 assert!(a.is_yes());
622 }
623
624 #[test]
625 fn primitive_different_kind_is_not_assignable_by_default() {
626 let reg = TypeRegistry::new();
627 let a = is_assignable(
628 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
629 &TypeIdentifier::Primitive(PrimitiveKind::Int64),
630 ®,
631 &AssignabilityConfig::default(),
632 );
633 assert!(!a.is_yes());
634 }
635
636 #[test]
637 fn primitive_widening_with_coercion_is_assignable() {
638 let reg = TypeRegistry::new();
639 let cfg = AssignabilityConfig {
640 allow_type_coercion: true,
641 ..Default::default()
642 };
643 assert!(
644 is_assignable(
645 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
646 &TypeIdentifier::Primitive(PrimitiveKind::Int64),
647 ®,
648 &cfg,
649 )
650 .is_yes()
651 );
652 assert!(
654 !is_assignable(
655 &TypeIdentifier::Primitive(PrimitiveKind::Int64),
656 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
657 ®,
658 &cfg,
659 )
660 .is_yes()
661 );
662 }
663
664 #[test]
665 fn appendable_struct_with_extra_writer_field_is_assignable() {
666 let mut reg = TypeRegistry::new();
667 let writer = MinimalTypeObject::Struct(
668 TypeObjectBuilder::struct_type("::X")
669 .extensibility(Extensibility::Appendable)
670 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
671 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
672 .build_minimal(),
673 );
674 let reader = MinimalTypeObject::Struct(
675 TypeObjectBuilder::struct_type("::X")
676 .extensibility(Extensibility::Appendable)
677 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
678 .build_minimal(),
679 );
680 let wh = compute_minimal_hash(&writer).unwrap();
681 let rh = compute_minimal_hash(&reader).unwrap();
682 reg.insert_minimal(wh, writer.clone());
683 reg.insert_minimal(rh, reader);
684
685 assert!(
686 is_assignable(
687 &TypeIdentifier::EquivalenceHashMinimal(wh),
688 &TypeIdentifier::EquivalenceHashMinimal(rh),
689 ®,
690 &AssignabilityConfig::default(),
691 )
692 .is_yes()
693 );
694 }
695
696 #[test]
697 fn final_struct_with_extra_writer_field_is_not_assignable() {
698 let mut reg = TypeRegistry::new();
699 let writer = MinimalTypeObject::Struct(
700 TypeObjectBuilder::struct_type("::X")
701 .extensibility(Extensibility::Final)
702 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
703 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
704 .build_minimal(),
705 );
706 let reader = MinimalTypeObject::Struct(
707 TypeObjectBuilder::struct_type("::X")
708 .extensibility(Extensibility::Final)
709 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
710 .build_minimal(),
711 );
712 let wh = compute_minimal_hash(&writer).unwrap();
713 let rh = compute_minimal_hash(&reader).unwrap();
714 reg.insert_minimal(wh, writer);
715 reg.insert_minimal(rh, reader);
716
717 assert!(
718 !is_assignable(
719 &TypeIdentifier::EquivalenceHashMinimal(wh),
720 &TypeIdentifier::EquivalenceHashMinimal(rh),
721 ®,
722 &AssignabilityConfig::default(),
723 )
724 .is_yes()
725 );
726 }
727
728 #[test]
729 fn mutable_struct_member_id_matching() {
730 let mut reg = TypeRegistry::new();
731 let writer = MinimalTypeObject::Struct(
734 TypeObjectBuilder::struct_type("::X")
735 .extensibility(Extensibility::Mutable)
736 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
737 m.id(2)
738 })
739 .member("c", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
740 m.id(3)
741 })
742 .build_minimal(),
743 );
744 let reader = MinimalTypeObject::Struct(
745 TypeObjectBuilder::struct_type("::X")
746 .extensibility(Extensibility::Mutable)
747 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
748 m.id(1).optional()
749 })
750 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
751 m.id(2)
752 })
753 .build_minimal(),
754 );
755 let wh = compute_minimal_hash(&writer).unwrap();
756 let rh = compute_minimal_hash(&reader).unwrap();
757 reg.insert_minimal(wh, writer);
758 reg.insert_minimal(rh, reader);
759
760 assert!(
761 is_assignable(
762 &TypeIdentifier::EquivalenceHashMinimal(wh),
763 &TypeIdentifier::EquivalenceHashMinimal(rh),
764 ®,
765 &AssignabilityConfig::default(),
766 )
767 .is_yes()
768 );
769 }
770
771 #[test]
772 fn extensibility_mismatch_fails() {
773 let mut reg = TypeRegistry::new();
774 let writer = MinimalTypeObject::Struct(
775 TypeObjectBuilder::struct_type("::X")
776 .extensibility(Extensibility::Final)
777 .build_minimal(),
778 );
779 let reader = MinimalTypeObject::Struct(
780 TypeObjectBuilder::struct_type("::X")
781 .extensibility(Extensibility::Mutable)
782 .build_minimal(),
783 );
784 let wh = compute_minimal_hash(&writer).unwrap();
785 let rh = compute_minimal_hash(&reader).unwrap();
786 reg.insert_minimal(wh, writer);
787 reg.insert_minimal(rh, reader);
788
789 let a = is_assignable(
790 &TypeIdentifier::EquivalenceHashMinimal(wh),
791 &TypeIdentifier::EquivalenceHashMinimal(rh),
792 ®,
793 &AssignabilityConfig::default(),
794 );
795 assert!(!a.is_yes());
796 }
797
798 #[test]
799 fn string_small_and_large_interchangeable() {
800 let reg = TypeRegistry::new();
801 assert!(
802 is_assignable(
803 &TypeIdentifier::String8Small { bound: 64 },
804 &TypeIdentifier::String8Large { bound: 100_000 },
805 ®,
806 &AssignabilityConfig::default(),
807 )
808 .is_yes()
809 );
810 }
811
812 #[allow(dead_code)]
814 fn _unused() -> TypeObject {
815 TypeObject::Minimal(MinimalTypeObject::Struct(
816 TypeObjectBuilder::struct_type("::dummy").build_minimal(),
817 ))
818 }
819
820 use crate::type_identifier::PlainCollectionHeader;
823 use alloc::boxed::Box;
824
825 fn reg() -> TypeRegistry {
826 TypeRegistry::new()
827 }
828
829 #[test]
830 fn string8_vs_string16_not_assignable() {
831 let a = is_assignable(
832 &TypeIdentifier::String8Small { bound: 16 },
833 &TypeIdentifier::String16Small { bound: 16 },
834 ®(),
835 &AssignabilityConfig::default(),
836 );
837 assert!(!a.is_yes());
838 assert!(matches!(a, Assignable::No(msg) if msg.contains("kinds")));
839 }
840
841 #[test]
842 fn string16_small_and_large_interchangeable() {
843 let a = is_assignable(
844 &TypeIdentifier::String16Small { bound: 32 },
845 &TypeIdentifier::String16Large { bound: 10_000 },
846 ®(),
847 &AssignabilityConfig::default(),
848 );
849 assert!(a.is_yes());
850 }
851
852 #[test]
853 fn identical_type_identifiers_short_circuit_yes() {
854 let ti = TypeIdentifier::Primitive(PrimitiveKind::UInt32);
856 let a = is_assignable(&ti, &ti, ®(), &AssignabilityConfig::default());
857 assert!(a.is_yes());
858 }
859
860 #[test]
861 fn sequence_writer_bound_exceeds_reader_bound_is_no() {
862 let w = TypeIdentifier::PlainSequenceSmall {
863 header: PlainCollectionHeader::default(),
864 bound: 20,
865 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
866 };
867 let r = TypeIdentifier::PlainSequenceSmall {
868 header: PlainCollectionHeader::default(),
869 bound: 10,
870 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
871 };
872 let cfg = AssignabilityConfig {
874 ignore_sequence_bounds: false,
875 ..Default::default()
876 };
877 let a = is_assignable(&w, &r, ®(), &cfg);
878 assert!(!a.is_yes());
879 assert!(matches!(a, Assignable::No(msg) if msg.contains("bound")));
880 }
881
882 #[test]
885 fn sequence_bounds_ignored_when_policy_allows() {
886 let w = TypeIdentifier::PlainSequenceSmall {
887 header: PlainCollectionHeader::default(),
888 bound: 20,
889 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
890 };
891 let r = TypeIdentifier::PlainSequenceSmall {
892 header: PlainCollectionHeader::default(),
893 bound: 10,
894 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
895 };
896 assert!(is_assignable(&w, &r, ®(), &AssignabilityConfig::default()).is_yes());
897 }
898
899 #[test]
900 fn sequence_reader_unbounded_accepts_any_writer_bound() {
901 let w = TypeIdentifier::PlainSequenceSmall {
903 header: PlainCollectionHeader::default(),
904 bound: 200,
905 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int16)),
906 };
907 let r = TypeIdentifier::PlainSequenceSmall {
908 header: PlainCollectionHeader::default(),
909 bound: 0,
910 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int16)),
911 };
912 assert!(is_assignable(&w, &r, ®(), &AssignabilityConfig::default()).is_yes());
913 }
914
915 #[test]
916 fn sequence_elements_must_be_assignable() {
917 let w = TypeIdentifier::PlainSequenceSmall {
919 header: PlainCollectionHeader::default(),
920 bound: 5,
921 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
922 };
923 let r = TypeIdentifier::PlainSequenceSmall {
924 header: PlainCollectionHeader::default(),
925 bound: 5,
926 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Float64)),
927 };
928 assert!(!is_assignable(&w, &r, ®(), &AssignabilityConfig::default()).is_yes());
929 }
930
931 #[test]
932 fn nested_sequence_of_sequence_assignable_when_elements_match() {
933 let inner = TypeIdentifier::PlainSequenceSmall {
934 header: PlainCollectionHeader::default(),
935 bound: 5,
936 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
937 };
938 let outer = TypeIdentifier::PlainSequenceSmall {
939 header: PlainCollectionHeader::default(),
940 bound: 3,
941 element: Box::new(inner.clone()),
942 };
943 let inner_wider = TypeIdentifier::PlainSequenceSmall {
945 header: PlainCollectionHeader::default(),
946 bound: 10,
947 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
948 };
949 let outer2 = TypeIdentifier::PlainSequenceSmall {
950 header: PlainCollectionHeader::default(),
951 bound: 3,
952 element: Box::new(inner_wider),
953 };
954 assert!(is_assignable(&outer, &outer2, ®(), &AssignabilityConfig::default()).is_yes());
955 }
956
957 #[test]
958 fn plain_array_identical_assigns_yes() {
959 let a = is_assignable(
960 &TypeIdentifier::PlainArraySmall {
961 header: PlainCollectionHeader::default(),
962 array_bounds: alloc::vec![3, 4],
963 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
964 },
965 &TypeIdentifier::PlainArraySmall {
966 header: PlainCollectionHeader::default(),
967 array_bounds: alloc::vec![3, 4],
968 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
969 },
970 ®(),
971 &AssignabilityConfig::default(),
972 );
973 assert!(a.is_yes());
974 }
975
976 #[test]
977 fn plain_array_diff_bounds_is_no() {
978 let b = is_assignable(
979 &TypeIdentifier::PlainArraySmall {
980 header: PlainCollectionHeader::default(),
981 array_bounds: alloc::vec![3, 4],
982 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
983 },
984 &TypeIdentifier::PlainArraySmall {
985 header: PlainCollectionHeader::default(),
986 array_bounds: alloc::vec![3, 5],
987 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
988 },
989 ®(),
990 &AssignabilityConfig::default(),
991 );
992 assert!(!b.is_yes());
993 assert!(matches!(b, Assignable::No(msg) if msg.contains("array bounds")));
994 }
995
996 #[test]
997 fn equivalence_hash_identical_short_circuits_to_yes() {
998 let mut reg = reg();
1003 let to = MinimalTypeObject::Struct(
1004 TypeObjectBuilder::struct_type("::T")
1005 .extensibility(Extensibility::Appendable)
1006 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1007 .build_minimal(),
1008 );
1009 let h = compute_minimal_hash(&to).unwrap();
1010 reg.insert_minimal(h, to);
1011 let a = is_assignable(
1012 &TypeIdentifier::EquivalenceHashMinimal(h),
1013 &TypeIdentifier::EquivalenceHashMinimal(h),
1014 ®,
1015 &AssignabilityConfig::default(),
1016 );
1017 assert!(a.is_yes());
1018 }
1019
1020 #[test]
1021 fn equivalence_hash_unresolved_writer_is_no() {
1022 let reg = reg();
1025 let wh = crate::type_identifier::EquivalenceHash([0x01; 14]);
1026 let rh = crate::type_identifier::EquivalenceHash([0x02; 14]);
1027 let a = is_assignable(
1028 &TypeIdentifier::EquivalenceHashMinimal(wh),
1029 &TypeIdentifier::EquivalenceHashMinimal(rh),
1030 ®,
1031 &AssignabilityConfig::default(),
1032 );
1033 assert!(!a.is_yes());
1034 assert!(matches!(a, Assignable::No(msg) if msg.contains("alias resolution")));
1035 }
1036
1037 #[test]
1038 fn equivalence_hash_unresolved_reader_is_no() {
1039 let mut reg = reg();
1041 let to = MinimalTypeObject::Struct(
1042 TypeObjectBuilder::struct_type("::T")
1043 .extensibility(Extensibility::Appendable)
1044 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1045 .build_minimal(),
1046 );
1047 let wh = compute_minimal_hash(&to).unwrap();
1048 reg.insert_minimal(wh, to);
1049 let rh = crate::type_identifier::EquivalenceHash([0x02; 14]);
1050 let a = is_assignable(
1051 &TypeIdentifier::EquivalenceHashMinimal(wh),
1052 &TypeIdentifier::EquivalenceHashMinimal(rh),
1053 ®,
1054 &AssignabilityConfig::default(),
1055 );
1056 assert!(!a.is_yes());
1057 assert!(matches!(a, Assignable::No(msg) if msg.contains("reader")));
1058 }
1059
1060 #[test]
1061 fn mixed_kinds_report_kinds_do_not_match() {
1062 let a = is_assignable(
1064 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
1065 &TypeIdentifier::String8Small { bound: 10 },
1066 ®(),
1067 &AssignabilityConfig::default(),
1068 );
1069 assert!(!a.is_yes());
1070 }
1071
1072 #[test]
1073 fn enum_mismatch_writer_literal_unknown_in_reader_is_no() {
1074 let mut reg = reg();
1075 let w = MinimalTypeObject::Enumerated(
1076 TypeObjectBuilder::enum_type("::E")
1077 .bit_bound(32)
1078 .literal("A", 1)
1079 .literal("B", 2)
1080 .build_minimal(),
1081 );
1082 let r = MinimalTypeObject::Enumerated(
1083 TypeObjectBuilder::enum_type("::E")
1084 .bit_bound(32)
1085 .literal("A", 1)
1086 .build_minimal(),
1087 );
1088 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1089 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1090 reg.insert_minimal(wh, w);
1091 reg.insert_minimal(rh, r);
1092
1093 let a = is_assignable(
1094 &TypeIdentifier::EquivalenceHashMinimal(wh),
1095 &TypeIdentifier::EquivalenceHashMinimal(rh),
1096 ®,
1097 &AssignabilityConfig::default(),
1098 );
1099 assert!(!a.is_yes());
1100 }
1101
1102 #[test]
1103 fn enum_bit_bound_mismatch_is_no() {
1104 let mut reg = reg();
1105 let w = MinimalTypeObject::Enumerated(
1106 TypeObjectBuilder::enum_type("::E")
1107 .bit_bound(32)
1108 .literal("A", 1)
1109 .build_minimal(),
1110 );
1111 let r = MinimalTypeObject::Enumerated(
1112 TypeObjectBuilder::enum_type("::E")
1113 .bit_bound(16)
1114 .literal("A", 1)
1115 .build_minimal(),
1116 );
1117 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1118 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1119 reg.insert_minimal(wh, w);
1120 reg.insert_minimal(rh, r);
1121
1122 let a = is_assignable(
1123 &TypeIdentifier::EquivalenceHashMinimal(wh),
1124 &TypeIdentifier::EquivalenceHashMinimal(rh),
1125 ®,
1126 &AssignabilityConfig::default(),
1127 );
1128 assert!(!a.is_yes());
1129 }
1130
1131 #[test]
1132 fn enum_identical_labels_is_yes() {
1133 let mut reg = reg();
1134 let w = MinimalTypeObject::Enumerated(
1135 TypeObjectBuilder::enum_type("::E")
1136 .bit_bound(32)
1137 .literal("A", 1)
1138 .literal("B", 2)
1139 .build_minimal(),
1140 );
1141 let r = MinimalTypeObject::Enumerated(
1142 TypeObjectBuilder::enum_type("::E")
1143 .bit_bound(32)
1144 .literal("A", 1)
1145 .literal("B", 2)
1146 .literal("C", 3) .build_minimal(),
1148 );
1149 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1150 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1151 reg.insert_minimal(wh, w);
1152 reg.insert_minimal(rh, r);
1153
1154 let a = is_assignable(
1155 &TypeIdentifier::EquivalenceHashMinimal(wh),
1156 &TypeIdentifier::EquivalenceHashMinimal(rh),
1157 ®,
1158 &AssignabilityConfig::default(),
1159 );
1160 assert!(a.is_yes());
1161 }
1162
1163 #[test]
1166 fn flatten_inheritance_no_base_returns_struct_unchanged() {
1167 let s = TypeObjectBuilder::struct_type("::S")
1168 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1169 .build_minimal();
1170 let reg = reg();
1171 let flat = flatten_inheritance(&s, ®, 8).unwrap();
1172 assert_eq!(flat.member_seq.len(), 1);
1173 }
1174
1175 #[test]
1176 fn flatten_inheritance_two_levels_concatenates_base_first() {
1177 let mut reg = reg();
1179 let root = TypeObjectBuilder::struct_type("::Root")
1180 .member("r", TypeIdentifier::Primitive(PrimitiveKind::Int8), |m| {
1181 m.id(101)
1182 })
1183 .build_minimal();
1184 let root_h = compute_minimal_hash(&MinimalTypeObject::Struct(root.clone())).unwrap();
1185 reg.insert_minimal(root_h, MinimalTypeObject::Struct(root));
1186
1187 let mid = TypeObjectBuilder::struct_type("::Mid")
1188 .base(TypeIdentifier::EquivalenceHashMinimal(root_h))
1189 .member("m", TypeIdentifier::Primitive(PrimitiveKind::Int16), |m| {
1190 m.id(202)
1191 })
1192 .build_minimal();
1193 let mid_h = compute_minimal_hash(&MinimalTypeObject::Struct(mid.clone())).unwrap();
1194 reg.insert_minimal(mid_h, MinimalTypeObject::Struct(mid));
1195
1196 let derived = TypeObjectBuilder::struct_type("::Derived")
1197 .base(TypeIdentifier::EquivalenceHashMinimal(mid_h))
1198 .member("d", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1199 m.id(303)
1200 })
1201 .build_minimal();
1202
1203 let flat = flatten_inheritance(&derived, ®, 8).unwrap();
1204 assert_eq!(flat.header.base_type, TypeIdentifier::None);
1206 assert_eq!(flat.member_seq.len(), 3);
1207 let first_id = flat.member_seq[0].common.member_id;
1209 let last_id = flat.member_seq[2].common.member_id;
1210 assert_ne!(first_id, last_id);
1211 }
1212
1213 #[test]
1214 fn inheritance_conflict_same_id() {
1215 let mut reg = reg();
1216 let base = TypeObjectBuilder::struct_type("::B")
1217 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1218 m.id(7)
1219 })
1220 .build_minimal();
1221 let bh = compute_minimal_hash(&MinimalTypeObject::Struct(base.clone())).unwrap();
1222 reg.insert_minimal(bh, MinimalTypeObject::Struct(base));
1223
1224 let derived = TypeObjectBuilder::struct_type("::D")
1225 .base(TypeIdentifier::EquivalenceHashMinimal(bh))
1226 .member("c", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1227 m.id(7) })
1229 .build_minimal();
1230 let err = flatten_inheritance(&derived, ®, 8).unwrap_err();
1231 assert!(matches!(
1232 err,
1233 InheritanceError::InheritanceConflict { reason, .. }
1234 if reason.contains("member_id")
1235 ));
1236 }
1237
1238 #[test]
1239 fn inheritance_conflict_same_name() {
1240 let mut reg = reg();
1241 let base = TypeObjectBuilder::struct_type("::B")
1242 .member(
1243 "dup",
1244 TypeIdentifier::Primitive(PrimitiveKind::Int32),
1245 |m| m.id(1),
1246 )
1247 .build_minimal();
1248 let bh = compute_minimal_hash(&MinimalTypeObject::Struct(base.clone())).unwrap();
1249 reg.insert_minimal(bh, MinimalTypeObject::Struct(base));
1250
1251 let derived = TypeObjectBuilder::struct_type("::D")
1252 .base(TypeIdentifier::EquivalenceHashMinimal(bh))
1253 .member(
1254 "dup",
1255 TypeIdentifier::Primitive(PrimitiveKind::Int32),
1256 |m| {
1257 m.id(2) },
1259 )
1260 .build_minimal();
1261 let err = flatten_inheritance(&derived, ®, 8).unwrap_err();
1262 assert!(matches!(
1263 err,
1264 InheritanceError::InheritanceConflict { reason, .. }
1265 if reason.contains("name_hash")
1266 ));
1267 }
1268
1269 #[test]
1270 fn flat_type_construction_two_levels() {
1271 let mut reg = reg();
1275 let base = TypeObjectBuilder::struct_type("::Base")
1276 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1277 m.id(1)
1278 })
1279 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1280 m.id(2)
1281 })
1282 .build_minimal();
1283 let bh = compute_minimal_hash(&MinimalTypeObject::Struct(base.clone())).unwrap();
1284 reg.insert_minimal(bh, MinimalTypeObject::Struct(base));
1285
1286 let derived = TypeObjectBuilder::struct_type("::Derived")
1287 .base(TypeIdentifier::EquivalenceHashMinimal(bh))
1288 .member("c", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1289 m.id(3)
1290 })
1291 .build_minimal();
1292
1293 let flat = flatten_inheritance(&derived, ®, 8).unwrap();
1294 assert_eq!(flat.member_seq.len(), 3);
1295 assert_eq!(flat.member_seq[0].common.member_id, 1);
1296 assert_eq!(flat.member_seq[1].common.member_id, 2);
1297 assert_eq!(flat.member_seq[2].common.member_id, 3);
1298 }
1299
1300 #[test]
1301 fn two_level_inheritance_assignability_chain() {
1302 let mut reg = reg();
1305
1306 let w_root = TypeObjectBuilder::struct_type("::WRoot")
1308 .member("r", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1309 m.id(1)
1310 })
1311 .build_minimal();
1312 let wr_h = compute_minimal_hash(&MinimalTypeObject::Struct(w_root.clone())).unwrap();
1313 reg.insert_minimal(wr_h, MinimalTypeObject::Struct(w_root));
1314
1315 let w_mid = TypeObjectBuilder::struct_type("::WMid")
1316 .base(TypeIdentifier::EquivalenceHashMinimal(wr_h))
1317 .member("m", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1318 m.id(2)
1319 })
1320 .build_minimal();
1321
1322 let w_flat = flatten_inheritance(&w_mid, ®, 8).unwrap();
1323 assert_eq!(w_flat.member_seq.len(), 2);
1324
1325 let r_root = TypeObjectBuilder::struct_type("::RRoot")
1327 .member("r", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1328 m.id(1)
1329 })
1330 .build_minimal();
1331 let rr_h = compute_minimal_hash(&MinimalTypeObject::Struct(r_root.clone())).unwrap();
1332 reg.insert_minimal(rr_h, MinimalTypeObject::Struct(r_root));
1333
1334 let r_mid = TypeObjectBuilder::struct_type("::RMid")
1335 .base(TypeIdentifier::EquivalenceHashMinimal(rr_h))
1336 .member("m", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1337 m.id(2)
1338 })
1339 .build_minimal();
1340
1341 let r_flat = flatten_inheritance(&r_mid, ®, 8).unwrap();
1342 assert_eq!(r_flat.member_seq.len(), 2);
1343
1344 let w_to = MinimalTypeObject::Struct(w_flat.clone());
1346 let r_to = MinimalTypeObject::Struct(r_flat.clone());
1347 let wh = compute_minimal_hash(&w_to).unwrap();
1348 let rh = compute_minimal_hash(&r_to).unwrap();
1349 reg.insert_minimal(wh, w_to);
1350 reg.insert_minimal(rh, r_to);
1351 let a = is_assignable(
1352 &TypeIdentifier::EquivalenceHashMinimal(wh),
1353 &TypeIdentifier::EquivalenceHashMinimal(rh),
1354 ®,
1355 &AssignabilityConfig::default(),
1356 );
1357 assert!(a.is_yes(), "got {a:?}");
1358 }
1359
1360 #[test]
1361 fn enum_not_assignable_strict_default() {
1362 let mut reg = reg();
1366 let w = MinimalTypeObject::Enumerated(
1367 TypeObjectBuilder::enum_type("::E")
1368 .bit_bound(32)
1369 .literal("RED", 1)
1370 .build_minimal(),
1371 );
1372 let r = MinimalTypeObject::Enumerated(
1373 TypeObjectBuilder::enum_type("::E")
1374 .bit_bound(32)
1375 .literal("ROUGE", 1) .build_minimal(),
1377 );
1378 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1379 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1380 reg.insert_minimal(wh, w);
1381 reg.insert_minimal(rh, r);
1382
1383 let a = is_assignable(
1384 &TypeIdentifier::EquivalenceHashMinimal(wh),
1385 &TypeIdentifier::EquivalenceHashMinimal(rh),
1386 ®,
1387 &AssignabilityConfig::default(),
1388 );
1389 assert!(!a.is_yes());
1390 }
1391
1392 #[test]
1393 fn enum_assignable_with_ignore_literal_names() {
1394 let mut reg = reg();
1397 let w = MinimalTypeObject::Enumerated(
1398 TypeObjectBuilder::enum_type("::E")
1399 .bit_bound(32)
1400 .literal("RED", 1)
1401 .literal("GREEN", 2)
1402 .build_minimal(),
1403 );
1404 let r = MinimalTypeObject::Enumerated(
1405 TypeObjectBuilder::enum_type("::E")
1406 .bit_bound(32)
1407 .literal("ROUGE", 1)
1408 .literal("VERT", 2)
1409 .build_minimal(),
1410 );
1411 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1412 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1413 reg.insert_minimal(wh, w);
1414 reg.insert_minimal(rh, r);
1415
1416 let cfg = AssignabilityConfig {
1417 ignore_literal_names: true,
1418 ..AssignabilityConfig::default()
1419 };
1420 let a = is_assignable(
1421 &TypeIdentifier::EquivalenceHashMinimal(wh),
1422 &TypeIdentifier::EquivalenceHashMinimal(rh),
1423 ®,
1424 &cfg,
1425 );
1426 assert!(a.is_yes());
1427 }
1428
1429 #[test]
1430 fn enum_assignable_with_ignore_literal_names_via_writer_flag() {
1431 let mut reg = reg();
1434 let mut w_e = TypeObjectBuilder::enum_type("::E")
1435 .bit_bound(32)
1436 .literal("RED", 1)
1437 .build_minimal();
1438 w_e.enum_flags = crate::type_object::flags::EnumTypeFlag(
1439 crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES,
1440 );
1441 let w = MinimalTypeObject::Enumerated(w_e);
1442 let r = MinimalTypeObject::Enumerated(
1443 TypeObjectBuilder::enum_type("::E")
1444 .bit_bound(32)
1445 .literal("ROUGE", 1)
1446 .build_minimal(),
1447 );
1448 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1449 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1450 reg.insert_minimal(wh, w);
1451 reg.insert_minimal(rh, r);
1452
1453 let a = is_assignable(
1454 &TypeIdentifier::EquivalenceHashMinimal(wh),
1455 &TypeIdentifier::EquivalenceHashMinimal(rh),
1456 ®,
1457 &AssignabilityConfig::default(),
1458 );
1459 assert!(a.is_yes());
1460 }
1461
1462 #[test]
1463 fn enum_assignable_with_ignore_literal_names_via_reader_flag() {
1464 let mut reg = reg();
1465 let w = MinimalTypeObject::Enumerated(
1466 TypeObjectBuilder::enum_type("::E")
1467 .bit_bound(32)
1468 .literal("RED", 1)
1469 .build_minimal(),
1470 );
1471 let mut r_e = TypeObjectBuilder::enum_type("::E")
1472 .bit_bound(32)
1473 .literal("ROUGE", 1)
1474 .build_minimal();
1475 r_e.enum_flags = crate::type_object::flags::EnumTypeFlag(
1476 crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES,
1477 );
1478 let r = MinimalTypeObject::Enumerated(r_e);
1479 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1480 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1481 reg.insert_minimal(wh, w);
1482 reg.insert_minimal(rh, r);
1483
1484 let a = is_assignable(
1485 &TypeIdentifier::EquivalenceHashMinimal(wh),
1486 &TypeIdentifier::EquivalenceHashMinimal(rh),
1487 ®,
1488 &AssignabilityConfig::default(),
1489 );
1490 assert!(a.is_yes());
1491 }
1492
1493 #[test]
1494 fn struct_vs_enum_type_object_kinds_dont_match() {
1495 let mut reg = reg();
1496 let w = MinimalTypeObject::Struct(
1497 TypeObjectBuilder::struct_type("::X")
1498 .extensibility(Extensibility::Appendable)
1499 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1500 .build_minimal(),
1501 );
1502 let r = MinimalTypeObject::Enumerated(
1503 TypeObjectBuilder::enum_type("::X")
1504 .bit_bound(32)
1505 .literal("A", 1)
1506 .build_minimal(),
1507 );
1508 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1509 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1510 reg.insert_minimal(wh, w);
1511 reg.insert_minimal(rh, r);
1512
1513 let a = is_assignable(
1514 &TypeIdentifier::EquivalenceHashMinimal(wh),
1515 &TypeIdentifier::EquivalenceHashMinimal(rh),
1516 ®,
1517 &AssignabilityConfig::default(),
1518 );
1519 assert!(!a.is_yes());
1520 }
1521
1522 #[test]
1523 fn appendable_struct_writer_smaller_than_reader_is_no() {
1524 let mut reg = reg();
1525 let writer = MinimalTypeObject::Struct(
1526 TypeObjectBuilder::struct_type("::X")
1527 .extensibility(Extensibility::Appendable)
1528 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1529 .build_minimal(),
1530 );
1531 let reader = MinimalTypeObject::Struct(
1532 TypeObjectBuilder::struct_type("::X")
1533 .extensibility(Extensibility::Appendable)
1534 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1535 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1536 .build_minimal(),
1537 );
1538 let wh = crate::hash::compute_minimal_hash(&writer).unwrap();
1539 let rh = crate::hash::compute_minimal_hash(&reader).unwrap();
1540 reg.insert_minimal(wh, writer);
1541 reg.insert_minimal(rh, reader);
1542
1543 assert!(
1544 !is_assignable(
1545 &TypeIdentifier::EquivalenceHashMinimal(wh),
1546 &TypeIdentifier::EquivalenceHashMinimal(rh),
1547 ®,
1548 &AssignabilityConfig::default(),
1549 )
1550 .is_yes()
1551 );
1552 }
1553
1554 #[test]
1555 fn mutable_reader_member_missing_in_writer_non_optional_is_no() {
1556 let mut reg = reg();
1557 let writer = MinimalTypeObject::Struct(
1558 TypeObjectBuilder::struct_type("::X")
1559 .extensibility(Extensibility::Mutable)
1560 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1561 m.id(2)
1562 })
1563 .build_minimal(),
1564 );
1565 let reader = MinimalTypeObject::Struct(
1566 TypeObjectBuilder::struct_type("::X")
1567 .extensibility(Extensibility::Mutable)
1568 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1569 m.id(1) })
1571 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1572 m.id(2)
1573 })
1574 .build_minimal(),
1575 );
1576 let wh = crate::hash::compute_minimal_hash(&writer).unwrap();
1577 let rh = crate::hash::compute_minimal_hash(&reader).unwrap();
1578 reg.insert_minimal(wh, writer);
1579 reg.insert_minimal(rh, reader);
1580
1581 assert!(
1582 !is_assignable(
1583 &TypeIdentifier::EquivalenceHashMinimal(wh),
1584 &TypeIdentifier::EquivalenceHashMinimal(rh),
1585 ®,
1586 &AssignabilityConfig::default(),
1587 )
1588 .is_yes()
1589 );
1590 }
1591
1592 #[test]
1593 fn final_struct_member_count_mismatch_is_no() {
1594 let mut reg = reg();
1595 let writer = MinimalTypeObject::Struct(
1596 TypeObjectBuilder::struct_type("::X")
1597 .extensibility(Extensibility::Final)
1598 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1599 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1600 .build_minimal(),
1601 );
1602 let reader = MinimalTypeObject::Struct(
1603 TypeObjectBuilder::struct_type("::X")
1604 .extensibility(Extensibility::Final)
1605 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1606 .build_minimal(),
1607 );
1608 let wh = crate::hash::compute_minimal_hash(&writer).unwrap();
1609 let rh = crate::hash::compute_minimal_hash(&reader).unwrap();
1610 reg.insert_minimal(wh, writer);
1611 reg.insert_minimal(rh, reader);
1612
1613 assert!(
1614 !is_assignable(
1615 &TypeIdentifier::EquivalenceHashMinimal(wh),
1616 &TypeIdentifier::EquivalenceHashMinimal(rh),
1617 ®,
1618 &AssignabilityConfig::default(),
1619 )
1620 .is_yes()
1621 );
1622 }
1623
1624 #[test]
1625 fn primitive_widening_int16_to_int64_is_assignable_with_coercion() {
1626 let cfg = AssignabilityConfig {
1627 allow_type_coercion: true,
1628 ..Default::default()
1629 };
1630 assert!(primitive_compatible(PrimitiveKind::Int16, PrimitiveKind::Int64, &cfg).is_yes());
1631 assert!(primitive_compatible(PrimitiveKind::Byte, PrimitiveKind::Int32, &cfg).is_yes());
1632 assert!(
1633 primitive_compatible(PrimitiveKind::Float32, PrimitiveKind::Float64, &cfg).is_yes()
1634 );
1635 }
1636
1637 #[test]
1638 fn primitive_unwidening_is_rejected_even_with_coercion() {
1639 let cfg = AssignabilityConfig {
1640 allow_type_coercion: true,
1641 ..Default::default()
1642 };
1643 assert!(!primitive_compatible(PrimitiveKind::Float64, PrimitiveKind::Int32, &cfg).is_yes());
1644 }
1645
1646 #[test]
1647 fn assignable_is_yes_matches_expectation() {
1648 assert!(Assignable::Yes.is_yes());
1649 assert!(!Assignable::No("reason").is_yes());
1650 }
1651
1652 #[test]
1659 fn equal_complete_hash_is_assignable_with_empty_registry() {
1660 let reg = TypeRegistry::new();
1661 let h = crate::type_identifier::EquivalenceHash([0xCE; 14]);
1662 let ti = TypeIdentifier::EquivalenceHashComplete(h);
1663 assert!(
1664 is_assignable(&ti, &ti, ®, &AssignabilityConfig::default()).is_yes(),
1665 "identical complete TypeIdentifiers must be assignable even with an empty registry"
1666 );
1667 }
1668
1669 #[test]
1670 fn equal_minimal_hash_is_assignable_with_empty_registry() {
1671 let reg = TypeRegistry::new();
1672 let h = crate::type_identifier::EquivalenceHash([0x4D; 14]);
1673 let ti = TypeIdentifier::EquivalenceHashMinimal(h);
1674 assert!(is_assignable(&ti, &ti, ®, &AssignabilityConfig::default()).is_yes());
1675 }
1676
1677 #[test]
1678 fn differing_complete_hashes_unknown_objects_are_not_assignable() {
1679 let reg = TypeRegistry::new();
1680 let wh = crate::type_identifier::EquivalenceHash([0x01; 14]);
1681 let rh = crate::type_identifier::EquivalenceHash([0x02; 14]);
1682 assert!(
1683 !is_assignable(
1684 &TypeIdentifier::EquivalenceHashComplete(wh),
1685 &TypeIdentifier::EquivalenceHashComplete(rh),
1686 ®,
1687 &AssignabilityConfig::default(),
1688 )
1689 .is_yes()
1690 );
1691 }
1692}