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 {
671 let mut out = Type::empty();
672 out.from_docblock = self.from_docblock;
673 for t in &self.types {
674 match t {
675 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
676 key: key.clone(),
677 value: value.clone(),
678 }),
679 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
680 value: value.clone(),
681 }),
682 Atomic::TKeyedArray {
683 properties,
684 is_open: false,
685 ..
686 } if properties.is_empty() => {}
687 _ => out.add_type(t.clone()),
688 }
689 }
690 out
691 }
692
693 pub fn narrow_to_empty_collection(&self) -> Type {
703 let mut out = Type::empty();
704 out.from_docblock = self.from_docblock;
705 for t in &self.types {
706 match t {
707 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
708 Atomic::TArray { .. } | Atomic::TList { .. } => {
709 out.add_type(Atomic::TKeyedArray {
710 properties: Box::default(),
711 is_open: false,
712 is_list: true,
713 });
714 }
715 _ => out.add_type(t.clone()),
716 }
717 }
718 out
719 }
720
721 pub fn narrow_to_list(&self) -> Type {
733 let mut out = Type::empty();
734 out.from_docblock = self.from_docblock;
735 for t in &self.types {
736 match t {
737 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
738 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
744 out.add_type(Atomic::TList {
745 value: value.clone(),
746 });
747 }
748 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
749 out.add_type(Atomic::TNonEmptyList {
750 value: value.clone(),
751 });
752 }
753 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
754 Atomic::TMixed => out.add_type(Atomic::TList {
755 value: Box::new(Type::mixed()),
756 }),
757 _ => {}
758 }
759 }
760 if out.is_empty() {
761 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
762 } else {
763 out
764 }
765 }
766
767 pub fn narrow_to_object(&self) -> Type {
772 let mut out = Type::empty();
773 for t in &self.types {
774 if matches!(t, Atomic::TMixed) {
775 out.add_type(Atomic::TObject);
776 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
777 out.add_type(t.clone());
778 }
779 }
780 if out.types.is_empty() {
781 self.filter(|t| t.is_object())
782 } else {
783 out
784 }
785 }
786
787 pub fn narrow_to_callable(&self) -> Type {
794 let narrowed = self.filter(|t| {
795 t.is_callable()
796 || t.is_string()
797 || t.is_array()
798 || t.is_object()
799 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
800 });
801 let mut result = Type::empty();
808 result.possibly_undefined = narrowed.possibly_undefined;
809 result.from_docblock = narrowed.from_docblock;
810 for atomic in narrowed.types {
811 if matches!(atomic, Atomic::TObject) {
812 result.add_type(Atomic::TCallable {
813 params: None,
814 return_type: None,
815 });
816 } else {
817 result.add_type(atomic);
818 }
819 }
820 result
821 }
822
823 pub fn narrow_to_scalar(&self) -> Type {
825 self.filter_replacing(
826 |t| {
827 t.is_string()
828 || t.is_int()
829 || matches!(
830 t,
831 Atomic::TFloat
832 | Atomic::TIntegralFloat
833 | Atomic::TLiteralFloat(..)
834 | Atomic::TBool
835 | Atomic::TTrue
836 | Atomic::TFalse
837 | Atomic::TScalar
838 | Atomic::TNumeric
839 | Atomic::TNumericString
840 | Atomic::TTemplateParam { .. }
841 )
842 },
843 |t| matches!(t, Atomic::TMixed),
844 Atomic::TScalar,
845 )
846 }
847
848 pub fn narrow_to_iterable(&self) -> Type {
851 self.filter(|t| {
852 t.is_array()
853 || t.is_object()
854 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
855 })
856 }
857
858 pub fn narrow_to_countable(&self) -> Type {
861 self.filter(|t| {
862 t.is_array()
863 || t.is_object()
864 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
865 })
866 }
867
868 pub fn narrow_to_resource(&self) -> Type {
872 self.filter(|t| matches!(t, Atomic::TMixed))
874 }
875
876 pub fn narrow_to_class_string(&self) -> Type {
881 let mut out = Type::empty();
882 out.from_docblock = self.from_docblock;
883 for t in &self.types {
884 match t {
885 Atomic::TClassString(_) => out.add_type(t.clone()),
886 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
887 out.add_type(Atomic::TClassString(None));
888 }
889 _ => {}
890 }
891 }
892 out
893 }
894
895 pub fn narrow_to_interface_string(&self) -> Type {
900 let mut out = Type::empty();
901 out.from_docblock = self.from_docblock;
902 for t in &self.types {
903 match t {
904 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
905 Atomic::TClassString(name) => {
909 out.add_type(Atomic::TInterfaceString(*name));
910 }
911 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
912 out.add_type(Atomic::TInterfaceString(None));
913 }
914 _ => {}
915 }
916 }
917 out
918 }
919
920 pub fn merge(a: &Type, b: &Type) -> Type {
925 if b.types.is_empty() {
927 let mut result = a.clone();
928 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
929 return result;
930 }
931 if a.types.is_empty() {
933 let mut result = b.clone();
934 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
935 return result;
936 }
937 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
939 let mut result = a.clone();
940 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
941 return result;
942 }
943 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
945 return Type {
946 types: smallvec::smallvec![Atomic::TMixed],
947 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
948 from_docblock: a.from_docblock || b.from_docblock,
949 falsy_stripped: false,
950 possibly_absent_offset: false,
951 };
952 }
953 let mut result = a.clone();
954 result.merge_with(b);
955 result
956 }
957
958 pub fn merge_with(&mut self, other: &Type) {
960 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
961 self.possibly_undefined |= other.possibly_undefined;
962 return;
963 }
964 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
965 self.types.clear();
966 self.types.push(Atomic::TMixed);
967 self.possibly_undefined |= other.possibly_undefined;
968 return;
969 }
970 for atomic in &other.types {
971 self.add_type(atomic.clone());
972 }
973 self.possibly_undefined |= other.possibly_undefined;
974 }
975
976 pub fn intersect_with(&self, other: &Type) -> Type {
980 if self.is_mixed() {
981 return other.clone();
982 }
983 if other.is_mixed() {
984 return self.clone();
985 }
986 let mut result = Type::empty();
994 for a in &self.types {
995 for b in &other.types {
996 if a == b {
997 result.add_type(a.clone());
998 } else if atomic_subtype(b, a) {
999 result.add_type(b.clone());
1000 } else if atomic_subtype(a, b) {
1001 result.add_type(a.clone());
1002 }
1003 }
1004 }
1005 if result.is_empty() {
1006 Type::never()
1007 } else {
1008 result
1009 }
1010 }
1011
1012 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1016 if bindings.is_empty() {
1017 return self.clone();
1018 }
1019 if !self.types.iter().any(atomic_may_contain_templates) {
1022 return self.clone();
1023 }
1024 let mut result = Type::empty();
1025 result.possibly_undefined = self.possibly_undefined;
1026 result.from_docblock = self.from_docblock;
1027 for atomic in &self.types {
1028 match atomic {
1029 Atomic::TTemplateParam { name, .. } => {
1030 if let Some(resolved) = bindings.get(name) {
1031 for t in &resolved.types {
1032 result.add_type(t.clone());
1033 }
1034 } else {
1035 result.add_type(atomic.clone());
1036 }
1037 }
1038 Atomic::TArray { key, value } => {
1039 result.add_type(Atomic::TArray {
1040 key: Box::new(key.substitute_templates(bindings)),
1041 value: Box::new(value.substitute_templates(bindings)),
1042 });
1043 }
1044 Atomic::TList { value } => {
1045 result.add_type(Atomic::TList {
1046 value: Box::new(value.substitute_templates(bindings)),
1047 });
1048 }
1049 Atomic::TNonEmptyArray { key, value } => {
1050 result.add_type(Atomic::TNonEmptyArray {
1051 key: Box::new(key.substitute_templates(bindings)),
1052 value: Box::new(value.substitute_templates(bindings)),
1053 });
1054 }
1055 Atomic::TNonEmptyList { value } => {
1056 result.add_type(Atomic::TNonEmptyList {
1057 value: Box::new(value.substitute_templates(bindings)),
1058 });
1059 }
1060 Atomic::TKeyedArray {
1061 properties,
1062 is_open,
1063 is_list,
1064 } => {
1065 use crate::atomic::KeyedProperty;
1066 let new_props = properties
1067 .iter()
1068 .map(|(k, prop)| {
1069 (
1070 k.clone(),
1071 KeyedProperty {
1072 ty: prop.ty.substitute_templates(bindings),
1073 optional: prop.optional,
1074 },
1075 )
1076 })
1077 .collect();
1078 result.add_type(Atomic::TKeyedArray {
1079 properties: Box::new(new_props),
1080 is_open: *is_open,
1081 is_list: *is_list,
1082 });
1083 }
1084 Atomic::TCallable {
1085 params,
1086 return_type,
1087 } => {
1088 result.add_type(Atomic::TCallable {
1089 params: params.as_ref().map(|ps| {
1090 ps.iter()
1091 .map(|p| substitute_in_fn_param(p, bindings))
1092 .collect()
1093 }),
1094 return_type: return_type
1095 .as_ref()
1096 .map(|r| Box::new(r.substitute_templates(bindings))),
1097 });
1098 }
1099 Atomic::TClosure { data } => {
1100 result.add_type(Atomic::TClosure {
1101 data: Box::new(crate::atomic::ClosureData {
1102 params: data
1103 .params
1104 .iter()
1105 .map(|p| substitute_in_fn_param(p, bindings))
1106 .collect(),
1107 return_type: data.return_type.substitute_templates(bindings),
1108 this_type: data
1109 .this_type
1110 .as_ref()
1111 .map(|t| t.substitute_templates(bindings)),
1112 }),
1113 });
1114 }
1115 Atomic::TConditional { data } => {
1116 let param_name = &data.param_name;
1117 let new_subject = data.subject.substitute_templates(bindings);
1118 let new_if_true = data.if_true.substitute_templates(bindings);
1119 let new_if_false = data.if_false.substitute_templates(bindings);
1120
1121 let resolved = if let Some(name) = param_name {
1125 if let Some(bound) = bindings.get(name) {
1126 if new_subject.types.len() == 1 {
1127 resolve_conditional_branch(
1128 &new_subject.types[0],
1129 bound,
1130 &new_if_true,
1131 &new_if_false,
1132 )
1133 } else {
1134 None
1135 }
1136 } else {
1137 None
1138 }
1139 } else {
1140 None
1141 };
1142
1143 if let Some(branch) = resolved {
1144 for t in branch.types {
1145 result.add_type(t);
1146 }
1147 } else {
1148 result.add_type(Atomic::TConditional {
1149 data: Box::new(crate::atomic::ConditionalData {
1150 param_name: *param_name,
1151 subject: new_subject,
1152 if_true: new_if_true,
1153 if_false: new_if_false,
1154 }),
1155 });
1156 }
1157 }
1158 Atomic::TKeyOf { target } => {
1159 let new_target = target.substitute_templates(bindings);
1160 if let Some(resolved) = eval_key_of_type(&new_target) {
1161 result.merge_with(&resolved);
1162 } else {
1163 result.add_type(Atomic::TKeyOf {
1164 target: Box::new(new_target),
1165 });
1166 }
1167 }
1168 Atomic::TValueOf { target } => {
1169 let new_target = target.substitute_templates(bindings);
1170 if let Some(resolved) = eval_value_of_type(&new_target) {
1171 result.merge_with(&resolved);
1172 } else {
1173 result.add_type(Atomic::TValueOf {
1174 target: Box::new(new_target),
1175 });
1176 }
1177 }
1178 Atomic::TIntersection { parts } => {
1179 result.add_type(Atomic::TIntersection {
1180 parts: vec_to_type_params(
1181 parts
1182 .iter()
1183 .map(|p| p.substitute_templates(bindings))
1184 .collect(),
1185 ),
1186 });
1187 }
1188 Atomic::TNamedObject { fqcn, type_params } => {
1189 if type_params.is_empty() && !fqcn.contains('\\') {
1191 if let Some(resolved) = bindings.get(fqcn) {
1192 for t in &resolved.types {
1193 result.add_type(t.clone());
1194 }
1195 continue;
1196 }
1197 }
1198 let new_params: Vec<Type> = type_params
1199 .iter()
1200 .map(|p| p.substitute_templates(bindings))
1201 .collect();
1202 result.add_type(Atomic::TNamedObject {
1203 fqcn: *fqcn,
1204 type_params: vec_to_type_params(new_params),
1205 });
1206 }
1207 Atomic::TClassString(Some(param_name)) => {
1209 if let Some(resolved) = bindings.get(param_name) {
1210 for r_atomic in &resolved.types {
1211 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1212 Some(*fqcn)
1213 } else {
1214 None
1215 };
1216 result.add_type(Atomic::TClassString(cls_name));
1217 }
1218 } else {
1219 result.add_type(atomic.clone());
1220 }
1221 }
1222 Atomic::TInterfaceString(Some(param_name)) => {
1224 if let Some(resolved) = bindings.get(param_name) {
1225 for r_atomic in &resolved.types {
1226 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1227 Some(*fqcn)
1228 } else {
1229 None
1230 };
1231 result.add_type(Atomic::TInterfaceString(iface_name));
1232 }
1233 } else {
1234 result.add_type(atomic.clone());
1235 }
1236 }
1237 _ => {
1238 result.add_type(atomic.clone());
1239 }
1240 }
1241 }
1242 result
1243 }
1244
1245 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1251 where
1252 F: Fn(&str) -> Option<Type>,
1253 {
1254 self.resolve_conditional_inner(&lookup)
1255 }
1256
1257 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1258 where
1259 F: Fn(&str) -> Option<Type>,
1260 {
1261 let mut result = Type::empty();
1262 for atomic in self.types {
1263 match atomic {
1264 Atomic::TConditional { ref data } => {
1265 let (param_name, subject, if_true, if_false) = (
1266 &data.param_name,
1267 &data.subject,
1268 &data.if_true,
1269 &data.if_false,
1270 );
1271 let resolved = if subject.types.len() == 1 {
1272 if let Some(name) = param_name {
1273 if let Some(arg_ty) = lookup(name.as_ref()) {
1274 resolve_conditional_branch(
1275 &subject.types[0],
1276 &arg_ty,
1277 if_true,
1278 if_false,
1279 )
1280 } else {
1281 None
1282 }
1283 } else {
1284 None
1285 }
1286 } else {
1287 None
1288 };
1289
1290 if let Some(branch) = resolved {
1291 for t in branch.resolve_conditional_inner(lookup).types {
1293 result.add_type(t);
1294 }
1295 } else {
1296 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1299 result.add_type(t);
1300 }
1301 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1302 result.add_type(t);
1303 }
1304 }
1305 }
1306 other => result.add_type(other),
1307 }
1308 }
1309 result
1310 }
1311
1312 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1322 if other.is_mixed() {
1323 return true;
1324 }
1325 if self.is_never() {
1326 return true; }
1328 self.types
1329 .iter()
1330 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1331 }
1332
1333 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1337 if self.is_mixed() {
1338 return true;
1339 }
1340 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1341 }
1342
1343 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1346 let mut result = Type::empty();
1347 result.possibly_undefined = self.possibly_undefined;
1348 result.from_docblock = self.from_docblock;
1349 for atomic in &self.types {
1350 if f(atomic) {
1351 result.types.push(atomic.clone());
1352 }
1353 }
1354 result
1355 }
1356
1357 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1362 &self,
1363 keep: K,
1364 placeholder: P,
1365 replacement: Atomic,
1366 ) -> Type {
1367 let mut result = Type::empty();
1368 result.possibly_undefined = self.possibly_undefined;
1369 result.from_docblock = self.from_docblock;
1370 for atomic in &self.types {
1371 if keep(atomic) {
1372 result.add_type(atomic.clone());
1373 } else if placeholder(atomic) {
1374 result.add_type(replacement.clone());
1375 }
1376 }
1377 result
1378 }
1379
1380 pub fn possibly_undefined(mut self) -> Self {
1382 self.possibly_undefined = true;
1383 self
1384 }
1385
1386 pub fn from_docblock(mut self) -> Self {
1388 self.from_docblock = true;
1389 self
1390 }
1391
1392 pub fn falsy_stripped(mut self) -> Self {
1395 self.falsy_stripped = true;
1396 self
1397 }
1398
1399 pub fn possibly_absent_offset(mut self) -> Self {
1403 self.possibly_absent_offset = true;
1404 self
1405 }
1406}
1407
1408#[derive(Clone, PartialEq, Eq, Debug)]
1422enum ValueClass {
1423 Int,
1424 Float,
1425 True,
1426 False,
1427 String,
1428 Array,
1429 List,
1431 Object,
1432 Null,
1433 Top,
1436 LitInt(i64),
1439 LitFloat(i64, i64),
1441 LitString(Arc<str>),
1443}
1444
1445impl ValueClass {
1446 fn includes(&self, b: &Self) -> bool {
1451 self == b
1452 || matches!(
1453 (self, b),
1454 (Self::Top, _)
1455 | (Self::Int, Self::LitInt(_))
1456 | (Self::Float, Self::LitFloat(..))
1457 | (Self::String, Self::LitString(_))
1458 | (Self::Array, Self::List)
1459 )
1460 }
1461}
1462
1463fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1469 use ValueClass::*;
1470 match a {
1471 Atomic::TInt
1474 | Atomic::TIntRange { .. }
1475 | Atomic::TPositiveInt
1476 | Atomic::TNegativeInt
1477 | Atomic::TNonNegativeInt => vec![Int],
1478 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1479 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1481 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1482 vec![LitFloat(*int_bits, *frac_bits)]
1483 }
1484 Atomic::TBool => vec![True, False],
1486 Atomic::TTrue => vec![True],
1487 Atomic::TFalse => vec![False],
1488 Atomic::TString
1490 | Atomic::TNonEmptyString
1491 | Atomic::TNumericString
1492 | Atomic::TClassString(_)
1493 | Atomic::TInterfaceString(_)
1494 | Atomic::TEnumString
1495 | Atomic::TTraitString
1496 | Atomic::TCallableString => vec![String],
1497 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1498 Atomic::TArray { .. }
1500 | Atomic::TNonEmptyArray { .. }
1501 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1502 Atomic::TList { .. }
1503 | Atomic::TNonEmptyList { .. }
1504 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1505 Atomic::TObject
1507 | Atomic::TNamedObject { .. }
1508 | Atomic::TStaticObject { .. }
1509 | Atomic::TSelf { .. }
1510 | Atomic::TParent { .. }
1511 | Atomic::TClosure { .. }
1512 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1513 Atomic::TNull => vec![Null],
1516 Atomic::TVoid => vec![Top],
1517 Atomic::TMixed => vec![Top],
1519 Atomic::TScalar => vec![Int, Float, True, False, String],
1520 Atomic::TNumeric => vec![Int, Float],
1523 Atomic::TCallable { .. }
1525 | Atomic::TNever
1526 | Atomic::TTemplateParam { .. }
1527 | Atomic::TKeyOf { .. }
1528 | Atomic::TValueOf { .. }
1529 | Atomic::TConditional { .. }
1530 | Atomic::TIntersection { .. } => Vec::new(),
1531 }
1532}
1533
1534fn subject_is_decidable(subject: &Atomic) -> bool {
1542 matches!(
1543 subject,
1544 Atomic::TNull
1545 | Atomic::TTrue
1546 | Atomic::TFalse
1547 | Atomic::TBool
1548 | Atomic::TString
1549 | Atomic::TInt
1550 | Atomic::TFloat
1551 | Atomic::TArray { .. }
1552 | Atomic::TList { .. }
1553 | Atomic::TObject
1554 | Atomic::TMixed
1555 | Atomic::TScalar
1556 )
1557}
1558
1559fn resolve_conditional_branch(
1581 subject: &Atomic,
1582 arg_ty: &Type,
1583 if_true: &Type,
1584 if_false: &Type,
1585) -> Option<Type> {
1586 if !subject_is_decidable(subject) {
1587 return None;
1588 }
1589 if arg_ty.types.is_empty() {
1590 return None;
1591 }
1592 let subject_classes = value_classes(subject);
1593 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1594 if arg_classes.is_empty() {
1595 return None;
1597 }
1598 let all_match = arg_classes
1599 .iter()
1600 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1601 let any_overlap = arg_classes.iter().any(|c| {
1602 subject_classes
1603 .iter()
1604 .any(|s| s.includes(c) || c.includes(s))
1605 });
1606 if all_match {
1607 Some(if_true.clone())
1608 } else if !any_overlap {
1609 Some(if_false.clone())
1610 } else {
1611 None
1612 }
1613}
1614
1615fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1623 match atomic {
1624 Atomic::TNamedObject { fqcn, type_params } => {
1628 !type_params.is_empty() || !fqcn.contains('\\')
1629 }
1630 Atomic::TTemplateParam { .. }
1631 | Atomic::TKeyOf { .. }
1632 | Atomic::TValueOf { .. }
1633 | Atomic::TArray { .. }
1634 | Atomic::TList { .. }
1635 | Atomic::TNonEmptyArray { .. }
1636 | Atomic::TNonEmptyList { .. }
1637 | Atomic::TKeyedArray { .. }
1638 | Atomic::TCallable { .. }
1639 | Atomic::TClosure { .. }
1640 | Atomic::TConditional { .. }
1641 | Atomic::TIntersection { .. }
1642 | Atomic::TClassString(Some(_))
1643 | Atomic::TInterfaceString(Some(_)) => true,
1644 _ => false,
1645 }
1646}
1647
1648fn eval_key_of_type(t: &Type) -> Option<Type> {
1649 let mut result = Type::empty();
1650 for atomic in &t.types {
1651 match atomic {
1652 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1653 for k in &key.types {
1654 result.add_type(k.clone());
1655 }
1656 }
1657 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1658 result.add_type(Atomic::TInt);
1659 }
1660 Atomic::TKeyedArray { properties, .. } => {
1661 for key in properties.keys() {
1662 match key {
1663 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1664 crate::atomic::ArrayKey::String(s) => {
1665 result.add_type(Atomic::TLiteralString(s.clone()))
1666 }
1667 }
1668 }
1669 }
1670 _ => return None,
1671 }
1672 }
1673 (!result.types.is_empty()).then_some(result)
1674}
1675
1676fn eval_value_of_type(t: &Type) -> Option<Type> {
1677 let mut result = Type::empty();
1678 for atomic in &t.types {
1679 match atomic {
1680 Atomic::TArray { value, .. }
1681 | Atomic::TNonEmptyArray { value, .. }
1682 | Atomic::TList { value }
1683 | Atomic::TNonEmptyList { value } => {
1684 result.merge_with(value);
1685 }
1686 Atomic::TKeyedArray { properties, .. } => {
1687 for prop in properties.values() {
1688 result.merge_with(&prop.ty);
1689 }
1690 }
1691 _ => return None,
1692 }
1693 }
1694 (!result.types.is_empty()).then_some(result)
1695}
1696
1697fn substitute_in_fn_param(
1698 p: &crate::atomic::FnParam,
1699 bindings: &FxHashMap<Name, Type>,
1700) -> crate::atomic::FnParam {
1701 crate::atomic::FnParam {
1702 name: p.name,
1703 ty: p.ty.as_ref().map(|t| {
1704 let u = t.to_union();
1705 let substituted = u.substitute_templates(bindings);
1706 crate::compact::SimpleType::from_union(substituted)
1707 }),
1708 out_ty: p.out_ty.as_ref().map(|t| {
1709 let u = t.to_union();
1710 let substituted = u.substitute_templates(bindings);
1711 crate::compact::SimpleType::from_union(substituted)
1712 }),
1713 default: p.default.as_ref().map(|d| {
1714 let u = d.to_union();
1715 let substituted = u.substitute_templates(bindings);
1716 crate::compact::SimpleType::from_union(substituted)
1717 }),
1718 is_variadic: p.is_variadic,
1719 is_byref: p.is_byref,
1720 is_optional: p.is_optional,
1721 }
1722}
1723
1724pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1730 if sub == sup {
1731 return true;
1732 }
1733 match (sub, sup) {
1734 (Atomic::TNever, _) => true,
1736 (_, Atomic::TMixed) => true,
1738 (Atomic::TMixed, _) => true,
1739 (_, Atomic::TTemplateParam { as_type, .. }) => {
1744 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1745 }
1746
1747 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1749 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1750 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1751 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1752 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1753 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1754 (Atomic::TPositiveInt, Atomic::TInt) => true,
1755 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1756 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1757 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1758 (Atomic::TNegativeInt, Atomic::TInt) => true,
1759 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1760 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1761 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1762 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1763 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1764 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1765 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1766 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1767 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1769 max.is_none() && min.is_none_or(|m| m <= 1)
1770 }
1771 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1773 min.is_none() && max.is_none_or(|m| m >= -1)
1774 }
1775 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1777 max.is_none() && min.is_none_or(|m| m <= 0)
1778 }
1779 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1781 sub_min.is_some_and(|lo| lo >= 1)
1782 }
1783 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1784 sub_min.is_some_and(|lo| lo >= 0)
1785 }
1786 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1787 sub_max.is_some_and(|hi| hi <= -1)
1788 }
1789 (
1791 Atomic::TIntRange {
1792 min: sub_min,
1793 max: sub_max,
1794 },
1795 Atomic::TIntRange {
1796 min: sup_min,
1797 max: sup_max,
1798 },
1799 ) => {
1800 let lower_ok = match (sub_min, sup_min) {
1801 (_, None) => true,
1802 (None, Some(_)) => false,
1803 (Some(sl), Some(su)) => sl >= su,
1804 };
1805 let upper_ok = match (sub_max, sup_max) {
1806 (None, None) | (Some(_), None) => true,
1807 (None, Some(_)) => false,
1808 (Some(sl), Some(su)) => sl <= su,
1809 };
1810 lower_ok && upper_ok
1811 }
1812
1813 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1814 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1815 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1816
1817 (Atomic::TLiteralString(s), Atomic::TString) => {
1818 let _ = s;
1819 true
1820 }
1821 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1822 let _ = s;
1823 true
1824 }
1825 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1826 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1827 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1830 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1833 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1834 (Atomic::TNonEmptyString, Atomic::TString) => true,
1835 (Atomic::TCallableString, Atomic::TString) => true,
1836 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1838 (Atomic::TNumericString, Atomic::TString) => true,
1839 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1843 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1844 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1845 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1846 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1847 (Atomic::TClassString(_), Atomic::TString) => true,
1848 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1849 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1854 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
1855 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1856 (Atomic::TEnumString, Atomic::TString) => true,
1857 (Atomic::TTraitString, Atomic::TString) => true,
1858
1859 (Atomic::TTrue, Atomic::TBool) => true,
1860 (Atomic::TFalse, Atomic::TBool) => true,
1861
1862 (Atomic::TInt, Atomic::TNumeric) => true,
1863 (Atomic::TFloat, Atomic::TNumeric) => true,
1864 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1865 (Atomic::TNumericString, Atomic::TNumeric) => true,
1866
1867 (Atomic::TInt, Atomic::TScalar) => true,
1868 (Atomic::TFloat, Atomic::TScalar) => true,
1869 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1870 (Atomic::TString, Atomic::TScalar) => true,
1871 (Atomic::TBool, Atomic::TScalar) => true,
1872 (Atomic::TNumeric, Atomic::TScalar) => true,
1873 (Atomic::TTrue, Atomic::TScalar) => true,
1874 (Atomic::TFalse, Atomic::TScalar) => true,
1875 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1879 (Atomic::TNumericString, Atomic::TScalar) => true,
1880 (Atomic::TCallableString, Atomic::TScalar) => true,
1881 (Atomic::TClassString(_), Atomic::TScalar) => true,
1882 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1883 (Atomic::TEnumString, Atomic::TScalar) => true,
1884 (Atomic::TTraitString, Atomic::TScalar) => true,
1885
1886 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1888 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1889 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1890 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
1892 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1894 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1895 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1897 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1898 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
1901 enum_fqcn == fqcn
1902 }
1903 (
1907 Atomic::TNamedObject {
1908 fqcn: sub_fqcn,
1909 type_params: sub_params,
1910 },
1911 Atomic::TNamedObject {
1912 fqcn: sup_fqcn,
1913 type_params: sup_params,
1914 },
1915 ) => {
1916 sub_fqcn == sup_fqcn
1917 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1918 }
1919
1920 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1922
1923 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1925 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1926 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1927 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1928 (Atomic::TInt, Atomic::TFloat) => true,
1929 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1930
1931 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1933 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1934 }
1935
1936 (Atomic::TString, Atomic::TCallable { .. }) => true,
1938 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1939 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1940 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1941 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1942 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1943
1944 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1946 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1948 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1958 fn has_nominal_type(t: &Type) -> bool {
1959 t.types.iter().any(|a| {
1960 matches!(
1961 a,
1962 Atomic::TNamedObject { .. }
1963 | Atomic::TSelf { .. }
1964 | Atomic::TStaticObject { .. }
1965 | Atomic::TTemplateParam { .. }
1966 | Atomic::TClosure { .. }
1967 | Atomic::TCallable { .. }
1968 )
1969 })
1970 }
1971 let sub_required = sub
1972 .params
1973 .iter()
1974 .filter(|p| !p.is_optional && !p.is_variadic)
1975 .count();
1976 if sub_required > sup.params.len() {
1977 false
1978 } else {
1979 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
1980 let Some(sub_param) = sub.params.get(i) else {
1981 return true;
1982 };
1983 if sub_param.is_optional || sub_param.is_variadic {
1984 return true;
1985 }
1986 let (Some(sub_ty), Some(sup_ty)) =
1987 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
1988 else {
1989 return true;
1990 };
1991 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
1992 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
1993 return true;
1994 }
1995 sup_u.is_subtype_structural(&sub_u)
1998 });
1999 params_ok
2000 && (sub.return_type.is_mixed()
2001 || sup.return_type.is_mixed()
2002 || sup.return_type.is_void()
2006 || has_nominal_type(&sub.return_type)
2007 || has_nominal_type(&sup.return_type)
2008 || sub.return_type.is_subtype_structural(&sup.return_type))
2009 }
2010 }
2011 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2013 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2015 fqcn.as_ref().eq_ignore_ascii_case("closure")
2016 }
2017 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2018 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2020 fqcn.as_ref().eq_ignore_ascii_case("closure")
2021 }
2022 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2024 fqcn.as_ref().eq_ignore_ascii_case("closure")
2025 }
2026
2027 (
2035 Atomic::TIntersection { parts: sub_parts },
2036 Atomic::TIntersection { parts: sup_parts },
2037 ) => sup_parts.iter().all(|sup_part| {
2038 sub_parts
2039 .iter()
2040 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2041 }),
2042
2043 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2045 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2046 }
2047 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2048 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2049 }
2050 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2051 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2052 }
2053 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2054 value.is_subtype_structural(lv)
2055 }
2056 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2058 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2059 && av.is_subtype_structural(lv)
2060 }
2061 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2062 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2063 && av.is_subtype_structural(lv)
2064 }
2065 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2066 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2067 && av.is_subtype_structural(lv)
2068 }
2069 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2070 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2071 && av.is_subtype_structural(lv)
2072 }
2073 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2075 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2076 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2077 }
2078
2079 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2081 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2082 }
2083
2084 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2093 properties.iter().all(|(prop_key, prop)| {
2094 let key_atomic = match prop_key {
2095 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2096 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2097 };
2098 if !Type::single(key_atomic).is_subtype_structural(key) {
2099 return false; }
2101 let has_named_obj = prop.ty.types.iter().any(|a| {
2103 matches!(
2104 a,
2105 Atomic::TNamedObject { .. }
2106 | Atomic::TSelf { .. }
2107 | Atomic::TStaticObject { .. }
2108 | Atomic::TClosure { .. }
2109 | Atomic::TTemplateParam { .. }
2110 )
2111 });
2112 has_named_obj || prop.ty.is_subtype_structural(value)
2113 })
2114 }
2115 (
2116 Atomic::TKeyedArray {
2117 properties,
2118 is_open,
2119 ..
2120 },
2121 Atomic::TNonEmptyArray { key, value },
2122 ) => {
2123 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2124 && properties.iter().all(|(prop_key, prop)| {
2125 let key_atomic = match prop_key {
2126 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2127 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2128 };
2129 if !Type::single(key_atomic).is_subtype_structural(key) {
2130 return false;
2131 }
2132 let has_named_obj = prop.ty.types.iter().any(|a| {
2133 matches!(
2134 a,
2135 Atomic::TNamedObject { .. }
2136 | Atomic::TSelf { .. }
2137 | Atomic::TStaticObject { .. }
2138 | Atomic::TClosure { .. }
2139 | Atomic::TTemplateParam { .. }
2140 )
2141 });
2142 has_named_obj || prop.ty.is_subtype_structural(value)
2143 })
2144 }
2145
2146 (
2148 Atomic::TKeyedArray {
2149 properties,
2150 is_list,
2151 ..
2152 },
2153 Atomic::TList { value: lv },
2154 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2155 (
2156 Atomic::TKeyedArray {
2157 properties,
2158 is_list,
2159 ..
2160 },
2161 Atomic::TNonEmptyList { value: lv },
2162 ) => {
2163 *is_list
2164 && !properties.is_empty()
2165 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2166 }
2167
2168 (
2173 Atomic::TKeyedArray {
2174 properties: sub_props,
2175 is_open: sub_open,
2176 ..
2177 },
2178 Atomic::TKeyedArray {
2179 properties: sup_props,
2180 is_open: sup_open,
2181 ..
2182 },
2183 ) => {
2184 let keys_satisfied = sup_props
2185 .iter()
2186 .all(|(key, sup_prop)| match sub_props.get(key) {
2187 Some(sub_prop) => {
2188 if !sup_prop.optional && sub_prop.optional {
2192 return false;
2193 }
2194 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2195 matches!(
2196 a,
2197 Atomic::TNamedObject { .. }
2198 | Atomic::TSelf { .. }
2199 | Atomic::TStaticObject { .. }
2200 | Atomic::TClosure { .. }
2201 | Atomic::TTemplateParam { .. }
2202 )
2203 });
2204 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2205 }
2206 None => sup_prop.optional || *sub_open,
2207 });
2208 let no_undeclared_extras =
2209 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2210 keys_satisfied && no_undeclared_extras
2211 }
2212 (
2217 Atomic::TKeyedArray {
2218 properties,
2219 is_open,
2220 ..
2221 },
2222 Atomic::TIntersection { parts },
2223 ) => parts.iter().all(|part| {
2224 part.types.iter().any(|part_atomic| match part_atomic {
2225 Atomic::TKeyedArray {
2226 properties: sup_props,
2227 is_open: sup_open,
2228 ..
2229 } => {
2230 let keys_satisfied =
2231 sup_props
2232 .iter()
2233 .all(|(key, sup_prop)| match properties.get(key) {
2234 Some(sub_prop) => {
2235 if !sup_prop.optional && sub_prop.optional {
2236 return false;
2237 }
2238 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2239 matches!(
2240 a,
2241 Atomic::TNamedObject { .. }
2242 | Atomic::TSelf { .. }
2243 | Atomic::TStaticObject { .. }
2244 | Atomic::TClosure { .. }
2245 | Atomic::TTemplateParam { .. }
2246 )
2247 });
2248 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2249 }
2250 None => *is_open || sup_prop.optional,
2251 });
2252 let has_array_part = parts.iter().any(|part| {
2253 part.types
2254 .iter()
2255 .any(|t| matches!(t, Atomic::TArray { .. }))
2256 });
2257 let has_keyed_array_part = parts.iter().any(|part| {
2258 part.types
2259 .iter()
2260 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2261 });
2262 let keys_allowed_by_some_part = if has_array_part {
2263 true
2264 } else if has_keyed_array_part {
2265 properties.keys().all(|k| {
2266 parts.iter().any(|part| {
2267 part.types.iter().any(|t| {
2268 if let Atomic::TKeyedArray {
2269 properties: part_props,
2270 ..
2271 } = t
2272 {
2273 part_props.contains_key(k)
2274 } else {
2275 false
2276 }
2277 })
2278 })
2279 })
2280 } else {
2281 true
2282 };
2283 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2284 keys_satisfied && no_undeclared_extras
2285 }
2286 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2287 let key_atomic = match prop_key {
2288 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2289 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2290 };
2291 if !Type::single(key_atomic).is_subtype_structural(key) {
2292 return false;
2293 }
2294 let has_named_obj = prop.ty.types.iter().any(|a| {
2295 matches!(
2296 a,
2297 Atomic::TNamedObject { .. }
2298 | Atomic::TSelf { .. }
2299 | Atomic::TStaticObject { .. }
2300 | Atomic::TClosure { .. }
2301 | Atomic::TTemplateParam { .. }
2302 )
2303 });
2304 has_named_obj || prop.ty.is_subtype_structural(value)
2305 }),
2306 Atomic::TNamedObject { .. }
2307 | Atomic::TSelf { .. }
2308 | Atomic::TStaticObject { .. }
2309 | Atomic::TClosure { .. }
2310 | Atomic::TTemplateParam { .. } => true,
2311 _ => false,
2312 })
2313 }),
2314
2315 _ => false,
2316 }
2317}
2318
2319fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2325 if sub.len() != sup.len() {
2326 return false;
2327 }
2328 sub.iter()
2329 .zip(sup.iter())
2330 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2331}
2332
2333fn is_empty_array_literal(t: &Type) -> bool {
2336 !t.types.is_empty()
2337 && t.types.iter().all(
2338 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2339 )
2340}
2341
2342fn is_array_like(t: &Type) -> bool {
2344 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2345}
2346
2347#[cfg(test)]
2352mod tests {
2353 use std::sync::Arc;
2354
2355 use super::*;
2356
2357 fn conditional(
2358 param_name: Option<Name>,
2359 subject: Type,
2360 if_true: Type,
2361 if_false: Type,
2362 ) -> Atomic {
2363 Atomic::TConditional {
2364 data: Box::new(crate::atomic::ConditionalData {
2365 param_name,
2366 subject,
2367 if_true,
2368 if_false,
2369 }),
2370 }
2371 }
2372
2373 #[test]
2374 fn single_is_single() {
2375 let u = Type::single(Atomic::TString);
2376 assert!(u.is_single());
2377 assert!(!u.is_nullable());
2378 }
2379
2380 #[test]
2381 fn nullable_has_null() {
2382 let u = Type::nullable(Atomic::TString);
2383 assert!(u.is_nullable());
2384 assert_eq!(u.types.len(), 2);
2385 }
2386
2387 #[test]
2388 fn add_type_deduplicates() {
2389 let mut u = Type::single(Atomic::TString);
2390 u.add_type(Atomic::TString);
2391 assert_eq!(u.types.len(), 1);
2392 }
2393
2394 #[test]
2395 fn array_key_is_int_string() {
2396 let k = Type::array_key();
2397 assert!(k.is_array_key());
2398 assert_eq!(k.types.len(), 2);
2399 }
2400
2401 #[test]
2402 fn is_array_key_false_for_plain_int() {
2403 assert!(!Type::int().is_array_key());
2404 }
2405
2406 #[test]
2407 fn is_array_key_false_for_mixed() {
2408 assert!(!Type::mixed().is_array_key());
2409 }
2410
2411 #[test]
2412 fn is_array_key_false_for_int_string_null() {
2413 let mut u = Type::array_key();
2414 u.add_type(Atomic::TNull);
2415 assert!(!u.is_array_key());
2416 }
2417
2418 #[test]
2419 fn add_type_literal_subsumed_by_base() {
2420 let mut u = Type::single(Atomic::TInt);
2421 u.add_type(Atomic::TLiteralInt(42));
2422 assert_eq!(u.types.len(), 1);
2423 assert!(matches!(u.types[0], Atomic::TInt));
2424 }
2425
2426 #[test]
2427 fn true_then_false_merges_to_bool() {
2428 let mut u = Type::single(Atomic::TTrue);
2429 u.add_type(Atomic::TFalse);
2430 assert_eq!(u.types.len(), 1);
2431 assert!(matches!(u.types[0], Atomic::TBool));
2432 }
2433
2434 #[test]
2435 fn false_then_true_merges_to_bool() {
2436 let mut u = Type::single(Atomic::TFalse);
2437 u.add_type(Atomic::TTrue);
2438 assert_eq!(u.types.len(), 1);
2439 assert!(matches!(u.types[0], Atomic::TBool));
2440 }
2441
2442 #[test]
2443 fn true_alone_stays_true() {
2444 let u = Type::single(Atomic::TTrue);
2445 assert_eq!(u.types.len(), 1);
2446 assert!(matches!(u.types[0], Atomic::TTrue));
2447 }
2448
2449 #[test]
2450 fn true_false_merge_preserves_other_union_members() {
2451 let mut u = Type::single(Atomic::TTrue);
2452 u.add_type(Atomic::TNull);
2453 u.add_type(Atomic::TFalse);
2454 assert_eq!(u.types.len(), 2);
2455 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2456 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2457 }
2458
2459 #[test]
2460 fn add_type_base_widens_literals() {
2461 let mut u = Type::single(Atomic::TLiteralInt(1));
2462 u.add_type(Atomic::TLiteralInt(2));
2463 u.add_type(Atomic::TInt);
2464 assert_eq!(u.types.len(), 1);
2465 assert!(matches!(u.types[0], Atomic::TInt));
2466 }
2467
2468 #[test]
2469 fn mixed_subsumes_everything() {
2470 let mut u = Type::single(Atomic::TString);
2471 u.add_type(Atomic::TMixed);
2472 assert_eq!(u.types.len(), 1);
2473 assert!(u.is_mixed());
2474 }
2475
2476 #[test]
2477 fn remove_null() {
2478 let u = Type::nullable(Atomic::TString);
2479 let narrowed = u.remove_null();
2480 assert!(!narrowed.is_nullable());
2481 assert_eq!(narrowed.types.len(), 1);
2482 }
2483
2484 #[test]
2485 fn narrow_to_truthy_removes_null_false() {
2486 let mut u = Type::empty();
2487 u.add_type(Atomic::TString);
2488 u.add_type(Atomic::TNull);
2489 u.add_type(Atomic::TFalse);
2490 let truthy = u.narrow_to_truthy();
2491 assert!(!truthy.is_nullable());
2492 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2493 }
2494
2495 #[test]
2496 fn merge_combines_types() {
2497 let a = Type::single(Atomic::TString);
2498 let b = Type::single(Atomic::TInt);
2499 let merged = Type::merge(&a, &b);
2500 assert_eq!(merged.types.len(), 2);
2501 }
2502
2503 #[test]
2504 fn intersect_keeps_narrower_side_not_self() {
2505 let int_ty = Type::single(Atomic::TInt);
2509 let mut literals = Type::empty();
2510 literals.add_type(Atomic::TLiteralInt(1));
2511 literals.add_type(Atomic::TLiteralInt(2));
2512
2513 let narrowed = int_ty.intersect_with(&literals);
2514 assert_eq!(narrowed.types.len(), 2);
2515 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2516 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2517 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2518 }
2519
2520 #[test]
2521 fn subtype_literal_int_under_int() {
2522 let sub = Type::single(Atomic::TLiteralInt(5));
2523 let sup = Type::single(Atomic::TInt);
2524 assert!(sub.is_subtype_structural(&sup));
2525 }
2526
2527 #[test]
2528 fn subtype_never_is_bottom() {
2529 let never = Type::never();
2530 let string = Type::single(Atomic::TString);
2531 assert!(never.is_subtype_structural(&string));
2532 }
2533
2534 #[test]
2535 fn subtype_everything_under_mixed() {
2536 let string = Type::single(Atomic::TString);
2537 let mixed = Type::mixed();
2538 assert!(string.is_subtype_structural(&mixed));
2539 }
2540
2541 #[test]
2542 fn subtype_enum_case_under_own_enum() {
2543 let sub = Type::single(Atomic::TLiteralEnumCase {
2544 enum_fqcn: Name::new("RoundingMode"),
2545 case_name: Name::new("Unnecessary"),
2546 });
2547 let sup = Type::single(Atomic::TNamedObject {
2548 fqcn: Name::new("RoundingMode"),
2549 type_params: empty_type_params(),
2550 });
2551 assert!(sub.is_subtype_structural(&sup));
2552 }
2553
2554 #[test]
2555 fn enum_case_not_subtype_of_unrelated_enum() {
2556 let sub = Type::single(Atomic::TLiteralEnumCase {
2557 enum_fqcn: Name::new("RoundingMode"),
2558 case_name: Name::new("Unnecessary"),
2559 });
2560 let sup = Type::single(Atomic::TNamedObject {
2561 fqcn: Name::new("Suit"),
2562 type_params: empty_type_params(),
2563 });
2564 assert!(!sub.is_subtype_structural(&sup));
2565 }
2566
2567 #[test]
2568 fn subtype_enum_case_under_bare_object() {
2569 let sub = Type::single(Atomic::TLiteralEnumCase {
2570 enum_fqcn: Name::new("RoundingMode"),
2571 case_name: Name::new("Unnecessary"),
2572 });
2573 let sup = Type::single(Atomic::TObject);
2574 assert!(sub.is_subtype_structural(&sup));
2575 }
2576
2577 #[test]
2578 fn template_substitution() {
2579 let mut bindings = FxHashMap::default();
2580 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2581
2582 let tmpl = Type::single(Atomic::TTemplateParam {
2583 name: Name::new("T"),
2584 as_type: Box::new(Type::mixed()),
2585 defining_entity: Name::new("MyClass"),
2586 });
2587
2588 let resolved = tmpl.substitute_templates(&bindings);
2589 assert_eq!(resolved.types.len(), 1);
2590 assert!(matches!(resolved.types[0], Atomic::TString));
2591 }
2592
2593 #[test]
2594 fn intersection_is_object() {
2595 let parts = vec![
2596 Type::single(Atomic::TNamedObject {
2597 fqcn: Name::new("Iterator"),
2598 type_params: empty_type_params(),
2599 }),
2600 Type::single(Atomic::TNamedObject {
2601 fqcn: Name::new("Countable"),
2602 type_params: empty_type_params(),
2603 }),
2604 ];
2605 let atomic = Atomic::TIntersection {
2606 parts: vec_to_type_params(parts),
2607 };
2608 assert!(atomic.is_object());
2609 assert!(!atomic.can_be_falsy());
2610 assert!(atomic.can_be_truthy());
2611 }
2612
2613 #[test]
2614 fn intersection_display_two_parts() {
2615 let parts = vec![
2616 Type::single(Atomic::TNamedObject {
2617 fqcn: Name::new("Iterator"),
2618 type_params: empty_type_params(),
2619 }),
2620 Type::single(Atomic::TNamedObject {
2621 fqcn: Name::new("Countable"),
2622 type_params: empty_type_params(),
2623 }),
2624 ];
2625 let u = Type::single(Atomic::TIntersection {
2626 parts: vec_to_type_params(parts),
2627 });
2628 assert_eq!(format!("{u}"), "Iterator&Countable");
2629 }
2630
2631 #[test]
2632 fn intersection_display_three_parts() {
2633 let parts = vec![
2634 Type::single(Atomic::TNamedObject {
2635 fqcn: Name::new("A"),
2636 type_params: empty_type_params(),
2637 }),
2638 Type::single(Atomic::TNamedObject {
2639 fqcn: Name::new("B"),
2640 type_params: empty_type_params(),
2641 }),
2642 Type::single(Atomic::TNamedObject {
2643 fqcn: Name::new("C"),
2644 type_params: empty_type_params(),
2645 }),
2646 ];
2647 let u = Type::single(Atomic::TIntersection {
2648 parts: vec_to_type_params(parts),
2649 });
2650 assert_eq!(format!("{u}"), "A&B&C");
2651 }
2652
2653 #[test]
2654 fn intersection_in_nullable_union_display() {
2655 let intersection = Atomic::TIntersection {
2656 parts: vec_to_type_params(vec![
2657 Type::single(Atomic::TNamedObject {
2658 fqcn: Name::new("Iterator"),
2659 type_params: empty_type_params(),
2660 }),
2661 Type::single(Atomic::TNamedObject {
2662 fqcn: Name::new("Countable"),
2663 type_params: empty_type_params(),
2664 }),
2665 ]),
2666 };
2667 let mut u = Type::single(intersection);
2668 u.add_type(Atomic::TNull);
2669 assert!(u.is_nullable());
2670 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2671 }
2672
2673 fn t_param(name: &str) -> Type {
2676 Type::single(Atomic::TTemplateParam {
2677 name: Name::new(name),
2678 as_type: Box::new(Type::mixed()),
2679 defining_entity: Name::new("Fn"),
2680 })
2681 }
2682
2683 fn bindings_t_string() -> FxHashMap<Name, Type> {
2684 let mut b = FxHashMap::default();
2685 b.insert(Name::new("T"), Type::single(Atomic::TString));
2686 b
2687 }
2688
2689 #[test]
2690 fn substitute_non_empty_array_key_and_value() {
2691 let ty = Type::single(Atomic::TNonEmptyArray {
2692 key: Box::new(t_param("T")),
2693 value: Box::new(t_param("T")),
2694 });
2695 let result = ty.substitute_templates(&bindings_t_string());
2696 assert_eq!(result.types.len(), 1);
2697 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2698 panic!("expected TNonEmptyArray");
2699 };
2700 assert!(matches!(key.types[0], Atomic::TString));
2701 assert!(matches!(value.types[0], Atomic::TString));
2702 }
2703
2704 #[test]
2705 fn substitute_non_empty_list_value() {
2706 let ty = Type::single(Atomic::TNonEmptyList {
2707 value: Box::new(t_param("T")),
2708 });
2709 let result = ty.substitute_templates(&bindings_t_string());
2710 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2711 panic!("expected TNonEmptyList");
2712 };
2713 assert!(matches!(value.types[0], Atomic::TString));
2714 }
2715
2716 #[test]
2717 fn substitute_keyed_array_property_types() {
2718 use crate::atomic::{ArrayKey, KeyedProperty};
2719 use indexmap::IndexMap;
2720 let mut props = IndexMap::new();
2721 props.insert(
2722 ArrayKey::String(Arc::from("name")),
2723 KeyedProperty {
2724 ty: t_param("T"),
2725 optional: false,
2726 },
2727 );
2728 props.insert(
2729 ArrayKey::String(Arc::from("tag")),
2730 KeyedProperty {
2731 ty: t_param("T"),
2732 optional: true,
2733 },
2734 );
2735 let ty = Type::single(Atomic::TKeyedArray {
2736 properties: Box::new(props),
2737 is_open: true,
2738 is_list: false,
2739 });
2740 let result = ty.substitute_templates(&bindings_t_string());
2741 let Atomic::TKeyedArray {
2742 properties,
2743 is_open,
2744 is_list,
2745 } = &result.types[0]
2746 else {
2747 panic!("expected TKeyedArray");
2748 };
2749 assert!(is_open);
2750 assert!(!is_list);
2751 assert!(matches!(
2752 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2753 Atomic::TString
2754 ));
2755 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2756 assert!(matches!(
2757 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2758 Atomic::TString
2759 ));
2760 }
2761
2762 #[test]
2763 fn substitute_callable_params_and_return() {
2764 use crate::atomic::FnParam;
2765 let ty = Type::single(Atomic::TCallable {
2766 params: Some(Box::new([FnParam {
2767 name: Name::new("x"),
2768 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2769 out_ty: None,
2770 default: None,
2771 is_variadic: false,
2772 is_byref: false,
2773 is_optional: false,
2774 }])),
2775 return_type: Some(Box::new(t_param("T"))),
2776 });
2777 let result = ty.substitute_templates(&bindings_t_string());
2778 let Atomic::TCallable {
2779 params,
2780 return_type,
2781 } = &result.types[0]
2782 else {
2783 panic!("expected TCallable");
2784 };
2785 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2786 let param_union = param_ty.to_union();
2787 assert!(matches!(param_union.types[0], Atomic::TString));
2788 let ret = return_type.as_ref().unwrap();
2789 assert!(matches!(ret.types[0], Atomic::TString));
2790 }
2791
2792 #[test]
2793 fn substitute_callable_bare_no_panic() {
2794 let ty = Type::single(Atomic::TCallable {
2796 params: None,
2797 return_type: None,
2798 });
2799 let result = ty.substitute_templates(&bindings_t_string());
2800 assert!(matches!(
2801 result.types[0],
2802 Atomic::TCallable {
2803 params: None,
2804 return_type: None
2805 }
2806 ));
2807 }
2808
2809 #[test]
2810 fn substitute_closure_params_return_and_this() {
2811 use crate::atomic::FnParam;
2812 let ty = Type::single(Atomic::TClosure {
2813 data: Box::new(crate::atomic::ClosureData {
2814 params: Box::new([FnParam {
2815 name: Name::new("a"),
2816 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2817 out_ty: None,
2818 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2819 is_variadic: true,
2820 is_byref: true,
2821 is_optional: true,
2822 }]),
2823 return_type: t_param("T"),
2824 this_type: Some(t_param("T")),
2825 }),
2826 });
2827 let result = ty.substitute_templates(&bindings_t_string());
2828 let Atomic::TClosure { data } = &result.types[0] else {
2829 panic!("expected TClosure");
2830 };
2831 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2832 let p = ¶ms[0];
2833 let ty_union = p.ty.as_ref().unwrap().to_union();
2834 let default_union = p.default.as_ref().unwrap().to_union();
2835 assert!(matches!(ty_union.types[0], Atomic::TString));
2836 assert!(matches!(default_union.types[0], Atomic::TString));
2837 assert!(p.is_variadic);
2839 assert!(p.is_byref);
2840 assert!(p.is_optional);
2841 assert!(matches!(return_type.types[0], Atomic::TString));
2842 assert!(matches!(
2843 this_type.as_ref().unwrap().types[0],
2844 Atomic::TString
2845 ));
2846 }
2847
2848 #[test]
2849 fn substitute_conditional_all_branches() {
2850 let ty = Type::single(conditional(
2851 None,
2852 t_param("T"),
2853 t_param("T"),
2854 Type::single(Atomic::TInt),
2855 ));
2856 let result = ty.substitute_templates(&bindings_t_string());
2857 let Atomic::TConditional { data } = &result.types[0] else {
2858 panic!("expected TConditional");
2859 };
2860 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2861 assert!(matches!(subject.types[0], Atomic::TString));
2862 assert!(matches!(if_true.types[0], Atomic::TString));
2863 assert!(matches!(if_false.types[0], Atomic::TInt));
2864 }
2865
2866 #[test]
2867 fn resolve_conditional_is_null_non_null_arg() {
2868 let ty = Type::single(conditional(
2869 Some(Name::new("x")),
2870 Type::single(Atomic::TNull),
2871 Type::single(Atomic::TInt),
2872 Type::single(Atomic::TString),
2873 ));
2874 let result = ty.resolve_conditional_returns(|name| {
2875 if name == "x" {
2876 Some(Type::single(Atomic::TString)) } else {
2878 None
2879 }
2880 });
2881 assert!(result.types.len() == 1);
2882 assert!(matches!(result.types[0], Atomic::TString));
2883 }
2884
2885 #[test]
2886 fn resolve_conditional_is_null_null_arg() {
2887 let ty = Type::single(conditional(
2888 Some(Name::new("x")),
2889 Type::single(Atomic::TNull),
2890 Type::single(Atomic::TInt),
2891 Type::single(Atomic::TString),
2892 ));
2893 let result = ty.resolve_conditional_returns(|name| {
2894 if name == "x" {
2895 Some(Type::single(Atomic::TNull)) } else {
2897 None
2898 }
2899 });
2900 assert!(result.types.len() == 1);
2901 assert!(matches!(result.types[0], Atomic::TInt));
2902 }
2903
2904 #[test]
2905 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2906 let mut nullable_str = Type::single(Atomic::TString);
2907 nullable_str.add_type(Atomic::TNull);
2908 let ty = Type::single(conditional(
2909 Some(Name::new("x")),
2910 Type::single(Atomic::TNull),
2911 Type::single(Atomic::TInt),
2912 Type::single(Atomic::TString),
2913 ));
2914 let result = ty.resolve_conditional_returns(|name| {
2915 if name == "x" {
2916 Some(nullable_str.clone())
2917 } else {
2918 None
2919 }
2920 });
2921 assert_eq!(result.types.len(), 2);
2923 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2924 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2925 }
2926
2927 #[test]
2928 fn resolve_conditional_nested_widens_inner_branch() {
2929 let inner = Type::single(conditional(
2932 Some(Name::new("x")),
2933 Type::single(Atomic::TString),
2934 Type::single(Atomic::TString),
2935 Type::single(Atomic::TFloat),
2936 ));
2937 let ty = Type::single(conditional(
2938 Some(Name::new("x")),
2939 Type::single(Atomic::TNull),
2940 Type::single(Atomic::TInt),
2941 inner,
2942 ));
2943 let result = ty.resolve_conditional_returns(|_| None);
2945 assert!(
2946 result
2947 .types
2948 .iter()
2949 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2950 "no TConditional should survive: {:?}",
2951 result.types
2952 );
2953 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2954 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2955 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2956 }
2957
2958 #[test]
2959 fn resolve_conditional_nested_resolves_inner_branch() {
2960 let inner = Type::single(conditional(
2964 Some(Name::new("x")),
2965 Type::single(Atomic::TString),
2966 Type::single(Atomic::TString),
2967 Type::single(Atomic::TFloat),
2968 ));
2969 let ty = Type::single(conditional(
2970 Some(Name::new("x")),
2971 Type::single(Atomic::TNull),
2972 Type::single(Atomic::TInt),
2973 inner,
2974 ));
2975 let result = ty.resolve_conditional_returns(|name| {
2977 if name == "x" {
2978 Some(Type::single(Atomic::TString))
2979 } else {
2980 None
2981 }
2982 });
2983 assert!(
2984 result
2985 .types
2986 .iter()
2987 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2988 "no TConditional should survive: {:?}",
2989 result.types
2990 );
2991 assert_eq!(result.types.len(), 1);
2992 assert!(matches!(result.types[0], Atomic::TString));
2993 }
2994
2995 #[test]
2996 fn resolve_conditional_true_subject_bool_arg_widens() {
2997 let ty = Type::single(conditional(
3001 Some(Name::new("x")),
3002 Type::single(Atomic::TTrue),
3003 Type::single(Atomic::TInt),
3004 Type::single(Atomic::TString),
3005 ));
3006 let result = ty.resolve_conditional_returns(|name| {
3007 if name == "x" {
3008 Some(Type::single(Atomic::TBool))
3009 } else {
3010 None
3011 }
3012 });
3013 assert_eq!(result.types.len(), 2);
3014 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3015 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3016 }
3017
3018 #[test]
3019 fn resolve_conditional_true_subject_mixed_arg_widens() {
3020 let ty = Type::single(conditional(
3023 Some(Name::new("x")),
3024 Type::single(Atomic::TTrue),
3025 Type::single(Atomic::TInt),
3026 Type::single(Atomic::TString),
3027 ));
3028 let result = ty.resolve_conditional_returns(|name| {
3029 if name == "x" {
3030 Some(Type::mixed())
3031 } else {
3032 None
3033 }
3034 });
3035 assert_eq!(result.types.len(), 2);
3036 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3037 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3038 }
3039
3040 #[test]
3041 fn resolve_conditional_true_subject_literal_args() {
3042 let ty = Type::single(conditional(
3045 Some(Name::new("x")),
3046 Type::single(Atomic::TTrue),
3047 Type::single(Atomic::TInt),
3048 Type::single(Atomic::TString),
3049 ));
3050 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3051 if name == "x" {
3052 Some(Type::single(Atomic::TFalse))
3053 } else {
3054 None
3055 }
3056 });
3057 assert_eq!(false_arg.types.len(), 1);
3058 assert!(matches!(false_arg.types[0], Atomic::TString));
3059
3060 let true_arg = ty.resolve_conditional_returns(|name| {
3061 if name == "x" {
3062 Some(Type::single(Atomic::TTrue))
3063 } else {
3064 None
3065 }
3066 });
3067 assert_eq!(true_arg.types.len(), 1);
3068 assert!(matches!(true_arg.types[0], Atomic::TInt));
3069 }
3070
3071 #[test]
3072 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3073 let ty = Type::single(conditional(
3077 Some(Name::new("x")),
3078 Type::single(Atomic::TBool),
3079 Type::single(Atomic::TInt),
3080 Type::single(Atomic::TString),
3081 ));
3082 let result = ty.resolve_conditional_returns(|name| {
3083 if name == "x" {
3084 Some(Type::single(Atomic::TScalar))
3085 } else {
3086 None
3087 }
3088 });
3089 assert_eq!(result.types.len(), 2);
3090 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3091 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3092 }
3093
3094 #[test]
3095 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3096 let ty = Type::single(conditional(
3100 Some(Name::new("x")),
3101 Type::single(Atomic::TString),
3102 Type::single(Atomic::TInt),
3103 Type::single(Atomic::TBool),
3104 ));
3105 let result = ty.resolve_conditional_returns(|name| {
3106 if name == "x" {
3107 Some(Type::single(Atomic::TBool))
3108 } else {
3109 None
3110 }
3111 });
3112 assert_eq!(result.types.len(), 1);
3113 assert!(matches!(result.types[0], Atomic::TBool));
3114 }
3115
3116 #[test]
3117 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3118 let ty = Type::single(conditional(
3121 Some(Name::new("x")),
3122 Type::single(Atomic::TList {
3123 value: Box::new(Type::mixed()),
3124 }),
3125 Type::single(Atomic::TInt),
3126 Type::single(Atomic::TString),
3127 ));
3128 let result = ty.resolve_conditional_returns(|name| {
3129 if name == "x" {
3130 Some(Type::single(Atomic::TArray {
3131 key: Box::new(Type::mixed()),
3132 value: Box::new(Type::mixed()),
3133 }))
3134 } else {
3135 None
3136 }
3137 });
3138 assert_eq!(result.types.len(), 2);
3139 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3140 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3141 }
3142
3143 #[test]
3144 fn resolve_conditional_float_subject_int_literal_false_branch() {
3145 let ty = resolve_conditional_branch(
3148 &Atomic::TFloat,
3149 &Type::single(Atomic::TLiteralInt(42)),
3150 &Type::single(Atomic::TInt),
3151 &Type::single(Atomic::TString),
3152 );
3153 assert!(matches!(
3154 ty.as_ref(),
3155 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3156 ));
3157 }
3158
3159 #[test]
3160 fn resolve_conditional_int_subject_float_literal_false_branch() {
3161 let ty = resolve_conditional_branch(
3162 &Atomic::TInt,
3163 &Type::single(Atomic::TLiteralFloat(1, 0)),
3164 &Type::single(Atomic::TInt),
3165 &Type::single(Atomic::TString),
3166 );
3167 assert!(matches!(
3168 ty.as_ref(),
3169 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3170 ));
3171 }
3172
3173 #[test]
3174 fn resolve_conditional_float_subject_float_literal_then_branch() {
3175 let ty = resolve_conditional_branch(
3176 &Atomic::TFloat,
3177 &Type::single(Atomic::TLiteralFloat(1, 5)),
3178 &Type::single(Atomic::TInt),
3179 &Type::single(Atomic::TString),
3180 );
3181 assert!(matches!(
3182 ty.as_ref(),
3183 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3184 ));
3185 }
3186
3187 #[test]
3188 fn resolve_conditional_int_subject_int_literal_then_branch() {
3189 let ty = resolve_conditional_branch(
3190 &Atomic::TInt,
3191 &Type::single(Atomic::TLiteralInt(7)),
3192 &Type::single(Atomic::TInt),
3193 &Type::single(Atomic::TString),
3194 );
3195 assert!(matches!(
3196 ty.as_ref(),
3197 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3198 ));
3199 }
3200
3201 #[test]
3202 fn resolve_conditional_int_subject_float_arg_false_branch() {
3203 let ty = resolve_conditional_branch(
3206 &Atomic::TInt,
3207 &Type::single(Atomic::TFloat),
3208 &Type::single(Atomic::TInt),
3209 &Type::single(Atomic::TString),
3210 );
3211 assert!(matches!(
3212 ty.as_ref(),
3213 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3214 ));
3215 }
3216
3217 #[test]
3218 fn resolve_conditional_array_subject_string_arg_false_branch() {
3219 let ty = resolve_conditional_branch(
3220 &Atomic::TArray {
3221 key: Box::new(Type::mixed()),
3222 value: Box::new(Type::mixed()),
3223 },
3224 &Type::single(Atomic::TString),
3225 &Type::single(Atomic::TInt),
3226 &Type::single(Atomic::TString),
3227 );
3228 assert!(matches!(
3229 ty.as_ref(),
3230 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3231 ));
3232 }
3233
3234 #[test]
3235 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3236 let ty = resolve_conditional_branch(
3237 &Atomic::TArray {
3238 key: Box::new(Type::mixed()),
3239 value: Box::new(Type::mixed()),
3240 },
3241 &Type::single(Atomic::TBool),
3242 &Type::single(Atomic::TInt),
3243 &Type::single(Atomic::TString),
3244 );
3245 assert!(matches!(
3246 ty.as_ref(),
3247 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3248 ));
3249 }
3250
3251 #[test]
3252 fn resolve_conditional_array_subject_mixed_arg_widens() {
3253 let ty = resolve_conditional_branch(
3257 &Atomic::TArray {
3258 key: Box::new(Type::mixed()),
3259 value: Box::new(Type::mixed()),
3260 },
3261 &Type::mixed(),
3262 &Type::single(Atomic::TInt),
3263 &Type::single(Atomic::TString),
3264 );
3265 assert!(ty.is_none());
3266 }
3267
3268 #[test]
3269 fn resolve_conditional_array_subject_list_arg_then_branch() {
3270 let ty = resolve_conditional_branch(
3273 &Atomic::TArray {
3274 key: Box::new(Type::mixed()),
3275 value: Box::new(Type::mixed()),
3276 },
3277 &Type::single(Atomic::TList {
3278 value: Box::new(Type::mixed()),
3279 }),
3280 &Type::single(Atomic::TInt),
3281 &Type::single(Atomic::TString),
3282 );
3283 assert!(matches!(
3284 ty.as_ref(),
3285 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3286 ));
3287 }
3288
3289 #[test]
3290 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3291 let ty = resolve_conditional_branch(
3293 &Atomic::TList {
3294 value: Box::new(Type::mixed()),
3295 },
3296 &Type::single(Atomic::TKeyedArray {
3297 properties: Box::default(),
3298 is_open: false,
3299 is_list: true,
3300 }),
3301 &Type::single(Atomic::TInt),
3302 &Type::single(Atomic::TString),
3303 );
3304 assert!(matches!(
3305 ty.as_ref(),
3306 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3307 ));
3308 }
3309
3310 #[test]
3311 fn resolve_conditional_list_subject_list_string_union_widens() {
3312 let ty = resolve_conditional_branch(
3316 &Atomic::TList {
3317 value: Box::new(Type::mixed()),
3318 },
3319 &Type::from_vec(vec![
3320 Atomic::TList {
3321 value: Box::new(Type::mixed()),
3322 },
3323 Atomic::TString,
3324 ]),
3325 &Type::single(Atomic::TInt),
3326 &Type::single(Atomic::TString),
3327 );
3328 assert!(ty.is_none());
3329 }
3330
3331 #[test]
3332 fn resolve_conditional_numeric_subject_widens() {
3333 let ty = resolve_conditional_branch(
3337 &Atomic::TNumeric,
3338 &Type::single(Atomic::TLiteralString("123".into())),
3339 &Type::single(Atomic::TInt),
3340 &Type::single(Atomic::TString),
3341 );
3342 assert!(ty.is_none());
3343 }
3344
3345 #[test]
3346 fn resolve_conditional_non_empty_string_subject_widens() {
3347 let ty = resolve_conditional_branch(
3350 &Atomic::TNonEmptyString,
3351 &Type::single(Atomic::TString),
3352 &Type::single(Atomic::TInt),
3353 &Type::single(Atomic::TString),
3354 );
3355 assert!(ty.is_none());
3356 }
3357
3358 #[test]
3359 fn resolve_conditional_template_arg_widens() {
3360 let ty = resolve_conditional_branch(
3363 &Atomic::TString,
3364 &t_param("T"),
3365 &Type::single(Atomic::TInt),
3366 &Type::single(Atomic::TString),
3367 );
3368 assert!(ty.is_none());
3369 }
3370
3371 #[test]
3372 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3373 let ty = resolve_conditional_branch(
3376 &Atomic::TMixed,
3377 &Type::single(Atomic::TBool),
3378 &Type::single(Atomic::TInt),
3379 &Type::single(Atomic::TString),
3380 );
3381 assert!(matches!(
3382 ty.as_ref(),
3383 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3384 ));
3385 }
3386
3387 #[test]
3388 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3389 let ty = resolve_conditional_branch(
3392 &Atomic::TScalar,
3393 &Type::single(Atomic::TNull),
3394 &Type::single(Atomic::TInt),
3395 &Type::single(Atomic::TString),
3396 );
3397 assert!(matches!(
3398 ty.as_ref(),
3399 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3400 ));
3401 }
3402
3403 #[test]
3404 fn resolve_conditional_object_subject_named_arg_then_branch() {
3405 let ty = resolve_conditional_branch(
3407 &Atomic::TObject,
3408 &Type::single(Atomic::TNamedObject {
3409 fqcn: Name::new("Foo"),
3410 type_params: empty_type_params(),
3411 }),
3412 &Type::single(Atomic::TInt),
3413 &Type::single(Atomic::TString),
3414 );
3415 assert!(matches!(
3416 ty.as_ref(),
3417 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3418 ));
3419 }
3420
3421 #[test]
3422 fn resolve_conditional_named_subject_other_class_widens() {
3423 let ty = resolve_conditional_branch(
3427 &Atomic::TNamedObject {
3428 fqcn: Name::new("Foo"),
3429 type_params: empty_type_params(),
3430 },
3431 &Type::single(Atomic::TNamedObject {
3432 fqcn: Name::new("Bar"),
3433 type_params: empty_type_params(),
3434 }),
3435 &Type::single(Atomic::TInt),
3436 &Type::single(Atomic::TString),
3437 );
3438 assert!(ty.is_none());
3439 }
3440
3441 #[test]
3442 fn resolve_conditional_enum_case_subject_widens() {
3443 let ty = resolve_conditional_branch(
3446 &Atomic::TLiteralEnumCase {
3447 enum_fqcn: Name::new("RoundingMode"),
3448 case_name: Name::new("Unnecessary"),
3449 },
3450 &Type::single(Atomic::TInt),
3451 &Type::single(Atomic::TInt),
3452 &Type::single(Atomic::TString),
3453 );
3454 assert!(ty.is_none());
3455 }
3456
3457 #[test]
3458 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3459 let ty = resolve_conditional_branch(
3461 &Atomic::TNull,
3462 &Type::single(Atomic::TBool),
3463 &Type::single(Atomic::TInt),
3464 &Type::single(Atomic::TString),
3465 );
3466 assert!(matches!(
3467 ty.as_ref(),
3468 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3469 ));
3470 }
3471
3472 #[test]
3473 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3474 let ty = resolve_conditional_branch(
3477 &Atomic::TBool,
3478 &Type::single(Atomic::TTrue),
3479 &Type::single(Atomic::TInt),
3480 &Type::single(Atomic::TString),
3481 );
3482 assert!(matches!(
3483 ty.as_ref(),
3484 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3485 ));
3486 }
3487
3488 #[test]
3489 fn substitute_intersection_parts() {
3490 let ty = Type::single(Atomic::TIntersection {
3491 parts: vec_to_type_params(vec![
3492 Type::single(Atomic::TNamedObject {
3493 fqcn: Name::new("Countable"),
3494 type_params: empty_type_params(),
3495 }),
3496 t_param("T"),
3497 ]),
3498 });
3499 let result = ty.substitute_templates(&bindings_t_string());
3500 let Atomic::TIntersection { parts } = &result.types[0] else {
3501 panic!("expected TIntersection");
3502 };
3503 assert_eq!(parts.len(), 2);
3504 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3505 assert!(matches!(parts[1].types[0], Atomic::TString));
3506 }
3507
3508 #[test]
3509 fn substitute_no_template_params_identity() {
3510 let ty = Type::single(Atomic::TInt);
3511 let result = ty.substitute_templates(&bindings_t_string());
3512 assert!(matches!(result.types[0], Atomic::TInt));
3513 }
3514}