1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::{ArrayKey, Atomic, KeyedProperties, KeyedProperty};
7use crate::symbol::Name;
8
9pub fn empty_type_params() -> Arc<[Type]> {
13 static EMPTY: OnceLock<Arc<[Type]>> = OnceLock::new();
14 EMPTY.get_or_init(|| Arc::from([] as [Type; 0])).clone()
15}
16
17pub fn vec_to_type_params(v: Vec<Type>) -> Arc<[Type]> {
20 if v.is_empty() {
21 empty_type_params()
22 } else {
23 Arc::from(v)
24 }
25}
26
27pub type AtomicVec = SmallVec<[Atomic; 2]>;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum CloneValidity {
33 Cloneable,
35 Invalid,
37 PossiblyInvalid,
39 Unknown,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub struct Type {
49 pub types: AtomicVec,
50 pub possibly_undefined: bool,
52 pub from_docblock: bool,
54 pub falsy_stripped: bool,
62 pub possibly_absent_offset: bool,
73}
74
75type IntBounds = (Option<i64>, Option<i64>);
76
77fn int_bounds(a: &Atomic) -> Option<IntBounds> {
79 match a {
80 Atomic::TInt => Some((None, None)),
81 Atomic::TLiteralInt(n) => Some((Some(*n), Some(*n))),
82 Atomic::TPositiveInt => Some((Some(1), None)),
83 Atomic::TNonNegativeInt => Some((Some(0), None)),
84 Atomic::TNegativeInt => Some((None, Some(-1))),
85 Atomic::TIntRange { min, max } => Some((*min, *max)),
86 _ => None,
87 }
88}
89
90fn bounds_contain(outer: IntBounds, inner: IntBounds) -> bool {
91 let lo_ok = match (outer.0, inner.0) {
92 (None, _) => true,
93 (Some(o), Some(i)) => o <= i,
94 (Some(_), None) => false,
95 };
96 let hi_ok = match (outer.1, inner.1) {
97 (None, _) => true,
98 (Some(o), Some(i)) => o >= i,
99 (Some(_), None) => false,
100 };
101 lo_ok && hi_ok
102}
103
104const MAX_KEYED_SHAPES: usize = 8;
111
112fn combine_keyed_arrays(a: &Atomic, b: &Atomic) -> Option<Atomic> {
117 let (
118 Atomic::TKeyedArray {
119 properties: pa,
120 is_open: oa,
121 is_list: la,
122 },
123 Atomic::TKeyedArray {
124 properties: pb,
125 is_open: ob,
126 is_list: lb,
127 },
128 ) = (a, b)
129 else {
130 return None;
131 };
132 let non_empty = |p: &KeyedProperties| p.values().any(|v| !v.optional);
133 if oa != ob || la != lb || non_empty(pa) != non_empty(pb) {
134 return None;
135 }
136 let mut properties =
137 KeyedProperties::with_capacity_and_hasher(pa.len().max(pb.len()), Default::default());
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: Arc::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 if self.types.contains(&atomic) {
411 return;
412 }
413 self.add_absent_type(atomic);
414 }
415
416 fn add_absent_type(&mut self, atomic: Atomic) {
418 let mut atomic = atomic;
421 if matches!(atomic, Atomic::TKeyedArray { .. })
422 && self
423 .types
424 .iter()
425 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
426 .count()
427 >= MAX_KEYED_SHAPES
428 {
429 if let Some((idx, combined)) = self
430 .types
431 .iter()
432 .enumerate()
433 .find_map(|(i, t)| combine_keyed_arrays(t, &atomic).map(|c| (i, c)))
434 {
435 self.types.remove(idx);
436 if self.types.contains(&combined) {
437 return;
438 }
439 atomic = combined;
440 }
441 }
442
443 if let Some(new_bounds) = int_bounds(&atomic) {
446 if self
447 .types
448 .iter()
449 .any(|t| int_bounds(t).is_some_and(|b| bounds_contain(b, new_bounds)))
450 {
451 return;
452 }
453 self.types
454 .retain(|t| !int_bounds(t).is_some_and(|b| bounds_contain(new_bounds, b)));
455 }
456 if let Atomic::TLiteralString(_) = &atomic {
458 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
459 return;
460 }
461 }
462 if matches!(atomic, Atomic::TNonEmptyString)
464 && self.types.iter().any(|t| matches!(t, Atomic::TString))
465 {
466 return;
467 }
468 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
470 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
471 {
472 return;
473 }
474 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
477 {
478 self.types.retain(|t| !matches!(t, Atomic::TFalse));
479 self.types.push(Atomic::TBool);
480 return;
481 }
482 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
483 {
484 self.types.retain(|t| !matches!(t, Atomic::TTrue));
485 self.types.push(Atomic::TBool);
486 return;
487 }
488 if matches!(atomic, Atomic::TString) {
490 self.types
491 .retain(|t| !matches!(t, Atomic::TLiteralString(_) | Atomic::TNonEmptyString));
492 }
493 if matches!(atomic, Atomic::TBool) {
495 self.types
496 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
497 }
498
499 if matches!(atomic, Atomic::TNever) {
501 if !self.types.is_empty() {
502 return;
503 }
504 } else {
505 self.types.retain(|t| !matches!(t, Atomic::TNever));
506 }
507
508 if let Atomic::TKeyedArray {
514 properties,
515 is_open,
516 ..
517 } = &atomic
518 {
519 if properties.is_empty() && !is_open {
520 for existing in &self.types {
521 match existing {
522 Atomic::TArray { .. }
523 | Atomic::TNonEmptyArray { .. }
524 | Atomic::TList { .. }
525 | Atomic::TNonEmptyList { .. } => {
526 return; }
528 _ => {}
529 }
530 }
531 }
532 }
533
534 let is_generic_array_or_list = matches!(
537 &atomic,
538 Atomic::TArray { .. }
539 | Atomic::TNonEmptyArray { .. }
540 | Atomic::TList { .. }
541 | Atomic::TNonEmptyList { .. }
542 );
543 if is_generic_array_or_list {
544 self.types.retain(|t| {
545 if let Atomic::TKeyedArray {
546 properties,
547 is_open,
548 ..
549 } = t
550 {
551 !properties.is_empty() || *is_open
552 } else {
553 true
554 }
555 });
556 }
557
558 self.types.push(atomic);
559 }
560
561 pub fn remove_null(&self) -> Type {
565 self.filter(|t| !matches!(t, Atomic::TNull))
566 }
567
568 pub fn remove_false(&self) -> Type {
571 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
572 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
573 result.add_type(Atomic::TTrue);
574 }
575 result
576 }
577
578 pub fn remove_true(&self) -> Type {
581 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
582 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
583 result.add_type(Atomic::TFalse);
584 }
585 result
586 }
587
588 pub fn core_type(&self) -> Type {
590 self.remove_null().remove_false()
591 }
592
593 pub fn narrow_to_truthy(&self) -> Type {
595 if self.is_mixed_not_template() {
596 return Type::mixed();
597 }
598 let mut result = Type::empty();
599 result.from_docblock = self.from_docblock;
600 for t in &self.types {
601 match t {
602 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
606 Atomic::TLiteralInt(0)
608 | Atomic::TLiteralFloat(0, 0)
609 | Atomic::TNull
610 | Atomic::TFalse => {}
611 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
612 Atomic::TBool => result.add_type(Atomic::TTrue),
614 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
616 key: key.clone(),
617 value: value.clone(),
618 }),
619 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
620 value: value.clone(),
621 }),
622 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
626 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
631 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
632 let atom = if max.is_none() {
633 Atomic::TPositiveInt
634 } else {
635 Atomic::TIntRange {
636 min: Some(1),
637 max: *max,
638 }
639 };
640 result.add_type(atom);
641 }
642 Atomic::TIntRange { min, max: Some(0) } => {
644 let atom = match min {
645 None => Atomic::TNegativeInt,
646 Some(n) if *n <= -1 => Atomic::TIntRange {
647 min: *min,
648 max: Some(-1),
649 },
650 _ => continue, };
652 result.add_type(atom);
653 }
654 t if !t.can_be_truthy() => {}
656 _ => result.add_type(t.clone()),
657 }
658 }
659 result
660 }
661
662 pub fn narrow_to_falsy(&self) -> Type {
664 if self.is_mixed_not_template() {
665 return Type::from_vec(vec![
666 Atomic::TNull,
667 Atomic::TFalse,
668 Atomic::TLiteralInt(0),
669 Atomic::TLiteralString("".into()),
670 ]);
671 }
672 let mut result = Type::empty();
673 result.from_docblock = self.from_docblock;
674 for t in &self.types {
675 match t {
676 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
681 Atomic::TBool => result.add_type(Atomic::TFalse),
683 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
685 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
687 Atomic::TString => {
689 result.add_type(Atomic::TLiteralString("".into()));
690 result.add_type(Atomic::TLiteralString("0".into()));
691 }
692 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
694 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
696 Atomic::TIntRange {
698 min: Some(0),
699 max: Some(_) | None,
700 } => result.add_type(Atomic::TLiteralInt(0)),
701 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
703 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
705 }
706 }
707 result
708 }
709
710 pub fn narrow_instanceof(&self, class: &str) -> Type {
716 let narrowed_ty = Atomic::TNamedObject {
717 fqcn: class.into(),
718 type_params: empty_type_params(),
719 };
720 let has_object = self.types.iter().any(|t| {
722 matches!(
723 t,
724 Atomic::TObject
725 | Atomic::TObjectShape { .. }
726 | Atomic::TNamedObject { .. }
727 | Atomic::TMixed
728 | Atomic::TNull )
730 });
731 if has_object || self.is_empty() {
732 Type::single(narrowed_ty)
733 } else {
734 Type::single(narrowed_ty)
737 }
738 }
739
740 pub fn narrow_to_string(&self) -> Type {
744 self.filter_replacing(
745 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
746 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
747 Atomic::TString,
748 )
749 .with_callable_form(self, Atomic::TCallableString)
750 }
751
752 pub fn narrow_to_int(&self) -> Type {
754 self.filter_replacing(
755 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
756 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
757 Atomic::TInt,
758 )
759 }
760
761 pub fn narrow_to_float(&self) -> Type {
763 self.filter_replacing(
764 |t| {
765 matches!(
766 t,
767 Atomic::TFloat
768 | Atomic::TIntegralFloat
769 | Atomic::TLiteralFloat(..)
770 | Atomic::TTemplateParam { .. }
771 )
772 },
773 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
774 Atomic::TFloat,
775 )
776 }
777
778 pub fn narrow_to_bool(&self) -> Type {
780 self.filter_replacing(
781 |t| {
782 matches!(
783 t,
784 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
785 )
786 },
787 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
788 Atomic::TBool,
789 )
790 }
791
792 pub fn narrow_to_null(&self) -> Type {
794 self.filter_replacing(
795 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
796 |t| matches!(t, Atomic::TMixed),
797 Atomic::TNull,
798 )
799 }
800
801 pub fn narrow_to_array(&self) -> Type {
803 self.filter_replacing(
804 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
805 |t| matches!(t, Atomic::TMixed),
806 Atomic::TArray {
807 key: Box::new(Type::mixed()),
808 value: Box::new(Type::mixed()),
809 },
810 )
811 .with_callable_form(self, callable_array())
812 }
813
814 pub fn widen_nested_non_empty(&self) -> Type {
817 fn relax_all(ty: &Type) -> Type {
818 map_atoms(ty, |a| match a {
819 Atomic::TNonEmptyList { value } => Atomic::TList {
820 value: Box::new(relax_all(value)),
821 },
822 Atomic::TNonEmptyArray { key, value } => Atomic::TArray {
823 key: key.clone(),
824 value: Box::new(relax_all(value)),
825 },
826 other => map_values(other, relax_all),
827 })
828 }
829 fn map_values(atomic: &Atomic, f: fn(&Type) -> Type) -> Atomic {
830 match atomic {
831 Atomic::TArray { key, value } => Atomic::TArray {
832 key: key.clone(),
833 value: Box::new(f(value)),
834 },
835 Atomic::TList { value } => Atomic::TList {
836 value: Box::new(f(value)),
837 },
838 Atomic::TNonEmptyArray { key, value } => Atomic::TNonEmptyArray {
839 key: key.clone(),
840 value: Box::new(f(value)),
841 },
842 Atomic::TNonEmptyList { value } => Atomic::TNonEmptyList {
843 value: Box::new(f(value)),
844 },
845 Atomic::TKeyedArray {
846 properties,
847 is_open,
848 is_list,
849 } => Atomic::TKeyedArray {
850 properties: Arc::new(
851 properties
852 .iter()
853 .map(|(k, p)| {
854 (
855 k.clone(),
856 crate::atomic::KeyedProperty {
857 ty: f(&p.ty),
858 optional: p.optional,
859 },
860 )
861 })
862 .collect(),
863 ),
864 is_open: *is_open,
865 is_list: *is_list,
866 },
867 other => other.clone(),
868 }
869 }
870 fn map_atoms(ty: &Type, f: impl Fn(&Atomic) -> Atomic) -> Type {
871 let mut result = Type::empty();
872 result.possibly_undefined = ty.possibly_undefined;
873 result.from_docblock = ty.from_docblock;
874 for atomic in &ty.types {
875 result.add_type(f(atomic));
876 }
877 result
878 }
879 map_atoms(self, |a| map_values(a, relax_all))
880 }
881
882 pub fn narrow_to_non_empty_collection(&self) -> Type {
885 let mut out = Type::empty();
886 out.from_docblock = self.from_docblock;
887 for t in &self.types {
888 match t {
889 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
890 key: key.clone(),
891 value: value.clone(),
892 }),
893 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
894 value: value.clone(),
895 }),
896 Atomic::TKeyedArray {
897 properties,
898 is_open: false,
899 ..
900 } if properties.is_empty() => {}
901 _ => out.add_type(t.clone()),
902 }
903 }
904 out
905 }
906
907 pub fn narrow_to_empty_collection(&self) -> Type {
917 let mut out = Type::empty();
918 out.from_docblock = self.from_docblock;
919 for t in &self.types {
920 match t {
921 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
922 Atomic::TArray { .. } | Atomic::TList { .. } => {
923 out.add_type(Atomic::TKeyedArray {
924 properties: Arc::default(),
925 is_open: false,
926 is_list: true,
927 });
928 }
929 _ => out.add_type(t.clone()),
930 }
931 }
932 out
933 }
934
935 pub fn narrow_to_list(&self) -> Type {
947 let mut out = Type::empty();
948 out.from_docblock = self.from_docblock;
949 for t in &self.types {
950 match t {
951 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
952 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
958 out.add_type(Atomic::TList {
959 value: value.clone(),
960 });
961 }
962 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
963 out.add_type(Atomic::TNonEmptyList {
964 value: value.clone(),
965 });
966 }
967 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
968 Atomic::TMixed => out.add_type(Atomic::TList {
969 value: Box::new(Type::mixed()),
970 }),
971 _ => {}
972 }
973 }
974 if out.is_empty() {
975 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
976 } else {
977 out
978 }
979 }
980
981 pub fn narrow_to_object(&self) -> Type {
986 let mut out = Type::empty();
987 for t in &self.types {
988 if matches!(t, Atomic::TMixed) {
989 out.add_type(Atomic::TObject);
990 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
991 out.add_type(t.clone());
992 }
993 }
994 if out.types.is_empty() {
995 self.filter(|t| t.is_object())
996 .with_callable_form(self, Atomic::TObject)
997 } else {
998 out.with_callable_form(self, Atomic::TObject)
999 }
1000 }
1001
1002 fn with_callable_form(mut self, source: &Type, form: Atomic) -> Type {
1005 if source
1006 .types
1007 .iter()
1008 .any(|t| matches!(t, Atomic::TCallable { .. }))
1009 {
1010 self.add_type(form);
1011 }
1012 self
1013 }
1014
1015 pub fn narrow_to_not_callable(&self) -> Type {
1018 let callable_array = callable_array();
1019 self.filter(|t| {
1020 !t.is_callable()
1021 && *t != callable_array
1022 && !matches!(t, Atomic::TNamedObject { fqcn, .. } if fqcn.eq_ignore_ascii_case("Closure"))
1023 })
1024 }
1025
1026 pub fn narrow_to_callable(&self) -> Type {
1033 let narrowed = self.filter(|t| {
1034 t.is_callable()
1035 || t.is_string()
1036 || t.is_array()
1037 || t.is_object()
1038 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1039 });
1040 let mut result = Type::empty();
1047 result.possibly_undefined = narrowed.possibly_undefined;
1048 result.from_docblock = narrowed.from_docblock;
1049 for atomic in narrowed.types {
1050 if matches!(atomic, Atomic::TObject | Atomic::TObjectShape { .. }) {
1051 result.add_type(Atomic::TCallable {
1052 params: None,
1053 return_type: None,
1054 is_pure: false,
1055 });
1056 } else {
1057 result.add_type(atomic);
1058 }
1059 }
1060 result
1061 }
1062
1063 pub fn narrow_to_scalar(&self) -> Type {
1065 self.filter_replacing(
1066 |t| {
1067 t.is_string()
1068 || t.is_int()
1069 || matches!(
1070 t,
1071 Atomic::TFloat
1072 | Atomic::TIntegralFloat
1073 | Atomic::TLiteralFloat(..)
1074 | Atomic::TBool
1075 | Atomic::TTrue
1076 | Atomic::TFalse
1077 | Atomic::TScalar
1078 | Atomic::TNumeric
1079 | Atomic::TNumericString
1080 | Atomic::TTemplateParam { .. }
1081 )
1082 },
1083 |t| matches!(t, Atomic::TMixed),
1084 Atomic::TScalar,
1085 )
1086 }
1087
1088 pub fn narrow_to_iterable(&self) -> Type {
1091 self.filter(|t| {
1092 t.is_array()
1093 || t.is_object()
1094 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1095 })
1096 }
1097
1098 pub fn narrow_to_countable(&self) -> Type {
1101 self.filter(|t| {
1102 t.is_array()
1103 || t.is_object()
1104 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
1105 })
1106 }
1107
1108 pub fn narrow_to_resource(&self) -> Type {
1112 self.filter(|t| matches!(t, Atomic::TMixed))
1114 }
1115
1116 pub fn narrow_to_class_string(&self) -> Type {
1121 let mut out = Type::empty();
1122 out.from_docblock = self.from_docblock;
1123 for t in &self.types {
1124 match t {
1125 Atomic::TClassString(_) => out.add_type(t.clone()),
1126 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1127 out.add_type(Atomic::TClassString(None));
1128 }
1129 _ => {}
1130 }
1131 }
1132 out
1133 }
1134
1135 pub fn narrow_to_interface_string(&self) -> Type {
1140 let mut out = Type::empty();
1141 out.from_docblock = self.from_docblock;
1142 for t in &self.types {
1143 match t {
1144 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
1145 Atomic::TClassString(name) => {
1149 out.add_type(Atomic::TInterfaceString(*name));
1150 }
1151 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1152 out.add_type(Atomic::TInterfaceString(None));
1153 }
1154 _ => {}
1155 }
1156 }
1157 out
1158 }
1159
1160 pub fn merge(a: &Type, b: &Type) -> Type {
1165 if b.types.is_empty() {
1167 let mut result = a.clone();
1168 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1169 return result;
1170 }
1171 if a.types.is_empty() {
1173 let mut result = b.clone();
1174 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1175 return result;
1176 }
1177 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
1179 let mut result = a.clone();
1180 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1181 return result;
1182 }
1183 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1185 return Type {
1186 types: smallvec::smallvec![Atomic::TMixed],
1187 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
1188 from_docblock: a.from_docblock || b.from_docblock,
1189 falsy_stripped: false,
1190 possibly_absent_offset: false,
1191 };
1192 }
1193 let mut result = a.clone();
1194 result.merge_with(b);
1195 result
1196 }
1197
1198 pub fn merge_with(&mut self, other: &Type) {
1200 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1201 self.possibly_undefined |= other.possibly_undefined;
1202 return;
1203 }
1204 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1205 self.types.clear();
1206 self.types.push(Atomic::TMixed);
1207 self.possibly_undefined |= other.possibly_undefined;
1208 return;
1209 }
1210 for atomic in &other.types {
1211 if matches!(atomic, Atomic::TConditional { .. }) {
1213 self.add_type(atomic.clone());
1214 } else if !self.types.contains(atomic)
1215 && !self.types.iter().any(|t| matches!(t, Atomic::TMixed))
1216 {
1217 self.add_absent_type(atomic.clone());
1218 }
1219 }
1220 self.possibly_undefined |= other.possibly_undefined;
1221 }
1222
1223 pub fn intersect_with(&self, other: &Type) -> Type {
1227 if self.is_mixed() {
1228 return other.clone();
1229 }
1230 if other.is_mixed() {
1231 return self.clone();
1232 }
1233 let mut result = Type::empty();
1241 for a in &self.types {
1242 for b in &other.types {
1243 if a == b {
1244 result.add_type(a.clone());
1245 } else if atomic_subtype(b, a) {
1246 result.add_type(b.clone());
1247 } else if atomic_subtype(a, b) {
1248 result.add_type(a.clone());
1249 }
1250 }
1251 }
1252 if result.is_empty() {
1253 Type::never()
1254 } else {
1255 result
1256 }
1257 }
1258
1259 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1263 if bindings.is_empty() {
1264 return self.clone();
1265 }
1266 if !self.types.iter().any(atomic_may_contain_templates) {
1269 return self.clone();
1270 }
1271 let mut result = Type::empty();
1272 result.possibly_undefined = self.possibly_undefined;
1273 result.from_docblock = self.from_docblock;
1274 for atomic in &self.types {
1275 match atomic {
1276 Atomic::TTemplateParam { name, .. } => {
1277 if let Some(resolved) = bindings.get(name) {
1278 for t in &resolved.types {
1279 result.add_type(t.clone());
1280 }
1281 } else {
1282 result.add_type(atomic.clone());
1283 }
1284 }
1285 Atomic::TArray { key, value } => {
1286 result.add_type(Atomic::TArray {
1287 key: Box::new(key.substitute_templates(bindings)),
1288 value: Box::new(value.substitute_templates(bindings)),
1289 });
1290 }
1291 Atomic::TList { value } => {
1292 result.add_type(Atomic::TList {
1293 value: Box::new(value.substitute_templates(bindings)),
1294 });
1295 }
1296 Atomic::TNonEmptyArray { key, value } => {
1297 result.add_type(Atomic::TNonEmptyArray {
1298 key: Box::new(key.substitute_templates(bindings)),
1299 value: Box::new(value.substitute_templates(bindings)),
1300 });
1301 }
1302 Atomic::TNonEmptyList { value } => {
1303 result.add_type(Atomic::TNonEmptyList {
1304 value: Box::new(value.substitute_templates(bindings)),
1305 });
1306 }
1307 Atomic::TKeyedArray {
1308 properties,
1309 is_open,
1310 is_list,
1311 } => {
1312 use crate::atomic::KeyedProperty;
1313 let new_props = properties
1314 .iter()
1315 .map(|(k, prop)| {
1316 (
1317 k.clone(),
1318 KeyedProperty {
1319 ty: prop.ty.substitute_templates(bindings),
1320 optional: prop.optional,
1321 },
1322 )
1323 })
1324 .collect();
1325 result.add_type(Atomic::TKeyedArray {
1326 properties: Arc::new(new_props),
1327 is_open: *is_open,
1328 is_list: *is_list,
1329 });
1330 }
1331 Atomic::TCallable {
1332 params,
1333 return_type,
1334 is_pure,
1335 } => {
1336 result.add_type(Atomic::TCallable {
1337 params: params.as_ref().map(|ps| {
1338 Box::new(
1339 ps.iter()
1340 .map(|p| substitute_in_fn_param(p, bindings))
1341 .collect(),
1342 )
1343 }),
1344 return_type: return_type
1345 .as_ref()
1346 .map(|r| Box::new(r.substitute_templates(bindings))),
1347 is_pure: *is_pure,
1348 });
1349 }
1350 Atomic::TClosure { data } => {
1351 result.add_type(Atomic::TClosure {
1352 data: Box::new(crate::atomic::ClosureData {
1353 params: data
1354 .params
1355 .iter()
1356 .map(|p| substitute_in_fn_param(p, bindings))
1357 .collect(),
1358 return_type: data.return_type.substitute_templates(bindings),
1359 this_type: data
1360 .this_type
1361 .as_ref()
1362 .map(|t| t.substitute_templates(bindings)),
1363 is_pure: data.is_pure,
1364 is_static: data.is_static,
1365 rejects_surplus_args: data.rejects_surplus_args,
1366 }),
1367 });
1368 }
1369 Atomic::TConditional { data } => {
1370 let param_name = &data.param_name;
1371 let new_subject = data.subject.substitute_templates(bindings);
1372 let new_if_true = data.if_true.substitute_templates(bindings);
1373 let new_if_false = data.if_false.substitute_templates(bindings);
1374
1375 let resolved = if let Some(name) = param_name {
1379 if let Some(bound) = bindings.get(name) {
1380 resolve_conditional_branch(
1381 &new_subject,
1382 bound,
1383 &new_if_true,
1384 &new_if_false,
1385 )
1386 } else {
1387 None
1388 }
1389 } else {
1390 None
1391 };
1392
1393 if let Some(branch) = resolved {
1394 for t in branch.types {
1395 result.add_type(t);
1396 }
1397 } else {
1398 result.add_type(Atomic::TConditional {
1399 data: Box::new(crate::atomic::ConditionalData {
1400 param_name: *param_name,
1401 subject: new_subject,
1402 if_true: new_if_true,
1403 if_false: new_if_false,
1404 }),
1405 });
1406 }
1407 }
1408 Atomic::TKeyOf { target } => {
1409 let new_target = target.substitute_templates(bindings);
1410 if let Some(resolved) = eval_key_of_type(&new_target) {
1411 result.merge_with(&resolved);
1412 } else {
1413 result.add_type(Atomic::TKeyOf {
1414 target: Box::new(new_target),
1415 });
1416 }
1417 }
1418 Atomic::TValueOf { target } => {
1419 let new_target = target.substitute_templates(bindings);
1420 if let Some(resolved) = eval_value_of_type(&new_target) {
1421 result.merge_with(&resolved);
1422 } else {
1423 result.add_type(Atomic::TValueOf {
1424 target: Box::new(new_target),
1425 });
1426 }
1427 }
1428 Atomic::TIntersection { parts } => {
1429 result.add_type(Atomic::TIntersection {
1430 parts: vec_to_type_params(
1431 parts
1432 .iter()
1433 .map(|p| p.substitute_templates(bindings))
1434 .collect(),
1435 ),
1436 });
1437 }
1438 Atomic::TNamedObject { fqcn, type_params } => {
1439 if type_params.is_empty() && !fqcn.contains('\\') {
1441 if let Some(resolved) = bindings.get(fqcn) {
1442 for t in &resolved.types {
1443 result.add_type(t.clone());
1444 }
1445 continue;
1446 }
1447 }
1448 let new_params: Vec<Type> = type_params
1449 .iter()
1450 .map(|p| p.substitute_templates(bindings))
1451 .collect();
1452 result.add_type(Atomic::TNamedObject {
1453 fqcn: *fqcn,
1454 type_params: vec_to_type_params(new_params),
1455 });
1456 }
1457 Atomic::TClassString(Some(param_name)) => {
1459 if let Some(resolved) = bindings.get(param_name) {
1460 for r_atomic in &resolved.types {
1461 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1462 Some(*fqcn)
1463 } else {
1464 None
1465 };
1466 result.add_type(Atomic::TClassString(cls_name));
1467 }
1468 } else {
1469 result.add_type(atomic.clone());
1470 }
1471 }
1472 Atomic::TInterfaceString(Some(param_name)) => {
1474 if let Some(resolved) = bindings.get(param_name) {
1475 for r_atomic in &resolved.types {
1476 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1477 Some(*fqcn)
1478 } else {
1479 None
1480 };
1481 result.add_type(Atomic::TInterfaceString(iface_name));
1482 }
1483 } else {
1484 result.add_type(atomic.clone());
1485 }
1486 }
1487 _ => {
1488 result.add_type(atomic.clone());
1489 }
1490 }
1491 }
1492 result
1493 }
1494
1495 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1501 where
1502 F: Fn(&str) -> Option<Type>,
1503 {
1504 self.resolve_conditional_inner(&lookup)
1505 }
1506
1507 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1508 where
1509 F: Fn(&str) -> Option<Type>,
1510 {
1511 let mut result = Type::empty();
1512 for atomic in self.types {
1513 match atomic {
1514 Atomic::TConditional { ref data } => {
1515 let (param_name, subject, if_true, if_false) = (
1516 &data.param_name,
1517 &data.subject,
1518 &data.if_true,
1519 &data.if_false,
1520 );
1521 let resolved =
1522 param_name
1523 .and_then(|name| lookup(name.as_ref()))
1524 .and_then(|arg_ty| {
1525 resolve_conditional_branch(subject, &arg_ty, if_true, if_false)
1526 });
1527
1528 if let Some(branch) = resolved {
1529 for t in branch.resolve_conditional_inner(lookup).types {
1531 result.add_type(t);
1532 }
1533 } else {
1534 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1537 result.add_type(t);
1538 }
1539 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1540 result.add_type(t);
1541 }
1542 }
1543 }
1544 other => result.add_type(other),
1545 }
1546 }
1547 result
1548 }
1549
1550 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1560 if other.is_mixed() {
1561 return true;
1562 }
1563 if self.is_never() {
1564 return true; }
1566 self.types.iter().all(|a| {
1567 let fits = |a: &Atomic| other.types.iter().any(|b| atomic_subtype(a, b));
1568 fits(a) || composite_parts(a).is_some_and(|parts| parts.iter().all(fits))
1569 })
1570 }
1571
1572 pub fn is_subtype_by_template_bounds(&self, other: &Type) -> bool {
1575 self.is_subtype_structural(other)
1576 || self.types.iter().all(|a| match a {
1577 Atomic::TTemplateParam { as_type, .. } => {
1578 as_type.is_subtype_by_template_bounds(other)
1579 }
1580 _ => other.accepts_atomic_structural(a),
1581 })
1582 }
1583
1584 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1588 if self.is_mixed() {
1589 return true;
1590 }
1591 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1592 }
1593
1594 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1597 let mut result = Type::empty();
1598 result.possibly_undefined = self.possibly_undefined;
1599 result.from_docblock = self.from_docblock;
1600 for atomic in &self.types {
1601 if f(atomic) {
1602 result.types.push(atomic.clone());
1603 }
1604 }
1605 result
1606 }
1607
1608 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1613 &self,
1614 keep: K,
1615 placeholder: P,
1616 replacement: Atomic,
1617 ) -> Type {
1618 let mut result = Type::empty();
1619 result.possibly_undefined = self.possibly_undefined;
1620 result.from_docblock = self.from_docblock;
1621 for atomic in &self.types {
1622 if keep(atomic) {
1623 result.add_type(atomic.clone());
1624 } else if placeholder(atomic) {
1625 result.add_type(replacement.clone());
1626 }
1627 }
1628 result
1629 }
1630
1631 pub fn possibly_undefined(mut self) -> Self {
1633 self.possibly_undefined = true;
1634 self
1635 }
1636
1637 pub fn from_docblock(mut self) -> Self {
1639 self.from_docblock = true;
1640 self
1641 }
1642
1643 pub fn falsy_stripped(mut self) -> Self {
1646 self.falsy_stripped = true;
1647 self
1648 }
1649
1650 pub fn possibly_absent_offset(mut self) -> Self {
1654 self.possibly_absent_offset = true;
1655 self
1656 }
1657}
1658
1659#[derive(Clone, PartialEq, Eq, Debug)]
1673enum ValueClass {
1674 Int,
1675 Float,
1676 True,
1677 False,
1678 String,
1679 Array,
1680 List,
1682 Object,
1683 Null,
1684 Top,
1687 LitInt(i64),
1690 LitFloat(i64, i64),
1692 LitString(Arc<str>),
1694 ClassString,
1696}
1697
1698impl ValueClass {
1699 fn includes(&self, b: &Self) -> bool {
1704 self == b
1705 || matches!(
1706 (self, b),
1707 (Self::Top, _)
1708 | (Self::Int, Self::LitInt(_))
1709 | (Self::Float, Self::LitFloat(..))
1710 | (Self::String, Self::LitString(_))
1711 | (Self::String, Self::ClassString)
1712 | (Self::Array, Self::List)
1713 )
1714 }
1715
1716 fn overlaps(&self, b: &Self) -> bool {
1718 self.includes(b)
1719 || b.includes(self)
1720 || matches!(
1721 (self, b),
1722 (Self::ClassString, Self::LitString(_)) | (Self::LitString(_), Self::ClassString)
1723 )
1724 }
1725}
1726
1727fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1733 use ValueClass::*;
1734 match a {
1735 Atomic::TInt
1738 | Atomic::TIntRange { .. }
1739 | Atomic::TPositiveInt
1740 | Atomic::TNegativeInt
1741 | Atomic::TNonNegativeInt => vec![Int],
1742 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1743 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1745 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1746 vec![LitFloat(*int_bits, *frac_bits)]
1747 }
1748 Atomic::TBool => vec![True, False],
1750 Atomic::TTrue => vec![True],
1751 Atomic::TFalse => vec![False],
1752 Atomic::TString
1754 | Atomic::TNonEmptyString
1755 | Atomic::TNumericString
1756 | Atomic::TCallableString => vec![String],
1757 Atomic::TClassString(_)
1758 | Atomic::TInterfaceString(_)
1759 | Atomic::TEnumString
1760 | Atomic::TTraitString => vec![ClassString],
1761 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1762 Atomic::TArray { .. }
1764 | Atomic::TNonEmptyArray { .. }
1765 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1766 Atomic::TList { .. }
1767 | Atomic::TNonEmptyList { .. }
1768 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1769 Atomic::TObject
1771 | Atomic::TObjectShape { .. }
1772 | Atomic::TNamedObject { .. }
1773 | Atomic::TStaticObject { .. }
1774 | Atomic::TSelf { .. }
1775 | Atomic::TParent { .. }
1776 | Atomic::TClosure { .. }
1777 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1778 Atomic::TNull => vec![Null],
1781 Atomic::TVoid => vec![Top],
1782 Atomic::TMixed => vec![Top],
1784 Atomic::TScalar => vec![Int, Float, True, False, String],
1785 Atomic::TNumeric => vec![Int, Float],
1788 Atomic::TCallable { .. }
1790 | Atomic::TNever
1791 | Atomic::TTemplateParam { .. }
1792 | Atomic::TKeyOf { .. }
1793 | Atomic::TValueOf { .. }
1794 | Atomic::TConditional { .. }
1795 | Atomic::TIntersection { .. } => Vec::new(),
1796 }
1797}
1798
1799fn subject_is_decidable(subject: &Atomic) -> bool {
1807 matches!(
1808 subject,
1809 Atomic::TNull
1810 | Atomic::TTrue
1811 | Atomic::TFalse
1812 | Atomic::TBool
1813 | Atomic::TString
1814 | Atomic::TClassString(None)
1815 | Atomic::TInt
1816 | Atomic::TFloat
1817 | Atomic::TLiteralString(_)
1818 | Atomic::TLiteralInt(_)
1819 | Atomic::TLiteralFloat(..)
1820 | Atomic::TArray { .. }
1821 | Atomic::TList { .. }
1822 | Atomic::TObject
1823 | Atomic::TObjectShape { .. }
1824 | Atomic::TMixed
1825 | Atomic::TScalar
1826 )
1827}
1828
1829fn resolve_conditional_branch(
1851 subject: &Type,
1852 arg_ty: &Type,
1853 if_true: &Type,
1854 if_false: &Type,
1855) -> Option<Type> {
1856 if subject.types.is_empty() || !subject.types.iter().all(subject_is_decidable) {
1857 return None;
1858 }
1859 if arg_ty.types.is_empty() {
1860 return None;
1861 }
1862 let subject_classes: Vec<ValueClass> = subject.types.iter().flat_map(value_classes).collect();
1863 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1864 if arg_classes.is_empty() {
1865 return None;
1867 }
1868 let all_match = arg_classes
1869 .iter()
1870 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1871 let any_overlap = arg_classes
1872 .iter()
1873 .any(|c| subject_classes.iter().any(|s| s.overlaps(c)));
1874 if all_match {
1875 Some(if_true.clone())
1876 } else if !any_overlap {
1877 Some(if_false.clone())
1878 } else {
1879 None
1880 }
1881}
1882
1883fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1891 match atomic {
1892 Atomic::TNamedObject { fqcn, type_params } => {
1896 !type_params.is_empty() || !fqcn.contains('\\')
1897 }
1898 Atomic::TTemplateParam { .. }
1899 | Atomic::TKeyOf { .. }
1900 | Atomic::TValueOf { .. }
1901 | Atomic::TArray { .. }
1902 | Atomic::TList { .. }
1903 | Atomic::TNonEmptyArray { .. }
1904 | Atomic::TNonEmptyList { .. }
1905 | Atomic::TKeyedArray { .. }
1906 | Atomic::TCallable { .. }
1907 | Atomic::TClosure { .. }
1908 | Atomic::TConditional { .. }
1909 | Atomic::TIntersection { .. }
1910 | Atomic::TClassString(Some(_))
1911 | Atomic::TInterfaceString(Some(_)) => true,
1912 _ => false,
1913 }
1914}
1915
1916pub fn eval_key_of_type(t: &Type) -> Option<Type> {
1917 let mut result = Type::empty();
1918 for atomic in &t.types {
1919 match atomic {
1920 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1921 for k in &key.types {
1922 result.add_type(k.clone());
1923 }
1924 }
1925 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1926 result.add_type(Atomic::TInt);
1927 }
1928 Atomic::TKeyedArray { properties, .. } => {
1929 for key in properties.keys() {
1930 match key {
1931 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1932 crate::atomic::ArrayKey::String(s) => {
1933 result.add_type(Atomic::TLiteralString(s.clone()))
1934 }
1935 }
1936 }
1937 }
1938 _ => return None,
1939 }
1940 }
1941 (!result.types.is_empty()).then_some(result)
1942}
1943
1944pub fn eval_value_of_type(t: &Type) -> Option<Type> {
1945 let mut result = Type::empty();
1946 for atomic in &t.types {
1947 match atomic {
1948 Atomic::TArray { value, .. }
1949 | Atomic::TNonEmptyArray { value, .. }
1950 | Atomic::TList { value }
1951 | Atomic::TNonEmptyList { value } => {
1952 result.merge_with(value);
1953 }
1954 Atomic::TKeyedArray { properties, .. } => {
1955 for prop in properties.values() {
1956 result.merge_with(&prop.ty);
1957 }
1958 }
1959 _ => return None,
1960 }
1961 }
1962 (!result.types.is_empty()).then_some(result)
1963}
1964
1965fn substitute_in_fn_param(
1966 p: &crate::atomic::FnParam,
1967 bindings: &FxHashMap<Name, Type>,
1968) -> crate::atomic::FnParam {
1969 crate::atomic::FnParam {
1970 name: p.name,
1971 ty: p.ty.as_ref().map(|t| {
1972 let u = t.to_union();
1973 let substituted = u.substitute_templates(bindings);
1974 crate::compact::SimpleType::from_union(substituted)
1975 }),
1976 out_ty: p.out_ty.as_ref().map(|t| {
1977 let u = t.to_union();
1978 let substituted = u.substitute_templates(bindings);
1979 crate::compact::SimpleType::from_union(substituted)
1980 }),
1981 default: p.default.as_ref().map(|d| {
1982 let u = d.to_union();
1983 let substituted = u.substitute_templates(bindings);
1984 crate::compact::SimpleType::from_union(substituted)
1985 }),
1986 is_variadic: p.is_variadic,
1987 is_byref: p.is_byref,
1988 is_optional: p.is_optional,
1989 }
1990}
1991
1992fn composite_parts(atomic: &Atomic) -> Option<&'static [Atomic]> {
1999 match atomic {
2000 Atomic::TScalar => Some(&[Atomic::TInt, Atomic::TFloat, Atomic::TString, Atomic::TBool]),
2001 Atomic::TNumeric => Some(&[Atomic::TInt, Atomic::TFloat, Atomic::TNumericString]),
2002 _ => None,
2003 }
2004}
2005
2006fn object_shape_subtype(have: &KeyedProperties, want: &KeyedProperties) -> bool {
2007 want.iter().all(|(name, wanted)| match have.get(name) {
2008 Some(had) => (wanted.optional || !had.optional) && had.ty.is_subtype_structural(&wanted.ty),
2009 None => wanted.optional,
2010 })
2011}
2012
2013pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
2015 if sub == sup {
2016 return true;
2017 }
2018 match (sub, sup) {
2019 (Atomic::TObjectShape { properties: have }, Atomic::TObjectShape { properties: want }) => {
2020 return object_shape_subtype(have, want);
2021 }
2022 (Atomic::TObjectShape { .. }, _) => return atomic_subtype(&Atomic::TObject, sup),
2023 (_, Atomic::TObjectShape { .. }) => return atomic_subtype(sub, &Atomic::TObject),
2025 _ => {}
2026 }
2027 match (sub, sup) {
2028 (Atomic::TNever, _) => true,
2030 (_, Atomic::TMixed) => true,
2032 (Atomic::TMixed, _) => true,
2033 (_, Atomic::TTemplateParam { as_type, .. }) => {
2038 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
2039 }
2040
2041 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
2043 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
2044 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
2045 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
2046 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
2047 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
2048 (Atomic::TPositiveInt, Atomic::TInt) => true,
2049 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
2050 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
2051 (Atomic::TPositiveInt, Atomic::TScalar) => true,
2052 (Atomic::TNegativeInt, Atomic::TInt) => true,
2053 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
2054 (Atomic::TNegativeInt, Atomic::TScalar) => true,
2055 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
2056 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
2057 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
2058 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
2059 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
2060 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
2061 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
2063 max.is_none() && min.is_none_or(|m| m <= 1)
2064 }
2065 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
2067 min.is_none() && max.is_none_or(|m| m >= -1)
2068 }
2069 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
2071 max.is_none() && min.is_none_or(|m| m <= 0)
2072 }
2073 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
2075 sub_min.is_some_and(|lo| lo >= 1)
2076 }
2077 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
2078 sub_min.is_some_and(|lo| lo >= 0)
2079 }
2080 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
2081 sub_max.is_some_and(|hi| hi <= -1)
2082 }
2083 (
2085 Atomic::TIntRange {
2086 min: sub_min,
2087 max: sub_max,
2088 },
2089 Atomic::TIntRange {
2090 min: sup_min,
2091 max: sup_max,
2092 },
2093 ) => {
2094 let lower_ok = match (sub_min, sup_min) {
2095 (_, None) => true,
2096 (None, Some(_)) => false,
2097 (Some(sl), Some(su)) => sl >= su,
2098 };
2099 let upper_ok = match (sub_max, sup_max) {
2100 (None, None) | (Some(_), None) => true,
2101 (None, Some(_)) => false,
2102 (Some(sl), Some(su)) => sl <= su,
2103 };
2104 lower_ok && upper_ok
2105 }
2106
2107 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
2108 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
2109 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
2110
2111 (Atomic::TLiteralString(s), Atomic::TString) => {
2112 let _ = s;
2113 true
2114 }
2115 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
2116 let _ = s;
2117 true
2118 }
2119 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
2120 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
2121 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
2124 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
2127 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
2128 (Atomic::TNonEmptyString, Atomic::TString) => true,
2129 (Atomic::TCallableString, Atomic::TString) => true,
2130 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
2132 (Atomic::TNumericString, Atomic::TString) => true,
2133 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
2137 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
2138 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
2139 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
2140 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
2141 (Atomic::TClassString(_), Atomic::TString) => true,
2142 (Atomic::TInterfaceString(_), Atomic::TString) => true,
2143 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
2148 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
2149 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
2150 (Atomic::TEnumString, Atomic::TString) => true,
2151 (Atomic::TTraitString, Atomic::TString) => true,
2152
2153 (Atomic::TTrue, Atomic::TBool) => true,
2154 (Atomic::TFalse, Atomic::TBool) => true,
2155
2156 (Atomic::TInt, Atomic::TNumeric) => true,
2157 (Atomic::TFloat, Atomic::TNumeric) => true,
2158 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
2159 (Atomic::TNumericString, Atomic::TNumeric) => true,
2160
2161 (Atomic::TInt, Atomic::TScalar) => true,
2162 (Atomic::TFloat, Atomic::TScalar) => true,
2163 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
2164 (Atomic::TString, Atomic::TScalar) => true,
2165 (Atomic::TBool, Atomic::TScalar) => true,
2166 (Atomic::TNumeric, Atomic::TScalar) => true,
2167 (Atomic::TTrue, Atomic::TScalar) => true,
2168 (Atomic::TFalse, Atomic::TScalar) => true,
2169 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
2173 (Atomic::TNumericString, Atomic::TScalar) => true,
2174 (Atomic::TCallableString, Atomic::TScalar) => true,
2175 (Atomic::TClassString(_), Atomic::TScalar) => true,
2176 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
2177 (Atomic::TEnumString, Atomic::TScalar) => true,
2178 (Atomic::TTraitString, Atomic::TScalar) => true,
2179
2180 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
2182 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
2183 (Atomic::TSelf { .. }, Atomic::TObject) => true,
2184 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
2186 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2188 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2189 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
2191 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
2192 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
2195 enum_fqcn == fqcn
2196 }
2197 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TSelf { fqcn })
2198 | (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TStaticObject { fqcn }) => {
2199 enum_fqcn == fqcn
2200 }
2201 (
2205 Atomic::TNamedObject {
2206 fqcn: sub_fqcn,
2207 type_params: sub_params,
2208 },
2209 Atomic::TNamedObject {
2210 fqcn: sup_fqcn,
2211 type_params: sup_params,
2212 },
2213 ) => {
2214 sub_fqcn == sup_fqcn
2215 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
2216 }
2217
2218 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
2220
2221 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
2223 (Atomic::TPositiveInt, Atomic::TFloat) => true,
2224 (Atomic::TNegativeInt, Atomic::TFloat) => true,
2225 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
2226 (Atomic::TInt, Atomic::TFloat) => true,
2227 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
2228
2229 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
2231 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
2232 }
2233
2234 (Atomic::TString, Atomic::TCallable { .. }) => true,
2236 (Atomic::TCallableString, Atomic::TCallable { .. }) => true,
2237 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
2238 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
2239 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
2240 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
2241 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
2242
2243 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
2245 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
2247 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
2257 fn has_nominal_type(t: &Type) -> bool {
2258 t.types.iter().any(|a| {
2259 matches!(
2260 a,
2261 Atomic::TNamedObject { .. }
2262 | Atomic::TSelf { .. }
2263 | Atomic::TStaticObject { .. }
2264 | Atomic::TTemplateParam { .. }
2265 | Atomic::TClosure { .. }
2266 | Atomic::TCallable { .. }
2267 )
2268 })
2269 }
2270 let sub_required = sub
2271 .params
2272 .iter()
2273 .filter(|p| !p.is_optional && !p.is_variadic)
2274 .count();
2275 if sub_required > sup.params.len() {
2276 false
2277 } else {
2278 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2279 let Some(sub_param) = sub.params.get(i) else {
2280 return true;
2281 };
2282 if sub_param.is_optional || sub_param.is_variadic {
2283 return true;
2284 }
2285 let (Some(sub_ty), Some(sup_ty)) =
2286 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2287 else {
2288 return true;
2289 };
2290 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2291 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2292 return true;
2293 }
2294 sup_u.is_subtype_structural(&sub_u)
2297 });
2298 params_ok
2299 && (sub.return_type.is_mixed()
2300 || sup.return_type.is_mixed()
2301 || sup.return_type.is_void()
2305 || has_nominal_type(&sub.return_type)
2306 || has_nominal_type(&sup.return_type)
2307 || sub.return_type.is_subtype_structural(&sup.return_type))
2308 }
2309 }
2310 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2312 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2314 fqcn.as_ref().eq_ignore_ascii_case("closure")
2315 }
2316 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2317 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2319 fqcn.as_ref().eq_ignore_ascii_case("closure")
2320 }
2321 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2323 fqcn.as_ref().eq_ignore_ascii_case("closure")
2324 }
2325
2326 (
2334 Atomic::TIntersection { parts: sub_parts },
2335 Atomic::TIntersection { parts: sup_parts },
2336 ) => sup_parts.iter().all(|sup_part| {
2337 sub_parts
2338 .iter()
2339 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2340 }),
2341 (Atomic::TIntersection { parts }, Atomic::TObject) => parts
2342 .iter()
2343 .any(|part| part.is_subtype_structural(&Type::single(Atomic::TObject))),
2344
2345 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2347 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2348 }
2349 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2350 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2351 }
2352 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2353 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2354 }
2355 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2356 value.is_subtype_structural(lv)
2357 }
2358 (Atomic::TNonEmptyList { value: v1 }, Atomic::TNonEmptyList { value: v2 }) => {
2359 v1.is_subtype_structural(v2)
2360 }
2361 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2363 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2364 && av.is_subtype_structural(lv)
2365 }
2366 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2367 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2368 && av.is_subtype_structural(lv)
2369 }
2370 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2371 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2372 && av.is_subtype_structural(lv)
2373 }
2374 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2375 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2376 && av.is_subtype_structural(lv)
2377 }
2378 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2380 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2381 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2382 }
2383
2384 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2386 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2387 }
2388
2389 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2398 properties.iter().all(|(prop_key, prop)| {
2399 let key_atomic = match prop_key {
2400 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2401 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2402 };
2403 if !Type::single(key_atomic).is_subtype_structural(key) {
2404 return false; }
2406 let has_named_obj = prop.ty.types.iter().any(|a| {
2408 matches!(
2409 a,
2410 Atomic::TNamedObject { .. }
2411 | Atomic::TSelf { .. }
2412 | Atomic::TStaticObject { .. }
2413 | Atomic::TClosure { .. }
2414 | Atomic::TTemplateParam { .. }
2415 )
2416 });
2417 has_named_obj || prop.ty.is_subtype_structural(value)
2418 })
2419 }
2420 (
2421 Atomic::TKeyedArray {
2422 properties,
2423 is_open,
2424 ..
2425 },
2426 Atomic::TNonEmptyArray { key, value },
2427 ) => {
2428 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2429 && properties.iter().all(|(prop_key, prop)| {
2430 let key_atomic = match prop_key {
2431 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2432 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2433 };
2434 if !Type::single(key_atomic).is_subtype_structural(key) {
2435 return false;
2436 }
2437 let has_named_obj = prop.ty.types.iter().any(|a| {
2438 matches!(
2439 a,
2440 Atomic::TNamedObject { .. }
2441 | Atomic::TSelf { .. }
2442 | Atomic::TStaticObject { .. }
2443 | Atomic::TClosure { .. }
2444 | Atomic::TTemplateParam { .. }
2445 )
2446 });
2447 has_named_obj || prop.ty.is_subtype_structural(value)
2448 })
2449 }
2450
2451 (
2453 Atomic::TKeyedArray {
2454 properties,
2455 is_list,
2456 ..
2457 },
2458 Atomic::TList { value: lv },
2459 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2460 (
2461 Atomic::TKeyedArray {
2462 properties,
2463 is_list,
2464 ..
2465 },
2466 Atomic::TNonEmptyList { value: lv },
2467 ) => {
2468 *is_list
2469 && !properties.is_empty()
2470 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2471 }
2472
2473 (
2478 Atomic::TKeyedArray {
2479 properties: sub_props,
2480 is_open: sub_open,
2481 ..
2482 },
2483 Atomic::TKeyedArray {
2484 properties: sup_props,
2485 is_open: sup_open,
2486 ..
2487 },
2488 ) => {
2489 let keys_satisfied = sup_props
2490 .iter()
2491 .all(|(key, sup_prop)| match sub_props.get(key) {
2492 Some(sub_prop) => {
2493 if !sup_prop.optional && sub_prop.optional {
2497 return false;
2498 }
2499 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2500 matches!(
2501 a,
2502 Atomic::TNamedObject { .. }
2503 | Atomic::TSelf { .. }
2504 | Atomic::TStaticObject { .. }
2505 | Atomic::TClosure { .. }
2506 | Atomic::TTemplateParam { .. }
2507 )
2508 });
2509 has_named_obj || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2510 }
2511 None => sup_prop.optional || *sub_open,
2512 });
2513 let no_undeclared_extras =
2514 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2515 keys_satisfied && no_undeclared_extras
2516 }
2517 (
2522 Atomic::TKeyedArray {
2523 properties,
2524 is_open,
2525 ..
2526 },
2527 Atomic::TIntersection { parts },
2528 ) => parts.iter().all(|part| {
2529 part.types.iter().any(|part_atomic| match part_atomic {
2530 Atomic::TKeyedArray {
2531 properties: sup_props,
2532 is_open: sup_open,
2533 ..
2534 } => {
2535 let keys_satisfied =
2536 sup_props
2537 .iter()
2538 .all(|(key, sup_prop)| match properties.get(key) {
2539 Some(sub_prop) => {
2540 if !sup_prop.optional && sub_prop.optional {
2541 return false;
2542 }
2543 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2544 matches!(
2545 a,
2546 Atomic::TNamedObject { .. }
2547 | Atomic::TSelf { .. }
2548 | Atomic::TStaticObject { .. }
2549 | Atomic::TClosure { .. }
2550 | Atomic::TTemplateParam { .. }
2551 )
2552 });
2553 has_named_obj
2554 || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2555 || bare_array_may_fit(&sub_prop.ty, &sup_prop.ty)
2556 }
2557 None => *is_open || sup_prop.optional,
2558 });
2559 let has_array_part = parts.iter().any(|part| {
2560 part.types
2561 .iter()
2562 .any(|t| matches!(t, Atomic::TArray { .. }))
2563 });
2564 let has_keyed_array_part = parts.iter().any(|part| {
2565 part.types
2566 .iter()
2567 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2568 });
2569 let keys_allowed_by_some_part = if has_array_part {
2570 true
2571 } else if has_keyed_array_part {
2572 properties.keys().all(|k| {
2573 parts.iter().any(|part| {
2574 part.types.iter().any(|t| {
2575 if let Atomic::TKeyedArray {
2576 properties: part_props,
2577 ..
2578 } = t
2579 {
2580 part_props.contains_key(k)
2581 } else {
2582 false
2583 }
2584 })
2585 })
2586 })
2587 } else {
2588 true
2589 };
2590 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2591 keys_satisfied && no_undeclared_extras
2592 }
2593 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2594 let key_atomic = match prop_key {
2595 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2596 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2597 };
2598 if !Type::single(key_atomic).is_subtype_structural(key) {
2599 return false;
2600 }
2601 let has_named_obj = prop.ty.types.iter().any(|a| {
2602 matches!(
2603 a,
2604 Atomic::TNamedObject { .. }
2605 | Atomic::TSelf { .. }
2606 | Atomic::TStaticObject { .. }
2607 | Atomic::TClosure { .. }
2608 | Atomic::TTemplateParam { .. }
2609 )
2610 });
2611 has_named_obj || prop.ty.is_subtype_structural(value)
2612 }),
2613 Atomic::TNamedObject { .. }
2614 | Atomic::TSelf { .. }
2615 | Atomic::TStaticObject { .. }
2616 | Atomic::TClosure { .. }
2617 | Atomic::TTemplateParam { .. } => true,
2618 _ => false,
2619 })
2620 }),
2621
2622 _ => false,
2623 }
2624}
2625
2626fn bare_array_may_fit(sub: &Type, sup: &Type) -> bool {
2628 fn array_like(t: &Atomic) -> bool {
2629 match t {
2630 Atomic::TArray { .. }
2631 | Atomic::TNonEmptyArray { .. }
2632 | Atomic::TList { .. }
2633 | Atomic::TNonEmptyList { .. }
2634 | Atomic::TKeyedArray { .. } => true,
2635 Atomic::TIntersection { parts } => parts.iter().all(|p| p.types.iter().all(array_like)),
2636 _ => false,
2637 }
2638 }
2639 matches!(
2640 sub.types.as_slice(),
2641 [Atomic::TArray { key, value }]
2642 if value.is_mixed()
2643 && (key.is_mixed()
2644 || matches!(key.types.as_slice(), [Atomic::TInt, Atomic::TString] | [Atomic::TString, Atomic::TInt]))
2645 ) && sup.types.iter().all(array_like)
2646}
2647
2648fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2654 if sub.len() != sup.len() {
2655 return false;
2656 }
2657 sub.iter()
2658 .zip(sup.iter())
2659 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2660}
2661
2662fn is_empty_array_literal(t: &Type) -> bool {
2665 !t.types.is_empty()
2666 && t.types.iter().all(
2667 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2668 )
2669}
2670
2671fn is_array_like(t: &Type) -> bool {
2673 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2674}
2675
2676pub struct UnionBuilder {
2683 ty: Type,
2684 strings: std::collections::HashSet<std::sync::Arc<str>>,
2685 ints: std::collections::HashSet<i64>,
2686 literals_independent: bool,
2688}
2689
2690impl Default for UnionBuilder {
2691 fn default() -> Self {
2692 Self::new()
2693 }
2694}
2695
2696impl UnionBuilder {
2697 pub fn new() -> Self {
2698 Self {
2699 ty: Type::empty(),
2700 strings: Default::default(),
2701 ints: Default::default(),
2702 literals_independent: true,
2703 }
2704 }
2705
2706 pub fn is_empty(&self) -> bool {
2707 self.ty.is_empty()
2708 }
2709
2710 pub fn merge(&mut self, other: &Type) {
2711 for a in &other.types {
2712 self.add(a.clone());
2713 }
2714 self.ty.possibly_undefined |= other.possibly_undefined;
2715 }
2716
2717 pub fn add(&mut self, atomic: Atomic) {
2718 let lone_never = matches!(self.ty.types.first(), Some(Atomic::TNever));
2720 if self.literals_independent && !lone_never {
2721 match &atomic {
2722 Atomic::TLiteralString(s) => {
2723 if self.strings.insert(s.clone()) {
2724 self.ty.types.push(atomic);
2725 }
2726 return;
2727 }
2728 Atomic::TLiteralInt(n) => {
2729 if self.ints.insert(*n) {
2730 self.ty.types.push(atomic);
2731 }
2732 return;
2733 }
2734 _ => {}
2735 }
2736 }
2737 let disturbs_literals = matches!(
2738 atomic,
2739 Atomic::TString | Atomic::TMixed | Atomic::TConditional { .. }
2740 ) || int_bounds(&atomic).is_some();
2741 self.ty.add_type(atomic);
2742 if disturbs_literals || lone_never {
2743 self.resync();
2744 }
2745 }
2746
2747 fn resync(&mut self) {
2748 self.strings.clear();
2749 self.ints.clear();
2750 self.literals_independent = true;
2751 for t in &self.ty.types {
2752 match t {
2753 Atomic::TLiteralString(s) => {
2754 self.strings.insert(s.clone());
2755 }
2756 Atomic::TLiteralInt(n) => {
2757 self.ints.insert(*n);
2758 }
2759 Atomic::TMixed | Atomic::TString => self.literals_independent = false,
2760 other if int_bounds(other).is_some() => self.literals_independent = false,
2761 _ => {}
2762 }
2763 }
2764 }
2765
2766 pub fn build(self) -> Type {
2767 self.ty
2768 }
2769}
2770
2771fn callable_array() -> Atomic {
2773 let mut properties = KeyedProperties::with_capacity_and_hasher(2, Default::default());
2774 properties.insert(
2775 ArrayKey::Int(0),
2776 KeyedProperty {
2777 ty: Type::from_vec(vec![Atomic::TClassString(None), Atomic::TObject]),
2778 optional: false,
2779 },
2780 );
2781 properties.insert(
2782 ArrayKey::Int(1),
2783 KeyedProperty {
2784 ty: Type::single(Atomic::TNonEmptyString),
2785 optional: false,
2786 },
2787 );
2788 Atomic::TKeyedArray {
2789 properties: Arc::new(properties),
2790 is_open: false,
2791 is_list: true,
2792 }
2793}
2794
2795#[cfg(test)]
2796mod tests {
2797 use std::sync::Arc;
2798
2799 use super::*;
2800
2801 fn conditional(
2802 param_name: Option<Name>,
2803 subject: Type,
2804 if_true: Type,
2805 if_false: Type,
2806 ) -> Atomic {
2807 Atomic::TConditional {
2808 data: Box::new(crate::atomic::ConditionalData {
2809 param_name,
2810 subject,
2811 if_true,
2812 if_false,
2813 }),
2814 }
2815 }
2816
2817 #[test]
2818 fn single_is_single() {
2819 let u = Type::single(Atomic::TString);
2820 assert!(u.is_single());
2821 assert!(!u.is_nullable());
2822 }
2823
2824 #[test]
2825 fn nullable_has_null() {
2826 let u = Type::nullable(Atomic::TString);
2827 assert!(u.is_nullable());
2828 assert_eq!(u.types.len(), 2);
2829 }
2830
2831 #[test]
2832 fn add_type_deduplicates() {
2833 let mut u = Type::single(Atomic::TString);
2834 u.add_type(Atomic::TString);
2835 assert_eq!(u.types.len(), 1);
2836 }
2837
2838 #[test]
2839 fn array_key_is_int_string() {
2840 let k = Type::array_key();
2841 assert!(k.is_array_key());
2842 assert_eq!(k.types.len(), 2);
2843 }
2844
2845 #[test]
2846 fn is_array_key_false_for_plain_int() {
2847 assert!(!Type::int().is_array_key());
2848 }
2849
2850 #[test]
2851 fn is_array_key_false_for_mixed() {
2852 assert!(!Type::mixed().is_array_key());
2853 }
2854
2855 #[test]
2856 fn is_array_key_false_for_int_string_null() {
2857 let mut u = Type::array_key();
2858 u.add_type(Atomic::TNull);
2859 assert!(!u.is_array_key());
2860 }
2861
2862 #[test]
2863 fn add_type_literal_subsumed_by_base() {
2864 let mut u = Type::single(Atomic::TInt);
2865 u.add_type(Atomic::TLiteralInt(42));
2866 assert_eq!(u.types.len(), 1);
2867 assert!(matches!(u.types[0], Atomic::TInt));
2868 }
2869
2870 #[test]
2871 fn true_then_false_merges_to_bool() {
2872 let mut u = Type::single(Atomic::TTrue);
2873 u.add_type(Atomic::TFalse);
2874 assert_eq!(u.types.len(), 1);
2875 assert!(matches!(u.types[0], Atomic::TBool));
2876 }
2877
2878 #[test]
2879 fn false_then_true_merges_to_bool() {
2880 let mut u = Type::single(Atomic::TFalse);
2881 u.add_type(Atomic::TTrue);
2882 assert_eq!(u.types.len(), 1);
2883 assert!(matches!(u.types[0], Atomic::TBool));
2884 }
2885
2886 #[test]
2887 fn true_alone_stays_true() {
2888 let u = Type::single(Atomic::TTrue);
2889 assert_eq!(u.types.len(), 1);
2890 assert!(matches!(u.types[0], Atomic::TTrue));
2891 }
2892
2893 #[test]
2894 fn true_false_merge_preserves_other_union_members() {
2895 let mut u = Type::single(Atomic::TTrue);
2896 u.add_type(Atomic::TNull);
2897 u.add_type(Atomic::TFalse);
2898 assert_eq!(u.types.len(), 2);
2899 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2900 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2901 }
2902
2903 #[test]
2904 fn add_type_base_widens_literals() {
2905 let mut u = Type::single(Atomic::TLiteralInt(1));
2906 u.add_type(Atomic::TLiteralInt(2));
2907 u.add_type(Atomic::TInt);
2908 assert_eq!(u.types.len(), 1);
2909 assert!(matches!(u.types[0], Atomic::TInt));
2910 }
2911
2912 #[test]
2913 fn mixed_subsumes_everything() {
2914 let mut u = Type::single(Atomic::TString);
2915 u.add_type(Atomic::TMixed);
2916 assert_eq!(u.types.len(), 1);
2917 assert!(u.is_mixed());
2918 }
2919
2920 #[test]
2921 fn remove_null() {
2922 let u = Type::nullable(Atomic::TString);
2923 let narrowed = u.remove_null();
2924 assert!(!narrowed.is_nullable());
2925 assert_eq!(narrowed.types.len(), 1);
2926 }
2927
2928 #[test]
2929 fn narrow_to_truthy_removes_null_false() {
2930 let mut u = Type::empty();
2931 u.add_type(Atomic::TString);
2932 u.add_type(Atomic::TNull);
2933 u.add_type(Atomic::TFalse);
2934 let truthy = u.narrow_to_truthy();
2935 assert!(!truthy.is_nullable());
2936 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2937 }
2938
2939 #[test]
2940 fn merge_combines_types() {
2941 let a = Type::single(Atomic::TString);
2942 let b = Type::single(Atomic::TInt);
2943 let merged = Type::merge(&a, &b);
2944 assert_eq!(merged.types.len(), 2);
2945 }
2946
2947 #[test]
2948 fn intersect_keeps_narrower_side_not_self() {
2949 let int_ty = Type::single(Atomic::TInt);
2953 let mut literals = Type::empty();
2954 literals.add_type(Atomic::TLiteralInt(1));
2955 literals.add_type(Atomic::TLiteralInt(2));
2956
2957 let narrowed = int_ty.intersect_with(&literals);
2958 assert_eq!(narrowed.types.len(), 2);
2959 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2960 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2961 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2962 }
2963
2964 #[test]
2965 fn subtype_literal_int_under_int() {
2966 let sub = Type::single(Atomic::TLiteralInt(5));
2967 let sup = Type::single(Atomic::TInt);
2968 assert!(sub.is_subtype_structural(&sup));
2969 }
2970
2971 #[test]
2972 fn subtype_never_is_bottom() {
2973 let never = Type::never();
2974 let string = Type::single(Atomic::TString);
2975 assert!(never.is_subtype_structural(&string));
2976 }
2977
2978 #[test]
2979 fn subtype_everything_under_mixed() {
2980 let string = Type::single(Atomic::TString);
2981 let mixed = Type::mixed();
2982 assert!(string.is_subtype_structural(&mixed));
2983 }
2984
2985 #[test]
2986 fn subtype_enum_case_under_own_enum() {
2987 let sub = Type::single(Atomic::TLiteralEnumCase {
2988 enum_fqcn: Name::new("RoundingMode"),
2989 case_name: Name::new("Unnecessary"),
2990 });
2991 let sup = Type::single(Atomic::TNamedObject {
2992 fqcn: Name::new("RoundingMode"),
2993 type_params: empty_type_params(),
2994 });
2995 assert!(sub.is_subtype_structural(&sup));
2996 }
2997
2998 #[test]
2999 fn enum_case_not_subtype_of_unrelated_enum() {
3000 let sub = Type::single(Atomic::TLiteralEnumCase {
3001 enum_fqcn: Name::new("RoundingMode"),
3002 case_name: Name::new("Unnecessary"),
3003 });
3004 let sup = Type::single(Atomic::TNamedObject {
3005 fqcn: Name::new("Suit"),
3006 type_params: empty_type_params(),
3007 });
3008 assert!(!sub.is_subtype_structural(&sup));
3009 }
3010
3011 #[test]
3012 fn subtype_enum_case_under_own_self_and_static() {
3013 let case = |enum_fqcn: &str| {
3014 Type::single(Atomic::TLiteralEnumCase {
3015 enum_fqcn: Name::new(enum_fqcn),
3016 case_name: Name::new("A"),
3017 })
3018 };
3019 let own = Type::single(Atomic::TSelf {
3020 fqcn: Name::new("Suit"),
3021 });
3022 let own_static = Type::single(Atomic::TStaticObject {
3023 fqcn: Name::new("Suit"),
3024 });
3025 assert!(case("Suit").is_subtype_structural(&own));
3026 assert!(case("Suit").is_subtype_structural(&own_static));
3027 assert!(!case("Other").is_subtype_structural(&own));
3028 }
3029
3030 #[test]
3031 fn subtype_enum_case_under_bare_object() {
3032 let sub = Type::single(Atomic::TLiteralEnumCase {
3033 enum_fqcn: Name::new("RoundingMode"),
3034 case_name: Name::new("Unnecessary"),
3035 });
3036 let sup = Type::single(Atomic::TObject);
3037 assert!(sub.is_subtype_structural(&sup));
3038 }
3039
3040 #[test]
3041 fn template_substitution() {
3042 let mut bindings = FxHashMap::default();
3043 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
3044
3045 let tmpl = Type::single(Atomic::TTemplateParam {
3046 name: Name::new("T"),
3047 as_type: Box::new(Type::mixed()),
3048 defining_entity: Name::new("MyClass"),
3049 });
3050
3051 let resolved = tmpl.substitute_templates(&bindings);
3052 assert_eq!(resolved.types.len(), 1);
3053 assert!(matches!(resolved.types[0], Atomic::TString));
3054 }
3055
3056 #[test]
3057 fn intersection_is_object() {
3058 let parts = vec![
3059 Type::single(Atomic::TNamedObject {
3060 fqcn: Name::new("Iterator"),
3061 type_params: empty_type_params(),
3062 }),
3063 Type::single(Atomic::TNamedObject {
3064 fqcn: Name::new("Countable"),
3065 type_params: empty_type_params(),
3066 }),
3067 ];
3068 let atomic = Atomic::TIntersection {
3069 parts: vec_to_type_params(parts),
3070 };
3071 assert!(atomic.is_object());
3072 assert!(!atomic.can_be_falsy());
3073 assert!(atomic.can_be_truthy());
3074 }
3075
3076 #[test]
3077 fn intersection_display_two_parts() {
3078 let parts = vec![
3079 Type::single(Atomic::TNamedObject {
3080 fqcn: Name::new("Iterator"),
3081 type_params: empty_type_params(),
3082 }),
3083 Type::single(Atomic::TNamedObject {
3084 fqcn: Name::new("Countable"),
3085 type_params: empty_type_params(),
3086 }),
3087 ];
3088 let u = Type::single(Atomic::TIntersection {
3089 parts: vec_to_type_params(parts),
3090 });
3091 assert_eq!(format!("{u}"), "Iterator&Countable");
3092 }
3093
3094 #[test]
3095 fn intersection_display_three_parts() {
3096 let parts = vec![
3097 Type::single(Atomic::TNamedObject {
3098 fqcn: Name::new("A"),
3099 type_params: empty_type_params(),
3100 }),
3101 Type::single(Atomic::TNamedObject {
3102 fqcn: Name::new("B"),
3103 type_params: empty_type_params(),
3104 }),
3105 Type::single(Atomic::TNamedObject {
3106 fqcn: Name::new("C"),
3107 type_params: empty_type_params(),
3108 }),
3109 ];
3110 let u = Type::single(Atomic::TIntersection {
3111 parts: vec_to_type_params(parts),
3112 });
3113 assert_eq!(format!("{u}"), "A&B&C");
3114 }
3115
3116 #[test]
3117 fn intersection_in_nullable_union_display() {
3118 let intersection = Atomic::TIntersection {
3119 parts: vec_to_type_params(vec![
3120 Type::single(Atomic::TNamedObject {
3121 fqcn: Name::new("Iterator"),
3122 type_params: empty_type_params(),
3123 }),
3124 Type::single(Atomic::TNamedObject {
3125 fqcn: Name::new("Countable"),
3126 type_params: empty_type_params(),
3127 }),
3128 ]),
3129 };
3130 let mut u = Type::single(intersection);
3131 u.add_type(Atomic::TNull);
3132 assert!(u.is_nullable());
3133 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
3134 }
3135
3136 fn t_param(name: &str) -> Type {
3139 Type::single(Atomic::TTemplateParam {
3140 name: Name::new(name),
3141 as_type: Box::new(Type::mixed()),
3142 defining_entity: Name::new("Fn"),
3143 })
3144 }
3145
3146 fn bindings_t_string() -> FxHashMap<Name, Type> {
3147 let mut b = FxHashMap::default();
3148 b.insert(Name::new("T"), Type::single(Atomic::TString));
3149 b
3150 }
3151
3152 #[test]
3153 fn substitute_non_empty_array_key_and_value() {
3154 let ty = Type::single(Atomic::TNonEmptyArray {
3155 key: Box::new(t_param("T")),
3156 value: Box::new(t_param("T")),
3157 });
3158 let result = ty.substitute_templates(&bindings_t_string());
3159 assert_eq!(result.types.len(), 1);
3160 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
3161 panic!("expected TNonEmptyArray");
3162 };
3163 assert!(matches!(key.types[0], Atomic::TString));
3164 assert!(matches!(value.types[0], Atomic::TString));
3165 }
3166
3167 #[test]
3168 fn substitute_non_empty_list_value() {
3169 let ty = Type::single(Atomic::TNonEmptyList {
3170 value: Box::new(t_param("T")),
3171 });
3172 let result = ty.substitute_templates(&bindings_t_string());
3173 let Atomic::TNonEmptyList { value } = &result.types[0] else {
3174 panic!("expected TNonEmptyList");
3175 };
3176 assert!(matches!(value.types[0], Atomic::TString));
3177 }
3178
3179 #[test]
3180 fn substitute_keyed_array_property_types() {
3181 use crate::atomic::{ArrayKey, KeyedProperty};
3182 let mut props = KeyedProperties::default();
3183 props.insert(
3184 ArrayKey::String(Arc::from("name")),
3185 KeyedProperty {
3186 ty: t_param("T"),
3187 optional: false,
3188 },
3189 );
3190 props.insert(
3191 ArrayKey::String(Arc::from("tag")),
3192 KeyedProperty {
3193 ty: t_param("T"),
3194 optional: true,
3195 },
3196 );
3197 let ty = Type::single(Atomic::TKeyedArray {
3198 properties: Arc::new(props),
3199 is_open: true,
3200 is_list: false,
3201 });
3202 let result = ty.substitute_templates(&bindings_t_string());
3203 let Atomic::TKeyedArray {
3204 properties,
3205 is_open,
3206 is_list,
3207 } = &result.types[0]
3208 else {
3209 panic!("expected TKeyedArray");
3210 };
3211 assert!(is_open);
3212 assert!(!is_list);
3213 assert!(matches!(
3214 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
3215 Atomic::TString
3216 ));
3217 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
3218 assert!(matches!(
3219 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
3220 Atomic::TString
3221 ));
3222 }
3223
3224 #[test]
3225 fn substitute_callable_params_and_return() {
3226 use crate::atomic::FnParam;
3227 let ty = Type::single(Atomic::TCallable {
3228 params: Some(Box::new(vec![FnParam {
3229 name: Name::new("x"),
3230 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3231 out_ty: None,
3232 default: None,
3233 is_variadic: false,
3234 is_byref: false,
3235 is_optional: false,
3236 }])),
3237 return_type: Some(Box::new(t_param("T"))),
3238 is_pure: false,
3239 });
3240 let result = ty.substitute_templates(&bindings_t_string());
3241 let Atomic::TCallable {
3242 params,
3243 return_type,
3244 ..
3245 } = &result.types[0]
3246 else {
3247 panic!("expected TCallable");
3248 };
3249 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
3250 let param_union = param_ty.to_union();
3251 assert!(matches!(param_union.types[0], Atomic::TString));
3252 let ret = return_type.as_ref().unwrap();
3253 assert!(matches!(ret.types[0], Atomic::TString));
3254 }
3255
3256 #[test]
3257 fn substitute_callable_bare_no_panic() {
3258 let ty = Type::single(Atomic::TCallable {
3260 params: None,
3261 return_type: None,
3262 is_pure: false,
3263 });
3264 let result = ty.substitute_templates(&bindings_t_string());
3265 assert!(matches!(
3266 result.types[0],
3267 Atomic::TCallable {
3268 params: None,
3269 return_type: None,
3270 ..
3271 }
3272 ));
3273 }
3274
3275 #[test]
3276 fn substitute_closure_params_return_and_this() {
3277 use crate::atomic::FnParam;
3278 let ty = Type::single(Atomic::TClosure {
3279 data: Box::new(crate::atomic::ClosureData {
3280 params: Box::new([FnParam {
3281 name: Name::new("a"),
3282 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3283 out_ty: None,
3284 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3285 is_variadic: true,
3286 is_byref: true,
3287 is_optional: true,
3288 }]),
3289 return_type: t_param("T"),
3290 this_type: Some(t_param("T")),
3291 is_pure: false,
3292 is_static: false,
3293 rejects_surplus_args: false,
3294 }),
3295 });
3296 let result = ty.substitute_templates(&bindings_t_string());
3297 let Atomic::TClosure { data } = &result.types[0] else {
3298 panic!("expected TClosure");
3299 };
3300 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
3301 let p = ¶ms[0];
3302 let ty_union = p.ty.as_ref().unwrap().to_union();
3303 let default_union = p.default.as_ref().unwrap().to_union();
3304 assert!(matches!(ty_union.types[0], Atomic::TString));
3305 assert!(matches!(default_union.types[0], Atomic::TString));
3306 assert!(p.is_variadic);
3308 assert!(p.is_byref);
3309 assert!(p.is_optional);
3310 assert!(matches!(return_type.types[0], Atomic::TString));
3311 assert!(matches!(
3312 this_type.as_ref().unwrap().types[0],
3313 Atomic::TString
3314 ));
3315 }
3316
3317 #[test]
3318 fn substitute_conditional_all_branches() {
3319 let ty = Type::single(conditional(
3320 None,
3321 t_param("T"),
3322 t_param("T"),
3323 Type::single(Atomic::TInt),
3324 ));
3325 let result = ty.substitute_templates(&bindings_t_string());
3326 let Atomic::TConditional { data } = &result.types[0] else {
3327 panic!("expected TConditional");
3328 };
3329 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
3330 assert!(matches!(subject.types[0], Atomic::TString));
3331 assert!(matches!(if_true.types[0], Atomic::TString));
3332 assert!(matches!(if_false.types[0], Atomic::TInt));
3333 }
3334
3335 #[test]
3336 fn resolve_conditional_is_null_non_null_arg() {
3337 let ty = Type::single(conditional(
3338 Some(Name::new("x")),
3339 Type::single(Atomic::TNull),
3340 Type::single(Atomic::TInt),
3341 Type::single(Atomic::TString),
3342 ));
3343 let result = ty.resolve_conditional_returns(|name| {
3344 if name == "x" {
3345 Some(Type::single(Atomic::TString)) } else {
3347 None
3348 }
3349 });
3350 assert!(result.types.len() == 1);
3351 assert!(matches!(result.types[0], Atomic::TString));
3352 }
3353
3354 #[test]
3355 fn resolve_conditional_is_null_null_arg() {
3356 let ty = Type::single(conditional(
3357 Some(Name::new("x")),
3358 Type::single(Atomic::TNull),
3359 Type::single(Atomic::TInt),
3360 Type::single(Atomic::TString),
3361 ));
3362 let result = ty.resolve_conditional_returns(|name| {
3363 if name == "x" {
3364 Some(Type::single(Atomic::TNull)) } else {
3366 None
3367 }
3368 });
3369 assert!(result.types.len() == 1);
3370 assert!(matches!(result.types[0], Atomic::TInt));
3371 }
3372
3373 #[test]
3374 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
3375 let mut nullable_str = Type::single(Atomic::TString);
3376 nullable_str.add_type(Atomic::TNull);
3377 let ty = Type::single(conditional(
3378 Some(Name::new("x")),
3379 Type::single(Atomic::TNull),
3380 Type::single(Atomic::TInt),
3381 Type::single(Atomic::TString),
3382 ));
3383 let result = ty.resolve_conditional_returns(|name| {
3384 if name == "x" {
3385 Some(nullable_str.clone())
3386 } else {
3387 None
3388 }
3389 });
3390 assert_eq!(result.types.len(), 2);
3392 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3393 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3394 }
3395
3396 #[test]
3397 fn resolve_conditional_nested_widens_inner_branch() {
3398 let inner = Type::single(conditional(
3401 Some(Name::new("x")),
3402 Type::single(Atomic::TString),
3403 Type::single(Atomic::TString),
3404 Type::single(Atomic::TFloat),
3405 ));
3406 let ty = Type::single(conditional(
3407 Some(Name::new("x")),
3408 Type::single(Atomic::TNull),
3409 Type::single(Atomic::TInt),
3410 inner,
3411 ));
3412 let result = ty.resolve_conditional_returns(|_| None);
3414 assert!(
3415 result
3416 .types
3417 .iter()
3418 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3419 "no TConditional should survive: {:?}",
3420 result.types
3421 );
3422 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3423 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3424 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
3425 }
3426
3427 #[test]
3428 fn resolve_conditional_nested_resolves_inner_branch() {
3429 let inner = Type::single(conditional(
3433 Some(Name::new("x")),
3434 Type::single(Atomic::TString),
3435 Type::single(Atomic::TString),
3436 Type::single(Atomic::TFloat),
3437 ));
3438 let ty = Type::single(conditional(
3439 Some(Name::new("x")),
3440 Type::single(Atomic::TNull),
3441 Type::single(Atomic::TInt),
3442 inner,
3443 ));
3444 let result = ty.resolve_conditional_returns(|name| {
3446 if name == "x" {
3447 Some(Type::single(Atomic::TString))
3448 } else {
3449 None
3450 }
3451 });
3452 assert!(
3453 result
3454 .types
3455 .iter()
3456 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3457 "no TConditional should survive: {:?}",
3458 result.types
3459 );
3460 assert_eq!(result.types.len(), 1);
3461 assert!(matches!(result.types[0], Atomic::TString));
3462 }
3463
3464 #[test]
3465 fn resolve_conditional_true_subject_bool_arg_widens() {
3466 let ty = Type::single(conditional(
3470 Some(Name::new("x")),
3471 Type::single(Atomic::TTrue),
3472 Type::single(Atomic::TInt),
3473 Type::single(Atomic::TString),
3474 ));
3475 let result = ty.resolve_conditional_returns(|name| {
3476 if name == "x" {
3477 Some(Type::single(Atomic::TBool))
3478 } else {
3479 None
3480 }
3481 });
3482 assert_eq!(result.types.len(), 2);
3483 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3484 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3485 }
3486
3487 #[test]
3488 fn resolve_conditional_true_subject_mixed_arg_widens() {
3489 let ty = Type::single(conditional(
3492 Some(Name::new("x")),
3493 Type::single(Atomic::TTrue),
3494 Type::single(Atomic::TInt),
3495 Type::single(Atomic::TString),
3496 ));
3497 let result = ty.resolve_conditional_returns(|name| {
3498 if name == "x" {
3499 Some(Type::mixed())
3500 } else {
3501 None
3502 }
3503 });
3504 assert_eq!(result.types.len(), 2);
3505 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3506 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3507 }
3508
3509 #[test]
3510 fn resolve_conditional_true_subject_literal_args() {
3511 let ty = Type::single(conditional(
3514 Some(Name::new("x")),
3515 Type::single(Atomic::TTrue),
3516 Type::single(Atomic::TInt),
3517 Type::single(Atomic::TString),
3518 ));
3519 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3520 if name == "x" {
3521 Some(Type::single(Atomic::TFalse))
3522 } else {
3523 None
3524 }
3525 });
3526 assert_eq!(false_arg.types.len(), 1);
3527 assert!(matches!(false_arg.types[0], Atomic::TString));
3528
3529 let true_arg = ty.resolve_conditional_returns(|name| {
3530 if name == "x" {
3531 Some(Type::single(Atomic::TTrue))
3532 } else {
3533 None
3534 }
3535 });
3536 assert_eq!(true_arg.types.len(), 1);
3537 assert!(matches!(true_arg.types[0], Atomic::TInt));
3538 }
3539
3540 #[test]
3541 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3542 let ty = Type::single(conditional(
3546 Some(Name::new("x")),
3547 Type::single(Atomic::TBool),
3548 Type::single(Atomic::TInt),
3549 Type::single(Atomic::TString),
3550 ));
3551 let result = ty.resolve_conditional_returns(|name| {
3552 if name == "x" {
3553 Some(Type::single(Atomic::TScalar))
3554 } else {
3555 None
3556 }
3557 });
3558 assert_eq!(result.types.len(), 2);
3559 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3560 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3561 }
3562
3563 #[test]
3564 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3565 let ty = Type::single(conditional(
3569 Some(Name::new("x")),
3570 Type::single(Atomic::TString),
3571 Type::single(Atomic::TInt),
3572 Type::single(Atomic::TBool),
3573 ));
3574 let result = ty.resolve_conditional_returns(|name| {
3575 if name == "x" {
3576 Some(Type::single(Atomic::TBool))
3577 } else {
3578 None
3579 }
3580 });
3581 assert_eq!(result.types.len(), 1);
3582 assert!(matches!(result.types[0], Atomic::TBool));
3583 }
3584
3585 #[test]
3586 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3587 let ty = Type::single(conditional(
3590 Some(Name::new("x")),
3591 Type::single(Atomic::TList {
3592 value: Box::new(Type::mixed()),
3593 }),
3594 Type::single(Atomic::TInt),
3595 Type::single(Atomic::TString),
3596 ));
3597 let result = ty.resolve_conditional_returns(|name| {
3598 if name == "x" {
3599 Some(Type::single(Atomic::TArray {
3600 key: Box::new(Type::mixed()),
3601 value: Box::new(Type::mixed()),
3602 }))
3603 } else {
3604 None
3605 }
3606 });
3607 assert_eq!(result.types.len(), 2);
3608 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3609 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3610 }
3611
3612 #[test]
3613 fn resolve_conditional_float_subject_int_literal_false_branch() {
3614 let ty = resolve_conditional_branch(
3617 &Type::single(Atomic::TFloat),
3618 &Type::single(Atomic::TLiteralInt(42)),
3619 &Type::single(Atomic::TInt),
3620 &Type::single(Atomic::TString),
3621 );
3622 assert!(matches!(
3623 ty.as_ref(),
3624 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3625 ));
3626 }
3627
3628 #[test]
3629 fn resolve_conditional_int_subject_float_literal_false_branch() {
3630 let ty = resolve_conditional_branch(
3631 &Type::single(Atomic::TInt),
3632 &Type::single(Atomic::TLiteralFloat(1, 0)),
3633 &Type::single(Atomic::TInt),
3634 &Type::single(Atomic::TString),
3635 );
3636 assert!(matches!(
3637 ty.as_ref(),
3638 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3639 ));
3640 }
3641
3642 #[test]
3643 fn resolve_conditional_float_subject_float_literal_then_branch() {
3644 let ty = resolve_conditional_branch(
3645 &Type::single(Atomic::TFloat),
3646 &Type::single(Atomic::TLiteralFloat(1, 5)),
3647 &Type::single(Atomic::TInt),
3648 &Type::single(Atomic::TString),
3649 );
3650 assert!(matches!(
3651 ty.as_ref(),
3652 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3653 ));
3654 }
3655
3656 #[test]
3657 fn resolve_conditional_int_subject_int_literal_then_branch() {
3658 let ty = resolve_conditional_branch(
3659 &Type::single(Atomic::TInt),
3660 &Type::single(Atomic::TLiteralInt(7)),
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::TInt)
3667 ));
3668 }
3669
3670 #[test]
3671 fn resolve_conditional_int_subject_float_arg_false_branch() {
3672 let ty = resolve_conditional_branch(
3675 &Type::single(Atomic::TInt),
3676 &Type::single(Atomic::TFloat),
3677 &Type::single(Atomic::TInt),
3678 &Type::single(Atomic::TString),
3679 );
3680 assert!(matches!(
3681 ty.as_ref(),
3682 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3683 ));
3684 }
3685
3686 #[test]
3687 fn resolve_conditional_array_subject_string_arg_false_branch() {
3688 let ty = resolve_conditional_branch(
3689 &Type::single(Atomic::TArray {
3690 key: Box::new(Type::mixed()),
3691 value: Box::new(Type::mixed()),
3692 }),
3693 &Type::single(Atomic::TString),
3694 &Type::single(Atomic::TInt),
3695 &Type::single(Atomic::TString),
3696 );
3697 assert!(matches!(
3698 ty.as_ref(),
3699 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3700 ));
3701 }
3702
3703 #[test]
3704 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3705 let ty = resolve_conditional_branch(
3706 &Type::single(Atomic::TArray {
3707 key: Box::new(Type::mixed()),
3708 value: Box::new(Type::mixed()),
3709 }),
3710 &Type::single(Atomic::TBool),
3711 &Type::single(Atomic::TInt),
3712 &Type::single(Atomic::TString),
3713 );
3714 assert!(matches!(
3715 ty.as_ref(),
3716 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3717 ));
3718 }
3719
3720 #[test]
3721 fn resolve_conditional_array_subject_mixed_arg_widens() {
3722 let ty = resolve_conditional_branch(
3726 &Type::single(Atomic::TArray {
3727 key: Box::new(Type::mixed()),
3728 value: Box::new(Type::mixed()),
3729 }),
3730 &Type::mixed(),
3731 &Type::single(Atomic::TInt),
3732 &Type::single(Atomic::TString),
3733 );
3734 assert!(ty.is_none());
3735 }
3736
3737 #[test]
3738 fn resolve_conditional_array_subject_list_arg_then_branch() {
3739 let ty = resolve_conditional_branch(
3742 &Type::single(Atomic::TArray {
3743 key: Box::new(Type::mixed()),
3744 value: Box::new(Type::mixed()),
3745 }),
3746 &Type::single(Atomic::TList {
3747 value: Box::new(Type::mixed()),
3748 }),
3749 &Type::single(Atomic::TInt),
3750 &Type::single(Atomic::TString),
3751 );
3752 assert!(matches!(
3753 ty.as_ref(),
3754 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3755 ));
3756 }
3757
3758 #[test]
3759 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3760 let ty = resolve_conditional_branch(
3762 &Type::single(Atomic::TList {
3763 value: Box::new(Type::mixed()),
3764 }),
3765 &Type::single(Atomic::TKeyedArray {
3766 properties: Arc::default(),
3767 is_open: false,
3768 is_list: true,
3769 }),
3770 &Type::single(Atomic::TInt),
3771 &Type::single(Atomic::TString),
3772 );
3773 assert!(matches!(
3774 ty.as_ref(),
3775 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3776 ));
3777 }
3778
3779 #[test]
3780 fn resolve_conditional_list_subject_list_string_union_widens() {
3781 let ty = resolve_conditional_branch(
3785 &Type::single(Atomic::TList {
3786 value: Box::new(Type::mixed()),
3787 }),
3788 &Type::from_vec(vec![
3789 Atomic::TList {
3790 value: Box::new(Type::mixed()),
3791 },
3792 Atomic::TString,
3793 ]),
3794 &Type::single(Atomic::TInt),
3795 &Type::single(Atomic::TString),
3796 );
3797 assert!(ty.is_none());
3798 }
3799
3800 #[test]
3801 fn resolve_conditional_numeric_subject_widens() {
3802 let ty = resolve_conditional_branch(
3806 &Type::single(Atomic::TNumeric),
3807 &Type::single(Atomic::TLiteralString("123".into())),
3808 &Type::single(Atomic::TInt),
3809 &Type::single(Atomic::TString),
3810 );
3811 assert!(ty.is_none());
3812 }
3813
3814 #[test]
3815 fn resolve_conditional_non_empty_string_subject_widens() {
3816 let ty = resolve_conditional_branch(
3819 &Type::single(Atomic::TNonEmptyString),
3820 &Type::single(Atomic::TString),
3821 &Type::single(Atomic::TInt),
3822 &Type::single(Atomic::TString),
3823 );
3824 assert!(ty.is_none());
3825 }
3826
3827 #[test]
3828 fn resolve_conditional_template_arg_widens() {
3829 let ty = resolve_conditional_branch(
3832 &Type::single(Atomic::TString),
3833 &t_param("T"),
3834 &Type::single(Atomic::TInt),
3835 &Type::single(Atomic::TString),
3836 );
3837 assert!(ty.is_none());
3838 }
3839
3840 #[test]
3841 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3842 let ty = resolve_conditional_branch(
3845 &Type::single(Atomic::TMixed),
3846 &Type::single(Atomic::TBool),
3847 &Type::single(Atomic::TInt),
3848 &Type::single(Atomic::TString),
3849 );
3850 assert!(matches!(
3851 ty.as_ref(),
3852 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3853 ));
3854 }
3855
3856 #[test]
3857 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3858 let ty = resolve_conditional_branch(
3861 &Type::single(Atomic::TScalar),
3862 &Type::single(Atomic::TNull),
3863 &Type::single(Atomic::TInt),
3864 &Type::single(Atomic::TString),
3865 );
3866 assert!(matches!(
3867 ty.as_ref(),
3868 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3869 ));
3870 }
3871
3872 #[test]
3873 fn resolve_conditional_object_subject_named_arg_then_branch() {
3874 let ty = resolve_conditional_branch(
3876 &Type::single(Atomic::TObject),
3877 &Type::single(Atomic::TNamedObject {
3878 fqcn: Name::new("Foo"),
3879 type_params: empty_type_params(),
3880 }),
3881 &Type::single(Atomic::TInt),
3882 &Type::single(Atomic::TString),
3883 );
3884 assert!(matches!(
3885 ty.as_ref(),
3886 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3887 ));
3888 }
3889
3890 #[test]
3891 fn resolve_conditional_named_subject_other_class_widens() {
3892 let ty = resolve_conditional_branch(
3896 &Type::single(Atomic::TNamedObject {
3897 fqcn: Name::new("Foo"),
3898 type_params: empty_type_params(),
3899 }),
3900 &Type::single(Atomic::TNamedObject {
3901 fqcn: Name::new("Bar"),
3902 type_params: empty_type_params(),
3903 }),
3904 &Type::single(Atomic::TInt),
3905 &Type::single(Atomic::TString),
3906 );
3907 assert!(ty.is_none());
3908 }
3909
3910 #[test]
3911 fn resolve_conditional_enum_case_subject_widens() {
3912 let ty = resolve_conditional_branch(
3915 &Type::single(Atomic::TLiteralEnumCase {
3916 enum_fqcn: Name::new("RoundingMode"),
3917 case_name: Name::new("Unnecessary"),
3918 }),
3919 &Type::single(Atomic::TInt),
3920 &Type::single(Atomic::TInt),
3921 &Type::single(Atomic::TString),
3922 );
3923 assert!(ty.is_none());
3924 }
3925
3926 #[test]
3927 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3928 let ty = resolve_conditional_branch(
3930 &Type::single(Atomic::TNull),
3931 &Type::single(Atomic::TBool),
3932 &Type::single(Atomic::TInt),
3933 &Type::single(Atomic::TString),
3934 );
3935 assert!(matches!(
3936 ty.as_ref(),
3937 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3938 ));
3939 }
3940
3941 #[test]
3942 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3943 let ty = resolve_conditional_branch(
3946 &Type::single(Atomic::TBool),
3947 &Type::single(Atomic::TTrue),
3948 &Type::single(Atomic::TInt),
3949 &Type::single(Atomic::TString),
3950 );
3951 assert!(matches!(
3952 ty.as_ref(),
3953 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3954 ));
3955 }
3956
3957 #[test]
3958 fn substitute_intersection_parts() {
3959 let ty = Type::single(Atomic::TIntersection {
3960 parts: vec_to_type_params(vec![
3961 Type::single(Atomic::TNamedObject {
3962 fqcn: Name::new("Countable"),
3963 type_params: empty_type_params(),
3964 }),
3965 t_param("T"),
3966 ]),
3967 });
3968 let result = ty.substitute_templates(&bindings_t_string());
3969 let Atomic::TIntersection { parts } = &result.types[0] else {
3970 panic!("expected TIntersection");
3971 };
3972 assert_eq!(parts.len(), 2);
3973 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3974 assert!(matches!(parts[1].types[0], Atomic::TString));
3975 }
3976
3977 fn shape(props: &[(&str, Atomic, bool)], is_open: bool) -> Atomic {
3978 Atomic::TKeyedArray {
3979 properties: Arc::new(
3980 props
3981 .iter()
3982 .map(|(k, ty, optional)| {
3983 (
3984 ArrayKey::String(Arc::from(*k)),
3985 KeyedProperty {
3986 ty: Type::single(ty.clone()),
3987 optional: *optional,
3988 },
3989 )
3990 })
3991 .collect(),
3992 ),
3993 is_open,
3994 is_list: false,
3995 }
3996 }
3997
3998 fn shape_with_bits(bits: u32, is_open: bool) -> Atomic {
4000 let names: Vec<String> = (0..5).map(|i| format!("k{i}")).collect();
4001 let mut props = vec![("a", Atomic::TInt, false)];
4002 props.extend(
4003 (0..5)
4004 .filter(|i| bits & (1 << i) != 0)
4005 .map(|i| (names[i].as_str(), Atomic::TString, false)),
4006 );
4007 shape(&props, is_open)
4008 }
4009
4010 fn keyed_count(ty: &Type) -> usize {
4011 ty.types
4012 .iter()
4013 .filter(|t| matches!(t, Atomic::TKeyedArray { .. }))
4014 .count()
4015 }
4016
4017 #[test]
4018 fn add_type_keeps_few_shapes_apart() {
4019 let mut ty = Type::empty();
4020 for bits in 0..MAX_KEYED_SHAPES as u32 {
4021 ty.add_type(shape_with_bits(bits, false));
4022 }
4023 assert_eq!(keyed_count(&ty), MAX_KEYED_SHAPES);
4024 }
4025
4026 #[test]
4027 fn add_type_folds_shapes_past_the_limit() {
4028 let mut ty = Type::empty();
4029 for bits in 0..32 {
4030 ty.add_type(shape_with_bits(bits, false));
4031 }
4032 assert!(keyed_count(&ty) <= MAX_KEYED_SHAPES);
4033 for t in &ty.types {
4035 let Atomic::TKeyedArray { properties, .. } = t else {
4036 continue;
4037 };
4038 assert!(!properties[&ArrayKey::String(Arc::from("a"))].optional);
4039 }
4040 for i in 0..5 {
4041 let key = ArrayKey::String(Arc::from(format!("k{i}").as_str()));
4042 assert!(ty.types.iter().any(|t| matches!(
4043 t,
4044 Atomic::TKeyedArray { properties, .. } if properties.contains_key(&key)
4045 )));
4046 }
4047 }
4048
4049 #[test]
4050 fn combine_keeps_emptiness_and_open_shape_soundness() {
4051 assert!(combine_keyed_arrays(
4053 &shape(&[], false),
4054 &shape(&[("a", Atomic::TInt, false)], false)
4055 )
4056 .is_none());
4057 assert!(combine_keyed_arrays(
4059 &shape(&[("a", Atomic::TInt, false)], true),
4060 &shape(&[("a", Atomic::TInt, false)], false)
4061 )
4062 .is_none());
4063 assert_eq!(
4065 combine_keyed_arrays(
4066 &shape(&[("a", Atomic::TInt, false)], true),
4067 &shape(
4068 &[("a", Atomic::TInt, false), ("k", Atomic::TString, false)],
4069 true
4070 )
4071 ),
4072 Some(shape(&[("a", Atomic::TInt, false)], true))
4073 );
4074 }
4075
4076 #[test]
4077 fn substitute_no_template_params_identity() {
4078 let ty = Type::single(Atomic::TInt);
4079 let result = ty.substitute_templates(&bindings_t_string());
4080 assert!(matches!(result.types[0], Atomic::TInt));
4081 }
4082}