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 nullable(atomic: Atomic) -> Self {
111 if matches!(atomic, Atomic::TMixed) {
113 return Self::mixed();
114 }
115 let mut types = SmallVec::new();
116 types.push(atomic);
117 types.push(Atomic::TNull);
118 Self {
119 types,
120 possibly_undefined: false,
121 from_docblock: false,
122 }
123 }
124
125 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
127 let mut u = Self::empty();
128 for a in atomics {
129 u.add_type(a);
130 }
131 u
132 }
133
134 pub fn is_empty(&self) -> bool {
137 self.types.is_empty()
138 }
139
140 pub fn is_single(&self) -> bool {
141 self.types.len() == 1
142 }
143
144 pub fn is_nullable(&self) -> bool {
145 self.types.iter().any(|t| matches!(t, Atomic::TNull))
146 }
147
148 pub fn is_mixed(&self) -> bool {
149 self.types.iter().any(|t| match t {
150 Atomic::TMixed => true,
151 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
152 _ => false,
153 })
154 }
155
156 pub fn is_mixed_not_template(&self) -> bool {
161 self.is_mixed()
162 && !self
163 .types
164 .iter()
165 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
166 }
167
168 pub fn is_never(&self) -> bool {
169 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
170 }
171
172 pub fn clone_validity(&self) -> CloneValidity {
175 if self.types.is_empty() {
176 return CloneValidity::Unknown;
177 }
178 let mut has_non_object = false;
179 let mut has_other = false; for t in &self.types {
181 match t {
182 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
183 CloneValidity::Invalid => has_non_object = true,
184 CloneValidity::PossiblyInvalid => {
185 has_non_object = true;
186 has_other = true;
187 }
188 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
189 },
190 other if other.is_definitely_non_object() => has_non_object = true,
191 _ => has_other = true,
192 }
193 }
194 match (has_non_object, has_other) {
195 (true, false) => CloneValidity::Invalid,
196 (true, true) => CloneValidity::PossiblyInvalid,
197 _ => CloneValidity::Cloneable,
198 }
199 }
200
201 pub fn is_void(&self) -> bool {
202 self.is_single() && matches!(self.types[0], Atomic::TVoid)
203 }
204
205 pub fn can_be_falsy(&self) -> bool {
206 self.types.iter().any(|t| t.can_be_falsy())
207 }
208
209 pub fn can_be_truthy(&self) -> bool {
210 self.types.iter().any(|t| t.can_be_truthy())
211 }
212
213 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
214 self.types.iter().any(f)
215 }
216
217 pub fn has_named_object(&self, fqcn: &str) -> bool {
218 self.types.iter().any(|t| match t {
219 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
220 _ => false,
221 })
222 }
223
224 pub fn add_type(&mut self, atomic: Atomic) {
229 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
231 return;
232 }
233
234 if matches!(atomic, Atomic::TMixed) {
236 self.types.clear();
237 self.types.push(Atomic::TMixed);
238 return;
239 }
240
241 let atomic = if let Atomic::TConditional {
244 param_name: _,
245 subject: _,
246 if_true,
247 if_false,
248 } = &atomic
249 {
250 let mut simplified_true = Type::empty();
251 for t in &if_true.types {
252 simplified_true.add_type(t.clone());
253 }
254 let mut simplified_false = Type::empty();
255 for t in &if_false.types {
256 simplified_false.add_type(t.clone());
257 }
258 if simplified_true == simplified_false {
259 for t in simplified_true.types {
260 self.add_type(t);
261 }
262 return;
263 }
264 atomic
265 } else {
266 atomic
267 };
268
269 if self.types.contains(&atomic) {
271 return;
272 }
273
274 if let Atomic::TLiteralInt(_) = &atomic {
276 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
277 return;
278 }
279 }
280 if let Atomic::TLiteralString(_) = &atomic {
282 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
283 return;
284 }
285 }
286 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
288 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
289 {
290 return;
291 }
292 if matches!(atomic, Atomic::TInt) {
294 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
295 }
296 if matches!(atomic, Atomic::TString) {
298 self.types
299 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
300 }
301 if matches!(atomic, Atomic::TBool) {
303 self.types
304 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
305 }
306
307 if matches!(atomic, Atomic::TNever) {
309 if !self.types.is_empty() {
310 return;
311 }
312 } else {
313 self.types.retain(|t| !matches!(t, Atomic::TNever));
314 }
315
316 if let Atomic::TKeyedArray { properties, .. } = &atomic {
319 if properties.is_empty() {
320 for existing in &self.types {
321 match existing {
322 Atomic::TArray { .. }
323 | Atomic::TNonEmptyArray { .. }
324 | Atomic::TList { .. }
325 | Atomic::TNonEmptyList { .. } => {
326 return; }
328 _ => {}
329 }
330 }
331 }
332 }
333
334 let is_generic_array_or_list = matches!(
336 &atomic,
337 Atomic::TArray { .. }
338 | Atomic::TNonEmptyArray { .. }
339 | Atomic::TList { .. }
340 | Atomic::TNonEmptyList { .. }
341 );
342 if is_generic_array_or_list {
343 self.types.retain(|t| {
344 if let Atomic::TKeyedArray { properties, .. } = t {
345 !properties.is_empty()
346 } else {
347 true
348 }
349 });
350 }
351
352 self.types.push(atomic);
353 }
354
355 pub fn remove_null(&self) -> Type {
359 self.filter(|t| !matches!(t, Atomic::TNull))
360 }
361
362 pub fn remove_false(&self) -> Type {
365 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
366 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
367 result.add_type(Atomic::TTrue);
368 }
369 result
370 }
371
372 pub fn core_type(&self) -> Type {
374 self.remove_null().remove_false()
375 }
376
377 pub fn narrow_to_truthy(&self) -> Type {
379 if self.is_mixed_not_template() {
380 return Type::mixed();
381 }
382 let mut result = Type::empty();
383 result.from_docblock = self.from_docblock;
384 for t in &self.types {
385 match t {
386 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
390 Atomic::TLiteralInt(0)
392 | Atomic::TLiteralFloat(0, 0)
393 | Atomic::TNull
394 | Atomic::TFalse => {}
395 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
396 Atomic::TBool => result.add_type(Atomic::TTrue),
398 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
400 key: key.clone(),
401 value: value.clone(),
402 }),
403 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
404 value: value.clone(),
405 }),
406 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
410 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
415 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
416 let atom = if max.is_none() {
417 Atomic::TPositiveInt
418 } else {
419 Atomic::TIntRange {
420 min: Some(1),
421 max: *max,
422 }
423 };
424 result.add_type(atom);
425 }
426 Atomic::TIntRange { min, max: Some(0) } => {
428 let atom = match min {
429 None => Atomic::TNegativeInt,
430 Some(n) if *n <= -1 => Atomic::TIntRange {
431 min: *min,
432 max: Some(-1),
433 },
434 _ => continue, };
436 result.add_type(atom);
437 }
438 t if !t.can_be_truthy() => {}
440 _ => result.add_type(t.clone()),
441 }
442 }
443 result
444 }
445
446 pub fn narrow_to_falsy(&self) -> Type {
448 if self.is_mixed_not_template() {
449 return Type::from_vec(vec![
450 Atomic::TNull,
451 Atomic::TFalse,
452 Atomic::TLiteralInt(0),
453 Atomic::TLiteralString("".into()),
454 ]);
455 }
456 let mut result = Type::empty();
457 result.from_docblock = self.from_docblock;
458 for t in &self.types {
459 match t {
460 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
465 Atomic::TBool => result.add_type(Atomic::TFalse),
467 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
469 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
471 Atomic::TString => {
473 result.add_type(Atomic::TLiteralString("".into()));
474 result.add_type(Atomic::TLiteralString("0".into()));
475 }
476 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
478 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
480 Atomic::TIntRange {
482 min: Some(0),
483 max: Some(_) | None,
484 } => result.add_type(Atomic::TLiteralInt(0)),
485 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
487 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
489 }
490 }
491 result
492 }
493
494 pub fn narrow_instanceof(&self, class: &str) -> Type {
500 let narrowed_ty = Atomic::TNamedObject {
501 fqcn: class.into(),
502 type_params: empty_type_params(),
503 };
504 let has_object = self.types.iter().any(|t| {
506 matches!(
507 t,
508 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
510 });
511 if has_object || self.is_empty() {
512 Type::single(narrowed_ty)
513 } else {
514 Type::single(narrowed_ty)
517 }
518 }
519
520 pub fn narrow_to_string(&self) -> Type {
524 self.filter_replacing(
525 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
526 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
527 Atomic::TString,
528 )
529 }
530
531 pub fn narrow_to_int(&self) -> Type {
533 self.filter_replacing(
534 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
535 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
536 Atomic::TInt,
537 )
538 }
539
540 pub fn narrow_to_float(&self) -> Type {
542 self.filter_replacing(
543 |t| {
544 matches!(
545 t,
546 Atomic::TFloat
547 | Atomic::TIntegralFloat
548 | Atomic::TLiteralFloat(..)
549 | Atomic::TTemplateParam { .. }
550 )
551 },
552 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
553 Atomic::TFloat,
554 )
555 }
556
557 pub fn narrow_to_bool(&self) -> Type {
559 self.filter_replacing(
560 |t| {
561 matches!(
562 t,
563 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
564 )
565 },
566 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
567 Atomic::TBool,
568 )
569 }
570
571 pub fn narrow_to_null(&self) -> Type {
573 self.filter_replacing(
574 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
575 |t| matches!(t, Atomic::TMixed),
576 Atomic::TNull,
577 )
578 }
579
580 pub fn narrow_to_array(&self) -> Type {
582 self.filter_replacing(
583 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
584 |t| matches!(t, Atomic::TMixed),
585 Atomic::TArray {
586 key: Box::new(Type::mixed()),
587 value: Box::new(Type::mixed()),
588 },
589 )
590 }
591
592 pub fn narrow_to_non_empty_collection(&self) -> Type {
594 let mut out = Type::empty();
595 out.from_docblock = self.from_docblock;
596 for t in &self.types {
597 match t {
598 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
599 key: key.clone(),
600 value: value.clone(),
601 }),
602 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
603 value: value.clone(),
604 }),
605 _ => out.add_type(t.clone()),
606 }
607 }
608 out
609 }
610
611 pub fn narrow_to_list(&self) -> Type {
619 let mut out = Type::empty();
620 out.from_docblock = self.from_docblock;
621 for t in &self.types {
622 match t {
623 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
624 Atomic::TArray { key, value } if matches!(key.types.as_slice(), [Atomic::TInt]) => {
625 out.add_type(Atomic::TList {
626 value: value.clone(),
627 });
628 }
629 Atomic::TNonEmptyArray { key, value }
630 if matches!(key.types.as_slice(), [Atomic::TInt]) =>
631 {
632 out.add_type(Atomic::TNonEmptyList {
633 value: value.clone(),
634 });
635 }
636 Atomic::TMixed => out.add_type(Atomic::TList {
637 value: Box::new(Type::mixed()),
638 }),
639 _ => {}
640 }
641 }
642 if out.is_empty() {
643 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
644 } else {
645 out
646 }
647 }
648
649 pub fn narrow_to_object(&self) -> Type {
654 let mut out = Type::empty();
655 for t in &self.types {
656 if matches!(t, Atomic::TMixed) {
657 out.add_type(Atomic::TObject);
658 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
659 out.add_type(t.clone());
660 }
661 }
662 if out.types.is_empty() {
663 self.filter(|t| t.is_object())
664 } else {
665 out
666 }
667 }
668
669 pub fn narrow_to_callable(&self) -> Type {
676 self.filter(|t| {
677 t.is_callable()
678 || t.is_string()
679 || t.is_array()
680 || t.is_object()
681 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
682 })
683 }
684
685 pub fn narrow_to_scalar(&self) -> Type {
687 self.filter_replacing(
688 |t| {
689 t.is_string()
690 || t.is_int()
691 || matches!(
692 t,
693 Atomic::TFloat
694 | Atomic::TIntegralFloat
695 | Atomic::TLiteralFloat(..)
696 | Atomic::TBool
697 | Atomic::TTrue
698 | Atomic::TFalse
699 | Atomic::TScalar
700 | Atomic::TNumeric
701 | Atomic::TNumericString
702 | Atomic::TTemplateParam { .. }
703 )
704 },
705 |t| matches!(t, Atomic::TMixed),
706 Atomic::TScalar,
707 )
708 }
709
710 pub fn narrow_to_iterable(&self) -> Type {
713 self.filter(|t| {
714 t.is_array()
715 || t.is_object()
716 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
717 })
718 }
719
720 pub fn narrow_to_countable(&self) -> Type {
723 self.filter(|t| {
724 t.is_array()
725 || t.is_object()
726 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
727 })
728 }
729
730 pub fn narrow_to_resource(&self) -> Type {
734 self.filter(|t| matches!(t, Atomic::TMixed))
736 }
737
738 pub fn narrow_to_class_string(&self) -> Type {
743 let mut out = Type::empty();
744 out.from_docblock = self.from_docblock;
745 for t in &self.types {
746 match t {
747 Atomic::TClassString(_) => out.add_type(t.clone()),
748 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
749 out.add_type(Atomic::TClassString(None));
750 }
751 _ => {}
752 }
753 }
754 out
755 }
756
757 pub fn narrow_to_interface_string(&self) -> Type {
762 let mut out = Type::empty();
763 out.from_docblock = self.from_docblock;
764 for t in &self.types {
765 match t {
766 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
767 Atomic::TClassString(name) => {
771 out.add_type(Atomic::TInterfaceString(*name));
772 }
773 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
774 out.add_type(Atomic::TInterfaceString(None));
775 }
776 _ => {}
777 }
778 }
779 out
780 }
781
782 pub fn merge(a: &Type, b: &Type) -> Type {
787 if b.types.is_empty() {
789 let mut result = a.clone();
790 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
791 return result;
792 }
793 if a.types.is_empty() {
795 let mut result = b.clone();
796 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
797 return result;
798 }
799 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
801 let mut result = a.clone();
802 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
803 return result;
804 }
805 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
807 return Type {
808 types: smallvec::smallvec![Atomic::TMixed],
809 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
810 from_docblock: a.from_docblock || b.from_docblock,
811 };
812 }
813 let mut result = a.clone();
814 result.merge_with(b);
815 result
816 }
817
818 pub fn merge_with(&mut self, other: &Type) {
820 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
821 self.possibly_undefined |= other.possibly_undefined;
822 return;
823 }
824 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
825 self.types.clear();
826 self.types.push(Atomic::TMixed);
827 self.possibly_undefined |= other.possibly_undefined;
828 return;
829 }
830 for atomic in &other.types {
831 self.add_type(atomic.clone());
832 }
833 self.possibly_undefined |= other.possibly_undefined;
834 }
835
836 pub fn intersect_with(&self, other: &Type) -> Type {
840 if self.is_mixed() {
841 return other.clone();
842 }
843 if other.is_mixed() {
844 return self.clone();
845 }
846 let mut result = Type::empty();
854 for a in &self.types {
855 for b in &other.types {
856 if a == b {
857 result.add_type(a.clone());
858 } else if atomic_subtype(b, a) {
859 result.add_type(b.clone());
860 } else if atomic_subtype(a, b) {
861 result.add_type(a.clone());
862 }
863 }
864 }
865 if result.is_empty() {
866 Type::never()
867 } else {
868 result
869 }
870 }
871
872 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
876 if bindings.is_empty() {
877 return self.clone();
878 }
879 let mut result = Type::empty();
880 result.possibly_undefined = self.possibly_undefined;
881 result.from_docblock = self.from_docblock;
882 for atomic in &self.types {
883 match atomic {
884 Atomic::TTemplateParam { name, .. } => {
885 if let Some(resolved) = bindings.get(name) {
886 for t in &resolved.types {
887 result.add_type(t.clone());
888 }
889 } else {
890 result.add_type(atomic.clone());
891 }
892 }
893 Atomic::TArray { key, value } => {
894 result.add_type(Atomic::TArray {
895 key: Box::new(key.substitute_templates(bindings)),
896 value: Box::new(value.substitute_templates(bindings)),
897 });
898 }
899 Atomic::TList { value } => {
900 result.add_type(Atomic::TList {
901 value: Box::new(value.substitute_templates(bindings)),
902 });
903 }
904 Atomic::TNonEmptyArray { key, value } => {
905 result.add_type(Atomic::TNonEmptyArray {
906 key: Box::new(key.substitute_templates(bindings)),
907 value: Box::new(value.substitute_templates(bindings)),
908 });
909 }
910 Atomic::TNonEmptyList { value } => {
911 result.add_type(Atomic::TNonEmptyList {
912 value: Box::new(value.substitute_templates(bindings)),
913 });
914 }
915 Atomic::TKeyedArray {
916 properties,
917 is_open,
918 is_list,
919 } => {
920 use crate::atomic::KeyedProperty;
921 let new_props = properties
922 .iter()
923 .map(|(k, prop)| {
924 (
925 k.clone(),
926 KeyedProperty {
927 ty: prop.ty.substitute_templates(bindings),
928 optional: prop.optional,
929 },
930 )
931 })
932 .collect();
933 result.add_type(Atomic::TKeyedArray {
934 properties: new_props,
935 is_open: *is_open,
936 is_list: *is_list,
937 });
938 }
939 Atomic::TCallable {
940 params,
941 return_type,
942 } => {
943 result.add_type(Atomic::TCallable {
944 params: params.as_ref().map(|ps| {
945 ps.iter()
946 .map(|p| substitute_in_fn_param(p, bindings))
947 .collect()
948 }),
949 return_type: return_type
950 .as_ref()
951 .map(|r| Box::new(r.substitute_templates(bindings))),
952 });
953 }
954 Atomic::TClosure {
955 params,
956 return_type,
957 this_type,
958 } => {
959 result.add_type(Atomic::TClosure {
960 params: params
961 .iter()
962 .map(|p| substitute_in_fn_param(p, bindings))
963 .collect(),
964 return_type: Box::new(return_type.substitute_templates(bindings)),
965 this_type: this_type
966 .as_ref()
967 .map(|t| Box::new(t.substitute_templates(bindings))),
968 });
969 }
970 Atomic::TConditional {
971 param_name,
972 subject,
973 if_true,
974 if_false,
975 } => {
976 let new_subject = subject.substitute_templates(bindings);
977 let new_if_true = if_true.substitute_templates(bindings);
978 let new_if_false = if_false.substitute_templates(bindings);
979
980 let resolved = if let Some(name) = param_name {
984 if let Some(bound) = bindings.get(name) {
985 if new_subject.types.len() == 1 {
986 resolve_conditional_branch(
987 &new_subject.types[0],
988 bound,
989 &new_if_true,
990 &new_if_false,
991 )
992 } else {
993 None
994 }
995 } else {
996 None
997 }
998 } else {
999 None
1000 };
1001
1002 if let Some(branch) = resolved {
1003 for t in branch.types {
1004 result.add_type(t);
1005 }
1006 } else {
1007 result.add_type(Atomic::TConditional {
1008 param_name: *param_name,
1009 subject: Box::new(new_subject),
1010 if_true: Box::new(new_if_true),
1011 if_false: Box::new(new_if_false),
1012 });
1013 }
1014 }
1015 Atomic::TIntersection { parts } => {
1016 result.add_type(Atomic::TIntersection {
1017 parts: vec_to_type_params(
1018 parts
1019 .iter()
1020 .map(|p| p.substitute_templates(bindings))
1021 .collect(),
1022 ),
1023 });
1024 }
1025 Atomic::TNamedObject { fqcn, type_params } => {
1026 if type_params.is_empty() && !fqcn.contains('\\') {
1033 if let Some(resolved) = bindings.get(fqcn) {
1034 for t in &resolved.types {
1035 result.add_type(t.clone());
1036 }
1037 continue;
1038 }
1039 }
1040 let new_params: Vec<Type> = type_params
1041 .iter()
1042 .map(|p| p.substitute_templates(bindings))
1043 .collect();
1044 result.add_type(Atomic::TNamedObject {
1045 fqcn: *fqcn,
1046 type_params: vec_to_type_params(new_params),
1047 });
1048 }
1049 Atomic::TClassString(Some(param_name)) => {
1051 if let Some(resolved) = bindings.get(param_name) {
1052 for r_atomic in &resolved.types {
1053 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1054 Some(*fqcn)
1055 } else {
1056 None
1057 };
1058 result.add_type(Atomic::TClassString(cls_name));
1059 }
1060 } else {
1061 result.add_type(atomic.clone());
1062 }
1063 }
1064 Atomic::TInterfaceString(Some(param_name)) => {
1066 if let Some(resolved) = bindings.get(param_name) {
1067 for r_atomic in &resolved.types {
1068 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1069 Some(*fqcn)
1070 } else {
1071 None
1072 };
1073 result.add_type(Atomic::TInterfaceString(iface_name));
1074 }
1075 } else {
1076 result.add_type(atomic.clone());
1077 }
1078 }
1079 _ => {
1080 result.add_type(atomic.clone());
1081 }
1082 }
1083 }
1084 result
1085 }
1086
1087 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1093 where
1094 F: Fn(&str) -> Option<Type>,
1095 {
1096 self.resolve_conditional_inner(&lookup)
1097 }
1098
1099 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1100 where
1101 F: Fn(&str) -> Option<Type>,
1102 {
1103 let mut result = Type::empty();
1104 for atomic in self.types {
1105 match atomic {
1106 Atomic::TConditional {
1107 ref param_name,
1108 ref subject,
1109 ref if_true,
1110 ref if_false,
1111 } => {
1112 let resolved = if subject.types.len() == 1 {
1113 if let Some(name) = param_name {
1114 if let Some(arg_ty) = lookup(name.as_ref()) {
1115 resolve_conditional_branch(
1116 &subject.types[0],
1117 &arg_ty,
1118 if_true,
1119 if_false,
1120 )
1121 } else {
1122 None
1123 }
1124 } else {
1125 None
1126 }
1127 } else {
1128 None
1129 };
1130
1131 if let Some(branch) = resolved {
1132 for t in branch.resolve_conditional_inner(lookup).types {
1134 result.add_type(t);
1135 }
1136 } else {
1137 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1140 result.add_type(t);
1141 }
1142 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1143 result.add_type(t);
1144 }
1145 }
1146 }
1147 other => result.add_type(other),
1148 }
1149 }
1150 result
1151 }
1152
1153 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1163 if other.is_mixed() {
1164 return true;
1165 }
1166 if self.is_never() {
1167 return true; }
1169 self.types
1170 .iter()
1171 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1172 }
1173
1174 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1177 let mut result = Type::empty();
1178 result.possibly_undefined = self.possibly_undefined;
1179 result.from_docblock = self.from_docblock;
1180 for atomic in &self.types {
1181 if f(atomic) {
1182 result.types.push(atomic.clone());
1183 }
1184 }
1185 result
1186 }
1187
1188 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1193 &self,
1194 keep: K,
1195 placeholder: P,
1196 replacement: Atomic,
1197 ) -> Type {
1198 let mut result = Type::empty();
1199 result.possibly_undefined = self.possibly_undefined;
1200 result.from_docblock = self.from_docblock;
1201 for atomic in &self.types {
1202 if keep(atomic) {
1203 result.add_type(atomic.clone());
1204 } else if placeholder(atomic) {
1205 result.add_type(replacement.clone());
1206 }
1207 }
1208 result
1209 }
1210
1211 pub fn possibly_undefined(mut self) -> Self {
1213 self.possibly_undefined = true;
1214 self
1215 }
1216
1217 pub fn from_docblock(mut self) -> Self {
1219 self.from_docblock = true;
1220 self
1221 }
1222}
1223
1224fn is_string_atomic(a: &Atomic) -> bool {
1229 matches!(
1230 a,
1231 Atomic::TString
1232 | Atomic::TNonEmptyString
1233 | Atomic::TLiteralString(_)
1234 | Atomic::TNumericString
1235 | Atomic::TClassString(_)
1236 | Atomic::TInterfaceString(_)
1237 | Atomic::TCallableString
1238 )
1239}
1240
1241fn is_array_atomic(a: &Atomic) -> bool {
1242 matches!(
1243 a,
1244 Atomic::TArray { .. }
1245 | Atomic::TNonEmptyArray { .. }
1246 | Atomic::TKeyedArray { .. }
1247 | Atomic::TList { .. }
1248 | Atomic::TNonEmptyList { .. }
1249 )
1250}
1251
1252fn is_list_atomic(a: &Atomic) -> bool {
1253 match a {
1254 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1255 Atomic::TKeyedArray { is_list, .. } => *is_list,
1256 _ => false,
1257 }
1258}
1259
1260fn is_float_atomic(a: &Atomic) -> bool {
1261 matches!(
1262 a,
1263 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..)
1264 )
1265}
1266
1267fn is_bool_atomic(a: &Atomic) -> bool {
1268 matches!(a, Atomic::TBool | Atomic::TTrue | Atomic::TFalse)
1269}
1270
1271fn resolve_conditional_branch(
1277 subject: &Atomic,
1278 arg_ty: &Type,
1279 if_true: &Type,
1280 if_false: &Type,
1281) -> Option<Type> {
1282 let predicate: fn(&Atomic) -> bool = match subject {
1283 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1284 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1285 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1286 Atomic::TString => is_string_atomic,
1287 Atomic::TList { .. } => is_list_atomic,
1288 Atomic::TArray { .. } => is_array_atomic,
1289 Atomic::TInt => Atomic::is_int,
1290 Atomic::TFloat => is_float_atomic,
1291 Atomic::TBool => is_bool_atomic,
1292 _ => return None,
1293 };
1294
1295 if arg_ty.types.is_empty() {
1296 return None;
1297 }
1298 let all_match = arg_ty.types.iter().all(&predicate);
1299 let none_match = !arg_ty.types.iter().any(predicate);
1300 if all_match {
1301 Some(if_true.clone())
1302 } else if none_match {
1303 Some(if_false.clone())
1304 } else {
1305 None
1306 }
1307}
1308
1309fn substitute_in_fn_param(
1314 p: &crate::atomic::FnParam,
1315 bindings: &FxHashMap<Name, Type>,
1316) -> crate::atomic::FnParam {
1317 crate::atomic::FnParam {
1318 name: p.name,
1319 ty: p.ty.as_ref().map(|t| {
1320 let u = t.to_union();
1321 let substituted = u.substitute_templates(bindings);
1322 crate::compact::SimpleType::from_union(substituted)
1323 }),
1324 out_ty: p.out_ty.as_ref().map(|t| {
1325 let u = t.to_union();
1326 let substituted = u.substitute_templates(bindings);
1327 crate::compact::SimpleType::from_union(substituted)
1328 }),
1329 default: p.default.as_ref().map(|d| {
1330 let u = d.to_union();
1331 let substituted = u.substitute_templates(bindings);
1332 crate::compact::SimpleType::from_union(substituted)
1333 }),
1334 is_variadic: p.is_variadic,
1335 is_byref: p.is_byref,
1336 is_optional: p.is_optional,
1337 }
1338}
1339
1340fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1345 if sub == sup {
1346 return true;
1347 }
1348 match (sub, sup) {
1349 (Atomic::TNever, _) => true,
1351 (_, Atomic::TMixed) => true,
1353 (Atomic::TMixed, _) => true,
1354 (_, Atomic::TTemplateParam { as_type, .. }) => {
1359 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1360 }
1361
1362 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1364 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1365 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1366 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1367 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1368 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1369 (Atomic::TPositiveInt, Atomic::TInt) => true,
1370 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1371 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1372 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1373 (Atomic::TNegativeInt, Atomic::TInt) => true,
1374 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1375 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1376 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1377 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1378 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1379 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1380 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1381 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1382 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1384 max.is_none() && min.is_none_or(|m| m <= 1)
1385 }
1386 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1388 min.is_none() && max.is_none_or(|m| m >= -1)
1389 }
1390 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1392 max.is_none() && min.is_none_or(|m| m <= 0)
1393 }
1394 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1396 sub_min.is_some_and(|lo| lo >= 1)
1397 }
1398 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1399 sub_min.is_some_and(|lo| lo >= 0)
1400 }
1401 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1402 sub_max.is_some_and(|hi| hi <= -1)
1403 }
1404 (
1406 Atomic::TIntRange {
1407 min: sub_min,
1408 max: sub_max,
1409 },
1410 Atomic::TIntRange {
1411 min: sup_min,
1412 max: sup_max,
1413 },
1414 ) => {
1415 let lower_ok = match (sub_min, sup_min) {
1416 (_, None) => true,
1417 (None, Some(_)) => false,
1418 (Some(sl), Some(su)) => sl >= su,
1419 };
1420 let upper_ok = match (sub_max, sup_max) {
1421 (None, None) | (Some(_), None) => true,
1422 (None, Some(_)) => false,
1423 (Some(sl), Some(su)) => sl <= su,
1424 };
1425 lower_ok && upper_ok
1426 }
1427
1428 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1429 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1430 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1431
1432 (Atomic::TLiteralString(s), Atomic::TString) => {
1433 let _ = s;
1434 true
1435 }
1436 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1437 let _ = s;
1438 true
1439 }
1440 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1441 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1442 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1445 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1448 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1449 (Atomic::TNonEmptyString, Atomic::TString) => true,
1450 (Atomic::TCallableString, Atomic::TString) => true,
1451 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1453 (Atomic::TNumericString, Atomic::TString) => true,
1454 (Atomic::TClassString(_), Atomic::TString) => true,
1455 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1456 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1461 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1462 (Atomic::TEnumString, Atomic::TString) => true,
1463 (Atomic::TTraitString, Atomic::TString) => true,
1464
1465 (Atomic::TTrue, Atomic::TBool) => true,
1466 (Atomic::TFalse, Atomic::TBool) => true,
1467
1468 (Atomic::TInt, Atomic::TNumeric) => true,
1469 (Atomic::TFloat, Atomic::TNumeric) => true,
1470 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1471 (Atomic::TNumericString, Atomic::TNumeric) => true,
1472
1473 (Atomic::TInt, Atomic::TScalar) => true,
1474 (Atomic::TFloat, Atomic::TScalar) => true,
1475 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1476 (Atomic::TString, Atomic::TScalar) => true,
1477 (Atomic::TBool, Atomic::TScalar) => true,
1478 (Atomic::TNumeric, Atomic::TScalar) => true,
1479 (Atomic::TTrue, Atomic::TScalar) => true,
1480 (Atomic::TFalse, Atomic::TScalar) => true,
1481
1482 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1484 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1485 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1486 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1488 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1489 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1491 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1492 (
1496 Atomic::TNamedObject {
1497 fqcn: sub_fqcn,
1498 type_params: sub_params,
1499 },
1500 Atomic::TNamedObject {
1501 fqcn: sup_fqcn,
1502 type_params: sup_params,
1503 },
1504 ) => {
1505 sub_fqcn == sup_fqcn
1506 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1507 }
1508
1509 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1511
1512 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1514 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1515 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1516 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1517 (Atomic::TInt, Atomic::TFloat) => true,
1518 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1519
1520 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1522 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1523 }
1524
1525 (Atomic::TString, Atomic::TCallable { .. }) => true,
1527 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1528 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1529 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1530 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1531 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1532
1533 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1535 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1537 (Atomic::TClosure { .. }, Atomic::TClosure { .. }) => true,
1539 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1541 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1543 fqcn.as_ref().eq_ignore_ascii_case("closure")
1544 }
1545 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1546 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1548 fqcn.as_ref().eq_ignore_ascii_case("closure")
1549 }
1550 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1552 fqcn.as_ref().eq_ignore_ascii_case("closure")
1553 }
1554
1555 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1557 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1558 }
1559 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1560 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1561 }
1562 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1563 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1564 }
1565 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1566 value.is_subtype_structural(lv)
1567 }
1568 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1570 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1571 && av.is_subtype_structural(lv)
1572 }
1573 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1574 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1575 && av.is_subtype_structural(lv)
1576 }
1577 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1578 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1579 && av.is_subtype_structural(lv)
1580 }
1581 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1582 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1583 && av.is_subtype_structural(lv)
1584 }
1585 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1587 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1588 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1589 }
1590
1591 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1593 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1594 }
1595
1596 (
1602 Atomic::TKeyedArray {
1603 properties,
1604 is_open,
1605 ..
1606 },
1607 Atomic::TArray { key, value },
1608 ) => {
1609 if *is_open {
1610 return true;
1611 }
1612 properties.iter().all(|(prop_key, prop)| {
1613 let key_atomic = match prop_key {
1614 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1615 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1616 };
1617 if !Type::single(key_atomic).is_subtype_structural(key) {
1618 return false; }
1620 let has_named_obj = prop.ty.types.iter().any(|a| {
1622 matches!(
1623 a,
1624 Atomic::TNamedObject { .. }
1625 | Atomic::TSelf { .. }
1626 | Atomic::TStaticObject { .. }
1627 | Atomic::TClosure { .. }
1628 | Atomic::TTemplateParam { .. }
1629 )
1630 });
1631 has_named_obj || prop.ty.is_subtype_structural(value)
1632 })
1633 }
1634 (
1635 Atomic::TKeyedArray {
1636 properties,
1637 is_open,
1638 ..
1639 },
1640 Atomic::TNonEmptyArray { key, value },
1641 ) => {
1642 if *is_open {
1643 return !properties.is_empty();
1644 }
1645 properties.iter().any(|(_, p)| !p.optional)
1646 && properties.iter().all(|(prop_key, prop)| {
1647 let key_atomic = match prop_key {
1648 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1649 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1650 };
1651 if !Type::single(key_atomic).is_subtype_structural(key) {
1652 return false;
1653 }
1654 let has_named_obj = prop.ty.types.iter().any(|a| {
1655 matches!(
1656 a,
1657 Atomic::TNamedObject { .. }
1658 | Atomic::TSelf { .. }
1659 | Atomic::TStaticObject { .. }
1660 | Atomic::TClosure { .. }
1661 | Atomic::TTemplateParam { .. }
1662 )
1663 });
1664 has_named_obj || prop.ty.is_subtype_structural(value)
1665 })
1666 }
1667
1668 (
1670 Atomic::TKeyedArray {
1671 properties,
1672 is_list,
1673 ..
1674 },
1675 Atomic::TList { value: lv },
1676 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1677 (
1678 Atomic::TKeyedArray {
1679 properties,
1680 is_list,
1681 ..
1682 },
1683 Atomic::TNonEmptyList { value: lv },
1684 ) => {
1685 *is_list
1686 && !properties.is_empty()
1687 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1688 }
1689
1690 _ => false,
1691 }
1692}
1693
1694fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
1700 if sub.len() != sup.len() {
1701 return false;
1702 }
1703 sub.iter()
1704 .zip(sup.iter())
1705 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
1706}
1707
1708fn is_empty_array_literal(t: &Type) -> bool {
1711 !t.types.is_empty()
1712 && t.types.iter().all(
1713 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
1714 )
1715}
1716
1717fn is_array_like(t: &Type) -> bool {
1719 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
1720}
1721
1722#[cfg(test)]
1727mod tests {
1728 use std::sync::Arc;
1729
1730 use super::*;
1731
1732 #[test]
1733 fn single_is_single() {
1734 let u = Type::single(Atomic::TString);
1735 assert!(u.is_single());
1736 assert!(!u.is_nullable());
1737 }
1738
1739 #[test]
1740 fn nullable_has_null() {
1741 let u = Type::nullable(Atomic::TString);
1742 assert!(u.is_nullable());
1743 assert_eq!(u.types.len(), 2);
1744 }
1745
1746 #[test]
1747 fn add_type_deduplicates() {
1748 let mut u = Type::single(Atomic::TString);
1749 u.add_type(Atomic::TString);
1750 assert_eq!(u.types.len(), 1);
1751 }
1752
1753 #[test]
1754 fn add_type_literal_subsumed_by_base() {
1755 let mut u = Type::single(Atomic::TInt);
1756 u.add_type(Atomic::TLiteralInt(42));
1757 assert_eq!(u.types.len(), 1);
1758 assert!(matches!(u.types[0], Atomic::TInt));
1759 }
1760
1761 #[test]
1762 fn add_type_base_widens_literals() {
1763 let mut u = Type::single(Atomic::TLiteralInt(1));
1764 u.add_type(Atomic::TLiteralInt(2));
1765 u.add_type(Atomic::TInt);
1766 assert_eq!(u.types.len(), 1);
1767 assert!(matches!(u.types[0], Atomic::TInt));
1768 }
1769
1770 #[test]
1771 fn mixed_subsumes_everything() {
1772 let mut u = Type::single(Atomic::TString);
1773 u.add_type(Atomic::TMixed);
1774 assert_eq!(u.types.len(), 1);
1775 assert!(u.is_mixed());
1776 }
1777
1778 #[test]
1779 fn remove_null() {
1780 let u = Type::nullable(Atomic::TString);
1781 let narrowed = u.remove_null();
1782 assert!(!narrowed.is_nullable());
1783 assert_eq!(narrowed.types.len(), 1);
1784 }
1785
1786 #[test]
1787 fn narrow_to_truthy_removes_null_false() {
1788 let mut u = Type::empty();
1789 u.add_type(Atomic::TString);
1790 u.add_type(Atomic::TNull);
1791 u.add_type(Atomic::TFalse);
1792 let truthy = u.narrow_to_truthy();
1793 assert!(!truthy.is_nullable());
1794 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
1795 }
1796
1797 #[test]
1798 fn merge_combines_types() {
1799 let a = Type::single(Atomic::TString);
1800 let b = Type::single(Atomic::TInt);
1801 let merged = Type::merge(&a, &b);
1802 assert_eq!(merged.types.len(), 2);
1803 }
1804
1805 #[test]
1806 fn intersect_keeps_narrower_side_not_self() {
1807 let int_ty = Type::single(Atomic::TInt);
1811 let mut literals = Type::empty();
1812 literals.add_type(Atomic::TLiteralInt(1));
1813 literals.add_type(Atomic::TLiteralInt(2));
1814
1815 let narrowed = int_ty.intersect_with(&literals);
1816 assert_eq!(narrowed.types.len(), 2);
1817 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
1818 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
1819 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
1820 }
1821
1822 #[test]
1823 fn subtype_literal_int_under_int() {
1824 let sub = Type::single(Atomic::TLiteralInt(5));
1825 let sup = Type::single(Atomic::TInt);
1826 assert!(sub.is_subtype_structural(&sup));
1827 }
1828
1829 #[test]
1830 fn subtype_never_is_bottom() {
1831 let never = Type::never();
1832 let string = Type::single(Atomic::TString);
1833 assert!(never.is_subtype_structural(&string));
1834 }
1835
1836 #[test]
1837 fn subtype_everything_under_mixed() {
1838 let string = Type::single(Atomic::TString);
1839 let mixed = Type::mixed();
1840 assert!(string.is_subtype_structural(&mixed));
1841 }
1842
1843 #[test]
1844 fn template_substitution() {
1845 let mut bindings = FxHashMap::default();
1846 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
1847
1848 let tmpl = Type::single(Atomic::TTemplateParam {
1849 name: Name::new("T"),
1850 as_type: Box::new(Type::mixed()),
1851 defining_entity: Name::new("MyClass"),
1852 });
1853
1854 let resolved = tmpl.substitute_templates(&bindings);
1855 assert_eq!(resolved.types.len(), 1);
1856 assert!(matches!(resolved.types[0], Atomic::TString));
1857 }
1858
1859 #[test]
1860 fn intersection_is_object() {
1861 let parts = vec![
1862 Type::single(Atomic::TNamedObject {
1863 fqcn: Name::new("Iterator"),
1864 type_params: empty_type_params(),
1865 }),
1866 Type::single(Atomic::TNamedObject {
1867 fqcn: Name::new("Countable"),
1868 type_params: empty_type_params(),
1869 }),
1870 ];
1871 let atomic = Atomic::TIntersection {
1872 parts: vec_to_type_params(parts),
1873 };
1874 assert!(atomic.is_object());
1875 assert!(!atomic.can_be_falsy());
1876 assert!(atomic.can_be_truthy());
1877 }
1878
1879 #[test]
1880 fn intersection_display_two_parts() {
1881 let parts = vec![
1882 Type::single(Atomic::TNamedObject {
1883 fqcn: Name::new("Iterator"),
1884 type_params: empty_type_params(),
1885 }),
1886 Type::single(Atomic::TNamedObject {
1887 fqcn: Name::new("Countable"),
1888 type_params: empty_type_params(),
1889 }),
1890 ];
1891 let u = Type::single(Atomic::TIntersection {
1892 parts: vec_to_type_params(parts),
1893 });
1894 assert_eq!(format!("{u}"), "Iterator&Countable");
1895 }
1896
1897 #[test]
1898 fn intersection_display_three_parts() {
1899 let parts = vec![
1900 Type::single(Atomic::TNamedObject {
1901 fqcn: Name::new("A"),
1902 type_params: empty_type_params(),
1903 }),
1904 Type::single(Atomic::TNamedObject {
1905 fqcn: Name::new("B"),
1906 type_params: empty_type_params(),
1907 }),
1908 Type::single(Atomic::TNamedObject {
1909 fqcn: Name::new("C"),
1910 type_params: empty_type_params(),
1911 }),
1912 ];
1913 let u = Type::single(Atomic::TIntersection {
1914 parts: vec_to_type_params(parts),
1915 });
1916 assert_eq!(format!("{u}"), "A&B&C");
1917 }
1918
1919 #[test]
1920 fn intersection_in_nullable_union_display() {
1921 let intersection = Atomic::TIntersection {
1922 parts: vec_to_type_params(vec![
1923 Type::single(Atomic::TNamedObject {
1924 fqcn: Name::new("Iterator"),
1925 type_params: empty_type_params(),
1926 }),
1927 Type::single(Atomic::TNamedObject {
1928 fqcn: Name::new("Countable"),
1929 type_params: empty_type_params(),
1930 }),
1931 ]),
1932 };
1933 let mut u = Type::single(intersection);
1934 u.add_type(Atomic::TNull);
1935 assert!(u.is_nullable());
1936 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
1937 }
1938
1939 fn t_param(name: &str) -> Type {
1942 Type::single(Atomic::TTemplateParam {
1943 name: Name::new(name),
1944 as_type: Box::new(Type::mixed()),
1945 defining_entity: Name::new("Fn"),
1946 })
1947 }
1948
1949 fn bindings_t_string() -> FxHashMap<Name, Type> {
1950 let mut b = FxHashMap::default();
1951 b.insert(Name::new("T"), Type::single(Atomic::TString));
1952 b
1953 }
1954
1955 #[test]
1956 fn substitute_non_empty_array_key_and_value() {
1957 let ty = Type::single(Atomic::TNonEmptyArray {
1958 key: Box::new(t_param("T")),
1959 value: Box::new(t_param("T")),
1960 });
1961 let result = ty.substitute_templates(&bindings_t_string());
1962 assert_eq!(result.types.len(), 1);
1963 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
1964 panic!("expected TNonEmptyArray");
1965 };
1966 assert!(matches!(key.types[0], Atomic::TString));
1967 assert!(matches!(value.types[0], Atomic::TString));
1968 }
1969
1970 #[test]
1971 fn substitute_non_empty_list_value() {
1972 let ty = Type::single(Atomic::TNonEmptyList {
1973 value: Box::new(t_param("T")),
1974 });
1975 let result = ty.substitute_templates(&bindings_t_string());
1976 let Atomic::TNonEmptyList { value } = &result.types[0] else {
1977 panic!("expected TNonEmptyList");
1978 };
1979 assert!(matches!(value.types[0], Atomic::TString));
1980 }
1981
1982 #[test]
1983 fn substitute_keyed_array_property_types() {
1984 use crate::atomic::{ArrayKey, KeyedProperty};
1985 use indexmap::IndexMap;
1986 let mut props = IndexMap::new();
1987 props.insert(
1988 ArrayKey::String(Arc::from("name")),
1989 KeyedProperty {
1990 ty: t_param("T"),
1991 optional: false,
1992 },
1993 );
1994 props.insert(
1995 ArrayKey::String(Arc::from("tag")),
1996 KeyedProperty {
1997 ty: t_param("T"),
1998 optional: true,
1999 },
2000 );
2001 let ty = Type::single(Atomic::TKeyedArray {
2002 properties: props,
2003 is_open: true,
2004 is_list: false,
2005 });
2006 let result = ty.substitute_templates(&bindings_t_string());
2007 let Atomic::TKeyedArray {
2008 properties,
2009 is_open,
2010 is_list,
2011 } = &result.types[0]
2012 else {
2013 panic!("expected TKeyedArray");
2014 };
2015 assert!(is_open);
2016 assert!(!is_list);
2017 assert!(matches!(
2018 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2019 Atomic::TString
2020 ));
2021 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2022 assert!(matches!(
2023 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2024 Atomic::TString
2025 ));
2026 }
2027
2028 #[test]
2029 fn substitute_callable_params_and_return() {
2030 use crate::atomic::FnParam;
2031 let ty = Type::single(Atomic::TCallable {
2032 params: Some(vec![FnParam {
2033 name: Name::new("x"),
2034 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2035 out_ty: None,
2036 default: None,
2037 is_variadic: false,
2038 is_byref: false,
2039 is_optional: false,
2040 }]),
2041 return_type: Some(Box::new(t_param("T"))),
2042 });
2043 let result = ty.substitute_templates(&bindings_t_string());
2044 let Atomic::TCallable {
2045 params,
2046 return_type,
2047 } = &result.types[0]
2048 else {
2049 panic!("expected TCallable");
2050 };
2051 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2052 let param_union = param_ty.to_union();
2053 assert!(matches!(param_union.types[0], Atomic::TString));
2054 let ret = return_type.as_ref().unwrap();
2055 assert!(matches!(ret.types[0], Atomic::TString));
2056 }
2057
2058 #[test]
2059 fn substitute_callable_bare_no_panic() {
2060 let ty = Type::single(Atomic::TCallable {
2062 params: None,
2063 return_type: None,
2064 });
2065 let result = ty.substitute_templates(&bindings_t_string());
2066 assert!(matches!(
2067 result.types[0],
2068 Atomic::TCallable {
2069 params: None,
2070 return_type: None
2071 }
2072 ));
2073 }
2074
2075 #[test]
2076 fn substitute_closure_params_return_and_this() {
2077 use crate::atomic::FnParam;
2078 let ty = Type::single(Atomic::TClosure {
2079 params: vec![FnParam {
2080 name: Name::new("a"),
2081 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2082 out_ty: None,
2083 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2084 is_variadic: true,
2085 is_byref: true,
2086 is_optional: true,
2087 }],
2088 return_type: Box::new(t_param("T")),
2089 this_type: Some(Box::new(t_param("T"))),
2090 });
2091 let result = ty.substitute_templates(&bindings_t_string());
2092 let Atomic::TClosure {
2093 params,
2094 return_type,
2095 this_type,
2096 } = &result.types[0]
2097 else {
2098 panic!("expected TClosure");
2099 };
2100 let p = ¶ms[0];
2101 let ty_union = p.ty.as_ref().unwrap().to_union();
2102 let default_union = p.default.as_ref().unwrap().to_union();
2103 assert!(matches!(ty_union.types[0], Atomic::TString));
2104 assert!(matches!(default_union.types[0], Atomic::TString));
2105 assert!(p.is_variadic);
2107 assert!(p.is_byref);
2108 assert!(p.is_optional);
2109 assert!(matches!(return_type.types[0], Atomic::TString));
2110 assert!(matches!(
2111 this_type.as_ref().unwrap().types[0],
2112 Atomic::TString
2113 ));
2114 }
2115
2116 #[test]
2117 fn substitute_conditional_all_branches() {
2118 let ty = Type::single(Atomic::TConditional {
2119 param_name: None,
2120 subject: Box::new(t_param("T")),
2121 if_true: Box::new(t_param("T")),
2122 if_false: Box::new(Type::single(Atomic::TInt)),
2123 });
2124 let result = ty.substitute_templates(&bindings_t_string());
2125 let Atomic::TConditional {
2126 param_name: _,
2127 subject,
2128 if_true,
2129 if_false,
2130 } = &result.types[0]
2131 else {
2132 panic!("expected TConditional");
2133 };
2134 assert!(matches!(subject.types[0], Atomic::TString));
2135 assert!(matches!(if_true.types[0], Atomic::TString));
2136 assert!(matches!(if_false.types[0], Atomic::TInt));
2137 }
2138
2139 #[test]
2140 fn resolve_conditional_is_null_non_null_arg() {
2141 let ty = Type::single(Atomic::TConditional {
2142 param_name: Some(Name::new("x")),
2143 subject: Box::new(Type::single(Atomic::TNull)),
2144 if_true: Box::new(Type::single(Atomic::TInt)),
2145 if_false: Box::new(Type::single(Atomic::TString)),
2146 });
2147 let result = ty.resolve_conditional_returns(|name| {
2148 if name == "x" {
2149 Some(Type::single(Atomic::TString)) } else {
2151 None
2152 }
2153 });
2154 assert!(result.types.len() == 1);
2155 assert!(matches!(result.types[0], Atomic::TString));
2156 }
2157
2158 #[test]
2159 fn resolve_conditional_is_null_null_arg() {
2160 let ty = Type::single(Atomic::TConditional {
2161 param_name: Some(Name::new("x")),
2162 subject: Box::new(Type::single(Atomic::TNull)),
2163 if_true: Box::new(Type::single(Atomic::TInt)),
2164 if_false: Box::new(Type::single(Atomic::TString)),
2165 });
2166 let result = ty.resolve_conditional_returns(|name| {
2167 if name == "x" {
2168 Some(Type::single(Atomic::TNull)) } else {
2170 None
2171 }
2172 });
2173 assert!(result.types.len() == 1);
2174 assert!(matches!(result.types[0], Atomic::TInt));
2175 }
2176
2177 #[test]
2178 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2179 let mut nullable_str = Type::single(Atomic::TString);
2180 nullable_str.add_type(Atomic::TNull);
2181 let ty = Type::single(Atomic::TConditional {
2182 param_name: Some(Name::new("x")),
2183 subject: Box::new(Type::single(Atomic::TNull)),
2184 if_true: Box::new(Type::single(Atomic::TInt)),
2185 if_false: Box::new(Type::single(Atomic::TString)),
2186 });
2187 let result = ty.resolve_conditional_returns(|name| {
2188 if name == "x" {
2189 Some(nullable_str.clone())
2190 } else {
2191 None
2192 }
2193 });
2194 assert_eq!(result.types.len(), 2);
2196 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2197 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2198 }
2199
2200 #[test]
2201 fn resolve_conditional_nested_widens_inner_branch() {
2202 let inner = Type::single(Atomic::TConditional {
2205 param_name: Some(Name::new("x")),
2206 subject: Box::new(Type::single(Atomic::TString)),
2207 if_true: Box::new(Type::single(Atomic::TString)),
2208 if_false: Box::new(Type::single(Atomic::TFloat)),
2209 });
2210 let ty = Type::single(Atomic::TConditional {
2211 param_name: Some(Name::new("x")),
2212 subject: Box::new(Type::single(Atomic::TNull)),
2213 if_true: Box::new(Type::single(Atomic::TInt)),
2214 if_false: Box::new(inner),
2215 });
2216 let result = ty.resolve_conditional_returns(|_| None);
2218 assert!(
2219 result
2220 .types
2221 .iter()
2222 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2223 "no TConditional should survive: {:?}",
2224 result.types
2225 );
2226 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2227 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2228 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2229 }
2230
2231 #[test]
2232 fn resolve_conditional_nested_resolves_inner_branch() {
2233 let inner = Type::single(Atomic::TConditional {
2237 param_name: Some(Name::new("x")),
2238 subject: Box::new(Type::single(Atomic::TString)),
2239 if_true: Box::new(Type::single(Atomic::TString)),
2240 if_false: Box::new(Type::single(Atomic::TFloat)),
2241 });
2242 let ty = Type::single(Atomic::TConditional {
2243 param_name: Some(Name::new("x")),
2244 subject: Box::new(Type::single(Atomic::TNull)),
2245 if_true: Box::new(Type::single(Atomic::TInt)),
2246 if_false: Box::new(inner),
2247 });
2248 let result = ty.resolve_conditional_returns(|name| {
2250 if name == "x" {
2251 Some(Type::single(Atomic::TString))
2252 } else {
2253 None
2254 }
2255 });
2256 assert!(
2257 result
2258 .types
2259 .iter()
2260 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2261 "no TConditional should survive: {:?}",
2262 result.types
2263 );
2264 assert_eq!(result.types.len(), 1);
2265 assert!(matches!(result.types[0], Atomic::TString));
2266 }
2267
2268 #[test]
2269 fn substitute_intersection_parts() {
2270 let ty = Type::single(Atomic::TIntersection {
2271 parts: vec_to_type_params(vec![
2272 Type::single(Atomic::TNamedObject {
2273 fqcn: Name::new("Countable"),
2274 type_params: empty_type_params(),
2275 }),
2276 t_param("T"),
2277 ]),
2278 });
2279 let result = ty.substitute_templates(&bindings_t_string());
2280 let Atomic::TIntersection { parts } = &result.types[0] else {
2281 panic!("expected TIntersection");
2282 };
2283 assert_eq!(parts.len(), 2);
2284 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2285 assert!(matches!(parts[1].types[0], Atomic::TString));
2286 }
2287
2288 #[test]
2289 fn substitute_no_template_params_identity() {
2290 let ty = Type::single(Atomic::TInt);
2291 let result = ty.substitute_templates(&bindings_t_string());
2292 assert!(matches!(result.types[0], Atomic::TInt));
2293 }
2294}