1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::{ArrayKey, Atomic, KeyedProperty};
7use crate::symbol::Name;
8use indexmap::IndexMap;
9
10pub fn empty_type_params() -> Arc<[Type]> {
14 static EMPTY: OnceLock<Arc<[Type]>> = OnceLock::new();
15 EMPTY.get_or_init(|| Arc::from([] as [Type; 0])).clone()
16}
17
18pub fn vec_to_type_params(v: Vec<Type>) -> Arc<[Type]> {
21 if v.is_empty() {
22 empty_type_params()
23 } else {
24 Arc::from(v)
25 }
26}
27
28pub type AtomicVec = SmallVec<[Atomic; 2]>;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum CloneValidity {
34 Cloneable,
36 Invalid,
38 PossiblyInvalid,
40 Unknown,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub struct Type {
50 pub types: AtomicVec,
51 pub possibly_undefined: bool,
53 pub from_docblock: bool,
55 pub falsy_stripped: bool,
63 pub possibly_absent_offset: bool,
74}
75
76type IntBounds = (Option<i64>, Option<i64>);
77
78fn int_bounds(a: &Atomic) -> Option<IntBounds> {
80 match a {
81 Atomic::TInt => Some((None, None)),
82 Atomic::TLiteralInt(n) => Some((Some(*n), Some(*n))),
83 Atomic::TPositiveInt => Some((Some(1), None)),
84 Atomic::TNonNegativeInt => Some((Some(0), None)),
85 Atomic::TNegativeInt => Some((None, Some(-1))),
86 Atomic::TIntRange { min, max } => Some((*min, *max)),
87 _ => None,
88 }
89}
90
91fn bounds_contain(outer: IntBounds, inner: IntBounds) -> bool {
92 let lo_ok = match (outer.0, inner.0) {
93 (None, _) => true,
94 (Some(o), Some(i)) => o <= i,
95 (Some(_), None) => false,
96 };
97 let hi_ok = match (outer.1, inner.1) {
98 (None, _) => true,
99 (Some(o), Some(i)) => o >= i,
100 (Some(_), None) => false,
101 };
102 lo_ok && hi_ok
103}
104
105const MAX_KEYED_SHAPES: usize = 8;
112
113fn combine_keyed_arrays(a: &Atomic, b: &Atomic) -> Option<Atomic> {
118 let (
119 Atomic::TKeyedArray {
120 properties: pa,
121 is_open: oa,
122 is_list: la,
123 },
124 Atomic::TKeyedArray {
125 properties: pb,
126 is_open: ob,
127 is_list: lb,
128 },
129 ) = (a, b)
130 else {
131 return None;
132 };
133 let non_empty = |p: &IndexMap<ArrayKey, KeyedProperty>| p.values().any(|v| !v.optional);
134 if oa != ob || la != lb || non_empty(pa) != non_empty(pb) {
135 return None;
136 }
137 let mut properties = IndexMap::with_capacity(pa.len().max(pb.len()));
138 for (key, x) in pa.iter() {
139 let prop = match pb.get(key) {
140 Some(y) => {
141 let mut ty = x.ty.clone();
142 ty.merge_with(&y.ty);
143 KeyedProperty {
144 ty,
145 optional: x.optional || y.optional,
146 }
147 }
148 None if *oa => continue,
151 None => KeyedProperty {
152 ty: x.ty.clone(),
153 optional: true,
154 },
155 };
156 properties.insert(key.clone(), prop);
157 }
158 if !*oa {
159 for (key, y) in pb.iter() {
160 if !pa.contains_key(key) {
161 properties.insert(
162 key.clone(),
163 KeyedProperty {
164 ty: y.ty.clone(),
165 optional: true,
166 },
167 );
168 }
169 }
170 }
171 Some(Atomic::TKeyedArray {
172 properties: Box::new(properties),
173 is_open: *oa,
174 is_list: *la,
175 })
176}
177
178impl Type {
179 pub fn empty() -> Self {
182 Self {
183 types: SmallVec::new(),
184 possibly_undefined: false,
185 from_docblock: false,
186 falsy_stripped: false,
187 possibly_absent_offset: false,
188 }
189 }
190
191 pub fn single(atomic: Atomic) -> Self {
192 let mut types = SmallVec::new();
193 types.push(atomic);
194 Self {
195 types,
196 possibly_undefined: false,
197 from_docblock: false,
198 falsy_stripped: false,
199 possibly_absent_offset: false,
200 }
201 }
202
203 pub fn mixed() -> Self {
204 Self::single(Atomic::TMixed)
205 }
206
207 pub fn void() -> Self {
208 Self::single(Atomic::TVoid)
209 }
210
211 pub fn never() -> Self {
212 Self::single(Atomic::TNever)
213 }
214
215 pub fn null() -> Self {
216 Self::single(Atomic::TNull)
217 }
218
219 pub fn bool() -> Self {
220 Self::single(Atomic::TBool)
221 }
222
223 pub fn int() -> Self {
224 Self::single(Atomic::TInt)
225 }
226
227 pub fn float() -> Self {
228 Self::single(Atomic::TFloat)
229 }
230
231 pub fn string() -> Self {
232 Self::single(Atomic::TString)
233 }
234
235 pub fn array_key() -> Self {
238 let mut u = Self::single(Atomic::TInt);
239 u.add_type(Atomic::TString);
240 u
241 }
242
243 pub fn nullable(atomic: Atomic) -> Self {
245 if matches!(atomic, Atomic::TMixed) {
247 return Self::mixed();
248 }
249 let mut types = SmallVec::new();
250 types.push(atomic);
251 types.push(Atomic::TNull);
252 Self {
253 types,
254 possibly_undefined: false,
255 from_docblock: false,
256 falsy_stripped: false,
257 possibly_absent_offset: false,
258 }
259 }
260
261 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
263 let mut u = Self::empty();
264 for a in atomics {
265 u.add_type(a);
266 }
267 u
268 }
269
270 pub fn is_empty(&self) -> bool {
273 self.types.is_empty()
274 }
275
276 pub fn is_single(&self) -> bool {
277 self.types.len() == 1
278 }
279
280 pub fn is_nullable(&self) -> bool {
281 self.types.iter().any(|t| matches!(t, Atomic::TNull))
282 }
283
284 pub fn is_array_key(&self) -> bool {
289 self.types.len() == 2
290 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
291 && self.types.iter().any(|t| matches!(t, Atomic::TString))
292 }
293
294 pub fn is_mixed(&self) -> bool {
295 self.types.iter().any(|t| match t {
296 Atomic::TMixed => true,
297 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
298 _ => false,
299 })
300 }
301
302 pub fn is_mixed_not_template(&self) -> bool {
307 self.is_mixed()
308 && !self
309 .types
310 .iter()
311 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
312 }
313
314 pub fn is_never(&self) -> bool {
315 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
316 }
317
318 pub fn clone_validity(&self) -> CloneValidity {
321 if self.types.is_empty() {
322 return CloneValidity::Unknown;
323 }
324 let mut has_non_object = false;
325 let mut has_other = false; for t in &self.types {
327 match t {
328 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
329 CloneValidity::Invalid => has_non_object = true,
330 CloneValidity::PossiblyInvalid => {
331 has_non_object = true;
332 has_other = true;
333 }
334 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
335 },
336 other if other.is_definitely_non_object() => has_non_object = true,
337 _ => has_other = true,
338 }
339 }
340 match (has_non_object, has_other) {
341 (true, false) => CloneValidity::Invalid,
342 (true, true) => CloneValidity::PossiblyInvalid,
343 _ => CloneValidity::Cloneable,
344 }
345 }
346
347 pub fn is_void(&self) -> bool {
348 self.is_single() && matches!(self.types[0], Atomic::TVoid)
349 }
350
351 pub fn can_be_falsy(&self) -> bool {
352 self.types.iter().any(|t| t.can_be_falsy())
353 }
354
355 pub fn can_be_truthy(&self) -> bool {
356 self.types.iter().any(|t| t.can_be_truthy())
357 }
358
359 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
360 self.types.iter().any(f)
361 }
362
363 pub fn has_named_object(&self, fqcn: &str) -> bool {
364 self.types.iter().any(|t| match t {
365 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
366 _ => false,
367 })
368 }
369
370 pub fn add_type(&mut self, atomic: Atomic) {
375 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
377 return;
378 }
379
380 if matches!(atomic, Atomic::TMixed) {
382 self.types.clear();
383 self.types.push(Atomic::TMixed);
384 return;
385 }
386
387 let atomic = if let Atomic::TConditional { data } = &atomic {
390 let (if_true, if_false) = (&data.if_true, &data.if_false);
391 let mut simplified_true = Type::empty();
392 for t in &if_true.types {
393 simplified_true.add_type(t.clone());
394 }
395 let mut simplified_false = Type::empty();
396 for t in &if_false.types {
397 simplified_false.add_type(t.clone());
398 }
399 if simplified_true == simplified_false {
400 for t in simplified_true.types {
401 self.add_type(t);
402 }
403 return;
404 }
405 atomic
406 } else {
407 atomic
408 };
409
410 let mut atomic = atomic;
413 if matches!(atomic, Atomic::TKeyedArray { .. })
414 && !self.types.contains(&atomic)
415 && self
416 .types
417 .iter()
418 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
419 .count()
420 >= MAX_KEYED_SHAPES
421 {
422 if let Some((idx, combined)) = self
423 .types
424 .iter()
425 .enumerate()
426 .find_map(|(i, t)| combine_keyed_arrays(t, &atomic).map(|c| (i, c)))
427 {
428 self.types.remove(idx);
429 atomic = combined;
430 }
431 }
432
433 if self.types.contains(&atomic) {
435 return;
436 }
437
438 if let Some(new_bounds) = int_bounds(&atomic) {
441 if self
442 .types
443 .iter()
444 .any(|t| int_bounds(t).is_some_and(|b| bounds_contain(b, new_bounds)))
445 {
446 return;
447 }
448 self.types
449 .retain(|t| !int_bounds(t).is_some_and(|b| bounds_contain(new_bounds, b)));
450 }
451 if let Atomic::TLiteralString(_) = &atomic {
453 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
454 return;
455 }
456 }
457 if matches!(atomic, Atomic::TNonEmptyString)
459 && self.types.iter().any(|t| matches!(t, Atomic::TString))
460 {
461 return;
462 }
463 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
465 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
466 {
467 return;
468 }
469 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
472 {
473 self.types.retain(|t| !matches!(t, Atomic::TFalse));
474 self.types.push(Atomic::TBool);
475 return;
476 }
477 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
478 {
479 self.types.retain(|t| !matches!(t, Atomic::TTrue));
480 self.types.push(Atomic::TBool);
481 return;
482 }
483 if matches!(atomic, Atomic::TString) {
485 self.types
486 .retain(|t| !matches!(t, Atomic::TLiteralString(_) | Atomic::TNonEmptyString));
487 }
488 if matches!(atomic, Atomic::TBool) {
490 self.types
491 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
492 }
493
494 if matches!(atomic, Atomic::TNever) {
496 if !self.types.is_empty() {
497 return;
498 }
499 } else {
500 self.types.retain(|t| !matches!(t, Atomic::TNever));
501 }
502
503 if let Atomic::TKeyedArray {
509 properties,
510 is_open,
511 ..
512 } = &atomic
513 {
514 if properties.is_empty() && !is_open {
515 for existing in &self.types {
516 match existing {
517 Atomic::TArray { .. }
518 | Atomic::TNonEmptyArray { .. }
519 | Atomic::TList { .. }
520 | Atomic::TNonEmptyList { .. } => {
521 return; }
523 _ => {}
524 }
525 }
526 }
527 }
528
529 let is_generic_array_or_list = matches!(
532 &atomic,
533 Atomic::TArray { .. }
534 | Atomic::TNonEmptyArray { .. }
535 | Atomic::TList { .. }
536 | Atomic::TNonEmptyList { .. }
537 );
538 if is_generic_array_or_list {
539 self.types.retain(|t| {
540 if let Atomic::TKeyedArray {
541 properties,
542 is_open,
543 ..
544 } = t
545 {
546 !properties.is_empty() || *is_open
547 } else {
548 true
549 }
550 });
551 }
552
553 self.types.push(atomic);
554 }
555
556 pub fn remove_null(&self) -> Type {
560 self.filter(|t| !matches!(t, Atomic::TNull))
561 }
562
563 pub fn remove_false(&self) -> Type {
566 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
567 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
568 result.add_type(Atomic::TTrue);
569 }
570 result
571 }
572
573 pub fn remove_true(&self) -> Type {
576 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
577 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
578 result.add_type(Atomic::TFalse);
579 }
580 result
581 }
582
583 pub fn core_type(&self) -> Type {
585 self.remove_null().remove_false()
586 }
587
588 pub fn narrow_to_truthy(&self) -> Type {
590 if self.is_mixed_not_template() {
591 return Type::mixed();
592 }
593 let mut result = Type::empty();
594 result.from_docblock = self.from_docblock;
595 for t in &self.types {
596 match t {
597 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
601 Atomic::TLiteralInt(0)
603 | Atomic::TLiteralFloat(0, 0)
604 | Atomic::TNull
605 | Atomic::TFalse => {}
606 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
607 Atomic::TBool => result.add_type(Atomic::TTrue),
609 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
611 key: key.clone(),
612 value: value.clone(),
613 }),
614 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
615 value: value.clone(),
616 }),
617 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
621 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
626 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
627 let atom = if max.is_none() {
628 Atomic::TPositiveInt
629 } else {
630 Atomic::TIntRange {
631 min: Some(1),
632 max: *max,
633 }
634 };
635 result.add_type(atom);
636 }
637 Atomic::TIntRange { min, max: Some(0) } => {
639 let atom = match min {
640 None => Atomic::TNegativeInt,
641 Some(n) if *n <= -1 => Atomic::TIntRange {
642 min: *min,
643 max: Some(-1),
644 },
645 _ => continue, };
647 result.add_type(atom);
648 }
649 t if !t.can_be_truthy() => {}
651 _ => result.add_type(t.clone()),
652 }
653 }
654 result
655 }
656
657 pub fn narrow_to_falsy(&self) -> Type {
659 if self.is_mixed_not_template() {
660 return Type::from_vec(vec![
661 Atomic::TNull,
662 Atomic::TFalse,
663 Atomic::TLiteralInt(0),
664 Atomic::TLiteralString("".into()),
665 ]);
666 }
667 let mut result = Type::empty();
668 result.from_docblock = self.from_docblock;
669 for t in &self.types {
670 match t {
671 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
676 Atomic::TBool => result.add_type(Atomic::TFalse),
678 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
680 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
682 Atomic::TString => {
684 result.add_type(Atomic::TLiteralString("".into()));
685 result.add_type(Atomic::TLiteralString("0".into()));
686 }
687 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
689 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
691 Atomic::TIntRange {
693 min: Some(0),
694 max: Some(_) | None,
695 } => result.add_type(Atomic::TLiteralInt(0)),
696 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
698 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
700 }
701 }
702 result
703 }
704
705 pub fn narrow_instanceof(&self, class: &str) -> Type {
711 let narrowed_ty = Atomic::TNamedObject {
712 fqcn: class.into(),
713 type_params: empty_type_params(),
714 };
715 let has_object = self.types.iter().any(|t| {
717 matches!(
718 t,
719 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
721 });
722 if has_object || self.is_empty() {
723 Type::single(narrowed_ty)
724 } else {
725 Type::single(narrowed_ty)
728 }
729 }
730
731 pub fn narrow_to_string(&self) -> Type {
735 self.filter_replacing(
736 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
737 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
738 Atomic::TString,
739 )
740 .with_callable_form(self, Atomic::TCallableString)
741 }
742
743 pub fn narrow_to_int(&self) -> Type {
745 self.filter_replacing(
746 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
747 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
748 Atomic::TInt,
749 )
750 }
751
752 pub fn narrow_to_float(&self) -> Type {
754 self.filter_replacing(
755 |t| {
756 matches!(
757 t,
758 Atomic::TFloat
759 | Atomic::TIntegralFloat
760 | Atomic::TLiteralFloat(..)
761 | Atomic::TTemplateParam { .. }
762 )
763 },
764 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
765 Atomic::TFloat,
766 )
767 }
768
769 pub fn narrow_to_bool(&self) -> Type {
771 self.filter_replacing(
772 |t| {
773 matches!(
774 t,
775 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
776 )
777 },
778 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
779 Atomic::TBool,
780 )
781 }
782
783 pub fn narrow_to_null(&self) -> Type {
785 self.filter_replacing(
786 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
787 |t| matches!(t, Atomic::TMixed),
788 Atomic::TNull,
789 )
790 }
791
792 pub fn narrow_to_array(&self) -> Type {
794 self.filter_replacing(
795 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
796 |t| matches!(t, Atomic::TMixed),
797 Atomic::TArray {
798 key: Box::new(Type::mixed()),
799 value: Box::new(Type::mixed()),
800 },
801 )
802 .with_callable_form(self, callable_array())
803 }
804
805 pub fn widen_nested_non_empty(&self) -> Type {
808 fn relax_all(ty: &Type) -> Type {
809 map_atoms(ty, |a| match a {
810 Atomic::TNonEmptyList { value } => Atomic::TList {
811 value: Box::new(relax_all(value)),
812 },
813 Atomic::TNonEmptyArray { key, value } => Atomic::TArray {
814 key: key.clone(),
815 value: Box::new(relax_all(value)),
816 },
817 other => map_values(other, relax_all),
818 })
819 }
820 fn map_values(atomic: &Atomic, f: fn(&Type) -> Type) -> Atomic {
821 match atomic {
822 Atomic::TArray { key, value } => Atomic::TArray {
823 key: key.clone(),
824 value: Box::new(f(value)),
825 },
826 Atomic::TList { value } => Atomic::TList {
827 value: Box::new(f(value)),
828 },
829 Atomic::TNonEmptyArray { key, value } => Atomic::TNonEmptyArray {
830 key: key.clone(),
831 value: Box::new(f(value)),
832 },
833 Atomic::TNonEmptyList { value } => Atomic::TNonEmptyList {
834 value: Box::new(f(value)),
835 },
836 Atomic::TKeyedArray {
837 properties,
838 is_open,
839 is_list,
840 } => Atomic::TKeyedArray {
841 properties: Box::new(
842 properties
843 .iter()
844 .map(|(k, p)| {
845 (
846 k.clone(),
847 crate::atomic::KeyedProperty {
848 ty: f(&p.ty),
849 optional: p.optional,
850 },
851 )
852 })
853 .collect(),
854 ),
855 is_open: *is_open,
856 is_list: *is_list,
857 },
858 other => other.clone(),
859 }
860 }
861 fn map_atoms(ty: &Type, f: impl Fn(&Atomic) -> Atomic) -> Type {
862 let mut result = Type::empty();
863 result.possibly_undefined = ty.possibly_undefined;
864 result.from_docblock = ty.from_docblock;
865 for atomic in &ty.types {
866 result.add_type(f(atomic));
867 }
868 result
869 }
870 map_atoms(self, |a| map_values(a, relax_all))
871 }
872
873 pub fn narrow_to_non_empty_collection(&self) -> Type {
876 let mut out = Type::empty();
877 out.from_docblock = self.from_docblock;
878 for t in &self.types {
879 match t {
880 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
881 key: key.clone(),
882 value: value.clone(),
883 }),
884 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
885 value: value.clone(),
886 }),
887 Atomic::TKeyedArray {
888 properties,
889 is_open: false,
890 ..
891 } if properties.is_empty() => {}
892 _ => out.add_type(t.clone()),
893 }
894 }
895 out
896 }
897
898 pub fn narrow_to_empty_collection(&self) -> Type {
908 let mut out = Type::empty();
909 out.from_docblock = self.from_docblock;
910 for t in &self.types {
911 match t {
912 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
913 Atomic::TArray { .. } | Atomic::TList { .. } => {
914 out.add_type(Atomic::TKeyedArray {
915 properties: Box::default(),
916 is_open: false,
917 is_list: true,
918 });
919 }
920 _ => out.add_type(t.clone()),
921 }
922 }
923 out
924 }
925
926 pub fn narrow_to_list(&self) -> Type {
938 let mut out = Type::empty();
939 out.from_docblock = self.from_docblock;
940 for t in &self.types {
941 match t {
942 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
943 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
949 out.add_type(Atomic::TList {
950 value: value.clone(),
951 });
952 }
953 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
954 out.add_type(Atomic::TNonEmptyList {
955 value: value.clone(),
956 });
957 }
958 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
959 Atomic::TMixed => out.add_type(Atomic::TList {
960 value: Box::new(Type::mixed()),
961 }),
962 _ => {}
963 }
964 }
965 if out.is_empty() {
966 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
967 } else {
968 out
969 }
970 }
971
972 pub fn narrow_to_object(&self) -> Type {
977 let mut out = Type::empty();
978 for t in &self.types {
979 if matches!(t, Atomic::TMixed) {
980 out.add_type(Atomic::TObject);
981 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
982 out.add_type(t.clone());
983 }
984 }
985 if out.types.is_empty() {
986 self.filter(|t| t.is_object())
987 .with_callable_form(self, Atomic::TObject)
988 } else {
989 out.with_callable_form(self, Atomic::TObject)
990 }
991 }
992
993 fn with_callable_form(mut self, source: &Type, form: Atomic) -> Type {
996 if source
997 .types
998 .iter()
999 .any(|t| matches!(t, Atomic::TCallable { .. }))
1000 {
1001 self.add_type(form);
1002 }
1003 self
1004 }
1005
1006 pub fn narrow_to_not_callable(&self) -> Type {
1009 let callable_array = callable_array();
1010 self.filter(|t| !t.is_callable() && *t != callable_array)
1011 }
1012
1013 pub fn narrow_to_callable(&self) -> Type {
1020 let narrowed = self.filter(|t| {
1021 t.is_callable()
1022 || t.is_string()
1023 || t.is_array()
1024 || t.is_object()
1025 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1026 });
1027 let mut result = Type::empty();
1034 result.possibly_undefined = narrowed.possibly_undefined;
1035 result.from_docblock = narrowed.from_docblock;
1036 for atomic in narrowed.types {
1037 if matches!(atomic, Atomic::TObject) {
1038 result.add_type(Atomic::TCallable {
1039 params: None,
1040 return_type: None,
1041 is_pure: false,
1042 });
1043 } else {
1044 result.add_type(atomic);
1045 }
1046 }
1047 result
1048 }
1049
1050 pub fn narrow_to_scalar(&self) -> Type {
1052 self.filter_replacing(
1053 |t| {
1054 t.is_string()
1055 || t.is_int()
1056 || matches!(
1057 t,
1058 Atomic::TFloat
1059 | Atomic::TIntegralFloat
1060 | Atomic::TLiteralFloat(..)
1061 | Atomic::TBool
1062 | Atomic::TTrue
1063 | Atomic::TFalse
1064 | Atomic::TScalar
1065 | Atomic::TNumeric
1066 | Atomic::TNumericString
1067 | Atomic::TTemplateParam { .. }
1068 )
1069 },
1070 |t| matches!(t, Atomic::TMixed),
1071 Atomic::TScalar,
1072 )
1073 }
1074
1075 pub fn narrow_to_iterable(&self) -> Type {
1078 self.filter(|t| {
1079 t.is_array()
1080 || t.is_object()
1081 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1082 })
1083 }
1084
1085 pub fn narrow_to_countable(&self) -> Type {
1088 self.filter(|t| {
1089 t.is_array()
1090 || t.is_object()
1091 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1092 })
1093 }
1094
1095 pub fn narrow_to_resource(&self) -> Type {
1099 self.filter(|t| matches!(t, Atomic::TMixed))
1101 }
1102
1103 pub fn narrow_to_class_string(&self) -> Type {
1108 let mut out = Type::empty();
1109 out.from_docblock = self.from_docblock;
1110 for t in &self.types {
1111 match t {
1112 Atomic::TClassString(_) => out.add_type(t.clone()),
1113 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1114 out.add_type(Atomic::TClassString(None));
1115 }
1116 _ => {}
1117 }
1118 }
1119 out
1120 }
1121
1122 pub fn narrow_to_interface_string(&self) -> Type {
1127 let mut out = Type::empty();
1128 out.from_docblock = self.from_docblock;
1129 for t in &self.types {
1130 match t {
1131 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
1132 Atomic::TClassString(name) => {
1136 out.add_type(Atomic::TInterfaceString(*name));
1137 }
1138 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1139 out.add_type(Atomic::TInterfaceString(None));
1140 }
1141 _ => {}
1142 }
1143 }
1144 out
1145 }
1146
1147 pub fn merge(a: &Type, b: &Type) -> Type {
1152 if b.types.is_empty() {
1154 let mut result = a.clone();
1155 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1156 return result;
1157 }
1158 if a.types.is_empty() {
1160 let mut result = b.clone();
1161 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1162 return result;
1163 }
1164 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
1166 let mut result = a.clone();
1167 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1168 return result;
1169 }
1170 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1172 return Type {
1173 types: smallvec::smallvec![Atomic::TMixed],
1174 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
1175 from_docblock: a.from_docblock || b.from_docblock,
1176 falsy_stripped: false,
1177 possibly_absent_offset: false,
1178 };
1179 }
1180 let mut result = a.clone();
1181 result.merge_with(b);
1182 result
1183 }
1184
1185 pub fn merge_with(&mut self, other: &Type) {
1187 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1188 self.possibly_undefined |= other.possibly_undefined;
1189 return;
1190 }
1191 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1192 self.types.clear();
1193 self.types.push(Atomic::TMixed);
1194 self.possibly_undefined |= other.possibly_undefined;
1195 return;
1196 }
1197 for atomic in &other.types {
1198 self.add_type(atomic.clone());
1199 }
1200 self.possibly_undefined |= other.possibly_undefined;
1201 }
1202
1203 pub fn intersect_with(&self, other: &Type) -> Type {
1207 if self.is_mixed() {
1208 return other.clone();
1209 }
1210 if other.is_mixed() {
1211 return self.clone();
1212 }
1213 let mut result = Type::empty();
1221 for a in &self.types {
1222 for b in &other.types {
1223 if a == b {
1224 result.add_type(a.clone());
1225 } else if atomic_subtype(b, a) {
1226 result.add_type(b.clone());
1227 } else if atomic_subtype(a, b) {
1228 result.add_type(a.clone());
1229 }
1230 }
1231 }
1232 if result.is_empty() {
1233 Type::never()
1234 } else {
1235 result
1236 }
1237 }
1238
1239 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1243 if bindings.is_empty() {
1244 return self.clone();
1245 }
1246 if !self.types.iter().any(atomic_may_contain_templates) {
1249 return self.clone();
1250 }
1251 let mut result = Type::empty();
1252 result.possibly_undefined = self.possibly_undefined;
1253 result.from_docblock = self.from_docblock;
1254 for atomic in &self.types {
1255 match atomic {
1256 Atomic::TTemplateParam { name, .. } => {
1257 if let Some(resolved) = bindings.get(name) {
1258 for t in &resolved.types {
1259 result.add_type(t.clone());
1260 }
1261 } else {
1262 result.add_type(atomic.clone());
1263 }
1264 }
1265 Atomic::TArray { key, value } => {
1266 result.add_type(Atomic::TArray {
1267 key: Box::new(key.substitute_templates(bindings)),
1268 value: Box::new(value.substitute_templates(bindings)),
1269 });
1270 }
1271 Atomic::TList { value } => {
1272 result.add_type(Atomic::TList {
1273 value: Box::new(value.substitute_templates(bindings)),
1274 });
1275 }
1276 Atomic::TNonEmptyArray { key, value } => {
1277 result.add_type(Atomic::TNonEmptyArray {
1278 key: Box::new(key.substitute_templates(bindings)),
1279 value: Box::new(value.substitute_templates(bindings)),
1280 });
1281 }
1282 Atomic::TNonEmptyList { value } => {
1283 result.add_type(Atomic::TNonEmptyList {
1284 value: Box::new(value.substitute_templates(bindings)),
1285 });
1286 }
1287 Atomic::TKeyedArray {
1288 properties,
1289 is_open,
1290 is_list,
1291 } => {
1292 use crate::atomic::KeyedProperty;
1293 let new_props = properties
1294 .iter()
1295 .map(|(k, prop)| {
1296 (
1297 k.clone(),
1298 KeyedProperty {
1299 ty: prop.ty.substitute_templates(bindings),
1300 optional: prop.optional,
1301 },
1302 )
1303 })
1304 .collect();
1305 result.add_type(Atomic::TKeyedArray {
1306 properties: Box::new(new_props),
1307 is_open: *is_open,
1308 is_list: *is_list,
1309 });
1310 }
1311 Atomic::TCallable {
1312 params,
1313 return_type,
1314 is_pure,
1315 } => {
1316 result.add_type(Atomic::TCallable {
1317 params: params.as_ref().map(|ps| {
1318 Box::new(
1319 ps.iter()
1320 .map(|p| substitute_in_fn_param(p, bindings))
1321 .collect(),
1322 )
1323 }),
1324 return_type: return_type
1325 .as_ref()
1326 .map(|r| Box::new(r.substitute_templates(bindings))),
1327 is_pure: *is_pure,
1328 });
1329 }
1330 Atomic::TClosure { data } => {
1331 result.add_type(Atomic::TClosure {
1332 data: Box::new(crate::atomic::ClosureData {
1333 params: data
1334 .params
1335 .iter()
1336 .map(|p| substitute_in_fn_param(p, bindings))
1337 .collect(),
1338 return_type: data.return_type.substitute_templates(bindings),
1339 this_type: data
1340 .this_type
1341 .as_ref()
1342 .map(|t| t.substitute_templates(bindings)),
1343 is_pure: data.is_pure,
1344 is_static: data.is_static,
1345 rejects_surplus_args: data.rejects_surplus_args,
1346 }),
1347 });
1348 }
1349 Atomic::TConditional { data } => {
1350 let param_name = &data.param_name;
1351 let new_subject = data.subject.substitute_templates(bindings);
1352 let new_if_true = data.if_true.substitute_templates(bindings);
1353 let new_if_false = data.if_false.substitute_templates(bindings);
1354
1355 let resolved = if let Some(name) = param_name {
1359 if let Some(bound) = bindings.get(name) {
1360 if new_subject.types.len() == 1 {
1361 resolve_conditional_branch(
1362 &new_subject.types[0],
1363 bound,
1364 &new_if_true,
1365 &new_if_false,
1366 )
1367 } else {
1368 None
1369 }
1370 } else {
1371 None
1372 }
1373 } else {
1374 None
1375 };
1376
1377 if let Some(branch) = resolved {
1378 for t in branch.types {
1379 result.add_type(t);
1380 }
1381 } else {
1382 result.add_type(Atomic::TConditional {
1383 data: Box::new(crate::atomic::ConditionalData {
1384 param_name: *param_name,
1385 subject: new_subject,
1386 if_true: new_if_true,
1387 if_false: new_if_false,
1388 }),
1389 });
1390 }
1391 }
1392 Atomic::TKeyOf { target } => {
1393 let new_target = target.substitute_templates(bindings);
1394 if let Some(resolved) = eval_key_of_type(&new_target) {
1395 result.merge_with(&resolved);
1396 } else {
1397 result.add_type(Atomic::TKeyOf {
1398 target: Box::new(new_target),
1399 });
1400 }
1401 }
1402 Atomic::TValueOf { target } => {
1403 let new_target = target.substitute_templates(bindings);
1404 if let Some(resolved) = eval_value_of_type(&new_target) {
1405 result.merge_with(&resolved);
1406 } else {
1407 result.add_type(Atomic::TValueOf {
1408 target: Box::new(new_target),
1409 });
1410 }
1411 }
1412 Atomic::TIntersection { parts } => {
1413 result.add_type(Atomic::TIntersection {
1414 parts: vec_to_type_params(
1415 parts
1416 .iter()
1417 .map(|p| p.substitute_templates(bindings))
1418 .collect(),
1419 ),
1420 });
1421 }
1422 Atomic::TNamedObject { fqcn, type_params } => {
1423 if type_params.is_empty() && !fqcn.contains('\\') {
1425 if let Some(resolved) = bindings.get(fqcn) {
1426 for t in &resolved.types {
1427 result.add_type(t.clone());
1428 }
1429 continue;
1430 }
1431 }
1432 let new_params: Vec<Type> = type_params
1433 .iter()
1434 .map(|p| p.substitute_templates(bindings))
1435 .collect();
1436 result.add_type(Atomic::TNamedObject {
1437 fqcn: *fqcn,
1438 type_params: vec_to_type_params(new_params),
1439 });
1440 }
1441 Atomic::TClassString(Some(param_name)) => {
1443 if let Some(resolved) = bindings.get(param_name) {
1444 for r_atomic in &resolved.types {
1445 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1446 Some(*fqcn)
1447 } else {
1448 None
1449 };
1450 result.add_type(Atomic::TClassString(cls_name));
1451 }
1452 } else {
1453 result.add_type(atomic.clone());
1454 }
1455 }
1456 Atomic::TInterfaceString(Some(param_name)) => {
1458 if let Some(resolved) = bindings.get(param_name) {
1459 for r_atomic in &resolved.types {
1460 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1461 Some(*fqcn)
1462 } else {
1463 None
1464 };
1465 result.add_type(Atomic::TInterfaceString(iface_name));
1466 }
1467 } else {
1468 result.add_type(atomic.clone());
1469 }
1470 }
1471 _ => {
1472 result.add_type(atomic.clone());
1473 }
1474 }
1475 }
1476 result
1477 }
1478
1479 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1485 where
1486 F: Fn(&str) -> Option<Type>,
1487 {
1488 self.resolve_conditional_inner(&lookup)
1489 }
1490
1491 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1492 where
1493 F: Fn(&str) -> Option<Type>,
1494 {
1495 let mut result = Type::empty();
1496 for atomic in self.types {
1497 match atomic {
1498 Atomic::TConditional { ref data } => {
1499 let (param_name, subject, if_true, if_false) = (
1500 &data.param_name,
1501 &data.subject,
1502 &data.if_true,
1503 &data.if_false,
1504 );
1505 let resolved = if subject.types.len() == 1 {
1506 if let Some(name) = param_name {
1507 if let Some(arg_ty) = lookup(name.as_ref()) {
1508 resolve_conditional_branch(
1509 &subject.types[0],
1510 &arg_ty,
1511 if_true,
1512 if_false,
1513 )
1514 } else {
1515 None
1516 }
1517 } else {
1518 None
1519 }
1520 } else {
1521 None
1522 };
1523
1524 if let Some(branch) = resolved {
1525 for t in branch.resolve_conditional_inner(lookup).types {
1527 result.add_type(t);
1528 }
1529 } else {
1530 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1533 result.add_type(t);
1534 }
1535 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1536 result.add_type(t);
1537 }
1538 }
1539 }
1540 other => result.add_type(other),
1541 }
1542 }
1543 result
1544 }
1545
1546 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1556 if other.is_mixed() {
1557 return true;
1558 }
1559 if self.is_never() {
1560 return true; }
1562 self.types.iter().all(|a| {
1563 let fits = |a: &Atomic| other.types.iter().any(|b| atomic_subtype(a, b));
1564 fits(a) || composite_parts(a).is_some_and(|parts| parts.iter().all(fits))
1565 })
1566 }
1567
1568 pub fn is_subtype_by_template_bounds(&self, other: &Type) -> bool {
1571 self.is_subtype_structural(other)
1572 || self.types.iter().all(|a| match a {
1573 Atomic::TTemplateParam { as_type, .. } => {
1574 as_type.is_subtype_by_template_bounds(other)
1575 }
1576 _ => other.accepts_atomic_structural(a),
1577 })
1578 }
1579
1580 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1584 if self.is_mixed() {
1585 return true;
1586 }
1587 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1588 }
1589
1590 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1593 let mut result = Type::empty();
1594 result.possibly_undefined = self.possibly_undefined;
1595 result.from_docblock = self.from_docblock;
1596 for atomic in &self.types {
1597 if f(atomic) {
1598 result.types.push(atomic.clone());
1599 }
1600 }
1601 result
1602 }
1603
1604 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1609 &self,
1610 keep: K,
1611 placeholder: P,
1612 replacement: Atomic,
1613 ) -> Type {
1614 let mut result = Type::empty();
1615 result.possibly_undefined = self.possibly_undefined;
1616 result.from_docblock = self.from_docblock;
1617 for atomic in &self.types {
1618 if keep(atomic) {
1619 result.add_type(atomic.clone());
1620 } else if placeholder(atomic) {
1621 result.add_type(replacement.clone());
1622 }
1623 }
1624 result
1625 }
1626
1627 pub fn possibly_undefined(mut self) -> Self {
1629 self.possibly_undefined = true;
1630 self
1631 }
1632
1633 pub fn from_docblock(mut self) -> Self {
1635 self.from_docblock = true;
1636 self
1637 }
1638
1639 pub fn falsy_stripped(mut self) -> Self {
1642 self.falsy_stripped = true;
1643 self
1644 }
1645
1646 pub fn possibly_absent_offset(mut self) -> Self {
1650 self.possibly_absent_offset = true;
1651 self
1652 }
1653}
1654
1655#[derive(Clone, PartialEq, Eq, Debug)]
1669enum ValueClass {
1670 Int,
1671 Float,
1672 True,
1673 False,
1674 String,
1675 Array,
1676 List,
1678 Object,
1679 Null,
1680 Top,
1683 LitInt(i64),
1686 LitFloat(i64, i64),
1688 LitString(Arc<str>),
1690}
1691
1692impl ValueClass {
1693 fn includes(&self, b: &Self) -> bool {
1698 self == b
1699 || matches!(
1700 (self, b),
1701 (Self::Top, _)
1702 | (Self::Int, Self::LitInt(_))
1703 | (Self::Float, Self::LitFloat(..))
1704 | (Self::String, Self::LitString(_))
1705 | (Self::Array, Self::List)
1706 )
1707 }
1708}
1709
1710fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1716 use ValueClass::*;
1717 match a {
1718 Atomic::TInt
1721 | Atomic::TIntRange { .. }
1722 | Atomic::TPositiveInt
1723 | Atomic::TNegativeInt
1724 | Atomic::TNonNegativeInt => vec![Int],
1725 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1726 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1728 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1729 vec![LitFloat(*int_bits, *frac_bits)]
1730 }
1731 Atomic::TBool => vec![True, False],
1733 Atomic::TTrue => vec![True],
1734 Atomic::TFalse => vec![False],
1735 Atomic::TString
1737 | Atomic::TNonEmptyString
1738 | Atomic::TNumericString
1739 | Atomic::TClassString(_)
1740 | Atomic::TInterfaceString(_)
1741 | Atomic::TEnumString
1742 | Atomic::TTraitString
1743 | Atomic::TCallableString => vec![String],
1744 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1745 Atomic::TArray { .. }
1747 | Atomic::TNonEmptyArray { .. }
1748 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1749 Atomic::TList { .. }
1750 | Atomic::TNonEmptyList { .. }
1751 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1752 Atomic::TObject
1754 | Atomic::TNamedObject { .. }
1755 | Atomic::TStaticObject { .. }
1756 | Atomic::TSelf { .. }
1757 | Atomic::TParent { .. }
1758 | Atomic::TClosure { .. }
1759 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1760 Atomic::TNull => vec![Null],
1763 Atomic::TVoid => vec![Top],
1764 Atomic::TMixed => vec![Top],
1766 Atomic::TScalar => vec![Int, Float, True, False, String],
1767 Atomic::TNumeric => vec![Int, Float],
1770 Atomic::TCallable { .. }
1772 | Atomic::TNever
1773 | Atomic::TTemplateParam { .. }
1774 | Atomic::TKeyOf { .. }
1775 | Atomic::TValueOf { .. }
1776 | Atomic::TConditional { .. }
1777 | Atomic::TIntersection { .. } => Vec::new(),
1778 }
1779}
1780
1781fn subject_is_decidable(subject: &Atomic) -> bool {
1789 matches!(
1790 subject,
1791 Atomic::TNull
1792 | Atomic::TTrue
1793 | Atomic::TFalse
1794 | Atomic::TBool
1795 | Atomic::TString
1796 | Atomic::TInt
1797 | Atomic::TFloat
1798 | Atomic::TLiteralString(_)
1799 | Atomic::TLiteralInt(_)
1800 | Atomic::TLiteralFloat(..)
1801 | Atomic::TArray { .. }
1802 | Atomic::TList { .. }
1803 | Atomic::TObject
1804 | Atomic::TMixed
1805 | Atomic::TScalar
1806 )
1807}
1808
1809fn resolve_conditional_branch(
1831 subject: &Atomic,
1832 arg_ty: &Type,
1833 if_true: &Type,
1834 if_false: &Type,
1835) -> Option<Type> {
1836 if !subject_is_decidable(subject) {
1837 return None;
1838 }
1839 if arg_ty.types.is_empty() {
1840 return None;
1841 }
1842 let subject_classes = value_classes(subject);
1843 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1844 if arg_classes.is_empty() {
1845 return None;
1847 }
1848 let all_match = arg_classes
1849 .iter()
1850 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1851 let any_overlap = arg_classes.iter().any(|c| {
1852 subject_classes
1853 .iter()
1854 .any(|s| s.includes(c) || c.includes(s))
1855 });
1856 if all_match {
1857 Some(if_true.clone())
1858 } else if !any_overlap {
1859 Some(if_false.clone())
1860 } else {
1861 None
1862 }
1863}
1864
1865fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1873 match atomic {
1874 Atomic::TNamedObject { fqcn, type_params } => {
1878 !type_params.is_empty() || !fqcn.contains('\\')
1879 }
1880 Atomic::TTemplateParam { .. }
1881 | Atomic::TKeyOf { .. }
1882 | Atomic::TValueOf { .. }
1883 | Atomic::TArray { .. }
1884 | Atomic::TList { .. }
1885 | Atomic::TNonEmptyArray { .. }
1886 | Atomic::TNonEmptyList { .. }
1887 | Atomic::TKeyedArray { .. }
1888 | Atomic::TCallable { .. }
1889 | Atomic::TClosure { .. }
1890 | Atomic::TConditional { .. }
1891 | Atomic::TIntersection { .. }
1892 | Atomic::TClassString(Some(_))
1893 | Atomic::TInterfaceString(Some(_)) => true,
1894 _ => false,
1895 }
1896}
1897
1898pub fn eval_key_of_type(t: &Type) -> Option<Type> {
1899 let mut result = Type::empty();
1900 for atomic in &t.types {
1901 match atomic {
1902 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1903 for k in &key.types {
1904 result.add_type(k.clone());
1905 }
1906 }
1907 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1908 result.add_type(Atomic::TInt);
1909 }
1910 Atomic::TKeyedArray { properties, .. } => {
1911 for key in properties.keys() {
1912 match key {
1913 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1914 crate::atomic::ArrayKey::String(s) => {
1915 result.add_type(Atomic::TLiteralString(s.clone()))
1916 }
1917 }
1918 }
1919 }
1920 _ => return None,
1921 }
1922 }
1923 (!result.types.is_empty()).then_some(result)
1924}
1925
1926pub fn eval_value_of_type(t: &Type) -> Option<Type> {
1927 let mut result = Type::empty();
1928 for atomic in &t.types {
1929 match atomic {
1930 Atomic::TArray { value, .. }
1931 | Atomic::TNonEmptyArray { value, .. }
1932 | Atomic::TList { value }
1933 | Atomic::TNonEmptyList { value } => {
1934 result.merge_with(value);
1935 }
1936 Atomic::TKeyedArray { properties, .. } => {
1937 for prop in properties.values() {
1938 result.merge_with(&prop.ty);
1939 }
1940 }
1941 _ => return None,
1942 }
1943 }
1944 (!result.types.is_empty()).then_some(result)
1945}
1946
1947fn substitute_in_fn_param(
1948 p: &crate::atomic::FnParam,
1949 bindings: &FxHashMap<Name, Type>,
1950) -> crate::atomic::FnParam {
1951 crate::atomic::FnParam {
1952 name: p.name,
1953 ty: p.ty.as_ref().map(|t| {
1954 let u = t.to_union();
1955 let substituted = u.substitute_templates(bindings);
1956 crate::compact::SimpleType::from_union(substituted)
1957 }),
1958 out_ty: p.out_ty.as_ref().map(|t| {
1959 let u = t.to_union();
1960 let substituted = u.substitute_templates(bindings);
1961 crate::compact::SimpleType::from_union(substituted)
1962 }),
1963 default: p.default.as_ref().map(|d| {
1964 let u = d.to_union();
1965 let substituted = u.substitute_templates(bindings);
1966 crate::compact::SimpleType::from_union(substituted)
1967 }),
1968 is_variadic: p.is_variadic,
1969 is_byref: p.is_byref,
1970 is_optional: p.is_optional,
1971 }
1972}
1973
1974fn composite_parts(atomic: &Atomic) -> Option<&'static [Atomic]> {
1981 match atomic {
1982 Atomic::TScalar => Some(&[Atomic::TInt, Atomic::TFloat, Atomic::TString, Atomic::TBool]),
1983 Atomic::TNumeric => Some(&[Atomic::TInt, Atomic::TFloat, Atomic::TNumericString]),
1984 _ => None,
1985 }
1986}
1987
1988pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1990 if sub == sup {
1991 return true;
1992 }
1993 match (sub, sup) {
1994 (Atomic::TNever, _) => true,
1996 (_, Atomic::TMixed) => true,
1998 (Atomic::TMixed, _) => true,
1999 (_, Atomic::TTemplateParam { as_type, .. }) => {
2004 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
2005 }
2006
2007 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
2009 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
2010 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
2011 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
2012 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
2013 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
2014 (Atomic::TPositiveInt, Atomic::TInt) => true,
2015 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
2016 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
2017 (Atomic::TPositiveInt, Atomic::TScalar) => true,
2018 (Atomic::TNegativeInt, Atomic::TInt) => true,
2019 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
2020 (Atomic::TNegativeInt, Atomic::TScalar) => true,
2021 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
2022 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
2023 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
2024 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
2025 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
2026 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
2027 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
2029 max.is_none() && min.is_none_or(|m| m <= 1)
2030 }
2031 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
2033 min.is_none() && max.is_none_or(|m| m >= -1)
2034 }
2035 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
2037 max.is_none() && min.is_none_or(|m| m <= 0)
2038 }
2039 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
2041 sub_min.is_some_and(|lo| lo >= 1)
2042 }
2043 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
2044 sub_min.is_some_and(|lo| lo >= 0)
2045 }
2046 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
2047 sub_max.is_some_and(|hi| hi <= -1)
2048 }
2049 (
2051 Atomic::TIntRange {
2052 min: sub_min,
2053 max: sub_max,
2054 },
2055 Atomic::TIntRange {
2056 min: sup_min,
2057 max: sup_max,
2058 },
2059 ) => {
2060 let lower_ok = match (sub_min, sup_min) {
2061 (_, None) => true,
2062 (None, Some(_)) => false,
2063 (Some(sl), Some(su)) => sl >= su,
2064 };
2065 let upper_ok = match (sub_max, sup_max) {
2066 (None, None) | (Some(_), None) => true,
2067 (None, Some(_)) => false,
2068 (Some(sl), Some(su)) => sl <= su,
2069 };
2070 lower_ok && upper_ok
2071 }
2072
2073 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
2074 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
2075 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
2076
2077 (Atomic::TLiteralString(s), Atomic::TString) => {
2078 let _ = s;
2079 true
2080 }
2081 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
2082 let _ = s;
2083 true
2084 }
2085 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
2086 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
2087 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
2090 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
2093 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
2094 (Atomic::TNonEmptyString, Atomic::TString) => true,
2095 (Atomic::TCallableString, Atomic::TString) => true,
2096 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
2098 (Atomic::TNumericString, Atomic::TString) => true,
2099 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
2103 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
2104 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
2105 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
2106 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
2107 (Atomic::TClassString(_), Atomic::TString) => true,
2108 (Atomic::TInterfaceString(_), Atomic::TString) => true,
2109 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
2114 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
2115 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
2116 (Atomic::TEnumString, Atomic::TString) => true,
2117 (Atomic::TTraitString, Atomic::TString) => true,
2118
2119 (Atomic::TTrue, Atomic::TBool) => true,
2120 (Atomic::TFalse, Atomic::TBool) => true,
2121
2122 (Atomic::TInt, Atomic::TNumeric) => true,
2123 (Atomic::TFloat, Atomic::TNumeric) => true,
2124 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
2125 (Atomic::TNumericString, Atomic::TNumeric) => true,
2126
2127 (Atomic::TInt, Atomic::TScalar) => true,
2128 (Atomic::TFloat, Atomic::TScalar) => true,
2129 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
2130 (Atomic::TString, Atomic::TScalar) => true,
2131 (Atomic::TBool, Atomic::TScalar) => true,
2132 (Atomic::TNumeric, Atomic::TScalar) => true,
2133 (Atomic::TTrue, Atomic::TScalar) => true,
2134 (Atomic::TFalse, Atomic::TScalar) => true,
2135 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
2139 (Atomic::TNumericString, Atomic::TScalar) => true,
2140 (Atomic::TCallableString, Atomic::TScalar) => true,
2141 (Atomic::TClassString(_), Atomic::TScalar) => true,
2142 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
2143 (Atomic::TEnumString, Atomic::TScalar) => true,
2144 (Atomic::TTraitString, Atomic::TScalar) => true,
2145
2146 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
2148 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
2149 (Atomic::TSelf { .. }, Atomic::TObject) => true,
2150 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
2152 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2154 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2155 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
2157 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
2158 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
2161 enum_fqcn == fqcn
2162 }
2163 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TSelf { fqcn })
2164 | (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TStaticObject { fqcn }) => {
2165 enum_fqcn == fqcn
2166 }
2167 (
2171 Atomic::TNamedObject {
2172 fqcn: sub_fqcn,
2173 type_params: sub_params,
2174 },
2175 Atomic::TNamedObject {
2176 fqcn: sup_fqcn,
2177 type_params: sup_params,
2178 },
2179 ) => {
2180 sub_fqcn == sup_fqcn
2181 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
2182 }
2183
2184 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
2186
2187 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
2189 (Atomic::TPositiveInt, Atomic::TFloat) => true,
2190 (Atomic::TNegativeInt, Atomic::TFloat) => true,
2191 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
2192 (Atomic::TInt, Atomic::TFloat) => true,
2193 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
2194
2195 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
2197 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
2198 }
2199
2200 (Atomic::TString, Atomic::TCallable { .. }) => true,
2202 (Atomic::TCallableString, Atomic::TCallable { .. }) => true,
2203 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
2204 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
2205 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
2206 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
2207 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
2208
2209 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
2211 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
2213 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
2223 fn has_nominal_type(t: &Type) -> bool {
2224 t.types.iter().any(|a| {
2225 matches!(
2226 a,
2227 Atomic::TNamedObject { .. }
2228 | Atomic::TSelf { .. }
2229 | Atomic::TStaticObject { .. }
2230 | Atomic::TTemplateParam { .. }
2231 | Atomic::TClosure { .. }
2232 | Atomic::TCallable { .. }
2233 )
2234 })
2235 }
2236 let sub_required = sub
2237 .params
2238 .iter()
2239 .filter(|p| !p.is_optional && !p.is_variadic)
2240 .count();
2241 if sub_required > sup.params.len() {
2242 false
2243 } else {
2244 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2245 let Some(sub_param) = sub.params.get(i) else {
2246 return true;
2247 };
2248 if sub_param.is_optional || sub_param.is_variadic {
2249 return true;
2250 }
2251 let (Some(sub_ty), Some(sup_ty)) =
2252 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2253 else {
2254 return true;
2255 };
2256 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2257 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2258 return true;
2259 }
2260 sup_u.is_subtype_structural(&sub_u)
2263 });
2264 params_ok
2265 && (sub.return_type.is_mixed()
2266 || sup.return_type.is_mixed()
2267 || sup.return_type.is_void()
2271 || has_nominal_type(&sub.return_type)
2272 || has_nominal_type(&sup.return_type)
2273 || sub.return_type.is_subtype_structural(&sup.return_type))
2274 }
2275 }
2276 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2278 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2280 fqcn.as_ref().eq_ignore_ascii_case("closure")
2281 }
2282 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2283 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2285 fqcn.as_ref().eq_ignore_ascii_case("closure")
2286 }
2287 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2289 fqcn.as_ref().eq_ignore_ascii_case("closure")
2290 }
2291
2292 (
2300 Atomic::TIntersection { parts: sub_parts },
2301 Atomic::TIntersection { parts: sup_parts },
2302 ) => sup_parts.iter().all(|sup_part| {
2303 sub_parts
2304 .iter()
2305 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2306 }),
2307 (Atomic::TIntersection { parts }, Atomic::TObject) => parts
2308 .iter()
2309 .any(|part| part.is_subtype_structural(&Type::single(Atomic::TObject))),
2310
2311 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2313 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2314 }
2315 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2316 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2317 }
2318 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2319 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2320 }
2321 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2322 value.is_subtype_structural(lv)
2323 }
2324 (Atomic::TNonEmptyList { value: v1 }, Atomic::TNonEmptyList { value: v2 }) => {
2325 v1.is_subtype_structural(v2)
2326 }
2327 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2329 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2330 && av.is_subtype_structural(lv)
2331 }
2332 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2333 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2334 && av.is_subtype_structural(lv)
2335 }
2336 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2337 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2338 && av.is_subtype_structural(lv)
2339 }
2340 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2341 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2342 && av.is_subtype_structural(lv)
2343 }
2344 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2346 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2347 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2348 }
2349
2350 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2352 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2353 }
2354
2355 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2364 properties.iter().all(|(prop_key, prop)| {
2365 let key_atomic = match prop_key {
2366 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2367 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2368 };
2369 if !Type::single(key_atomic).is_subtype_structural(key) {
2370 return false; }
2372 let has_named_obj = prop.ty.types.iter().any(|a| {
2374 matches!(
2375 a,
2376 Atomic::TNamedObject { .. }
2377 | Atomic::TSelf { .. }
2378 | Atomic::TStaticObject { .. }
2379 | Atomic::TClosure { .. }
2380 | Atomic::TTemplateParam { .. }
2381 )
2382 });
2383 has_named_obj || prop.ty.is_subtype_structural(value)
2384 })
2385 }
2386 (
2387 Atomic::TKeyedArray {
2388 properties,
2389 is_open,
2390 ..
2391 },
2392 Atomic::TNonEmptyArray { key, value },
2393 ) => {
2394 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2395 && properties.iter().all(|(prop_key, prop)| {
2396 let key_atomic = match prop_key {
2397 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2398 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2399 };
2400 if !Type::single(key_atomic).is_subtype_structural(key) {
2401 return false;
2402 }
2403 let has_named_obj = prop.ty.types.iter().any(|a| {
2404 matches!(
2405 a,
2406 Atomic::TNamedObject { .. }
2407 | Atomic::TSelf { .. }
2408 | Atomic::TStaticObject { .. }
2409 | Atomic::TClosure { .. }
2410 | Atomic::TTemplateParam { .. }
2411 )
2412 });
2413 has_named_obj || prop.ty.is_subtype_structural(value)
2414 })
2415 }
2416
2417 (
2419 Atomic::TKeyedArray {
2420 properties,
2421 is_list,
2422 ..
2423 },
2424 Atomic::TList { value: lv },
2425 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2426 (
2427 Atomic::TKeyedArray {
2428 properties,
2429 is_list,
2430 ..
2431 },
2432 Atomic::TNonEmptyList { value: lv },
2433 ) => {
2434 *is_list
2435 && !properties.is_empty()
2436 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2437 }
2438
2439 (
2444 Atomic::TKeyedArray {
2445 properties: sub_props,
2446 is_open: sub_open,
2447 ..
2448 },
2449 Atomic::TKeyedArray {
2450 properties: sup_props,
2451 is_open: sup_open,
2452 ..
2453 },
2454 ) => {
2455 let keys_satisfied = sup_props
2456 .iter()
2457 .all(|(key, sup_prop)| match sub_props.get(key) {
2458 Some(sub_prop) => {
2459 if !sup_prop.optional && sub_prop.optional {
2463 return false;
2464 }
2465 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2466 matches!(
2467 a,
2468 Atomic::TNamedObject { .. }
2469 | Atomic::TSelf { .. }
2470 | Atomic::TStaticObject { .. }
2471 | Atomic::TClosure { .. }
2472 | Atomic::TTemplateParam { .. }
2473 )
2474 });
2475 has_named_obj || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2476 }
2477 None => sup_prop.optional || *sub_open,
2478 });
2479 let no_undeclared_extras =
2480 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2481 keys_satisfied && no_undeclared_extras
2482 }
2483 (
2488 Atomic::TKeyedArray {
2489 properties,
2490 is_open,
2491 ..
2492 },
2493 Atomic::TIntersection { parts },
2494 ) => parts.iter().all(|part| {
2495 part.types.iter().any(|part_atomic| match part_atomic {
2496 Atomic::TKeyedArray {
2497 properties: sup_props,
2498 is_open: sup_open,
2499 ..
2500 } => {
2501 let keys_satisfied =
2502 sup_props
2503 .iter()
2504 .all(|(key, sup_prop)| match properties.get(key) {
2505 Some(sub_prop) => {
2506 if !sup_prop.optional && sub_prop.optional {
2507 return false;
2508 }
2509 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2510 matches!(
2511 a,
2512 Atomic::TNamedObject { .. }
2513 | Atomic::TSelf { .. }
2514 | Atomic::TStaticObject { .. }
2515 | Atomic::TClosure { .. }
2516 | Atomic::TTemplateParam { .. }
2517 )
2518 });
2519 has_named_obj
2520 || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2521 || bare_array_may_fit(&sub_prop.ty, &sup_prop.ty)
2522 }
2523 None => *is_open || sup_prop.optional,
2524 });
2525 let has_array_part = parts.iter().any(|part| {
2526 part.types
2527 .iter()
2528 .any(|t| matches!(t, Atomic::TArray { .. }))
2529 });
2530 let has_keyed_array_part = parts.iter().any(|part| {
2531 part.types
2532 .iter()
2533 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2534 });
2535 let keys_allowed_by_some_part = if has_array_part {
2536 true
2537 } else if has_keyed_array_part {
2538 properties.keys().all(|k| {
2539 parts.iter().any(|part| {
2540 part.types.iter().any(|t| {
2541 if let Atomic::TKeyedArray {
2542 properties: part_props,
2543 ..
2544 } = t
2545 {
2546 part_props.contains_key(k)
2547 } else {
2548 false
2549 }
2550 })
2551 })
2552 })
2553 } else {
2554 true
2555 };
2556 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2557 keys_satisfied && no_undeclared_extras
2558 }
2559 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2560 let key_atomic = match prop_key {
2561 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2562 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2563 };
2564 if !Type::single(key_atomic).is_subtype_structural(key) {
2565 return false;
2566 }
2567 let has_named_obj = prop.ty.types.iter().any(|a| {
2568 matches!(
2569 a,
2570 Atomic::TNamedObject { .. }
2571 | Atomic::TSelf { .. }
2572 | Atomic::TStaticObject { .. }
2573 | Atomic::TClosure { .. }
2574 | Atomic::TTemplateParam { .. }
2575 )
2576 });
2577 has_named_obj || prop.ty.is_subtype_structural(value)
2578 }),
2579 Atomic::TNamedObject { .. }
2580 | Atomic::TSelf { .. }
2581 | Atomic::TStaticObject { .. }
2582 | Atomic::TClosure { .. }
2583 | Atomic::TTemplateParam { .. } => true,
2584 _ => false,
2585 })
2586 }),
2587
2588 _ => false,
2589 }
2590}
2591
2592fn bare_array_may_fit(sub: &Type, sup: &Type) -> bool {
2594 fn array_like(t: &Atomic) -> bool {
2595 match t {
2596 Atomic::TArray { .. }
2597 | Atomic::TNonEmptyArray { .. }
2598 | Atomic::TList { .. }
2599 | Atomic::TNonEmptyList { .. }
2600 | Atomic::TKeyedArray { .. } => true,
2601 Atomic::TIntersection { parts } => parts.iter().all(|p| p.types.iter().all(array_like)),
2602 _ => false,
2603 }
2604 }
2605 matches!(
2606 sub.types.as_slice(),
2607 [Atomic::TArray { key, value }]
2608 if value.is_mixed()
2609 && (key.is_mixed()
2610 || matches!(key.types.as_slice(), [Atomic::TInt, Atomic::TString] | [Atomic::TString, Atomic::TInt]))
2611 ) && sup.types.iter().all(array_like)
2612}
2613
2614fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2620 if sub.len() != sup.len() {
2621 return false;
2622 }
2623 sub.iter()
2624 .zip(sup.iter())
2625 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2626}
2627
2628fn is_empty_array_literal(t: &Type) -> bool {
2631 !t.types.is_empty()
2632 && t.types.iter().all(
2633 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2634 )
2635}
2636
2637fn is_array_like(t: &Type) -> bool {
2639 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2640}
2641
2642pub struct UnionBuilder {
2649 ty: Type,
2650 strings: std::collections::HashSet<std::sync::Arc<str>>,
2651 ints: std::collections::HashSet<i64>,
2652 literals_independent: bool,
2654}
2655
2656impl Default for UnionBuilder {
2657 fn default() -> Self {
2658 Self::new()
2659 }
2660}
2661
2662impl UnionBuilder {
2663 pub fn new() -> Self {
2664 Self {
2665 ty: Type::empty(),
2666 strings: Default::default(),
2667 ints: Default::default(),
2668 literals_independent: true,
2669 }
2670 }
2671
2672 pub fn is_empty(&self) -> bool {
2673 self.ty.is_empty()
2674 }
2675
2676 pub fn merge(&mut self, other: &Type) {
2677 for a in &other.types {
2678 self.add(a.clone());
2679 }
2680 self.ty.possibly_undefined |= other.possibly_undefined;
2681 }
2682
2683 pub fn add(&mut self, atomic: Atomic) {
2684 let lone_never = matches!(self.ty.types.first(), Some(Atomic::TNever));
2686 if self.literals_independent && !lone_never {
2687 match &atomic {
2688 Atomic::TLiteralString(s) => {
2689 if self.strings.insert(s.clone()) {
2690 self.ty.types.push(atomic);
2691 }
2692 return;
2693 }
2694 Atomic::TLiteralInt(n) => {
2695 if self.ints.insert(*n) {
2696 self.ty.types.push(atomic);
2697 }
2698 return;
2699 }
2700 _ => {}
2701 }
2702 }
2703 let disturbs_literals = matches!(
2704 atomic,
2705 Atomic::TString | Atomic::TMixed | Atomic::TConditional { .. }
2706 ) || int_bounds(&atomic).is_some();
2707 self.ty.add_type(atomic);
2708 if disturbs_literals || lone_never {
2709 self.resync();
2710 }
2711 }
2712
2713 fn resync(&mut self) {
2714 self.strings.clear();
2715 self.ints.clear();
2716 self.literals_independent = true;
2717 for t in &self.ty.types {
2718 match t {
2719 Atomic::TLiteralString(s) => {
2720 self.strings.insert(s.clone());
2721 }
2722 Atomic::TLiteralInt(n) => {
2723 self.ints.insert(*n);
2724 }
2725 Atomic::TMixed | Atomic::TString => self.literals_independent = false,
2726 other if int_bounds(other).is_some() => self.literals_independent = false,
2727 _ => {}
2728 }
2729 }
2730 }
2731
2732 pub fn build(self) -> Type {
2733 self.ty
2734 }
2735}
2736
2737fn callable_array() -> Atomic {
2739 let mut properties = IndexMap::with_capacity(2);
2740 properties.insert(
2741 ArrayKey::Int(0),
2742 KeyedProperty {
2743 ty: Type::from_vec(vec![Atomic::TClassString(None), Atomic::TObject]),
2744 optional: false,
2745 },
2746 );
2747 properties.insert(
2748 ArrayKey::Int(1),
2749 KeyedProperty {
2750 ty: Type::single(Atomic::TNonEmptyString),
2751 optional: false,
2752 },
2753 );
2754 Atomic::TKeyedArray {
2755 properties: Box::new(properties),
2756 is_open: false,
2757 is_list: true,
2758 }
2759}
2760
2761#[cfg(test)]
2762mod tests {
2763 use std::sync::Arc;
2764
2765 use super::*;
2766
2767 fn conditional(
2768 param_name: Option<Name>,
2769 subject: Type,
2770 if_true: Type,
2771 if_false: Type,
2772 ) -> Atomic {
2773 Atomic::TConditional {
2774 data: Box::new(crate::atomic::ConditionalData {
2775 param_name,
2776 subject,
2777 if_true,
2778 if_false,
2779 }),
2780 }
2781 }
2782
2783 #[test]
2784 fn single_is_single() {
2785 let u = Type::single(Atomic::TString);
2786 assert!(u.is_single());
2787 assert!(!u.is_nullable());
2788 }
2789
2790 #[test]
2791 fn nullable_has_null() {
2792 let u = Type::nullable(Atomic::TString);
2793 assert!(u.is_nullable());
2794 assert_eq!(u.types.len(), 2);
2795 }
2796
2797 #[test]
2798 fn add_type_deduplicates() {
2799 let mut u = Type::single(Atomic::TString);
2800 u.add_type(Atomic::TString);
2801 assert_eq!(u.types.len(), 1);
2802 }
2803
2804 #[test]
2805 fn array_key_is_int_string() {
2806 let k = Type::array_key();
2807 assert!(k.is_array_key());
2808 assert_eq!(k.types.len(), 2);
2809 }
2810
2811 #[test]
2812 fn is_array_key_false_for_plain_int() {
2813 assert!(!Type::int().is_array_key());
2814 }
2815
2816 #[test]
2817 fn is_array_key_false_for_mixed() {
2818 assert!(!Type::mixed().is_array_key());
2819 }
2820
2821 #[test]
2822 fn is_array_key_false_for_int_string_null() {
2823 let mut u = Type::array_key();
2824 u.add_type(Atomic::TNull);
2825 assert!(!u.is_array_key());
2826 }
2827
2828 #[test]
2829 fn add_type_literal_subsumed_by_base() {
2830 let mut u = Type::single(Atomic::TInt);
2831 u.add_type(Atomic::TLiteralInt(42));
2832 assert_eq!(u.types.len(), 1);
2833 assert!(matches!(u.types[0], Atomic::TInt));
2834 }
2835
2836 #[test]
2837 fn true_then_false_merges_to_bool() {
2838 let mut u = Type::single(Atomic::TTrue);
2839 u.add_type(Atomic::TFalse);
2840 assert_eq!(u.types.len(), 1);
2841 assert!(matches!(u.types[0], Atomic::TBool));
2842 }
2843
2844 #[test]
2845 fn false_then_true_merges_to_bool() {
2846 let mut u = Type::single(Atomic::TFalse);
2847 u.add_type(Atomic::TTrue);
2848 assert_eq!(u.types.len(), 1);
2849 assert!(matches!(u.types[0], Atomic::TBool));
2850 }
2851
2852 #[test]
2853 fn true_alone_stays_true() {
2854 let u = Type::single(Atomic::TTrue);
2855 assert_eq!(u.types.len(), 1);
2856 assert!(matches!(u.types[0], Atomic::TTrue));
2857 }
2858
2859 #[test]
2860 fn true_false_merge_preserves_other_union_members() {
2861 let mut u = Type::single(Atomic::TTrue);
2862 u.add_type(Atomic::TNull);
2863 u.add_type(Atomic::TFalse);
2864 assert_eq!(u.types.len(), 2);
2865 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2866 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2867 }
2868
2869 #[test]
2870 fn add_type_base_widens_literals() {
2871 let mut u = Type::single(Atomic::TLiteralInt(1));
2872 u.add_type(Atomic::TLiteralInt(2));
2873 u.add_type(Atomic::TInt);
2874 assert_eq!(u.types.len(), 1);
2875 assert!(matches!(u.types[0], Atomic::TInt));
2876 }
2877
2878 #[test]
2879 fn mixed_subsumes_everything() {
2880 let mut u = Type::single(Atomic::TString);
2881 u.add_type(Atomic::TMixed);
2882 assert_eq!(u.types.len(), 1);
2883 assert!(u.is_mixed());
2884 }
2885
2886 #[test]
2887 fn remove_null() {
2888 let u = Type::nullable(Atomic::TString);
2889 let narrowed = u.remove_null();
2890 assert!(!narrowed.is_nullable());
2891 assert_eq!(narrowed.types.len(), 1);
2892 }
2893
2894 #[test]
2895 fn narrow_to_truthy_removes_null_false() {
2896 let mut u = Type::empty();
2897 u.add_type(Atomic::TString);
2898 u.add_type(Atomic::TNull);
2899 u.add_type(Atomic::TFalse);
2900 let truthy = u.narrow_to_truthy();
2901 assert!(!truthy.is_nullable());
2902 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2903 }
2904
2905 #[test]
2906 fn merge_combines_types() {
2907 let a = Type::single(Atomic::TString);
2908 let b = Type::single(Atomic::TInt);
2909 let merged = Type::merge(&a, &b);
2910 assert_eq!(merged.types.len(), 2);
2911 }
2912
2913 #[test]
2914 fn intersect_keeps_narrower_side_not_self() {
2915 let int_ty = Type::single(Atomic::TInt);
2919 let mut literals = Type::empty();
2920 literals.add_type(Atomic::TLiteralInt(1));
2921 literals.add_type(Atomic::TLiteralInt(2));
2922
2923 let narrowed = int_ty.intersect_with(&literals);
2924 assert_eq!(narrowed.types.len(), 2);
2925 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2926 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2927 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2928 }
2929
2930 #[test]
2931 fn subtype_literal_int_under_int() {
2932 let sub = Type::single(Atomic::TLiteralInt(5));
2933 let sup = Type::single(Atomic::TInt);
2934 assert!(sub.is_subtype_structural(&sup));
2935 }
2936
2937 #[test]
2938 fn subtype_never_is_bottom() {
2939 let never = Type::never();
2940 let string = Type::single(Atomic::TString);
2941 assert!(never.is_subtype_structural(&string));
2942 }
2943
2944 #[test]
2945 fn subtype_everything_under_mixed() {
2946 let string = Type::single(Atomic::TString);
2947 let mixed = Type::mixed();
2948 assert!(string.is_subtype_structural(&mixed));
2949 }
2950
2951 #[test]
2952 fn subtype_enum_case_under_own_enum() {
2953 let sub = Type::single(Atomic::TLiteralEnumCase {
2954 enum_fqcn: Name::new("RoundingMode"),
2955 case_name: Name::new("Unnecessary"),
2956 });
2957 let sup = Type::single(Atomic::TNamedObject {
2958 fqcn: Name::new("RoundingMode"),
2959 type_params: empty_type_params(),
2960 });
2961 assert!(sub.is_subtype_structural(&sup));
2962 }
2963
2964 #[test]
2965 fn enum_case_not_subtype_of_unrelated_enum() {
2966 let sub = Type::single(Atomic::TLiteralEnumCase {
2967 enum_fqcn: Name::new("RoundingMode"),
2968 case_name: Name::new("Unnecessary"),
2969 });
2970 let sup = Type::single(Atomic::TNamedObject {
2971 fqcn: Name::new("Suit"),
2972 type_params: empty_type_params(),
2973 });
2974 assert!(!sub.is_subtype_structural(&sup));
2975 }
2976
2977 #[test]
2978 fn subtype_enum_case_under_own_self_and_static() {
2979 let case = |enum_fqcn: &str| {
2980 Type::single(Atomic::TLiteralEnumCase {
2981 enum_fqcn: Name::new(enum_fqcn),
2982 case_name: Name::new("A"),
2983 })
2984 };
2985 let own = Type::single(Atomic::TSelf {
2986 fqcn: Name::new("Suit"),
2987 });
2988 let own_static = Type::single(Atomic::TStaticObject {
2989 fqcn: Name::new("Suit"),
2990 });
2991 assert!(case("Suit").is_subtype_structural(&own));
2992 assert!(case("Suit").is_subtype_structural(&own_static));
2993 assert!(!case("Other").is_subtype_structural(&own));
2994 }
2995
2996 #[test]
2997 fn subtype_enum_case_under_bare_object() {
2998 let sub = Type::single(Atomic::TLiteralEnumCase {
2999 enum_fqcn: Name::new("RoundingMode"),
3000 case_name: Name::new("Unnecessary"),
3001 });
3002 let sup = Type::single(Atomic::TObject);
3003 assert!(sub.is_subtype_structural(&sup));
3004 }
3005
3006 #[test]
3007 fn template_substitution() {
3008 let mut bindings = FxHashMap::default();
3009 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
3010
3011 let tmpl = Type::single(Atomic::TTemplateParam {
3012 name: Name::new("T"),
3013 as_type: Box::new(Type::mixed()),
3014 defining_entity: Name::new("MyClass"),
3015 });
3016
3017 let resolved = tmpl.substitute_templates(&bindings);
3018 assert_eq!(resolved.types.len(), 1);
3019 assert!(matches!(resolved.types[0], Atomic::TString));
3020 }
3021
3022 #[test]
3023 fn intersection_is_object() {
3024 let parts = vec![
3025 Type::single(Atomic::TNamedObject {
3026 fqcn: Name::new("Iterator"),
3027 type_params: empty_type_params(),
3028 }),
3029 Type::single(Atomic::TNamedObject {
3030 fqcn: Name::new("Countable"),
3031 type_params: empty_type_params(),
3032 }),
3033 ];
3034 let atomic = Atomic::TIntersection {
3035 parts: vec_to_type_params(parts),
3036 };
3037 assert!(atomic.is_object());
3038 assert!(!atomic.can_be_falsy());
3039 assert!(atomic.can_be_truthy());
3040 }
3041
3042 #[test]
3043 fn intersection_display_two_parts() {
3044 let parts = vec![
3045 Type::single(Atomic::TNamedObject {
3046 fqcn: Name::new("Iterator"),
3047 type_params: empty_type_params(),
3048 }),
3049 Type::single(Atomic::TNamedObject {
3050 fqcn: Name::new("Countable"),
3051 type_params: empty_type_params(),
3052 }),
3053 ];
3054 let u = Type::single(Atomic::TIntersection {
3055 parts: vec_to_type_params(parts),
3056 });
3057 assert_eq!(format!("{u}"), "Iterator&Countable");
3058 }
3059
3060 #[test]
3061 fn intersection_display_three_parts() {
3062 let parts = vec![
3063 Type::single(Atomic::TNamedObject {
3064 fqcn: Name::new("A"),
3065 type_params: empty_type_params(),
3066 }),
3067 Type::single(Atomic::TNamedObject {
3068 fqcn: Name::new("B"),
3069 type_params: empty_type_params(),
3070 }),
3071 Type::single(Atomic::TNamedObject {
3072 fqcn: Name::new("C"),
3073 type_params: empty_type_params(),
3074 }),
3075 ];
3076 let u = Type::single(Atomic::TIntersection {
3077 parts: vec_to_type_params(parts),
3078 });
3079 assert_eq!(format!("{u}"), "A&B&C");
3080 }
3081
3082 #[test]
3083 fn intersection_in_nullable_union_display() {
3084 let intersection = Atomic::TIntersection {
3085 parts: vec_to_type_params(vec![
3086 Type::single(Atomic::TNamedObject {
3087 fqcn: Name::new("Iterator"),
3088 type_params: empty_type_params(),
3089 }),
3090 Type::single(Atomic::TNamedObject {
3091 fqcn: Name::new("Countable"),
3092 type_params: empty_type_params(),
3093 }),
3094 ]),
3095 };
3096 let mut u = Type::single(intersection);
3097 u.add_type(Atomic::TNull);
3098 assert!(u.is_nullable());
3099 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
3100 }
3101
3102 fn t_param(name: &str) -> Type {
3105 Type::single(Atomic::TTemplateParam {
3106 name: Name::new(name),
3107 as_type: Box::new(Type::mixed()),
3108 defining_entity: Name::new("Fn"),
3109 })
3110 }
3111
3112 fn bindings_t_string() -> FxHashMap<Name, Type> {
3113 let mut b = FxHashMap::default();
3114 b.insert(Name::new("T"), Type::single(Atomic::TString));
3115 b
3116 }
3117
3118 #[test]
3119 fn substitute_non_empty_array_key_and_value() {
3120 let ty = Type::single(Atomic::TNonEmptyArray {
3121 key: Box::new(t_param("T")),
3122 value: Box::new(t_param("T")),
3123 });
3124 let result = ty.substitute_templates(&bindings_t_string());
3125 assert_eq!(result.types.len(), 1);
3126 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
3127 panic!("expected TNonEmptyArray");
3128 };
3129 assert!(matches!(key.types[0], Atomic::TString));
3130 assert!(matches!(value.types[0], Atomic::TString));
3131 }
3132
3133 #[test]
3134 fn substitute_non_empty_list_value() {
3135 let ty = Type::single(Atomic::TNonEmptyList {
3136 value: Box::new(t_param("T")),
3137 });
3138 let result = ty.substitute_templates(&bindings_t_string());
3139 let Atomic::TNonEmptyList { value } = &result.types[0] else {
3140 panic!("expected TNonEmptyList");
3141 };
3142 assert!(matches!(value.types[0], Atomic::TString));
3143 }
3144
3145 #[test]
3146 fn substitute_keyed_array_property_types() {
3147 use crate::atomic::{ArrayKey, KeyedProperty};
3148 use indexmap::IndexMap;
3149 let mut props = IndexMap::new();
3150 props.insert(
3151 ArrayKey::String(Arc::from("name")),
3152 KeyedProperty {
3153 ty: t_param("T"),
3154 optional: false,
3155 },
3156 );
3157 props.insert(
3158 ArrayKey::String(Arc::from("tag")),
3159 KeyedProperty {
3160 ty: t_param("T"),
3161 optional: true,
3162 },
3163 );
3164 let ty = Type::single(Atomic::TKeyedArray {
3165 properties: Box::new(props),
3166 is_open: true,
3167 is_list: false,
3168 });
3169 let result = ty.substitute_templates(&bindings_t_string());
3170 let Atomic::TKeyedArray {
3171 properties,
3172 is_open,
3173 is_list,
3174 } = &result.types[0]
3175 else {
3176 panic!("expected TKeyedArray");
3177 };
3178 assert!(is_open);
3179 assert!(!is_list);
3180 assert!(matches!(
3181 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
3182 Atomic::TString
3183 ));
3184 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
3185 assert!(matches!(
3186 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
3187 Atomic::TString
3188 ));
3189 }
3190
3191 #[test]
3192 fn substitute_callable_params_and_return() {
3193 use crate::atomic::FnParam;
3194 let ty = Type::single(Atomic::TCallable {
3195 params: Some(Box::new(vec![FnParam {
3196 name: Name::new("x"),
3197 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3198 out_ty: None,
3199 default: None,
3200 is_variadic: false,
3201 is_byref: false,
3202 is_optional: false,
3203 }])),
3204 return_type: Some(Box::new(t_param("T"))),
3205 is_pure: false,
3206 });
3207 let result = ty.substitute_templates(&bindings_t_string());
3208 let Atomic::TCallable {
3209 params,
3210 return_type,
3211 ..
3212 } = &result.types[0]
3213 else {
3214 panic!("expected TCallable");
3215 };
3216 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
3217 let param_union = param_ty.to_union();
3218 assert!(matches!(param_union.types[0], Atomic::TString));
3219 let ret = return_type.as_ref().unwrap();
3220 assert!(matches!(ret.types[0], Atomic::TString));
3221 }
3222
3223 #[test]
3224 fn substitute_callable_bare_no_panic() {
3225 let ty = Type::single(Atomic::TCallable {
3227 params: None,
3228 return_type: None,
3229 is_pure: false,
3230 });
3231 let result = ty.substitute_templates(&bindings_t_string());
3232 assert!(matches!(
3233 result.types[0],
3234 Atomic::TCallable {
3235 params: None,
3236 return_type: None,
3237 ..
3238 }
3239 ));
3240 }
3241
3242 #[test]
3243 fn substitute_closure_params_return_and_this() {
3244 use crate::atomic::FnParam;
3245 let ty = Type::single(Atomic::TClosure {
3246 data: Box::new(crate::atomic::ClosureData {
3247 params: Box::new([FnParam {
3248 name: Name::new("a"),
3249 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3250 out_ty: None,
3251 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3252 is_variadic: true,
3253 is_byref: true,
3254 is_optional: true,
3255 }]),
3256 return_type: t_param("T"),
3257 this_type: Some(t_param("T")),
3258 is_pure: false,
3259 is_static: false,
3260 rejects_surplus_args: false,
3261 }),
3262 });
3263 let result = ty.substitute_templates(&bindings_t_string());
3264 let Atomic::TClosure { data } = &result.types[0] else {
3265 panic!("expected TClosure");
3266 };
3267 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
3268 let p = ¶ms[0];
3269 let ty_union = p.ty.as_ref().unwrap().to_union();
3270 let default_union = p.default.as_ref().unwrap().to_union();
3271 assert!(matches!(ty_union.types[0], Atomic::TString));
3272 assert!(matches!(default_union.types[0], Atomic::TString));
3273 assert!(p.is_variadic);
3275 assert!(p.is_byref);
3276 assert!(p.is_optional);
3277 assert!(matches!(return_type.types[0], Atomic::TString));
3278 assert!(matches!(
3279 this_type.as_ref().unwrap().types[0],
3280 Atomic::TString
3281 ));
3282 }
3283
3284 #[test]
3285 fn substitute_conditional_all_branches() {
3286 let ty = Type::single(conditional(
3287 None,
3288 t_param("T"),
3289 t_param("T"),
3290 Type::single(Atomic::TInt),
3291 ));
3292 let result = ty.substitute_templates(&bindings_t_string());
3293 let Atomic::TConditional { data } = &result.types[0] else {
3294 panic!("expected TConditional");
3295 };
3296 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
3297 assert!(matches!(subject.types[0], Atomic::TString));
3298 assert!(matches!(if_true.types[0], Atomic::TString));
3299 assert!(matches!(if_false.types[0], Atomic::TInt));
3300 }
3301
3302 #[test]
3303 fn resolve_conditional_is_null_non_null_arg() {
3304 let ty = Type::single(conditional(
3305 Some(Name::new("x")),
3306 Type::single(Atomic::TNull),
3307 Type::single(Atomic::TInt),
3308 Type::single(Atomic::TString),
3309 ));
3310 let result = ty.resolve_conditional_returns(|name| {
3311 if name == "x" {
3312 Some(Type::single(Atomic::TString)) } else {
3314 None
3315 }
3316 });
3317 assert!(result.types.len() == 1);
3318 assert!(matches!(result.types[0], Atomic::TString));
3319 }
3320
3321 #[test]
3322 fn resolve_conditional_is_null_null_arg() {
3323 let ty = Type::single(conditional(
3324 Some(Name::new("x")),
3325 Type::single(Atomic::TNull),
3326 Type::single(Atomic::TInt),
3327 Type::single(Atomic::TString),
3328 ));
3329 let result = ty.resolve_conditional_returns(|name| {
3330 if name == "x" {
3331 Some(Type::single(Atomic::TNull)) } else {
3333 None
3334 }
3335 });
3336 assert!(result.types.len() == 1);
3337 assert!(matches!(result.types[0], Atomic::TInt));
3338 }
3339
3340 #[test]
3341 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
3342 let mut nullable_str = Type::single(Atomic::TString);
3343 nullable_str.add_type(Atomic::TNull);
3344 let ty = Type::single(conditional(
3345 Some(Name::new("x")),
3346 Type::single(Atomic::TNull),
3347 Type::single(Atomic::TInt),
3348 Type::single(Atomic::TString),
3349 ));
3350 let result = ty.resolve_conditional_returns(|name| {
3351 if name == "x" {
3352 Some(nullable_str.clone())
3353 } else {
3354 None
3355 }
3356 });
3357 assert_eq!(result.types.len(), 2);
3359 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3360 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3361 }
3362
3363 #[test]
3364 fn resolve_conditional_nested_widens_inner_branch() {
3365 let inner = Type::single(conditional(
3368 Some(Name::new("x")),
3369 Type::single(Atomic::TString),
3370 Type::single(Atomic::TString),
3371 Type::single(Atomic::TFloat),
3372 ));
3373 let ty = Type::single(conditional(
3374 Some(Name::new("x")),
3375 Type::single(Atomic::TNull),
3376 Type::single(Atomic::TInt),
3377 inner,
3378 ));
3379 let result = ty.resolve_conditional_returns(|_| None);
3381 assert!(
3382 result
3383 .types
3384 .iter()
3385 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3386 "no TConditional should survive: {:?}",
3387 result.types
3388 );
3389 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3390 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3391 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
3392 }
3393
3394 #[test]
3395 fn resolve_conditional_nested_resolves_inner_branch() {
3396 let inner = Type::single(conditional(
3400 Some(Name::new("x")),
3401 Type::single(Atomic::TString),
3402 Type::single(Atomic::TString),
3403 Type::single(Atomic::TFloat),
3404 ));
3405 let ty = Type::single(conditional(
3406 Some(Name::new("x")),
3407 Type::single(Atomic::TNull),
3408 Type::single(Atomic::TInt),
3409 inner,
3410 ));
3411 let result = ty.resolve_conditional_returns(|name| {
3413 if name == "x" {
3414 Some(Type::single(Atomic::TString))
3415 } else {
3416 None
3417 }
3418 });
3419 assert!(
3420 result
3421 .types
3422 .iter()
3423 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3424 "no TConditional should survive: {:?}",
3425 result.types
3426 );
3427 assert_eq!(result.types.len(), 1);
3428 assert!(matches!(result.types[0], Atomic::TString));
3429 }
3430
3431 #[test]
3432 fn resolve_conditional_true_subject_bool_arg_widens() {
3433 let ty = Type::single(conditional(
3437 Some(Name::new("x")),
3438 Type::single(Atomic::TTrue),
3439 Type::single(Atomic::TInt),
3440 Type::single(Atomic::TString),
3441 ));
3442 let result = ty.resolve_conditional_returns(|name| {
3443 if name == "x" {
3444 Some(Type::single(Atomic::TBool))
3445 } else {
3446 None
3447 }
3448 });
3449 assert_eq!(result.types.len(), 2);
3450 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3451 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3452 }
3453
3454 #[test]
3455 fn resolve_conditional_true_subject_mixed_arg_widens() {
3456 let ty = Type::single(conditional(
3459 Some(Name::new("x")),
3460 Type::single(Atomic::TTrue),
3461 Type::single(Atomic::TInt),
3462 Type::single(Atomic::TString),
3463 ));
3464 let result = ty.resolve_conditional_returns(|name| {
3465 if name == "x" {
3466 Some(Type::mixed())
3467 } else {
3468 None
3469 }
3470 });
3471 assert_eq!(result.types.len(), 2);
3472 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3473 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3474 }
3475
3476 #[test]
3477 fn resolve_conditional_true_subject_literal_args() {
3478 let ty = Type::single(conditional(
3481 Some(Name::new("x")),
3482 Type::single(Atomic::TTrue),
3483 Type::single(Atomic::TInt),
3484 Type::single(Atomic::TString),
3485 ));
3486 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3487 if name == "x" {
3488 Some(Type::single(Atomic::TFalse))
3489 } else {
3490 None
3491 }
3492 });
3493 assert_eq!(false_arg.types.len(), 1);
3494 assert!(matches!(false_arg.types[0], Atomic::TString));
3495
3496 let true_arg = ty.resolve_conditional_returns(|name| {
3497 if name == "x" {
3498 Some(Type::single(Atomic::TTrue))
3499 } else {
3500 None
3501 }
3502 });
3503 assert_eq!(true_arg.types.len(), 1);
3504 assert!(matches!(true_arg.types[0], Atomic::TInt));
3505 }
3506
3507 #[test]
3508 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3509 let ty = Type::single(conditional(
3513 Some(Name::new("x")),
3514 Type::single(Atomic::TBool),
3515 Type::single(Atomic::TInt),
3516 Type::single(Atomic::TString),
3517 ));
3518 let result = ty.resolve_conditional_returns(|name| {
3519 if name == "x" {
3520 Some(Type::single(Atomic::TScalar))
3521 } else {
3522 None
3523 }
3524 });
3525 assert_eq!(result.types.len(), 2);
3526 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3527 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3528 }
3529
3530 #[test]
3531 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3532 let ty = Type::single(conditional(
3536 Some(Name::new("x")),
3537 Type::single(Atomic::TString),
3538 Type::single(Atomic::TInt),
3539 Type::single(Atomic::TBool),
3540 ));
3541 let result = ty.resolve_conditional_returns(|name| {
3542 if name == "x" {
3543 Some(Type::single(Atomic::TBool))
3544 } else {
3545 None
3546 }
3547 });
3548 assert_eq!(result.types.len(), 1);
3549 assert!(matches!(result.types[0], Atomic::TBool));
3550 }
3551
3552 #[test]
3553 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3554 let ty = Type::single(conditional(
3557 Some(Name::new("x")),
3558 Type::single(Atomic::TList {
3559 value: Box::new(Type::mixed()),
3560 }),
3561 Type::single(Atomic::TInt),
3562 Type::single(Atomic::TString),
3563 ));
3564 let result = ty.resolve_conditional_returns(|name| {
3565 if name == "x" {
3566 Some(Type::single(Atomic::TArray {
3567 key: Box::new(Type::mixed()),
3568 value: Box::new(Type::mixed()),
3569 }))
3570 } else {
3571 None
3572 }
3573 });
3574 assert_eq!(result.types.len(), 2);
3575 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3576 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3577 }
3578
3579 #[test]
3580 fn resolve_conditional_float_subject_int_literal_false_branch() {
3581 let ty = resolve_conditional_branch(
3584 &Atomic::TFloat,
3585 &Type::single(Atomic::TLiteralInt(42)),
3586 &Type::single(Atomic::TInt),
3587 &Type::single(Atomic::TString),
3588 );
3589 assert!(matches!(
3590 ty.as_ref(),
3591 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3592 ));
3593 }
3594
3595 #[test]
3596 fn resolve_conditional_int_subject_float_literal_false_branch() {
3597 let ty = resolve_conditional_branch(
3598 &Atomic::TInt,
3599 &Type::single(Atomic::TLiteralFloat(1, 0)),
3600 &Type::single(Atomic::TInt),
3601 &Type::single(Atomic::TString),
3602 );
3603 assert!(matches!(
3604 ty.as_ref(),
3605 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3606 ));
3607 }
3608
3609 #[test]
3610 fn resolve_conditional_float_subject_float_literal_then_branch() {
3611 let ty = resolve_conditional_branch(
3612 &Atomic::TFloat,
3613 &Type::single(Atomic::TLiteralFloat(1, 5)),
3614 &Type::single(Atomic::TInt),
3615 &Type::single(Atomic::TString),
3616 );
3617 assert!(matches!(
3618 ty.as_ref(),
3619 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3620 ));
3621 }
3622
3623 #[test]
3624 fn resolve_conditional_int_subject_int_literal_then_branch() {
3625 let ty = resolve_conditional_branch(
3626 &Atomic::TInt,
3627 &Type::single(Atomic::TLiteralInt(7)),
3628 &Type::single(Atomic::TInt),
3629 &Type::single(Atomic::TString),
3630 );
3631 assert!(matches!(
3632 ty.as_ref(),
3633 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3634 ));
3635 }
3636
3637 #[test]
3638 fn resolve_conditional_int_subject_float_arg_false_branch() {
3639 let ty = resolve_conditional_branch(
3642 &Atomic::TInt,
3643 &Type::single(Atomic::TFloat),
3644 &Type::single(Atomic::TInt),
3645 &Type::single(Atomic::TString),
3646 );
3647 assert!(matches!(
3648 ty.as_ref(),
3649 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3650 ));
3651 }
3652
3653 #[test]
3654 fn resolve_conditional_array_subject_string_arg_false_branch() {
3655 let ty = resolve_conditional_branch(
3656 &Atomic::TArray {
3657 key: Box::new(Type::mixed()),
3658 value: Box::new(Type::mixed()),
3659 },
3660 &Type::single(Atomic::TString),
3661 &Type::single(Atomic::TInt),
3662 &Type::single(Atomic::TString),
3663 );
3664 assert!(matches!(
3665 ty.as_ref(),
3666 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3667 ));
3668 }
3669
3670 #[test]
3671 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3672 let ty = resolve_conditional_branch(
3673 &Atomic::TArray {
3674 key: Box::new(Type::mixed()),
3675 value: Box::new(Type::mixed()),
3676 },
3677 &Type::single(Atomic::TBool),
3678 &Type::single(Atomic::TInt),
3679 &Type::single(Atomic::TString),
3680 );
3681 assert!(matches!(
3682 ty.as_ref(),
3683 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3684 ));
3685 }
3686
3687 #[test]
3688 fn resolve_conditional_array_subject_mixed_arg_widens() {
3689 let ty = resolve_conditional_branch(
3693 &Atomic::TArray {
3694 key: Box::new(Type::mixed()),
3695 value: Box::new(Type::mixed()),
3696 },
3697 &Type::mixed(),
3698 &Type::single(Atomic::TInt),
3699 &Type::single(Atomic::TString),
3700 );
3701 assert!(ty.is_none());
3702 }
3703
3704 #[test]
3705 fn resolve_conditional_array_subject_list_arg_then_branch() {
3706 let ty = resolve_conditional_branch(
3709 &Atomic::TArray {
3710 key: Box::new(Type::mixed()),
3711 value: Box::new(Type::mixed()),
3712 },
3713 &Type::single(Atomic::TList {
3714 value: Box::new(Type::mixed()),
3715 }),
3716 &Type::single(Atomic::TInt),
3717 &Type::single(Atomic::TString),
3718 );
3719 assert!(matches!(
3720 ty.as_ref(),
3721 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3722 ));
3723 }
3724
3725 #[test]
3726 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3727 let ty = resolve_conditional_branch(
3729 &Atomic::TList {
3730 value: Box::new(Type::mixed()),
3731 },
3732 &Type::single(Atomic::TKeyedArray {
3733 properties: Box::default(),
3734 is_open: false,
3735 is_list: true,
3736 }),
3737 &Type::single(Atomic::TInt),
3738 &Type::single(Atomic::TString),
3739 );
3740 assert!(matches!(
3741 ty.as_ref(),
3742 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3743 ));
3744 }
3745
3746 #[test]
3747 fn resolve_conditional_list_subject_list_string_union_widens() {
3748 let ty = resolve_conditional_branch(
3752 &Atomic::TList {
3753 value: Box::new(Type::mixed()),
3754 },
3755 &Type::from_vec(vec![
3756 Atomic::TList {
3757 value: Box::new(Type::mixed()),
3758 },
3759 Atomic::TString,
3760 ]),
3761 &Type::single(Atomic::TInt),
3762 &Type::single(Atomic::TString),
3763 );
3764 assert!(ty.is_none());
3765 }
3766
3767 #[test]
3768 fn resolve_conditional_numeric_subject_widens() {
3769 let ty = resolve_conditional_branch(
3773 &Atomic::TNumeric,
3774 &Type::single(Atomic::TLiteralString("123".into())),
3775 &Type::single(Atomic::TInt),
3776 &Type::single(Atomic::TString),
3777 );
3778 assert!(ty.is_none());
3779 }
3780
3781 #[test]
3782 fn resolve_conditional_non_empty_string_subject_widens() {
3783 let ty = resolve_conditional_branch(
3786 &Atomic::TNonEmptyString,
3787 &Type::single(Atomic::TString),
3788 &Type::single(Atomic::TInt),
3789 &Type::single(Atomic::TString),
3790 );
3791 assert!(ty.is_none());
3792 }
3793
3794 #[test]
3795 fn resolve_conditional_template_arg_widens() {
3796 let ty = resolve_conditional_branch(
3799 &Atomic::TString,
3800 &t_param("T"),
3801 &Type::single(Atomic::TInt),
3802 &Type::single(Atomic::TString),
3803 );
3804 assert!(ty.is_none());
3805 }
3806
3807 #[test]
3808 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3809 let ty = resolve_conditional_branch(
3812 &Atomic::TMixed,
3813 &Type::single(Atomic::TBool),
3814 &Type::single(Atomic::TInt),
3815 &Type::single(Atomic::TString),
3816 );
3817 assert!(matches!(
3818 ty.as_ref(),
3819 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3820 ));
3821 }
3822
3823 #[test]
3824 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3825 let ty = resolve_conditional_branch(
3828 &Atomic::TScalar,
3829 &Type::single(Atomic::TNull),
3830 &Type::single(Atomic::TInt),
3831 &Type::single(Atomic::TString),
3832 );
3833 assert!(matches!(
3834 ty.as_ref(),
3835 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3836 ));
3837 }
3838
3839 #[test]
3840 fn resolve_conditional_object_subject_named_arg_then_branch() {
3841 let ty = resolve_conditional_branch(
3843 &Atomic::TObject,
3844 &Type::single(Atomic::TNamedObject {
3845 fqcn: Name::new("Foo"),
3846 type_params: empty_type_params(),
3847 }),
3848 &Type::single(Atomic::TInt),
3849 &Type::single(Atomic::TString),
3850 );
3851 assert!(matches!(
3852 ty.as_ref(),
3853 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3854 ));
3855 }
3856
3857 #[test]
3858 fn resolve_conditional_named_subject_other_class_widens() {
3859 let ty = resolve_conditional_branch(
3863 &Atomic::TNamedObject {
3864 fqcn: Name::new("Foo"),
3865 type_params: empty_type_params(),
3866 },
3867 &Type::single(Atomic::TNamedObject {
3868 fqcn: Name::new("Bar"),
3869 type_params: empty_type_params(),
3870 }),
3871 &Type::single(Atomic::TInt),
3872 &Type::single(Atomic::TString),
3873 );
3874 assert!(ty.is_none());
3875 }
3876
3877 #[test]
3878 fn resolve_conditional_enum_case_subject_widens() {
3879 let ty = resolve_conditional_branch(
3882 &Atomic::TLiteralEnumCase {
3883 enum_fqcn: Name::new("RoundingMode"),
3884 case_name: Name::new("Unnecessary"),
3885 },
3886 &Type::single(Atomic::TInt),
3887 &Type::single(Atomic::TInt),
3888 &Type::single(Atomic::TString),
3889 );
3890 assert!(ty.is_none());
3891 }
3892
3893 #[test]
3894 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3895 let ty = resolve_conditional_branch(
3897 &Atomic::TNull,
3898 &Type::single(Atomic::TBool),
3899 &Type::single(Atomic::TInt),
3900 &Type::single(Atomic::TString),
3901 );
3902 assert!(matches!(
3903 ty.as_ref(),
3904 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3905 ));
3906 }
3907
3908 #[test]
3909 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3910 let ty = resolve_conditional_branch(
3913 &Atomic::TBool,
3914 &Type::single(Atomic::TTrue),
3915 &Type::single(Atomic::TInt),
3916 &Type::single(Atomic::TString),
3917 );
3918 assert!(matches!(
3919 ty.as_ref(),
3920 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3921 ));
3922 }
3923
3924 #[test]
3925 fn substitute_intersection_parts() {
3926 let ty = Type::single(Atomic::TIntersection {
3927 parts: vec_to_type_params(vec![
3928 Type::single(Atomic::TNamedObject {
3929 fqcn: Name::new("Countable"),
3930 type_params: empty_type_params(),
3931 }),
3932 t_param("T"),
3933 ]),
3934 });
3935 let result = ty.substitute_templates(&bindings_t_string());
3936 let Atomic::TIntersection { parts } = &result.types[0] else {
3937 panic!("expected TIntersection");
3938 };
3939 assert_eq!(parts.len(), 2);
3940 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3941 assert!(matches!(parts[1].types[0], Atomic::TString));
3942 }
3943
3944 fn shape(props: &[(&str, Atomic, bool)], is_open: bool) -> Atomic {
3945 Atomic::TKeyedArray {
3946 properties: Box::new(
3947 props
3948 .iter()
3949 .map(|(k, ty, optional)| {
3950 (
3951 ArrayKey::String(Arc::from(*k)),
3952 KeyedProperty {
3953 ty: Type::single(ty.clone()),
3954 optional: *optional,
3955 },
3956 )
3957 })
3958 .collect(),
3959 ),
3960 is_open,
3961 is_list: false,
3962 }
3963 }
3964
3965 fn shape_with_bits(bits: u32, is_open: bool) -> Atomic {
3967 let names: Vec<String> = (0..5).map(|i| format!("k{i}")).collect();
3968 let mut props = vec![("a", Atomic::TInt, false)];
3969 props.extend(
3970 (0..5)
3971 .filter(|i| bits & (1 << i) != 0)
3972 .map(|i| (names[i].as_str(), Atomic::TString, false)),
3973 );
3974 shape(&props, is_open)
3975 }
3976
3977 fn keyed_count(ty: &Type) -> usize {
3978 ty.types
3979 .iter()
3980 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
3981 .count()
3982 }
3983
3984 #[test]
3985 fn add_type_keeps_few_shapes_apart() {
3986 let mut ty = Type::empty();
3987 for bits in 0..MAX_KEYED_SHAPES as u32 {
3988 ty.add_type(shape_with_bits(bits, false));
3989 }
3990 assert_eq!(keyed_count(&ty), MAX_KEYED_SHAPES);
3991 }
3992
3993 #[test]
3994 fn add_type_folds_shapes_past_the_limit() {
3995 let mut ty = Type::empty();
3996 for bits in 0..32 {
3997 ty.add_type(shape_with_bits(bits, false));
3998 }
3999 assert!(keyed_count(&ty) <= MAX_KEYED_SHAPES);
4000 for t in &ty.types {
4002 let Atomic::TKeyedArray { properties, .. } = t else {
4003 continue;
4004 };
4005 assert!(!properties[&ArrayKey::String(Arc::from("a"))].optional);
4006 }
4007 for i in 0..5 {
4008 let key = ArrayKey::String(Arc::from(format!("k{i}").as_str()));
4009 assert!(ty.types.iter().any(|t| matches!(
4010 t,
4011 Atomic::TKeyedArray { properties, .. } if properties.contains_key(&key)
4012 )));
4013 }
4014 }
4015
4016 #[test]
4017 fn combine_keeps_emptiness_and_open_shape_soundness() {
4018 assert!(combine_keyed_arrays(
4020 &shape(&[], false),
4021 &shape(&[("a", Atomic::TInt, false)], false)
4022 )
4023 .is_none());
4024 assert!(combine_keyed_arrays(
4026 &shape(&[("a", Atomic::TInt, false)], true),
4027 &shape(&[("a", Atomic::TInt, false)], false)
4028 )
4029 .is_none());
4030 assert_eq!(
4032 combine_keyed_arrays(
4033 &shape(&[("a", Atomic::TInt, false)], true),
4034 &shape(
4035 &[("a", Atomic::TInt, false), ("k", Atomic::TString, false)],
4036 true
4037 )
4038 ),
4039 Some(shape(&[("a", Atomic::TInt, false)], true))
4040 );
4041 }
4042
4043 #[test]
4044 fn substitute_no_template_params_identity() {
4045 let ty = Type::single(Atomic::TInt);
4046 let result = ty.substitute_templates(&bindings_t_string());
4047 assert!(matches!(result.types[0], Atomic::TInt));
4048 }
4049}