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 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 }
768
769 pub fn narrow_to_scalar(&self) -> Type {
771 self.filter_replacing(
772 |t| {
773 t.is_string()
774 || t.is_int()
775 || matches!(
776 t,
777 Atomic::TFloat
778 | Atomic::TIntegralFloat
779 | Atomic::TLiteralFloat(..)
780 | Atomic::TBool
781 | Atomic::TTrue
782 | Atomic::TFalse
783 | Atomic::TScalar
784 | Atomic::TNumeric
785 | Atomic::TNumericString
786 | Atomic::TTemplateParam { .. }
787 )
788 },
789 |t| matches!(t, Atomic::TMixed),
790 Atomic::TScalar,
791 )
792 }
793
794 pub fn narrow_to_iterable(&self) -> Type {
797 self.filter(|t| {
798 t.is_array()
799 || t.is_object()
800 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
801 })
802 }
803
804 pub fn narrow_to_countable(&self) -> Type {
807 self.filter(|t| {
808 t.is_array()
809 || t.is_object()
810 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
811 })
812 }
813
814 pub fn narrow_to_resource(&self) -> Type {
818 self.filter(|t| matches!(t, Atomic::TMixed))
820 }
821
822 pub fn narrow_to_class_string(&self) -> Type {
827 let mut out = Type::empty();
828 out.from_docblock = self.from_docblock;
829 for t in &self.types {
830 match t {
831 Atomic::TClassString(_) => out.add_type(t.clone()),
832 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
833 out.add_type(Atomic::TClassString(None));
834 }
835 _ => {}
836 }
837 }
838 out
839 }
840
841 pub fn narrow_to_interface_string(&self) -> Type {
846 let mut out = Type::empty();
847 out.from_docblock = self.from_docblock;
848 for t in &self.types {
849 match t {
850 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
851 Atomic::TClassString(name) => {
855 out.add_type(Atomic::TInterfaceString(*name));
856 }
857 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
858 out.add_type(Atomic::TInterfaceString(None));
859 }
860 _ => {}
861 }
862 }
863 out
864 }
865
866 pub fn merge(a: &Type, b: &Type) -> Type {
871 if b.types.is_empty() {
873 let mut result = a.clone();
874 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
875 return result;
876 }
877 if a.types.is_empty() {
879 let mut result = b.clone();
880 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
881 return result;
882 }
883 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
885 let mut result = a.clone();
886 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
887 return result;
888 }
889 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
891 return Type {
892 types: smallvec::smallvec![Atomic::TMixed],
893 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
894 from_docblock: a.from_docblock || b.from_docblock,
895 };
896 }
897 let mut result = a.clone();
898 result.merge_with(b);
899 result
900 }
901
902 pub fn merge_with(&mut self, other: &Type) {
904 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
905 self.possibly_undefined |= other.possibly_undefined;
906 return;
907 }
908 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
909 self.types.clear();
910 self.types.push(Atomic::TMixed);
911 self.possibly_undefined |= other.possibly_undefined;
912 return;
913 }
914 for atomic in &other.types {
915 self.add_type(atomic.clone());
916 }
917 self.possibly_undefined |= other.possibly_undefined;
918 }
919
920 pub fn intersect_with(&self, other: &Type) -> Type {
924 if self.is_mixed() {
925 return other.clone();
926 }
927 if other.is_mixed() {
928 return self.clone();
929 }
930 let mut result = Type::empty();
938 for a in &self.types {
939 for b in &other.types {
940 if a == b {
941 result.add_type(a.clone());
942 } else if atomic_subtype(b, a) {
943 result.add_type(b.clone());
944 } else if atomic_subtype(a, b) {
945 result.add_type(a.clone());
946 }
947 }
948 }
949 if result.is_empty() {
950 Type::never()
951 } else {
952 result
953 }
954 }
955
956 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
960 if bindings.is_empty() {
961 return self.clone();
962 }
963 if !self.types.iter().any(atomic_may_contain_templates) {
966 return self.clone();
967 }
968 let mut result = Type::empty();
969 result.possibly_undefined = self.possibly_undefined;
970 result.from_docblock = self.from_docblock;
971 for atomic in &self.types {
972 match atomic {
973 Atomic::TTemplateParam { name, .. } => {
974 if let Some(resolved) = bindings.get(name) {
975 for t in &resolved.types {
976 result.add_type(t.clone());
977 }
978 } else {
979 result.add_type(atomic.clone());
980 }
981 }
982 Atomic::TArray { key, value } => {
983 result.add_type(Atomic::TArray {
984 key: Box::new(key.substitute_templates(bindings)),
985 value: Box::new(value.substitute_templates(bindings)),
986 });
987 }
988 Atomic::TList { value } => {
989 result.add_type(Atomic::TList {
990 value: Box::new(value.substitute_templates(bindings)),
991 });
992 }
993 Atomic::TNonEmptyArray { key, value } => {
994 result.add_type(Atomic::TNonEmptyArray {
995 key: Box::new(key.substitute_templates(bindings)),
996 value: Box::new(value.substitute_templates(bindings)),
997 });
998 }
999 Atomic::TNonEmptyList { value } => {
1000 result.add_type(Atomic::TNonEmptyList {
1001 value: Box::new(value.substitute_templates(bindings)),
1002 });
1003 }
1004 Atomic::TKeyedArray {
1005 properties,
1006 is_open,
1007 is_list,
1008 } => {
1009 use crate::atomic::KeyedProperty;
1010 let new_props = properties
1011 .iter()
1012 .map(|(k, prop)| {
1013 (
1014 k.clone(),
1015 KeyedProperty {
1016 ty: prop.ty.substitute_templates(bindings),
1017 optional: prop.optional,
1018 },
1019 )
1020 })
1021 .collect();
1022 result.add_type(Atomic::TKeyedArray {
1023 properties: Box::new(new_props),
1024 is_open: *is_open,
1025 is_list: *is_list,
1026 });
1027 }
1028 Atomic::TCallable {
1029 params,
1030 return_type,
1031 } => {
1032 result.add_type(Atomic::TCallable {
1033 params: params.as_ref().map(|ps| {
1034 ps.iter()
1035 .map(|p| substitute_in_fn_param(p, bindings))
1036 .collect()
1037 }),
1038 return_type: return_type
1039 .as_ref()
1040 .map(|r| Box::new(r.substitute_templates(bindings))),
1041 });
1042 }
1043 Atomic::TClosure { data } => {
1044 result.add_type(Atomic::TClosure {
1045 data: Box::new(crate::atomic::ClosureData {
1046 params: data
1047 .params
1048 .iter()
1049 .map(|p| substitute_in_fn_param(p, bindings))
1050 .collect(),
1051 return_type: data.return_type.substitute_templates(bindings),
1052 this_type: data
1053 .this_type
1054 .as_ref()
1055 .map(|t| t.substitute_templates(bindings)),
1056 }),
1057 });
1058 }
1059 Atomic::TConditional { data } => {
1060 let param_name = &data.param_name;
1061 let new_subject = data.subject.substitute_templates(bindings);
1062 let new_if_true = data.if_true.substitute_templates(bindings);
1063 let new_if_false = data.if_false.substitute_templates(bindings);
1064
1065 let resolved = if let Some(name) = param_name {
1069 if let Some(bound) = bindings.get(name) {
1070 if new_subject.types.len() == 1 {
1071 resolve_conditional_branch(
1072 &new_subject.types[0],
1073 bound,
1074 &new_if_true,
1075 &new_if_false,
1076 )
1077 } else {
1078 None
1079 }
1080 } else {
1081 None
1082 }
1083 } else {
1084 None
1085 };
1086
1087 if let Some(branch) = resolved {
1088 for t in branch.types {
1089 result.add_type(t);
1090 }
1091 } else {
1092 result.add_type(Atomic::TConditional {
1093 data: Box::new(crate::atomic::ConditionalData {
1094 param_name: *param_name,
1095 subject: new_subject,
1096 if_true: new_if_true,
1097 if_false: new_if_false,
1098 }),
1099 });
1100 }
1101 }
1102 Atomic::TIntersection { parts } => {
1103 result.add_type(Atomic::TIntersection {
1104 parts: vec_to_type_params(
1105 parts
1106 .iter()
1107 .map(|p| p.substitute_templates(bindings))
1108 .collect(),
1109 ),
1110 });
1111 }
1112 Atomic::TNamedObject { fqcn, type_params } => {
1113 if type_params.is_empty() && !fqcn.contains('\\') {
1120 if let Some(resolved) = bindings.get(fqcn) {
1121 for t in &resolved.types {
1122 result.add_type(t.clone());
1123 }
1124 continue;
1125 }
1126 }
1127 let new_params: Vec<Type> = type_params
1128 .iter()
1129 .map(|p| p.substitute_templates(bindings))
1130 .collect();
1131 result.add_type(Atomic::TNamedObject {
1132 fqcn: *fqcn,
1133 type_params: vec_to_type_params(new_params),
1134 });
1135 }
1136 Atomic::TClassString(Some(param_name)) => {
1138 if let Some(resolved) = bindings.get(param_name) {
1139 for r_atomic in &resolved.types {
1140 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1141 Some(*fqcn)
1142 } else {
1143 None
1144 };
1145 result.add_type(Atomic::TClassString(cls_name));
1146 }
1147 } else {
1148 result.add_type(atomic.clone());
1149 }
1150 }
1151 Atomic::TInterfaceString(Some(param_name)) => {
1153 if let Some(resolved) = bindings.get(param_name) {
1154 for r_atomic in &resolved.types {
1155 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1156 Some(*fqcn)
1157 } else {
1158 None
1159 };
1160 result.add_type(Atomic::TInterfaceString(iface_name));
1161 }
1162 } else {
1163 result.add_type(atomic.clone());
1164 }
1165 }
1166 _ => {
1167 result.add_type(atomic.clone());
1168 }
1169 }
1170 }
1171 result
1172 }
1173
1174 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1180 where
1181 F: Fn(&str) -> Option<Type>,
1182 {
1183 self.resolve_conditional_inner(&lookup)
1184 }
1185
1186 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1187 where
1188 F: Fn(&str) -> Option<Type>,
1189 {
1190 let mut result = Type::empty();
1191 for atomic in self.types {
1192 match atomic {
1193 Atomic::TConditional { ref data } => {
1194 let (param_name, subject, if_true, if_false) = (
1195 &data.param_name,
1196 &data.subject,
1197 &data.if_true,
1198 &data.if_false,
1199 );
1200 let resolved = if subject.types.len() == 1 {
1201 if let Some(name) = param_name {
1202 if let Some(arg_ty) = lookup(name.as_ref()) {
1203 resolve_conditional_branch(
1204 &subject.types[0],
1205 &arg_ty,
1206 if_true,
1207 if_false,
1208 )
1209 } else {
1210 None
1211 }
1212 } else {
1213 None
1214 }
1215 } else {
1216 None
1217 };
1218
1219 if let Some(branch) = resolved {
1220 for t in branch.resolve_conditional_inner(lookup).types {
1222 result.add_type(t);
1223 }
1224 } else {
1225 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1228 result.add_type(t);
1229 }
1230 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1231 result.add_type(t);
1232 }
1233 }
1234 }
1235 other => result.add_type(other),
1236 }
1237 }
1238 result
1239 }
1240
1241 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1251 if other.is_mixed() {
1252 return true;
1253 }
1254 if self.is_never() {
1255 return true; }
1257 self.types
1258 .iter()
1259 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1260 }
1261
1262 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1266 if self.is_mixed() {
1267 return true;
1268 }
1269 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1270 }
1271
1272 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1275 let mut result = Type::empty();
1276 result.possibly_undefined = self.possibly_undefined;
1277 result.from_docblock = self.from_docblock;
1278 for atomic in &self.types {
1279 if f(atomic) {
1280 result.types.push(atomic.clone());
1281 }
1282 }
1283 result
1284 }
1285
1286 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1291 &self,
1292 keep: K,
1293 placeholder: P,
1294 replacement: Atomic,
1295 ) -> Type {
1296 let mut result = Type::empty();
1297 result.possibly_undefined = self.possibly_undefined;
1298 result.from_docblock = self.from_docblock;
1299 for atomic in &self.types {
1300 if keep(atomic) {
1301 result.add_type(atomic.clone());
1302 } else if placeholder(atomic) {
1303 result.add_type(replacement.clone());
1304 }
1305 }
1306 result
1307 }
1308
1309 pub fn possibly_undefined(mut self) -> Self {
1311 self.possibly_undefined = true;
1312 self
1313 }
1314
1315 pub fn from_docblock(mut self) -> Self {
1317 self.from_docblock = true;
1318 self
1319 }
1320}
1321
1322fn is_string_atomic(a: &Atomic) -> bool {
1327 matches!(
1328 a,
1329 Atomic::TString
1330 | Atomic::TNonEmptyString
1331 | Atomic::TLiteralString(_)
1332 | Atomic::TNumericString
1333 | Atomic::TClassString(_)
1334 | Atomic::TInterfaceString(_)
1335 | Atomic::TCallableString
1336 )
1337}
1338
1339fn is_array_atomic(a: &Atomic) -> bool {
1340 matches!(
1341 a,
1342 Atomic::TArray { .. }
1343 | Atomic::TNonEmptyArray { .. }
1344 | Atomic::TKeyedArray { .. }
1345 | Atomic::TList { .. }
1346 | Atomic::TNonEmptyList { .. }
1347 )
1348}
1349
1350fn is_list_atomic(a: &Atomic) -> bool {
1351 match a {
1352 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1353 Atomic::TKeyedArray { is_list, .. } => *is_list,
1354 _ => false,
1355 }
1356}
1357
1358fn is_float_atomic(a: &Atomic) -> bool {
1359 matches!(
1360 a,
1361 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..)
1362 )
1363}
1364
1365fn is_bool_atomic(a: &Atomic) -> bool {
1366 matches!(a, Atomic::TBool | Atomic::TTrue | Atomic::TFalse)
1367}
1368
1369fn resolve_conditional_branch(
1375 subject: &Atomic,
1376 arg_ty: &Type,
1377 if_true: &Type,
1378 if_false: &Type,
1379) -> Option<Type> {
1380 let predicate: fn(&Atomic) -> bool = match subject {
1381 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1382 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1383 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1384 Atomic::TString => is_string_atomic,
1385 Atomic::TList { .. } => is_list_atomic,
1386 Atomic::TArray { .. } => is_array_atomic,
1387 Atomic::TInt => Atomic::is_int,
1388 Atomic::TFloat => is_float_atomic,
1389 Atomic::TBool => is_bool_atomic,
1390 _ => return None,
1391 };
1392
1393 if arg_ty.types.is_empty() {
1394 return None;
1395 }
1396 let all_match = arg_ty.types.iter().all(&predicate);
1397 let none_match = !arg_ty.types.iter().any(predicate);
1398 if all_match {
1399 Some(if_true.clone())
1400 } else if none_match {
1401 Some(if_false.clone())
1402 } else {
1403 None
1404 }
1405}
1406
1407fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1415 match atomic {
1416 Atomic::TNamedObject { fqcn, type_params } => {
1420 !type_params.is_empty() || !fqcn.contains('\\')
1421 }
1422 Atomic::TTemplateParam { .. }
1423 | Atomic::TArray { .. }
1424 | Atomic::TList { .. }
1425 | Atomic::TNonEmptyArray { .. }
1426 | Atomic::TNonEmptyList { .. }
1427 | Atomic::TKeyedArray { .. }
1428 | Atomic::TCallable { .. }
1429 | Atomic::TClosure { .. }
1430 | Atomic::TConditional { .. }
1431 | Atomic::TIntersection { .. }
1432 | Atomic::TClassString(Some(_))
1433 | Atomic::TInterfaceString(Some(_)) => true,
1434 _ => false,
1435 }
1436}
1437
1438fn substitute_in_fn_param(
1439 p: &crate::atomic::FnParam,
1440 bindings: &FxHashMap<Name, Type>,
1441) -> crate::atomic::FnParam {
1442 crate::atomic::FnParam {
1443 name: p.name,
1444 ty: p.ty.as_ref().map(|t| {
1445 let u = t.to_union();
1446 let substituted = u.substitute_templates(bindings);
1447 crate::compact::SimpleType::from_union(substituted)
1448 }),
1449 out_ty: p.out_ty.as_ref().map(|t| {
1450 let u = t.to_union();
1451 let substituted = u.substitute_templates(bindings);
1452 crate::compact::SimpleType::from_union(substituted)
1453 }),
1454 default: p.default.as_ref().map(|d| {
1455 let u = d.to_union();
1456 let substituted = u.substitute_templates(bindings);
1457 crate::compact::SimpleType::from_union(substituted)
1458 }),
1459 is_variadic: p.is_variadic,
1460 is_byref: p.is_byref,
1461 is_optional: p.is_optional,
1462 }
1463}
1464
1465pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1471 if sub == sup {
1472 return true;
1473 }
1474 match (sub, sup) {
1475 (Atomic::TNever, _) => true,
1477 (_, Atomic::TMixed) => true,
1479 (Atomic::TMixed, _) => true,
1480 (_, Atomic::TTemplateParam { as_type, .. }) => {
1485 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1486 }
1487
1488 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1490 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1491 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1492 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1493 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1494 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1495 (Atomic::TPositiveInt, Atomic::TInt) => true,
1496 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1497 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1498 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1499 (Atomic::TNegativeInt, Atomic::TInt) => true,
1500 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1501 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1502 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1503 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1504 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1505 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1506 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1507 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1508 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1510 max.is_none() && min.is_none_or(|m| m <= 1)
1511 }
1512 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1514 min.is_none() && max.is_none_or(|m| m >= -1)
1515 }
1516 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1518 max.is_none() && min.is_none_or(|m| m <= 0)
1519 }
1520 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1522 sub_min.is_some_and(|lo| lo >= 1)
1523 }
1524 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1525 sub_min.is_some_and(|lo| lo >= 0)
1526 }
1527 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1528 sub_max.is_some_and(|hi| hi <= -1)
1529 }
1530 (
1532 Atomic::TIntRange {
1533 min: sub_min,
1534 max: sub_max,
1535 },
1536 Atomic::TIntRange {
1537 min: sup_min,
1538 max: sup_max,
1539 },
1540 ) => {
1541 let lower_ok = match (sub_min, sup_min) {
1542 (_, None) => true,
1543 (None, Some(_)) => false,
1544 (Some(sl), Some(su)) => sl >= su,
1545 };
1546 let upper_ok = match (sub_max, sup_max) {
1547 (None, None) | (Some(_), None) => true,
1548 (None, Some(_)) => false,
1549 (Some(sl), Some(su)) => sl <= su,
1550 };
1551 lower_ok && upper_ok
1552 }
1553
1554 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1555 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1556 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1557
1558 (Atomic::TLiteralString(s), Atomic::TString) => {
1559 let _ = s;
1560 true
1561 }
1562 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1563 let _ = s;
1564 true
1565 }
1566 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1567 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1568 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1571 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1574 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1575 (Atomic::TNonEmptyString, Atomic::TString) => true,
1576 (Atomic::TCallableString, Atomic::TString) => true,
1577 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1579 (Atomic::TNumericString, Atomic::TString) => true,
1580 (Atomic::TClassString(_), Atomic::TString) => true,
1581 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1582 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1587 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1588 (Atomic::TEnumString, Atomic::TString) => true,
1589 (Atomic::TTraitString, Atomic::TString) => true,
1590
1591 (Atomic::TTrue, Atomic::TBool) => true,
1592 (Atomic::TFalse, Atomic::TBool) => true,
1593
1594 (Atomic::TInt, Atomic::TNumeric) => true,
1595 (Atomic::TFloat, Atomic::TNumeric) => true,
1596 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1597 (Atomic::TNumericString, Atomic::TNumeric) => true,
1598
1599 (Atomic::TInt, Atomic::TScalar) => true,
1600 (Atomic::TFloat, Atomic::TScalar) => true,
1601 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1602 (Atomic::TString, Atomic::TScalar) => true,
1603 (Atomic::TBool, Atomic::TScalar) => true,
1604 (Atomic::TNumeric, Atomic::TScalar) => true,
1605 (Atomic::TTrue, Atomic::TScalar) => true,
1606 (Atomic::TFalse, Atomic::TScalar) => true,
1607
1608 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1610 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1611 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1612 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1614 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1615 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1617 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1618 (
1622 Atomic::TNamedObject {
1623 fqcn: sub_fqcn,
1624 type_params: sub_params,
1625 },
1626 Atomic::TNamedObject {
1627 fqcn: sup_fqcn,
1628 type_params: sup_params,
1629 },
1630 ) => {
1631 sub_fqcn == sup_fqcn
1632 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1633 }
1634
1635 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1637
1638 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1640 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1641 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1642 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1643 (Atomic::TInt, Atomic::TFloat) => true,
1644 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1645
1646 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1648 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1649 }
1650
1651 (Atomic::TString, Atomic::TCallable { .. }) => true,
1653 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1654 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1655 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1656 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1657 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1658
1659 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1661 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1663 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1673 fn has_nominal_type(t: &Type) -> bool {
1674 t.types.iter().any(|a| {
1675 matches!(
1676 a,
1677 Atomic::TNamedObject { .. }
1678 | Atomic::TSelf { .. }
1679 | Atomic::TStaticObject { .. }
1680 | Atomic::TTemplateParam { .. }
1681 | Atomic::TClosure { .. }
1682 | Atomic::TCallable { .. }
1683 )
1684 })
1685 }
1686 let sub_required = sub
1687 .params
1688 .iter()
1689 .filter(|p| !p.is_optional && !p.is_variadic)
1690 .count();
1691 if sub_required > sup.params.len() {
1692 false
1693 } else {
1694 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
1695 let Some(sub_param) = sub.params.get(i) else {
1696 return true;
1697 };
1698 if sub_param.is_optional || sub_param.is_variadic {
1699 return true;
1700 }
1701 let (Some(sub_ty), Some(sup_ty)) =
1702 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
1703 else {
1704 return true;
1705 };
1706 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
1707 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
1708 return true;
1709 }
1710 sup_u.is_subtype_structural(&sub_u)
1713 });
1714 params_ok
1715 && (sub.return_type.is_mixed()
1716 || sup.return_type.is_mixed()
1717 || has_nominal_type(&sub.return_type)
1718 || has_nominal_type(&sup.return_type)
1719 || sub.return_type.is_subtype_structural(&sup.return_type))
1720 }
1721 }
1722 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1724 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1726 fqcn.as_ref().eq_ignore_ascii_case("closure")
1727 }
1728 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1729 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1731 fqcn.as_ref().eq_ignore_ascii_case("closure")
1732 }
1733 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1735 fqcn.as_ref().eq_ignore_ascii_case("closure")
1736 }
1737
1738 (
1746 Atomic::TIntersection { parts: sub_parts },
1747 Atomic::TIntersection { parts: sup_parts },
1748 ) => sup_parts.iter().all(|sup_part| {
1749 sub_parts
1750 .iter()
1751 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
1752 }),
1753
1754 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1756 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1757 }
1758 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1759 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1760 }
1761 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1762 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1763 }
1764 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1765 value.is_subtype_structural(lv)
1766 }
1767 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1769 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1770 && av.is_subtype_structural(lv)
1771 }
1772 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1773 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1774 && av.is_subtype_structural(lv)
1775 }
1776 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1777 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1778 && av.is_subtype_structural(lv)
1779 }
1780 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1781 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1782 && av.is_subtype_structural(lv)
1783 }
1784 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1786 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1787 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1788 }
1789
1790 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1792 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1793 }
1794
1795 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
1804 properties.iter().all(|(prop_key, prop)| {
1805 let key_atomic = match prop_key {
1806 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1807 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1808 };
1809 if !Type::single(key_atomic).is_subtype_structural(key) {
1810 return false; }
1812 let has_named_obj = prop.ty.types.iter().any(|a| {
1814 matches!(
1815 a,
1816 Atomic::TNamedObject { .. }
1817 | Atomic::TSelf { .. }
1818 | Atomic::TStaticObject { .. }
1819 | Atomic::TClosure { .. }
1820 | Atomic::TTemplateParam { .. }
1821 )
1822 });
1823 has_named_obj || prop.ty.is_subtype_structural(value)
1824 })
1825 }
1826 (
1827 Atomic::TKeyedArray {
1828 properties,
1829 is_open,
1830 ..
1831 },
1832 Atomic::TNonEmptyArray { key, value },
1833 ) => {
1834 (*is_open || properties.iter().any(|(_, p)| !p.optional))
1835 && properties.iter().all(|(prop_key, prop)| {
1836 let key_atomic = match prop_key {
1837 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1838 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1839 };
1840 if !Type::single(key_atomic).is_subtype_structural(key) {
1841 return false;
1842 }
1843 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 (
1859 Atomic::TKeyedArray {
1860 properties,
1861 is_list,
1862 ..
1863 },
1864 Atomic::TList { value: lv },
1865 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1866 (
1867 Atomic::TKeyedArray {
1868 properties,
1869 is_list,
1870 ..
1871 },
1872 Atomic::TNonEmptyList { value: lv },
1873 ) => {
1874 *is_list
1875 && !properties.is_empty()
1876 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1877 }
1878
1879 (
1884 Atomic::TKeyedArray {
1885 properties: sub_props,
1886 is_open: sub_open,
1887 ..
1888 },
1889 Atomic::TKeyedArray {
1890 properties: sup_props,
1891 is_open: sup_open,
1892 ..
1893 },
1894 ) => {
1895 let keys_satisfied = sup_props
1896 .iter()
1897 .all(|(key, sup_prop)| match sub_props.get(key) {
1898 Some(sub_prop) => {
1899 if !sup_prop.optional && sub_prop.optional {
1903 return false;
1904 }
1905 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
1906 matches!(
1907 a,
1908 Atomic::TNamedObject { .. }
1909 | Atomic::TSelf { .. }
1910 | Atomic::TStaticObject { .. }
1911 | Atomic::TClosure { .. }
1912 | Atomic::TTemplateParam { .. }
1913 )
1914 });
1915 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
1916 }
1917 None => sup_prop.optional || *sub_open,
1918 });
1919 let no_undeclared_extras =
1920 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
1921 keys_satisfied && no_undeclared_extras
1922 }
1923
1924 _ => false,
1925 }
1926}
1927
1928fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
1934 if sub.len() != sup.len() {
1935 return false;
1936 }
1937 sub.iter()
1938 .zip(sup.iter())
1939 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
1940}
1941
1942fn is_empty_array_literal(t: &Type) -> bool {
1945 !t.types.is_empty()
1946 && t.types.iter().all(
1947 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
1948 )
1949}
1950
1951fn is_array_like(t: &Type) -> bool {
1953 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
1954}
1955
1956#[cfg(test)]
1961mod tests {
1962 use std::sync::Arc;
1963
1964 use super::*;
1965
1966 fn conditional(
1967 param_name: Option<Name>,
1968 subject: Type,
1969 if_true: Type,
1970 if_false: Type,
1971 ) -> Atomic {
1972 Atomic::TConditional {
1973 data: Box::new(crate::atomic::ConditionalData {
1974 param_name,
1975 subject,
1976 if_true,
1977 if_false,
1978 }),
1979 }
1980 }
1981
1982 #[test]
1983 fn single_is_single() {
1984 let u = Type::single(Atomic::TString);
1985 assert!(u.is_single());
1986 assert!(!u.is_nullable());
1987 }
1988
1989 #[test]
1990 fn nullable_has_null() {
1991 let u = Type::nullable(Atomic::TString);
1992 assert!(u.is_nullable());
1993 assert_eq!(u.types.len(), 2);
1994 }
1995
1996 #[test]
1997 fn add_type_deduplicates() {
1998 let mut u = Type::single(Atomic::TString);
1999 u.add_type(Atomic::TString);
2000 assert_eq!(u.types.len(), 1);
2001 }
2002
2003 #[test]
2004 fn array_key_is_int_string() {
2005 let k = Type::array_key();
2006 assert!(k.is_array_key());
2007 assert_eq!(k.types.len(), 2);
2008 }
2009
2010 #[test]
2011 fn is_array_key_false_for_plain_int() {
2012 assert!(!Type::int().is_array_key());
2013 }
2014
2015 #[test]
2016 fn is_array_key_false_for_mixed() {
2017 assert!(!Type::mixed().is_array_key());
2018 }
2019
2020 #[test]
2021 fn is_array_key_false_for_int_string_null() {
2022 let mut u = Type::array_key();
2023 u.add_type(Atomic::TNull);
2024 assert!(!u.is_array_key());
2025 }
2026
2027 #[test]
2028 fn add_type_literal_subsumed_by_base() {
2029 let mut u = Type::single(Atomic::TInt);
2030 u.add_type(Atomic::TLiteralInt(42));
2031 assert_eq!(u.types.len(), 1);
2032 assert!(matches!(u.types[0], Atomic::TInt));
2033 }
2034
2035 #[test]
2036 fn true_then_false_merges_to_bool() {
2037 let mut u = Type::single(Atomic::TTrue);
2038 u.add_type(Atomic::TFalse);
2039 assert_eq!(u.types.len(), 1);
2040 assert!(matches!(u.types[0], Atomic::TBool));
2041 }
2042
2043 #[test]
2044 fn false_then_true_merges_to_bool() {
2045 let mut u = Type::single(Atomic::TFalse);
2046 u.add_type(Atomic::TTrue);
2047 assert_eq!(u.types.len(), 1);
2048 assert!(matches!(u.types[0], Atomic::TBool));
2049 }
2050
2051 #[test]
2052 fn true_alone_stays_true() {
2053 let u = Type::single(Atomic::TTrue);
2054 assert_eq!(u.types.len(), 1);
2055 assert!(matches!(u.types[0], Atomic::TTrue));
2056 }
2057
2058 #[test]
2059 fn true_false_merge_preserves_other_union_members() {
2060 let mut u = Type::single(Atomic::TTrue);
2061 u.add_type(Atomic::TNull);
2062 u.add_type(Atomic::TFalse);
2063 assert_eq!(u.types.len(), 2);
2064 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2065 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2066 }
2067
2068 #[test]
2069 fn add_type_base_widens_literals() {
2070 let mut u = Type::single(Atomic::TLiteralInt(1));
2071 u.add_type(Atomic::TLiteralInt(2));
2072 u.add_type(Atomic::TInt);
2073 assert_eq!(u.types.len(), 1);
2074 assert!(matches!(u.types[0], Atomic::TInt));
2075 }
2076
2077 #[test]
2078 fn mixed_subsumes_everything() {
2079 let mut u = Type::single(Atomic::TString);
2080 u.add_type(Atomic::TMixed);
2081 assert_eq!(u.types.len(), 1);
2082 assert!(u.is_mixed());
2083 }
2084
2085 #[test]
2086 fn remove_null() {
2087 let u = Type::nullable(Atomic::TString);
2088 let narrowed = u.remove_null();
2089 assert!(!narrowed.is_nullable());
2090 assert_eq!(narrowed.types.len(), 1);
2091 }
2092
2093 #[test]
2094 fn narrow_to_truthy_removes_null_false() {
2095 let mut u = Type::empty();
2096 u.add_type(Atomic::TString);
2097 u.add_type(Atomic::TNull);
2098 u.add_type(Atomic::TFalse);
2099 let truthy = u.narrow_to_truthy();
2100 assert!(!truthy.is_nullable());
2101 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2102 }
2103
2104 #[test]
2105 fn merge_combines_types() {
2106 let a = Type::single(Atomic::TString);
2107 let b = Type::single(Atomic::TInt);
2108 let merged = Type::merge(&a, &b);
2109 assert_eq!(merged.types.len(), 2);
2110 }
2111
2112 #[test]
2113 fn intersect_keeps_narrower_side_not_self() {
2114 let int_ty = Type::single(Atomic::TInt);
2118 let mut literals = Type::empty();
2119 literals.add_type(Atomic::TLiteralInt(1));
2120 literals.add_type(Atomic::TLiteralInt(2));
2121
2122 let narrowed = int_ty.intersect_with(&literals);
2123 assert_eq!(narrowed.types.len(), 2);
2124 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2125 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2126 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2127 }
2128
2129 #[test]
2130 fn subtype_literal_int_under_int() {
2131 let sub = Type::single(Atomic::TLiteralInt(5));
2132 let sup = Type::single(Atomic::TInt);
2133 assert!(sub.is_subtype_structural(&sup));
2134 }
2135
2136 #[test]
2137 fn subtype_never_is_bottom() {
2138 let never = Type::never();
2139 let string = Type::single(Atomic::TString);
2140 assert!(never.is_subtype_structural(&string));
2141 }
2142
2143 #[test]
2144 fn subtype_everything_under_mixed() {
2145 let string = Type::single(Atomic::TString);
2146 let mixed = Type::mixed();
2147 assert!(string.is_subtype_structural(&mixed));
2148 }
2149
2150 #[test]
2151 fn template_substitution() {
2152 let mut bindings = FxHashMap::default();
2153 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2154
2155 let tmpl = Type::single(Atomic::TTemplateParam {
2156 name: Name::new("T"),
2157 as_type: Box::new(Type::mixed()),
2158 defining_entity: Name::new("MyClass"),
2159 });
2160
2161 let resolved = tmpl.substitute_templates(&bindings);
2162 assert_eq!(resolved.types.len(), 1);
2163 assert!(matches!(resolved.types[0], Atomic::TString));
2164 }
2165
2166 #[test]
2167 fn intersection_is_object() {
2168 let parts = vec![
2169 Type::single(Atomic::TNamedObject {
2170 fqcn: Name::new("Iterator"),
2171 type_params: empty_type_params(),
2172 }),
2173 Type::single(Atomic::TNamedObject {
2174 fqcn: Name::new("Countable"),
2175 type_params: empty_type_params(),
2176 }),
2177 ];
2178 let atomic = Atomic::TIntersection {
2179 parts: vec_to_type_params(parts),
2180 };
2181 assert!(atomic.is_object());
2182 assert!(!atomic.can_be_falsy());
2183 assert!(atomic.can_be_truthy());
2184 }
2185
2186 #[test]
2187 fn intersection_display_two_parts() {
2188 let parts = vec![
2189 Type::single(Atomic::TNamedObject {
2190 fqcn: Name::new("Iterator"),
2191 type_params: empty_type_params(),
2192 }),
2193 Type::single(Atomic::TNamedObject {
2194 fqcn: Name::new("Countable"),
2195 type_params: empty_type_params(),
2196 }),
2197 ];
2198 let u = Type::single(Atomic::TIntersection {
2199 parts: vec_to_type_params(parts),
2200 });
2201 assert_eq!(format!("{u}"), "Iterator&Countable");
2202 }
2203
2204 #[test]
2205 fn intersection_display_three_parts() {
2206 let parts = vec![
2207 Type::single(Atomic::TNamedObject {
2208 fqcn: Name::new("A"),
2209 type_params: empty_type_params(),
2210 }),
2211 Type::single(Atomic::TNamedObject {
2212 fqcn: Name::new("B"),
2213 type_params: empty_type_params(),
2214 }),
2215 Type::single(Atomic::TNamedObject {
2216 fqcn: Name::new("C"),
2217 type_params: empty_type_params(),
2218 }),
2219 ];
2220 let u = Type::single(Atomic::TIntersection {
2221 parts: vec_to_type_params(parts),
2222 });
2223 assert_eq!(format!("{u}"), "A&B&C");
2224 }
2225
2226 #[test]
2227 fn intersection_in_nullable_union_display() {
2228 let intersection = Atomic::TIntersection {
2229 parts: vec_to_type_params(vec![
2230 Type::single(Atomic::TNamedObject {
2231 fqcn: Name::new("Iterator"),
2232 type_params: empty_type_params(),
2233 }),
2234 Type::single(Atomic::TNamedObject {
2235 fqcn: Name::new("Countable"),
2236 type_params: empty_type_params(),
2237 }),
2238 ]),
2239 };
2240 let mut u = Type::single(intersection);
2241 u.add_type(Atomic::TNull);
2242 assert!(u.is_nullable());
2243 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2244 }
2245
2246 fn t_param(name: &str) -> Type {
2249 Type::single(Atomic::TTemplateParam {
2250 name: Name::new(name),
2251 as_type: Box::new(Type::mixed()),
2252 defining_entity: Name::new("Fn"),
2253 })
2254 }
2255
2256 fn bindings_t_string() -> FxHashMap<Name, Type> {
2257 let mut b = FxHashMap::default();
2258 b.insert(Name::new("T"), Type::single(Atomic::TString));
2259 b
2260 }
2261
2262 #[test]
2263 fn substitute_non_empty_array_key_and_value() {
2264 let ty = Type::single(Atomic::TNonEmptyArray {
2265 key: Box::new(t_param("T")),
2266 value: Box::new(t_param("T")),
2267 });
2268 let result = ty.substitute_templates(&bindings_t_string());
2269 assert_eq!(result.types.len(), 1);
2270 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2271 panic!("expected TNonEmptyArray");
2272 };
2273 assert!(matches!(key.types[0], Atomic::TString));
2274 assert!(matches!(value.types[0], Atomic::TString));
2275 }
2276
2277 #[test]
2278 fn substitute_non_empty_list_value() {
2279 let ty = Type::single(Atomic::TNonEmptyList {
2280 value: Box::new(t_param("T")),
2281 });
2282 let result = ty.substitute_templates(&bindings_t_string());
2283 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2284 panic!("expected TNonEmptyList");
2285 };
2286 assert!(matches!(value.types[0], Atomic::TString));
2287 }
2288
2289 #[test]
2290 fn substitute_keyed_array_property_types() {
2291 use crate::atomic::{ArrayKey, KeyedProperty};
2292 use indexmap::IndexMap;
2293 let mut props = IndexMap::new();
2294 props.insert(
2295 ArrayKey::String(Arc::from("name")),
2296 KeyedProperty {
2297 ty: t_param("T"),
2298 optional: false,
2299 },
2300 );
2301 props.insert(
2302 ArrayKey::String(Arc::from("tag")),
2303 KeyedProperty {
2304 ty: t_param("T"),
2305 optional: true,
2306 },
2307 );
2308 let ty = Type::single(Atomic::TKeyedArray {
2309 properties: Box::new(props),
2310 is_open: true,
2311 is_list: false,
2312 });
2313 let result = ty.substitute_templates(&bindings_t_string());
2314 let Atomic::TKeyedArray {
2315 properties,
2316 is_open,
2317 is_list,
2318 } = &result.types[0]
2319 else {
2320 panic!("expected TKeyedArray");
2321 };
2322 assert!(is_open);
2323 assert!(!is_list);
2324 assert!(matches!(
2325 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2326 Atomic::TString
2327 ));
2328 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2329 assert!(matches!(
2330 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2331 Atomic::TString
2332 ));
2333 }
2334
2335 #[test]
2336 fn substitute_callable_params_and_return() {
2337 use crate::atomic::FnParam;
2338 let ty = Type::single(Atomic::TCallable {
2339 params: Some(Box::new([FnParam {
2340 name: Name::new("x"),
2341 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2342 out_ty: None,
2343 default: None,
2344 is_variadic: false,
2345 is_byref: false,
2346 is_optional: false,
2347 }])),
2348 return_type: Some(Box::new(t_param("T"))),
2349 });
2350 let result = ty.substitute_templates(&bindings_t_string());
2351 let Atomic::TCallable {
2352 params,
2353 return_type,
2354 } = &result.types[0]
2355 else {
2356 panic!("expected TCallable");
2357 };
2358 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2359 let param_union = param_ty.to_union();
2360 assert!(matches!(param_union.types[0], Atomic::TString));
2361 let ret = return_type.as_ref().unwrap();
2362 assert!(matches!(ret.types[0], Atomic::TString));
2363 }
2364
2365 #[test]
2366 fn substitute_callable_bare_no_panic() {
2367 let ty = Type::single(Atomic::TCallable {
2369 params: None,
2370 return_type: None,
2371 });
2372 let result = ty.substitute_templates(&bindings_t_string());
2373 assert!(matches!(
2374 result.types[0],
2375 Atomic::TCallable {
2376 params: None,
2377 return_type: None
2378 }
2379 ));
2380 }
2381
2382 #[test]
2383 fn substitute_closure_params_return_and_this() {
2384 use crate::atomic::FnParam;
2385 let ty = Type::single(Atomic::TClosure {
2386 data: Box::new(crate::atomic::ClosureData {
2387 params: Box::new([FnParam {
2388 name: Name::new("a"),
2389 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2390 out_ty: None,
2391 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2392 is_variadic: true,
2393 is_byref: true,
2394 is_optional: true,
2395 }]),
2396 return_type: t_param("T"),
2397 this_type: Some(t_param("T")),
2398 }),
2399 });
2400 let result = ty.substitute_templates(&bindings_t_string());
2401 let Atomic::TClosure { data } = &result.types[0] else {
2402 panic!("expected TClosure");
2403 };
2404 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2405 let p = ¶ms[0];
2406 let ty_union = p.ty.as_ref().unwrap().to_union();
2407 let default_union = p.default.as_ref().unwrap().to_union();
2408 assert!(matches!(ty_union.types[0], Atomic::TString));
2409 assert!(matches!(default_union.types[0], Atomic::TString));
2410 assert!(p.is_variadic);
2412 assert!(p.is_byref);
2413 assert!(p.is_optional);
2414 assert!(matches!(return_type.types[0], Atomic::TString));
2415 assert!(matches!(
2416 this_type.as_ref().unwrap().types[0],
2417 Atomic::TString
2418 ));
2419 }
2420
2421 #[test]
2422 fn substitute_conditional_all_branches() {
2423 let ty = Type::single(conditional(
2424 None,
2425 t_param("T"),
2426 t_param("T"),
2427 Type::single(Atomic::TInt),
2428 ));
2429 let result = ty.substitute_templates(&bindings_t_string());
2430 let Atomic::TConditional { data } = &result.types[0] else {
2431 panic!("expected TConditional");
2432 };
2433 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2434 assert!(matches!(subject.types[0], Atomic::TString));
2435 assert!(matches!(if_true.types[0], Atomic::TString));
2436 assert!(matches!(if_false.types[0], Atomic::TInt));
2437 }
2438
2439 #[test]
2440 fn resolve_conditional_is_null_non_null_arg() {
2441 let ty = Type::single(conditional(
2442 Some(Name::new("x")),
2443 Type::single(Atomic::TNull),
2444 Type::single(Atomic::TInt),
2445 Type::single(Atomic::TString),
2446 ));
2447 let result = ty.resolve_conditional_returns(|name| {
2448 if name == "x" {
2449 Some(Type::single(Atomic::TString)) } else {
2451 None
2452 }
2453 });
2454 assert!(result.types.len() == 1);
2455 assert!(matches!(result.types[0], Atomic::TString));
2456 }
2457
2458 #[test]
2459 fn resolve_conditional_is_null_null_arg() {
2460 let ty = Type::single(conditional(
2461 Some(Name::new("x")),
2462 Type::single(Atomic::TNull),
2463 Type::single(Atomic::TInt),
2464 Type::single(Atomic::TString),
2465 ));
2466 let result = ty.resolve_conditional_returns(|name| {
2467 if name == "x" {
2468 Some(Type::single(Atomic::TNull)) } else {
2470 None
2471 }
2472 });
2473 assert!(result.types.len() == 1);
2474 assert!(matches!(result.types[0], Atomic::TInt));
2475 }
2476
2477 #[test]
2478 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2479 let mut nullable_str = Type::single(Atomic::TString);
2480 nullable_str.add_type(Atomic::TNull);
2481 let ty = Type::single(conditional(
2482 Some(Name::new("x")),
2483 Type::single(Atomic::TNull),
2484 Type::single(Atomic::TInt),
2485 Type::single(Atomic::TString),
2486 ));
2487 let result = ty.resolve_conditional_returns(|name| {
2488 if name == "x" {
2489 Some(nullable_str.clone())
2490 } else {
2491 None
2492 }
2493 });
2494 assert_eq!(result.types.len(), 2);
2496 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2497 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2498 }
2499
2500 #[test]
2501 fn resolve_conditional_nested_widens_inner_branch() {
2502 let inner = Type::single(conditional(
2505 Some(Name::new("x")),
2506 Type::single(Atomic::TString),
2507 Type::single(Atomic::TString),
2508 Type::single(Atomic::TFloat),
2509 ));
2510 let ty = Type::single(conditional(
2511 Some(Name::new("x")),
2512 Type::single(Atomic::TNull),
2513 Type::single(Atomic::TInt),
2514 inner,
2515 ));
2516 let result = ty.resolve_conditional_returns(|_| None);
2518 assert!(
2519 result
2520 .types
2521 .iter()
2522 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2523 "no TConditional should survive: {:?}",
2524 result.types
2525 );
2526 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2527 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2528 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2529 }
2530
2531 #[test]
2532 fn resolve_conditional_nested_resolves_inner_branch() {
2533 let inner = Type::single(conditional(
2537 Some(Name::new("x")),
2538 Type::single(Atomic::TString),
2539 Type::single(Atomic::TString),
2540 Type::single(Atomic::TFloat),
2541 ));
2542 let ty = Type::single(conditional(
2543 Some(Name::new("x")),
2544 Type::single(Atomic::TNull),
2545 Type::single(Atomic::TInt),
2546 inner,
2547 ));
2548 let result = ty.resolve_conditional_returns(|name| {
2550 if name == "x" {
2551 Some(Type::single(Atomic::TString))
2552 } else {
2553 None
2554 }
2555 });
2556 assert!(
2557 result
2558 .types
2559 .iter()
2560 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2561 "no TConditional should survive: {:?}",
2562 result.types
2563 );
2564 assert_eq!(result.types.len(), 1);
2565 assert!(matches!(result.types[0], Atomic::TString));
2566 }
2567
2568 #[test]
2569 fn substitute_intersection_parts() {
2570 let ty = Type::single(Atomic::TIntersection {
2571 parts: vec_to_type_params(vec![
2572 Type::single(Atomic::TNamedObject {
2573 fqcn: Name::new("Countable"),
2574 type_params: empty_type_params(),
2575 }),
2576 t_param("T"),
2577 ]),
2578 });
2579 let result = ty.substitute_templates(&bindings_t_string());
2580 let Atomic::TIntersection { parts } = &result.types[0] else {
2581 panic!("expected TIntersection");
2582 };
2583 assert_eq!(parts.len(), 2);
2584 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2585 assert!(matches!(parts[1].types[0], Atomic::TString));
2586 }
2587
2588 #[test]
2589 fn substitute_no_template_params_identity() {
2590 let ty = Type::single(Atomic::TInt);
2591 let result = ty.substitute_templates(&bindings_t_string());
2592 assert!(matches!(result.types[0], Atomic::TInt));
2593 }
2594}