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 let Ok(w) = resolve_alias_chain(w, registry, cfg.max_depth) else {
206 return Assignable::No("writer alias resolution failed");
207 };
208 let Ok(r) = resolve_alias_chain(r, registry, cfg.max_depth) else {
209 return Assignable::No("reader alias resolution failed");
210 };
211
212 check_direct(&w, &r, registry, cfg)
213}
214
215fn check_direct(
221 w: &TypeIdentifier,
222 r: &TypeIdentifier,
223 registry: &TypeRegistry,
224 cfg: &AssignabilityConfig,
225) -> Assignable {
226 if w == r {
228 return Assignable::Yes;
229 }
230
231 match (w, r) {
232 (TypeIdentifier::Primitive(wp), TypeIdentifier::Primitive(rp)) => {
234 primitive_compatible(*wp, *rp, cfg)
235 }
236 (
239 TypeIdentifier::String8Small { .. } | TypeIdentifier::String8Large { .. },
240 TypeIdentifier::String8Small { .. } | TypeIdentifier::String8Large { .. },
241 ) => {
242 let (wb, rb) = (string_bound_u32_s8(w), string_bound_u32_s8(r));
243 if !cfg.ignore_string_bounds && rb != 0 && wb > rb {
244 Assignable::No("writer string8 bound exceeds reader bound")
245 } else {
246 Assignable::Yes
247 }
248 }
249 (
250 TypeIdentifier::String16Small { .. } | TypeIdentifier::String16Large { .. },
251 TypeIdentifier::String16Small { .. } | TypeIdentifier::String16Large { .. },
252 ) => {
253 let (wb, rb) = (string_bound_u32_s16(w), string_bound_u32_s16(r));
254 if !cfg.ignore_string_bounds && rb != 0 && wb > rb {
255 Assignable::No("writer string16 bound exceeds reader bound")
256 } else {
257 Assignable::Yes
258 }
259 }
260
261 (
265 TypeIdentifier::PlainSequenceSmall { .. } | TypeIdentifier::PlainSequenceLarge { .. },
266 TypeIdentifier::PlainSequenceSmall { .. } | TypeIdentifier::PlainSequenceLarge { .. },
267 ) => {
268 let (we, wb) = sequence_parts(w);
269 let (re, rb) = sequence_parts(r);
270 if !cfg.ignore_sequence_bounds && rb != 0 && wb > rb {
271 return Assignable::No("writer sequence bound exceeds reader bound");
272 }
273 is_assignable(we, re, registry, cfg)
274 }
275
276 (
281 TypeIdentifier::PlainArraySmall { .. } | TypeIdentifier::PlainArrayLarge { .. },
282 TypeIdentifier::PlainArraySmall { .. } | TypeIdentifier::PlainArrayLarge { .. },
283 ) => {
284 let (we, wb) = array_parts(w);
285 let (re, rb) = array_parts(r);
286 if wb != rb {
287 return Assignable::No("array bounds differ");
288 }
289 is_assignable(we, re, registry, cfg)
290 }
291
292 (
294 TypeIdentifier::PlainMapSmall { .. } | TypeIdentifier::PlainMapLarge { .. },
295 TypeIdentifier::PlainMapSmall { .. } | TypeIdentifier::PlainMapLarge { .. },
296 ) => {
297 let (we, wk, wb) = map_parts(w);
298 let (re, rk, rb) = map_parts(r);
299 if !cfg.ignore_sequence_bounds && rb != 0 && wb > rb {
300 return Assignable::No("writer map bound exceeds reader bound");
301 }
302 match is_assignable(wk, rk, registry, cfg) {
303 Assignable::Yes => is_assignable(we, re, registry, cfg),
304 e => e,
305 }
306 }
307
308 (
310 TypeIdentifier::EquivalenceHashMinimal(wh),
311 TypeIdentifier::EquivalenceHashMinimal(rh),
312 ) => {
313 if wh == rh {
314 return Assignable::Yes;
315 }
316 match (registry.get_minimal(wh), registry.get_minimal(rh)) {
318 (Some(wobj), Some(robj)) => check_minimal_types(wobj, robj, registry, cfg),
319 _ => Assignable::No("unknown type objects for hash comparison"),
320 }
321 }
322
323 _ => Assignable::No("kinds do not match"),
324 }
325}
326
327fn sequence_parts(ti: &TypeIdentifier) -> (&TypeIdentifier, u32) {
328 match ti {
329 TypeIdentifier::PlainSequenceSmall { element, bound, .. } => (element, u32::from(*bound)),
330 TypeIdentifier::PlainSequenceLarge { element, bound, .. } => (element, *bound),
331 _ => (ti, 0),
332 }
333}
334
335fn array_parts(ti: &TypeIdentifier) -> (&TypeIdentifier, alloc::vec::Vec<u32>) {
336 match ti {
337 TypeIdentifier::PlainArraySmall {
338 element,
339 array_bounds,
340 ..
341 } => (
342 element,
343 array_bounds.iter().map(|b| u32::from(*b)).collect(),
344 ),
345 TypeIdentifier::PlainArrayLarge {
346 element,
347 array_bounds,
348 ..
349 } => (element, array_bounds.clone()),
350 _ => (ti, alloc::vec::Vec::new()),
351 }
352}
353
354fn map_parts(ti: &TypeIdentifier) -> (&TypeIdentifier, &TypeIdentifier, u32) {
355 match ti {
356 TypeIdentifier::PlainMapSmall {
357 element,
358 key,
359 bound,
360 ..
361 } => (element, key, u32::from(*bound)),
362 TypeIdentifier::PlainMapLarge {
363 element,
364 key,
365 bound,
366 ..
367 } => (element, key, *bound),
368 _ => (ti, ti, 0),
369 }
370}
371
372fn string_bound_u32_s8(ti: &TypeIdentifier) -> u32 {
373 match ti {
374 TypeIdentifier::String8Small { bound } => u32::from(*bound),
375 TypeIdentifier::String8Large { bound } => *bound,
376 _ => 0,
377 }
378}
379
380fn string_bound_u32_s16(ti: &TypeIdentifier) -> u32 {
381 match ti {
382 TypeIdentifier::String16Small { bound } => u32::from(*bound),
383 TypeIdentifier::String16Large { bound } => *bound,
384 _ => 0,
385 }
386}
387
388fn primitive_compatible(
389 w: PrimitiveKind,
390 r: PrimitiveKind,
391 cfg: &AssignabilityConfig,
392) -> Assignable {
393 if w == r {
394 return Assignable::Yes;
395 }
396 if !cfg.allow_type_coercion {
397 return Assignable::No("primitive kinds differ (no coercion allowed)");
398 }
399 use PrimitiveKind::*;
404 let ok = matches!(
405 (w, r),
406 (Int8 | UInt8 | Byte, Int16 | Int32 | Int64)
407 | (Int16 | UInt16, Int32 | Int64)
408 | (Int32 | UInt32, Int64)
409 | (UInt8 | Byte, UInt16 | UInt32 | UInt64)
410 | (UInt16, UInt32 | UInt64)
411 | (UInt32, UInt64)
412 | (Float32, Float64)
413 );
414 if ok {
415 Assignable::Yes
416 } else {
417 Assignable::No("primitive coercion not widening-safe")
418 }
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
424enum StructExt {
425 Final,
426 Appendable,
427 Mutable,
428}
429
430fn struct_extensibility(flags: StructTypeFlag) -> StructExt {
431 if flags.has(StructTypeFlag::IS_FINAL) {
432 StructExt::Final
433 } else if flags.has(StructTypeFlag::IS_MUTABLE) {
434 StructExt::Mutable
435 } else {
436 StructExt::Appendable
437 }
438}
439
440fn check_minimal_types(
441 w: &MinimalTypeObject,
442 r: &MinimalTypeObject,
443 registry: &TypeRegistry,
444 cfg: &AssignabilityConfig,
445) -> Assignable {
446 match (w, r) {
447 (MinimalTypeObject::Struct(ws), MinimalTypeObject::Struct(rs)) => {
448 let w_ext = struct_extensibility(ws.struct_flags);
454 let r_ext = struct_extensibility(rs.struct_flags);
455 if w_ext != r_ext {
456 return Assignable::No("extensibility mismatch");
457 }
458 let w_final = matches!(w_ext, StructExt::Final);
459 let w_mut = matches!(w_ext, StructExt::Mutable);
460
461 if w_final {
462 if ws.member_seq.len() != rs.member_seq.len() {
464 return Assignable::No("final struct member count mismatch");
465 }
466 for (wm, rm) in ws.member_seq.iter().zip(rs.member_seq.iter()) {
467 if !cfg.ignore_member_names && wm.detail != rm.detail {
468 return Assignable::No("final struct member name-hash differs");
469 }
470 match is_assignable(
471 &wm.common.member_type_id,
472 &rm.common.member_type_id,
473 registry,
474 cfg,
475 ) {
476 Assignable::Yes => {}
477 e => return e,
478 }
479 }
480 Assignable::Yes
481 } else if w_mut {
482 for rm in &rs.member_seq {
487 let rm_optional = rm
488 .common
489 .member_flags
490 .has(crate::type_object::flags::StructMemberFlag::IS_OPTIONAL);
491 match ws
492 .member_seq
493 .iter()
494 .find(|wm| wm.common.member_id == rm.common.member_id)
495 {
496 Some(wm) => {
497 if !cfg.ignore_member_names && wm.detail != rm.detail {
498 return Assignable::No(
499 "mutable: member name-hash differs despite id match",
500 );
501 }
502 match is_assignable(
503 &wm.common.member_type_id,
504 &rm.common.member_type_id,
505 registry,
506 cfg,
507 ) {
508 Assignable::Yes => {}
509 e => return e,
510 }
511 }
512 None if rm_optional => {}
513 None => return Assignable::No("mutable: reader member missing in writer"),
514 }
515 }
516 Assignable::Yes
517 } else {
518 if ws.member_seq.len() < rs.member_seq.len() {
521 return Assignable::No("appendable: writer has fewer members than reader");
522 }
523 for (wm, rm) in ws.member_seq.iter().zip(rs.member_seq.iter()) {
524 if !cfg.ignore_member_names && wm.detail != rm.detail {
525 return Assignable::No("appendable: member name-hash differs");
526 }
527 match is_assignable(
528 &wm.common.member_type_id,
529 &rm.common.member_type_id,
530 registry,
531 cfg,
532 ) {
533 Assignable::Yes => {}
534 e => return e,
535 }
536 }
537 Assignable::Yes
538 }
539 }
540 (MinimalTypeObject::Enumerated(we), MinimalTypeObject::Enumerated(re)) => {
541 if we.header.common.bit_bound != re.header.common.bit_bound {
546 return Assignable::No("enum bit_bound mismatch");
547 }
548 let ignore_names = cfg.ignore_literal_names
551 || we
552 .enum_flags
553 .has(crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES)
554 || re
555 .enum_flags
556 .has(crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES);
557 for wl in &we.literal_seq {
558 let found = re.literal_seq.iter().any(|rl| {
559 rl.common.value == wl.common.value && (ignore_names || rl.detail == wl.detail)
560 });
561 if !found {
562 return Assignable::No("enum writer literal unknown in reader");
563 }
564 }
565 Assignable::Yes
566 }
567 _ => Assignable::No("type kinds do not match"),
568 }
569}
570
571#[cfg(test)]
572#[allow(clippy::unwrap_used)]
573mod tests {
574 use super::*;
575 use crate::builder::{Extensibility, TypeObjectBuilder};
576 use crate::hash::compute_minimal_hash;
577 use crate::type_object::TypeObject;
578
579 #[test]
580 fn primitive_same_kind_is_assignable() {
581 let reg = TypeRegistry::new();
582 let a = is_assignable(
583 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
584 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
585 ®,
586 &AssignabilityConfig::default(),
587 );
588 assert!(a.is_yes());
589 }
590
591 #[test]
592 fn primitive_different_kind_is_not_assignable_by_default() {
593 let reg = TypeRegistry::new();
594 let a = is_assignable(
595 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
596 &TypeIdentifier::Primitive(PrimitiveKind::Int64),
597 ®,
598 &AssignabilityConfig::default(),
599 );
600 assert!(!a.is_yes());
601 }
602
603 #[test]
604 fn primitive_widening_with_coercion_is_assignable() {
605 let reg = TypeRegistry::new();
606 let cfg = AssignabilityConfig {
607 allow_type_coercion: true,
608 ..Default::default()
609 };
610 assert!(
611 is_assignable(
612 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
613 &TypeIdentifier::Primitive(PrimitiveKind::Int64),
614 ®,
615 &cfg,
616 )
617 .is_yes()
618 );
619 assert!(
621 !is_assignable(
622 &TypeIdentifier::Primitive(PrimitiveKind::Int64),
623 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
624 ®,
625 &cfg,
626 )
627 .is_yes()
628 );
629 }
630
631 #[test]
632 fn appendable_struct_with_extra_writer_field_is_assignable() {
633 let mut reg = TypeRegistry::new();
634 let writer = MinimalTypeObject::Struct(
635 TypeObjectBuilder::struct_type("::X")
636 .extensibility(Extensibility::Appendable)
637 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
638 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
639 .build_minimal(),
640 );
641 let reader = MinimalTypeObject::Struct(
642 TypeObjectBuilder::struct_type("::X")
643 .extensibility(Extensibility::Appendable)
644 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
645 .build_minimal(),
646 );
647 let wh = compute_minimal_hash(&writer).unwrap();
648 let rh = compute_minimal_hash(&reader).unwrap();
649 reg.insert_minimal(wh, writer.clone());
650 reg.insert_minimal(rh, reader);
651
652 assert!(
653 is_assignable(
654 &TypeIdentifier::EquivalenceHashMinimal(wh),
655 &TypeIdentifier::EquivalenceHashMinimal(rh),
656 ®,
657 &AssignabilityConfig::default(),
658 )
659 .is_yes()
660 );
661 }
662
663 #[test]
664 fn final_struct_with_extra_writer_field_is_not_assignable() {
665 let mut reg = TypeRegistry::new();
666 let writer = MinimalTypeObject::Struct(
667 TypeObjectBuilder::struct_type("::X")
668 .extensibility(Extensibility::Final)
669 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
670 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
671 .build_minimal(),
672 );
673 let reader = MinimalTypeObject::Struct(
674 TypeObjectBuilder::struct_type("::X")
675 .extensibility(Extensibility::Final)
676 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
677 .build_minimal(),
678 );
679 let wh = compute_minimal_hash(&writer).unwrap();
680 let rh = compute_minimal_hash(&reader).unwrap();
681 reg.insert_minimal(wh, writer);
682 reg.insert_minimal(rh, reader);
683
684 assert!(
685 !is_assignable(
686 &TypeIdentifier::EquivalenceHashMinimal(wh),
687 &TypeIdentifier::EquivalenceHashMinimal(rh),
688 ®,
689 &AssignabilityConfig::default(),
690 )
691 .is_yes()
692 );
693 }
694
695 #[test]
696 fn mutable_struct_member_id_matching() {
697 let mut reg = TypeRegistry::new();
698 let writer = MinimalTypeObject::Struct(
701 TypeObjectBuilder::struct_type("::X")
702 .extensibility(Extensibility::Mutable)
703 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
704 m.id(2)
705 })
706 .member("c", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
707 m.id(3)
708 })
709 .build_minimal(),
710 );
711 let reader = MinimalTypeObject::Struct(
712 TypeObjectBuilder::struct_type("::X")
713 .extensibility(Extensibility::Mutable)
714 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
715 m.id(1).optional()
716 })
717 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| {
718 m.id(2)
719 })
720 .build_minimal(),
721 );
722 let wh = compute_minimal_hash(&writer).unwrap();
723 let rh = compute_minimal_hash(&reader).unwrap();
724 reg.insert_minimal(wh, writer);
725 reg.insert_minimal(rh, reader);
726
727 assert!(
728 is_assignable(
729 &TypeIdentifier::EquivalenceHashMinimal(wh),
730 &TypeIdentifier::EquivalenceHashMinimal(rh),
731 ®,
732 &AssignabilityConfig::default(),
733 )
734 .is_yes()
735 );
736 }
737
738 #[test]
739 fn extensibility_mismatch_fails() {
740 let mut reg = TypeRegistry::new();
741 let writer = MinimalTypeObject::Struct(
742 TypeObjectBuilder::struct_type("::X")
743 .extensibility(Extensibility::Final)
744 .build_minimal(),
745 );
746 let reader = MinimalTypeObject::Struct(
747 TypeObjectBuilder::struct_type("::X")
748 .extensibility(Extensibility::Mutable)
749 .build_minimal(),
750 );
751 let wh = compute_minimal_hash(&writer).unwrap();
752 let rh = compute_minimal_hash(&reader).unwrap();
753 reg.insert_minimal(wh, writer);
754 reg.insert_minimal(rh, reader);
755
756 let a = is_assignable(
757 &TypeIdentifier::EquivalenceHashMinimal(wh),
758 &TypeIdentifier::EquivalenceHashMinimal(rh),
759 ®,
760 &AssignabilityConfig::default(),
761 );
762 assert!(!a.is_yes());
763 }
764
765 #[test]
766 fn string_small_and_large_interchangeable() {
767 let reg = TypeRegistry::new();
768 assert!(
769 is_assignable(
770 &TypeIdentifier::String8Small { bound: 64 },
771 &TypeIdentifier::String8Large { bound: 100_000 },
772 ®,
773 &AssignabilityConfig::default(),
774 )
775 .is_yes()
776 );
777 }
778
779 #[allow(dead_code)]
781 fn _unused() -> TypeObject {
782 TypeObject::Minimal(MinimalTypeObject::Struct(
783 TypeObjectBuilder::struct_type("::dummy").build_minimal(),
784 ))
785 }
786
787 use crate::type_identifier::PlainCollectionHeader;
790 use alloc::boxed::Box;
791
792 fn reg() -> TypeRegistry {
793 TypeRegistry::new()
794 }
795
796 #[test]
797 fn string8_vs_string16_not_assignable() {
798 let a = is_assignable(
799 &TypeIdentifier::String8Small { bound: 16 },
800 &TypeIdentifier::String16Small { bound: 16 },
801 ®(),
802 &AssignabilityConfig::default(),
803 );
804 assert!(!a.is_yes());
805 assert!(matches!(a, Assignable::No(msg) if msg.contains("kinds")));
806 }
807
808 #[test]
809 fn string16_small_and_large_interchangeable() {
810 let a = is_assignable(
811 &TypeIdentifier::String16Small { bound: 32 },
812 &TypeIdentifier::String16Large { bound: 10_000 },
813 ®(),
814 &AssignabilityConfig::default(),
815 );
816 assert!(a.is_yes());
817 }
818
819 #[test]
820 fn identical_type_identifiers_short_circuit_yes() {
821 let ti = TypeIdentifier::Primitive(PrimitiveKind::UInt32);
823 let a = is_assignable(&ti, &ti, ®(), &AssignabilityConfig::default());
824 assert!(a.is_yes());
825 }
826
827 #[test]
828 fn sequence_writer_bound_exceeds_reader_bound_is_no() {
829 let w = TypeIdentifier::PlainSequenceSmall {
830 header: PlainCollectionHeader::default(),
831 bound: 20,
832 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
833 };
834 let r = TypeIdentifier::PlainSequenceSmall {
835 header: PlainCollectionHeader::default(),
836 bound: 10,
837 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
838 };
839 let cfg = AssignabilityConfig {
841 ignore_sequence_bounds: false,
842 ..Default::default()
843 };
844 let a = is_assignable(&w, &r, ®(), &cfg);
845 assert!(!a.is_yes());
846 assert!(matches!(a, Assignable::No(msg) if msg.contains("bound")));
847 }
848
849 #[test]
852 fn sequence_bounds_ignored_when_policy_allows() {
853 let w = TypeIdentifier::PlainSequenceSmall {
854 header: PlainCollectionHeader::default(),
855 bound: 20,
856 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
857 };
858 let r = TypeIdentifier::PlainSequenceSmall {
859 header: PlainCollectionHeader::default(),
860 bound: 10,
861 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
862 };
863 assert!(is_assignable(&w, &r, ®(), &AssignabilityConfig::default()).is_yes());
864 }
865
866 #[test]
867 fn sequence_reader_unbounded_accepts_any_writer_bound() {
868 let w = TypeIdentifier::PlainSequenceSmall {
870 header: PlainCollectionHeader::default(),
871 bound: 200,
872 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int16)),
873 };
874 let r = TypeIdentifier::PlainSequenceSmall {
875 header: PlainCollectionHeader::default(),
876 bound: 0,
877 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int16)),
878 };
879 assert!(is_assignable(&w, &r, ®(), &AssignabilityConfig::default()).is_yes());
880 }
881
882 #[test]
883 fn sequence_elements_must_be_assignable() {
884 let w = TypeIdentifier::PlainSequenceSmall {
886 header: PlainCollectionHeader::default(),
887 bound: 5,
888 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
889 };
890 let r = TypeIdentifier::PlainSequenceSmall {
891 header: PlainCollectionHeader::default(),
892 bound: 5,
893 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Float64)),
894 };
895 assert!(!is_assignable(&w, &r, ®(), &AssignabilityConfig::default()).is_yes());
896 }
897
898 #[test]
899 fn nested_sequence_of_sequence_assignable_when_elements_match() {
900 let inner = TypeIdentifier::PlainSequenceSmall {
901 header: PlainCollectionHeader::default(),
902 bound: 5,
903 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
904 };
905 let outer = TypeIdentifier::PlainSequenceSmall {
906 header: PlainCollectionHeader::default(),
907 bound: 3,
908 element: Box::new(inner.clone()),
909 };
910 let inner_wider = TypeIdentifier::PlainSequenceSmall {
912 header: PlainCollectionHeader::default(),
913 bound: 10,
914 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Byte)),
915 };
916 let outer2 = TypeIdentifier::PlainSequenceSmall {
917 header: PlainCollectionHeader::default(),
918 bound: 3,
919 element: Box::new(inner_wider),
920 };
921 assert!(is_assignable(&outer, &outer2, ®(), &AssignabilityConfig::default()).is_yes());
922 }
923
924 #[test]
925 fn plain_array_identical_assigns_yes() {
926 let a = is_assignable(
927 &TypeIdentifier::PlainArraySmall {
928 header: PlainCollectionHeader::default(),
929 array_bounds: alloc::vec![3, 4],
930 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
931 },
932 &TypeIdentifier::PlainArraySmall {
933 header: PlainCollectionHeader::default(),
934 array_bounds: alloc::vec![3, 4],
935 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
936 },
937 ®(),
938 &AssignabilityConfig::default(),
939 );
940 assert!(a.is_yes());
941 }
942
943 #[test]
944 fn plain_array_diff_bounds_is_no() {
945 let b = is_assignable(
946 &TypeIdentifier::PlainArraySmall {
947 header: PlainCollectionHeader::default(),
948 array_bounds: alloc::vec![3, 4],
949 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
950 },
951 &TypeIdentifier::PlainArraySmall {
952 header: PlainCollectionHeader::default(),
953 array_bounds: alloc::vec![3, 5],
954 element: Box::new(TypeIdentifier::Primitive(PrimitiveKind::Int32)),
955 },
956 ®(),
957 &AssignabilityConfig::default(),
958 );
959 assert!(!b.is_yes());
960 assert!(matches!(b, Assignable::No(msg) if msg.contains("array bounds")));
961 }
962
963 #[test]
964 fn equivalence_hash_identical_short_circuits_to_yes() {
965 let mut reg = reg();
970 let to = MinimalTypeObject::Struct(
971 TypeObjectBuilder::struct_type("::T")
972 .extensibility(Extensibility::Appendable)
973 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
974 .build_minimal(),
975 );
976 let h = compute_minimal_hash(&to).unwrap();
977 reg.insert_minimal(h, to);
978 let a = is_assignable(
979 &TypeIdentifier::EquivalenceHashMinimal(h),
980 &TypeIdentifier::EquivalenceHashMinimal(h),
981 ®,
982 &AssignabilityConfig::default(),
983 );
984 assert!(a.is_yes());
985 }
986
987 #[test]
988 fn equivalence_hash_unresolved_writer_is_no() {
989 let reg = reg();
992 let wh = crate::type_identifier::EquivalenceHash([0x01; 14]);
993 let rh = crate::type_identifier::EquivalenceHash([0x02; 14]);
994 let a = is_assignable(
995 &TypeIdentifier::EquivalenceHashMinimal(wh),
996 &TypeIdentifier::EquivalenceHashMinimal(rh),
997 ®,
998 &AssignabilityConfig::default(),
999 );
1000 assert!(!a.is_yes());
1001 assert!(matches!(a, Assignable::No(msg) if msg.contains("alias resolution")));
1002 }
1003
1004 #[test]
1005 fn equivalence_hash_unresolved_reader_is_no() {
1006 let mut reg = reg();
1008 let to = MinimalTypeObject::Struct(
1009 TypeObjectBuilder::struct_type("::T")
1010 .extensibility(Extensibility::Appendable)
1011 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1012 .build_minimal(),
1013 );
1014 let wh = compute_minimal_hash(&to).unwrap();
1015 reg.insert_minimal(wh, to);
1016 let rh = crate::type_identifier::EquivalenceHash([0x02; 14]);
1017 let a = is_assignable(
1018 &TypeIdentifier::EquivalenceHashMinimal(wh),
1019 &TypeIdentifier::EquivalenceHashMinimal(rh),
1020 ®,
1021 &AssignabilityConfig::default(),
1022 );
1023 assert!(!a.is_yes());
1024 assert!(matches!(a, Assignable::No(msg) if msg.contains("reader")));
1025 }
1026
1027 #[test]
1028 fn mixed_kinds_report_kinds_do_not_match() {
1029 let a = is_assignable(
1031 &TypeIdentifier::Primitive(PrimitiveKind::Int32),
1032 &TypeIdentifier::String8Small { bound: 10 },
1033 ®(),
1034 &AssignabilityConfig::default(),
1035 );
1036 assert!(!a.is_yes());
1037 }
1038
1039 #[test]
1040 fn enum_mismatch_writer_literal_unknown_in_reader_is_no() {
1041 let mut reg = reg();
1042 let w = MinimalTypeObject::Enumerated(
1043 TypeObjectBuilder::enum_type("::E")
1044 .bit_bound(32)
1045 .literal("A", 1)
1046 .literal("B", 2)
1047 .build_minimal(),
1048 );
1049 let r = MinimalTypeObject::Enumerated(
1050 TypeObjectBuilder::enum_type("::E")
1051 .bit_bound(32)
1052 .literal("A", 1)
1053 .build_minimal(),
1054 );
1055 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1056 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1057 reg.insert_minimal(wh, w);
1058 reg.insert_minimal(rh, r);
1059
1060 let a = is_assignable(
1061 &TypeIdentifier::EquivalenceHashMinimal(wh),
1062 &TypeIdentifier::EquivalenceHashMinimal(rh),
1063 ®,
1064 &AssignabilityConfig::default(),
1065 );
1066 assert!(!a.is_yes());
1067 }
1068
1069 #[test]
1070 fn enum_bit_bound_mismatch_is_no() {
1071 let mut reg = reg();
1072 let w = MinimalTypeObject::Enumerated(
1073 TypeObjectBuilder::enum_type("::E")
1074 .bit_bound(32)
1075 .literal("A", 1)
1076 .build_minimal(),
1077 );
1078 let r = MinimalTypeObject::Enumerated(
1079 TypeObjectBuilder::enum_type("::E")
1080 .bit_bound(16)
1081 .literal("A", 1)
1082 .build_minimal(),
1083 );
1084 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1085 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1086 reg.insert_minimal(wh, w);
1087 reg.insert_minimal(rh, r);
1088
1089 let a = is_assignable(
1090 &TypeIdentifier::EquivalenceHashMinimal(wh),
1091 &TypeIdentifier::EquivalenceHashMinimal(rh),
1092 ®,
1093 &AssignabilityConfig::default(),
1094 );
1095 assert!(!a.is_yes());
1096 }
1097
1098 #[test]
1099 fn enum_identical_labels_is_yes() {
1100 let mut reg = reg();
1101 let w = MinimalTypeObject::Enumerated(
1102 TypeObjectBuilder::enum_type("::E")
1103 .bit_bound(32)
1104 .literal("A", 1)
1105 .literal("B", 2)
1106 .build_minimal(),
1107 );
1108 let r = MinimalTypeObject::Enumerated(
1109 TypeObjectBuilder::enum_type("::E")
1110 .bit_bound(32)
1111 .literal("A", 1)
1112 .literal("B", 2)
1113 .literal("C", 3) .build_minimal(),
1115 );
1116 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1117 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1118 reg.insert_minimal(wh, w);
1119 reg.insert_minimal(rh, r);
1120
1121 let a = is_assignable(
1122 &TypeIdentifier::EquivalenceHashMinimal(wh),
1123 &TypeIdentifier::EquivalenceHashMinimal(rh),
1124 ®,
1125 &AssignabilityConfig::default(),
1126 );
1127 assert!(a.is_yes());
1128 }
1129
1130 #[test]
1133 fn flatten_inheritance_no_base_returns_struct_unchanged() {
1134 let s = TypeObjectBuilder::struct_type("::S")
1135 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1136 .build_minimal();
1137 let reg = reg();
1138 let flat = flatten_inheritance(&s, ®, 8).unwrap();
1139 assert_eq!(flat.member_seq.len(), 1);
1140 }
1141
1142 #[test]
1143 fn flatten_inheritance_two_levels_concatenates_base_first() {
1144 let mut reg = reg();
1146 let root = TypeObjectBuilder::struct_type("::Root")
1147 .member("r", TypeIdentifier::Primitive(PrimitiveKind::Int8), |m| {
1148 m.id(101)
1149 })
1150 .build_minimal();
1151 let root_h = compute_minimal_hash(&MinimalTypeObject::Struct(root.clone())).unwrap();
1152 reg.insert_minimal(root_h, MinimalTypeObject::Struct(root));
1153
1154 let mid = TypeObjectBuilder::struct_type("::Mid")
1155 .base(TypeIdentifier::EquivalenceHashMinimal(root_h))
1156 .member("m", TypeIdentifier::Primitive(PrimitiveKind::Int16), |m| {
1157 m.id(202)
1158 })
1159 .build_minimal();
1160 let mid_h = compute_minimal_hash(&MinimalTypeObject::Struct(mid.clone())).unwrap();
1161 reg.insert_minimal(mid_h, MinimalTypeObject::Struct(mid));
1162
1163 let derived = TypeObjectBuilder::struct_type("::Derived")
1164 .base(TypeIdentifier::EquivalenceHashMinimal(mid_h))
1165 .member("d", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1166 m.id(303)
1167 })
1168 .build_minimal();
1169
1170 let flat = flatten_inheritance(&derived, ®, 8).unwrap();
1171 assert_eq!(flat.header.base_type, TypeIdentifier::None);
1173 assert_eq!(flat.member_seq.len(), 3);
1174 let first_id = flat.member_seq[0].common.member_id;
1176 let last_id = flat.member_seq[2].common.member_id;
1177 assert_ne!(first_id, last_id);
1178 }
1179
1180 #[test]
1181 fn inheritance_conflict_same_id() {
1182 let mut reg = reg();
1183 let base = TypeObjectBuilder::struct_type("::B")
1184 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1185 m.id(7)
1186 })
1187 .build_minimal();
1188 let bh = compute_minimal_hash(&MinimalTypeObject::Struct(base.clone())).unwrap();
1189 reg.insert_minimal(bh, MinimalTypeObject::Struct(base));
1190
1191 let derived = TypeObjectBuilder::struct_type("::D")
1192 .base(TypeIdentifier::EquivalenceHashMinimal(bh))
1193 .member("c", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1194 m.id(7) })
1196 .build_minimal();
1197 let err = flatten_inheritance(&derived, ®, 8).unwrap_err();
1198 assert!(matches!(
1199 err,
1200 InheritanceError::InheritanceConflict { reason, .. }
1201 if reason.contains("member_id")
1202 ));
1203 }
1204
1205 #[test]
1206 fn inheritance_conflict_same_name() {
1207 let mut reg = reg();
1208 let base = TypeObjectBuilder::struct_type("::B")
1209 .member(
1210 "dup",
1211 TypeIdentifier::Primitive(PrimitiveKind::Int32),
1212 |m| m.id(1),
1213 )
1214 .build_minimal();
1215 let bh = compute_minimal_hash(&MinimalTypeObject::Struct(base.clone())).unwrap();
1216 reg.insert_minimal(bh, MinimalTypeObject::Struct(base));
1217
1218 let derived = TypeObjectBuilder::struct_type("::D")
1219 .base(TypeIdentifier::EquivalenceHashMinimal(bh))
1220 .member(
1221 "dup",
1222 TypeIdentifier::Primitive(PrimitiveKind::Int32),
1223 |m| {
1224 m.id(2) },
1226 )
1227 .build_minimal();
1228 let err = flatten_inheritance(&derived, ®, 8).unwrap_err();
1229 assert!(matches!(
1230 err,
1231 InheritanceError::InheritanceConflict { reason, .. }
1232 if reason.contains("name_hash")
1233 ));
1234 }
1235
1236 #[test]
1237 fn flat_type_construction_two_levels() {
1238 let mut reg = reg();
1242 let base = TypeObjectBuilder::struct_type("::Base")
1243 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1244 m.id(1)
1245 })
1246 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1247 m.id(2)
1248 })
1249 .build_minimal();
1250 let bh = compute_minimal_hash(&MinimalTypeObject::Struct(base.clone())).unwrap();
1251 reg.insert_minimal(bh, MinimalTypeObject::Struct(base));
1252
1253 let derived = TypeObjectBuilder::struct_type("::Derived")
1254 .base(TypeIdentifier::EquivalenceHashMinimal(bh))
1255 .member("c", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1256 m.id(3)
1257 })
1258 .build_minimal();
1259
1260 let flat = flatten_inheritance(&derived, ®, 8).unwrap();
1261 assert_eq!(flat.member_seq.len(), 3);
1262 assert_eq!(flat.member_seq[0].common.member_id, 1);
1263 assert_eq!(flat.member_seq[1].common.member_id, 2);
1264 assert_eq!(flat.member_seq[2].common.member_id, 3);
1265 }
1266
1267 #[test]
1268 fn two_level_inheritance_assignability_chain() {
1269 let mut reg = reg();
1272
1273 let w_root = TypeObjectBuilder::struct_type("::WRoot")
1275 .member("r", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1276 m.id(1)
1277 })
1278 .build_minimal();
1279 let wr_h = compute_minimal_hash(&MinimalTypeObject::Struct(w_root.clone())).unwrap();
1280 reg.insert_minimal(wr_h, MinimalTypeObject::Struct(w_root));
1281
1282 let w_mid = TypeObjectBuilder::struct_type("::WMid")
1283 .base(TypeIdentifier::EquivalenceHashMinimal(wr_h))
1284 .member("m", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1285 m.id(2)
1286 })
1287 .build_minimal();
1288
1289 let w_flat = flatten_inheritance(&w_mid, ®, 8).unwrap();
1290 assert_eq!(w_flat.member_seq.len(), 2);
1291
1292 let r_root = TypeObjectBuilder::struct_type("::RRoot")
1294 .member("r", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1295 m.id(1)
1296 })
1297 .build_minimal();
1298 let rr_h = compute_minimal_hash(&MinimalTypeObject::Struct(r_root.clone())).unwrap();
1299 reg.insert_minimal(rr_h, MinimalTypeObject::Struct(r_root));
1300
1301 let r_mid = TypeObjectBuilder::struct_type("::RMid")
1302 .base(TypeIdentifier::EquivalenceHashMinimal(rr_h))
1303 .member("m", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1304 m.id(2)
1305 })
1306 .build_minimal();
1307
1308 let r_flat = flatten_inheritance(&r_mid, ®, 8).unwrap();
1309 assert_eq!(r_flat.member_seq.len(), 2);
1310
1311 let w_to = MinimalTypeObject::Struct(w_flat.clone());
1313 let r_to = MinimalTypeObject::Struct(r_flat.clone());
1314 let wh = compute_minimal_hash(&w_to).unwrap();
1315 let rh = compute_minimal_hash(&r_to).unwrap();
1316 reg.insert_minimal(wh, w_to);
1317 reg.insert_minimal(rh, r_to);
1318 let a = is_assignable(
1319 &TypeIdentifier::EquivalenceHashMinimal(wh),
1320 &TypeIdentifier::EquivalenceHashMinimal(rh),
1321 ®,
1322 &AssignabilityConfig::default(),
1323 );
1324 assert!(a.is_yes(), "got {a:?}");
1325 }
1326
1327 #[test]
1328 fn enum_not_assignable_strict_default() {
1329 let mut reg = reg();
1333 let w = MinimalTypeObject::Enumerated(
1334 TypeObjectBuilder::enum_type("::E")
1335 .bit_bound(32)
1336 .literal("RED", 1)
1337 .build_minimal(),
1338 );
1339 let r = MinimalTypeObject::Enumerated(
1340 TypeObjectBuilder::enum_type("::E")
1341 .bit_bound(32)
1342 .literal("ROUGE", 1) .build_minimal(),
1344 );
1345 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1346 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1347 reg.insert_minimal(wh, w);
1348 reg.insert_minimal(rh, r);
1349
1350 let a = is_assignable(
1351 &TypeIdentifier::EquivalenceHashMinimal(wh),
1352 &TypeIdentifier::EquivalenceHashMinimal(rh),
1353 ®,
1354 &AssignabilityConfig::default(),
1355 );
1356 assert!(!a.is_yes());
1357 }
1358
1359 #[test]
1360 fn enum_assignable_with_ignore_literal_names() {
1361 let mut reg = reg();
1364 let w = MinimalTypeObject::Enumerated(
1365 TypeObjectBuilder::enum_type("::E")
1366 .bit_bound(32)
1367 .literal("RED", 1)
1368 .literal("GREEN", 2)
1369 .build_minimal(),
1370 );
1371 let r = MinimalTypeObject::Enumerated(
1372 TypeObjectBuilder::enum_type("::E")
1373 .bit_bound(32)
1374 .literal("ROUGE", 1)
1375 .literal("VERT", 2)
1376 .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 cfg = AssignabilityConfig {
1384 ignore_literal_names: true,
1385 ..AssignabilityConfig::default()
1386 };
1387 let a = is_assignable(
1388 &TypeIdentifier::EquivalenceHashMinimal(wh),
1389 &TypeIdentifier::EquivalenceHashMinimal(rh),
1390 ®,
1391 &cfg,
1392 );
1393 assert!(a.is_yes());
1394 }
1395
1396 #[test]
1397 fn enum_assignable_with_ignore_literal_names_via_writer_flag() {
1398 let mut reg = reg();
1401 let mut w_e = TypeObjectBuilder::enum_type("::E")
1402 .bit_bound(32)
1403 .literal("RED", 1)
1404 .build_minimal();
1405 w_e.enum_flags = crate::type_object::flags::EnumTypeFlag(
1406 crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES,
1407 );
1408 let w = MinimalTypeObject::Enumerated(w_e);
1409 let r = MinimalTypeObject::Enumerated(
1410 TypeObjectBuilder::enum_type("::E")
1411 .bit_bound(32)
1412 .literal("ROUGE", 1)
1413 .build_minimal(),
1414 );
1415 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1416 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1417 reg.insert_minimal(wh, w);
1418 reg.insert_minimal(rh, r);
1419
1420 let a = is_assignable(
1421 &TypeIdentifier::EquivalenceHashMinimal(wh),
1422 &TypeIdentifier::EquivalenceHashMinimal(rh),
1423 ®,
1424 &AssignabilityConfig::default(),
1425 );
1426 assert!(a.is_yes());
1427 }
1428
1429 #[test]
1430 fn enum_assignable_with_ignore_literal_names_via_reader_flag() {
1431 let mut reg = reg();
1432 let w = MinimalTypeObject::Enumerated(
1433 TypeObjectBuilder::enum_type("::E")
1434 .bit_bound(32)
1435 .literal("RED", 1)
1436 .build_minimal(),
1437 );
1438 let mut r_e = TypeObjectBuilder::enum_type("::E")
1439 .bit_bound(32)
1440 .literal("ROUGE", 1)
1441 .build_minimal();
1442 r_e.enum_flags = crate::type_object::flags::EnumTypeFlag(
1443 crate::type_object::flags::EnumTypeFlag::IGNORE_LITERAL_NAMES,
1444 );
1445 let r = MinimalTypeObject::Enumerated(r_e);
1446 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1447 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1448 reg.insert_minimal(wh, w);
1449 reg.insert_minimal(rh, r);
1450
1451 let a = is_assignable(
1452 &TypeIdentifier::EquivalenceHashMinimal(wh),
1453 &TypeIdentifier::EquivalenceHashMinimal(rh),
1454 ®,
1455 &AssignabilityConfig::default(),
1456 );
1457 assert!(a.is_yes());
1458 }
1459
1460 #[test]
1461 fn struct_vs_enum_type_object_kinds_dont_match() {
1462 let mut reg = reg();
1463 let w = MinimalTypeObject::Struct(
1464 TypeObjectBuilder::struct_type("::X")
1465 .extensibility(Extensibility::Appendable)
1466 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1467 .build_minimal(),
1468 );
1469 let r = MinimalTypeObject::Enumerated(
1470 TypeObjectBuilder::enum_type("::X")
1471 .bit_bound(32)
1472 .literal("A", 1)
1473 .build_minimal(),
1474 );
1475 let wh = crate::hash::compute_minimal_hash(&w).unwrap();
1476 let rh = crate::hash::compute_minimal_hash(&r).unwrap();
1477 reg.insert_minimal(wh, w);
1478 reg.insert_minimal(rh, r);
1479
1480 let a = is_assignable(
1481 &TypeIdentifier::EquivalenceHashMinimal(wh),
1482 &TypeIdentifier::EquivalenceHashMinimal(rh),
1483 ®,
1484 &AssignabilityConfig::default(),
1485 );
1486 assert!(!a.is_yes());
1487 }
1488
1489 #[test]
1490 fn appendable_struct_writer_smaller_than_reader_is_no() {
1491 let mut reg = reg();
1492 let writer = MinimalTypeObject::Struct(
1493 TypeObjectBuilder::struct_type("::X")
1494 .extensibility(Extensibility::Appendable)
1495 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1496 .build_minimal(),
1497 );
1498 let reader = MinimalTypeObject::Struct(
1499 TypeObjectBuilder::struct_type("::X")
1500 .extensibility(Extensibility::Appendable)
1501 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1502 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1503 .build_minimal(),
1504 );
1505 let wh = crate::hash::compute_minimal_hash(&writer).unwrap();
1506 let rh = crate::hash::compute_minimal_hash(&reader).unwrap();
1507 reg.insert_minimal(wh, writer);
1508 reg.insert_minimal(rh, reader);
1509
1510 assert!(
1511 !is_assignable(
1512 &TypeIdentifier::EquivalenceHashMinimal(wh),
1513 &TypeIdentifier::EquivalenceHashMinimal(rh),
1514 ®,
1515 &AssignabilityConfig::default(),
1516 )
1517 .is_yes()
1518 );
1519 }
1520
1521 #[test]
1522 fn mutable_reader_member_missing_in_writer_non_optional_is_no() {
1523 let mut reg = reg();
1524 let writer = MinimalTypeObject::Struct(
1525 TypeObjectBuilder::struct_type("::X")
1526 .extensibility(Extensibility::Mutable)
1527 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1528 m.id(2)
1529 })
1530 .build_minimal(),
1531 );
1532 let reader = MinimalTypeObject::Struct(
1533 TypeObjectBuilder::struct_type("::X")
1534 .extensibility(Extensibility::Mutable)
1535 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1536 m.id(1) })
1538 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| {
1539 m.id(2)
1540 })
1541 .build_minimal(),
1542 );
1543 let wh = crate::hash::compute_minimal_hash(&writer).unwrap();
1544 let rh = crate::hash::compute_minimal_hash(&reader).unwrap();
1545 reg.insert_minimal(wh, writer);
1546 reg.insert_minimal(rh, reader);
1547
1548 assert!(
1549 !is_assignable(
1550 &TypeIdentifier::EquivalenceHashMinimal(wh),
1551 &TypeIdentifier::EquivalenceHashMinimal(rh),
1552 ®,
1553 &AssignabilityConfig::default(),
1554 )
1555 .is_yes()
1556 );
1557 }
1558
1559 #[test]
1560 fn final_struct_member_count_mismatch_is_no() {
1561 let mut reg = reg();
1562 let writer = MinimalTypeObject::Struct(
1563 TypeObjectBuilder::struct_type("::X")
1564 .extensibility(Extensibility::Final)
1565 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1566 .member("b", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1567 .build_minimal(),
1568 );
1569 let reader = MinimalTypeObject::Struct(
1570 TypeObjectBuilder::struct_type("::X")
1571 .extensibility(Extensibility::Final)
1572 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int32), |m| m)
1573 .build_minimal(),
1574 );
1575 let wh = crate::hash::compute_minimal_hash(&writer).unwrap();
1576 let rh = crate::hash::compute_minimal_hash(&reader).unwrap();
1577 reg.insert_minimal(wh, writer);
1578 reg.insert_minimal(rh, reader);
1579
1580 assert!(
1581 !is_assignable(
1582 &TypeIdentifier::EquivalenceHashMinimal(wh),
1583 &TypeIdentifier::EquivalenceHashMinimal(rh),
1584 ®,
1585 &AssignabilityConfig::default(),
1586 )
1587 .is_yes()
1588 );
1589 }
1590
1591 #[test]
1592 fn primitive_widening_int16_to_int64_is_assignable_with_coercion() {
1593 let cfg = AssignabilityConfig {
1594 allow_type_coercion: true,
1595 ..Default::default()
1596 };
1597 assert!(primitive_compatible(PrimitiveKind::Int16, PrimitiveKind::Int64, &cfg).is_yes());
1598 assert!(primitive_compatible(PrimitiveKind::Byte, PrimitiveKind::Int32, &cfg).is_yes());
1599 assert!(
1600 primitive_compatible(PrimitiveKind::Float32, PrimitiveKind::Float64, &cfg).is_yes()
1601 );
1602 }
1603
1604 #[test]
1605 fn primitive_unwidening_is_rejected_even_with_coercion() {
1606 let cfg = AssignabilityConfig {
1607 allow_type_coercion: true,
1608 ..Default::default()
1609 };
1610 assert!(!primitive_compatible(PrimitiveKind::Float64, PrimitiveKind::Int32, &cfg).is_yes());
1611 }
1612
1613 #[test]
1614 fn assignable_is_yes_matches_expectation() {
1615 assert!(Assignable::Yes.is_yes());
1616 assert!(!Assignable::No("reason").is_yes());
1617 }
1618}