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 { data } = &atomic {
244 let (if_true, if_false) = (&data.if_true, &data.if_false);
245 let mut simplified_true = Type::empty();
246 for t in &if_true.types {
247 simplified_true.add_type(t.clone());
248 }
249 let mut simplified_false = Type::empty();
250 for t in &if_false.types {
251 simplified_false.add_type(t.clone());
252 }
253 if simplified_true == simplified_false {
254 for t in simplified_true.types {
255 self.add_type(t);
256 }
257 return;
258 }
259 atomic
260 } else {
261 atomic
262 };
263
264 if self.types.contains(&atomic) {
266 return;
267 }
268
269 if let Atomic::TLiteralInt(_) = &atomic {
271 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
272 return;
273 }
274 }
275 if let Atomic::TLiteralString(_) = &atomic {
277 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
278 return;
279 }
280 }
281 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
283 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
284 {
285 return;
286 }
287 if matches!(atomic, Atomic::TInt) {
289 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
290 }
291 if matches!(atomic, Atomic::TString) {
293 self.types
294 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
295 }
296 if matches!(atomic, Atomic::TBool) {
298 self.types
299 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
300 }
301
302 if matches!(atomic, Atomic::TNever) {
304 if !self.types.is_empty() {
305 return;
306 }
307 } else {
308 self.types.retain(|t| !matches!(t, Atomic::TNever));
309 }
310
311 if let Atomic::TKeyedArray { properties, .. } = &atomic {
314 if properties.is_empty() {
315 for existing in &self.types {
316 match existing {
317 Atomic::TArray { .. }
318 | Atomic::TNonEmptyArray { .. }
319 | Atomic::TList { .. }
320 | Atomic::TNonEmptyList { .. } => {
321 return; }
323 _ => {}
324 }
325 }
326 }
327 }
328
329 let is_generic_array_or_list = matches!(
331 &atomic,
332 Atomic::TArray { .. }
333 | Atomic::TNonEmptyArray { .. }
334 | Atomic::TList { .. }
335 | Atomic::TNonEmptyList { .. }
336 );
337 if is_generic_array_or_list {
338 self.types.retain(|t| {
339 if let Atomic::TKeyedArray { properties, .. } = t {
340 !properties.is_empty()
341 } else {
342 true
343 }
344 });
345 }
346
347 self.types.push(atomic);
348 }
349
350 pub fn remove_null(&self) -> Type {
354 self.filter(|t| !matches!(t, Atomic::TNull))
355 }
356
357 pub fn remove_false(&self) -> Type {
360 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
361 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
362 result.add_type(Atomic::TTrue);
363 }
364 result
365 }
366
367 pub fn core_type(&self) -> Type {
369 self.remove_null().remove_false()
370 }
371
372 pub fn narrow_to_truthy(&self) -> Type {
374 if self.is_mixed_not_template() {
375 return Type::mixed();
376 }
377 let mut result = Type::empty();
378 result.from_docblock = self.from_docblock;
379 for t in &self.types {
380 match t {
381 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
385 Atomic::TLiteralInt(0)
387 | Atomic::TLiteralFloat(0, 0)
388 | Atomic::TNull
389 | Atomic::TFalse => {}
390 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
391 Atomic::TBool => result.add_type(Atomic::TTrue),
393 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
395 key: key.clone(),
396 value: value.clone(),
397 }),
398 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
399 value: value.clone(),
400 }),
401 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
405 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
410 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
411 let atom = if max.is_none() {
412 Atomic::TPositiveInt
413 } else {
414 Atomic::TIntRange {
415 min: Some(1),
416 max: *max,
417 }
418 };
419 result.add_type(atom);
420 }
421 Atomic::TIntRange { min, max: Some(0) } => {
423 let atom = match min {
424 None => Atomic::TNegativeInt,
425 Some(n) if *n <= -1 => Atomic::TIntRange {
426 min: *min,
427 max: Some(-1),
428 },
429 _ => continue, };
431 result.add_type(atom);
432 }
433 t if !t.can_be_truthy() => {}
435 _ => result.add_type(t.clone()),
436 }
437 }
438 result
439 }
440
441 pub fn narrow_to_falsy(&self) -> Type {
443 if self.is_mixed_not_template() {
444 return Type::from_vec(vec![
445 Atomic::TNull,
446 Atomic::TFalse,
447 Atomic::TLiteralInt(0),
448 Atomic::TLiteralString("".into()),
449 ]);
450 }
451 let mut result = Type::empty();
452 result.from_docblock = self.from_docblock;
453 for t in &self.types {
454 match t {
455 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
460 Atomic::TBool => result.add_type(Atomic::TFalse),
462 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
464 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
466 Atomic::TString => {
468 result.add_type(Atomic::TLiteralString("".into()));
469 result.add_type(Atomic::TLiteralString("0".into()));
470 }
471 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
473 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
475 Atomic::TIntRange {
477 min: Some(0),
478 max: Some(_) | None,
479 } => result.add_type(Atomic::TLiteralInt(0)),
480 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
482 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
484 }
485 }
486 result
487 }
488
489 pub fn narrow_instanceof(&self, class: &str) -> Type {
495 let narrowed_ty = Atomic::TNamedObject {
496 fqcn: class.into(),
497 type_params: empty_type_params(),
498 };
499 let has_object = self.types.iter().any(|t| {
501 matches!(
502 t,
503 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
505 });
506 if has_object || self.is_empty() {
507 Type::single(narrowed_ty)
508 } else {
509 Type::single(narrowed_ty)
512 }
513 }
514
515 pub fn narrow_to_string(&self) -> Type {
519 self.filter_replacing(
520 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
521 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
522 Atomic::TString,
523 )
524 }
525
526 pub fn narrow_to_int(&self) -> Type {
528 self.filter_replacing(
529 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
530 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
531 Atomic::TInt,
532 )
533 }
534
535 pub fn narrow_to_float(&self) -> Type {
537 self.filter_replacing(
538 |t| {
539 matches!(
540 t,
541 Atomic::TFloat
542 | Atomic::TIntegralFloat
543 | Atomic::TLiteralFloat(..)
544 | Atomic::TTemplateParam { .. }
545 )
546 },
547 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
548 Atomic::TFloat,
549 )
550 }
551
552 pub fn narrow_to_bool(&self) -> Type {
554 self.filter_replacing(
555 |t| {
556 matches!(
557 t,
558 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
559 )
560 },
561 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
562 Atomic::TBool,
563 )
564 }
565
566 pub fn narrow_to_null(&self) -> Type {
568 self.filter_replacing(
569 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
570 |t| matches!(t, Atomic::TMixed),
571 Atomic::TNull,
572 )
573 }
574
575 pub fn narrow_to_array(&self) -> Type {
577 self.filter_replacing(
578 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
579 |t| matches!(t, Atomic::TMixed),
580 Atomic::TArray {
581 key: Box::new(Type::mixed()),
582 value: Box::new(Type::mixed()),
583 },
584 )
585 }
586
587 pub fn narrow_to_non_empty_collection(&self) -> Type {
589 let mut out = Type::empty();
590 out.from_docblock = self.from_docblock;
591 for t in &self.types {
592 match t {
593 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
594 key: key.clone(),
595 value: value.clone(),
596 }),
597 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
598 value: value.clone(),
599 }),
600 _ => out.add_type(t.clone()),
601 }
602 }
603 out
604 }
605
606 pub fn narrow_to_list(&self) -> Type {
614 let mut out = Type::empty();
615 out.from_docblock = self.from_docblock;
616 for t in &self.types {
617 match t {
618 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
619 Atomic::TArray { key, value } if matches!(key.types.as_slice(), [Atomic::TInt]) => {
620 out.add_type(Atomic::TList {
621 value: value.clone(),
622 });
623 }
624 Atomic::TNonEmptyArray { key, value }
625 if matches!(key.types.as_slice(), [Atomic::TInt]) =>
626 {
627 out.add_type(Atomic::TNonEmptyList {
628 value: value.clone(),
629 });
630 }
631 Atomic::TMixed => out.add_type(Atomic::TList {
632 value: Box::new(Type::mixed()),
633 }),
634 _ => {}
635 }
636 }
637 if out.is_empty() {
638 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
639 } else {
640 out
641 }
642 }
643
644 pub fn narrow_to_object(&self) -> Type {
649 let mut out = Type::empty();
650 for t in &self.types {
651 if matches!(t, Atomic::TMixed) {
652 out.add_type(Atomic::TObject);
653 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
654 out.add_type(t.clone());
655 }
656 }
657 if out.types.is_empty() {
658 self.filter(|t| t.is_object())
659 } else {
660 out
661 }
662 }
663
664 pub fn narrow_to_callable(&self) -> Type {
671 self.filter(|t| {
672 t.is_callable()
673 || t.is_string()
674 || t.is_array()
675 || t.is_object()
676 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
677 })
678 }
679
680 pub fn narrow_to_scalar(&self) -> Type {
682 self.filter_replacing(
683 |t| {
684 t.is_string()
685 || t.is_int()
686 || matches!(
687 t,
688 Atomic::TFloat
689 | Atomic::TIntegralFloat
690 | Atomic::TLiteralFloat(..)
691 | Atomic::TBool
692 | Atomic::TTrue
693 | Atomic::TFalse
694 | Atomic::TScalar
695 | Atomic::TNumeric
696 | Atomic::TNumericString
697 | Atomic::TTemplateParam { .. }
698 )
699 },
700 |t| matches!(t, Atomic::TMixed),
701 Atomic::TScalar,
702 )
703 }
704
705 pub fn narrow_to_iterable(&self) -> Type {
708 self.filter(|t| {
709 t.is_array()
710 || t.is_object()
711 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
712 })
713 }
714
715 pub fn narrow_to_countable(&self) -> Type {
718 self.filter(|t| {
719 t.is_array()
720 || t.is_object()
721 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
722 })
723 }
724
725 pub fn narrow_to_resource(&self) -> Type {
729 self.filter(|t| matches!(t, Atomic::TMixed))
731 }
732
733 pub fn narrow_to_class_string(&self) -> Type {
738 let mut out = Type::empty();
739 out.from_docblock = self.from_docblock;
740 for t in &self.types {
741 match t {
742 Atomic::TClassString(_) => out.add_type(t.clone()),
743 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
744 out.add_type(Atomic::TClassString(None));
745 }
746 _ => {}
747 }
748 }
749 out
750 }
751
752 pub fn narrow_to_interface_string(&self) -> Type {
757 let mut out = Type::empty();
758 out.from_docblock = self.from_docblock;
759 for t in &self.types {
760 match t {
761 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
762 Atomic::TClassString(name) => {
766 out.add_type(Atomic::TInterfaceString(*name));
767 }
768 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
769 out.add_type(Atomic::TInterfaceString(None));
770 }
771 _ => {}
772 }
773 }
774 out
775 }
776
777 pub fn merge(a: &Type, b: &Type) -> Type {
782 if b.types.is_empty() {
784 let mut result = a.clone();
785 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
786 return result;
787 }
788 if a.types.is_empty() {
790 let mut result = b.clone();
791 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
792 return result;
793 }
794 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
796 let mut result = a.clone();
797 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
798 return result;
799 }
800 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
802 return Type {
803 types: smallvec::smallvec![Atomic::TMixed],
804 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
805 from_docblock: a.from_docblock || b.from_docblock,
806 };
807 }
808 let mut result = a.clone();
809 result.merge_with(b);
810 result
811 }
812
813 pub fn merge_with(&mut self, other: &Type) {
815 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
816 self.possibly_undefined |= other.possibly_undefined;
817 return;
818 }
819 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
820 self.types.clear();
821 self.types.push(Atomic::TMixed);
822 self.possibly_undefined |= other.possibly_undefined;
823 return;
824 }
825 for atomic in &other.types {
826 self.add_type(atomic.clone());
827 }
828 self.possibly_undefined |= other.possibly_undefined;
829 }
830
831 pub fn intersect_with(&self, other: &Type) -> Type {
835 if self.is_mixed() {
836 return other.clone();
837 }
838 if other.is_mixed() {
839 return self.clone();
840 }
841 let mut result = Type::empty();
849 for a in &self.types {
850 for b in &other.types {
851 if a == b {
852 result.add_type(a.clone());
853 } else if atomic_subtype(b, a) {
854 result.add_type(b.clone());
855 } else if atomic_subtype(a, b) {
856 result.add_type(a.clone());
857 }
858 }
859 }
860 if result.is_empty() {
861 Type::never()
862 } else {
863 result
864 }
865 }
866
867 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
871 if bindings.is_empty() {
872 return self.clone();
873 }
874 if !self.types.iter().any(atomic_may_contain_templates) {
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: Box::new(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 { data } => {
955 result.add_type(Atomic::TClosure {
956 data: Box::new(crate::atomic::ClosureData {
957 params: data
958 .params
959 .iter()
960 .map(|p| substitute_in_fn_param(p, bindings))
961 .collect(),
962 return_type: data.return_type.substitute_templates(bindings),
963 this_type: data
964 .this_type
965 .as_ref()
966 .map(|t| t.substitute_templates(bindings)),
967 }),
968 });
969 }
970 Atomic::TConditional { data } => {
971 let param_name = &data.param_name;
972 let new_subject = data.subject.substitute_templates(bindings);
973 let new_if_true = data.if_true.substitute_templates(bindings);
974 let new_if_false = data.if_false.substitute_templates(bindings);
975
976 let resolved = if let Some(name) = param_name {
980 if let Some(bound) = bindings.get(name) {
981 if new_subject.types.len() == 1 {
982 resolve_conditional_branch(
983 &new_subject.types[0],
984 bound,
985 &new_if_true,
986 &new_if_false,
987 )
988 } else {
989 None
990 }
991 } else {
992 None
993 }
994 } else {
995 None
996 };
997
998 if let Some(branch) = resolved {
999 for t in branch.types {
1000 result.add_type(t);
1001 }
1002 } else {
1003 result.add_type(Atomic::TConditional {
1004 data: Box::new(crate::atomic::ConditionalData {
1005 param_name: *param_name,
1006 subject: new_subject,
1007 if_true: new_if_true,
1008 if_false: new_if_false,
1009 }),
1010 });
1011 }
1012 }
1013 Atomic::TIntersection { parts } => {
1014 result.add_type(Atomic::TIntersection {
1015 parts: vec_to_type_params(
1016 parts
1017 .iter()
1018 .map(|p| p.substitute_templates(bindings))
1019 .collect(),
1020 ),
1021 });
1022 }
1023 Atomic::TNamedObject { fqcn, type_params } => {
1024 if type_params.is_empty() && !fqcn.contains('\\') {
1031 if let Some(resolved) = bindings.get(fqcn) {
1032 for t in &resolved.types {
1033 result.add_type(t.clone());
1034 }
1035 continue;
1036 }
1037 }
1038 let new_params: Vec<Type> = type_params
1039 .iter()
1040 .map(|p| p.substitute_templates(bindings))
1041 .collect();
1042 result.add_type(Atomic::TNamedObject {
1043 fqcn: *fqcn,
1044 type_params: vec_to_type_params(new_params),
1045 });
1046 }
1047 Atomic::TClassString(Some(param_name)) => {
1049 if let Some(resolved) = bindings.get(param_name) {
1050 for r_atomic in &resolved.types {
1051 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1052 Some(*fqcn)
1053 } else {
1054 None
1055 };
1056 result.add_type(Atomic::TClassString(cls_name));
1057 }
1058 } else {
1059 result.add_type(atomic.clone());
1060 }
1061 }
1062 Atomic::TInterfaceString(Some(param_name)) => {
1064 if let Some(resolved) = bindings.get(param_name) {
1065 for r_atomic in &resolved.types {
1066 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1067 Some(*fqcn)
1068 } else {
1069 None
1070 };
1071 result.add_type(Atomic::TInterfaceString(iface_name));
1072 }
1073 } else {
1074 result.add_type(atomic.clone());
1075 }
1076 }
1077 _ => {
1078 result.add_type(atomic.clone());
1079 }
1080 }
1081 }
1082 result
1083 }
1084
1085 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1091 where
1092 F: Fn(&str) -> Option<Type>,
1093 {
1094 self.resolve_conditional_inner(&lookup)
1095 }
1096
1097 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1098 where
1099 F: Fn(&str) -> Option<Type>,
1100 {
1101 let mut result = Type::empty();
1102 for atomic in self.types {
1103 match atomic {
1104 Atomic::TConditional { ref data } => {
1105 let (param_name, subject, if_true, if_false) = (
1106 &data.param_name,
1107 &data.subject,
1108 &data.if_true,
1109 &data.if_false,
1110 );
1111 let resolved = if subject.types.len() == 1 {
1112 if let Some(name) = param_name {
1113 if let Some(arg_ty) = lookup(name.as_ref()) {
1114 resolve_conditional_branch(
1115 &subject.types[0],
1116 &arg_ty,
1117 if_true,
1118 if_false,
1119 )
1120 } else {
1121 None
1122 }
1123 } else {
1124 None
1125 }
1126 } else {
1127 None
1128 };
1129
1130 if let Some(branch) = resolved {
1131 for t in branch.resolve_conditional_inner(lookup).types {
1133 result.add_type(t);
1134 }
1135 } else {
1136 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1139 result.add_type(t);
1140 }
1141 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1142 result.add_type(t);
1143 }
1144 }
1145 }
1146 other => result.add_type(other),
1147 }
1148 }
1149 result
1150 }
1151
1152 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1162 if other.is_mixed() {
1163 return true;
1164 }
1165 if self.is_never() {
1166 return true; }
1168 self.types
1169 .iter()
1170 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1171 }
1172
1173 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1177 if self.is_mixed() {
1178 return true;
1179 }
1180 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1181 }
1182
1183 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1186 let mut result = Type::empty();
1187 result.possibly_undefined = self.possibly_undefined;
1188 result.from_docblock = self.from_docblock;
1189 for atomic in &self.types {
1190 if f(atomic) {
1191 result.types.push(atomic.clone());
1192 }
1193 }
1194 result
1195 }
1196
1197 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1202 &self,
1203 keep: K,
1204 placeholder: P,
1205 replacement: Atomic,
1206 ) -> Type {
1207 let mut result = Type::empty();
1208 result.possibly_undefined = self.possibly_undefined;
1209 result.from_docblock = self.from_docblock;
1210 for atomic in &self.types {
1211 if keep(atomic) {
1212 result.add_type(atomic.clone());
1213 } else if placeholder(atomic) {
1214 result.add_type(replacement.clone());
1215 }
1216 }
1217 result
1218 }
1219
1220 pub fn possibly_undefined(mut self) -> Self {
1222 self.possibly_undefined = true;
1223 self
1224 }
1225
1226 pub fn from_docblock(mut self) -> Self {
1228 self.from_docblock = true;
1229 self
1230 }
1231}
1232
1233fn is_string_atomic(a: &Atomic) -> bool {
1238 matches!(
1239 a,
1240 Atomic::TString
1241 | Atomic::TNonEmptyString
1242 | Atomic::TLiteralString(_)
1243 | Atomic::TNumericString
1244 | Atomic::TClassString(_)
1245 | Atomic::TInterfaceString(_)
1246 | Atomic::TCallableString
1247 )
1248}
1249
1250fn is_array_atomic(a: &Atomic) -> bool {
1251 matches!(
1252 a,
1253 Atomic::TArray { .. }
1254 | Atomic::TNonEmptyArray { .. }
1255 | Atomic::TKeyedArray { .. }
1256 | Atomic::TList { .. }
1257 | Atomic::TNonEmptyList { .. }
1258 )
1259}
1260
1261fn is_list_atomic(a: &Atomic) -> bool {
1262 match a {
1263 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1264 Atomic::TKeyedArray { is_list, .. } => *is_list,
1265 _ => false,
1266 }
1267}
1268
1269fn is_float_atomic(a: &Atomic) -> bool {
1270 matches!(
1271 a,
1272 Atomic::TFloat | Atomic::TIntegralFloat | Atomic::TLiteralFloat(..)
1273 )
1274}
1275
1276fn is_bool_atomic(a: &Atomic) -> bool {
1277 matches!(a, Atomic::TBool | Atomic::TTrue | Atomic::TFalse)
1278}
1279
1280fn resolve_conditional_branch(
1286 subject: &Atomic,
1287 arg_ty: &Type,
1288 if_true: &Type,
1289 if_false: &Type,
1290) -> Option<Type> {
1291 let predicate: fn(&Atomic) -> bool = match subject {
1292 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1293 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1294 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1295 Atomic::TString => is_string_atomic,
1296 Atomic::TList { .. } => is_list_atomic,
1297 Atomic::TArray { .. } => is_array_atomic,
1298 Atomic::TInt => Atomic::is_int,
1299 Atomic::TFloat => is_float_atomic,
1300 Atomic::TBool => is_bool_atomic,
1301 _ => return None,
1302 };
1303
1304 if arg_ty.types.is_empty() {
1305 return None;
1306 }
1307 let all_match = arg_ty.types.iter().all(&predicate);
1308 let none_match = !arg_ty.types.iter().any(predicate);
1309 if all_match {
1310 Some(if_true.clone())
1311 } else if none_match {
1312 Some(if_false.clone())
1313 } else {
1314 None
1315 }
1316}
1317
1318fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1326 match atomic {
1327 Atomic::TNamedObject { fqcn, type_params } => {
1331 !type_params.is_empty() || !fqcn.contains('\\')
1332 }
1333 Atomic::TTemplateParam { .. }
1334 | Atomic::TArray { .. }
1335 | Atomic::TList { .. }
1336 | Atomic::TNonEmptyArray { .. }
1337 | Atomic::TNonEmptyList { .. }
1338 | Atomic::TKeyedArray { .. }
1339 | Atomic::TCallable { .. }
1340 | Atomic::TClosure { .. }
1341 | Atomic::TConditional { .. }
1342 | Atomic::TIntersection { .. }
1343 | Atomic::TClassString(Some(_))
1344 | Atomic::TInterfaceString(Some(_)) => true,
1345 _ => false,
1346 }
1347}
1348
1349fn substitute_in_fn_param(
1350 p: &crate::atomic::FnParam,
1351 bindings: &FxHashMap<Name, Type>,
1352) -> crate::atomic::FnParam {
1353 crate::atomic::FnParam {
1354 name: p.name,
1355 ty: p.ty.as_ref().map(|t| {
1356 let u = t.to_union();
1357 let substituted = u.substitute_templates(bindings);
1358 crate::compact::SimpleType::from_union(substituted)
1359 }),
1360 out_ty: p.out_ty.as_ref().map(|t| {
1361 let u = t.to_union();
1362 let substituted = u.substitute_templates(bindings);
1363 crate::compact::SimpleType::from_union(substituted)
1364 }),
1365 default: p.default.as_ref().map(|d| {
1366 let u = d.to_union();
1367 let substituted = u.substitute_templates(bindings);
1368 crate::compact::SimpleType::from_union(substituted)
1369 }),
1370 is_variadic: p.is_variadic,
1371 is_byref: p.is_byref,
1372 is_optional: p.is_optional,
1373 }
1374}
1375
1376pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1382 if sub == sup {
1383 return true;
1384 }
1385 match (sub, sup) {
1386 (Atomic::TNever, _) => true,
1388 (_, Atomic::TMixed) => true,
1390 (Atomic::TMixed, _) => true,
1391 (_, Atomic::TTemplateParam { as_type, .. }) => {
1396 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1397 }
1398
1399 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1401 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1402 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1403 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1404 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1405 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1406 (Atomic::TPositiveInt, Atomic::TInt) => true,
1407 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1408 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1409 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1410 (Atomic::TNegativeInt, Atomic::TInt) => true,
1411 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1412 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1413 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1414 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1415 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1416 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1417 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1418 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1419 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1421 max.is_none() && min.is_none_or(|m| m <= 1)
1422 }
1423 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1425 min.is_none() && max.is_none_or(|m| m >= -1)
1426 }
1427 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1429 max.is_none() && min.is_none_or(|m| m <= 0)
1430 }
1431 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1433 sub_min.is_some_and(|lo| lo >= 1)
1434 }
1435 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1436 sub_min.is_some_and(|lo| lo >= 0)
1437 }
1438 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1439 sub_max.is_some_and(|hi| hi <= -1)
1440 }
1441 (
1443 Atomic::TIntRange {
1444 min: sub_min,
1445 max: sub_max,
1446 },
1447 Atomic::TIntRange {
1448 min: sup_min,
1449 max: sup_max,
1450 },
1451 ) => {
1452 let lower_ok = match (sub_min, sup_min) {
1453 (_, None) => true,
1454 (None, Some(_)) => false,
1455 (Some(sl), Some(su)) => sl >= su,
1456 };
1457 let upper_ok = match (sub_max, sup_max) {
1458 (None, None) | (Some(_), None) => true,
1459 (None, Some(_)) => false,
1460 (Some(sl), Some(su)) => sl <= su,
1461 };
1462 lower_ok && upper_ok
1463 }
1464
1465 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1466 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1467 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1468
1469 (Atomic::TLiteralString(s), Atomic::TString) => {
1470 let _ = s;
1471 true
1472 }
1473 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1474 let _ = s;
1475 true
1476 }
1477 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1478 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1479 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1482 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1485 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1486 (Atomic::TNonEmptyString, Atomic::TString) => true,
1487 (Atomic::TCallableString, Atomic::TString) => true,
1488 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1490 (Atomic::TNumericString, Atomic::TString) => true,
1491 (Atomic::TClassString(_), Atomic::TString) => true,
1492 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1493 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1498 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1499 (Atomic::TEnumString, Atomic::TString) => true,
1500 (Atomic::TTraitString, Atomic::TString) => true,
1501
1502 (Atomic::TTrue, Atomic::TBool) => true,
1503 (Atomic::TFalse, Atomic::TBool) => true,
1504
1505 (Atomic::TInt, Atomic::TNumeric) => true,
1506 (Atomic::TFloat, Atomic::TNumeric) => true,
1507 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1508 (Atomic::TNumericString, Atomic::TNumeric) => true,
1509
1510 (Atomic::TInt, Atomic::TScalar) => true,
1511 (Atomic::TFloat, Atomic::TScalar) => true,
1512 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1513 (Atomic::TString, Atomic::TScalar) => true,
1514 (Atomic::TBool, Atomic::TScalar) => true,
1515 (Atomic::TNumeric, Atomic::TScalar) => true,
1516 (Atomic::TTrue, Atomic::TScalar) => true,
1517 (Atomic::TFalse, Atomic::TScalar) => true,
1518
1519 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1521 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1522 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1523 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1525 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1526 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1528 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1529 (
1533 Atomic::TNamedObject {
1534 fqcn: sub_fqcn,
1535 type_params: sub_params,
1536 },
1537 Atomic::TNamedObject {
1538 fqcn: sup_fqcn,
1539 type_params: sup_params,
1540 },
1541 ) => {
1542 sub_fqcn == sup_fqcn
1543 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1544 }
1545
1546 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1548
1549 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1551 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1552 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1553 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1554 (Atomic::TInt, Atomic::TFloat) => true,
1555 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1556
1557 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1559 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1560 }
1561
1562 (Atomic::TString, Atomic::TCallable { .. }) => true,
1564 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1565 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1566 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1567 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1568 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1569
1570 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1572 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1574 (Atomic::TClosure { .. }, Atomic::TClosure { .. }) => true,
1576 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1578 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1580 fqcn.as_ref().eq_ignore_ascii_case("closure")
1581 }
1582 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1583 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1585 fqcn.as_ref().eq_ignore_ascii_case("closure")
1586 }
1587 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1589 fqcn.as_ref().eq_ignore_ascii_case("closure")
1590 }
1591
1592 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1594 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1595 }
1596 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1597 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1598 }
1599 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1600 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1601 }
1602 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1603 value.is_subtype_structural(lv)
1604 }
1605 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1607 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1608 && av.is_subtype_structural(lv)
1609 }
1610 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1611 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1612 && av.is_subtype_structural(lv)
1613 }
1614 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1615 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1616 && av.is_subtype_structural(lv)
1617 }
1618 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1619 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1620 && av.is_subtype_structural(lv)
1621 }
1622 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1624 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1625 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1626 }
1627
1628 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1630 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1631 }
1632
1633 (
1639 Atomic::TKeyedArray {
1640 properties,
1641 is_open,
1642 ..
1643 },
1644 Atomic::TArray { key, value },
1645 ) => {
1646 if *is_open {
1647 return true;
1648 }
1649 properties.iter().all(|(prop_key, prop)| {
1650 let key_atomic = match prop_key {
1651 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1652 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1653 };
1654 if !Type::single(key_atomic).is_subtype_structural(key) {
1655 return false; }
1657 let has_named_obj = prop.ty.types.iter().any(|a| {
1659 matches!(
1660 a,
1661 Atomic::TNamedObject { .. }
1662 | Atomic::TSelf { .. }
1663 | Atomic::TStaticObject { .. }
1664 | Atomic::TClosure { .. }
1665 | Atomic::TTemplateParam { .. }
1666 )
1667 });
1668 has_named_obj || prop.ty.is_subtype_structural(value)
1669 })
1670 }
1671 (
1672 Atomic::TKeyedArray {
1673 properties,
1674 is_open,
1675 ..
1676 },
1677 Atomic::TNonEmptyArray { key, value },
1678 ) => {
1679 if *is_open {
1680 return !properties.is_empty();
1681 }
1682 properties.iter().any(|(_, p)| !p.optional)
1683 && properties.iter().all(|(prop_key, prop)| {
1684 let key_atomic = match prop_key {
1685 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1686 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1687 };
1688 if !Type::single(key_atomic).is_subtype_structural(key) {
1689 return false;
1690 }
1691 let has_named_obj = prop.ty.types.iter().any(|a| {
1692 matches!(
1693 a,
1694 Atomic::TNamedObject { .. }
1695 | Atomic::TSelf { .. }
1696 | Atomic::TStaticObject { .. }
1697 | Atomic::TClosure { .. }
1698 | Atomic::TTemplateParam { .. }
1699 )
1700 });
1701 has_named_obj || prop.ty.is_subtype_structural(value)
1702 })
1703 }
1704
1705 (
1707 Atomic::TKeyedArray {
1708 properties,
1709 is_list,
1710 ..
1711 },
1712 Atomic::TList { value: lv },
1713 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1714 (
1715 Atomic::TKeyedArray {
1716 properties,
1717 is_list,
1718 ..
1719 },
1720 Atomic::TNonEmptyList { value: lv },
1721 ) => {
1722 *is_list
1723 && !properties.is_empty()
1724 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1725 }
1726
1727 _ => false,
1728 }
1729}
1730
1731fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
1737 if sub.len() != sup.len() {
1738 return false;
1739 }
1740 sub.iter()
1741 .zip(sup.iter())
1742 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
1743}
1744
1745fn is_empty_array_literal(t: &Type) -> bool {
1748 !t.types.is_empty()
1749 && t.types.iter().all(
1750 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
1751 )
1752}
1753
1754fn is_array_like(t: &Type) -> bool {
1756 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
1757}
1758
1759#[cfg(test)]
1764mod tests {
1765 use std::sync::Arc;
1766
1767 use super::*;
1768
1769 fn conditional(
1770 param_name: Option<Name>,
1771 subject: Type,
1772 if_true: Type,
1773 if_false: Type,
1774 ) -> Atomic {
1775 Atomic::TConditional {
1776 data: Box::new(crate::atomic::ConditionalData {
1777 param_name,
1778 subject,
1779 if_true,
1780 if_false,
1781 }),
1782 }
1783 }
1784
1785 #[test]
1786 fn single_is_single() {
1787 let u = Type::single(Atomic::TString);
1788 assert!(u.is_single());
1789 assert!(!u.is_nullable());
1790 }
1791
1792 #[test]
1793 fn nullable_has_null() {
1794 let u = Type::nullable(Atomic::TString);
1795 assert!(u.is_nullable());
1796 assert_eq!(u.types.len(), 2);
1797 }
1798
1799 #[test]
1800 fn add_type_deduplicates() {
1801 let mut u = Type::single(Atomic::TString);
1802 u.add_type(Atomic::TString);
1803 assert_eq!(u.types.len(), 1);
1804 }
1805
1806 #[test]
1807 fn add_type_literal_subsumed_by_base() {
1808 let mut u = Type::single(Atomic::TInt);
1809 u.add_type(Atomic::TLiteralInt(42));
1810 assert_eq!(u.types.len(), 1);
1811 assert!(matches!(u.types[0], Atomic::TInt));
1812 }
1813
1814 #[test]
1815 fn add_type_base_widens_literals() {
1816 let mut u = Type::single(Atomic::TLiteralInt(1));
1817 u.add_type(Atomic::TLiteralInt(2));
1818 u.add_type(Atomic::TInt);
1819 assert_eq!(u.types.len(), 1);
1820 assert!(matches!(u.types[0], Atomic::TInt));
1821 }
1822
1823 #[test]
1824 fn mixed_subsumes_everything() {
1825 let mut u = Type::single(Atomic::TString);
1826 u.add_type(Atomic::TMixed);
1827 assert_eq!(u.types.len(), 1);
1828 assert!(u.is_mixed());
1829 }
1830
1831 #[test]
1832 fn remove_null() {
1833 let u = Type::nullable(Atomic::TString);
1834 let narrowed = u.remove_null();
1835 assert!(!narrowed.is_nullable());
1836 assert_eq!(narrowed.types.len(), 1);
1837 }
1838
1839 #[test]
1840 fn narrow_to_truthy_removes_null_false() {
1841 let mut u = Type::empty();
1842 u.add_type(Atomic::TString);
1843 u.add_type(Atomic::TNull);
1844 u.add_type(Atomic::TFalse);
1845 let truthy = u.narrow_to_truthy();
1846 assert!(!truthy.is_nullable());
1847 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
1848 }
1849
1850 #[test]
1851 fn merge_combines_types() {
1852 let a = Type::single(Atomic::TString);
1853 let b = Type::single(Atomic::TInt);
1854 let merged = Type::merge(&a, &b);
1855 assert_eq!(merged.types.len(), 2);
1856 }
1857
1858 #[test]
1859 fn intersect_keeps_narrower_side_not_self() {
1860 let int_ty = Type::single(Atomic::TInt);
1864 let mut literals = Type::empty();
1865 literals.add_type(Atomic::TLiteralInt(1));
1866 literals.add_type(Atomic::TLiteralInt(2));
1867
1868 let narrowed = int_ty.intersect_with(&literals);
1869 assert_eq!(narrowed.types.len(), 2);
1870 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
1871 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
1872 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
1873 }
1874
1875 #[test]
1876 fn subtype_literal_int_under_int() {
1877 let sub = Type::single(Atomic::TLiteralInt(5));
1878 let sup = Type::single(Atomic::TInt);
1879 assert!(sub.is_subtype_structural(&sup));
1880 }
1881
1882 #[test]
1883 fn subtype_never_is_bottom() {
1884 let never = Type::never();
1885 let string = Type::single(Atomic::TString);
1886 assert!(never.is_subtype_structural(&string));
1887 }
1888
1889 #[test]
1890 fn subtype_everything_under_mixed() {
1891 let string = Type::single(Atomic::TString);
1892 let mixed = Type::mixed();
1893 assert!(string.is_subtype_structural(&mixed));
1894 }
1895
1896 #[test]
1897 fn template_substitution() {
1898 let mut bindings = FxHashMap::default();
1899 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
1900
1901 let tmpl = Type::single(Atomic::TTemplateParam {
1902 name: Name::new("T"),
1903 as_type: Box::new(Type::mixed()),
1904 defining_entity: Name::new("MyClass"),
1905 });
1906
1907 let resolved = tmpl.substitute_templates(&bindings);
1908 assert_eq!(resolved.types.len(), 1);
1909 assert!(matches!(resolved.types[0], Atomic::TString));
1910 }
1911
1912 #[test]
1913 fn intersection_is_object() {
1914 let parts = vec![
1915 Type::single(Atomic::TNamedObject {
1916 fqcn: Name::new("Iterator"),
1917 type_params: empty_type_params(),
1918 }),
1919 Type::single(Atomic::TNamedObject {
1920 fqcn: Name::new("Countable"),
1921 type_params: empty_type_params(),
1922 }),
1923 ];
1924 let atomic = Atomic::TIntersection {
1925 parts: vec_to_type_params(parts),
1926 };
1927 assert!(atomic.is_object());
1928 assert!(!atomic.can_be_falsy());
1929 assert!(atomic.can_be_truthy());
1930 }
1931
1932 #[test]
1933 fn intersection_display_two_parts() {
1934 let parts = vec![
1935 Type::single(Atomic::TNamedObject {
1936 fqcn: Name::new("Iterator"),
1937 type_params: empty_type_params(),
1938 }),
1939 Type::single(Atomic::TNamedObject {
1940 fqcn: Name::new("Countable"),
1941 type_params: empty_type_params(),
1942 }),
1943 ];
1944 let u = Type::single(Atomic::TIntersection {
1945 parts: vec_to_type_params(parts),
1946 });
1947 assert_eq!(format!("{u}"), "Iterator&Countable");
1948 }
1949
1950 #[test]
1951 fn intersection_display_three_parts() {
1952 let parts = vec![
1953 Type::single(Atomic::TNamedObject {
1954 fqcn: Name::new("A"),
1955 type_params: empty_type_params(),
1956 }),
1957 Type::single(Atomic::TNamedObject {
1958 fqcn: Name::new("B"),
1959 type_params: empty_type_params(),
1960 }),
1961 Type::single(Atomic::TNamedObject {
1962 fqcn: Name::new("C"),
1963 type_params: empty_type_params(),
1964 }),
1965 ];
1966 let u = Type::single(Atomic::TIntersection {
1967 parts: vec_to_type_params(parts),
1968 });
1969 assert_eq!(format!("{u}"), "A&B&C");
1970 }
1971
1972 #[test]
1973 fn intersection_in_nullable_union_display() {
1974 let intersection = Atomic::TIntersection {
1975 parts: vec_to_type_params(vec![
1976 Type::single(Atomic::TNamedObject {
1977 fqcn: Name::new("Iterator"),
1978 type_params: empty_type_params(),
1979 }),
1980 Type::single(Atomic::TNamedObject {
1981 fqcn: Name::new("Countable"),
1982 type_params: empty_type_params(),
1983 }),
1984 ]),
1985 };
1986 let mut u = Type::single(intersection);
1987 u.add_type(Atomic::TNull);
1988 assert!(u.is_nullable());
1989 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
1990 }
1991
1992 fn t_param(name: &str) -> Type {
1995 Type::single(Atomic::TTemplateParam {
1996 name: Name::new(name),
1997 as_type: Box::new(Type::mixed()),
1998 defining_entity: Name::new("Fn"),
1999 })
2000 }
2001
2002 fn bindings_t_string() -> FxHashMap<Name, Type> {
2003 let mut b = FxHashMap::default();
2004 b.insert(Name::new("T"), Type::single(Atomic::TString));
2005 b
2006 }
2007
2008 #[test]
2009 fn substitute_non_empty_array_key_and_value() {
2010 let ty = Type::single(Atomic::TNonEmptyArray {
2011 key: Box::new(t_param("T")),
2012 value: Box::new(t_param("T")),
2013 });
2014 let result = ty.substitute_templates(&bindings_t_string());
2015 assert_eq!(result.types.len(), 1);
2016 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2017 panic!("expected TNonEmptyArray");
2018 };
2019 assert!(matches!(key.types[0], Atomic::TString));
2020 assert!(matches!(value.types[0], Atomic::TString));
2021 }
2022
2023 #[test]
2024 fn substitute_non_empty_list_value() {
2025 let ty = Type::single(Atomic::TNonEmptyList {
2026 value: Box::new(t_param("T")),
2027 });
2028 let result = ty.substitute_templates(&bindings_t_string());
2029 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2030 panic!("expected TNonEmptyList");
2031 };
2032 assert!(matches!(value.types[0], Atomic::TString));
2033 }
2034
2035 #[test]
2036 fn substitute_keyed_array_property_types() {
2037 use crate::atomic::{ArrayKey, KeyedProperty};
2038 use indexmap::IndexMap;
2039 let mut props = IndexMap::new();
2040 props.insert(
2041 ArrayKey::String(Arc::from("name")),
2042 KeyedProperty {
2043 ty: t_param("T"),
2044 optional: false,
2045 },
2046 );
2047 props.insert(
2048 ArrayKey::String(Arc::from("tag")),
2049 KeyedProperty {
2050 ty: t_param("T"),
2051 optional: true,
2052 },
2053 );
2054 let ty = Type::single(Atomic::TKeyedArray {
2055 properties: Box::new(props),
2056 is_open: true,
2057 is_list: false,
2058 });
2059 let result = ty.substitute_templates(&bindings_t_string());
2060 let Atomic::TKeyedArray {
2061 properties,
2062 is_open,
2063 is_list,
2064 } = &result.types[0]
2065 else {
2066 panic!("expected TKeyedArray");
2067 };
2068 assert!(is_open);
2069 assert!(!is_list);
2070 assert!(matches!(
2071 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2072 Atomic::TString
2073 ));
2074 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2075 assert!(matches!(
2076 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2077 Atomic::TString
2078 ));
2079 }
2080
2081 #[test]
2082 fn substitute_callable_params_and_return() {
2083 use crate::atomic::FnParam;
2084 let ty = Type::single(Atomic::TCallable {
2085 params: Some(Box::new([FnParam {
2086 name: Name::new("x"),
2087 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2088 out_ty: None,
2089 default: None,
2090 is_variadic: false,
2091 is_byref: false,
2092 is_optional: false,
2093 }])),
2094 return_type: Some(Box::new(t_param("T"))),
2095 });
2096 let result = ty.substitute_templates(&bindings_t_string());
2097 let Atomic::TCallable {
2098 params,
2099 return_type,
2100 } = &result.types[0]
2101 else {
2102 panic!("expected TCallable");
2103 };
2104 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2105 let param_union = param_ty.to_union();
2106 assert!(matches!(param_union.types[0], Atomic::TString));
2107 let ret = return_type.as_ref().unwrap();
2108 assert!(matches!(ret.types[0], Atomic::TString));
2109 }
2110
2111 #[test]
2112 fn substitute_callable_bare_no_panic() {
2113 let ty = Type::single(Atomic::TCallable {
2115 params: None,
2116 return_type: None,
2117 });
2118 let result = ty.substitute_templates(&bindings_t_string());
2119 assert!(matches!(
2120 result.types[0],
2121 Atomic::TCallable {
2122 params: None,
2123 return_type: None
2124 }
2125 ));
2126 }
2127
2128 #[test]
2129 fn substitute_closure_params_return_and_this() {
2130 use crate::atomic::FnParam;
2131 let ty = Type::single(Atomic::TClosure {
2132 data: Box::new(crate::atomic::ClosureData {
2133 params: Box::new([FnParam {
2134 name: Name::new("a"),
2135 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2136 out_ty: None,
2137 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2138 is_variadic: true,
2139 is_byref: true,
2140 is_optional: true,
2141 }]),
2142 return_type: t_param("T"),
2143 this_type: Some(t_param("T")),
2144 }),
2145 });
2146 let result = ty.substitute_templates(&bindings_t_string());
2147 let Atomic::TClosure { data } = &result.types[0] else {
2148 panic!("expected TClosure");
2149 };
2150 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2151 let p = ¶ms[0];
2152 let ty_union = p.ty.as_ref().unwrap().to_union();
2153 let default_union = p.default.as_ref().unwrap().to_union();
2154 assert!(matches!(ty_union.types[0], Atomic::TString));
2155 assert!(matches!(default_union.types[0], Atomic::TString));
2156 assert!(p.is_variadic);
2158 assert!(p.is_byref);
2159 assert!(p.is_optional);
2160 assert!(matches!(return_type.types[0], Atomic::TString));
2161 assert!(matches!(
2162 this_type.as_ref().unwrap().types[0],
2163 Atomic::TString
2164 ));
2165 }
2166
2167 #[test]
2168 fn substitute_conditional_all_branches() {
2169 let ty = Type::single(conditional(
2170 None,
2171 t_param("T"),
2172 t_param("T"),
2173 Type::single(Atomic::TInt),
2174 ));
2175 let result = ty.substitute_templates(&bindings_t_string());
2176 let Atomic::TConditional { data } = &result.types[0] else {
2177 panic!("expected TConditional");
2178 };
2179 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2180 assert!(matches!(subject.types[0], Atomic::TString));
2181 assert!(matches!(if_true.types[0], Atomic::TString));
2182 assert!(matches!(if_false.types[0], Atomic::TInt));
2183 }
2184
2185 #[test]
2186 fn resolve_conditional_is_null_non_null_arg() {
2187 let ty = Type::single(conditional(
2188 Some(Name::new("x")),
2189 Type::single(Atomic::TNull),
2190 Type::single(Atomic::TInt),
2191 Type::single(Atomic::TString),
2192 ));
2193 let result = ty.resolve_conditional_returns(|name| {
2194 if name == "x" {
2195 Some(Type::single(Atomic::TString)) } else {
2197 None
2198 }
2199 });
2200 assert!(result.types.len() == 1);
2201 assert!(matches!(result.types[0], Atomic::TString));
2202 }
2203
2204 #[test]
2205 fn resolve_conditional_is_null_null_arg() {
2206 let ty = Type::single(conditional(
2207 Some(Name::new("x")),
2208 Type::single(Atomic::TNull),
2209 Type::single(Atomic::TInt),
2210 Type::single(Atomic::TString),
2211 ));
2212 let result = ty.resolve_conditional_returns(|name| {
2213 if name == "x" {
2214 Some(Type::single(Atomic::TNull)) } else {
2216 None
2217 }
2218 });
2219 assert!(result.types.len() == 1);
2220 assert!(matches!(result.types[0], Atomic::TInt));
2221 }
2222
2223 #[test]
2224 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2225 let mut nullable_str = Type::single(Atomic::TString);
2226 nullable_str.add_type(Atomic::TNull);
2227 let ty = Type::single(conditional(
2228 Some(Name::new("x")),
2229 Type::single(Atomic::TNull),
2230 Type::single(Atomic::TInt),
2231 Type::single(Atomic::TString),
2232 ));
2233 let result = ty.resolve_conditional_returns(|name| {
2234 if name == "x" {
2235 Some(nullable_str.clone())
2236 } else {
2237 None
2238 }
2239 });
2240 assert_eq!(result.types.len(), 2);
2242 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2243 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2244 }
2245
2246 #[test]
2247 fn resolve_conditional_nested_widens_inner_branch() {
2248 let inner = Type::single(conditional(
2251 Some(Name::new("x")),
2252 Type::single(Atomic::TString),
2253 Type::single(Atomic::TString),
2254 Type::single(Atomic::TFloat),
2255 ));
2256 let ty = Type::single(conditional(
2257 Some(Name::new("x")),
2258 Type::single(Atomic::TNull),
2259 Type::single(Atomic::TInt),
2260 inner,
2261 ));
2262 let result = ty.resolve_conditional_returns(|_| None);
2264 assert!(
2265 result
2266 .types
2267 .iter()
2268 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2269 "no TConditional should survive: {:?}",
2270 result.types
2271 );
2272 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2273 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2274 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2275 }
2276
2277 #[test]
2278 fn resolve_conditional_nested_resolves_inner_branch() {
2279 let inner = Type::single(conditional(
2283 Some(Name::new("x")),
2284 Type::single(Atomic::TString),
2285 Type::single(Atomic::TString),
2286 Type::single(Atomic::TFloat),
2287 ));
2288 let ty = Type::single(conditional(
2289 Some(Name::new("x")),
2290 Type::single(Atomic::TNull),
2291 Type::single(Atomic::TInt),
2292 inner,
2293 ));
2294 let result = ty.resolve_conditional_returns(|name| {
2296 if name == "x" {
2297 Some(Type::single(Atomic::TString))
2298 } else {
2299 None
2300 }
2301 });
2302 assert!(
2303 result
2304 .types
2305 .iter()
2306 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2307 "no TConditional should survive: {:?}",
2308 result.types
2309 );
2310 assert_eq!(result.types.len(), 1);
2311 assert!(matches!(result.types[0], Atomic::TString));
2312 }
2313
2314 #[test]
2315 fn substitute_intersection_parts() {
2316 let ty = Type::single(Atomic::TIntersection {
2317 parts: vec_to_type_params(vec![
2318 Type::single(Atomic::TNamedObject {
2319 fqcn: Name::new("Countable"),
2320 type_params: empty_type_params(),
2321 }),
2322 t_param("T"),
2323 ]),
2324 });
2325 let result = ty.substitute_templates(&bindings_t_string());
2326 let Atomic::TIntersection { parts } = &result.types[0] else {
2327 panic!("expected TIntersection");
2328 };
2329 assert_eq!(parts.len(), 2);
2330 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2331 assert!(matches!(parts[1].types[0], Atomic::TString));
2332 }
2333
2334 #[test]
2335 fn substitute_no_template_params_identity() {
2336 let ty = Type::single(Atomic::TInt);
2337 let result = ty.substitute_templates(&bindings_t_string());
2338 assert!(matches!(result.types[0], Atomic::TInt));
2339 }
2340}