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}
55
56impl Type {
57 pub fn empty() -> Self {
60 Self {
61 types: SmallVec::new(),
62 possibly_undefined: false,
63 from_docblock: false,
64 }
65 }
66
67 pub fn single(atomic: Atomic) -> Self {
68 let mut types = SmallVec::new();
69 types.push(atomic);
70 Self {
71 types,
72 possibly_undefined: false,
73 from_docblock: false,
74 }
75 }
76
77 pub fn mixed() -> Self {
78 Self::single(Atomic::TMixed)
79 }
80
81 pub fn void() -> Self {
82 Self::single(Atomic::TVoid)
83 }
84
85 pub fn never() -> Self {
86 Self::single(Atomic::TNever)
87 }
88
89 pub fn null() -> Self {
90 Self::single(Atomic::TNull)
91 }
92
93 pub fn bool() -> Self {
94 Self::single(Atomic::TBool)
95 }
96
97 pub fn int() -> Self {
98 Self::single(Atomic::TInt)
99 }
100
101 pub fn float() -> Self {
102 Self::single(Atomic::TFloat)
103 }
104
105 pub fn string() -> Self {
106 Self::single(Atomic::TString)
107 }
108
109 pub fn array_key() -> Self {
112 let mut u = Self::single(Atomic::TInt);
113 u.add_type(Atomic::TString);
114 u
115 }
116
117 pub fn nullable(atomic: Atomic) -> Self {
119 if matches!(atomic, Atomic::TMixed) {
121 return Self::mixed();
122 }
123 let mut types = SmallVec::new();
124 types.push(atomic);
125 types.push(Atomic::TNull);
126 Self {
127 types,
128 possibly_undefined: false,
129 from_docblock: false,
130 }
131 }
132
133 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
135 let mut u = Self::empty();
136 for a in atomics {
137 u.add_type(a);
138 }
139 u
140 }
141
142 pub fn is_empty(&self) -> bool {
145 self.types.is_empty()
146 }
147
148 pub fn is_single(&self) -> bool {
149 self.types.len() == 1
150 }
151
152 pub fn is_nullable(&self) -> bool {
153 self.types.iter().any(|t| matches!(t, Atomic::TNull))
154 }
155
156 pub fn is_array_key(&self) -> bool {
161 self.types.len() == 2
162 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
163 && self.types.iter().any(|t| matches!(t, Atomic::TString))
164 }
165
166 pub fn is_mixed(&self) -> bool {
167 self.types.iter().any(|t| match t {
168 Atomic::TMixed => true,
169 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
170 _ => false,
171 })
172 }
173
174 pub fn is_mixed_not_template(&self) -> bool {
179 self.is_mixed()
180 && !self
181 .types
182 .iter()
183 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
184 }
185
186 pub fn is_never(&self) -> bool {
187 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
188 }
189
190 pub fn clone_validity(&self) -> CloneValidity {
193 if self.types.is_empty() {
194 return CloneValidity::Unknown;
195 }
196 let mut has_non_object = false;
197 let mut has_other = false; for t in &self.types {
199 match t {
200 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
201 CloneValidity::Invalid => has_non_object = true,
202 CloneValidity::PossiblyInvalid => {
203 has_non_object = true;
204 has_other = true;
205 }
206 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
207 },
208 other if other.is_definitely_non_object() => has_non_object = true,
209 _ => has_other = true,
210 }
211 }
212 match (has_non_object, has_other) {
213 (true, false) => CloneValidity::Invalid,
214 (true, true) => CloneValidity::PossiblyInvalid,
215 _ => CloneValidity::Cloneable,
216 }
217 }
218
219 pub fn is_void(&self) -> bool {
220 self.is_single() && matches!(self.types[0], Atomic::TVoid)
221 }
222
223 pub fn can_be_falsy(&self) -> bool {
224 self.types.iter().any(|t| t.can_be_falsy())
225 }
226
227 pub fn can_be_truthy(&self) -> bool {
228 self.types.iter().any(|t| t.can_be_truthy())
229 }
230
231 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
232 self.types.iter().any(f)
233 }
234
235 pub fn has_named_object(&self, fqcn: &str) -> bool {
236 self.types.iter().any(|t| match t {
237 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
238 _ => false,
239 })
240 }
241
242 pub fn add_type(&mut self, atomic: Atomic) {
247 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
249 return;
250 }
251
252 if matches!(atomic, Atomic::TMixed) {
254 self.types.clear();
255 self.types.push(Atomic::TMixed);
256 return;
257 }
258
259 let atomic = if let Atomic::TConditional { data } = &atomic {
262 let (if_true, if_false) = (&data.if_true, &data.if_false);
263 let mut simplified_true = Type::empty();
264 for t in &if_true.types {
265 simplified_true.add_type(t.clone());
266 }
267 let mut simplified_false = Type::empty();
268 for t in &if_false.types {
269 simplified_false.add_type(t.clone());
270 }
271 if simplified_true == simplified_false {
272 for t in simplified_true.types {
273 self.add_type(t);
274 }
275 return;
276 }
277 atomic
278 } else {
279 atomic
280 };
281
282 if self.types.contains(&atomic) {
284 return;
285 }
286
287 if let Atomic::TLiteralInt(_) = &atomic {
289 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
290 return;
291 }
292 }
293 if let Atomic::TLiteralString(_) = &atomic {
295 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
296 return;
297 }
298 }
299 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
301 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
302 {
303 return;
304 }
305 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
308 {
309 self.types.retain(|t| !matches!(t, Atomic::TFalse));
310 self.types.push(Atomic::TBool);
311 return;
312 }
313 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
314 {
315 self.types.retain(|t| !matches!(t, Atomic::TTrue));
316 self.types.push(Atomic::TBool);
317 return;
318 }
319 if matches!(atomic, Atomic::TInt) {
321 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
322 }
323 if matches!(atomic, Atomic::TString) {
325 self.types
326 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
327 }
328 if matches!(atomic, Atomic::TBool) {
330 self.types
331 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
332 }
333
334 if matches!(atomic, Atomic::TNever) {
336 if !self.types.is_empty() {
337 return;
338 }
339 } else {
340 self.types.retain(|t| !matches!(t, Atomic::TNever));
341 }
342
343 if let Atomic::TKeyedArray {
349 properties,
350 is_open,
351 ..
352 } = &atomic
353 {
354 if properties.is_empty() && !is_open {
355 for existing in &self.types {
356 match existing {
357 Atomic::TArray { .. }
358 | Atomic::TNonEmptyArray { .. }
359 | Atomic::TList { .. }
360 | Atomic::TNonEmptyList { .. } => {
361 return; }
363 _ => {}
364 }
365 }
366 }
367 }
368
369 let is_generic_array_or_list = matches!(
372 &atomic,
373 Atomic::TArray { .. }
374 | Atomic::TNonEmptyArray { .. }
375 | Atomic::TList { .. }
376 | Atomic::TNonEmptyList { .. }
377 );
378 if is_generic_array_or_list {
379 self.types.retain(|t| {
380 if let Atomic::TKeyedArray {
381 properties,
382 is_open,
383 ..
384 } = t
385 {
386 !properties.is_empty() || *is_open
387 } else {
388 true
389 }
390 });
391 }
392
393 self.types.push(atomic);
394 }
395
396 pub fn remove_null(&self) -> Type {
400 self.filter(|t| !matches!(t, Atomic::TNull))
401 }
402
403 pub fn remove_false(&self) -> Type {
406 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
407 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
408 result.add_type(Atomic::TTrue);
409 }
410 result
411 }
412
413 pub fn remove_true(&self) -> Type {
416 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
417 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
418 result.add_type(Atomic::TFalse);
419 }
420 result
421 }
422
423 pub fn core_type(&self) -> Type {
425 self.remove_null().remove_false()
426 }
427
428 pub fn narrow_to_truthy(&self) -> Type {
430 if self.is_mixed_not_template() {
431 return Type::mixed();
432 }
433 let mut result = Type::empty();
434 result.from_docblock = self.from_docblock;
435 for t in &self.types {
436 match t {
437 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
441 Atomic::TLiteralInt(0)
443 | Atomic::TLiteralFloat(0, 0)
444 | Atomic::TNull
445 | Atomic::TFalse => {}
446 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
447 Atomic::TBool => result.add_type(Atomic::TTrue),
449 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
451 key: key.clone(),
452 value: value.clone(),
453 }),
454 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
455 value: value.clone(),
456 }),
457 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
461 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
466 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
467 let atom = if max.is_none() {
468 Atomic::TPositiveInt
469 } else {
470 Atomic::TIntRange {
471 min: Some(1),
472 max: *max,
473 }
474 };
475 result.add_type(atom);
476 }
477 Atomic::TIntRange { min, max: Some(0) } => {
479 let atom = match min {
480 None => Atomic::TNegativeInt,
481 Some(n) if *n <= -1 => Atomic::TIntRange {
482 min: *min,
483 max: Some(-1),
484 },
485 _ => continue, };
487 result.add_type(atom);
488 }
489 t if !t.can_be_truthy() => {}
491 _ => result.add_type(t.clone()),
492 }
493 }
494 result
495 }
496
497 pub fn narrow_to_falsy(&self) -> Type {
499 if self.is_mixed_not_template() {
500 return Type::from_vec(vec![
501 Atomic::TNull,
502 Atomic::TFalse,
503 Atomic::TLiteralInt(0),
504 Atomic::TLiteralString("".into()),
505 ]);
506 }
507 let mut result = Type::empty();
508 result.from_docblock = self.from_docblock;
509 for t in &self.types {
510 match t {
511 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
516 Atomic::TBool => result.add_type(Atomic::TFalse),
518 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
520 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
522 Atomic::TString => {
524 result.add_type(Atomic::TLiteralString("".into()));
525 result.add_type(Atomic::TLiteralString("0".into()));
526 }
527 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
529 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
531 Atomic::TIntRange {
533 min: Some(0),
534 max: Some(_) | None,
535 } => result.add_type(Atomic::TLiteralInt(0)),
536 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
538 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
540 }
541 }
542 result
543 }
544
545 pub fn narrow_instanceof(&self, class: &str) -> Type {
551 let narrowed_ty = Atomic::TNamedObject {
552 fqcn: class.into(),
553 type_params: empty_type_params(),
554 };
555 let has_object = self.types.iter().any(|t| {
557 matches!(
558 t,
559 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
561 });
562 if has_object || self.is_empty() {
563 Type::single(narrowed_ty)
564 } else {
565 Type::single(narrowed_ty)
568 }
569 }
570
571 pub fn narrow_to_string(&self) -> Type {
575 self.filter_replacing(
576 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
577 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
578 Atomic::TString,
579 )
580 }
581
582 pub fn narrow_to_int(&self) -> Type {
584 self.filter_replacing(
585 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
586 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
587 Atomic::TInt,
588 )
589 }
590
591 pub fn narrow_to_float(&self) -> Type {
593 self.filter_replacing(
594 |t| {
595 matches!(
596 t,
597 Atomic::TFloat
598 | Atomic::TIntegralFloat
599 | Atomic::TLiteralFloat(..)
600 | Atomic::TTemplateParam { .. }
601 )
602 },
603 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
604 Atomic::TFloat,
605 )
606 }
607
608 pub fn narrow_to_bool(&self) -> Type {
610 self.filter_replacing(
611 |t| {
612 matches!(
613 t,
614 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
615 )
616 },
617 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
618 Atomic::TBool,
619 )
620 }
621
622 pub fn narrow_to_null(&self) -> Type {
624 self.filter_replacing(
625 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
626 |t| matches!(t, Atomic::TMixed),
627 Atomic::TNull,
628 )
629 }
630
631 pub fn narrow_to_array(&self) -> Type {
633 self.filter_replacing(
634 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
635 |t| matches!(t, Atomic::TMixed),
636 Atomic::TArray {
637 key: Box::new(Type::mixed()),
638 value: Box::new(Type::mixed()),
639 },
640 )
641 }
642
643 pub fn narrow_to_non_empty_collection(&self) -> Type {
645 let mut out = Type::empty();
646 out.from_docblock = self.from_docblock;
647 for t in &self.types {
648 match t {
649 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
650 key: key.clone(),
651 value: value.clone(),
652 }),
653 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
654 value: value.clone(),
655 }),
656 _ => out.add_type(t.clone()),
657 }
658 }
659 out
660 }
661
662 pub fn narrow_to_empty_collection(&self) -> Type {
672 let mut out = Type::empty();
673 out.from_docblock = self.from_docblock;
674 for t in &self.types {
675 match t {
676 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
677 Atomic::TArray { .. } | Atomic::TList { .. } => {
678 out.add_type(Atomic::TKeyedArray {
679 properties: Box::default(),
680 is_open: false,
681 is_list: true,
682 });
683 }
684 _ => out.add_type(t.clone()),
685 }
686 }
687 out
688 }
689
690 pub fn narrow_to_list(&self) -> Type {
702 let mut out = Type::empty();
703 out.from_docblock = self.from_docblock;
704 for t in &self.types {
705 match t {
706 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
707 Atomic::TArray { key, value } if matches!(key.types.as_slice(), [Atomic::TInt]) => {
708 out.add_type(Atomic::TList {
709 value: value.clone(),
710 });
711 }
712 Atomic::TNonEmptyArray { key, value }
713 if matches!(key.types.as_slice(), [Atomic::TInt]) =>
714 {
715 out.add_type(Atomic::TNonEmptyList {
716 value: value.clone(),
717 });
718 }
719 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
720 Atomic::TMixed => out.add_type(Atomic::TList {
721 value: Box::new(Type::mixed()),
722 }),
723 _ => {}
724 }
725 }
726 if out.is_empty() {
727 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
728 } else {
729 out
730 }
731 }
732
733 pub fn narrow_to_object(&self) -> Type {
738 let mut out = Type::empty();
739 for t in &self.types {
740 if matches!(t, Atomic::TMixed) {
741 out.add_type(Atomic::TObject);
742 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
743 out.add_type(t.clone());
744 }
745 }
746 if out.types.is_empty() {
747 self.filter(|t| t.is_object())
748 } else {
749 out
750 }
751 }
752
753 pub fn narrow_to_callable(&self) -> Type {
760 let narrowed = self.filter(|t| {
761 t.is_callable()
762 || t.is_string()
763 || t.is_array()
764 || t.is_object()
765 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
766 });
767 let mut result = Type::empty();
774 result.possibly_undefined = narrowed.possibly_undefined;
775 result.from_docblock = narrowed.from_docblock;
776 for atomic in narrowed.types {
777 if matches!(atomic, Atomic::TObject) {
778 result.add_type(Atomic::TCallable {
779 params: None,
780 return_type: None,
781 });
782 } else {
783 result.add_type(atomic);
784 }
785 }
786 result
787 }
788
789 pub fn narrow_to_scalar(&self) -> Type {
791 self.filter_replacing(
792 |t| {
793 t.is_string()
794 || t.is_int()
795 || matches!(
796 t,
797 Atomic::TFloat
798 | Atomic::TIntegralFloat
799 | Atomic::TLiteralFloat(..)
800 | Atomic::TBool
801 | Atomic::TTrue
802 | Atomic::TFalse
803 | Atomic::TScalar
804 | Atomic::TNumeric
805 | Atomic::TNumericString
806 | Atomic::TTemplateParam { .. }
807 )
808 },
809 |t| matches!(t, Atomic::TMixed),
810 Atomic::TScalar,
811 )
812 }
813
814 pub fn narrow_to_iterable(&self) -> Type {
817 self.filter(|t| {
818 t.is_array()
819 || t.is_object()
820 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
821 })
822 }
823
824 pub fn narrow_to_countable(&self) -> Type {
827 self.filter(|t| {
828 t.is_array()
829 || t.is_object()
830 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
831 })
832 }
833
834 pub fn narrow_to_resource(&self) -> Type {
838 self.filter(|t| matches!(t, Atomic::TMixed))
840 }
841
842 pub fn narrow_to_class_string(&self) -> Type {
847 let mut out = Type::empty();
848 out.from_docblock = self.from_docblock;
849 for t in &self.types {
850 match t {
851 Atomic::TClassString(_) => out.add_type(t.clone()),
852 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
853 out.add_type(Atomic::TClassString(None));
854 }
855 _ => {}
856 }
857 }
858 out
859 }
860
861 pub fn narrow_to_interface_string(&self) -> Type {
866 let mut out = Type::empty();
867 out.from_docblock = self.from_docblock;
868 for t in &self.types {
869 match t {
870 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
871 Atomic::TClassString(name) => {
875 out.add_type(Atomic::TInterfaceString(*name));
876 }
877 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
878 out.add_type(Atomic::TInterfaceString(None));
879 }
880 _ => {}
881 }
882 }
883 out
884 }
885
886 pub fn merge(a: &Type, b: &Type) -> Type {
891 if b.types.is_empty() {
893 let mut result = a.clone();
894 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
895 return result;
896 }
897 if a.types.is_empty() {
899 let mut result = b.clone();
900 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
901 return result;
902 }
903 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
905 let mut result = a.clone();
906 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
907 return result;
908 }
909 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
911 return Type {
912 types: smallvec::smallvec![Atomic::TMixed],
913 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
914 from_docblock: a.from_docblock || b.from_docblock,
915 };
916 }
917 let mut result = a.clone();
918 result.merge_with(b);
919 result
920 }
921
922 pub fn merge_with(&mut self, other: &Type) {
924 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
925 self.possibly_undefined |= other.possibly_undefined;
926 return;
927 }
928 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
929 self.types.clear();
930 self.types.push(Atomic::TMixed);
931 self.possibly_undefined |= other.possibly_undefined;
932 return;
933 }
934 for atomic in &other.types {
935 self.add_type(atomic.clone());
936 }
937 self.possibly_undefined |= other.possibly_undefined;
938 }
939
940 pub fn intersect_with(&self, other: &Type) -> Type {
944 if self.is_mixed() {
945 return other.clone();
946 }
947 if other.is_mixed() {
948 return self.clone();
949 }
950 let mut result = Type::empty();
958 for a in &self.types {
959 for b in &other.types {
960 if a == b {
961 result.add_type(a.clone());
962 } else if atomic_subtype(b, a) {
963 result.add_type(b.clone());
964 } else if atomic_subtype(a, b) {
965 result.add_type(a.clone());
966 }
967 }
968 }
969 if result.is_empty() {
970 Type::never()
971 } else {
972 result
973 }
974 }
975
976 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
980 if bindings.is_empty() {
981 return self.clone();
982 }
983 if !self.types.iter().any(atomic_may_contain_templates) {
986 return self.clone();
987 }
988 let mut result = Type::empty();
989 result.possibly_undefined = self.possibly_undefined;
990 result.from_docblock = self.from_docblock;
991 for atomic in &self.types {
992 match atomic {
993 Atomic::TTemplateParam { name, .. } => {
994 if let Some(resolved) = bindings.get(name) {
995 for t in &resolved.types {
996 result.add_type(t.clone());
997 }
998 } else {
999 result.add_type(atomic.clone());
1000 }
1001 }
1002 Atomic::TArray { key, value } => {
1003 result.add_type(Atomic::TArray {
1004 key: Box::new(key.substitute_templates(bindings)),
1005 value: Box::new(value.substitute_templates(bindings)),
1006 });
1007 }
1008 Atomic::TList { value } => {
1009 result.add_type(Atomic::TList {
1010 value: Box::new(value.substitute_templates(bindings)),
1011 });
1012 }
1013 Atomic::TNonEmptyArray { key, value } => {
1014 result.add_type(Atomic::TNonEmptyArray {
1015 key: Box::new(key.substitute_templates(bindings)),
1016 value: Box::new(value.substitute_templates(bindings)),
1017 });
1018 }
1019 Atomic::TNonEmptyList { value } => {
1020 result.add_type(Atomic::TNonEmptyList {
1021 value: Box::new(value.substitute_templates(bindings)),
1022 });
1023 }
1024 Atomic::TKeyedArray {
1025 properties,
1026 is_open,
1027 is_list,
1028 } => {
1029 use crate::atomic::KeyedProperty;
1030 let new_props = properties
1031 .iter()
1032 .map(|(k, prop)| {
1033 (
1034 k.clone(),
1035 KeyedProperty {
1036 ty: prop.ty.substitute_templates(bindings),
1037 optional: prop.optional,
1038 },
1039 )
1040 })
1041 .collect();
1042 result.add_type(Atomic::TKeyedArray {
1043 properties: Box::new(new_props),
1044 is_open: *is_open,
1045 is_list: *is_list,
1046 });
1047 }
1048 Atomic::TCallable {
1049 params,
1050 return_type,
1051 } => {
1052 result.add_type(Atomic::TCallable {
1053 params: params.as_ref().map(|ps| {
1054 ps.iter()
1055 .map(|p| substitute_in_fn_param(p, bindings))
1056 .collect()
1057 }),
1058 return_type: return_type
1059 .as_ref()
1060 .map(|r| Box::new(r.substitute_templates(bindings))),
1061 });
1062 }
1063 Atomic::TClosure { data } => {
1064 result.add_type(Atomic::TClosure {
1065 data: Box::new(crate::atomic::ClosureData {
1066 params: data
1067 .params
1068 .iter()
1069 .map(|p| substitute_in_fn_param(p, bindings))
1070 .collect(),
1071 return_type: data.return_type.substitute_templates(bindings),
1072 this_type: data
1073 .this_type
1074 .as_ref()
1075 .map(|t| t.substitute_templates(bindings)),
1076 }),
1077 });
1078 }
1079 Atomic::TConditional { data } => {
1080 let param_name = &data.param_name;
1081 let new_subject = data.subject.substitute_templates(bindings);
1082 let new_if_true = data.if_true.substitute_templates(bindings);
1083 let new_if_false = data.if_false.substitute_templates(bindings);
1084
1085 let resolved = if let Some(name) = param_name {
1089 if let Some(bound) = bindings.get(name) {
1090 if new_subject.types.len() == 1 {
1091 resolve_conditional_branch(
1092 &new_subject.types[0],
1093 bound,
1094 &new_if_true,
1095 &new_if_false,
1096 )
1097 } else {
1098 None
1099 }
1100 } else {
1101 None
1102 }
1103 } else {
1104 None
1105 };
1106
1107 if let Some(branch) = resolved {
1108 for t in branch.types {
1109 result.add_type(t);
1110 }
1111 } else {
1112 result.add_type(Atomic::TConditional {
1113 data: Box::new(crate::atomic::ConditionalData {
1114 param_name: *param_name,
1115 subject: new_subject,
1116 if_true: new_if_true,
1117 if_false: new_if_false,
1118 }),
1119 });
1120 }
1121 }
1122 Atomic::TIntersection { parts } => {
1123 result.add_type(Atomic::TIntersection {
1124 parts: vec_to_type_params(
1125 parts
1126 .iter()
1127 .map(|p| p.substitute_templates(bindings))
1128 .collect(),
1129 ),
1130 });
1131 }
1132 Atomic::TNamedObject { fqcn, type_params } => {
1133 if type_params.is_empty() && !fqcn.contains('\\') {
1140 if let Some(resolved) = bindings.get(fqcn) {
1141 for t in &resolved.types {
1142 result.add_type(t.clone());
1143 }
1144 continue;
1145 }
1146 }
1147 let new_params: Vec<Type> = type_params
1148 .iter()
1149 .map(|p| p.substitute_templates(bindings))
1150 .collect();
1151 result.add_type(Atomic::TNamedObject {
1152 fqcn: *fqcn,
1153 type_params: vec_to_type_params(new_params),
1154 });
1155 }
1156 Atomic::TClassString(Some(param_name)) => {
1158 if let Some(resolved) = bindings.get(param_name) {
1159 for r_atomic in &resolved.types {
1160 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1161 Some(*fqcn)
1162 } else {
1163 None
1164 };
1165 result.add_type(Atomic::TClassString(cls_name));
1166 }
1167 } else {
1168 result.add_type(atomic.clone());
1169 }
1170 }
1171 Atomic::TInterfaceString(Some(param_name)) => {
1173 if let Some(resolved) = bindings.get(param_name) {
1174 for r_atomic in &resolved.types {
1175 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1176 Some(*fqcn)
1177 } else {
1178 None
1179 };
1180 result.add_type(Atomic::TInterfaceString(iface_name));
1181 }
1182 } else {
1183 result.add_type(atomic.clone());
1184 }
1185 }
1186 _ => {
1187 result.add_type(atomic.clone());
1188 }
1189 }
1190 }
1191 result
1192 }
1193
1194 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1200 where
1201 F: Fn(&str) -> Option<Type>,
1202 {
1203 self.resolve_conditional_inner(&lookup)
1204 }
1205
1206 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1207 where
1208 F: Fn(&str) -> Option<Type>,
1209 {
1210 let mut result = Type::empty();
1211 for atomic in self.types {
1212 match atomic {
1213 Atomic::TConditional { ref data } => {
1214 let (param_name, subject, if_true, if_false) = (
1215 &data.param_name,
1216 &data.subject,
1217 &data.if_true,
1218 &data.if_false,
1219 );
1220 let resolved = if subject.types.len() == 1 {
1221 if let Some(name) = param_name {
1222 if let Some(arg_ty) = lookup(name.as_ref()) {
1223 resolve_conditional_branch(
1224 &subject.types[0],
1225 &arg_ty,
1226 if_true,
1227 if_false,
1228 )
1229 } else {
1230 None
1231 }
1232 } else {
1233 None
1234 }
1235 } else {
1236 None
1237 };
1238
1239 if let Some(branch) = resolved {
1240 for t in branch.resolve_conditional_inner(lookup).types {
1242 result.add_type(t);
1243 }
1244 } else {
1245 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1248 result.add_type(t);
1249 }
1250 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1251 result.add_type(t);
1252 }
1253 }
1254 }
1255 other => result.add_type(other),
1256 }
1257 }
1258 result
1259 }
1260
1261 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1271 if other.is_mixed() {
1272 return true;
1273 }
1274 if self.is_never() {
1275 return true; }
1277 self.types
1278 .iter()
1279 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1280 }
1281
1282 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1286 if self.is_mixed() {
1287 return true;
1288 }
1289 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1290 }
1291
1292 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1295 let mut result = Type::empty();
1296 result.possibly_undefined = self.possibly_undefined;
1297 result.from_docblock = self.from_docblock;
1298 for atomic in &self.types {
1299 if f(atomic) {
1300 result.types.push(atomic.clone());
1301 }
1302 }
1303 result
1304 }
1305
1306 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1311 &self,
1312 keep: K,
1313 placeholder: P,
1314 replacement: Atomic,
1315 ) -> Type {
1316 let mut result = Type::empty();
1317 result.possibly_undefined = self.possibly_undefined;
1318 result.from_docblock = self.from_docblock;
1319 for atomic in &self.types {
1320 if keep(atomic) {
1321 result.add_type(atomic.clone());
1322 } else if placeholder(atomic) {
1323 result.add_type(replacement.clone());
1324 }
1325 }
1326 result
1327 }
1328
1329 pub fn possibly_undefined(mut self) -> Self {
1331 self.possibly_undefined = true;
1332 self
1333 }
1334
1335 pub fn from_docblock(mut self) -> Self {
1337 self.from_docblock = true;
1338 self
1339 }
1340}
1341
1342fn is_string_atomic(a: &Atomic) -> bool {
1347 matches!(
1348 a,
1349 Atomic::TString
1350 | Atomic::TNonEmptyString
1351 | Atomic::TLiteralString(_)
1352 | Atomic::TNumericString
1353 | Atomic::TClassString(_)
1354 | Atomic::TInterfaceString(_)
1355 | Atomic::TCallableString
1356 )
1357}
1358
1359fn is_array_atomic(a: &Atomic) -> bool {
1360 matches!(
1361 a,
1362 Atomic::TArray { .. }
1363 | Atomic::TNonEmptyArray { .. }
1364 | Atomic::TKeyedArray { .. }
1365 | Atomic::TList { .. }
1366 | Atomic::TNonEmptyList { .. }
1367 )
1368}
1369
1370fn is_list_atomic(a: &Atomic) -> bool {
1371 match a {
1372 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1373 Atomic::TKeyedArray { is_list, .. } => *is_list,
1374 _ => false,
1375 }
1376}
1377
1378fn is_float_atomic(a: &Atomic) -> bool {
1379 matches!(
1380 a,
1381 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..)
1382 )
1383}
1384
1385fn is_bool_atomic(a: &Atomic) -> bool {
1386 matches!(a, Atomic::TBool | Atomic::TTrue | Atomic::TFalse)
1387}
1388
1389fn resolve_conditional_branch(
1395 subject: &Atomic,
1396 arg_ty: &Type,
1397 if_true: &Type,
1398 if_false: &Type,
1399) -> Option<Type> {
1400 let predicate: fn(&Atomic) -> bool = match subject {
1401 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1402 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1403 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1404 Atomic::TString => is_string_atomic,
1405 Atomic::TList { .. } => is_list_atomic,
1406 Atomic::TArray { .. } => is_array_atomic,
1407 Atomic::TInt => Atomic::is_int,
1408 Atomic::TFloat => is_float_atomic,
1409 Atomic::TBool => is_bool_atomic,
1410 _ => return None,
1411 };
1412
1413 if arg_ty.types.is_empty() {
1414 return None;
1415 }
1416 let all_match = arg_ty.types.iter().all(&predicate);
1417 let none_match = !arg_ty.types.iter().any(predicate);
1418 if all_match {
1419 Some(if_true.clone())
1420 } else if none_match {
1421 Some(if_false.clone())
1422 } else {
1423 None
1424 }
1425}
1426
1427fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1435 match atomic {
1436 Atomic::TNamedObject { fqcn, type_params } => {
1440 !type_params.is_empty() || !fqcn.contains('\\')
1441 }
1442 Atomic::TTemplateParam { .. }
1443 | Atomic::TArray { .. }
1444 | Atomic::TList { .. }
1445 | Atomic::TNonEmptyArray { .. }
1446 | Atomic::TNonEmptyList { .. }
1447 | Atomic::TKeyedArray { .. }
1448 | Atomic::TCallable { .. }
1449 | Atomic::TClosure { .. }
1450 | Atomic::TConditional { .. }
1451 | Atomic::TIntersection { .. }
1452 | Atomic::TClassString(Some(_))
1453 | Atomic::TInterfaceString(Some(_)) => true,
1454 _ => false,
1455 }
1456}
1457
1458fn substitute_in_fn_param(
1459 p: &crate::atomic::FnParam,
1460 bindings: &FxHashMap<Name, Type>,
1461) -> crate::atomic::FnParam {
1462 crate::atomic::FnParam {
1463 name: p.name,
1464 ty: p.ty.as_ref().map(|t| {
1465 let u = t.to_union();
1466 let substituted = u.substitute_templates(bindings);
1467 crate::compact::SimpleType::from_union(substituted)
1468 }),
1469 out_ty: p.out_ty.as_ref().map(|t| {
1470 let u = t.to_union();
1471 let substituted = u.substitute_templates(bindings);
1472 crate::compact::SimpleType::from_union(substituted)
1473 }),
1474 default: p.default.as_ref().map(|d| {
1475 let u = d.to_union();
1476 let substituted = u.substitute_templates(bindings);
1477 crate::compact::SimpleType::from_union(substituted)
1478 }),
1479 is_variadic: p.is_variadic,
1480 is_byref: p.is_byref,
1481 is_optional: p.is_optional,
1482 }
1483}
1484
1485pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1491 if sub == sup {
1492 return true;
1493 }
1494 match (sub, sup) {
1495 (Atomic::TNever, _) => true,
1497 (_, Atomic::TMixed) => true,
1499 (Atomic::TMixed, _) => true,
1500 (_, Atomic::TTemplateParam { as_type, .. }) => {
1505 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1506 }
1507
1508 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1510 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1511 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1512 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1513 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1514 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1515 (Atomic::TPositiveInt, Atomic::TInt) => true,
1516 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1517 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1518 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1519 (Atomic::TNegativeInt, Atomic::TInt) => true,
1520 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1521 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1522 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1523 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1524 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1525 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1526 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1527 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1528 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1530 max.is_none() && min.is_none_or(|m| m <= 1)
1531 }
1532 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1534 min.is_none() && max.is_none_or(|m| m >= -1)
1535 }
1536 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1538 max.is_none() && min.is_none_or(|m| m <= 0)
1539 }
1540 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1542 sub_min.is_some_and(|lo| lo >= 1)
1543 }
1544 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1545 sub_min.is_some_and(|lo| lo >= 0)
1546 }
1547 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1548 sub_max.is_some_and(|hi| hi <= -1)
1549 }
1550 (
1552 Atomic::TIntRange {
1553 min: sub_min,
1554 max: sub_max,
1555 },
1556 Atomic::TIntRange {
1557 min: sup_min,
1558 max: sup_max,
1559 },
1560 ) => {
1561 let lower_ok = match (sub_min, sup_min) {
1562 (_, None) => true,
1563 (None, Some(_)) => false,
1564 (Some(sl), Some(su)) => sl >= su,
1565 };
1566 let upper_ok = match (sub_max, sup_max) {
1567 (None, None) | (Some(_), None) => true,
1568 (None, Some(_)) => false,
1569 (Some(sl), Some(su)) => sl <= su,
1570 };
1571 lower_ok && upper_ok
1572 }
1573
1574 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1575 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1576 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1577
1578 (Atomic::TLiteralString(s), Atomic::TString) => {
1579 let _ = s;
1580 true
1581 }
1582 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1583 let _ = s;
1584 true
1585 }
1586 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1587 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1588 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1591 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1594 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1595 (Atomic::TNonEmptyString, Atomic::TString) => true,
1596 (Atomic::TCallableString, Atomic::TString) => true,
1597 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1599 (Atomic::TNumericString, Atomic::TString) => true,
1600 (Atomic::TClassString(_), Atomic::TString) => true,
1601 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1602 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1607 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1608 (Atomic::TEnumString, Atomic::TString) => true,
1609 (Atomic::TTraitString, Atomic::TString) => true,
1610
1611 (Atomic::TTrue, Atomic::TBool) => true,
1612 (Atomic::TFalse, Atomic::TBool) => true,
1613
1614 (Atomic::TInt, Atomic::TNumeric) => true,
1615 (Atomic::TFloat, Atomic::TNumeric) => true,
1616 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1617 (Atomic::TNumericString, Atomic::TNumeric) => true,
1618
1619 (Atomic::TInt, Atomic::TScalar) => true,
1620 (Atomic::TFloat, Atomic::TScalar) => true,
1621 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1622 (Atomic::TString, Atomic::TScalar) => true,
1623 (Atomic::TBool, Atomic::TScalar) => true,
1624 (Atomic::TNumeric, Atomic::TScalar) => true,
1625 (Atomic::TTrue, Atomic::TScalar) => true,
1626 (Atomic::TFalse, Atomic::TScalar) => true,
1627 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1631 (Atomic::TNumericString, Atomic::TScalar) => true,
1632 (Atomic::TCallableString, Atomic::TScalar) => true,
1633 (Atomic::TClassString(_), Atomic::TScalar) => true,
1634 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1635 (Atomic::TEnumString, Atomic::TScalar) => true,
1636 (Atomic::TTraitString, Atomic::TScalar) => true,
1637
1638 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1640 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1641 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1642 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1644 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1645 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1647 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1648 (
1652 Atomic::TNamedObject {
1653 fqcn: sub_fqcn,
1654 type_params: sub_params,
1655 },
1656 Atomic::TNamedObject {
1657 fqcn: sup_fqcn,
1658 type_params: sup_params,
1659 },
1660 ) => {
1661 sub_fqcn == sup_fqcn
1662 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1663 }
1664
1665 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1667
1668 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1670 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1671 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1672 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1673 (Atomic::TInt, Atomic::TFloat) => true,
1674 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1675
1676 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1678 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1679 }
1680
1681 (Atomic::TString, Atomic::TCallable { .. }) => true,
1683 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1684 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1685 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1686 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1687 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1688
1689 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1691 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1693 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1703 fn has_nominal_type(t: &Type) -> bool {
1704 t.types.iter().any(|a| {
1705 matches!(
1706 a,
1707 Atomic::TNamedObject { .. }
1708 | Atomic::TSelf { .. }
1709 | Atomic::TStaticObject { .. }
1710 | Atomic::TTemplateParam { .. }
1711 | Atomic::TClosure { .. }
1712 | Atomic::TCallable { .. }
1713 )
1714 })
1715 }
1716 let sub_required = sub
1717 .params
1718 .iter()
1719 .filter(|p| !p.is_optional && !p.is_variadic)
1720 .count();
1721 if sub_required > sup.params.len() {
1722 false
1723 } else {
1724 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
1725 let Some(sub_param) = sub.params.get(i) else {
1726 return true;
1727 };
1728 if sub_param.is_optional || sub_param.is_variadic {
1729 return true;
1730 }
1731 let (Some(sub_ty), Some(sup_ty)) =
1732 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
1733 else {
1734 return true;
1735 };
1736 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
1737 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
1738 return true;
1739 }
1740 sup_u.is_subtype_structural(&sub_u)
1743 });
1744 params_ok
1745 && (sub.return_type.is_mixed()
1746 || sup.return_type.is_mixed()
1747 || has_nominal_type(&sub.return_type)
1748 || has_nominal_type(&sup.return_type)
1749 || sub.return_type.is_subtype_structural(&sup.return_type))
1750 }
1751 }
1752 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1754 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1756 fqcn.as_ref().eq_ignore_ascii_case("closure")
1757 }
1758 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1759 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1761 fqcn.as_ref().eq_ignore_ascii_case("closure")
1762 }
1763 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1765 fqcn.as_ref().eq_ignore_ascii_case("closure")
1766 }
1767
1768 (
1776 Atomic::TIntersection { parts: sub_parts },
1777 Atomic::TIntersection { parts: sup_parts },
1778 ) => sup_parts.iter().all(|sup_part| {
1779 sub_parts
1780 .iter()
1781 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
1782 }),
1783
1784 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1786 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1787 }
1788 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1789 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1790 }
1791 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1792 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1793 }
1794 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1795 value.is_subtype_structural(lv)
1796 }
1797 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1799 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1800 && av.is_subtype_structural(lv)
1801 }
1802 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1803 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1804 && av.is_subtype_structural(lv)
1805 }
1806 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1807 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1808 && av.is_subtype_structural(lv)
1809 }
1810 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1811 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1812 && av.is_subtype_structural(lv)
1813 }
1814 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1816 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1817 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1818 }
1819
1820 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1822 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1823 }
1824
1825 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
1834 properties.iter().all(|(prop_key, prop)| {
1835 let key_atomic = match prop_key {
1836 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1837 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1838 };
1839 if !Type::single(key_atomic).is_subtype_structural(key) {
1840 return false; }
1842 let has_named_obj = prop.ty.types.iter().any(|a| {
1844 matches!(
1845 a,
1846 Atomic::TNamedObject { .. }
1847 | Atomic::TSelf { .. }
1848 | Atomic::TStaticObject { .. }
1849 | Atomic::TClosure { .. }
1850 | Atomic::TTemplateParam { .. }
1851 )
1852 });
1853 has_named_obj || prop.ty.is_subtype_structural(value)
1854 })
1855 }
1856 (
1857 Atomic::TKeyedArray {
1858 properties,
1859 is_open,
1860 ..
1861 },
1862 Atomic::TNonEmptyArray { key, value },
1863 ) => {
1864 (*is_open || properties.iter().any(|(_, p)| !p.optional))
1865 && properties.iter().all(|(prop_key, prop)| {
1866 let key_atomic = match prop_key {
1867 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1868 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1869 };
1870 if !Type::single(key_atomic).is_subtype_structural(key) {
1871 return false;
1872 }
1873 let has_named_obj = prop.ty.types.iter().any(|a| {
1874 matches!(
1875 a,
1876 Atomic::TNamedObject { .. }
1877 | Atomic::TSelf { .. }
1878 | Atomic::TStaticObject { .. }
1879 | Atomic::TClosure { .. }
1880 | Atomic::TTemplateParam { .. }
1881 )
1882 });
1883 has_named_obj || prop.ty.is_subtype_structural(value)
1884 })
1885 }
1886
1887 (
1889 Atomic::TKeyedArray {
1890 properties,
1891 is_list,
1892 ..
1893 },
1894 Atomic::TList { value: lv },
1895 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1896 (
1897 Atomic::TKeyedArray {
1898 properties,
1899 is_list,
1900 ..
1901 },
1902 Atomic::TNonEmptyList { value: lv },
1903 ) => {
1904 *is_list
1905 && !properties.is_empty()
1906 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1907 }
1908
1909 (
1914 Atomic::TKeyedArray {
1915 properties: sub_props,
1916 is_open: sub_open,
1917 ..
1918 },
1919 Atomic::TKeyedArray {
1920 properties: sup_props,
1921 is_open: sup_open,
1922 ..
1923 },
1924 ) => {
1925 let keys_satisfied = sup_props
1926 .iter()
1927 .all(|(key, sup_prop)| match sub_props.get(key) {
1928 Some(sub_prop) => {
1929 if !sup_prop.optional && sub_prop.optional {
1933 return false;
1934 }
1935 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
1936 matches!(
1937 a,
1938 Atomic::TNamedObject { .. }
1939 | Atomic::TSelf { .. }
1940 | Atomic::TStaticObject { .. }
1941 | Atomic::TClosure { .. }
1942 | Atomic::TTemplateParam { .. }
1943 )
1944 });
1945 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
1946 }
1947 None => sup_prop.optional || *sub_open,
1948 });
1949 let no_undeclared_extras =
1950 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
1951 keys_satisfied && no_undeclared_extras
1952 }
1953
1954 _ => false,
1955 }
1956}
1957
1958fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
1964 if sub.len() != sup.len() {
1965 return false;
1966 }
1967 sub.iter()
1968 .zip(sup.iter())
1969 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
1970}
1971
1972fn is_empty_array_literal(t: &Type) -> bool {
1975 !t.types.is_empty()
1976 && t.types.iter().all(
1977 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
1978 )
1979}
1980
1981fn is_array_like(t: &Type) -> bool {
1983 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
1984}
1985
1986#[cfg(test)]
1991mod tests {
1992 use std::sync::Arc;
1993
1994 use super::*;
1995
1996 fn conditional(
1997 param_name: Option<Name>,
1998 subject: Type,
1999 if_true: Type,
2000 if_false: Type,
2001 ) -> Atomic {
2002 Atomic::TConditional {
2003 data: Box::new(crate::atomic::ConditionalData {
2004 param_name,
2005 subject,
2006 if_true,
2007 if_false,
2008 }),
2009 }
2010 }
2011
2012 #[test]
2013 fn single_is_single() {
2014 let u = Type::single(Atomic::TString);
2015 assert!(u.is_single());
2016 assert!(!u.is_nullable());
2017 }
2018
2019 #[test]
2020 fn nullable_has_null() {
2021 let u = Type::nullable(Atomic::TString);
2022 assert!(u.is_nullable());
2023 assert_eq!(u.types.len(), 2);
2024 }
2025
2026 #[test]
2027 fn add_type_deduplicates() {
2028 let mut u = Type::single(Atomic::TString);
2029 u.add_type(Atomic::TString);
2030 assert_eq!(u.types.len(), 1);
2031 }
2032
2033 #[test]
2034 fn array_key_is_int_string() {
2035 let k = Type::array_key();
2036 assert!(k.is_array_key());
2037 assert_eq!(k.types.len(), 2);
2038 }
2039
2040 #[test]
2041 fn is_array_key_false_for_plain_int() {
2042 assert!(!Type::int().is_array_key());
2043 }
2044
2045 #[test]
2046 fn is_array_key_false_for_mixed() {
2047 assert!(!Type::mixed().is_array_key());
2048 }
2049
2050 #[test]
2051 fn is_array_key_false_for_int_string_null() {
2052 let mut u = Type::array_key();
2053 u.add_type(Atomic::TNull);
2054 assert!(!u.is_array_key());
2055 }
2056
2057 #[test]
2058 fn add_type_literal_subsumed_by_base() {
2059 let mut u = Type::single(Atomic::TInt);
2060 u.add_type(Atomic::TLiteralInt(42));
2061 assert_eq!(u.types.len(), 1);
2062 assert!(matches!(u.types[0], Atomic::TInt));
2063 }
2064
2065 #[test]
2066 fn true_then_false_merges_to_bool() {
2067 let mut u = Type::single(Atomic::TTrue);
2068 u.add_type(Atomic::TFalse);
2069 assert_eq!(u.types.len(), 1);
2070 assert!(matches!(u.types[0], Atomic::TBool));
2071 }
2072
2073 #[test]
2074 fn false_then_true_merges_to_bool() {
2075 let mut u = Type::single(Atomic::TFalse);
2076 u.add_type(Atomic::TTrue);
2077 assert_eq!(u.types.len(), 1);
2078 assert!(matches!(u.types[0], Atomic::TBool));
2079 }
2080
2081 #[test]
2082 fn true_alone_stays_true() {
2083 let u = Type::single(Atomic::TTrue);
2084 assert_eq!(u.types.len(), 1);
2085 assert!(matches!(u.types[0], Atomic::TTrue));
2086 }
2087
2088 #[test]
2089 fn true_false_merge_preserves_other_union_members() {
2090 let mut u = Type::single(Atomic::TTrue);
2091 u.add_type(Atomic::TNull);
2092 u.add_type(Atomic::TFalse);
2093 assert_eq!(u.types.len(), 2);
2094 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2095 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2096 }
2097
2098 #[test]
2099 fn add_type_base_widens_literals() {
2100 let mut u = Type::single(Atomic::TLiteralInt(1));
2101 u.add_type(Atomic::TLiteralInt(2));
2102 u.add_type(Atomic::TInt);
2103 assert_eq!(u.types.len(), 1);
2104 assert!(matches!(u.types[0], Atomic::TInt));
2105 }
2106
2107 #[test]
2108 fn mixed_subsumes_everything() {
2109 let mut u = Type::single(Atomic::TString);
2110 u.add_type(Atomic::TMixed);
2111 assert_eq!(u.types.len(), 1);
2112 assert!(u.is_mixed());
2113 }
2114
2115 #[test]
2116 fn remove_null() {
2117 let u = Type::nullable(Atomic::TString);
2118 let narrowed = u.remove_null();
2119 assert!(!narrowed.is_nullable());
2120 assert_eq!(narrowed.types.len(), 1);
2121 }
2122
2123 #[test]
2124 fn narrow_to_truthy_removes_null_false() {
2125 let mut u = Type::empty();
2126 u.add_type(Atomic::TString);
2127 u.add_type(Atomic::TNull);
2128 u.add_type(Atomic::TFalse);
2129 let truthy = u.narrow_to_truthy();
2130 assert!(!truthy.is_nullable());
2131 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2132 }
2133
2134 #[test]
2135 fn merge_combines_types() {
2136 let a = Type::single(Atomic::TString);
2137 let b = Type::single(Atomic::TInt);
2138 let merged = Type::merge(&a, &b);
2139 assert_eq!(merged.types.len(), 2);
2140 }
2141
2142 #[test]
2143 fn intersect_keeps_narrower_side_not_self() {
2144 let int_ty = Type::single(Atomic::TInt);
2148 let mut literals = Type::empty();
2149 literals.add_type(Atomic::TLiteralInt(1));
2150 literals.add_type(Atomic::TLiteralInt(2));
2151
2152 let narrowed = int_ty.intersect_with(&literals);
2153 assert_eq!(narrowed.types.len(), 2);
2154 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2155 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2156 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2157 }
2158
2159 #[test]
2160 fn subtype_literal_int_under_int() {
2161 let sub = Type::single(Atomic::TLiteralInt(5));
2162 let sup = Type::single(Atomic::TInt);
2163 assert!(sub.is_subtype_structural(&sup));
2164 }
2165
2166 #[test]
2167 fn subtype_never_is_bottom() {
2168 let never = Type::never();
2169 let string = Type::single(Atomic::TString);
2170 assert!(never.is_subtype_structural(&string));
2171 }
2172
2173 #[test]
2174 fn subtype_everything_under_mixed() {
2175 let string = Type::single(Atomic::TString);
2176 let mixed = Type::mixed();
2177 assert!(string.is_subtype_structural(&mixed));
2178 }
2179
2180 #[test]
2181 fn template_substitution() {
2182 let mut bindings = FxHashMap::default();
2183 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2184
2185 let tmpl = Type::single(Atomic::TTemplateParam {
2186 name: Name::new("T"),
2187 as_type: Box::new(Type::mixed()),
2188 defining_entity: Name::new("MyClass"),
2189 });
2190
2191 let resolved = tmpl.substitute_templates(&bindings);
2192 assert_eq!(resolved.types.len(), 1);
2193 assert!(matches!(resolved.types[0], Atomic::TString));
2194 }
2195
2196 #[test]
2197 fn intersection_is_object() {
2198 let parts = vec![
2199 Type::single(Atomic::TNamedObject {
2200 fqcn: Name::new("Iterator"),
2201 type_params: empty_type_params(),
2202 }),
2203 Type::single(Atomic::TNamedObject {
2204 fqcn: Name::new("Countable"),
2205 type_params: empty_type_params(),
2206 }),
2207 ];
2208 let atomic = Atomic::TIntersection {
2209 parts: vec_to_type_params(parts),
2210 };
2211 assert!(atomic.is_object());
2212 assert!(!atomic.can_be_falsy());
2213 assert!(atomic.can_be_truthy());
2214 }
2215
2216 #[test]
2217 fn intersection_display_two_parts() {
2218 let parts = vec![
2219 Type::single(Atomic::TNamedObject {
2220 fqcn: Name::new("Iterator"),
2221 type_params: empty_type_params(),
2222 }),
2223 Type::single(Atomic::TNamedObject {
2224 fqcn: Name::new("Countable"),
2225 type_params: empty_type_params(),
2226 }),
2227 ];
2228 let u = Type::single(Atomic::TIntersection {
2229 parts: vec_to_type_params(parts),
2230 });
2231 assert_eq!(format!("{u}"), "Iterator&Countable");
2232 }
2233
2234 #[test]
2235 fn intersection_display_three_parts() {
2236 let parts = vec![
2237 Type::single(Atomic::TNamedObject {
2238 fqcn: Name::new("A"),
2239 type_params: empty_type_params(),
2240 }),
2241 Type::single(Atomic::TNamedObject {
2242 fqcn: Name::new("B"),
2243 type_params: empty_type_params(),
2244 }),
2245 Type::single(Atomic::TNamedObject {
2246 fqcn: Name::new("C"),
2247 type_params: empty_type_params(),
2248 }),
2249 ];
2250 let u = Type::single(Atomic::TIntersection {
2251 parts: vec_to_type_params(parts),
2252 });
2253 assert_eq!(format!("{u}"), "A&B&C");
2254 }
2255
2256 #[test]
2257 fn intersection_in_nullable_union_display() {
2258 let intersection = Atomic::TIntersection {
2259 parts: vec_to_type_params(vec![
2260 Type::single(Atomic::TNamedObject {
2261 fqcn: Name::new("Iterator"),
2262 type_params: empty_type_params(),
2263 }),
2264 Type::single(Atomic::TNamedObject {
2265 fqcn: Name::new("Countable"),
2266 type_params: empty_type_params(),
2267 }),
2268 ]),
2269 };
2270 let mut u = Type::single(intersection);
2271 u.add_type(Atomic::TNull);
2272 assert!(u.is_nullable());
2273 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2274 }
2275
2276 fn t_param(name: &str) -> Type {
2279 Type::single(Atomic::TTemplateParam {
2280 name: Name::new(name),
2281 as_type: Box::new(Type::mixed()),
2282 defining_entity: Name::new("Fn"),
2283 })
2284 }
2285
2286 fn bindings_t_string() -> FxHashMap<Name, Type> {
2287 let mut b = FxHashMap::default();
2288 b.insert(Name::new("T"), Type::single(Atomic::TString));
2289 b
2290 }
2291
2292 #[test]
2293 fn substitute_non_empty_array_key_and_value() {
2294 let ty = Type::single(Atomic::TNonEmptyArray {
2295 key: Box::new(t_param("T")),
2296 value: Box::new(t_param("T")),
2297 });
2298 let result = ty.substitute_templates(&bindings_t_string());
2299 assert_eq!(result.types.len(), 1);
2300 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2301 panic!("expected TNonEmptyArray");
2302 };
2303 assert!(matches!(key.types[0], Atomic::TString));
2304 assert!(matches!(value.types[0], Atomic::TString));
2305 }
2306
2307 #[test]
2308 fn substitute_non_empty_list_value() {
2309 let ty = Type::single(Atomic::TNonEmptyList {
2310 value: Box::new(t_param("T")),
2311 });
2312 let result = ty.substitute_templates(&bindings_t_string());
2313 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2314 panic!("expected TNonEmptyList");
2315 };
2316 assert!(matches!(value.types[0], Atomic::TString));
2317 }
2318
2319 #[test]
2320 fn substitute_keyed_array_property_types() {
2321 use crate::atomic::{ArrayKey, KeyedProperty};
2322 use indexmap::IndexMap;
2323 let mut props = IndexMap::new();
2324 props.insert(
2325 ArrayKey::String(Arc::from("name")),
2326 KeyedProperty {
2327 ty: t_param("T"),
2328 optional: false,
2329 },
2330 );
2331 props.insert(
2332 ArrayKey::String(Arc::from("tag")),
2333 KeyedProperty {
2334 ty: t_param("T"),
2335 optional: true,
2336 },
2337 );
2338 let ty = Type::single(Atomic::TKeyedArray {
2339 properties: Box::new(props),
2340 is_open: true,
2341 is_list: false,
2342 });
2343 let result = ty.substitute_templates(&bindings_t_string());
2344 let Atomic::TKeyedArray {
2345 properties,
2346 is_open,
2347 is_list,
2348 } = &result.types[0]
2349 else {
2350 panic!("expected TKeyedArray");
2351 };
2352 assert!(is_open);
2353 assert!(!is_list);
2354 assert!(matches!(
2355 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2356 Atomic::TString
2357 ));
2358 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2359 assert!(matches!(
2360 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2361 Atomic::TString
2362 ));
2363 }
2364
2365 #[test]
2366 fn substitute_callable_params_and_return() {
2367 use crate::atomic::FnParam;
2368 let ty = Type::single(Atomic::TCallable {
2369 params: Some(Box::new([FnParam {
2370 name: Name::new("x"),
2371 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2372 out_ty: None,
2373 default: None,
2374 is_variadic: false,
2375 is_byref: false,
2376 is_optional: false,
2377 }])),
2378 return_type: Some(Box::new(t_param("T"))),
2379 });
2380 let result = ty.substitute_templates(&bindings_t_string());
2381 let Atomic::TCallable {
2382 params,
2383 return_type,
2384 } = &result.types[0]
2385 else {
2386 panic!("expected TCallable");
2387 };
2388 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2389 let param_union = param_ty.to_union();
2390 assert!(matches!(param_union.types[0], Atomic::TString));
2391 let ret = return_type.as_ref().unwrap();
2392 assert!(matches!(ret.types[0], Atomic::TString));
2393 }
2394
2395 #[test]
2396 fn substitute_callable_bare_no_panic() {
2397 let ty = Type::single(Atomic::TCallable {
2399 params: None,
2400 return_type: None,
2401 });
2402 let result = ty.substitute_templates(&bindings_t_string());
2403 assert!(matches!(
2404 result.types[0],
2405 Atomic::TCallable {
2406 params: None,
2407 return_type: None
2408 }
2409 ));
2410 }
2411
2412 #[test]
2413 fn substitute_closure_params_return_and_this() {
2414 use crate::atomic::FnParam;
2415 let ty = Type::single(Atomic::TClosure {
2416 data: Box::new(crate::atomic::ClosureData {
2417 params: Box::new([FnParam {
2418 name: Name::new("a"),
2419 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2420 out_ty: None,
2421 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2422 is_variadic: true,
2423 is_byref: true,
2424 is_optional: true,
2425 }]),
2426 return_type: t_param("T"),
2427 this_type: Some(t_param("T")),
2428 }),
2429 });
2430 let result = ty.substitute_templates(&bindings_t_string());
2431 let Atomic::TClosure { data } = &result.types[0] else {
2432 panic!("expected TClosure");
2433 };
2434 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2435 let p = ¶ms[0];
2436 let ty_union = p.ty.as_ref().unwrap().to_union();
2437 let default_union = p.default.as_ref().unwrap().to_union();
2438 assert!(matches!(ty_union.types[0], Atomic::TString));
2439 assert!(matches!(default_union.types[0], Atomic::TString));
2440 assert!(p.is_variadic);
2442 assert!(p.is_byref);
2443 assert!(p.is_optional);
2444 assert!(matches!(return_type.types[0], Atomic::TString));
2445 assert!(matches!(
2446 this_type.as_ref().unwrap().types[0],
2447 Atomic::TString
2448 ));
2449 }
2450
2451 #[test]
2452 fn substitute_conditional_all_branches() {
2453 let ty = Type::single(conditional(
2454 None,
2455 t_param("T"),
2456 t_param("T"),
2457 Type::single(Atomic::TInt),
2458 ));
2459 let result = ty.substitute_templates(&bindings_t_string());
2460 let Atomic::TConditional { data } = &result.types[0] else {
2461 panic!("expected TConditional");
2462 };
2463 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2464 assert!(matches!(subject.types[0], Atomic::TString));
2465 assert!(matches!(if_true.types[0], Atomic::TString));
2466 assert!(matches!(if_false.types[0], Atomic::TInt));
2467 }
2468
2469 #[test]
2470 fn resolve_conditional_is_null_non_null_arg() {
2471 let ty = Type::single(conditional(
2472 Some(Name::new("x")),
2473 Type::single(Atomic::TNull),
2474 Type::single(Atomic::TInt),
2475 Type::single(Atomic::TString),
2476 ));
2477 let result = ty.resolve_conditional_returns(|name| {
2478 if name == "x" {
2479 Some(Type::single(Atomic::TString)) } else {
2481 None
2482 }
2483 });
2484 assert!(result.types.len() == 1);
2485 assert!(matches!(result.types[0], Atomic::TString));
2486 }
2487
2488 #[test]
2489 fn resolve_conditional_is_null_null_arg() {
2490 let ty = Type::single(conditional(
2491 Some(Name::new("x")),
2492 Type::single(Atomic::TNull),
2493 Type::single(Atomic::TInt),
2494 Type::single(Atomic::TString),
2495 ));
2496 let result = ty.resolve_conditional_returns(|name| {
2497 if name == "x" {
2498 Some(Type::single(Atomic::TNull)) } else {
2500 None
2501 }
2502 });
2503 assert!(result.types.len() == 1);
2504 assert!(matches!(result.types[0], Atomic::TInt));
2505 }
2506
2507 #[test]
2508 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2509 let mut nullable_str = Type::single(Atomic::TString);
2510 nullable_str.add_type(Atomic::TNull);
2511 let ty = Type::single(conditional(
2512 Some(Name::new("x")),
2513 Type::single(Atomic::TNull),
2514 Type::single(Atomic::TInt),
2515 Type::single(Atomic::TString),
2516 ));
2517 let result = ty.resolve_conditional_returns(|name| {
2518 if name == "x" {
2519 Some(nullable_str.clone())
2520 } else {
2521 None
2522 }
2523 });
2524 assert_eq!(result.types.len(), 2);
2526 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2527 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2528 }
2529
2530 #[test]
2531 fn resolve_conditional_nested_widens_inner_branch() {
2532 let inner = Type::single(conditional(
2535 Some(Name::new("x")),
2536 Type::single(Atomic::TString),
2537 Type::single(Atomic::TString),
2538 Type::single(Atomic::TFloat),
2539 ));
2540 let ty = Type::single(conditional(
2541 Some(Name::new("x")),
2542 Type::single(Atomic::TNull),
2543 Type::single(Atomic::TInt),
2544 inner,
2545 ));
2546 let result = ty.resolve_conditional_returns(|_| None);
2548 assert!(
2549 result
2550 .types
2551 .iter()
2552 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2553 "no TConditional should survive: {:?}",
2554 result.types
2555 );
2556 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2557 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2558 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2559 }
2560
2561 #[test]
2562 fn resolve_conditional_nested_resolves_inner_branch() {
2563 let inner = Type::single(conditional(
2567 Some(Name::new("x")),
2568 Type::single(Atomic::TString),
2569 Type::single(Atomic::TString),
2570 Type::single(Atomic::TFloat),
2571 ));
2572 let ty = Type::single(conditional(
2573 Some(Name::new("x")),
2574 Type::single(Atomic::TNull),
2575 Type::single(Atomic::TInt),
2576 inner,
2577 ));
2578 let result = ty.resolve_conditional_returns(|name| {
2580 if name == "x" {
2581 Some(Type::single(Atomic::TString))
2582 } else {
2583 None
2584 }
2585 });
2586 assert!(
2587 result
2588 .types
2589 .iter()
2590 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2591 "no TConditional should survive: {:?}",
2592 result.types
2593 );
2594 assert_eq!(result.types.len(), 1);
2595 assert!(matches!(result.types[0], Atomic::TString));
2596 }
2597
2598 #[test]
2599 fn substitute_intersection_parts() {
2600 let ty = Type::single(Atomic::TIntersection {
2601 parts: vec_to_type_params(vec![
2602 Type::single(Atomic::TNamedObject {
2603 fqcn: Name::new("Countable"),
2604 type_params: empty_type_params(),
2605 }),
2606 t_param("T"),
2607 ]),
2608 });
2609 let result = ty.substitute_templates(&bindings_t_string());
2610 let Atomic::TIntersection { parts } = &result.types[0] else {
2611 panic!("expected TIntersection");
2612 };
2613 assert_eq!(parts.len(), 2);
2614 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2615 assert!(matches!(parts[1].types[0], Atomic::TString));
2616 }
2617
2618 #[test]
2619 fn substitute_no_template_params_identity() {
2620 let ty = Type::single(Atomic::TInt);
2621 let result = ty.substitute_templates(&bindings_t_string());
2622 assert!(matches!(result.types[0], Atomic::TInt));
2623 }
2624}