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}
63
64impl Type {
65 pub fn empty() -> Self {
68 Self {
69 types: SmallVec::new(),
70 possibly_undefined: false,
71 from_docblock: false,
72 falsy_stripped: false,
73 }
74 }
75
76 pub fn single(atomic: Atomic) -> Self {
77 let mut types = SmallVec::new();
78 types.push(atomic);
79 Self {
80 types,
81 possibly_undefined: false,
82 from_docblock: false,
83 falsy_stripped: false,
84 }
85 }
86
87 pub fn mixed() -> Self {
88 Self::single(Atomic::TMixed)
89 }
90
91 pub fn void() -> Self {
92 Self::single(Atomic::TVoid)
93 }
94
95 pub fn never() -> Self {
96 Self::single(Atomic::TNever)
97 }
98
99 pub fn null() -> Self {
100 Self::single(Atomic::TNull)
101 }
102
103 pub fn bool() -> Self {
104 Self::single(Atomic::TBool)
105 }
106
107 pub fn int() -> Self {
108 Self::single(Atomic::TInt)
109 }
110
111 pub fn float() -> Self {
112 Self::single(Atomic::TFloat)
113 }
114
115 pub fn string() -> Self {
116 Self::single(Atomic::TString)
117 }
118
119 pub fn array_key() -> Self {
122 let mut u = Self::single(Atomic::TInt);
123 u.add_type(Atomic::TString);
124 u
125 }
126
127 pub fn nullable(atomic: Atomic) -> Self {
129 if matches!(atomic, Atomic::TMixed) {
131 return Self::mixed();
132 }
133 let mut types = SmallVec::new();
134 types.push(atomic);
135 types.push(Atomic::TNull);
136 Self {
137 types,
138 possibly_undefined: false,
139 from_docblock: false,
140 falsy_stripped: false,
141 }
142 }
143
144 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
146 let mut u = Self::empty();
147 for a in atomics {
148 u.add_type(a);
149 }
150 u
151 }
152
153 pub fn is_empty(&self) -> bool {
156 self.types.is_empty()
157 }
158
159 pub fn is_single(&self) -> bool {
160 self.types.len() == 1
161 }
162
163 pub fn is_nullable(&self) -> bool {
164 self.types.iter().any(|t| matches!(t, Atomic::TNull))
165 }
166
167 pub fn is_array_key(&self) -> bool {
172 self.types.len() == 2
173 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
174 && self.types.iter().any(|t| matches!(t, Atomic::TString))
175 }
176
177 pub fn is_mixed(&self) -> bool {
178 self.types.iter().any(|t| match t {
179 Atomic::TMixed => true,
180 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
181 _ => false,
182 })
183 }
184
185 pub fn is_mixed_not_template(&self) -> bool {
190 self.is_mixed()
191 && !self
192 .types
193 .iter()
194 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
195 }
196
197 pub fn is_never(&self) -> bool {
198 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
199 }
200
201 pub fn clone_validity(&self) -> CloneValidity {
204 if self.types.is_empty() {
205 return CloneValidity::Unknown;
206 }
207 let mut has_non_object = false;
208 let mut has_other = false; for t in &self.types {
210 match t {
211 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
212 CloneValidity::Invalid => has_non_object = true,
213 CloneValidity::PossiblyInvalid => {
214 has_non_object = true;
215 has_other = true;
216 }
217 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
218 },
219 other if other.is_definitely_non_object() => has_non_object = true,
220 _ => has_other = true,
221 }
222 }
223 match (has_non_object, has_other) {
224 (true, false) => CloneValidity::Invalid,
225 (true, true) => CloneValidity::PossiblyInvalid,
226 _ => CloneValidity::Cloneable,
227 }
228 }
229
230 pub fn is_void(&self) -> bool {
231 self.is_single() && matches!(self.types[0], Atomic::TVoid)
232 }
233
234 pub fn can_be_falsy(&self) -> bool {
235 self.types.iter().any(|t| t.can_be_falsy())
236 }
237
238 pub fn can_be_truthy(&self) -> bool {
239 self.types.iter().any(|t| t.can_be_truthy())
240 }
241
242 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
243 self.types.iter().any(f)
244 }
245
246 pub fn has_named_object(&self, fqcn: &str) -> bool {
247 self.types.iter().any(|t| match t {
248 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
249 _ => false,
250 })
251 }
252
253 pub fn add_type(&mut self, atomic: Atomic) {
258 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
260 return;
261 }
262
263 if matches!(atomic, Atomic::TMixed) {
265 self.types.clear();
266 self.types.push(Atomic::TMixed);
267 return;
268 }
269
270 let atomic = if let Atomic::TConditional { data } = &atomic {
273 let (if_true, if_false) = (&data.if_true, &data.if_false);
274 let mut simplified_true = Type::empty();
275 for t in &if_true.types {
276 simplified_true.add_type(t.clone());
277 }
278 let mut simplified_false = Type::empty();
279 for t in &if_false.types {
280 simplified_false.add_type(t.clone());
281 }
282 if simplified_true == simplified_false {
283 for t in simplified_true.types {
284 self.add_type(t);
285 }
286 return;
287 }
288 atomic
289 } else {
290 atomic
291 };
292
293 if self.types.contains(&atomic) {
295 return;
296 }
297
298 if let Atomic::TLiteralInt(_) = &atomic {
300 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
301 return;
302 }
303 }
304 if let Atomic::TLiteralString(_) = &atomic {
306 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
307 return;
308 }
309 }
310 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
312 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
313 {
314 return;
315 }
316 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
319 {
320 self.types.retain(|t| !matches!(t, Atomic::TFalse));
321 self.types.push(Atomic::TBool);
322 return;
323 }
324 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
325 {
326 self.types.retain(|t| !matches!(t, Atomic::TTrue));
327 self.types.push(Atomic::TBool);
328 return;
329 }
330 if matches!(atomic, Atomic::TInt) {
332 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
333 }
334 if matches!(atomic, Atomic::TString) {
336 self.types
337 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
338 }
339 if matches!(atomic, Atomic::TBool) {
341 self.types
342 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
343 }
344
345 if matches!(atomic, Atomic::TNever) {
347 if !self.types.is_empty() {
348 return;
349 }
350 } else {
351 self.types.retain(|t| !matches!(t, Atomic::TNever));
352 }
353
354 if let Atomic::TKeyedArray {
360 properties,
361 is_open,
362 ..
363 } = &atomic
364 {
365 if properties.is_empty() && !is_open {
366 for existing in &self.types {
367 match existing {
368 Atomic::TArray { .. }
369 | Atomic::TNonEmptyArray { .. }
370 | Atomic::TList { .. }
371 | Atomic::TNonEmptyList { .. } => {
372 return; }
374 _ => {}
375 }
376 }
377 }
378 }
379
380 let is_generic_array_or_list = matches!(
383 &atomic,
384 Atomic::TArray { .. }
385 | Atomic::TNonEmptyArray { .. }
386 | Atomic::TList { .. }
387 | Atomic::TNonEmptyList { .. }
388 );
389 if is_generic_array_or_list {
390 self.types.retain(|t| {
391 if let Atomic::TKeyedArray {
392 properties,
393 is_open,
394 ..
395 } = t
396 {
397 !properties.is_empty() || *is_open
398 } else {
399 true
400 }
401 });
402 }
403
404 self.types.push(atomic);
405 }
406
407 pub fn remove_null(&self) -> Type {
411 self.filter(|t| !matches!(t, Atomic::TNull))
412 }
413
414 pub fn remove_false(&self) -> Type {
417 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
418 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
419 result.add_type(Atomic::TTrue);
420 }
421 result
422 }
423
424 pub fn remove_true(&self) -> Type {
427 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
428 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
429 result.add_type(Atomic::TFalse);
430 }
431 result
432 }
433
434 pub fn core_type(&self) -> Type {
436 self.remove_null().remove_false()
437 }
438
439 pub fn narrow_to_truthy(&self) -> Type {
441 if self.is_mixed_not_template() {
442 return Type::mixed();
443 }
444 let mut result = Type::empty();
445 result.from_docblock = self.from_docblock;
446 for t in &self.types {
447 match t {
448 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
452 Atomic::TLiteralInt(0)
454 | Atomic::TLiteralFloat(0, 0)
455 | Atomic::TNull
456 | Atomic::TFalse => {}
457 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
458 Atomic::TBool => result.add_type(Atomic::TTrue),
460 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
462 key: key.clone(),
463 value: value.clone(),
464 }),
465 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
466 value: value.clone(),
467 }),
468 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
472 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
477 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
478 let atom = if max.is_none() {
479 Atomic::TPositiveInt
480 } else {
481 Atomic::TIntRange {
482 min: Some(1),
483 max: *max,
484 }
485 };
486 result.add_type(atom);
487 }
488 Atomic::TIntRange { min, max: Some(0) } => {
490 let atom = match min {
491 None => Atomic::TNegativeInt,
492 Some(n) if *n <= -1 => Atomic::TIntRange {
493 min: *min,
494 max: Some(-1),
495 },
496 _ => continue, };
498 result.add_type(atom);
499 }
500 t if !t.can_be_truthy() => {}
502 _ => result.add_type(t.clone()),
503 }
504 }
505 result
506 }
507
508 pub fn narrow_to_falsy(&self) -> Type {
510 if self.is_mixed_not_template() {
511 return Type::from_vec(vec![
512 Atomic::TNull,
513 Atomic::TFalse,
514 Atomic::TLiteralInt(0),
515 Atomic::TLiteralString("".into()),
516 ]);
517 }
518 let mut result = Type::empty();
519 result.from_docblock = self.from_docblock;
520 for t in &self.types {
521 match t {
522 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
527 Atomic::TBool => result.add_type(Atomic::TFalse),
529 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
531 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
533 Atomic::TString => {
535 result.add_type(Atomic::TLiteralString("".into()));
536 result.add_type(Atomic::TLiteralString("0".into()));
537 }
538 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
540 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
542 Atomic::TIntRange {
544 min: Some(0),
545 max: Some(_) | None,
546 } => result.add_type(Atomic::TLiteralInt(0)),
547 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
549 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
551 }
552 }
553 result
554 }
555
556 pub fn narrow_instanceof(&self, class: &str) -> Type {
562 let narrowed_ty = Atomic::TNamedObject {
563 fqcn: class.into(),
564 type_params: empty_type_params(),
565 };
566 let has_object = self.types.iter().any(|t| {
568 matches!(
569 t,
570 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
572 });
573 if has_object || self.is_empty() {
574 Type::single(narrowed_ty)
575 } else {
576 Type::single(narrowed_ty)
579 }
580 }
581
582 pub fn narrow_to_string(&self) -> Type {
586 self.filter_replacing(
587 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
588 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
589 Atomic::TString,
590 )
591 }
592
593 pub fn narrow_to_int(&self) -> Type {
595 self.filter_replacing(
596 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
597 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
598 Atomic::TInt,
599 )
600 }
601
602 pub fn narrow_to_float(&self) -> Type {
604 self.filter_replacing(
605 |t| {
606 matches!(
607 t,
608 Atomic::TFloat
609 | Atomic::TIntegralFloat
610 | Atomic::TLiteralFloat(..)
611 | Atomic::TTemplateParam { .. }
612 )
613 },
614 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
615 Atomic::TFloat,
616 )
617 }
618
619 pub fn narrow_to_bool(&self) -> Type {
621 self.filter_replacing(
622 |t| {
623 matches!(
624 t,
625 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
626 )
627 },
628 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
629 Atomic::TBool,
630 )
631 }
632
633 pub fn narrow_to_null(&self) -> Type {
635 self.filter_replacing(
636 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
637 |t| matches!(t, Atomic::TMixed),
638 Atomic::TNull,
639 )
640 }
641
642 pub fn narrow_to_array(&self) -> Type {
644 self.filter_replacing(
645 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
646 |t| matches!(t, Atomic::TMixed),
647 Atomic::TArray {
648 key: Box::new(Type::mixed()),
649 value: Box::new(Type::mixed()),
650 },
651 )
652 }
653
654 pub fn narrow_to_non_empty_collection(&self) -> Type {
656 let mut out = Type::empty();
657 out.from_docblock = self.from_docblock;
658 for t in &self.types {
659 match t {
660 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
661 key: key.clone(),
662 value: value.clone(),
663 }),
664 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
665 value: value.clone(),
666 }),
667 _ => out.add_type(t.clone()),
668 }
669 }
670 out
671 }
672
673 pub fn narrow_to_empty_collection(&self) -> Type {
683 let mut out = Type::empty();
684 out.from_docblock = self.from_docblock;
685 for t in &self.types {
686 match t {
687 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
688 Atomic::TArray { .. } | Atomic::TList { .. } => {
689 out.add_type(Atomic::TKeyedArray {
690 properties: Box::default(),
691 is_open: false,
692 is_list: true,
693 });
694 }
695 _ => out.add_type(t.clone()),
696 }
697 }
698 out
699 }
700
701 pub fn narrow_to_list(&self) -> Type {
713 let mut out = Type::empty();
714 out.from_docblock = self.from_docblock;
715 for t in &self.types {
716 match t {
717 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
718 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
724 out.add_type(Atomic::TList {
725 value: value.clone(),
726 });
727 }
728 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
729 out.add_type(Atomic::TNonEmptyList {
730 value: value.clone(),
731 });
732 }
733 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
734 Atomic::TMixed => out.add_type(Atomic::TList {
735 value: Box::new(Type::mixed()),
736 }),
737 _ => {}
738 }
739 }
740 if out.is_empty() {
741 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
742 } else {
743 out
744 }
745 }
746
747 pub fn narrow_to_object(&self) -> Type {
752 let mut out = Type::empty();
753 for t in &self.types {
754 if matches!(t, Atomic::TMixed) {
755 out.add_type(Atomic::TObject);
756 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
757 out.add_type(t.clone());
758 }
759 }
760 if out.types.is_empty() {
761 self.filter(|t| t.is_object())
762 } else {
763 out
764 }
765 }
766
767 pub fn narrow_to_callable(&self) -> Type {
774 let narrowed = self.filter(|t| {
775 t.is_callable()
776 || t.is_string()
777 || t.is_array()
778 || t.is_object()
779 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
780 });
781 let mut result = Type::empty();
788 result.possibly_undefined = narrowed.possibly_undefined;
789 result.from_docblock = narrowed.from_docblock;
790 for atomic in narrowed.types {
791 if matches!(atomic, Atomic::TObject) {
792 result.add_type(Atomic::TCallable {
793 params: None,
794 return_type: None,
795 });
796 } else {
797 result.add_type(atomic);
798 }
799 }
800 result
801 }
802
803 pub fn narrow_to_scalar(&self) -> Type {
805 self.filter_replacing(
806 |t| {
807 t.is_string()
808 || t.is_int()
809 || matches!(
810 t,
811 Atomic::TFloat
812 | Atomic::TIntegralFloat
813 | Atomic::TLiteralFloat(..)
814 | Atomic::TBool
815 | Atomic::TTrue
816 | Atomic::TFalse
817 | Atomic::TScalar
818 | Atomic::TNumeric
819 | Atomic::TNumericString
820 | Atomic::TTemplateParam { .. }
821 )
822 },
823 |t| matches!(t, Atomic::TMixed),
824 Atomic::TScalar,
825 )
826 }
827
828 pub fn narrow_to_iterable(&self) -> Type {
831 self.filter(|t| {
832 t.is_array()
833 || t.is_object()
834 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
835 })
836 }
837
838 pub fn narrow_to_countable(&self) -> Type {
841 self.filter(|t| {
842 t.is_array()
843 || t.is_object()
844 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
845 })
846 }
847
848 pub fn narrow_to_resource(&self) -> Type {
852 self.filter(|t| matches!(t, Atomic::TMixed))
854 }
855
856 pub fn narrow_to_class_string(&self) -> Type {
861 let mut out = Type::empty();
862 out.from_docblock = self.from_docblock;
863 for t in &self.types {
864 match t {
865 Atomic::TClassString(_) => out.add_type(t.clone()),
866 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
867 out.add_type(Atomic::TClassString(None));
868 }
869 _ => {}
870 }
871 }
872 out
873 }
874
875 pub fn narrow_to_interface_string(&self) -> Type {
880 let mut out = Type::empty();
881 out.from_docblock = self.from_docblock;
882 for t in &self.types {
883 match t {
884 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
885 Atomic::TClassString(name) => {
889 out.add_type(Atomic::TInterfaceString(*name));
890 }
891 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
892 out.add_type(Atomic::TInterfaceString(None));
893 }
894 _ => {}
895 }
896 }
897 out
898 }
899
900 pub fn merge(a: &Type, b: &Type) -> Type {
905 if b.types.is_empty() {
907 let mut result = a.clone();
908 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
909 return result;
910 }
911 if a.types.is_empty() {
913 let mut result = b.clone();
914 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
915 return result;
916 }
917 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
919 let mut result = a.clone();
920 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
921 return result;
922 }
923 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
925 return Type {
926 types: smallvec::smallvec![Atomic::TMixed],
927 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
928 from_docblock: a.from_docblock || b.from_docblock,
929 falsy_stripped: false,
930 };
931 }
932 let mut result = a.clone();
933 result.merge_with(b);
934 result
935 }
936
937 pub fn merge_with(&mut self, other: &Type) {
939 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
940 self.possibly_undefined |= other.possibly_undefined;
941 return;
942 }
943 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
944 self.types.clear();
945 self.types.push(Atomic::TMixed);
946 self.possibly_undefined |= other.possibly_undefined;
947 return;
948 }
949 for atomic in &other.types {
950 self.add_type(atomic.clone());
951 }
952 self.possibly_undefined |= other.possibly_undefined;
953 }
954
955 pub fn intersect_with(&self, other: &Type) -> Type {
959 if self.is_mixed() {
960 return other.clone();
961 }
962 if other.is_mixed() {
963 return self.clone();
964 }
965 let mut result = Type::empty();
973 for a in &self.types {
974 for b in &other.types {
975 if a == b {
976 result.add_type(a.clone());
977 } else if atomic_subtype(b, a) {
978 result.add_type(b.clone());
979 } else if atomic_subtype(a, b) {
980 result.add_type(a.clone());
981 }
982 }
983 }
984 if result.is_empty() {
985 Type::never()
986 } else {
987 result
988 }
989 }
990
991 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
995 if bindings.is_empty() {
996 return self.clone();
997 }
998 if !self.types.iter().any(atomic_may_contain_templates) {
1001 return self.clone();
1002 }
1003 let mut result = Type::empty();
1004 result.possibly_undefined = self.possibly_undefined;
1005 result.from_docblock = self.from_docblock;
1006 for atomic in &self.types {
1007 match atomic {
1008 Atomic::TTemplateParam { name, .. } => {
1009 if let Some(resolved) = bindings.get(name) {
1010 for t in &resolved.types {
1011 result.add_type(t.clone());
1012 }
1013 } else {
1014 result.add_type(atomic.clone());
1015 }
1016 }
1017 Atomic::TArray { key, value } => {
1018 result.add_type(Atomic::TArray {
1019 key: Box::new(key.substitute_templates(bindings)),
1020 value: Box::new(value.substitute_templates(bindings)),
1021 });
1022 }
1023 Atomic::TList { value } => {
1024 result.add_type(Atomic::TList {
1025 value: Box::new(value.substitute_templates(bindings)),
1026 });
1027 }
1028 Atomic::TNonEmptyArray { key, value } => {
1029 result.add_type(Atomic::TNonEmptyArray {
1030 key: Box::new(key.substitute_templates(bindings)),
1031 value: Box::new(value.substitute_templates(bindings)),
1032 });
1033 }
1034 Atomic::TNonEmptyList { value } => {
1035 result.add_type(Atomic::TNonEmptyList {
1036 value: Box::new(value.substitute_templates(bindings)),
1037 });
1038 }
1039 Atomic::TKeyedArray {
1040 properties,
1041 is_open,
1042 is_list,
1043 } => {
1044 use crate::atomic::KeyedProperty;
1045 let new_props = properties
1046 .iter()
1047 .map(|(k, prop)| {
1048 (
1049 k.clone(),
1050 KeyedProperty {
1051 ty: prop.ty.substitute_templates(bindings),
1052 optional: prop.optional,
1053 },
1054 )
1055 })
1056 .collect();
1057 result.add_type(Atomic::TKeyedArray {
1058 properties: Box::new(new_props),
1059 is_open: *is_open,
1060 is_list: *is_list,
1061 });
1062 }
1063 Atomic::TCallable {
1064 params,
1065 return_type,
1066 } => {
1067 result.add_type(Atomic::TCallable {
1068 params: params.as_ref().map(|ps| {
1069 ps.iter()
1070 .map(|p| substitute_in_fn_param(p, bindings))
1071 .collect()
1072 }),
1073 return_type: return_type
1074 .as_ref()
1075 .map(|r| Box::new(r.substitute_templates(bindings))),
1076 });
1077 }
1078 Atomic::TClosure { data } => {
1079 result.add_type(Atomic::TClosure {
1080 data: Box::new(crate::atomic::ClosureData {
1081 params: data
1082 .params
1083 .iter()
1084 .map(|p| substitute_in_fn_param(p, bindings))
1085 .collect(),
1086 return_type: data.return_type.substitute_templates(bindings),
1087 this_type: data
1088 .this_type
1089 .as_ref()
1090 .map(|t| t.substitute_templates(bindings)),
1091 }),
1092 });
1093 }
1094 Atomic::TConditional { data } => {
1095 let param_name = &data.param_name;
1096 let new_subject = data.subject.substitute_templates(bindings);
1097 let new_if_true = data.if_true.substitute_templates(bindings);
1098 let new_if_false = data.if_false.substitute_templates(bindings);
1099
1100 let resolved = if let Some(name) = param_name {
1104 if let Some(bound) = bindings.get(name) {
1105 if new_subject.types.len() == 1 {
1106 resolve_conditional_branch(
1107 &new_subject.types[0],
1108 bound,
1109 &new_if_true,
1110 &new_if_false,
1111 )
1112 } else {
1113 None
1114 }
1115 } else {
1116 None
1117 }
1118 } else {
1119 None
1120 };
1121
1122 if let Some(branch) = resolved {
1123 for t in branch.types {
1124 result.add_type(t);
1125 }
1126 } else {
1127 result.add_type(Atomic::TConditional {
1128 data: Box::new(crate::atomic::ConditionalData {
1129 param_name: *param_name,
1130 subject: new_subject,
1131 if_true: new_if_true,
1132 if_false: new_if_false,
1133 }),
1134 });
1135 }
1136 }
1137 Atomic::TIntersection { parts } => {
1138 result.add_type(Atomic::TIntersection {
1139 parts: vec_to_type_params(
1140 parts
1141 .iter()
1142 .map(|p| p.substitute_templates(bindings))
1143 .collect(),
1144 ),
1145 });
1146 }
1147 Atomic::TNamedObject { fqcn, type_params } => {
1148 if type_params.is_empty() && !fqcn.contains('\\') {
1155 if let Some(resolved) = bindings.get(fqcn) {
1156 for t in &resolved.types {
1157 result.add_type(t.clone());
1158 }
1159 continue;
1160 }
1161 }
1162 let new_params: Vec<Type> = type_params
1163 .iter()
1164 .map(|p| p.substitute_templates(bindings))
1165 .collect();
1166 result.add_type(Atomic::TNamedObject {
1167 fqcn: *fqcn,
1168 type_params: vec_to_type_params(new_params),
1169 });
1170 }
1171 Atomic::TClassString(Some(param_name)) => {
1173 if let Some(resolved) = bindings.get(param_name) {
1174 for r_atomic in &resolved.types {
1175 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1176 Some(*fqcn)
1177 } else {
1178 None
1179 };
1180 result.add_type(Atomic::TClassString(cls_name));
1181 }
1182 } else {
1183 result.add_type(atomic.clone());
1184 }
1185 }
1186 Atomic::TInterfaceString(Some(param_name)) => {
1188 if let Some(resolved) = bindings.get(param_name) {
1189 for r_atomic in &resolved.types {
1190 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1191 Some(*fqcn)
1192 } else {
1193 None
1194 };
1195 result.add_type(Atomic::TInterfaceString(iface_name));
1196 }
1197 } else {
1198 result.add_type(atomic.clone());
1199 }
1200 }
1201 _ => {
1202 result.add_type(atomic.clone());
1203 }
1204 }
1205 }
1206 result
1207 }
1208
1209 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1215 where
1216 F: Fn(&str) -> Option<Type>,
1217 {
1218 self.resolve_conditional_inner(&lookup)
1219 }
1220
1221 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1222 where
1223 F: Fn(&str) -> Option<Type>,
1224 {
1225 let mut result = Type::empty();
1226 for atomic in self.types {
1227 match atomic {
1228 Atomic::TConditional { ref data } => {
1229 let (param_name, subject, if_true, if_false) = (
1230 &data.param_name,
1231 &data.subject,
1232 &data.if_true,
1233 &data.if_false,
1234 );
1235 let resolved = if subject.types.len() == 1 {
1236 if let Some(name) = param_name {
1237 if let Some(arg_ty) = lookup(name.as_ref()) {
1238 resolve_conditional_branch(
1239 &subject.types[0],
1240 &arg_ty,
1241 if_true,
1242 if_false,
1243 )
1244 } else {
1245 None
1246 }
1247 } else {
1248 None
1249 }
1250 } else {
1251 None
1252 };
1253
1254 if let Some(branch) = resolved {
1255 for t in branch.resolve_conditional_inner(lookup).types {
1257 result.add_type(t);
1258 }
1259 } else {
1260 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1263 result.add_type(t);
1264 }
1265 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1266 result.add_type(t);
1267 }
1268 }
1269 }
1270 other => result.add_type(other),
1271 }
1272 }
1273 result
1274 }
1275
1276 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1286 if other.is_mixed() {
1287 return true;
1288 }
1289 if self.is_never() {
1290 return true; }
1292 self.types
1293 .iter()
1294 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1295 }
1296
1297 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1301 if self.is_mixed() {
1302 return true;
1303 }
1304 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1305 }
1306
1307 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1310 let mut result = Type::empty();
1311 result.possibly_undefined = self.possibly_undefined;
1312 result.from_docblock = self.from_docblock;
1313 for atomic in &self.types {
1314 if f(atomic) {
1315 result.types.push(atomic.clone());
1316 }
1317 }
1318 result
1319 }
1320
1321 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1326 &self,
1327 keep: K,
1328 placeholder: P,
1329 replacement: Atomic,
1330 ) -> Type {
1331 let mut result = Type::empty();
1332 result.possibly_undefined = self.possibly_undefined;
1333 result.from_docblock = self.from_docblock;
1334 for atomic in &self.types {
1335 if keep(atomic) {
1336 result.add_type(atomic.clone());
1337 } else if placeholder(atomic) {
1338 result.add_type(replacement.clone());
1339 }
1340 }
1341 result
1342 }
1343
1344 pub fn possibly_undefined(mut self) -> Self {
1346 self.possibly_undefined = true;
1347 self
1348 }
1349
1350 pub fn from_docblock(mut self) -> Self {
1352 self.from_docblock = true;
1353 self
1354 }
1355
1356 pub fn falsy_stripped(mut self) -> Self {
1359 self.falsy_stripped = true;
1360 self
1361 }
1362}
1363
1364fn is_string_atomic(a: &Atomic) -> bool {
1369 matches!(
1370 a,
1371 Atomic::TString
1372 | Atomic::TNonEmptyString
1373 | Atomic::TLiteralString(_)
1374 | Atomic::TNumericString
1375 | Atomic::TClassString(_)
1376 | Atomic::TInterfaceString(_)
1377 | Atomic::TCallableString
1378 )
1379}
1380
1381fn is_array_atomic(a: &Atomic) -> bool {
1382 matches!(
1383 a,
1384 Atomic::TArray { .. }
1385 | Atomic::TNonEmptyArray { .. }
1386 | Atomic::TKeyedArray { .. }
1387 | Atomic::TList { .. }
1388 | Atomic::TNonEmptyList { .. }
1389 )
1390}
1391
1392fn is_list_atomic(a: &Atomic) -> bool {
1393 match a {
1394 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1395 Atomic::TKeyedArray { is_list, .. } => *is_list,
1396 _ => false,
1397 }
1398}
1399
1400fn is_float_atomic(a: &Atomic) -> bool {
1401 matches!(
1402 a,
1403 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..)
1404 )
1405}
1406
1407fn is_bool_atomic(a: &Atomic) -> bool {
1408 matches!(a, Atomic::TBool | Atomic::TTrue | Atomic::TFalse)
1409}
1410
1411fn resolve_conditional_branch(
1417 subject: &Atomic,
1418 arg_ty: &Type,
1419 if_true: &Type,
1420 if_false: &Type,
1421) -> Option<Type> {
1422 let predicate: fn(&Atomic) -> bool = match subject {
1423 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1424 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1425 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1426 Atomic::TString => is_string_atomic,
1427 Atomic::TList { .. } => is_list_atomic,
1428 Atomic::TArray { .. } => is_array_atomic,
1429 Atomic::TInt => Atomic::is_int,
1430 Atomic::TFloat => is_float_atomic,
1431 Atomic::TBool => is_bool_atomic,
1432 _ => return None,
1433 };
1434
1435 if arg_ty.types.is_empty() {
1436 return None;
1437 }
1438 let all_match = arg_ty.types.iter().all(&predicate);
1439 let none_match = !arg_ty.types.iter().any(predicate);
1440 if all_match {
1441 Some(if_true.clone())
1442 } else if none_match {
1443 Some(if_false.clone())
1444 } else {
1445 None
1446 }
1447}
1448
1449fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1457 match atomic {
1458 Atomic::TNamedObject { fqcn, type_params } => {
1462 !type_params.is_empty() || !fqcn.contains('\\')
1463 }
1464 Atomic::TTemplateParam { .. }
1465 | Atomic::TArray { .. }
1466 | Atomic::TList { .. }
1467 | Atomic::TNonEmptyArray { .. }
1468 | Atomic::TNonEmptyList { .. }
1469 | Atomic::TKeyedArray { .. }
1470 | Atomic::TCallable { .. }
1471 | Atomic::TClosure { .. }
1472 | Atomic::TConditional { .. }
1473 | Atomic::TIntersection { .. }
1474 | Atomic::TClassString(Some(_))
1475 | Atomic::TInterfaceString(Some(_)) => true,
1476 _ => false,
1477 }
1478}
1479
1480fn substitute_in_fn_param(
1481 p: &crate::atomic::FnParam,
1482 bindings: &FxHashMap<Name, Type>,
1483) -> crate::atomic::FnParam {
1484 crate::atomic::FnParam {
1485 name: p.name,
1486 ty: p.ty.as_ref().map(|t| {
1487 let u = t.to_union();
1488 let substituted = u.substitute_templates(bindings);
1489 crate::compact::SimpleType::from_union(substituted)
1490 }),
1491 out_ty: p.out_ty.as_ref().map(|t| {
1492 let u = t.to_union();
1493 let substituted = u.substitute_templates(bindings);
1494 crate::compact::SimpleType::from_union(substituted)
1495 }),
1496 default: p.default.as_ref().map(|d| {
1497 let u = d.to_union();
1498 let substituted = u.substitute_templates(bindings);
1499 crate::compact::SimpleType::from_union(substituted)
1500 }),
1501 is_variadic: p.is_variadic,
1502 is_byref: p.is_byref,
1503 is_optional: p.is_optional,
1504 }
1505}
1506
1507pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1513 if sub == sup {
1514 return true;
1515 }
1516 match (sub, sup) {
1517 (Atomic::TNever, _) => true,
1519 (_, Atomic::TMixed) => true,
1521 (Atomic::TMixed, _) => true,
1522 (_, Atomic::TTemplateParam { as_type, .. }) => {
1527 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1528 }
1529
1530 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1532 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1533 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1534 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1535 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1536 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1537 (Atomic::TPositiveInt, Atomic::TInt) => true,
1538 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1539 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1540 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1541 (Atomic::TNegativeInt, Atomic::TInt) => true,
1542 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1543 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1544 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1545 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1546 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1547 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1548 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1549 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1550 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1552 max.is_none() && min.is_none_or(|m| m <= 1)
1553 }
1554 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1556 min.is_none() && max.is_none_or(|m| m >= -1)
1557 }
1558 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1560 max.is_none() && min.is_none_or(|m| m <= 0)
1561 }
1562 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1564 sub_min.is_some_and(|lo| lo >= 1)
1565 }
1566 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1567 sub_min.is_some_and(|lo| lo >= 0)
1568 }
1569 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1570 sub_max.is_some_and(|hi| hi <= -1)
1571 }
1572 (
1574 Atomic::TIntRange {
1575 min: sub_min,
1576 max: sub_max,
1577 },
1578 Atomic::TIntRange {
1579 min: sup_min,
1580 max: sup_max,
1581 },
1582 ) => {
1583 let lower_ok = match (sub_min, sup_min) {
1584 (_, None) => true,
1585 (None, Some(_)) => false,
1586 (Some(sl), Some(su)) => sl >= su,
1587 };
1588 let upper_ok = match (sub_max, sup_max) {
1589 (None, None) | (Some(_), None) => true,
1590 (None, Some(_)) => false,
1591 (Some(sl), Some(su)) => sl <= su,
1592 };
1593 lower_ok && upper_ok
1594 }
1595
1596 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1597 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1598 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1599
1600 (Atomic::TLiteralString(s), Atomic::TString) => {
1601 let _ = s;
1602 true
1603 }
1604 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1605 let _ = s;
1606 true
1607 }
1608 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1609 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1610 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1613 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1616 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1617 (Atomic::TNonEmptyString, Atomic::TString) => true,
1618 (Atomic::TCallableString, Atomic::TString) => true,
1619 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1621 (Atomic::TNumericString, Atomic::TString) => true,
1622 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1626 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1627 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1628 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1629 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1630 (Atomic::TClassString(_), Atomic::TString) => true,
1631 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1632 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1637 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1638 (Atomic::TEnumString, Atomic::TString) => true,
1639 (Atomic::TTraitString, Atomic::TString) => true,
1640
1641 (Atomic::TTrue, Atomic::TBool) => true,
1642 (Atomic::TFalse, Atomic::TBool) => true,
1643
1644 (Atomic::TInt, Atomic::TNumeric) => true,
1645 (Atomic::TFloat, Atomic::TNumeric) => true,
1646 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1647 (Atomic::TNumericString, Atomic::TNumeric) => true,
1648
1649 (Atomic::TInt, Atomic::TScalar) => true,
1650 (Atomic::TFloat, Atomic::TScalar) => true,
1651 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1652 (Atomic::TString, Atomic::TScalar) => true,
1653 (Atomic::TBool, Atomic::TScalar) => true,
1654 (Atomic::TNumeric, Atomic::TScalar) => true,
1655 (Atomic::TTrue, Atomic::TScalar) => true,
1656 (Atomic::TFalse, Atomic::TScalar) => true,
1657 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1661 (Atomic::TNumericString, Atomic::TScalar) => true,
1662 (Atomic::TCallableString, Atomic::TScalar) => true,
1663 (Atomic::TClassString(_), Atomic::TScalar) => true,
1664 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1665 (Atomic::TEnumString, Atomic::TScalar) => true,
1666 (Atomic::TTraitString, Atomic::TScalar) => true,
1667
1668 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1670 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1671 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1672 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
1674 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1676 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1677 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1679 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1680 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
1683 enum_fqcn == fqcn
1684 }
1685 (
1689 Atomic::TNamedObject {
1690 fqcn: sub_fqcn,
1691 type_params: sub_params,
1692 },
1693 Atomic::TNamedObject {
1694 fqcn: sup_fqcn,
1695 type_params: sup_params,
1696 },
1697 ) => {
1698 sub_fqcn == sup_fqcn
1699 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1700 }
1701
1702 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1704
1705 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1707 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1708 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1709 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1710 (Atomic::TInt, Atomic::TFloat) => true,
1711 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1712
1713 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1715 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1716 }
1717
1718 (Atomic::TString, Atomic::TCallable { .. }) => true,
1720 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1721 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1722 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1723 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1724 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1725
1726 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1728 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1730 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1740 fn has_nominal_type(t: &Type) -> bool {
1741 t.types.iter().any(|a| {
1742 matches!(
1743 a,
1744 Atomic::TNamedObject { .. }
1745 | Atomic::TSelf { .. }
1746 | Atomic::TStaticObject { .. }
1747 | Atomic::TTemplateParam { .. }
1748 | Atomic::TClosure { .. }
1749 | Atomic::TCallable { .. }
1750 )
1751 })
1752 }
1753 let sub_required = sub
1754 .params
1755 .iter()
1756 .filter(|p| !p.is_optional && !p.is_variadic)
1757 .count();
1758 if sub_required > sup.params.len() {
1759 false
1760 } else {
1761 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
1762 let Some(sub_param) = sub.params.get(i) else {
1763 return true;
1764 };
1765 if sub_param.is_optional || sub_param.is_variadic {
1766 return true;
1767 }
1768 let (Some(sub_ty), Some(sup_ty)) =
1769 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
1770 else {
1771 return true;
1772 };
1773 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
1774 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
1775 return true;
1776 }
1777 sup_u.is_subtype_structural(&sub_u)
1780 });
1781 params_ok
1782 && (sub.return_type.is_mixed()
1783 || sup.return_type.is_mixed()
1784 || has_nominal_type(&sub.return_type)
1785 || has_nominal_type(&sup.return_type)
1786 || sub.return_type.is_subtype_structural(&sup.return_type))
1787 }
1788 }
1789 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1791 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1793 fqcn.as_ref().eq_ignore_ascii_case("closure")
1794 }
1795 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1796 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1798 fqcn.as_ref().eq_ignore_ascii_case("closure")
1799 }
1800 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1802 fqcn.as_ref().eq_ignore_ascii_case("closure")
1803 }
1804
1805 (
1813 Atomic::TIntersection { parts: sub_parts },
1814 Atomic::TIntersection { parts: sup_parts },
1815 ) => sup_parts.iter().all(|sup_part| {
1816 sub_parts
1817 .iter()
1818 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
1819 }),
1820
1821 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1823 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1824 }
1825 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1826 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1827 }
1828 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1829 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1830 }
1831 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1832 value.is_subtype_structural(lv)
1833 }
1834 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1836 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1837 && av.is_subtype_structural(lv)
1838 }
1839 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1840 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1841 && av.is_subtype_structural(lv)
1842 }
1843 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1844 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1845 && av.is_subtype_structural(lv)
1846 }
1847 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1848 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1849 && av.is_subtype_structural(lv)
1850 }
1851 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1853 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1854 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1855 }
1856
1857 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1859 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1860 }
1861
1862 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
1871 properties.iter().all(|(prop_key, prop)| {
1872 let key_atomic = match prop_key {
1873 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1874 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1875 };
1876 if !Type::single(key_atomic).is_subtype_structural(key) {
1877 return false; }
1879 let has_named_obj = prop.ty.types.iter().any(|a| {
1881 matches!(
1882 a,
1883 Atomic::TNamedObject { .. }
1884 | Atomic::TSelf { .. }
1885 | Atomic::TStaticObject { .. }
1886 | Atomic::TClosure { .. }
1887 | Atomic::TTemplateParam { .. }
1888 )
1889 });
1890 has_named_obj || prop.ty.is_subtype_structural(value)
1891 })
1892 }
1893 (
1894 Atomic::TKeyedArray {
1895 properties,
1896 is_open,
1897 ..
1898 },
1899 Atomic::TNonEmptyArray { key, value },
1900 ) => {
1901 (*is_open || properties.iter().any(|(_, p)| !p.optional))
1902 && properties.iter().all(|(prop_key, prop)| {
1903 let key_atomic = match prop_key {
1904 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1905 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1906 };
1907 if !Type::single(key_atomic).is_subtype_structural(key) {
1908 return false;
1909 }
1910 let has_named_obj = prop.ty.types.iter().any(|a| {
1911 matches!(
1912 a,
1913 Atomic::TNamedObject { .. }
1914 | Atomic::TSelf { .. }
1915 | Atomic::TStaticObject { .. }
1916 | Atomic::TClosure { .. }
1917 | Atomic::TTemplateParam { .. }
1918 )
1919 });
1920 has_named_obj || prop.ty.is_subtype_structural(value)
1921 })
1922 }
1923
1924 (
1926 Atomic::TKeyedArray {
1927 properties,
1928 is_list,
1929 ..
1930 },
1931 Atomic::TList { value: lv },
1932 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1933 (
1934 Atomic::TKeyedArray {
1935 properties,
1936 is_list,
1937 ..
1938 },
1939 Atomic::TNonEmptyList { value: lv },
1940 ) => {
1941 *is_list
1942 && !properties.is_empty()
1943 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1944 }
1945
1946 (
1951 Atomic::TKeyedArray {
1952 properties: sub_props,
1953 is_open: sub_open,
1954 ..
1955 },
1956 Atomic::TKeyedArray {
1957 properties: sup_props,
1958 is_open: sup_open,
1959 ..
1960 },
1961 ) => {
1962 let keys_satisfied = sup_props
1963 .iter()
1964 .all(|(key, sup_prop)| match sub_props.get(key) {
1965 Some(sub_prop) => {
1966 if !sup_prop.optional && sub_prop.optional {
1970 return false;
1971 }
1972 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
1973 matches!(
1974 a,
1975 Atomic::TNamedObject { .. }
1976 | Atomic::TSelf { .. }
1977 | Atomic::TStaticObject { .. }
1978 | Atomic::TClosure { .. }
1979 | Atomic::TTemplateParam { .. }
1980 )
1981 });
1982 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
1983 }
1984 None => sup_prop.optional || *sub_open,
1985 });
1986 let no_undeclared_extras =
1987 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
1988 keys_satisfied && no_undeclared_extras
1989 }
1990
1991 _ => false,
1992 }
1993}
1994
1995fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2001 if sub.len() != sup.len() {
2002 return false;
2003 }
2004 sub.iter()
2005 .zip(sup.iter())
2006 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2007}
2008
2009fn is_empty_array_literal(t: &Type) -> bool {
2012 !t.types.is_empty()
2013 && t.types.iter().all(
2014 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2015 )
2016}
2017
2018fn is_array_like(t: &Type) -> bool {
2020 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2021}
2022
2023#[cfg(test)]
2028mod tests {
2029 use std::sync::Arc;
2030
2031 use super::*;
2032
2033 fn conditional(
2034 param_name: Option<Name>,
2035 subject: Type,
2036 if_true: Type,
2037 if_false: Type,
2038 ) -> Atomic {
2039 Atomic::TConditional {
2040 data: Box::new(crate::atomic::ConditionalData {
2041 param_name,
2042 subject,
2043 if_true,
2044 if_false,
2045 }),
2046 }
2047 }
2048
2049 #[test]
2050 fn single_is_single() {
2051 let u = Type::single(Atomic::TString);
2052 assert!(u.is_single());
2053 assert!(!u.is_nullable());
2054 }
2055
2056 #[test]
2057 fn nullable_has_null() {
2058 let u = Type::nullable(Atomic::TString);
2059 assert!(u.is_nullable());
2060 assert_eq!(u.types.len(), 2);
2061 }
2062
2063 #[test]
2064 fn add_type_deduplicates() {
2065 let mut u = Type::single(Atomic::TString);
2066 u.add_type(Atomic::TString);
2067 assert_eq!(u.types.len(), 1);
2068 }
2069
2070 #[test]
2071 fn array_key_is_int_string() {
2072 let k = Type::array_key();
2073 assert!(k.is_array_key());
2074 assert_eq!(k.types.len(), 2);
2075 }
2076
2077 #[test]
2078 fn is_array_key_false_for_plain_int() {
2079 assert!(!Type::int().is_array_key());
2080 }
2081
2082 #[test]
2083 fn is_array_key_false_for_mixed() {
2084 assert!(!Type::mixed().is_array_key());
2085 }
2086
2087 #[test]
2088 fn is_array_key_false_for_int_string_null() {
2089 let mut u = Type::array_key();
2090 u.add_type(Atomic::TNull);
2091 assert!(!u.is_array_key());
2092 }
2093
2094 #[test]
2095 fn add_type_literal_subsumed_by_base() {
2096 let mut u = Type::single(Atomic::TInt);
2097 u.add_type(Atomic::TLiteralInt(42));
2098 assert_eq!(u.types.len(), 1);
2099 assert!(matches!(u.types[0], Atomic::TInt));
2100 }
2101
2102 #[test]
2103 fn true_then_false_merges_to_bool() {
2104 let mut u = Type::single(Atomic::TTrue);
2105 u.add_type(Atomic::TFalse);
2106 assert_eq!(u.types.len(), 1);
2107 assert!(matches!(u.types[0], Atomic::TBool));
2108 }
2109
2110 #[test]
2111 fn false_then_true_merges_to_bool() {
2112 let mut u = Type::single(Atomic::TFalse);
2113 u.add_type(Atomic::TTrue);
2114 assert_eq!(u.types.len(), 1);
2115 assert!(matches!(u.types[0], Atomic::TBool));
2116 }
2117
2118 #[test]
2119 fn true_alone_stays_true() {
2120 let u = Type::single(Atomic::TTrue);
2121 assert_eq!(u.types.len(), 1);
2122 assert!(matches!(u.types[0], Atomic::TTrue));
2123 }
2124
2125 #[test]
2126 fn true_false_merge_preserves_other_union_members() {
2127 let mut u = Type::single(Atomic::TTrue);
2128 u.add_type(Atomic::TNull);
2129 u.add_type(Atomic::TFalse);
2130 assert_eq!(u.types.len(), 2);
2131 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2132 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2133 }
2134
2135 #[test]
2136 fn add_type_base_widens_literals() {
2137 let mut u = Type::single(Atomic::TLiteralInt(1));
2138 u.add_type(Atomic::TLiteralInt(2));
2139 u.add_type(Atomic::TInt);
2140 assert_eq!(u.types.len(), 1);
2141 assert!(matches!(u.types[0], Atomic::TInt));
2142 }
2143
2144 #[test]
2145 fn mixed_subsumes_everything() {
2146 let mut u = Type::single(Atomic::TString);
2147 u.add_type(Atomic::TMixed);
2148 assert_eq!(u.types.len(), 1);
2149 assert!(u.is_mixed());
2150 }
2151
2152 #[test]
2153 fn remove_null() {
2154 let u = Type::nullable(Atomic::TString);
2155 let narrowed = u.remove_null();
2156 assert!(!narrowed.is_nullable());
2157 assert_eq!(narrowed.types.len(), 1);
2158 }
2159
2160 #[test]
2161 fn narrow_to_truthy_removes_null_false() {
2162 let mut u = Type::empty();
2163 u.add_type(Atomic::TString);
2164 u.add_type(Atomic::TNull);
2165 u.add_type(Atomic::TFalse);
2166 let truthy = u.narrow_to_truthy();
2167 assert!(!truthy.is_nullable());
2168 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2169 }
2170
2171 #[test]
2172 fn merge_combines_types() {
2173 let a = Type::single(Atomic::TString);
2174 let b = Type::single(Atomic::TInt);
2175 let merged = Type::merge(&a, &b);
2176 assert_eq!(merged.types.len(), 2);
2177 }
2178
2179 #[test]
2180 fn intersect_keeps_narrower_side_not_self() {
2181 let int_ty = Type::single(Atomic::TInt);
2185 let mut literals = Type::empty();
2186 literals.add_type(Atomic::TLiteralInt(1));
2187 literals.add_type(Atomic::TLiteralInt(2));
2188
2189 let narrowed = int_ty.intersect_with(&literals);
2190 assert_eq!(narrowed.types.len(), 2);
2191 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2192 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2193 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2194 }
2195
2196 #[test]
2197 fn subtype_literal_int_under_int() {
2198 let sub = Type::single(Atomic::TLiteralInt(5));
2199 let sup = Type::single(Atomic::TInt);
2200 assert!(sub.is_subtype_structural(&sup));
2201 }
2202
2203 #[test]
2204 fn subtype_never_is_bottom() {
2205 let never = Type::never();
2206 let string = Type::single(Atomic::TString);
2207 assert!(never.is_subtype_structural(&string));
2208 }
2209
2210 #[test]
2211 fn subtype_everything_under_mixed() {
2212 let string = Type::single(Atomic::TString);
2213 let mixed = Type::mixed();
2214 assert!(string.is_subtype_structural(&mixed));
2215 }
2216
2217 #[test]
2218 fn subtype_enum_case_under_own_enum() {
2219 let sub = Type::single(Atomic::TLiteralEnumCase {
2220 enum_fqcn: Name::new("RoundingMode"),
2221 case_name: Name::new("Unnecessary"),
2222 });
2223 let sup = Type::single(Atomic::TNamedObject {
2224 fqcn: Name::new("RoundingMode"),
2225 type_params: empty_type_params(),
2226 });
2227 assert!(sub.is_subtype_structural(&sup));
2228 }
2229
2230 #[test]
2231 fn enum_case_not_subtype_of_unrelated_enum() {
2232 let sub = Type::single(Atomic::TLiteralEnumCase {
2233 enum_fqcn: Name::new("RoundingMode"),
2234 case_name: Name::new("Unnecessary"),
2235 });
2236 let sup = Type::single(Atomic::TNamedObject {
2237 fqcn: Name::new("Suit"),
2238 type_params: empty_type_params(),
2239 });
2240 assert!(!sub.is_subtype_structural(&sup));
2241 }
2242
2243 #[test]
2244 fn subtype_enum_case_under_bare_object() {
2245 let sub = Type::single(Atomic::TLiteralEnumCase {
2246 enum_fqcn: Name::new("RoundingMode"),
2247 case_name: Name::new("Unnecessary"),
2248 });
2249 let sup = Type::single(Atomic::TObject);
2250 assert!(sub.is_subtype_structural(&sup));
2251 }
2252
2253 #[test]
2254 fn template_substitution() {
2255 let mut bindings = FxHashMap::default();
2256 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2257
2258 let tmpl = Type::single(Atomic::TTemplateParam {
2259 name: Name::new("T"),
2260 as_type: Box::new(Type::mixed()),
2261 defining_entity: Name::new("MyClass"),
2262 });
2263
2264 let resolved = tmpl.substitute_templates(&bindings);
2265 assert_eq!(resolved.types.len(), 1);
2266 assert!(matches!(resolved.types[0], Atomic::TString));
2267 }
2268
2269 #[test]
2270 fn intersection_is_object() {
2271 let parts = vec![
2272 Type::single(Atomic::TNamedObject {
2273 fqcn: Name::new("Iterator"),
2274 type_params: empty_type_params(),
2275 }),
2276 Type::single(Atomic::TNamedObject {
2277 fqcn: Name::new("Countable"),
2278 type_params: empty_type_params(),
2279 }),
2280 ];
2281 let atomic = Atomic::TIntersection {
2282 parts: vec_to_type_params(parts),
2283 };
2284 assert!(atomic.is_object());
2285 assert!(!atomic.can_be_falsy());
2286 assert!(atomic.can_be_truthy());
2287 }
2288
2289 #[test]
2290 fn intersection_display_two_parts() {
2291 let parts = vec![
2292 Type::single(Atomic::TNamedObject {
2293 fqcn: Name::new("Iterator"),
2294 type_params: empty_type_params(),
2295 }),
2296 Type::single(Atomic::TNamedObject {
2297 fqcn: Name::new("Countable"),
2298 type_params: empty_type_params(),
2299 }),
2300 ];
2301 let u = Type::single(Atomic::TIntersection {
2302 parts: vec_to_type_params(parts),
2303 });
2304 assert_eq!(format!("{u}"), "Iterator&Countable");
2305 }
2306
2307 #[test]
2308 fn intersection_display_three_parts() {
2309 let parts = vec![
2310 Type::single(Atomic::TNamedObject {
2311 fqcn: Name::new("A"),
2312 type_params: empty_type_params(),
2313 }),
2314 Type::single(Atomic::TNamedObject {
2315 fqcn: Name::new("B"),
2316 type_params: empty_type_params(),
2317 }),
2318 Type::single(Atomic::TNamedObject {
2319 fqcn: Name::new("C"),
2320 type_params: empty_type_params(),
2321 }),
2322 ];
2323 let u = Type::single(Atomic::TIntersection {
2324 parts: vec_to_type_params(parts),
2325 });
2326 assert_eq!(format!("{u}"), "A&B&C");
2327 }
2328
2329 #[test]
2330 fn intersection_in_nullable_union_display() {
2331 let intersection = Atomic::TIntersection {
2332 parts: vec_to_type_params(vec![
2333 Type::single(Atomic::TNamedObject {
2334 fqcn: Name::new("Iterator"),
2335 type_params: empty_type_params(),
2336 }),
2337 Type::single(Atomic::TNamedObject {
2338 fqcn: Name::new("Countable"),
2339 type_params: empty_type_params(),
2340 }),
2341 ]),
2342 };
2343 let mut u = Type::single(intersection);
2344 u.add_type(Atomic::TNull);
2345 assert!(u.is_nullable());
2346 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2347 }
2348
2349 fn t_param(name: &str) -> Type {
2352 Type::single(Atomic::TTemplateParam {
2353 name: Name::new(name),
2354 as_type: Box::new(Type::mixed()),
2355 defining_entity: Name::new("Fn"),
2356 })
2357 }
2358
2359 fn bindings_t_string() -> FxHashMap<Name, Type> {
2360 let mut b = FxHashMap::default();
2361 b.insert(Name::new("T"), Type::single(Atomic::TString));
2362 b
2363 }
2364
2365 #[test]
2366 fn substitute_non_empty_array_key_and_value() {
2367 let ty = Type::single(Atomic::TNonEmptyArray {
2368 key: Box::new(t_param("T")),
2369 value: Box::new(t_param("T")),
2370 });
2371 let result = ty.substitute_templates(&bindings_t_string());
2372 assert_eq!(result.types.len(), 1);
2373 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2374 panic!("expected TNonEmptyArray");
2375 };
2376 assert!(matches!(key.types[0], Atomic::TString));
2377 assert!(matches!(value.types[0], Atomic::TString));
2378 }
2379
2380 #[test]
2381 fn substitute_non_empty_list_value() {
2382 let ty = Type::single(Atomic::TNonEmptyList {
2383 value: Box::new(t_param("T")),
2384 });
2385 let result = ty.substitute_templates(&bindings_t_string());
2386 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2387 panic!("expected TNonEmptyList");
2388 };
2389 assert!(matches!(value.types[0], Atomic::TString));
2390 }
2391
2392 #[test]
2393 fn substitute_keyed_array_property_types() {
2394 use crate::atomic::{ArrayKey, KeyedProperty};
2395 use indexmap::IndexMap;
2396 let mut props = IndexMap::new();
2397 props.insert(
2398 ArrayKey::String(Arc::from("name")),
2399 KeyedProperty {
2400 ty: t_param("T"),
2401 optional: false,
2402 },
2403 );
2404 props.insert(
2405 ArrayKey::String(Arc::from("tag")),
2406 KeyedProperty {
2407 ty: t_param("T"),
2408 optional: true,
2409 },
2410 );
2411 let ty = Type::single(Atomic::TKeyedArray {
2412 properties: Box::new(props),
2413 is_open: true,
2414 is_list: false,
2415 });
2416 let result = ty.substitute_templates(&bindings_t_string());
2417 let Atomic::TKeyedArray {
2418 properties,
2419 is_open,
2420 is_list,
2421 } = &result.types[0]
2422 else {
2423 panic!("expected TKeyedArray");
2424 };
2425 assert!(is_open);
2426 assert!(!is_list);
2427 assert!(matches!(
2428 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2429 Atomic::TString
2430 ));
2431 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2432 assert!(matches!(
2433 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2434 Atomic::TString
2435 ));
2436 }
2437
2438 #[test]
2439 fn substitute_callable_params_and_return() {
2440 use crate::atomic::FnParam;
2441 let ty = Type::single(Atomic::TCallable {
2442 params: Some(Box::new([FnParam {
2443 name: Name::new("x"),
2444 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2445 out_ty: None,
2446 default: None,
2447 is_variadic: false,
2448 is_byref: false,
2449 is_optional: false,
2450 }])),
2451 return_type: Some(Box::new(t_param("T"))),
2452 });
2453 let result = ty.substitute_templates(&bindings_t_string());
2454 let Atomic::TCallable {
2455 params,
2456 return_type,
2457 } = &result.types[0]
2458 else {
2459 panic!("expected TCallable");
2460 };
2461 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2462 let param_union = param_ty.to_union();
2463 assert!(matches!(param_union.types[0], Atomic::TString));
2464 let ret = return_type.as_ref().unwrap();
2465 assert!(matches!(ret.types[0], Atomic::TString));
2466 }
2467
2468 #[test]
2469 fn substitute_callable_bare_no_panic() {
2470 let ty = Type::single(Atomic::TCallable {
2472 params: None,
2473 return_type: None,
2474 });
2475 let result = ty.substitute_templates(&bindings_t_string());
2476 assert!(matches!(
2477 result.types[0],
2478 Atomic::TCallable {
2479 params: None,
2480 return_type: None
2481 }
2482 ));
2483 }
2484
2485 #[test]
2486 fn substitute_closure_params_return_and_this() {
2487 use crate::atomic::FnParam;
2488 let ty = Type::single(Atomic::TClosure {
2489 data: Box::new(crate::atomic::ClosureData {
2490 params: Box::new([FnParam {
2491 name: Name::new("a"),
2492 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2493 out_ty: None,
2494 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2495 is_variadic: true,
2496 is_byref: true,
2497 is_optional: true,
2498 }]),
2499 return_type: t_param("T"),
2500 this_type: Some(t_param("T")),
2501 }),
2502 });
2503 let result = ty.substitute_templates(&bindings_t_string());
2504 let Atomic::TClosure { data } = &result.types[0] else {
2505 panic!("expected TClosure");
2506 };
2507 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2508 let p = ¶ms[0];
2509 let ty_union = p.ty.as_ref().unwrap().to_union();
2510 let default_union = p.default.as_ref().unwrap().to_union();
2511 assert!(matches!(ty_union.types[0], Atomic::TString));
2512 assert!(matches!(default_union.types[0], Atomic::TString));
2513 assert!(p.is_variadic);
2515 assert!(p.is_byref);
2516 assert!(p.is_optional);
2517 assert!(matches!(return_type.types[0], Atomic::TString));
2518 assert!(matches!(
2519 this_type.as_ref().unwrap().types[0],
2520 Atomic::TString
2521 ));
2522 }
2523
2524 #[test]
2525 fn substitute_conditional_all_branches() {
2526 let ty = Type::single(conditional(
2527 None,
2528 t_param("T"),
2529 t_param("T"),
2530 Type::single(Atomic::TInt),
2531 ));
2532 let result = ty.substitute_templates(&bindings_t_string());
2533 let Atomic::TConditional { data } = &result.types[0] else {
2534 panic!("expected TConditional");
2535 };
2536 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2537 assert!(matches!(subject.types[0], Atomic::TString));
2538 assert!(matches!(if_true.types[0], Atomic::TString));
2539 assert!(matches!(if_false.types[0], Atomic::TInt));
2540 }
2541
2542 #[test]
2543 fn resolve_conditional_is_null_non_null_arg() {
2544 let ty = Type::single(conditional(
2545 Some(Name::new("x")),
2546 Type::single(Atomic::TNull),
2547 Type::single(Atomic::TInt),
2548 Type::single(Atomic::TString),
2549 ));
2550 let result = ty.resolve_conditional_returns(|name| {
2551 if name == "x" {
2552 Some(Type::single(Atomic::TString)) } else {
2554 None
2555 }
2556 });
2557 assert!(result.types.len() == 1);
2558 assert!(matches!(result.types[0], Atomic::TString));
2559 }
2560
2561 #[test]
2562 fn resolve_conditional_is_null_null_arg() {
2563 let ty = Type::single(conditional(
2564 Some(Name::new("x")),
2565 Type::single(Atomic::TNull),
2566 Type::single(Atomic::TInt),
2567 Type::single(Atomic::TString),
2568 ));
2569 let result = ty.resolve_conditional_returns(|name| {
2570 if name == "x" {
2571 Some(Type::single(Atomic::TNull)) } else {
2573 None
2574 }
2575 });
2576 assert!(result.types.len() == 1);
2577 assert!(matches!(result.types[0], Atomic::TInt));
2578 }
2579
2580 #[test]
2581 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2582 let mut nullable_str = Type::single(Atomic::TString);
2583 nullable_str.add_type(Atomic::TNull);
2584 let ty = Type::single(conditional(
2585 Some(Name::new("x")),
2586 Type::single(Atomic::TNull),
2587 Type::single(Atomic::TInt),
2588 Type::single(Atomic::TString),
2589 ));
2590 let result = ty.resolve_conditional_returns(|name| {
2591 if name == "x" {
2592 Some(nullable_str.clone())
2593 } else {
2594 None
2595 }
2596 });
2597 assert_eq!(result.types.len(), 2);
2599 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2600 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2601 }
2602
2603 #[test]
2604 fn resolve_conditional_nested_widens_inner_branch() {
2605 let inner = Type::single(conditional(
2608 Some(Name::new("x")),
2609 Type::single(Atomic::TString),
2610 Type::single(Atomic::TString),
2611 Type::single(Atomic::TFloat),
2612 ));
2613 let ty = Type::single(conditional(
2614 Some(Name::new("x")),
2615 Type::single(Atomic::TNull),
2616 Type::single(Atomic::TInt),
2617 inner,
2618 ));
2619 let result = ty.resolve_conditional_returns(|_| None);
2621 assert!(
2622 result
2623 .types
2624 .iter()
2625 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2626 "no TConditional should survive: {:?}",
2627 result.types
2628 );
2629 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2630 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2631 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2632 }
2633
2634 #[test]
2635 fn resolve_conditional_nested_resolves_inner_branch() {
2636 let inner = Type::single(conditional(
2640 Some(Name::new("x")),
2641 Type::single(Atomic::TString),
2642 Type::single(Atomic::TString),
2643 Type::single(Atomic::TFloat),
2644 ));
2645 let ty = Type::single(conditional(
2646 Some(Name::new("x")),
2647 Type::single(Atomic::TNull),
2648 Type::single(Atomic::TInt),
2649 inner,
2650 ));
2651 let result = ty.resolve_conditional_returns(|name| {
2653 if name == "x" {
2654 Some(Type::single(Atomic::TString))
2655 } else {
2656 None
2657 }
2658 });
2659 assert!(
2660 result
2661 .types
2662 .iter()
2663 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2664 "no TConditional should survive: {:?}",
2665 result.types
2666 );
2667 assert_eq!(result.types.len(), 1);
2668 assert!(matches!(result.types[0], Atomic::TString));
2669 }
2670
2671 #[test]
2672 fn substitute_intersection_parts() {
2673 let ty = Type::single(Atomic::TIntersection {
2674 parts: vec_to_type_params(vec![
2675 Type::single(Atomic::TNamedObject {
2676 fqcn: Name::new("Countable"),
2677 type_params: empty_type_params(),
2678 }),
2679 t_param("T"),
2680 ]),
2681 });
2682 let result = ty.substitute_templates(&bindings_t_string());
2683 let Atomic::TIntersection { parts } = &result.types[0] else {
2684 panic!("expected TIntersection");
2685 };
2686 assert_eq!(parts.len(), 2);
2687 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2688 assert!(matches!(parts[1].types[0], Atomic::TString));
2689 }
2690
2691 #[test]
2692 fn substitute_no_template_params_identity() {
2693 let ty = Type::single(Atomic::TInt);
2694 let result = ty.substitute_templates(&bindings_t_string());
2695 assert!(matches!(result.types[0], Atomic::TInt));
2696 }
2697}