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_never(&self) -> bool {
157 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
158 }
159
160 pub fn clone_validity(&self) -> CloneValidity {
163 if self.types.is_empty() {
164 return CloneValidity::Unknown;
165 }
166 let mut has_non_object = false;
167 let mut has_other = false; for t in &self.types {
169 match t {
170 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
171 CloneValidity::Invalid => has_non_object = true,
172 CloneValidity::PossiblyInvalid => {
173 has_non_object = true;
174 has_other = true;
175 }
176 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
177 },
178 other if other.is_definitely_non_object() => has_non_object = true,
179 _ => has_other = true,
180 }
181 }
182 match (has_non_object, has_other) {
183 (true, false) => CloneValidity::Invalid,
184 (true, true) => CloneValidity::PossiblyInvalid,
185 _ => CloneValidity::Cloneable,
186 }
187 }
188
189 pub fn is_void(&self) -> bool {
190 self.is_single() && matches!(self.types[0], Atomic::TVoid)
191 }
192
193 pub fn can_be_falsy(&self) -> bool {
194 self.types.iter().any(|t| t.can_be_falsy())
195 }
196
197 pub fn can_be_truthy(&self) -> bool {
198 self.types.iter().any(|t| t.can_be_truthy())
199 }
200
201 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
202 self.types.iter().any(f)
203 }
204
205 pub fn has_named_object(&self, fqcn: &str) -> bool {
206 self.types.iter().any(|t| match t {
207 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
208 _ => false,
209 })
210 }
211
212 pub fn add_type(&mut self, atomic: Atomic) {
217 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
219 return;
220 }
221
222 if matches!(atomic, Atomic::TMixed) {
224 self.types.clear();
225 self.types.push(Atomic::TMixed);
226 return;
227 }
228
229 let atomic = if let Atomic::TConditional {
232 param_name: _,
233 subject: _,
234 if_true,
235 if_false,
236 } = &atomic
237 {
238 let mut simplified_true = Type::empty();
239 for t in &if_true.types {
240 simplified_true.add_type(t.clone());
241 }
242 let mut simplified_false = Type::empty();
243 for t in &if_false.types {
244 simplified_false.add_type(t.clone());
245 }
246 if simplified_true == simplified_false {
247 for t in simplified_true.types {
248 self.add_type(t);
249 }
250 return;
251 }
252 atomic
253 } else {
254 atomic
255 };
256
257 if self.types.contains(&atomic) {
259 return;
260 }
261
262 if let Atomic::TLiteralInt(_) = &atomic {
264 if self.types.iter().any(|t| matches!(t, Atomic::TInt)) {
265 return;
266 }
267 }
268 if let Atomic::TLiteralString(_) = &atomic {
270 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
271 return;
272 }
273 }
274 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
276 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
277 {
278 return;
279 }
280 if matches!(atomic, Atomic::TInt) {
282 self.types.retain(|t| !matches!(t, Atomic::TLiteralInt(_)));
283 }
284 if matches!(atomic, Atomic::TString) {
286 self.types
287 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
288 }
289 if matches!(atomic, Atomic::TBool) {
291 self.types
292 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
293 }
294
295 if matches!(atomic, Atomic::TNever) {
297 if !self.types.is_empty() {
298 return;
299 }
300 } else {
301 self.types.retain(|t| !matches!(t, Atomic::TNever));
302 }
303
304 if let Atomic::TKeyedArray { properties, .. } = &atomic {
307 if properties.is_empty() {
308 for existing in &self.types {
309 match existing {
310 Atomic::TArray { .. }
311 | Atomic::TNonEmptyArray { .. }
312 | Atomic::TList { .. }
313 | Atomic::TNonEmptyList { .. } => {
314 return; }
316 _ => {}
317 }
318 }
319 }
320 }
321
322 let is_generic_array_or_list = matches!(
324 &atomic,
325 Atomic::TArray { .. }
326 | Atomic::TNonEmptyArray { .. }
327 | Atomic::TList { .. }
328 | Atomic::TNonEmptyList { .. }
329 );
330 if is_generic_array_or_list {
331 self.types.retain(|t| {
332 if let Atomic::TKeyedArray { properties, .. } = t {
333 !properties.is_empty()
334 } else {
335 true
336 }
337 });
338 }
339
340 self.types.push(atomic);
341 }
342
343 pub fn remove_null(&self) -> Type {
347 self.filter(|t| !matches!(t, Atomic::TNull))
348 }
349
350 pub fn remove_false(&self) -> Type {
353 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
354 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
355 result.add_type(Atomic::TTrue);
356 }
357 result
358 }
359
360 pub fn core_type(&self) -> Type {
362 self.remove_null().remove_false()
363 }
364
365 pub fn narrow_to_truthy(&self) -> Type {
367 if self.is_mixed() {
368 return Type::mixed();
369 }
370 let mut result = Type::empty();
371 result.from_docblock = self.from_docblock;
372 for t in &self.types {
373 match t {
374 Atomic::TLiteralInt(0)
376 | Atomic::TLiteralFloat(0, 0)
377 | Atomic::TNull
378 | Atomic::TFalse => {}
379 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
380 Atomic::TBool => result.add_type(Atomic::TTrue),
382 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
384 key: key.clone(),
385 value: value.clone(),
386 }),
387 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
388 value: value.clone(),
389 }),
390 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
394 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
399 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
400 let atom = if max.is_none() {
401 Atomic::TPositiveInt
402 } else {
403 Atomic::TIntRange {
404 min: Some(1),
405 max: *max,
406 }
407 };
408 result.add_type(atom);
409 }
410 Atomic::TIntRange { min, max: Some(0) } => {
412 let atom = match min {
413 None => Atomic::TNegativeInt,
414 Some(n) if *n <= -1 => Atomic::TIntRange {
415 min: *min,
416 max: Some(-1),
417 },
418 _ => continue, };
420 result.add_type(atom);
421 }
422 t if !t.can_be_truthy() => {}
424 _ => result.add_type(t.clone()),
425 }
426 }
427 result
428 }
429
430 pub fn narrow_to_falsy(&self) -> Type {
432 if self.is_mixed() {
433 return Type::from_vec(vec![
434 Atomic::TNull,
435 Atomic::TFalse,
436 Atomic::TLiteralInt(0),
437 Atomic::TLiteralString("".into()),
438 ]);
439 }
440 let mut result = Type::empty();
441 result.from_docblock = self.from_docblock;
442 for t in &self.types {
443 match t {
444 Atomic::TBool => result.add_type(Atomic::TFalse),
446 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
448 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
450 Atomic::TString => {
452 result.add_type(Atomic::TLiteralString("".into()));
453 result.add_type(Atomic::TLiteralString("0".into()));
454 }
455 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
457 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
459 Atomic::TIntRange {
461 min: Some(0),
462 max: Some(_) | None,
463 } => result.add_type(Atomic::TLiteralInt(0)),
464 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
466 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
468 }
469 }
470 result
471 }
472
473 pub fn narrow_instanceof(&self, class: &str) -> Type {
479 let narrowed_ty = Atomic::TNamedObject {
480 fqcn: class.into(),
481 type_params: empty_type_params(),
482 };
483 let has_object = self.types.iter().any(|t| {
485 matches!(
486 t,
487 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
489 });
490 if has_object || self.is_empty() {
491 Type::single(narrowed_ty)
492 } else {
493 Type::single(narrowed_ty)
496 }
497 }
498
499 pub fn narrow_to_string(&self) -> Type {
501 self.filter(|t| t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar))
502 }
503
504 pub fn narrow_to_int(&self) -> Type {
506 self.filter(|t| {
507 t.is_int() || matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric)
508 })
509 }
510
511 pub fn narrow_to_float(&self) -> Type {
513 self.filter(|t| {
514 matches!(
515 t,
516 Atomic::TFloat
517 | Atomic::TLiteralFloat(..)
518 | Atomic::TMixed
519 | Atomic::TScalar
520 | Atomic::TNumeric
521 )
522 })
523 }
524
525 pub fn narrow_to_bool(&self) -> Type {
527 self.filter(|t| {
528 matches!(
529 t,
530 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TMixed | Atomic::TScalar
531 )
532 })
533 }
534
535 pub fn narrow_to_null(&self) -> Type {
537 self.filter(|t| matches!(t, Atomic::TNull | Atomic::TMixed))
538 }
539
540 pub fn narrow_to_array(&self) -> Type {
542 self.filter(|t| t.is_array() || matches!(t, Atomic::TMixed))
543 }
544
545 pub fn narrow_to_non_empty_collection(&self) -> Type {
547 let mut out = Type::empty();
548 out.from_docblock = self.from_docblock;
549 for t in &self.types {
550 match t {
551 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
552 key: key.clone(),
553 value: value.clone(),
554 }),
555 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
556 value: value.clone(),
557 }),
558 _ => out.add_type(t.clone()),
559 }
560 }
561 out
562 }
563
564 pub fn narrow_to_list(&self) -> Type {
572 let mut out = Type::empty();
573 out.from_docblock = self.from_docblock;
574 for t in &self.types {
575 match t {
576 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
577 Atomic::TArray { key, value } if matches!(key.types.as_slice(), [Atomic::TInt]) => {
578 out.add_type(Atomic::TList {
579 value: value.clone(),
580 });
581 }
582 Atomic::TNonEmptyArray { key, value }
583 if matches!(key.types.as_slice(), [Atomic::TInt]) =>
584 {
585 out.add_type(Atomic::TNonEmptyList {
586 value: value.clone(),
587 });
588 }
589 Atomic::TMixed => out.add_type(Atomic::TList {
590 value: Box::new(Type::mixed()),
591 }),
592 _ => {}
593 }
594 }
595 if out.is_empty() {
596 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
597 } else {
598 out
599 }
600 }
601
602 pub fn narrow_to_object(&self) -> Type {
607 let mut out = Type::empty();
608 for t in &self.types {
609 if matches!(t, Atomic::TMixed) {
610 out.add_type(Atomic::TObject);
611 } else if t.is_object() {
612 out.add_type(t.clone());
613 }
614 }
615 if out.types.is_empty() {
616 self.filter(|t| t.is_object())
617 } else {
618 out
619 }
620 }
621
622 pub fn narrow_to_callable(&self) -> Type {
629 self.filter(|t| {
630 t.is_callable()
631 || t.is_string()
632 || t.is_array()
633 || t.is_object()
634 || matches!(t, Atomic::TMixed)
635 })
636 }
637
638 pub fn narrow_to_scalar(&self) -> Type {
640 self.filter(|t| {
641 t.is_string()
642 || t.is_int()
643 || matches!(
644 t,
645 Atomic::TFloat
646 | Atomic::TLiteralFloat(..)
647 | Atomic::TBool
648 | Atomic::TTrue
649 | Atomic::TFalse
650 | Atomic::TScalar
651 | Atomic::TNumeric
652 | Atomic::TNumericString
653 | Atomic::TMixed
654 )
655 })
656 }
657
658 pub fn narrow_to_iterable(&self) -> Type {
661 self.filter(|t| t.is_array() || t.is_object() || matches!(t, Atomic::TMixed))
662 }
663
664 pub fn narrow_to_countable(&self) -> Type {
667 self.filter(|t| t.is_array() || t.is_object() || matches!(t, Atomic::TMixed))
668 }
669
670 pub fn narrow_to_resource(&self) -> Type {
674 self.filter(|t| matches!(t, Atomic::TMixed))
676 }
677
678 pub fn merge(a: &Type, b: &Type) -> Type {
683 if b.types.is_empty() {
685 let mut result = a.clone();
686 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
687 return result;
688 }
689 if a.types.is_empty() {
691 let mut result = b.clone();
692 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
693 return result;
694 }
695 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
697 let mut result = a.clone();
698 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
699 return result;
700 }
701 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
703 return Type {
704 types: smallvec::smallvec![Atomic::TMixed],
705 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
706 from_docblock: a.from_docblock || b.from_docblock,
707 };
708 }
709 let mut result = a.clone();
710 result.merge_with(b);
711 result
712 }
713
714 pub fn merge_with(&mut self, other: &Type) {
716 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
717 self.possibly_undefined |= other.possibly_undefined;
718 return;
719 }
720 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
721 self.types.clear();
722 self.types.push(Atomic::TMixed);
723 self.possibly_undefined |= other.possibly_undefined;
724 return;
725 }
726 for atomic in &other.types {
727 self.add_type(atomic.clone());
728 }
729 self.possibly_undefined |= other.possibly_undefined;
730 }
731
732 pub fn intersect_with(&self, other: &Type) -> Type {
736 if self.is_mixed() {
737 return other.clone();
738 }
739 if other.is_mixed() {
740 return self.clone();
741 }
742 let mut result = Type::empty();
744 for a in &self.types {
745 for b in &other.types {
746 if a == b || atomic_subtype(a, b) || atomic_subtype(b, a) {
747 result.add_type(a.clone());
748 break;
749 }
750 }
751 }
752 if result.is_empty() {
753 Type::never()
754 } else {
755 result
756 }
757 }
758
759 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
763 if bindings.is_empty() {
764 return self.clone();
765 }
766 let mut result = Type::empty();
767 result.possibly_undefined = self.possibly_undefined;
768 result.from_docblock = self.from_docblock;
769 for atomic in &self.types {
770 match atomic {
771 Atomic::TTemplateParam { name, .. } => {
772 if let Some(resolved) = bindings.get(name) {
773 for t in &resolved.types {
774 result.add_type(t.clone());
775 }
776 } else {
777 result.add_type(atomic.clone());
778 }
779 }
780 Atomic::TArray { key, value } => {
781 result.add_type(Atomic::TArray {
782 key: Box::new(key.substitute_templates(bindings)),
783 value: Box::new(value.substitute_templates(bindings)),
784 });
785 }
786 Atomic::TList { value } => {
787 result.add_type(Atomic::TList {
788 value: Box::new(value.substitute_templates(bindings)),
789 });
790 }
791 Atomic::TNonEmptyArray { key, value } => {
792 result.add_type(Atomic::TNonEmptyArray {
793 key: Box::new(key.substitute_templates(bindings)),
794 value: Box::new(value.substitute_templates(bindings)),
795 });
796 }
797 Atomic::TNonEmptyList { value } => {
798 result.add_type(Atomic::TNonEmptyList {
799 value: Box::new(value.substitute_templates(bindings)),
800 });
801 }
802 Atomic::TKeyedArray {
803 properties,
804 is_open,
805 is_list,
806 } => {
807 use crate::atomic::KeyedProperty;
808 let new_props = properties
809 .iter()
810 .map(|(k, prop)| {
811 (
812 k.clone(),
813 KeyedProperty {
814 ty: prop.ty.substitute_templates(bindings),
815 optional: prop.optional,
816 },
817 )
818 })
819 .collect();
820 result.add_type(Atomic::TKeyedArray {
821 properties: new_props,
822 is_open: *is_open,
823 is_list: *is_list,
824 });
825 }
826 Atomic::TCallable {
827 params,
828 return_type,
829 } => {
830 result.add_type(Atomic::TCallable {
831 params: params.as_ref().map(|ps| {
832 ps.iter()
833 .map(|p| substitute_in_fn_param(p, bindings))
834 .collect()
835 }),
836 return_type: return_type
837 .as_ref()
838 .map(|r| Box::new(r.substitute_templates(bindings))),
839 });
840 }
841 Atomic::TClosure {
842 params,
843 return_type,
844 this_type,
845 } => {
846 result.add_type(Atomic::TClosure {
847 params: params
848 .iter()
849 .map(|p| substitute_in_fn_param(p, bindings))
850 .collect(),
851 return_type: Box::new(return_type.substitute_templates(bindings)),
852 this_type: this_type
853 .as_ref()
854 .map(|t| Box::new(t.substitute_templates(bindings))),
855 });
856 }
857 Atomic::TConditional {
858 param_name,
859 subject,
860 if_true,
861 if_false,
862 } => {
863 let new_subject = subject.substitute_templates(bindings);
864 let new_if_true = if_true.substitute_templates(bindings);
865 let new_if_false = if_false.substitute_templates(bindings);
866
867 let resolved = if let Some(name) = param_name {
871 if let Some(bound) = bindings.get(name) {
872 if new_subject.types.len() == 1 {
873 resolve_conditional_branch(
874 &new_subject.types[0],
875 bound,
876 &new_if_true,
877 &new_if_false,
878 )
879 } else {
880 None
881 }
882 } else {
883 None
884 }
885 } else {
886 None
887 };
888
889 if let Some(branch) = resolved {
890 for t in branch.types {
891 result.add_type(t);
892 }
893 } else {
894 result.add_type(Atomic::TConditional {
895 param_name: *param_name,
896 subject: Box::new(new_subject),
897 if_true: Box::new(new_if_true),
898 if_false: Box::new(new_if_false),
899 });
900 }
901 }
902 Atomic::TIntersection { parts } => {
903 result.add_type(Atomic::TIntersection {
904 parts: vec_to_type_params(
905 parts
906 .iter()
907 .map(|p| p.substitute_templates(bindings))
908 .collect(),
909 ),
910 });
911 }
912 Atomic::TNamedObject { fqcn, type_params } => {
913 if type_params.is_empty() && !fqcn.contains('\\') {
920 if let Some(resolved) = bindings.get(fqcn) {
921 for t in &resolved.types {
922 result.add_type(t.clone());
923 }
924 continue;
925 }
926 }
927 let new_params: Vec<Type> = type_params
928 .iter()
929 .map(|p| p.substitute_templates(bindings))
930 .collect();
931 result.add_type(Atomic::TNamedObject {
932 fqcn: *fqcn,
933 type_params: vec_to_type_params(new_params),
934 });
935 }
936 Atomic::TClassString(Some(param_name)) => {
938 if let Some(resolved) = bindings.get(param_name) {
939 for r_atomic in &resolved.types {
940 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
941 Some(*fqcn)
942 } else {
943 None
944 };
945 result.add_type(Atomic::TClassString(cls_name));
946 }
947 } else {
948 result.add_type(atomic.clone());
949 }
950 }
951 _ => {
952 result.add_type(atomic.clone());
953 }
954 }
955 }
956 result
957 }
958
959 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
965 where
966 F: Fn(&str) -> Option<Type>,
967 {
968 self.resolve_conditional_inner(&lookup)
969 }
970
971 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
972 where
973 F: Fn(&str) -> Option<Type>,
974 {
975 let mut result = Type::empty();
976 for atomic in self.types {
977 match atomic {
978 Atomic::TConditional {
979 ref param_name,
980 ref subject,
981 ref if_true,
982 ref if_false,
983 } => {
984 let resolved = if subject.types.len() == 1 {
985 if let Some(name) = param_name {
986 if let Some(arg_ty) = lookup(name.as_ref()) {
987 resolve_conditional_branch(
988 &subject.types[0],
989 &arg_ty,
990 if_true,
991 if_false,
992 )
993 } else {
994 None
995 }
996 } else {
997 None
998 }
999 } else {
1000 None
1001 };
1002
1003 if let Some(branch) = resolved {
1004 for t in branch.resolve_conditional_inner(lookup).types {
1006 result.add_type(t);
1007 }
1008 } else {
1009 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1012 result.add_type(t);
1013 }
1014 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1015 result.add_type(t);
1016 }
1017 }
1018 }
1019 other => result.add_type(other),
1020 }
1021 }
1022 result
1023 }
1024
1025 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1035 if other.is_mixed() {
1036 return true;
1037 }
1038 if self.is_never() {
1039 return true; }
1041 self.types
1042 .iter()
1043 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1044 }
1045
1046 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1049 let mut result = Type::empty();
1050 result.possibly_undefined = self.possibly_undefined;
1051 result.from_docblock = self.from_docblock;
1052 for atomic in &self.types {
1053 if f(atomic) {
1054 result.types.push(atomic.clone());
1055 }
1056 }
1057 result
1058 }
1059
1060 pub fn possibly_undefined(mut self) -> Self {
1062 self.possibly_undefined = true;
1063 self
1064 }
1065
1066 pub fn from_docblock(mut self) -> Self {
1068 self.from_docblock = true;
1069 self
1070 }
1071}
1072
1073fn is_string_atomic(a: &Atomic) -> bool {
1078 matches!(
1079 a,
1080 Atomic::TString
1081 | Atomic::TNonEmptyString
1082 | Atomic::TLiteralString(_)
1083 | Atomic::TNumericString
1084 | Atomic::TClassString(_)
1085 | Atomic::TCallableString
1086 )
1087}
1088
1089fn is_array_atomic(a: &Atomic) -> bool {
1090 matches!(
1091 a,
1092 Atomic::TArray { .. }
1093 | Atomic::TNonEmptyArray { .. }
1094 | Atomic::TKeyedArray { .. }
1095 | Atomic::TList { .. }
1096 | Atomic::TNonEmptyList { .. }
1097 )
1098}
1099
1100fn is_list_atomic(a: &Atomic) -> bool {
1101 match a {
1102 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => true,
1103 Atomic::TKeyedArray { is_list, .. } => *is_list,
1104 _ => false,
1105 }
1106}
1107
1108fn resolve_conditional_branch(
1114 subject: &Atomic,
1115 arg_ty: &Type,
1116 if_true: &Type,
1117 if_false: &Type,
1118) -> Option<Type> {
1119 let predicate: fn(&Atomic) -> bool = match subject {
1120 Atomic::TNull => |a| matches!(a, Atomic::TNull),
1121 Atomic::TTrue => |a| matches!(a, Atomic::TTrue),
1122 Atomic::TFalse => |a| matches!(a, Atomic::TFalse),
1123 Atomic::TString => is_string_atomic,
1124 Atomic::TList { .. } => is_list_atomic,
1125 Atomic::TArray { .. } => is_array_atomic,
1126 _ => return None,
1127 };
1128
1129 if arg_ty.types.is_empty() {
1130 return None;
1131 }
1132 let all_match = arg_ty.types.iter().all(&predicate);
1133 let none_match = !arg_ty.types.iter().any(predicate);
1134 if all_match {
1135 Some(if_true.clone())
1136 } else if none_match {
1137 Some(if_false.clone())
1138 } else {
1139 None
1140 }
1141}
1142
1143fn substitute_in_fn_param(
1148 p: &crate::atomic::FnParam,
1149 bindings: &FxHashMap<Name, Type>,
1150) -> crate::atomic::FnParam {
1151 crate::atomic::FnParam {
1152 name: p.name,
1153 ty: p.ty.as_ref().map(|t| {
1154 let u = t.to_union();
1155 let substituted = u.substitute_templates(bindings);
1156 crate::compact::SimpleType::from_union(substituted)
1157 }),
1158 default: p.default.as_ref().map(|d| {
1159 let u = d.to_union();
1160 let substituted = u.substitute_templates(bindings);
1161 crate::compact::SimpleType::from_union(substituted)
1162 }),
1163 is_variadic: p.is_variadic,
1164 is_byref: p.is_byref,
1165 is_optional: p.is_optional,
1166 }
1167}
1168
1169fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1174 if sub == sup {
1175 return true;
1176 }
1177 match (sub, sup) {
1178 (Atomic::TNever, _) => true,
1180 (_, Atomic::TMixed) => true,
1182 (Atomic::TMixed, _) => true,
1183 (_, Atomic::TTemplateParam { as_type, .. }) => {
1188 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1189 }
1190
1191 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1193 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1194 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1195 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1196 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1197 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1198 (Atomic::TPositiveInt, Atomic::TInt) => true,
1199 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1200 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1201 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1202 (Atomic::TNegativeInt, Atomic::TInt) => true,
1203 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1204 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1205 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1206 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1207 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1208 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1209 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1210 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1211 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1213 max.is_none() && min.is_none_or(|m| m <= 1)
1214 }
1215 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1217 min.is_none() && max.is_none_or(|m| m >= -1)
1218 }
1219 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1221 max.is_none() && min.is_none_or(|m| m <= 0)
1222 }
1223 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1225 sub_min.is_some_and(|lo| lo >= 1)
1226 }
1227 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1228 sub_min.is_some_and(|lo| lo >= 0)
1229 }
1230 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1231 sub_max.is_some_and(|hi| hi <= -1)
1232 }
1233 (
1235 Atomic::TIntRange {
1236 min: sub_min,
1237 max: sub_max,
1238 },
1239 Atomic::TIntRange {
1240 min: sup_min,
1241 max: sup_max,
1242 },
1243 ) => {
1244 let lower_ok = match (sub_min, sup_min) {
1245 (_, None) => true,
1246 (None, Some(_)) => false,
1247 (Some(sl), Some(su)) => sl >= su,
1248 };
1249 let upper_ok = match (sub_max, sup_max) {
1250 (None, None) | (Some(_), None) => true,
1251 (None, Some(_)) => false,
1252 (Some(sl), Some(su)) => sl <= su,
1253 };
1254 lower_ok && upper_ok
1255 }
1256
1257 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1258 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1259 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1260
1261 (Atomic::TLiteralString(s), Atomic::TString) => {
1262 let _ = s;
1263 true
1264 }
1265 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1266 let _ = s;
1267 true
1268 }
1269 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1270 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1271 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1274 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1275 (Atomic::TNonEmptyString, Atomic::TString) => true,
1276 (Atomic::TCallableString, Atomic::TString) => true,
1277 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1279 (Atomic::TNumericString, Atomic::TString) => true,
1280 (Atomic::TClassString(_), Atomic::TString) => true,
1281 (Atomic::TInterfaceString, Atomic::TString) => true,
1282 (Atomic::TEnumString, Atomic::TString) => true,
1283 (Atomic::TTraitString, Atomic::TString) => true,
1284
1285 (Atomic::TTrue, Atomic::TBool) => true,
1286 (Atomic::TFalse, Atomic::TBool) => true,
1287
1288 (Atomic::TInt, Atomic::TNumeric) => true,
1289 (Atomic::TFloat, Atomic::TNumeric) => true,
1290 (Atomic::TNumericString, Atomic::TNumeric) => true,
1291
1292 (Atomic::TInt, Atomic::TScalar) => true,
1293 (Atomic::TFloat, Atomic::TScalar) => true,
1294 (Atomic::TString, Atomic::TScalar) => true,
1295 (Atomic::TBool, Atomic::TScalar) => true,
1296 (Atomic::TNumeric, Atomic::TScalar) => true,
1297 (Atomic::TTrue, Atomic::TScalar) => true,
1298 (Atomic::TFalse, Atomic::TScalar) => true,
1299
1300 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1302 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1303 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1304 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1306 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1307 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1309 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1310 (
1314 Atomic::TNamedObject {
1315 fqcn: sub_fqcn,
1316 type_params: sub_params,
1317 },
1318 Atomic::TNamedObject {
1319 fqcn: sup_fqcn,
1320 type_params: sup_params,
1321 },
1322 ) => {
1323 sub_fqcn == sup_fqcn
1324 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1325 }
1326
1327 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1329 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1330 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1331 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1332 (Atomic::TInt, Atomic::TFloat) => true,
1333 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1334
1335 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1337 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1338 }
1339
1340 (Atomic::TString, Atomic::TCallable { .. }) => true,
1342 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1343 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1344 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1345 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1346 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1347
1348 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1350 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1352 (Atomic::TClosure { .. }, Atomic::TClosure { .. }) => true,
1354 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
1356 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
1358 fqcn.as_ref().eq_ignore_ascii_case("closure")
1359 }
1360 (Atomic::TClosure { .. }, Atomic::TObject) => true,
1361 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
1363 fqcn.as_ref().eq_ignore_ascii_case("closure")
1364 }
1365 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
1367 fqcn.as_ref().eq_ignore_ascii_case("closure")
1368 }
1369
1370 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
1372 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1373 }
1374 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
1375 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1376 }
1377 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
1378 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
1379 }
1380 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
1381 value.is_subtype_structural(lv)
1382 }
1383 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
1385 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1386 && av.is_subtype_structural(lv)
1387 }
1388 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1389 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1390 && av.is_subtype_structural(lv)
1391 }
1392 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
1393 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1394 && av.is_subtype_structural(lv)
1395 }
1396 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
1397 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
1398 && av.is_subtype_structural(lv)
1399 }
1400 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
1402 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1403 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1404 }
1405
1406 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
1408 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
1409 }
1410
1411 (
1417 Atomic::TKeyedArray {
1418 properties,
1419 is_open,
1420 ..
1421 },
1422 Atomic::TArray { key, value },
1423 ) => {
1424 if *is_open {
1425 return true;
1426 }
1427 properties.iter().all(|(prop_key, prop)| {
1428 let key_atomic = match prop_key {
1429 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1430 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1431 };
1432 if !Type::single(key_atomic).is_subtype_structural(key) {
1433 return false; }
1435 let has_named_obj = prop.ty.types.iter().any(|a| {
1437 matches!(
1438 a,
1439 Atomic::TNamedObject { .. }
1440 | Atomic::TSelf { .. }
1441 | Atomic::TStaticObject { .. }
1442 | Atomic::TClosure { .. }
1443 | Atomic::TTemplateParam { .. }
1444 )
1445 });
1446 has_named_obj || prop.ty.is_subtype_structural(value)
1447 })
1448 }
1449 (
1450 Atomic::TKeyedArray {
1451 properties,
1452 is_open,
1453 ..
1454 },
1455 Atomic::TNonEmptyArray { key, value },
1456 ) => {
1457 if *is_open {
1458 return !properties.is_empty();
1459 }
1460 properties.iter().any(|(_, p)| !p.optional)
1461 && properties.iter().all(|(prop_key, prop)| {
1462 let key_atomic = match prop_key {
1463 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
1464 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
1465 };
1466 if !Type::single(key_atomic).is_subtype_structural(key) {
1467 return false;
1468 }
1469 let has_named_obj = prop.ty.types.iter().any(|a| {
1470 matches!(
1471 a,
1472 Atomic::TNamedObject { .. }
1473 | Atomic::TSelf { .. }
1474 | Atomic::TStaticObject { .. }
1475 | Atomic::TClosure { .. }
1476 | Atomic::TTemplateParam { .. }
1477 )
1478 });
1479 has_named_obj || prop.ty.is_subtype_structural(value)
1480 })
1481 }
1482
1483 (
1485 Atomic::TKeyedArray {
1486 properties,
1487 is_list,
1488 ..
1489 },
1490 Atomic::TList { value: lv },
1491 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
1492 (
1493 Atomic::TKeyedArray {
1494 properties,
1495 is_list,
1496 ..
1497 },
1498 Atomic::TNonEmptyList { value: lv },
1499 ) => {
1500 *is_list
1501 && !properties.is_empty()
1502 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
1503 }
1504
1505 _ => false,
1506 }
1507}
1508
1509fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
1515 if sub.len() != sup.len() {
1516 return false;
1517 }
1518 sub.iter()
1519 .zip(sup.iter())
1520 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
1521}
1522
1523fn is_empty_array_literal(t: &Type) -> bool {
1526 !t.types.is_empty()
1527 && t.types.iter().all(
1528 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
1529 )
1530}
1531
1532fn is_array_like(t: &Type) -> bool {
1534 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
1535}
1536
1537#[cfg(test)]
1542mod tests {
1543 use std::sync::Arc;
1544
1545 use super::*;
1546
1547 #[test]
1548 fn single_is_single() {
1549 let u = Type::single(Atomic::TString);
1550 assert!(u.is_single());
1551 assert!(!u.is_nullable());
1552 }
1553
1554 #[test]
1555 fn nullable_has_null() {
1556 let u = Type::nullable(Atomic::TString);
1557 assert!(u.is_nullable());
1558 assert_eq!(u.types.len(), 2);
1559 }
1560
1561 #[test]
1562 fn add_type_deduplicates() {
1563 let mut u = Type::single(Atomic::TString);
1564 u.add_type(Atomic::TString);
1565 assert_eq!(u.types.len(), 1);
1566 }
1567
1568 #[test]
1569 fn add_type_literal_subsumed_by_base() {
1570 let mut u = Type::single(Atomic::TInt);
1571 u.add_type(Atomic::TLiteralInt(42));
1572 assert_eq!(u.types.len(), 1);
1573 assert!(matches!(u.types[0], Atomic::TInt));
1574 }
1575
1576 #[test]
1577 fn add_type_base_widens_literals() {
1578 let mut u = Type::single(Atomic::TLiteralInt(1));
1579 u.add_type(Atomic::TLiteralInt(2));
1580 u.add_type(Atomic::TInt);
1581 assert_eq!(u.types.len(), 1);
1582 assert!(matches!(u.types[0], Atomic::TInt));
1583 }
1584
1585 #[test]
1586 fn mixed_subsumes_everything() {
1587 let mut u = Type::single(Atomic::TString);
1588 u.add_type(Atomic::TMixed);
1589 assert_eq!(u.types.len(), 1);
1590 assert!(u.is_mixed());
1591 }
1592
1593 #[test]
1594 fn remove_null() {
1595 let u = Type::nullable(Atomic::TString);
1596 let narrowed = u.remove_null();
1597 assert!(!narrowed.is_nullable());
1598 assert_eq!(narrowed.types.len(), 1);
1599 }
1600
1601 #[test]
1602 fn narrow_to_truthy_removes_null_false() {
1603 let mut u = Type::empty();
1604 u.add_type(Atomic::TString);
1605 u.add_type(Atomic::TNull);
1606 u.add_type(Atomic::TFalse);
1607 let truthy = u.narrow_to_truthy();
1608 assert!(!truthy.is_nullable());
1609 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
1610 }
1611
1612 #[test]
1613 fn merge_combines_types() {
1614 let a = Type::single(Atomic::TString);
1615 let b = Type::single(Atomic::TInt);
1616 let merged = Type::merge(&a, &b);
1617 assert_eq!(merged.types.len(), 2);
1618 }
1619
1620 #[test]
1621 fn subtype_literal_int_under_int() {
1622 let sub = Type::single(Atomic::TLiteralInt(5));
1623 let sup = Type::single(Atomic::TInt);
1624 assert!(sub.is_subtype_structural(&sup));
1625 }
1626
1627 #[test]
1628 fn subtype_never_is_bottom() {
1629 let never = Type::never();
1630 let string = Type::single(Atomic::TString);
1631 assert!(never.is_subtype_structural(&string));
1632 }
1633
1634 #[test]
1635 fn subtype_everything_under_mixed() {
1636 let string = Type::single(Atomic::TString);
1637 let mixed = Type::mixed();
1638 assert!(string.is_subtype_structural(&mixed));
1639 }
1640
1641 #[test]
1642 fn template_substitution() {
1643 let mut bindings = FxHashMap::default();
1644 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
1645
1646 let tmpl = Type::single(Atomic::TTemplateParam {
1647 name: Name::new("T"),
1648 as_type: Box::new(Type::mixed()),
1649 defining_entity: Name::new("MyClass"),
1650 });
1651
1652 let resolved = tmpl.substitute_templates(&bindings);
1653 assert_eq!(resolved.types.len(), 1);
1654 assert!(matches!(resolved.types[0], Atomic::TString));
1655 }
1656
1657 #[test]
1658 fn intersection_is_object() {
1659 let parts = vec![
1660 Type::single(Atomic::TNamedObject {
1661 fqcn: Name::new("Iterator"),
1662 type_params: empty_type_params(),
1663 }),
1664 Type::single(Atomic::TNamedObject {
1665 fqcn: Name::new("Countable"),
1666 type_params: empty_type_params(),
1667 }),
1668 ];
1669 let atomic = Atomic::TIntersection {
1670 parts: vec_to_type_params(parts),
1671 };
1672 assert!(atomic.is_object());
1673 assert!(!atomic.can_be_falsy());
1674 assert!(atomic.can_be_truthy());
1675 }
1676
1677 #[test]
1678 fn intersection_display_two_parts() {
1679 let parts = vec![
1680 Type::single(Atomic::TNamedObject {
1681 fqcn: Name::new("Iterator"),
1682 type_params: empty_type_params(),
1683 }),
1684 Type::single(Atomic::TNamedObject {
1685 fqcn: Name::new("Countable"),
1686 type_params: empty_type_params(),
1687 }),
1688 ];
1689 let u = Type::single(Atomic::TIntersection {
1690 parts: vec_to_type_params(parts),
1691 });
1692 assert_eq!(format!("{u}"), "Iterator&Countable");
1693 }
1694
1695 #[test]
1696 fn intersection_display_three_parts() {
1697 let parts = vec![
1698 Type::single(Atomic::TNamedObject {
1699 fqcn: Name::new("A"),
1700 type_params: empty_type_params(),
1701 }),
1702 Type::single(Atomic::TNamedObject {
1703 fqcn: Name::new("B"),
1704 type_params: empty_type_params(),
1705 }),
1706 Type::single(Atomic::TNamedObject {
1707 fqcn: Name::new("C"),
1708 type_params: empty_type_params(),
1709 }),
1710 ];
1711 let u = Type::single(Atomic::TIntersection {
1712 parts: vec_to_type_params(parts),
1713 });
1714 assert_eq!(format!("{u}"), "A&B&C");
1715 }
1716
1717 #[test]
1718 fn intersection_in_nullable_union_display() {
1719 let intersection = Atomic::TIntersection {
1720 parts: vec_to_type_params(vec![
1721 Type::single(Atomic::TNamedObject {
1722 fqcn: Name::new("Iterator"),
1723 type_params: empty_type_params(),
1724 }),
1725 Type::single(Atomic::TNamedObject {
1726 fqcn: Name::new("Countable"),
1727 type_params: empty_type_params(),
1728 }),
1729 ]),
1730 };
1731 let mut u = Type::single(intersection);
1732 u.add_type(Atomic::TNull);
1733 assert!(u.is_nullable());
1734 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
1735 }
1736
1737 fn t_param(name: &str) -> Type {
1740 Type::single(Atomic::TTemplateParam {
1741 name: Name::new(name),
1742 as_type: Box::new(Type::mixed()),
1743 defining_entity: Name::new("Fn"),
1744 })
1745 }
1746
1747 fn bindings_t_string() -> FxHashMap<Name, Type> {
1748 let mut b = FxHashMap::default();
1749 b.insert(Name::new("T"), Type::single(Atomic::TString));
1750 b
1751 }
1752
1753 #[test]
1754 fn substitute_non_empty_array_key_and_value() {
1755 let ty = Type::single(Atomic::TNonEmptyArray {
1756 key: Box::new(t_param("T")),
1757 value: Box::new(t_param("T")),
1758 });
1759 let result = ty.substitute_templates(&bindings_t_string());
1760 assert_eq!(result.types.len(), 1);
1761 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
1762 panic!("expected TNonEmptyArray");
1763 };
1764 assert!(matches!(key.types[0], Atomic::TString));
1765 assert!(matches!(value.types[0], Atomic::TString));
1766 }
1767
1768 #[test]
1769 fn substitute_non_empty_list_value() {
1770 let ty = Type::single(Atomic::TNonEmptyList {
1771 value: Box::new(t_param("T")),
1772 });
1773 let result = ty.substitute_templates(&bindings_t_string());
1774 let Atomic::TNonEmptyList { value } = &result.types[0] else {
1775 panic!("expected TNonEmptyList");
1776 };
1777 assert!(matches!(value.types[0], Atomic::TString));
1778 }
1779
1780 #[test]
1781 fn substitute_keyed_array_property_types() {
1782 use crate::atomic::{ArrayKey, KeyedProperty};
1783 use indexmap::IndexMap;
1784 let mut props = IndexMap::new();
1785 props.insert(
1786 ArrayKey::String(Arc::from("name")),
1787 KeyedProperty {
1788 ty: t_param("T"),
1789 optional: false,
1790 },
1791 );
1792 props.insert(
1793 ArrayKey::String(Arc::from("tag")),
1794 KeyedProperty {
1795 ty: t_param("T"),
1796 optional: true,
1797 },
1798 );
1799 let ty = Type::single(Atomic::TKeyedArray {
1800 properties: props,
1801 is_open: true,
1802 is_list: false,
1803 });
1804 let result = ty.substitute_templates(&bindings_t_string());
1805 let Atomic::TKeyedArray {
1806 properties,
1807 is_open,
1808 is_list,
1809 } = &result.types[0]
1810 else {
1811 panic!("expected TKeyedArray");
1812 };
1813 assert!(is_open);
1814 assert!(!is_list);
1815 assert!(matches!(
1816 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
1817 Atomic::TString
1818 ));
1819 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
1820 assert!(matches!(
1821 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
1822 Atomic::TString
1823 ));
1824 }
1825
1826 #[test]
1827 fn substitute_callable_params_and_return() {
1828 use crate::atomic::FnParam;
1829 let ty = Type::single(Atomic::TCallable {
1830 params: Some(vec![FnParam {
1831 name: Name::new("x"),
1832 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
1833 default: None,
1834 is_variadic: false,
1835 is_byref: false,
1836 is_optional: false,
1837 }]),
1838 return_type: Some(Box::new(t_param("T"))),
1839 });
1840 let result = ty.substitute_templates(&bindings_t_string());
1841 let Atomic::TCallable {
1842 params,
1843 return_type,
1844 } = &result.types[0]
1845 else {
1846 panic!("expected TCallable");
1847 };
1848 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
1849 let param_union = param_ty.to_union();
1850 assert!(matches!(param_union.types[0], Atomic::TString));
1851 let ret = return_type.as_ref().unwrap();
1852 assert!(matches!(ret.types[0], Atomic::TString));
1853 }
1854
1855 #[test]
1856 fn substitute_callable_bare_no_panic() {
1857 let ty = Type::single(Atomic::TCallable {
1859 params: None,
1860 return_type: None,
1861 });
1862 let result = ty.substitute_templates(&bindings_t_string());
1863 assert!(matches!(
1864 result.types[0],
1865 Atomic::TCallable {
1866 params: None,
1867 return_type: None
1868 }
1869 ));
1870 }
1871
1872 #[test]
1873 fn substitute_closure_params_return_and_this() {
1874 use crate::atomic::FnParam;
1875 let ty = Type::single(Atomic::TClosure {
1876 params: vec![FnParam {
1877 name: Name::new("a"),
1878 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
1879 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
1880 is_variadic: true,
1881 is_byref: true,
1882 is_optional: true,
1883 }],
1884 return_type: Box::new(t_param("T")),
1885 this_type: Some(Box::new(t_param("T"))),
1886 });
1887 let result = ty.substitute_templates(&bindings_t_string());
1888 let Atomic::TClosure {
1889 params,
1890 return_type,
1891 this_type,
1892 } = &result.types[0]
1893 else {
1894 panic!("expected TClosure");
1895 };
1896 let p = ¶ms[0];
1897 let ty_union = p.ty.as_ref().unwrap().to_union();
1898 let default_union = p.default.as_ref().unwrap().to_union();
1899 assert!(matches!(ty_union.types[0], Atomic::TString));
1900 assert!(matches!(default_union.types[0], Atomic::TString));
1901 assert!(p.is_variadic);
1903 assert!(p.is_byref);
1904 assert!(p.is_optional);
1905 assert!(matches!(return_type.types[0], Atomic::TString));
1906 assert!(matches!(
1907 this_type.as_ref().unwrap().types[0],
1908 Atomic::TString
1909 ));
1910 }
1911
1912 #[test]
1913 fn substitute_conditional_all_branches() {
1914 let ty = Type::single(Atomic::TConditional {
1915 param_name: None,
1916 subject: Box::new(t_param("T")),
1917 if_true: Box::new(t_param("T")),
1918 if_false: Box::new(Type::single(Atomic::TInt)),
1919 });
1920 let result = ty.substitute_templates(&bindings_t_string());
1921 let Atomic::TConditional {
1922 param_name: _,
1923 subject,
1924 if_true,
1925 if_false,
1926 } = &result.types[0]
1927 else {
1928 panic!("expected TConditional");
1929 };
1930 assert!(matches!(subject.types[0], Atomic::TString));
1931 assert!(matches!(if_true.types[0], Atomic::TString));
1932 assert!(matches!(if_false.types[0], Atomic::TInt));
1933 }
1934
1935 #[test]
1936 fn resolve_conditional_is_null_non_null_arg() {
1937 let ty = Type::single(Atomic::TConditional {
1938 param_name: Some(Name::new("x")),
1939 subject: Box::new(Type::single(Atomic::TNull)),
1940 if_true: Box::new(Type::single(Atomic::TInt)),
1941 if_false: Box::new(Type::single(Atomic::TString)),
1942 });
1943 let result = ty.resolve_conditional_returns(|name| {
1944 if name == "x" {
1945 Some(Type::single(Atomic::TString)) } else {
1947 None
1948 }
1949 });
1950 assert!(result.types.len() == 1);
1951 assert!(matches!(result.types[0], Atomic::TString));
1952 }
1953
1954 #[test]
1955 fn resolve_conditional_is_null_null_arg() {
1956 let ty = Type::single(Atomic::TConditional {
1957 param_name: Some(Name::new("x")),
1958 subject: Box::new(Type::single(Atomic::TNull)),
1959 if_true: Box::new(Type::single(Atomic::TInt)),
1960 if_false: Box::new(Type::single(Atomic::TString)),
1961 });
1962 let result = ty.resolve_conditional_returns(|name| {
1963 if name == "x" {
1964 Some(Type::single(Atomic::TNull)) } else {
1966 None
1967 }
1968 });
1969 assert!(result.types.len() == 1);
1970 assert!(matches!(result.types[0], Atomic::TInt));
1971 }
1972
1973 #[test]
1974 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
1975 let mut nullable_str = Type::single(Atomic::TString);
1976 nullable_str.add_type(Atomic::TNull);
1977 let ty = Type::single(Atomic::TConditional {
1978 param_name: Some(Name::new("x")),
1979 subject: Box::new(Type::single(Atomic::TNull)),
1980 if_true: Box::new(Type::single(Atomic::TInt)),
1981 if_false: Box::new(Type::single(Atomic::TString)),
1982 });
1983 let result = ty.resolve_conditional_returns(|name| {
1984 if name == "x" {
1985 Some(nullable_str.clone())
1986 } else {
1987 None
1988 }
1989 });
1990 assert_eq!(result.types.len(), 2);
1992 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
1993 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
1994 }
1995
1996 #[test]
1997 fn resolve_conditional_nested_widens_inner_branch() {
1998 let inner = Type::single(Atomic::TConditional {
2001 param_name: Some(Name::new("x")),
2002 subject: Box::new(Type::single(Atomic::TString)),
2003 if_true: Box::new(Type::single(Atomic::TString)),
2004 if_false: Box::new(Type::single(Atomic::TFloat)),
2005 });
2006 let ty = Type::single(Atomic::TConditional {
2007 param_name: Some(Name::new("x")),
2008 subject: Box::new(Type::single(Atomic::TNull)),
2009 if_true: Box::new(Type::single(Atomic::TInt)),
2010 if_false: Box::new(inner),
2011 });
2012 let result = ty.resolve_conditional_returns(|_| None);
2014 assert!(
2015 result
2016 .types
2017 .iter()
2018 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2019 "no TConditional should survive: {:?}",
2020 result.types
2021 );
2022 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2023 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2024 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2025 }
2026
2027 #[test]
2028 fn resolve_conditional_nested_resolves_inner_branch() {
2029 let inner = Type::single(Atomic::TConditional {
2033 param_name: Some(Name::new("x")),
2034 subject: Box::new(Type::single(Atomic::TString)),
2035 if_true: Box::new(Type::single(Atomic::TString)),
2036 if_false: Box::new(Type::single(Atomic::TFloat)),
2037 });
2038 let ty = Type::single(Atomic::TConditional {
2039 param_name: Some(Name::new("x")),
2040 subject: Box::new(Type::single(Atomic::TNull)),
2041 if_true: Box::new(Type::single(Atomic::TInt)),
2042 if_false: Box::new(inner),
2043 });
2044 let result = ty.resolve_conditional_returns(|name| {
2046 if name == "x" {
2047 Some(Type::single(Atomic::TString))
2048 } else {
2049 None
2050 }
2051 });
2052 assert!(
2053 result
2054 .types
2055 .iter()
2056 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2057 "no TConditional should survive: {:?}",
2058 result.types
2059 );
2060 assert_eq!(result.types.len(), 1);
2061 assert!(matches!(result.types[0], Atomic::TString));
2062 }
2063
2064 #[test]
2065 fn substitute_intersection_parts() {
2066 let ty = Type::single(Atomic::TIntersection {
2067 parts: vec_to_type_params(vec![
2068 Type::single(Atomic::TNamedObject {
2069 fqcn: Name::new("Countable"),
2070 type_params: empty_type_params(),
2071 }),
2072 t_param("T"),
2073 ]),
2074 });
2075 let result = ty.substitute_templates(&bindings_t_string());
2076 let Atomic::TIntersection { parts } = &result.types[0] else {
2077 panic!("expected TIntersection");
2078 };
2079 assert_eq!(parts.len(), 2);
2080 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
2081 assert!(matches!(parts[1].types[0], Atomic::TString));
2082 }
2083
2084 #[test]
2085 fn substitute_no_template_params_identity() {
2086 let ty = Type::single(Atomic::TInt);
2087 let result = ty.substitute_templates(&bindings_t_string());
2088 assert!(matches!(result.types[0], Atomic::TInt));
2089 }
2090}