1use rustc_hash::FxHashMap;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use std::sync::{Arc, OnceLock};
5
6use crate::atomic::Atomic;
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
75impl Type {
76 pub fn empty() -> Self {
79 Self {
80 types: SmallVec::new(),
81 possibly_undefined: false,
82 from_docblock: false,
83 falsy_stripped: false,
84 possibly_absent_offset: false,
85 }
86 }
87
88 pub fn single(atomic: Atomic) -> Self {
89 let mut types = SmallVec::new();
90 types.push(atomic);
91 Self {
92 types,
93 possibly_undefined: false,
94 from_docblock: false,
95 falsy_stripped: false,
96 possibly_absent_offset: false,
97 }
98 }
99
100 pub fn mixed() -> Self {
101 Self::single(Atomic::TMixed)
102 }
103
104 pub fn void() -> Self {
105 Self::single(Atomic::TVoid)
106 }
107
108 pub fn never() -> Self {
109 Self::single(Atomic::TNever)
110 }
111
112 pub fn null() -> Self {
113 Self::single(Atomic::TNull)
114 }
115
116 pub fn bool() -> Self {
117 Self::single(Atomic::TBool)
118 }
119
120 pub fn int() -> Self {
121 Self::single(Atomic::TInt)
122 }
123
124 pub fn float() -> Self {
125 Self::single(Atomic::TFloat)
126 }
127
128 pub fn string() -> Self {
129 Self::single(Atomic::TString)
130 }
131
132 pub fn array_key() -> Self {
135 let mut u = Self::single(Atomic::TInt);
136 u.add_type(Atomic::TString);
137 u
138 }
139
140 pub fn nullable(atomic: Atomic) -> Self {
142 if matches!(atomic, Atomic::TMixed) {
144 return Self::mixed();
145 }
146 let mut types = SmallVec::new();
147 types.push(atomic);
148 types.push(Atomic::TNull);
149 Self {
150 types,
151 possibly_undefined: false,
152 from_docblock: false,
153 falsy_stripped: false,
154 possibly_absent_offset: false,
155 }
156 }
157
158 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
160 let mut u = Self::empty();
161 for a in atomics {
162 u.add_type(a);
163 }
164 u
165 }
166
167 pub fn is_empty(&self) -> bool {
170 self.types.is_empty()
171 }
172
173 pub fn is_single(&self) -> bool {
174 self.types.len() == 1
175 }
176
177 pub fn is_nullable(&self) -> bool {
178 self.types.iter().any(|t| matches!(t, Atomic::TNull))
179 }
180
181 pub fn is_array_key(&self) -> bool {
186 self.types.len() == 2
187 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
188 && self.types.iter().any(|t| matches!(t, Atomic::TString))
189 }
190
191 pub fn is_mixed(&self) -> bool {
192 self.types.iter().any(|t| match t {
193 Atomic::TMixed => true,
194 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
195 _ => false,
196 })
197 }
198
199 pub fn is_mixed_not_template(&self) -> bool {
204 self.is_mixed()
205 && !self
206 .types
207 .iter()
208 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
209 }
210
211 pub fn is_never(&self) -> bool {
212 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
213 }
214
215 pub fn clone_validity(&self) -> CloneValidity {
218 if self.types.is_empty() {
219 return CloneValidity::Unknown;
220 }
221 let mut has_non_object = false;
222 let mut has_other = false; for t in &self.types {
224 match t {
225 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
226 CloneValidity::Invalid => has_non_object = true,
227 CloneValidity::PossiblyInvalid => {
228 has_non_object = true;
229 has_other = true;
230 }
231 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
232 },
233 other if other.is_definitely_non_object() => has_non_object = true,
234 _ => has_other = true,
235 }
236 }
237 match (has_non_object, has_other) {
238 (true, false) => CloneValidity::Invalid,
239 (true, true) => CloneValidity::PossiblyInvalid,
240 _ => CloneValidity::Cloneable,
241 }
242 }
243
244 pub fn is_void(&self) -> bool {
245 self.is_single() && matches!(self.types[0], Atomic::TVoid)
246 }
247
248 pub fn can_be_falsy(&self) -> bool {
249 self.types.iter().any(|t| t.can_be_falsy())
250 }
251
252 pub fn can_be_truthy(&self) -> bool {
253 self.types.iter().any(|t| t.can_be_truthy())
254 }
255
256 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
257 self.types.iter().any(f)
258 }
259
260 pub fn has_named_object(&self, fqcn: &str) -> bool {
261 self.types.iter().any(|t| match t {
262 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
263 _ => false,
264 })
265 }
266
267 pub fn add_type(&mut self, atomic: Atomic) {
272 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
274 return;
275 }
276
277 if matches!(atomic, Atomic::TMixed) {
279 self.types.clear();
280 self.types.push(Atomic::TMixed);
281 return;
282 }
283
284 let atomic = if let Atomic::TConditional { data } = &atomic {
287 let (if_true, if_false) = (&data.if_true, &data.if_false);
288 let mut simplified_true = Type::empty();
289 for t in &if_true.types {
290 simplified_true.add_type(t.clone());
291 }
292 let mut simplified_false = Type::empty();
293 for t in &if_false.types {
294 simplified_false.add_type(t.clone());
295 }
296 if simplified_true == simplified_false {
297 for t in simplified_true.types {
298 self.add_type(t);
299 }
300 return;
301 }
302 atomic
303 } else {
304 atomic
305 };
306
307 if self.types.contains(&atomic) {
309 return;
310 }
311
312 if let Atomic::TLiteralInt(_) = &atomic {
314 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
315 return;
316 }
317 }
318 if let Atomic::TLiteralString(_) = &atomic {
320 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
321 return;
322 }
323 }
324 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
326 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
327 {
328 return;
329 }
330 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
333 {
334 self.types.retain(|t| !matches!(t, Atomic::TFalse));
335 self.types.push(Atomic::TBool);
336 return;
337 }
338 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
339 {
340 self.types.retain(|t| !matches!(t, Atomic::TTrue));
341 self.types.push(Atomic::TBool);
342 return;
343 }
344 if matches!(atomic, Atomic::TInt) {
346 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
347 }
348 if matches!(atomic, Atomic::TString) {
350 self.types
351 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
352 }
353 if matches!(atomic, Atomic::TBool) {
355 self.types
356 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
357 }
358
359 if matches!(atomic, Atomic::TNever) {
361 if !self.types.is_empty() {
362 return;
363 }
364 } else {
365 self.types.retain(|t| !matches!(t, Atomic::TNever));
366 }
367
368 if let Atomic::TKeyedArray {
374 properties,
375 is_open,
376 ..
377 } = &atomic
378 {
379 if properties.is_empty() && !is_open {
380 for existing in &self.types {
381 match existing {
382 Atomic::TArray { .. }
383 | Atomic::TNonEmptyArray { .. }
384 | Atomic::TList { .. }
385 | Atomic::TNonEmptyList { .. } => {
386 return; }
388 _ => {}
389 }
390 }
391 }
392 }
393
394 let is_generic_array_or_list = matches!(
397 &atomic,
398 Atomic::TArray { .. }
399 | Atomic::TNonEmptyArray { .. }
400 | Atomic::TList { .. }
401 | Atomic::TNonEmptyList { .. }
402 );
403 if is_generic_array_or_list {
404 self.types.retain(|t| {
405 if let Atomic::TKeyedArray {
406 properties,
407 is_open,
408 ..
409 } = t
410 {
411 !properties.is_empty() || *is_open
412 } else {
413 true
414 }
415 });
416 }
417
418 self.types.push(atomic);
419 }
420
421 pub fn remove_null(&self) -> Type {
425 self.filter(|t| !matches!(t, Atomic::TNull))
426 }
427
428 pub fn remove_false(&self) -> Type {
431 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
432 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
433 result.add_type(Atomic::TTrue);
434 }
435 result
436 }
437
438 pub fn remove_true(&self) -> Type {
441 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
442 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
443 result.add_type(Atomic::TFalse);
444 }
445 result
446 }
447
448 pub fn core_type(&self) -> Type {
450 self.remove_null().remove_false()
451 }
452
453 pub fn narrow_to_truthy(&self) -> Type {
455 if self.is_mixed_not_template() {
456 return Type::mixed();
457 }
458 let mut result = Type::empty();
459 result.from_docblock = self.from_docblock;
460 for t in &self.types {
461 match t {
462 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
466 Atomic::TLiteralInt(0)
468 | Atomic::TLiteralFloat(0, 0)
469 | Atomic::TNull
470 | Atomic::TFalse => {}
471 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
472 Atomic::TBool => result.add_type(Atomic::TTrue),
474 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
476 key: key.clone(),
477 value: value.clone(),
478 }),
479 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
480 value: value.clone(),
481 }),
482 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
486 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
491 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
492 let atom = if max.is_none() {
493 Atomic::TPositiveInt
494 } else {
495 Atomic::TIntRange {
496 min: Some(1),
497 max: *max,
498 }
499 };
500 result.add_type(atom);
501 }
502 Atomic::TIntRange { min, max: Some(0) } => {
504 let atom = match min {
505 None => Atomic::TNegativeInt,
506 Some(n) if *n <= -1 => Atomic::TIntRange {
507 min: *min,
508 max: Some(-1),
509 },
510 _ => continue, };
512 result.add_type(atom);
513 }
514 t if !t.can_be_truthy() => {}
516 _ => result.add_type(t.clone()),
517 }
518 }
519 result
520 }
521
522 pub fn narrow_to_falsy(&self) -> Type {
524 if self.is_mixed_not_template() {
525 return Type::from_vec(vec![
526 Atomic::TNull,
527 Atomic::TFalse,
528 Atomic::TLiteralInt(0),
529 Atomic::TLiteralString("".into()),
530 ]);
531 }
532 let mut result = Type::empty();
533 result.from_docblock = self.from_docblock;
534 for t in &self.types {
535 match t {
536 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
541 Atomic::TBool => result.add_type(Atomic::TFalse),
543 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
545 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
547 Atomic::TString => {
549 result.add_type(Atomic::TLiteralString("".into()));
550 result.add_type(Atomic::TLiteralString("0".into()));
551 }
552 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
554 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
556 Atomic::TIntRange {
558 min: Some(0),
559 max: Some(_) | None,
560 } => result.add_type(Atomic::TLiteralInt(0)),
561 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
563 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
565 }
566 }
567 result
568 }
569
570 pub fn narrow_instanceof(&self, class: &str) -> Type {
576 let narrowed_ty = Atomic::TNamedObject {
577 fqcn: class.into(),
578 type_params: empty_type_params(),
579 };
580 let has_object = self.types.iter().any(|t| {
582 matches!(
583 t,
584 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
586 });
587 if has_object || self.is_empty() {
588 Type::single(narrowed_ty)
589 } else {
590 Type::single(narrowed_ty)
593 }
594 }
595
596 pub fn narrow_to_string(&self) -> Type {
600 self.filter_replacing(
601 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
602 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
603 Atomic::TString,
604 )
605 }
606
607 pub fn narrow_to_int(&self) -> Type {
609 self.filter_replacing(
610 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
611 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
612 Atomic::TInt,
613 )
614 }
615
616 pub fn narrow_to_float(&self) -> Type {
618 self.filter_replacing(
619 |t| {
620 matches!(
621 t,
622 Atomic::TFloat
623 | Atomic::TIntegralFloat
624 | Atomic::TLiteralFloat(..)
625 | Atomic::TTemplateParam { .. }
626 )
627 },
628 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
629 Atomic::TFloat,
630 )
631 }
632
633 pub fn narrow_to_bool(&self) -> Type {
635 self.filter_replacing(
636 |t| {
637 matches!(
638 t,
639 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
640 )
641 },
642 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
643 Atomic::TBool,
644 )
645 }
646
647 pub fn narrow_to_null(&self) -> Type {
649 self.filter_replacing(
650 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
651 |t| matches!(t, Atomic::TMixed),
652 Atomic::TNull,
653 )
654 }
655
656 pub fn narrow_to_array(&self) -> Type {
658 self.filter_replacing(
659 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
660 |t| matches!(t, Atomic::TMixed),
661 Atomic::TArray {
662 key: Box::new(Type::mixed()),
663 value: Box::new(Type::mixed()),
664 },
665 )
666 }
667
668 pub fn narrow_to_non_empty_collection(&self) -> Type {
670 let mut out = Type::empty();
671 out.from_docblock = self.from_docblock;
672 for t in &self.types {
673 match t {
674 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
675 key: key.clone(),
676 value: value.clone(),
677 }),
678 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
679 value: value.clone(),
680 }),
681 _ => out.add_type(t.clone()),
682 }
683 }
684 out
685 }
686
687 pub fn narrow_to_empty_collection(&self) -> Type {
697 let mut out = Type::empty();
698 out.from_docblock = self.from_docblock;
699 for t in &self.types {
700 match t {
701 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
702 Atomic::TArray { .. } | Atomic::TList { .. } => {
703 out.add_type(Atomic::TKeyedArray {
704 properties: Box::default(),
705 is_open: false,
706 is_list: true,
707 });
708 }
709 _ => out.add_type(t.clone()),
710 }
711 }
712 out
713 }
714
715 pub fn narrow_to_list(&self) -> Type {
727 let mut out = Type::empty();
728 out.from_docblock = self.from_docblock;
729 for t in &self.types {
730 match t {
731 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
732 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
738 out.add_type(Atomic::TList {
739 value: value.clone(),
740 });
741 }
742 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
743 out.add_type(Atomic::TNonEmptyList {
744 value: value.clone(),
745 });
746 }
747 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
748 Atomic::TMixed => out.add_type(Atomic::TList {
749 value: Box::new(Type::mixed()),
750 }),
751 _ => {}
752 }
753 }
754 if out.is_empty() {
755 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
756 } else {
757 out
758 }
759 }
760
761 pub fn narrow_to_object(&self) -> Type {
766 let mut out = Type::empty();
767 for t in &self.types {
768 if matches!(t, Atomic::TMixed) {
769 out.add_type(Atomic::TObject);
770 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
771 out.add_type(t.clone());
772 }
773 }
774 if out.types.is_empty() {
775 self.filter(|t| t.is_object())
776 } else {
777 out
778 }
779 }
780
781 pub fn narrow_to_callable(&self) -> Type {
788 let narrowed = self.filter(|t| {
789 t.is_callable()
790 || t.is_string()
791 || t.is_array()
792 || t.is_object()
793 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
794 });
795 let mut result = Type::empty();
802 result.possibly_undefined = narrowed.possibly_undefined;
803 result.from_docblock = narrowed.from_docblock;
804 for atomic in narrowed.types {
805 if matches!(atomic, Atomic::TObject) {
806 result.add_type(Atomic::TCallable {
807 params: None,
808 return_type: None,
809 });
810 } else {
811 result.add_type(atomic);
812 }
813 }
814 result
815 }
816
817 pub fn narrow_to_scalar(&self) -> Type {
819 self.filter_replacing(
820 |t| {
821 t.is_string()
822 || t.is_int()
823 || matches!(
824 t,
825 Atomic::TFloat
826 | Atomic::TIntegralFloat
827 | Atomic::TLiteralFloat(..)
828 | Atomic::TBool
829 | Atomic::TTrue
830 | Atomic::TFalse
831 | Atomic::TScalar
832 | Atomic::TNumeric
833 | Atomic::TNumericString
834 | Atomic::TTemplateParam { .. }
835 )
836 },
837 |t| matches!(t, Atomic::TMixed),
838 Atomic::TScalar,
839 )
840 }
841
842 pub fn narrow_to_iterable(&self) -> Type {
845 self.filter(|t| {
846 t.is_array()
847 || t.is_object()
848 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
849 })
850 }
851
852 pub fn narrow_to_countable(&self) -> Type {
855 self.filter(|t| {
856 t.is_array()
857 || t.is_object()
858 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
859 })
860 }
861
862 pub fn narrow_to_resource(&self) -> Type {
866 self.filter(|t| matches!(t, Atomic::TMixed))
868 }
869
870 pub fn narrow_to_class_string(&self) -> Type {
875 let mut out = Type::empty();
876 out.from_docblock = self.from_docblock;
877 for t in &self.types {
878 match t {
879 Atomic::TClassString(_) => out.add_type(t.clone()),
880 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
881 out.add_type(Atomic::TClassString(None));
882 }
883 _ => {}
884 }
885 }
886 out
887 }
888
889 pub fn narrow_to_interface_string(&self) -> Type {
894 let mut out = Type::empty();
895 out.from_docblock = self.from_docblock;
896 for t in &self.types {
897 match t {
898 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
899 Atomic::TClassString(name) => {
903 out.add_type(Atomic::TInterfaceString(*name));
904 }
905 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
906 out.add_type(Atomic::TInterfaceString(None));
907 }
908 _ => {}
909 }
910 }
911 out
912 }
913
914 pub fn merge(a: &Type, b: &Type) -> Type {
919 if b.types.is_empty() {
921 let mut result = a.clone();
922 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
923 return result;
924 }
925 if a.types.is_empty() {
927 let mut result = b.clone();
928 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
929 return result;
930 }
931 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
933 let mut result = a.clone();
934 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
935 return result;
936 }
937 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
939 return Type {
940 types: smallvec::smallvec![Atomic::TMixed],
941 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
942 from_docblock: a.from_docblock || b.from_docblock,
943 falsy_stripped: false,
944 possibly_absent_offset: false,
945 };
946 }
947 let mut result = a.clone();
948 result.merge_with(b);
949 result
950 }
951
952 pub fn merge_with(&mut self, other: &Type) {
954 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
955 self.possibly_undefined |= other.possibly_undefined;
956 return;
957 }
958 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
959 self.types.clear();
960 self.types.push(Atomic::TMixed);
961 self.possibly_undefined |= other.possibly_undefined;
962 return;
963 }
964 for atomic in &other.types {
965 self.add_type(atomic.clone());
966 }
967 self.possibly_undefined |= other.possibly_undefined;
968 }
969
970 pub fn intersect_with(&self, other: &Type) -> Type {
974 if self.is_mixed() {
975 return other.clone();
976 }
977 if other.is_mixed() {
978 return self.clone();
979 }
980 let mut result = Type::empty();
988 for a in &self.types {
989 for b in &other.types {
990 if a == b {
991 result.add_type(a.clone());
992 } else if atomic_subtype(b, a) {
993 result.add_type(b.clone());
994 } else if atomic_subtype(a, b) {
995 result.add_type(a.clone());
996 }
997 }
998 }
999 if result.is_empty() {
1000 Type::never()
1001 } else {
1002 result
1003 }
1004 }
1005
1006 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1010 if bindings.is_empty() {
1011 return self.clone();
1012 }
1013 if !self.types.iter().any(atomic_may_contain_templates) {
1016 return self.clone();
1017 }
1018 let mut result = Type::empty();
1019 result.possibly_undefined = self.possibly_undefined;
1020 result.from_docblock = self.from_docblock;
1021 for atomic in &self.types {
1022 match atomic {
1023 Atomic::TTemplateParam { name, .. } => {
1024 if let Some(resolved) = bindings.get(name) {
1025 for t in &resolved.types {
1026 result.add_type(t.clone());
1027 }
1028 } else {
1029 result.add_type(atomic.clone());
1030 }
1031 }
1032 Atomic::TArray { key, value } => {
1033 result.add_type(Atomic::TArray {
1034 key: Box::new(key.substitute_templates(bindings)),
1035 value: Box::new(value.substitute_templates(bindings)),
1036 });
1037 }
1038 Atomic::TList { value } => {
1039 result.add_type(Atomic::TList {
1040 value: Box::new(value.substitute_templates(bindings)),
1041 });
1042 }
1043 Atomic::TNonEmptyArray { key, value } => {
1044 result.add_type(Atomic::TNonEmptyArray {
1045 key: Box::new(key.substitute_templates(bindings)),
1046 value: Box::new(value.substitute_templates(bindings)),
1047 });
1048 }
1049 Atomic::TNonEmptyList { value } => {
1050 result.add_type(Atomic::TNonEmptyList {
1051 value: Box::new(value.substitute_templates(bindings)),
1052 });
1053 }
1054 Atomic::TKeyedArray {
1055 properties,
1056 is_open,
1057 is_list,
1058 } => {
1059 use crate::atomic::KeyedProperty;
1060 let new_props = properties
1061 .iter()
1062 .map(|(k, prop)| {
1063 (
1064 k.clone(),
1065 KeyedProperty {
1066 ty: prop.ty.substitute_templates(bindings),
1067 optional: prop.optional,
1068 },
1069 )
1070 })
1071 .collect();
1072 result.add_type(Atomic::TKeyedArray {
1073 properties: Box::new(new_props),
1074 is_open: *is_open,
1075 is_list: *is_list,
1076 });
1077 }
1078 Atomic::TCallable {
1079 params,
1080 return_type,
1081 } => {
1082 result.add_type(Atomic::TCallable {
1083 params: params.as_ref().map(|ps| {
1084 ps.iter()
1085 .map(|p| substitute_in_fn_param(p, bindings))
1086 .collect()
1087 }),
1088 return_type: return_type
1089 .as_ref()
1090 .map(|r| Box::new(r.substitute_templates(bindings))),
1091 });
1092 }
1093 Atomic::TClosure { data } => {
1094 result.add_type(Atomic::TClosure {
1095 data: Box::new(crate::atomic::ClosureData {
1096 params: data
1097 .params
1098 .iter()
1099 .map(|p| substitute_in_fn_param(p, bindings))
1100 .collect(),
1101 return_type: data.return_type.substitute_templates(bindings),
1102 this_type: data
1103 .this_type
1104 .as_ref()
1105 .map(|t| t.substitute_templates(bindings)),
1106 }),
1107 });
1108 }
1109 Atomic::TConditional { data } => {
1110 let param_name = &data.param_name;
1111 let new_subject = data.subject.substitute_templates(bindings);
1112 let new_if_true = data.if_true.substitute_templates(bindings);
1113 let new_if_false = data.if_false.substitute_templates(bindings);
1114
1115 let resolved = if let Some(name) = param_name {
1119 if let Some(bound) = bindings.get(name) {
1120 if new_subject.types.len() == 1 {
1121 resolve_conditional_branch(
1122 &new_subject.types[0],
1123 bound,
1124 &new_if_true,
1125 &new_if_false,
1126 )
1127 } else {
1128 None
1129 }
1130 } else {
1131 None
1132 }
1133 } else {
1134 None
1135 };
1136
1137 if let Some(branch) = resolved {
1138 for t in branch.types {
1139 result.add_type(t);
1140 }
1141 } else {
1142 result.add_type(Atomic::TConditional {
1143 data: Box::new(crate::atomic::ConditionalData {
1144 param_name: *param_name,
1145 subject: new_subject,
1146 if_true: new_if_true,
1147 if_false: new_if_false,
1148 }),
1149 });
1150 }
1151 }
1152 Atomic::TKeyOf { target } => {
1153 let new_target = target.substitute_templates(bindings);
1154 if let Some(resolved) = eval_key_of_type(&new_target) {
1155 result.merge_with(&resolved);
1156 } else {
1157 result.add_type(Atomic::TKeyOf {
1158 target: Box::new(new_target),
1159 });
1160 }
1161 }
1162 Atomic::TValueOf { target } => {
1163 let new_target = target.substitute_templates(bindings);
1164 if let Some(resolved) = eval_value_of_type(&new_target) {
1165 result.merge_with(&resolved);
1166 } else {
1167 result.add_type(Atomic::TValueOf {
1168 target: Box::new(new_target),
1169 });
1170 }
1171 }
1172 Atomic::TIntersection { parts } => {
1173 result.add_type(Atomic::TIntersection {
1174 parts: vec_to_type_params(
1175 parts
1176 .iter()
1177 .map(|p| p.substitute_templates(bindings))
1178 .collect(),
1179 ),
1180 });
1181 }
1182 Atomic::TNamedObject { fqcn, type_params } => {
1183 if type_params.is_empty() && !fqcn.contains('\\') {
1185 if let Some(resolved) = bindings.get(fqcn) {
1186 for t in &resolved.types {
1187 result.add_type(t.clone());
1188 }
1189 continue;
1190 }
1191 }
1192 let new_params: Vec<Type> = type_params
1193 .iter()
1194 .map(|p| p.substitute_templates(bindings))
1195 .collect();
1196 result.add_type(Atomic::TNamedObject {
1197 fqcn: *fqcn,
1198 type_params: vec_to_type_params(new_params),
1199 });
1200 }
1201 Atomic::TClassString(Some(param_name)) => {
1203 if let Some(resolved) = bindings.get(param_name) {
1204 for r_atomic in &resolved.types {
1205 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1206 Some(*fqcn)
1207 } else {
1208 None
1209 };
1210 result.add_type(Atomic::TClassString(cls_name));
1211 }
1212 } else {
1213 result.add_type(atomic.clone());
1214 }
1215 }
1216 Atomic::TInterfaceString(Some(param_name)) => {
1218 if let Some(resolved) = bindings.get(param_name) {
1219 for r_atomic in &resolved.types {
1220 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1221 Some(*fqcn)
1222 } else {
1223 None
1224 };
1225 result.add_type(Atomic::TInterfaceString(iface_name));
1226 }
1227 } else {
1228 result.add_type(atomic.clone());
1229 }
1230 }
1231 _ => {
1232 result.add_type(atomic.clone());
1233 }
1234 }
1235 }
1236 result
1237 }
1238
1239 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1245 where
1246 F: Fn(&str) -> Option<Type>,
1247 {
1248 self.resolve_conditional_inner(&lookup)
1249 }
1250
1251 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1252 where
1253 F: Fn(&str) -> Option<Type>,
1254 {
1255 let mut result = Type::empty();
1256 for atomic in self.types {
1257 match atomic {
1258 Atomic::TConditional { ref data } => {
1259 let (param_name, subject, if_true, if_false) = (
1260 &data.param_name,
1261 &data.subject,
1262 &data.if_true,
1263 &data.if_false,
1264 );
1265 let resolved = if subject.types.len() == 1 {
1266 if let Some(name) = param_name {
1267 if let Some(arg_ty) = lookup(name.as_ref()) {
1268 resolve_conditional_branch(
1269 &subject.types[0],
1270 &arg_ty,
1271 if_true,
1272 if_false,
1273 )
1274 } else {
1275 None
1276 }
1277 } else {
1278 None
1279 }
1280 } else {
1281 None
1282 };
1283
1284 if let Some(branch) = resolved {
1285 for t in branch.resolve_conditional_inner(lookup).types {
1287 result.add_type(t);
1288 }
1289 } else {
1290 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1293 result.add_type(t);
1294 }
1295 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1296 result.add_type(t);
1297 }
1298 }
1299 }
1300 other => result.add_type(other),
1301 }
1302 }
1303 result
1304 }
1305
1306 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1316 if other.is_mixed() {
1317 return true;
1318 }
1319 if self.is_never() {
1320 return true; }
1322 self.types
1323 .iter()
1324 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1325 }
1326
1327 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1331 if self.is_mixed() {
1332 return true;
1333 }
1334 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1335 }
1336
1337 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1340 let mut result = Type::empty();
1341 result.possibly_undefined = self.possibly_undefined;
1342 result.from_docblock = self.from_docblock;
1343 for atomic in &self.types {
1344 if f(atomic) {
1345 result.types.push(atomic.clone());
1346 }
1347 }
1348 result
1349 }
1350
1351 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1356 &self,
1357 keep: K,
1358 placeholder: P,
1359 replacement: Atomic,
1360 ) -> Type {
1361 let mut result = Type::empty();
1362 result.possibly_undefined = self.possibly_undefined;
1363 result.from_docblock = self.from_docblock;
1364 for atomic in &self.types {
1365 if keep(atomic) {
1366 result.add_type(atomic.clone());
1367 } else if placeholder(atomic) {
1368 result.add_type(replacement.clone());
1369 }
1370 }
1371 result
1372 }
1373
1374 pub fn possibly_undefined(mut self) -> Self {
1376 self.possibly_undefined = true;
1377 self
1378 }
1379
1380 pub fn from_docblock(mut self) -> Self {
1382 self.from_docblock = true;
1383 self
1384 }
1385
1386 pub fn falsy_stripped(mut self) -> Self {
1389 self.falsy_stripped = true;
1390 self
1391 }
1392
1393 pub fn possibly_absent_offset(mut self) -> Self {
1397 self.possibly_absent_offset = true;
1398 self
1399 }
1400}
1401
1402#[derive(Clone, PartialEq, Eq, Debug)]
1416enum ValueClass {
1417 Int,
1418 Float,
1419 True,
1420 False,
1421 String,
1422 Array,
1423 List,
1425 Object,
1426 Null,
1427 Top,
1430 LitInt(i64),
1433 LitFloat(i64, i64),
1435 LitString(Arc<str>),
1437}
1438
1439impl ValueClass {
1440 fn includes(&self, b: &Self) -> bool {
1445 self == b
1446 || matches!(
1447 (self, b),
1448 (Self::Top, _)
1449 | (Self::Int, Self::LitInt(_))
1450 | (Self::Float, Self::LitFloat(..))
1451 | (Self::String, Self::LitString(_))
1452 | (Self::Array, Self::List)
1453 )
1454 }
1455}
1456
1457fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1463 use ValueClass::*;
1464 match a {
1465 Atomic::TInt
1468 | Atomic::TIntRange { .. }
1469 | Atomic::TPositiveInt
1470 | Atomic::TNegativeInt
1471 | Atomic::TNonNegativeInt => vec![Int],
1472 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1473 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1475 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1476 vec![LitFloat(*int_bits, *frac_bits)]
1477 }
1478 Atomic::TBool => vec![True, False],
1480 Atomic::TTrue => vec![True],
1481 Atomic::TFalse => vec![False],
1482 Atomic::TString
1484 | Atomic::TNonEmptyString
1485 | Atomic::TNumericString
1486 | Atomic::TClassString(_)
1487 | Atomic::TInterfaceString(_)
1488 | Atomic::TEnumString
1489 | Atomic::TTraitString
1490 | Atomic::TCallableString => vec![String],
1491 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1492 Atomic::TArray { .. }
1494 | Atomic::TNonEmptyArray { .. }
1495 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1496 Atomic::TList { .. }
1497 | Atomic::TNonEmptyList { .. }
1498 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1499 Atomic::TObject
1501 | Atomic::TNamedObject { .. }
1502 | Atomic::TStaticObject { .. }
1503 | Atomic::TSelf { .. }
1504 | Atomic::TParent { .. }
1505 | Atomic::TClosure { .. }
1506 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1507 Atomic::TNull => vec![Null],
1510 Atomic::TVoid => vec![Top],
1511 Atomic::TMixed => vec![Top],
1513 Atomic::TScalar => vec![Int, Float, True, False, String],
1514 Atomic::TNumeric => vec![Int, Float],
1517 Atomic::TCallable { .. }
1519 | Atomic::TNever
1520 | Atomic::TTemplateParam { .. }
1521 | Atomic::TKeyOf { .. }
1522 | Atomic::TValueOf { .. }
1523 | Atomic::TConditional { .. }
1524 | Atomic::TIntersection { .. } => Vec::new(),
1525 }
1526}
1527
1528fn subject_is_decidable(subject: &Atomic) -> bool {
1536 matches!(
1537 subject,
1538 Atomic::TNull
1539 | Atomic::TTrue
1540 | Atomic::TFalse
1541 | Atomic::TBool
1542 | Atomic::TString
1543 | Atomic::TInt
1544 | Atomic::TFloat
1545 | Atomic::TArray { .. }
1546 | Atomic::TList { .. }
1547 | Atomic::TObject
1548 | Atomic::TMixed
1549 | Atomic::TScalar
1550 )
1551}
1552
1553fn resolve_conditional_branch(
1575 subject: &Atomic,
1576 arg_ty: &Type,
1577 if_true: &Type,
1578 if_false: &Type,
1579) -> Option<Type> {
1580 if !subject_is_decidable(subject) {
1581 return None;
1582 }
1583 if arg_ty.types.is_empty() {
1584 return None;
1585 }
1586 let subject_classes = value_classes(subject);
1587 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1588 if arg_classes.is_empty() {
1589 return None;
1591 }
1592 let all_match = arg_classes
1593 .iter()
1594 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1595 let any_overlap = arg_classes.iter().any(|c| {
1596 subject_classes
1597 .iter()
1598 .any(|s| s.includes(c) || c.includes(s))
1599 });
1600 if all_match {
1601 Some(if_true.clone())
1602 } else if !any_overlap {
1603 Some(if_false.clone())
1604 } else {
1605 None
1606 }
1607}
1608
1609fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1617 match atomic {
1618 Atomic::TNamedObject { fqcn, type_params } => {
1622 !type_params.is_empty() || !fqcn.contains('\\')
1623 }
1624 Atomic::TTemplateParam { .. }
1625 | Atomic::TKeyOf { .. }
1626 | Atomic::TValueOf { .. }
1627 | Atomic::TArray { .. }
1628 | Atomic::TList { .. }
1629 | Atomic::TNonEmptyArray { .. }
1630 | Atomic::TNonEmptyList { .. }
1631 | Atomic::TKeyedArray { .. }
1632 | Atomic::TCallable { .. }
1633 | Atomic::TClosure { .. }
1634 | Atomic::TConditional { .. }
1635 | Atomic::TIntersection { .. }
1636 | Atomic::TClassString(Some(_))
1637 | Atomic::TInterfaceString(Some(_)) => true,
1638 _ => false,
1639 }
1640}
1641
1642fn eval_key_of_type(t: &Type) -> Option<Type> {
1643 let mut result = Type::empty();
1644 for atomic in &t.types {
1645 match atomic {
1646 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1647 for k in &key.types {
1648 result.add_type(k.clone());
1649 }
1650 }
1651 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1652 result.add_type(Atomic::TInt);
1653 }
1654 Atomic::TKeyedArray { properties, .. } => {
1655 for key in properties.keys() {
1656 match key {
1657 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1658 crate::atomic::ArrayKey::String(s) => {
1659 result.add_type(Atomic::TLiteralString(s.clone()))
1660 }
1661 }
1662 }
1663 }
1664 _ => return None,
1665 }
1666 }
1667 (!result.types.is_empty()).then_some(result)
1668}
1669
1670fn eval_value_of_type(t: &Type) -> Option<Type> {
1671 let mut result = Type::empty();
1672 for atomic in &t.types {
1673 match atomic {
1674 Atomic::TArray { value, .. }
1675 | Atomic::TNonEmptyArray { value, .. }
1676 | Atomic::TList { value }
1677 | Atomic::TNonEmptyList { value } => {
1678 result.merge_with(value);
1679 }
1680 Atomic::TKeyedArray { properties, .. } => {
1681 for prop in properties.values() {
1682 result.merge_with(&prop.ty);
1683 }
1684 }
1685 _ => return None,
1686 }
1687 }
1688 (!result.types.is_empty()).then_some(result)
1689}
1690
1691fn substitute_in_fn_param(
1692 p: &crate::atomic::FnParam,
1693 bindings: &FxHashMap<Name, Type>,
1694) -> crate::atomic::FnParam {
1695 crate::atomic::FnParam {
1696 name: p.name,
1697 ty: p.ty.as_ref().map(|t| {
1698 let u = t.to_union();
1699 let substituted = u.substitute_templates(bindings);
1700 crate::compact::SimpleType::from_union(substituted)
1701 }),
1702 out_ty: p.out_ty.as_ref().map(|t| {
1703 let u = t.to_union();
1704 let substituted = u.substitute_templates(bindings);
1705 crate::compact::SimpleType::from_union(substituted)
1706 }),
1707 default: p.default.as_ref().map(|d| {
1708 let u = d.to_union();
1709 let substituted = u.substitute_templates(bindings);
1710 crate::compact::SimpleType::from_union(substituted)
1711 }),
1712 is_variadic: p.is_variadic,
1713 is_byref: p.is_byref,
1714 is_optional: p.is_optional,
1715 }
1716}
1717
1718pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1724 if sub == sup {
1725 return true;
1726 }
1727 match (sub, sup) {
1728 (Atomic::TNever, _) => true,
1730 (_, Atomic::TMixed) => true,
1732 (Atomic::TMixed, _) => true,
1733 (_, Atomic::TTemplateParam { as_type, .. }) => {
1738 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1739 }
1740
1741 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1743 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1744 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1745 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1746 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1747 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1748 (Atomic::TPositiveInt, Atomic::TInt) => true,
1749 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1750 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1751 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1752 (Atomic::TNegativeInt, Atomic::TInt) => true,
1753 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1754 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1755 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1756 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1757 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1758 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1759 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1760 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1761 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1763 max.is_none() && min.is_none_or(|m| m <= 1)
1764 }
1765 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1767 min.is_none() && max.is_none_or(|m| m >= -1)
1768 }
1769 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1771 max.is_none() && min.is_none_or(|m| m <= 0)
1772 }
1773 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1775 sub_min.is_some_and(|lo| lo >= 1)
1776 }
1777 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1778 sub_min.is_some_and(|lo| lo >= 0)
1779 }
1780 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1781 sub_max.is_some_and(|hi| hi <= -1)
1782 }
1783 (
1785 Atomic::TIntRange {
1786 min: sub_min,
1787 max: sub_max,
1788 },
1789 Atomic::TIntRange {
1790 min: sup_min,
1791 max: sup_max,
1792 },
1793 ) => {
1794 let lower_ok = match (sub_min, sup_min) {
1795 (_, None) => true,
1796 (None, Some(_)) => false,
1797 (Some(sl), Some(su)) => sl >= su,
1798 };
1799 let upper_ok = match (sub_max, sup_max) {
1800 (None, None) | (Some(_), None) => true,
1801 (None, Some(_)) => false,
1802 (Some(sl), Some(su)) => sl <= su,
1803 };
1804 lower_ok && upper_ok
1805 }
1806
1807 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1808 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1809 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1810
1811 (Atomic::TLiteralString(s), Atomic::TString) => {
1812 let _ = s;
1813 true
1814 }
1815 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1816 let _ = s;
1817 true
1818 }
1819 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1820 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1821 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1824 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1827 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1828 (Atomic::TNonEmptyString, Atomic::TString) => true,
1829 (Atomic::TCallableString, Atomic::TString) => true,
1830 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1832 (Atomic::TNumericString, Atomic::TString) => true,
1833 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1837 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1838 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1839 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1840 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1841 (Atomic::TClassString(_), Atomic::TString) => true,
1842 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1843 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1848 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
1849 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1850 (Atomic::TEnumString, Atomic::TString) => true,
1851 (Atomic::TTraitString, Atomic::TString) => true,
1852
1853 (Atomic::TTrue, Atomic::TBool) => true,
1854 (Atomic::TFalse, Atomic::TBool) => true,
1855
1856 (Atomic::TInt, Atomic::TNumeric) => true,
1857 (Atomic::TFloat, Atomic::TNumeric) => true,
1858 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1859 (Atomic::TNumericString, Atomic::TNumeric) => true,
1860
1861 (Atomic::TInt, Atomic::TScalar) => true,
1862 (Atomic::TFloat, Atomic::TScalar) => true,
1863 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1864 (Atomic::TString, Atomic::TScalar) => true,
1865 (Atomic::TBool, Atomic::TScalar) => true,
1866 (Atomic::TNumeric, Atomic::TScalar) => true,
1867 (Atomic::TTrue, Atomic::TScalar) => true,
1868 (Atomic::TFalse, Atomic::TScalar) => true,
1869 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1873 (Atomic::TNumericString, Atomic::TScalar) => true,
1874 (Atomic::TCallableString, Atomic::TScalar) => true,
1875 (Atomic::TClassString(_), Atomic::TScalar) => true,
1876 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1877 (Atomic::TEnumString, Atomic::TScalar) => true,
1878 (Atomic::TTraitString, Atomic::TScalar) => true,
1879
1880 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1882 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1883 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1884 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
1886 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1888 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1889 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1891 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1892 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
1895 enum_fqcn == fqcn
1896 }
1897 (
1901 Atomic::TNamedObject {
1902 fqcn: sub_fqcn,
1903 type_params: sub_params,
1904 },
1905 Atomic::TNamedObject {
1906 fqcn: sup_fqcn,
1907 type_params: sup_params,
1908 },
1909 ) => {
1910 sub_fqcn == sup_fqcn
1911 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1912 }
1913
1914 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1916
1917 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1919 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1920 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1921 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1922 (Atomic::TInt, Atomic::TFloat) => true,
1923 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1924
1925 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1927 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1928 }
1929
1930 (Atomic::TString, Atomic::TCallable { .. }) => true,
1932 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1933 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1934 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1935 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1936 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1937
1938 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1940 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1942 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1952 fn has_nominal_type(t: &Type) -> bool {
1953 t.types.iter().any(|a| {
1954 matches!(
1955 a,
1956 Atomic::TNamedObject { .. }
1957 | Atomic::TSelf { .. }
1958 | Atomic::TStaticObject { .. }
1959 | Atomic::TTemplateParam { .. }
1960 | Atomic::TClosure { .. }
1961 | Atomic::TCallable { .. }
1962 )
1963 })
1964 }
1965 let sub_required = sub
1966 .params
1967 .iter()
1968 .filter(|p| !p.is_optional && !p.is_variadic)
1969 .count();
1970 if sub_required > sup.params.len() {
1971 false
1972 } else {
1973 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
1974 let Some(sub_param) = sub.params.get(i) else {
1975 return true;
1976 };
1977 if sub_param.is_optional || sub_param.is_variadic {
1978 return true;
1979 }
1980 let (Some(sub_ty), Some(sup_ty)) =
1981 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
1982 else {
1983 return true;
1984 };
1985 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
1986 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
1987 return true;
1988 }
1989 sup_u.is_subtype_structural(&sub_u)
1992 });
1993 params_ok
1994 && (sub.return_type.is_mixed()
1995 || sup.return_type.is_mixed()
1996 || sup.return_type.is_void()
2000 || has_nominal_type(&sub.return_type)
2001 || has_nominal_type(&sup.return_type)
2002 || sub.return_type.is_subtype_structural(&sup.return_type))
2003 }
2004 }
2005 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2007 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2009 fqcn.as_ref().eq_ignore_ascii_case("closure")
2010 }
2011 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2012 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2014 fqcn.as_ref().eq_ignore_ascii_case("closure")
2015 }
2016 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2018 fqcn.as_ref().eq_ignore_ascii_case("closure")
2019 }
2020
2021 (
2029 Atomic::TIntersection { parts: sub_parts },
2030 Atomic::TIntersection { parts: sup_parts },
2031 ) => sup_parts.iter().all(|sup_part| {
2032 sub_parts
2033 .iter()
2034 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2035 }),
2036
2037 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2039 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2040 }
2041 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2042 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2043 }
2044 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2045 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2046 }
2047 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2048 value.is_subtype_structural(lv)
2049 }
2050 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2052 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2053 && av.is_subtype_structural(lv)
2054 }
2055 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2056 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2057 && av.is_subtype_structural(lv)
2058 }
2059 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2060 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2061 && av.is_subtype_structural(lv)
2062 }
2063 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2064 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2065 && av.is_subtype_structural(lv)
2066 }
2067 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2069 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2070 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2071 }
2072
2073 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2075 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2076 }
2077
2078 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2087 properties.iter().all(|(prop_key, prop)| {
2088 let key_atomic = match prop_key {
2089 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2090 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2091 };
2092 if !Type::single(key_atomic).is_subtype_structural(key) {
2093 return false; }
2095 let has_named_obj = prop.ty.types.iter().any(|a| {
2097 matches!(
2098 a,
2099 Atomic::TNamedObject { .. }
2100 | Atomic::TSelf { .. }
2101 | Atomic::TStaticObject { .. }
2102 | Atomic::TClosure { .. }
2103 | Atomic::TTemplateParam { .. }
2104 )
2105 });
2106 has_named_obj || prop.ty.is_subtype_structural(value)
2107 })
2108 }
2109 (
2110 Atomic::TKeyedArray {
2111 properties,
2112 is_open,
2113 ..
2114 },
2115 Atomic::TNonEmptyArray { key, value },
2116 ) => {
2117 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2118 && properties.iter().all(|(prop_key, prop)| {
2119 let key_atomic = match prop_key {
2120 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2121 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2122 };
2123 if !Type::single(key_atomic).is_subtype_structural(key) {
2124 return false;
2125 }
2126 let has_named_obj = prop.ty.types.iter().any(|a| {
2127 matches!(
2128 a,
2129 Atomic::TNamedObject { .. }
2130 | Atomic::TSelf { .. }
2131 | Atomic::TStaticObject { .. }
2132 | Atomic::TClosure { .. }
2133 | Atomic::TTemplateParam { .. }
2134 )
2135 });
2136 has_named_obj || prop.ty.is_subtype_structural(value)
2137 })
2138 }
2139
2140 (
2142 Atomic::TKeyedArray {
2143 properties,
2144 is_list,
2145 ..
2146 },
2147 Atomic::TList { value: lv },
2148 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2149 (
2150 Atomic::TKeyedArray {
2151 properties,
2152 is_list,
2153 ..
2154 },
2155 Atomic::TNonEmptyList { value: lv },
2156 ) => {
2157 *is_list
2158 && !properties.is_empty()
2159 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2160 }
2161
2162 (
2167 Atomic::TKeyedArray {
2168 properties: sub_props,
2169 is_open: sub_open,
2170 ..
2171 },
2172 Atomic::TKeyedArray {
2173 properties: sup_props,
2174 is_open: sup_open,
2175 ..
2176 },
2177 ) => {
2178 let keys_satisfied = sup_props
2179 .iter()
2180 .all(|(key, sup_prop)| match sub_props.get(key) {
2181 Some(sub_prop) => {
2182 if !sup_prop.optional && sub_prop.optional {
2186 return false;
2187 }
2188 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2189 matches!(
2190 a,
2191 Atomic::TNamedObject { .. }
2192 | Atomic::TSelf { .. }
2193 | Atomic::TStaticObject { .. }
2194 | Atomic::TClosure { .. }
2195 | Atomic::TTemplateParam { .. }
2196 )
2197 });
2198 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2199 }
2200 None => sup_prop.optional || *sub_open,
2201 });
2202 let no_undeclared_extras =
2203 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2204 keys_satisfied && no_undeclared_extras
2205 }
2206 (
2211 Atomic::TKeyedArray {
2212 properties,
2213 is_open,
2214 ..
2215 },
2216 Atomic::TIntersection { parts },
2217 ) => parts.iter().all(|part| {
2218 part.types.iter().any(|part_atomic| match part_atomic {
2219 Atomic::TKeyedArray {
2220 properties: sup_props,
2221 is_open: sup_open,
2222 ..
2223 } => {
2224 let keys_satisfied =
2225 sup_props
2226 .iter()
2227 .all(|(key, sup_prop)| match properties.get(key) {
2228 Some(sub_prop) => {
2229 if !sup_prop.optional && sub_prop.optional {
2230 return false;
2231 }
2232 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2233 matches!(
2234 a,
2235 Atomic::TNamedObject { .. }
2236 | Atomic::TSelf { .. }
2237 | Atomic::TStaticObject { .. }
2238 | Atomic::TClosure { .. }
2239 | Atomic::TTemplateParam { .. }
2240 )
2241 });
2242 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2243 }
2244 None => *is_open || sup_prop.optional,
2245 });
2246 let has_array_part = parts.iter().any(|part| {
2247 part.types
2248 .iter()
2249 .any(|t| matches!(t, Atomic::TArray { .. }))
2250 });
2251 let has_keyed_array_part = parts.iter().any(|part| {
2252 part.types
2253 .iter()
2254 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2255 });
2256 let keys_allowed_by_some_part = if has_array_part {
2257 true
2258 } else if has_keyed_array_part {
2259 properties.keys().all(|k| {
2260 parts.iter().any(|part| {
2261 part.types.iter().any(|t| {
2262 if let Atomic::TKeyedArray {
2263 properties: part_props,
2264 ..
2265 } = t
2266 {
2267 part_props.contains_key(k)
2268 } else {
2269 false
2270 }
2271 })
2272 })
2273 })
2274 } else {
2275 true
2276 };
2277 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2278 keys_satisfied && no_undeclared_extras
2279 }
2280 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2281 let key_atomic = match prop_key {
2282 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2283 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2284 };
2285 if !Type::single(key_atomic).is_subtype_structural(key) {
2286 return false;
2287 }
2288 let has_named_obj = prop.ty.types.iter().any(|a| {
2289 matches!(
2290 a,
2291 Atomic::TNamedObject { .. }
2292 | Atomic::TSelf { .. }
2293 | Atomic::TStaticObject { .. }
2294 | Atomic::TClosure { .. }
2295 | Atomic::TTemplateParam { .. }
2296 )
2297 });
2298 has_named_obj || prop.ty.is_subtype_structural(value)
2299 }),
2300 Atomic::TNamedObject { .. }
2301 | Atomic::TSelf { .. }
2302 | Atomic::TStaticObject { .. }
2303 | Atomic::TClosure { .. }
2304 | Atomic::TTemplateParam { .. } => true,
2305 _ => false,
2306 })
2307 }),
2308
2309 _ => false,
2310 }
2311}
2312
2313fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2319 if sub.len() != sup.len() {
2320 return false;
2321 }
2322 sub.iter()
2323 .zip(sup.iter())
2324 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2325}
2326
2327fn is_empty_array_literal(t: &Type) -> bool {
2330 !t.types.is_empty()
2331 && t.types.iter().all(
2332 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2333 )
2334}
2335
2336fn is_array_like(t: &Type) -> bool {
2338 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2339}
2340
2341#[cfg(test)]
2346mod tests {
2347 use std::sync::Arc;
2348
2349 use super::*;
2350
2351 fn conditional(
2352 param_name: Option<Name>,
2353 subject: Type,
2354 if_true: Type,
2355 if_false: Type,
2356 ) -> Atomic {
2357 Atomic::TConditional {
2358 data: Box::new(crate::atomic::ConditionalData {
2359 param_name,
2360 subject,
2361 if_true,
2362 if_false,
2363 }),
2364 }
2365 }
2366
2367 #[test]
2368 fn single_is_single() {
2369 let u = Type::single(Atomic::TString);
2370 assert!(u.is_single());
2371 assert!(!u.is_nullable());
2372 }
2373
2374 #[test]
2375 fn nullable_has_null() {
2376 let u = Type::nullable(Atomic::TString);
2377 assert!(u.is_nullable());
2378 assert_eq!(u.types.len(), 2);
2379 }
2380
2381 #[test]
2382 fn add_type_deduplicates() {
2383 let mut u = Type::single(Atomic::TString);
2384 u.add_type(Atomic::TString);
2385 assert_eq!(u.types.len(), 1);
2386 }
2387
2388 #[test]
2389 fn array_key_is_int_string() {
2390 let k = Type::array_key();
2391 assert!(k.is_array_key());
2392 assert_eq!(k.types.len(), 2);
2393 }
2394
2395 #[test]
2396 fn is_array_key_false_for_plain_int() {
2397 assert!(!Type::int().is_array_key());
2398 }
2399
2400 #[test]
2401 fn is_array_key_false_for_mixed() {
2402 assert!(!Type::mixed().is_array_key());
2403 }
2404
2405 #[test]
2406 fn is_array_key_false_for_int_string_null() {
2407 let mut u = Type::array_key();
2408 u.add_type(Atomic::TNull);
2409 assert!(!u.is_array_key());
2410 }
2411
2412 #[test]
2413 fn add_type_literal_subsumed_by_base() {
2414 let mut u = Type::single(Atomic::TInt);
2415 u.add_type(Atomic::TLiteralInt(42));
2416 assert_eq!(u.types.len(), 1);
2417 assert!(matches!(u.types[0], Atomic::TInt));
2418 }
2419
2420 #[test]
2421 fn true_then_false_merges_to_bool() {
2422 let mut u = Type::single(Atomic::TTrue);
2423 u.add_type(Atomic::TFalse);
2424 assert_eq!(u.types.len(), 1);
2425 assert!(matches!(u.types[0], Atomic::TBool));
2426 }
2427
2428 #[test]
2429 fn false_then_true_merges_to_bool() {
2430 let mut u = Type::single(Atomic::TFalse);
2431 u.add_type(Atomic::TTrue);
2432 assert_eq!(u.types.len(), 1);
2433 assert!(matches!(u.types[0], Atomic::TBool));
2434 }
2435
2436 #[test]
2437 fn true_alone_stays_true() {
2438 let u = Type::single(Atomic::TTrue);
2439 assert_eq!(u.types.len(), 1);
2440 assert!(matches!(u.types[0], Atomic::TTrue));
2441 }
2442
2443 #[test]
2444 fn true_false_merge_preserves_other_union_members() {
2445 let mut u = Type::single(Atomic::TTrue);
2446 u.add_type(Atomic::TNull);
2447 u.add_type(Atomic::TFalse);
2448 assert_eq!(u.types.len(), 2);
2449 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2450 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2451 }
2452
2453 #[test]
2454 fn add_type_base_widens_literals() {
2455 let mut u = Type::single(Atomic::TLiteralInt(1));
2456 u.add_type(Atomic::TLiteralInt(2));
2457 u.add_type(Atomic::TInt);
2458 assert_eq!(u.types.len(), 1);
2459 assert!(matches!(u.types[0], Atomic::TInt));
2460 }
2461
2462 #[test]
2463 fn mixed_subsumes_everything() {
2464 let mut u = Type::single(Atomic::TString);
2465 u.add_type(Atomic::TMixed);
2466 assert_eq!(u.types.len(), 1);
2467 assert!(u.is_mixed());
2468 }
2469
2470 #[test]
2471 fn remove_null() {
2472 let u = Type::nullable(Atomic::TString);
2473 let narrowed = u.remove_null();
2474 assert!(!narrowed.is_nullable());
2475 assert_eq!(narrowed.types.len(), 1);
2476 }
2477
2478 #[test]
2479 fn narrow_to_truthy_removes_null_false() {
2480 let mut u = Type::empty();
2481 u.add_type(Atomic::TString);
2482 u.add_type(Atomic::TNull);
2483 u.add_type(Atomic::TFalse);
2484 let truthy = u.narrow_to_truthy();
2485 assert!(!truthy.is_nullable());
2486 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2487 }
2488
2489 #[test]
2490 fn merge_combines_types() {
2491 let a = Type::single(Atomic::TString);
2492 let b = Type::single(Atomic::TInt);
2493 let merged = Type::merge(&a, &b);
2494 assert_eq!(merged.types.len(), 2);
2495 }
2496
2497 #[test]
2498 fn intersect_keeps_narrower_side_not_self() {
2499 let int_ty = Type::single(Atomic::TInt);
2503 let mut literals = Type::empty();
2504 literals.add_type(Atomic::TLiteralInt(1));
2505 literals.add_type(Atomic::TLiteralInt(2));
2506
2507 let narrowed = int_ty.intersect_with(&literals);
2508 assert_eq!(narrowed.types.len(), 2);
2509 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2510 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2511 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2512 }
2513
2514 #[test]
2515 fn subtype_literal_int_under_int() {
2516 let sub = Type::single(Atomic::TLiteralInt(5));
2517 let sup = Type::single(Atomic::TInt);
2518 assert!(sub.is_subtype_structural(&sup));
2519 }
2520
2521 #[test]
2522 fn subtype_never_is_bottom() {
2523 let never = Type::never();
2524 let string = Type::single(Atomic::TString);
2525 assert!(never.is_subtype_structural(&string));
2526 }
2527
2528 #[test]
2529 fn subtype_everything_under_mixed() {
2530 let string = Type::single(Atomic::TString);
2531 let mixed = Type::mixed();
2532 assert!(string.is_subtype_structural(&mixed));
2533 }
2534
2535 #[test]
2536 fn subtype_enum_case_under_own_enum() {
2537 let sub = Type::single(Atomic::TLiteralEnumCase {
2538 enum_fqcn: Name::new("RoundingMode"),
2539 case_name: Name::new("Unnecessary"),
2540 });
2541 let sup = Type::single(Atomic::TNamedObject {
2542 fqcn: Name::new("RoundingMode"),
2543 type_params: empty_type_params(),
2544 });
2545 assert!(sub.is_subtype_structural(&sup));
2546 }
2547
2548 #[test]
2549 fn enum_case_not_subtype_of_unrelated_enum() {
2550 let sub = Type::single(Atomic::TLiteralEnumCase {
2551 enum_fqcn: Name::new("RoundingMode"),
2552 case_name: Name::new("Unnecessary"),
2553 });
2554 let sup = Type::single(Atomic::TNamedObject {
2555 fqcn: Name::new("Suit"),
2556 type_params: empty_type_params(),
2557 });
2558 assert!(!sub.is_subtype_structural(&sup));
2559 }
2560
2561 #[test]
2562 fn subtype_enum_case_under_bare_object() {
2563 let sub = Type::single(Atomic::TLiteralEnumCase {
2564 enum_fqcn: Name::new("RoundingMode"),
2565 case_name: Name::new("Unnecessary"),
2566 });
2567 let sup = Type::single(Atomic::TObject);
2568 assert!(sub.is_subtype_structural(&sup));
2569 }
2570
2571 #[test]
2572 fn template_substitution() {
2573 let mut bindings = FxHashMap::default();
2574 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2575
2576 let tmpl = Type::single(Atomic::TTemplateParam {
2577 name: Name::new("T"),
2578 as_type: Box::new(Type::mixed()),
2579 defining_entity: Name::new("MyClass"),
2580 });
2581
2582 let resolved = tmpl.substitute_templates(&bindings);
2583 assert_eq!(resolved.types.len(), 1);
2584 assert!(matches!(resolved.types[0], Atomic::TString));
2585 }
2586
2587 #[test]
2588 fn intersection_is_object() {
2589 let parts = vec![
2590 Type::single(Atomic::TNamedObject {
2591 fqcn: Name::new("Iterator"),
2592 type_params: empty_type_params(),
2593 }),
2594 Type::single(Atomic::TNamedObject {
2595 fqcn: Name::new("Countable"),
2596 type_params: empty_type_params(),
2597 }),
2598 ];
2599 let atomic = Atomic::TIntersection {
2600 parts: vec_to_type_params(parts),
2601 };
2602 assert!(atomic.is_object());
2603 assert!(!atomic.can_be_falsy());
2604 assert!(atomic.can_be_truthy());
2605 }
2606
2607 #[test]
2608 fn intersection_display_two_parts() {
2609 let parts = vec![
2610 Type::single(Atomic::TNamedObject {
2611 fqcn: Name::new("Iterator"),
2612 type_params: empty_type_params(),
2613 }),
2614 Type::single(Atomic::TNamedObject {
2615 fqcn: Name::new("Countable"),
2616 type_params: empty_type_params(),
2617 }),
2618 ];
2619 let u = Type::single(Atomic::TIntersection {
2620 parts: vec_to_type_params(parts),
2621 });
2622 assert_eq!(format!("{u}"), "Iterator&Countable");
2623 }
2624
2625 #[test]
2626 fn intersection_display_three_parts() {
2627 let parts = vec![
2628 Type::single(Atomic::TNamedObject {
2629 fqcn: Name::new("A"),
2630 type_params: empty_type_params(),
2631 }),
2632 Type::single(Atomic::TNamedObject {
2633 fqcn: Name::new("B"),
2634 type_params: empty_type_params(),
2635 }),
2636 Type::single(Atomic::TNamedObject {
2637 fqcn: Name::new("C"),
2638 type_params: empty_type_params(),
2639 }),
2640 ];
2641 let u = Type::single(Atomic::TIntersection {
2642 parts: vec_to_type_params(parts),
2643 });
2644 assert_eq!(format!("{u}"), "A&B&C");
2645 }
2646
2647 #[test]
2648 fn intersection_in_nullable_union_display() {
2649 let intersection = Atomic::TIntersection {
2650 parts: vec_to_type_params(vec![
2651 Type::single(Atomic::TNamedObject {
2652 fqcn: Name::new("Iterator"),
2653 type_params: empty_type_params(),
2654 }),
2655 Type::single(Atomic::TNamedObject {
2656 fqcn: Name::new("Countable"),
2657 type_params: empty_type_params(),
2658 }),
2659 ]),
2660 };
2661 let mut u = Type::single(intersection);
2662 u.add_type(Atomic::TNull);
2663 assert!(u.is_nullable());
2664 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2665 }
2666
2667 fn t_param(name: &str) -> Type {
2670 Type::single(Atomic::TTemplateParam {
2671 name: Name::new(name),
2672 as_type: Box::new(Type::mixed()),
2673 defining_entity: Name::new("Fn"),
2674 })
2675 }
2676
2677 fn bindings_t_string() -> FxHashMap<Name, Type> {
2678 let mut b = FxHashMap::default();
2679 b.insert(Name::new("T"), Type::single(Atomic::TString));
2680 b
2681 }
2682
2683 #[test]
2684 fn substitute_non_empty_array_key_and_value() {
2685 let ty = Type::single(Atomic::TNonEmptyArray {
2686 key: Box::new(t_param("T")),
2687 value: Box::new(t_param("T")),
2688 });
2689 let result = ty.substitute_templates(&bindings_t_string());
2690 assert_eq!(result.types.len(), 1);
2691 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2692 panic!("expected TNonEmptyArray");
2693 };
2694 assert!(matches!(key.types[0], Atomic::TString));
2695 assert!(matches!(value.types[0], Atomic::TString));
2696 }
2697
2698 #[test]
2699 fn substitute_non_empty_list_value() {
2700 let ty = Type::single(Atomic::TNonEmptyList {
2701 value: Box::new(t_param("T")),
2702 });
2703 let result = ty.substitute_templates(&bindings_t_string());
2704 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2705 panic!("expected TNonEmptyList");
2706 };
2707 assert!(matches!(value.types[0], Atomic::TString));
2708 }
2709
2710 #[test]
2711 fn substitute_keyed_array_property_types() {
2712 use crate::atomic::{ArrayKey, KeyedProperty};
2713 use indexmap::IndexMap;
2714 let mut props = IndexMap::new();
2715 props.insert(
2716 ArrayKey::String(Arc::from("name")),
2717 KeyedProperty {
2718 ty: t_param("T"),
2719 optional: false,
2720 },
2721 );
2722 props.insert(
2723 ArrayKey::String(Arc::from("tag")),
2724 KeyedProperty {
2725 ty: t_param("T"),
2726 optional: true,
2727 },
2728 );
2729 let ty = Type::single(Atomic::TKeyedArray {
2730 properties: Box::new(props),
2731 is_open: true,
2732 is_list: false,
2733 });
2734 let result = ty.substitute_templates(&bindings_t_string());
2735 let Atomic::TKeyedArray {
2736 properties,
2737 is_open,
2738 is_list,
2739 } = &result.types[0]
2740 else {
2741 panic!("expected TKeyedArray");
2742 };
2743 assert!(is_open);
2744 assert!(!is_list);
2745 assert!(matches!(
2746 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2747 Atomic::TString
2748 ));
2749 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2750 assert!(matches!(
2751 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2752 Atomic::TString
2753 ));
2754 }
2755
2756 #[test]
2757 fn substitute_callable_params_and_return() {
2758 use crate::atomic::FnParam;
2759 let ty = Type::single(Atomic::TCallable {
2760 params: Some(Box::new([FnParam {
2761 name: Name::new("x"),
2762 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2763 out_ty: None,
2764 default: None,
2765 is_variadic: false,
2766 is_byref: false,
2767 is_optional: false,
2768 }])),
2769 return_type: Some(Box::new(t_param("T"))),
2770 });
2771 let result = ty.substitute_templates(&bindings_t_string());
2772 let Atomic::TCallable {
2773 params,
2774 return_type,
2775 } = &result.types[0]
2776 else {
2777 panic!("expected TCallable");
2778 };
2779 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2780 let param_union = param_ty.to_union();
2781 assert!(matches!(param_union.types[0], Atomic::TString));
2782 let ret = return_type.as_ref().unwrap();
2783 assert!(matches!(ret.types[0], Atomic::TString));
2784 }
2785
2786 #[test]
2787 fn substitute_callable_bare_no_panic() {
2788 let ty = Type::single(Atomic::TCallable {
2790 params: None,
2791 return_type: None,
2792 });
2793 let result = ty.substitute_templates(&bindings_t_string());
2794 assert!(matches!(
2795 result.types[0],
2796 Atomic::TCallable {
2797 params: None,
2798 return_type: None
2799 }
2800 ));
2801 }
2802
2803 #[test]
2804 fn substitute_closure_params_return_and_this() {
2805 use crate::atomic::FnParam;
2806 let ty = Type::single(Atomic::TClosure {
2807 data: Box::new(crate::atomic::ClosureData {
2808 params: Box::new([FnParam {
2809 name: Name::new("a"),
2810 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2811 out_ty: None,
2812 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2813 is_variadic: true,
2814 is_byref: true,
2815 is_optional: true,
2816 }]),
2817 return_type: t_param("T"),
2818 this_type: Some(t_param("T")),
2819 }),
2820 });
2821 let result = ty.substitute_templates(&bindings_t_string());
2822 let Atomic::TClosure { data } = &result.types[0] else {
2823 panic!("expected TClosure");
2824 };
2825 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2826 let p = ¶ms[0];
2827 let ty_union = p.ty.as_ref().unwrap().to_union();
2828 let default_union = p.default.as_ref().unwrap().to_union();
2829 assert!(matches!(ty_union.types[0], Atomic::TString));
2830 assert!(matches!(default_union.types[0], Atomic::TString));
2831 assert!(p.is_variadic);
2833 assert!(p.is_byref);
2834 assert!(p.is_optional);
2835 assert!(matches!(return_type.types[0], Atomic::TString));
2836 assert!(matches!(
2837 this_type.as_ref().unwrap().types[0],
2838 Atomic::TString
2839 ));
2840 }
2841
2842 #[test]
2843 fn substitute_conditional_all_branches() {
2844 let ty = Type::single(conditional(
2845 None,
2846 t_param("T"),
2847 t_param("T"),
2848 Type::single(Atomic::TInt),
2849 ));
2850 let result = ty.substitute_templates(&bindings_t_string());
2851 let Atomic::TConditional { data } = &result.types[0] else {
2852 panic!("expected TConditional");
2853 };
2854 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2855 assert!(matches!(subject.types[0], Atomic::TString));
2856 assert!(matches!(if_true.types[0], Atomic::TString));
2857 assert!(matches!(if_false.types[0], Atomic::TInt));
2858 }
2859
2860 #[test]
2861 fn resolve_conditional_is_null_non_null_arg() {
2862 let ty = Type::single(conditional(
2863 Some(Name::new("x")),
2864 Type::single(Atomic::TNull),
2865 Type::single(Atomic::TInt),
2866 Type::single(Atomic::TString),
2867 ));
2868 let result = ty.resolve_conditional_returns(|name| {
2869 if name == "x" {
2870 Some(Type::single(Atomic::TString)) } else {
2872 None
2873 }
2874 });
2875 assert!(result.types.len() == 1);
2876 assert!(matches!(result.types[0], Atomic::TString));
2877 }
2878
2879 #[test]
2880 fn resolve_conditional_is_null_null_arg() {
2881 let ty = Type::single(conditional(
2882 Some(Name::new("x")),
2883 Type::single(Atomic::TNull),
2884 Type::single(Atomic::TInt),
2885 Type::single(Atomic::TString),
2886 ));
2887 let result = ty.resolve_conditional_returns(|name| {
2888 if name == "x" {
2889 Some(Type::single(Atomic::TNull)) } else {
2891 None
2892 }
2893 });
2894 assert!(result.types.len() == 1);
2895 assert!(matches!(result.types[0], Atomic::TInt));
2896 }
2897
2898 #[test]
2899 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2900 let mut nullable_str = Type::single(Atomic::TString);
2901 nullable_str.add_type(Atomic::TNull);
2902 let ty = Type::single(conditional(
2903 Some(Name::new("x")),
2904 Type::single(Atomic::TNull),
2905 Type::single(Atomic::TInt),
2906 Type::single(Atomic::TString),
2907 ));
2908 let result = ty.resolve_conditional_returns(|name| {
2909 if name == "x" {
2910 Some(nullable_str.clone())
2911 } else {
2912 None
2913 }
2914 });
2915 assert_eq!(result.types.len(), 2);
2917 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2918 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2919 }
2920
2921 #[test]
2922 fn resolve_conditional_nested_widens_inner_branch() {
2923 let inner = Type::single(conditional(
2926 Some(Name::new("x")),
2927 Type::single(Atomic::TString),
2928 Type::single(Atomic::TString),
2929 Type::single(Atomic::TFloat),
2930 ));
2931 let ty = Type::single(conditional(
2932 Some(Name::new("x")),
2933 Type::single(Atomic::TNull),
2934 Type::single(Atomic::TInt),
2935 inner,
2936 ));
2937 let result = ty.resolve_conditional_returns(|_| None);
2939 assert!(
2940 result
2941 .types
2942 .iter()
2943 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2944 "no TConditional should survive: {:?}",
2945 result.types
2946 );
2947 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2948 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2949 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2950 }
2951
2952 #[test]
2953 fn resolve_conditional_nested_resolves_inner_branch() {
2954 let inner = Type::single(conditional(
2958 Some(Name::new("x")),
2959 Type::single(Atomic::TString),
2960 Type::single(Atomic::TString),
2961 Type::single(Atomic::TFloat),
2962 ));
2963 let ty = Type::single(conditional(
2964 Some(Name::new("x")),
2965 Type::single(Atomic::TNull),
2966 Type::single(Atomic::TInt),
2967 inner,
2968 ));
2969 let result = ty.resolve_conditional_returns(|name| {
2971 if name == "x" {
2972 Some(Type::single(Atomic::TString))
2973 } else {
2974 None
2975 }
2976 });
2977 assert!(
2978 result
2979 .types
2980 .iter()
2981 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2982 "no TConditional should survive: {:?}",
2983 result.types
2984 );
2985 assert_eq!(result.types.len(), 1);
2986 assert!(matches!(result.types[0], Atomic::TString));
2987 }
2988
2989 #[test]
2990 fn resolve_conditional_true_subject_bool_arg_widens() {
2991 let ty = Type::single(conditional(
2995 Some(Name::new("x")),
2996 Type::single(Atomic::TTrue),
2997 Type::single(Atomic::TInt),
2998 Type::single(Atomic::TString),
2999 ));
3000 let result = ty.resolve_conditional_returns(|name| {
3001 if name == "x" {
3002 Some(Type::single(Atomic::TBool))
3003 } else {
3004 None
3005 }
3006 });
3007 assert_eq!(result.types.len(), 2);
3008 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3009 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3010 }
3011
3012 #[test]
3013 fn resolve_conditional_true_subject_mixed_arg_widens() {
3014 let ty = Type::single(conditional(
3017 Some(Name::new("x")),
3018 Type::single(Atomic::TTrue),
3019 Type::single(Atomic::TInt),
3020 Type::single(Atomic::TString),
3021 ));
3022 let result = ty.resolve_conditional_returns(|name| {
3023 if name == "x" {
3024 Some(Type::mixed())
3025 } else {
3026 None
3027 }
3028 });
3029 assert_eq!(result.types.len(), 2);
3030 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3031 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3032 }
3033
3034 #[test]
3035 fn resolve_conditional_true_subject_literal_args() {
3036 let ty = Type::single(conditional(
3039 Some(Name::new("x")),
3040 Type::single(Atomic::TTrue),
3041 Type::single(Atomic::TInt),
3042 Type::single(Atomic::TString),
3043 ));
3044 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3045 if name == "x" {
3046 Some(Type::single(Atomic::TFalse))
3047 } else {
3048 None
3049 }
3050 });
3051 assert_eq!(false_arg.types.len(), 1);
3052 assert!(matches!(false_arg.types[0], Atomic::TString));
3053
3054 let true_arg = ty.resolve_conditional_returns(|name| {
3055 if name == "x" {
3056 Some(Type::single(Atomic::TTrue))
3057 } else {
3058 None
3059 }
3060 });
3061 assert_eq!(true_arg.types.len(), 1);
3062 assert!(matches!(true_arg.types[0], Atomic::TInt));
3063 }
3064
3065 #[test]
3066 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3067 let ty = Type::single(conditional(
3071 Some(Name::new("x")),
3072 Type::single(Atomic::TBool),
3073 Type::single(Atomic::TInt),
3074 Type::single(Atomic::TString),
3075 ));
3076 let result = ty.resolve_conditional_returns(|name| {
3077 if name == "x" {
3078 Some(Type::single(Atomic::TScalar))
3079 } else {
3080 None
3081 }
3082 });
3083 assert_eq!(result.types.len(), 2);
3084 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3085 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3086 }
3087
3088 #[test]
3089 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3090 let ty = Type::single(conditional(
3094 Some(Name::new("x")),
3095 Type::single(Atomic::TString),
3096 Type::single(Atomic::TInt),
3097 Type::single(Atomic::TBool),
3098 ));
3099 let result = ty.resolve_conditional_returns(|name| {
3100 if name == "x" {
3101 Some(Type::single(Atomic::TBool))
3102 } else {
3103 None
3104 }
3105 });
3106 assert_eq!(result.types.len(), 1);
3107 assert!(matches!(result.types[0], Atomic::TBool));
3108 }
3109
3110 #[test]
3111 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3112 let ty = Type::single(conditional(
3115 Some(Name::new("x")),
3116 Type::single(Atomic::TList {
3117 value: Box::new(Type::mixed()),
3118 }),
3119 Type::single(Atomic::TInt),
3120 Type::single(Atomic::TString),
3121 ));
3122 let result = ty.resolve_conditional_returns(|name| {
3123 if name == "x" {
3124 Some(Type::single(Atomic::TArray {
3125 key: Box::new(Type::mixed()),
3126 value: Box::new(Type::mixed()),
3127 }))
3128 } else {
3129 None
3130 }
3131 });
3132 assert_eq!(result.types.len(), 2);
3133 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3134 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3135 }
3136
3137 #[test]
3138 fn resolve_conditional_float_subject_int_literal_false_branch() {
3139 let ty = resolve_conditional_branch(
3142 &Atomic::TFloat,
3143 &Type::single(Atomic::TLiteralInt(42)),
3144 &Type::single(Atomic::TInt),
3145 &Type::single(Atomic::TString),
3146 );
3147 assert!(matches!(
3148 ty.as_ref(),
3149 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3150 ));
3151 }
3152
3153 #[test]
3154 fn resolve_conditional_int_subject_float_literal_false_branch() {
3155 let ty = resolve_conditional_branch(
3156 &Atomic::TInt,
3157 &Type::single(Atomic::TLiteralFloat(1, 0)),
3158 &Type::single(Atomic::TInt),
3159 &Type::single(Atomic::TString),
3160 );
3161 assert!(matches!(
3162 ty.as_ref(),
3163 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3164 ));
3165 }
3166
3167 #[test]
3168 fn resolve_conditional_float_subject_float_literal_then_branch() {
3169 let ty = resolve_conditional_branch(
3170 &Atomic::TFloat,
3171 &Type::single(Atomic::TLiteralFloat(1, 5)),
3172 &Type::single(Atomic::TInt),
3173 &Type::single(Atomic::TString),
3174 );
3175 assert!(matches!(
3176 ty.as_ref(),
3177 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3178 ));
3179 }
3180
3181 #[test]
3182 fn resolve_conditional_int_subject_int_literal_then_branch() {
3183 let ty = resolve_conditional_branch(
3184 &Atomic::TInt,
3185 &Type::single(Atomic::TLiteralInt(7)),
3186 &Type::single(Atomic::TInt),
3187 &Type::single(Atomic::TString),
3188 );
3189 assert!(matches!(
3190 ty.as_ref(),
3191 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3192 ));
3193 }
3194
3195 #[test]
3196 fn resolve_conditional_int_subject_float_arg_false_branch() {
3197 let ty = resolve_conditional_branch(
3200 &Atomic::TInt,
3201 &Type::single(Atomic::TFloat),
3202 &Type::single(Atomic::TInt),
3203 &Type::single(Atomic::TString),
3204 );
3205 assert!(matches!(
3206 ty.as_ref(),
3207 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3208 ));
3209 }
3210
3211 #[test]
3212 fn resolve_conditional_array_subject_string_arg_false_branch() {
3213 let ty = resolve_conditional_branch(
3214 &Atomic::TArray {
3215 key: Box::new(Type::mixed()),
3216 value: Box::new(Type::mixed()),
3217 },
3218 &Type::single(Atomic::TString),
3219 &Type::single(Atomic::TInt),
3220 &Type::single(Atomic::TString),
3221 );
3222 assert!(matches!(
3223 ty.as_ref(),
3224 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3225 ));
3226 }
3227
3228 #[test]
3229 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3230 let ty = resolve_conditional_branch(
3231 &Atomic::TArray {
3232 key: Box::new(Type::mixed()),
3233 value: Box::new(Type::mixed()),
3234 },
3235 &Type::single(Atomic::TBool),
3236 &Type::single(Atomic::TInt),
3237 &Type::single(Atomic::TString),
3238 );
3239 assert!(matches!(
3240 ty.as_ref(),
3241 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3242 ));
3243 }
3244
3245 #[test]
3246 fn resolve_conditional_array_subject_mixed_arg_widens() {
3247 let ty = resolve_conditional_branch(
3251 &Atomic::TArray {
3252 key: Box::new(Type::mixed()),
3253 value: Box::new(Type::mixed()),
3254 },
3255 &Type::mixed(),
3256 &Type::single(Atomic::TInt),
3257 &Type::single(Atomic::TString),
3258 );
3259 assert!(ty.is_none());
3260 }
3261
3262 #[test]
3263 fn resolve_conditional_array_subject_list_arg_then_branch() {
3264 let ty = resolve_conditional_branch(
3267 &Atomic::TArray {
3268 key: Box::new(Type::mixed()),
3269 value: Box::new(Type::mixed()),
3270 },
3271 &Type::single(Atomic::TList {
3272 value: Box::new(Type::mixed()),
3273 }),
3274 &Type::single(Atomic::TInt),
3275 &Type::single(Atomic::TString),
3276 );
3277 assert!(matches!(
3278 ty.as_ref(),
3279 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3280 ));
3281 }
3282
3283 #[test]
3284 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3285 let ty = resolve_conditional_branch(
3287 &Atomic::TList {
3288 value: Box::new(Type::mixed()),
3289 },
3290 &Type::single(Atomic::TKeyedArray {
3291 properties: Box::default(),
3292 is_open: false,
3293 is_list: true,
3294 }),
3295 &Type::single(Atomic::TInt),
3296 &Type::single(Atomic::TString),
3297 );
3298 assert!(matches!(
3299 ty.as_ref(),
3300 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3301 ));
3302 }
3303
3304 #[test]
3305 fn resolve_conditional_list_subject_list_string_union_widens() {
3306 let ty = resolve_conditional_branch(
3310 &Atomic::TList {
3311 value: Box::new(Type::mixed()),
3312 },
3313 &Type::from_vec(vec![
3314 Atomic::TList {
3315 value: Box::new(Type::mixed()),
3316 },
3317 Atomic::TString,
3318 ]),
3319 &Type::single(Atomic::TInt),
3320 &Type::single(Atomic::TString),
3321 );
3322 assert!(ty.is_none());
3323 }
3324
3325 #[test]
3326 fn resolve_conditional_numeric_subject_widens() {
3327 let ty = resolve_conditional_branch(
3331 &Atomic::TNumeric,
3332 &Type::single(Atomic::TLiteralString("123".into())),
3333 &Type::single(Atomic::TInt),
3334 &Type::single(Atomic::TString),
3335 );
3336 assert!(ty.is_none());
3337 }
3338
3339 #[test]
3340 fn resolve_conditional_non_empty_string_subject_widens() {
3341 let ty = resolve_conditional_branch(
3344 &Atomic::TNonEmptyString,
3345 &Type::single(Atomic::TString),
3346 &Type::single(Atomic::TInt),
3347 &Type::single(Atomic::TString),
3348 );
3349 assert!(ty.is_none());
3350 }
3351
3352 #[test]
3353 fn resolve_conditional_template_arg_widens() {
3354 let ty = resolve_conditional_branch(
3357 &Atomic::TString,
3358 &t_param("T"),
3359 &Type::single(Atomic::TInt),
3360 &Type::single(Atomic::TString),
3361 );
3362 assert!(ty.is_none());
3363 }
3364
3365 #[test]
3366 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3367 let ty = resolve_conditional_branch(
3370 &Atomic::TMixed,
3371 &Type::single(Atomic::TBool),
3372 &Type::single(Atomic::TInt),
3373 &Type::single(Atomic::TString),
3374 );
3375 assert!(matches!(
3376 ty.as_ref(),
3377 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3378 ));
3379 }
3380
3381 #[test]
3382 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3383 let ty = resolve_conditional_branch(
3386 &Atomic::TScalar,
3387 &Type::single(Atomic::TNull),
3388 &Type::single(Atomic::TInt),
3389 &Type::single(Atomic::TString),
3390 );
3391 assert!(matches!(
3392 ty.as_ref(),
3393 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3394 ));
3395 }
3396
3397 #[test]
3398 fn resolve_conditional_object_subject_named_arg_then_branch() {
3399 let ty = resolve_conditional_branch(
3401 &Atomic::TObject,
3402 &Type::single(Atomic::TNamedObject {
3403 fqcn: Name::new("Foo"),
3404 type_params: empty_type_params(),
3405 }),
3406 &Type::single(Atomic::TInt),
3407 &Type::single(Atomic::TString),
3408 );
3409 assert!(matches!(
3410 ty.as_ref(),
3411 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3412 ));
3413 }
3414
3415 #[test]
3416 fn resolve_conditional_named_subject_other_class_widens() {
3417 let ty = resolve_conditional_branch(
3421 &Atomic::TNamedObject {
3422 fqcn: Name::new("Foo"),
3423 type_params: empty_type_params(),
3424 },
3425 &Type::single(Atomic::TNamedObject {
3426 fqcn: Name::new("Bar"),
3427 type_params: empty_type_params(),
3428 }),
3429 &Type::single(Atomic::TInt),
3430 &Type::single(Atomic::TString),
3431 );
3432 assert!(ty.is_none());
3433 }
3434
3435 #[test]
3436 fn resolve_conditional_enum_case_subject_widens() {
3437 let ty = resolve_conditional_branch(
3440 &Atomic::TLiteralEnumCase {
3441 enum_fqcn: Name::new("RoundingMode"),
3442 case_name: Name::new("Unnecessary"),
3443 },
3444 &Type::single(Atomic::TInt),
3445 &Type::single(Atomic::TInt),
3446 &Type::single(Atomic::TString),
3447 );
3448 assert!(ty.is_none());
3449 }
3450
3451 #[test]
3452 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3453 let ty = resolve_conditional_branch(
3455 &Atomic::TNull,
3456 &Type::single(Atomic::TBool),
3457 &Type::single(Atomic::TInt),
3458 &Type::single(Atomic::TString),
3459 );
3460 assert!(matches!(
3461 ty.as_ref(),
3462 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3463 ));
3464 }
3465
3466 #[test]
3467 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3468 let ty = resolve_conditional_branch(
3471 &Atomic::TBool,
3472 &Type::single(Atomic::TTrue),
3473 &Type::single(Atomic::TInt),
3474 &Type::single(Atomic::TString),
3475 );
3476 assert!(matches!(
3477 ty.as_ref(),
3478 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3479 ));
3480 }
3481
3482 #[test]
3483 fn substitute_intersection_parts() {
3484 let ty = Type::single(Atomic::TIntersection {
3485 parts: vec_to_type_params(vec![
3486 Type::single(Atomic::TNamedObject {
3487 fqcn: Name::new("Countable"),
3488 type_params: empty_type_params(),
3489 }),
3490 t_param("T"),
3491 ]),
3492 });
3493 let result = ty.substitute_templates(&bindings_t_string());
3494 let Atomic::TIntersection { parts } = &result.types[0] else {
3495 panic!("expected TIntersection");
3496 };
3497 assert_eq!(parts.len(), 2);
3498 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3499 assert!(matches!(parts[1].types[0], Atomic::TString));
3500 }
3501
3502 #[test]
3503 fn substitute_no_template_params_identity() {
3504 let ty = Type::single(Atomic::TInt);
3505 let result = ty.substitute_templates(&bindings_t_string());
3506 assert!(matches!(result.types[0], Atomic::TInt));
3507 }
3508}