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 pub falsy_stripped: bool,
62 pub possibly_absent_offset: bool,
73}
74
75type IntBounds = (Option<i64>, Option<i64>);
76
77fn int_bounds(a: &Atomic) -> Option<IntBounds> {
79 match a {
80 Atomic::TInt => Some((None, None)),
81 Atomic::TLiteralInt(n) => Some((Some(*n), Some(*n))),
82 Atomic::TPositiveInt => Some((Some(1), None)),
83 Atomic::TNonNegativeInt => Some((Some(0), None)),
84 Atomic::TNegativeInt => Some((None, Some(-1))),
85 Atomic::TIntRange { min, max } => Some((*min, *max)),
86 _ => None,
87 }
88}
89
90fn bounds_contain(outer: IntBounds, inner: IntBounds) -> bool {
91 let lo_ok = match (outer.0, inner.0) {
92 (None, _) => true,
93 (Some(o), Some(i)) => o <= i,
94 (Some(_), None) => false,
95 };
96 let hi_ok = match (outer.1, inner.1) {
97 (None, _) => true,
98 (Some(o), Some(i)) => o >= i,
99 (Some(_), None) => false,
100 };
101 lo_ok && hi_ok
102}
103
104impl Type {
105 pub fn empty() -> Self {
108 Self {
109 types: SmallVec::new(),
110 possibly_undefined: false,
111 from_docblock: false,
112 falsy_stripped: false,
113 possibly_absent_offset: false,
114 }
115 }
116
117 pub fn single(atomic: Atomic) -> Self {
118 let mut types = SmallVec::new();
119 types.push(atomic);
120 Self {
121 types,
122 possibly_undefined: false,
123 from_docblock: false,
124 falsy_stripped: false,
125 possibly_absent_offset: false,
126 }
127 }
128
129 pub fn mixed() -> Self {
130 Self::single(Atomic::TMixed)
131 }
132
133 pub fn void() -> Self {
134 Self::single(Atomic::TVoid)
135 }
136
137 pub fn never() -> Self {
138 Self::single(Atomic::TNever)
139 }
140
141 pub fn null() -> Self {
142 Self::single(Atomic::TNull)
143 }
144
145 pub fn bool() -> Self {
146 Self::single(Atomic::TBool)
147 }
148
149 pub fn int() -> Self {
150 Self::single(Atomic::TInt)
151 }
152
153 pub fn float() -> Self {
154 Self::single(Atomic::TFloat)
155 }
156
157 pub fn string() -> Self {
158 Self::single(Atomic::TString)
159 }
160
161 pub fn array_key() -> Self {
164 let mut u = Self::single(Atomic::TInt);
165 u.add_type(Atomic::TString);
166 u
167 }
168
169 pub fn nullable(atomic: Atomic) -> Self {
171 if matches!(atomic, Atomic::TMixed) {
173 return Self::mixed();
174 }
175 let mut types = SmallVec::new();
176 types.push(atomic);
177 types.push(Atomic::TNull);
178 Self {
179 types,
180 possibly_undefined: false,
181 from_docblock: false,
182 falsy_stripped: false,
183 possibly_absent_offset: false,
184 }
185 }
186
187 pub fn from_vec(atomics: Vec<Atomic>) -> Self {
189 let mut u = Self::empty();
190 for a in atomics {
191 u.add_type(a);
192 }
193 u
194 }
195
196 pub fn is_empty(&self) -> bool {
199 self.types.is_empty()
200 }
201
202 pub fn is_single(&self) -> bool {
203 self.types.len() == 1
204 }
205
206 pub fn is_nullable(&self) -> bool {
207 self.types.iter().any(|t| matches!(t, Atomic::TNull))
208 }
209
210 pub fn is_array_key(&self) -> bool {
215 self.types.len() == 2
216 && self.types.iter().any(|t| matches!(t, Atomic::TInt))
217 && self.types.iter().any(|t| matches!(t, Atomic::TString))
218 }
219
220 pub fn is_mixed(&self) -> bool {
221 self.types.iter().any(|t| match t {
222 Atomic::TMixed => true,
223 Atomic::TTemplateParam { as_type, .. } => as_type.is_mixed(),
224 _ => false,
225 })
226 }
227
228 pub fn is_mixed_not_template(&self) -> bool {
233 self.is_mixed()
234 && !self
235 .types
236 .iter()
237 .any(|t| matches!(t, Atomic::TTemplateParam { .. }))
238 }
239
240 pub fn is_never(&self) -> bool {
241 self.types.iter().all(|t| matches!(t, Atomic::TNever)) && !self.types.is_empty()
242 }
243
244 pub fn clone_validity(&self) -> CloneValidity {
247 if self.types.is_empty() {
248 return CloneValidity::Unknown;
249 }
250 let mut has_non_object = false;
251 let mut has_other = false; for t in &self.types {
253 match t {
254 Atomic::TTemplateParam { as_type, .. } => match as_type.clone_validity() {
255 CloneValidity::Invalid => has_non_object = true,
256 CloneValidity::PossiblyInvalid => {
257 has_non_object = true;
258 has_other = true;
259 }
260 CloneValidity::Cloneable | CloneValidity::Unknown => has_other = true,
261 },
262 other if other.is_definitely_non_object() => has_non_object = true,
263 _ => has_other = true,
264 }
265 }
266 match (has_non_object, has_other) {
267 (true, false) => CloneValidity::Invalid,
268 (true, true) => CloneValidity::PossiblyInvalid,
269 _ => CloneValidity::Cloneable,
270 }
271 }
272
273 pub fn is_void(&self) -> bool {
274 self.is_single() && matches!(self.types[0], Atomic::TVoid)
275 }
276
277 pub fn can_be_falsy(&self) -> bool {
278 self.types.iter().any(|t| t.can_be_falsy())
279 }
280
281 pub fn can_be_truthy(&self) -> bool {
282 self.types.iter().any(|t| t.can_be_truthy())
283 }
284
285 pub fn contains<F: Fn(&Atomic) -> bool>(&self, f: F) -> bool {
286 self.types.iter().any(f)
287 }
288
289 pub fn has_named_object(&self, fqcn: &str) -> bool {
290 self.types.iter().any(|t| match t {
291 Atomic::TNamedObject { fqcn: f, .. } => f.as_ref() == fqcn,
292 _ => false,
293 })
294 }
295
296 pub fn add_type(&mut self, atomic: Atomic) {
301 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
303 return;
304 }
305
306 if matches!(atomic, Atomic::TMixed) {
308 self.types.clear();
309 self.types.push(Atomic::TMixed);
310 return;
311 }
312
313 let atomic = if let Atomic::TConditional { data } = &atomic {
316 let (if_true, if_false) = (&data.if_true, &data.if_false);
317 let mut simplified_true = Type::empty();
318 for t in &if_true.types {
319 simplified_true.add_type(t.clone());
320 }
321 let mut simplified_false = Type::empty();
322 for t in &if_false.types {
323 simplified_false.add_type(t.clone());
324 }
325 if simplified_true == simplified_false {
326 for t in simplified_true.types {
327 self.add_type(t);
328 }
329 return;
330 }
331 atomic
332 } else {
333 atomic
334 };
335
336 if self.types.contains(&atomic) {
338 return;
339 }
340
341 if let Some(new_bounds) = int_bounds(&atomic) {
344 if self
345 .types
346 .iter()
347 .any(|t| int_bounds(t).is_some_and(|b| bounds_contain(b, new_bounds)))
348 {
349 return;
350 }
351 self.types
352 .retain(|t| !int_bounds(t).is_some_and(|b| bounds_contain(new_bounds, b)));
353 }
354 if let Atomic::TLiteralString(_) = &atomic {
356 if self.types.iter().any(|t| matches!(t, Atomic::TString)) {
357 return;
358 }
359 }
360 if matches!(atomic, Atomic::TNonEmptyString)
362 && self.types.iter().any(|t| matches!(t, Atomic::TString))
363 {
364 return;
365 }
366 if matches!(atomic, Atomic::TTrue | Atomic::TFalse)
368 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
369 {
370 return;
371 }
372 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
375 {
376 self.types.retain(|t| !matches!(t, Atomic::TFalse));
377 self.types.push(Atomic::TBool);
378 return;
379 }
380 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
381 {
382 self.types.retain(|t| !matches!(t, Atomic::TTrue));
383 self.types.push(Atomic::TBool);
384 return;
385 }
386 if matches!(atomic, Atomic::TString) {
388 self.types
389 .retain(|t| !matches!(t, Atomic::TLiteralString(_) | Atomic::TNonEmptyString));
390 }
391 if matches!(atomic, Atomic::TBool) {
393 self.types
394 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
395 }
396
397 if matches!(atomic, Atomic::TNever) {
399 if !self.types.is_empty() {
400 return;
401 }
402 } else {
403 self.types.retain(|t| !matches!(t, Atomic::TNever));
404 }
405
406 if let Atomic::TKeyedArray {
412 properties,
413 is_open,
414 ..
415 } = &atomic
416 {
417 if properties.is_empty() && !is_open {
418 for existing in &self.types {
419 match existing {
420 Atomic::TArray { .. }
421 | Atomic::TNonEmptyArray { .. }
422 | Atomic::TList { .. }
423 | Atomic::TNonEmptyList { .. } => {
424 return; }
426 _ => {}
427 }
428 }
429 }
430 }
431
432 let is_generic_array_or_list = matches!(
435 &atomic,
436 Atomic::TArray { .. }
437 | Atomic::TNonEmptyArray { .. }
438 | Atomic::TList { .. }
439 | Atomic::TNonEmptyList { .. }
440 );
441 if is_generic_array_or_list {
442 self.types.retain(|t| {
443 if let Atomic::TKeyedArray {
444 properties,
445 is_open,
446 ..
447 } = t
448 {
449 !properties.is_empty() || *is_open
450 } else {
451 true
452 }
453 });
454 }
455
456 self.types.push(atomic);
457 }
458
459 pub fn remove_null(&self) -> Type {
463 self.filter(|t| !matches!(t, Atomic::TNull))
464 }
465
466 pub fn remove_false(&self) -> Type {
469 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
470 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
471 result.add_type(Atomic::TTrue);
472 }
473 result
474 }
475
476 pub fn remove_true(&self) -> Type {
479 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
480 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
481 result.add_type(Atomic::TFalse);
482 }
483 result
484 }
485
486 pub fn core_type(&self) -> Type {
488 self.remove_null().remove_false()
489 }
490
491 pub fn narrow_to_truthy(&self) -> Type {
493 if self.is_mixed_not_template() {
494 return Type::mixed();
495 }
496 let mut result = Type::empty();
497 result.from_docblock = self.from_docblock;
498 for t in &self.types {
499 match t {
500 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
504 Atomic::TLiteralInt(0)
506 | Atomic::TLiteralFloat(0, 0)
507 | Atomic::TNull
508 | Atomic::TFalse => {}
509 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
510 Atomic::TBool => result.add_type(Atomic::TTrue),
512 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
514 key: key.clone(),
515 value: value.clone(),
516 }),
517 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
518 value: value.clone(),
519 }),
520 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
524 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
529 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
530 let atom = if max.is_none() {
531 Atomic::TPositiveInt
532 } else {
533 Atomic::TIntRange {
534 min: Some(1),
535 max: *max,
536 }
537 };
538 result.add_type(atom);
539 }
540 Atomic::TIntRange { min, max: Some(0) } => {
542 let atom = match min {
543 None => Atomic::TNegativeInt,
544 Some(n) if *n <= -1 => Atomic::TIntRange {
545 min: *min,
546 max: Some(-1),
547 },
548 _ => continue, };
550 result.add_type(atom);
551 }
552 t if !t.can_be_truthy() => {}
554 _ => result.add_type(t.clone()),
555 }
556 }
557 result
558 }
559
560 pub fn narrow_to_falsy(&self) -> Type {
562 if self.is_mixed_not_template() {
563 return Type::from_vec(vec![
564 Atomic::TNull,
565 Atomic::TFalse,
566 Atomic::TLiteralInt(0),
567 Atomic::TLiteralString("".into()),
568 ]);
569 }
570 let mut result = Type::empty();
571 result.from_docblock = self.from_docblock;
572 for t in &self.types {
573 match t {
574 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
579 Atomic::TBool => result.add_type(Atomic::TFalse),
581 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
583 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
585 Atomic::TString => {
587 result.add_type(Atomic::TLiteralString("".into()));
588 result.add_type(Atomic::TLiteralString("0".into()));
589 }
590 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
592 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
594 Atomic::TIntRange {
596 min: Some(0),
597 max: Some(_) | None,
598 } => result.add_type(Atomic::TLiteralInt(0)),
599 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
601 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
603 }
604 }
605 result
606 }
607
608 pub fn narrow_instanceof(&self, class: &str) -> Type {
614 let narrowed_ty = Atomic::TNamedObject {
615 fqcn: class.into(),
616 type_params: empty_type_params(),
617 };
618 let has_object = self.types.iter().any(|t| {
620 matches!(
621 t,
622 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
624 });
625 if has_object || self.is_empty() {
626 Type::single(narrowed_ty)
627 } else {
628 Type::single(narrowed_ty)
631 }
632 }
633
634 pub fn narrow_to_string(&self) -> Type {
638 self.filter_replacing(
639 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
640 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
641 Atomic::TString,
642 )
643 }
644
645 pub fn narrow_to_int(&self) -> Type {
647 self.filter_replacing(
648 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
649 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
650 Atomic::TInt,
651 )
652 }
653
654 pub fn narrow_to_float(&self) -> Type {
656 self.filter_replacing(
657 |t| {
658 matches!(
659 t,
660 Atomic::TFloat
661 | Atomic::TIntegralFloat
662 | Atomic::TLiteralFloat(..)
663 | Atomic::TTemplateParam { .. }
664 )
665 },
666 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
667 Atomic::TFloat,
668 )
669 }
670
671 pub fn narrow_to_bool(&self) -> Type {
673 self.filter_replacing(
674 |t| {
675 matches!(
676 t,
677 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
678 )
679 },
680 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
681 Atomic::TBool,
682 )
683 }
684
685 pub fn narrow_to_null(&self) -> Type {
687 self.filter_replacing(
688 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
689 |t| matches!(t, Atomic::TMixed),
690 Atomic::TNull,
691 )
692 }
693
694 pub fn narrow_to_array(&self) -> Type {
696 self.filter_replacing(
697 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
698 |t| matches!(t, Atomic::TMixed),
699 Atomic::TArray {
700 key: Box::new(Type::mixed()),
701 value: Box::new(Type::mixed()),
702 },
703 )
704 }
705
706 pub fn widen_nested_non_empty(&self) -> Type {
709 fn relax_all(ty: &Type) -> Type {
710 map_atoms(ty, |a| match a {
711 Atomic::TNonEmptyList { value } => Atomic::TList {
712 value: Box::new(relax_all(value)),
713 },
714 Atomic::TNonEmptyArray { key, value } => Atomic::TArray {
715 key: key.clone(),
716 value: Box::new(relax_all(value)),
717 },
718 other => map_values(other, relax_all),
719 })
720 }
721 fn map_values(atomic: &Atomic, f: fn(&Type) -> Type) -> Atomic {
722 match atomic {
723 Atomic::TArray { key, value } => Atomic::TArray {
724 key: key.clone(),
725 value: Box::new(f(value)),
726 },
727 Atomic::TList { value } => Atomic::TList {
728 value: Box::new(f(value)),
729 },
730 Atomic::TNonEmptyArray { key, value } => Atomic::TNonEmptyArray {
731 key: key.clone(),
732 value: Box::new(f(value)),
733 },
734 Atomic::TNonEmptyList { value } => Atomic::TNonEmptyList {
735 value: Box::new(f(value)),
736 },
737 Atomic::TKeyedArray {
738 properties,
739 is_open,
740 is_list,
741 } => Atomic::TKeyedArray {
742 properties: Box::new(
743 properties
744 .iter()
745 .map(|(k, p)| {
746 (
747 k.clone(),
748 crate::atomic::KeyedProperty {
749 ty: f(&p.ty),
750 optional: p.optional,
751 },
752 )
753 })
754 .collect(),
755 ),
756 is_open: *is_open,
757 is_list: *is_list,
758 },
759 other => other.clone(),
760 }
761 }
762 fn map_atoms(ty: &Type, f: impl Fn(&Atomic) -> Atomic) -> Type {
763 let mut result = Type::empty();
764 result.possibly_undefined = ty.possibly_undefined;
765 result.from_docblock = ty.from_docblock;
766 for atomic in &ty.types {
767 result.add_type(f(atomic));
768 }
769 result
770 }
771 map_atoms(self, |a| map_values(a, relax_all))
772 }
773
774 pub fn narrow_to_non_empty_collection(&self) -> Type {
777 let mut out = Type::empty();
778 out.from_docblock = self.from_docblock;
779 for t in &self.types {
780 match t {
781 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
782 key: key.clone(),
783 value: value.clone(),
784 }),
785 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
786 value: value.clone(),
787 }),
788 Atomic::TKeyedArray {
789 properties,
790 is_open: false,
791 ..
792 } if properties.is_empty() => {}
793 _ => out.add_type(t.clone()),
794 }
795 }
796 out
797 }
798
799 pub fn narrow_to_empty_collection(&self) -> Type {
809 let mut out = Type::empty();
810 out.from_docblock = self.from_docblock;
811 for t in &self.types {
812 match t {
813 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
814 Atomic::TArray { .. } | Atomic::TList { .. } => {
815 out.add_type(Atomic::TKeyedArray {
816 properties: Box::default(),
817 is_open: false,
818 is_list: true,
819 });
820 }
821 _ => out.add_type(t.clone()),
822 }
823 }
824 out
825 }
826
827 pub fn narrow_to_list(&self) -> Type {
839 let mut out = Type::empty();
840 out.from_docblock = self.from_docblock;
841 for t in &self.types {
842 match t {
843 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
844 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
850 out.add_type(Atomic::TList {
851 value: value.clone(),
852 });
853 }
854 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
855 out.add_type(Atomic::TNonEmptyList {
856 value: value.clone(),
857 });
858 }
859 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
860 Atomic::TMixed => out.add_type(Atomic::TList {
861 value: Box::new(Type::mixed()),
862 }),
863 _ => {}
864 }
865 }
866 if out.is_empty() {
867 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
868 } else {
869 out
870 }
871 }
872
873 pub fn narrow_to_object(&self) -> Type {
878 let mut out = Type::empty();
879 for t in &self.types {
880 if matches!(t, Atomic::TMixed) {
881 out.add_type(Atomic::TObject);
882 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
883 out.add_type(t.clone());
884 }
885 }
886 if out.types.is_empty() {
887 self.filter(|t| t.is_object())
888 } else {
889 out
890 }
891 }
892
893 pub fn narrow_to_callable(&self) -> Type {
900 let narrowed = self.filter(|t| {
901 t.is_callable()
902 || t.is_string()
903 || t.is_array()
904 || t.is_object()
905 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
906 });
907 let mut result = Type::empty();
914 result.possibly_undefined = narrowed.possibly_undefined;
915 result.from_docblock = narrowed.from_docblock;
916 for atomic in narrowed.types {
917 if matches!(atomic, Atomic::TObject) {
918 result.add_type(Atomic::TCallable {
919 params: None,
920 return_type: None,
921 is_pure: false,
922 });
923 } else {
924 result.add_type(atomic);
925 }
926 }
927 result
928 }
929
930 pub fn narrow_to_scalar(&self) -> Type {
932 self.filter_replacing(
933 |t| {
934 t.is_string()
935 || t.is_int()
936 || matches!(
937 t,
938 Atomic::TFloat
939 | Atomic::TIntegralFloat
940 | Atomic::TLiteralFloat(..)
941 | Atomic::TBool
942 | Atomic::TTrue
943 | Atomic::TFalse
944 | Atomic::TScalar
945 | Atomic::TNumeric
946 | Atomic::TNumericString
947 | Atomic::TTemplateParam { .. }
948 )
949 },
950 |t| matches!(t, Atomic::TMixed),
951 Atomic::TScalar,
952 )
953 }
954
955 pub fn narrow_to_iterable(&self) -> Type {
958 self.filter(|t| {
959 t.is_array()
960 || t.is_object()
961 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
962 })
963 }
964
965 pub fn narrow_to_countable(&self) -> Type {
968 self.filter(|t| {
969 t.is_array()
970 || t.is_object()
971 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
972 })
973 }
974
975 pub fn narrow_to_resource(&self) -> Type {
979 self.filter(|t| matches!(t, Atomic::TMixed))
981 }
982
983 pub fn narrow_to_class_string(&self) -> Type {
988 let mut out = Type::empty();
989 out.from_docblock = self.from_docblock;
990 for t in &self.types {
991 match t {
992 Atomic::TClassString(_) => out.add_type(t.clone()),
993 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
994 out.add_type(Atomic::TClassString(None));
995 }
996 _ => {}
997 }
998 }
999 out
1000 }
1001
1002 pub fn narrow_to_interface_string(&self) -> Type {
1007 let mut out = Type::empty();
1008 out.from_docblock = self.from_docblock;
1009 for t in &self.types {
1010 match t {
1011 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
1012 Atomic::TClassString(name) => {
1016 out.add_type(Atomic::TInterfaceString(*name));
1017 }
1018 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
1019 out.add_type(Atomic::TInterfaceString(None));
1020 }
1021 _ => {}
1022 }
1023 }
1024 out
1025 }
1026
1027 pub fn merge(a: &Type, b: &Type) -> Type {
1032 if b.types.is_empty() {
1034 let mut result = a.clone();
1035 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1036 return result;
1037 }
1038 if a.types.is_empty() {
1040 let mut result = b.clone();
1041 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1042 return result;
1043 }
1044 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
1046 let mut result = a.clone();
1047 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
1048 return result;
1049 }
1050 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1052 return Type {
1053 types: smallvec::smallvec![Atomic::TMixed],
1054 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
1055 from_docblock: a.from_docblock || b.from_docblock,
1056 falsy_stripped: false,
1057 possibly_absent_offset: false,
1058 };
1059 }
1060 let mut result = a.clone();
1061 result.merge_with(b);
1062 result
1063 }
1064
1065 pub fn merge_with(&mut self, other: &Type) {
1067 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1068 self.possibly_undefined |= other.possibly_undefined;
1069 return;
1070 }
1071 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
1072 self.types.clear();
1073 self.types.push(Atomic::TMixed);
1074 self.possibly_undefined |= other.possibly_undefined;
1075 return;
1076 }
1077 for atomic in &other.types {
1078 self.add_type(atomic.clone());
1079 }
1080 self.possibly_undefined |= other.possibly_undefined;
1081 }
1082
1083 pub fn intersect_with(&self, other: &Type) -> Type {
1087 if self.is_mixed() {
1088 return other.clone();
1089 }
1090 if other.is_mixed() {
1091 return self.clone();
1092 }
1093 let mut result = Type::empty();
1101 for a in &self.types {
1102 for b in &other.types {
1103 if a == b {
1104 result.add_type(a.clone());
1105 } else if atomic_subtype(b, a) {
1106 result.add_type(b.clone());
1107 } else if atomic_subtype(a, b) {
1108 result.add_type(a.clone());
1109 }
1110 }
1111 }
1112 if result.is_empty() {
1113 Type::never()
1114 } else {
1115 result
1116 }
1117 }
1118
1119 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1123 if bindings.is_empty() {
1124 return self.clone();
1125 }
1126 if !self.types.iter().any(atomic_may_contain_templates) {
1129 return self.clone();
1130 }
1131 let mut result = Type::empty();
1132 result.possibly_undefined = self.possibly_undefined;
1133 result.from_docblock = self.from_docblock;
1134 for atomic in &self.types {
1135 match atomic {
1136 Atomic::TTemplateParam { name, .. } => {
1137 if let Some(resolved) = bindings.get(name) {
1138 for t in &resolved.types {
1139 result.add_type(t.clone());
1140 }
1141 } else {
1142 result.add_type(atomic.clone());
1143 }
1144 }
1145 Atomic::TArray { key, value } => {
1146 result.add_type(Atomic::TArray {
1147 key: Box::new(key.substitute_templates(bindings)),
1148 value: Box::new(value.substitute_templates(bindings)),
1149 });
1150 }
1151 Atomic::TList { value } => {
1152 result.add_type(Atomic::TList {
1153 value: Box::new(value.substitute_templates(bindings)),
1154 });
1155 }
1156 Atomic::TNonEmptyArray { key, value } => {
1157 result.add_type(Atomic::TNonEmptyArray {
1158 key: Box::new(key.substitute_templates(bindings)),
1159 value: Box::new(value.substitute_templates(bindings)),
1160 });
1161 }
1162 Atomic::TNonEmptyList { value } => {
1163 result.add_type(Atomic::TNonEmptyList {
1164 value: Box::new(value.substitute_templates(bindings)),
1165 });
1166 }
1167 Atomic::TKeyedArray {
1168 properties,
1169 is_open,
1170 is_list,
1171 } => {
1172 use crate::atomic::KeyedProperty;
1173 let new_props = properties
1174 .iter()
1175 .map(|(k, prop)| {
1176 (
1177 k.clone(),
1178 KeyedProperty {
1179 ty: prop.ty.substitute_templates(bindings),
1180 optional: prop.optional,
1181 },
1182 )
1183 })
1184 .collect();
1185 result.add_type(Atomic::TKeyedArray {
1186 properties: Box::new(new_props),
1187 is_open: *is_open,
1188 is_list: *is_list,
1189 });
1190 }
1191 Atomic::TCallable {
1192 params,
1193 return_type,
1194 is_pure,
1195 } => {
1196 result.add_type(Atomic::TCallable {
1197 params: params.as_ref().map(|ps| {
1198 Box::new(
1199 ps.iter()
1200 .map(|p| substitute_in_fn_param(p, bindings))
1201 .collect(),
1202 )
1203 }),
1204 return_type: return_type
1205 .as_ref()
1206 .map(|r| Box::new(r.substitute_templates(bindings))),
1207 is_pure: *is_pure,
1208 });
1209 }
1210 Atomic::TClosure { data } => {
1211 result.add_type(Atomic::TClosure {
1212 data: Box::new(crate::atomic::ClosureData {
1213 params: data
1214 .params
1215 .iter()
1216 .map(|p| substitute_in_fn_param(p, bindings))
1217 .collect(),
1218 return_type: data.return_type.substitute_templates(bindings),
1219 this_type: data
1220 .this_type
1221 .as_ref()
1222 .map(|t| t.substitute_templates(bindings)),
1223 is_pure: data.is_pure,
1224 is_static: data.is_static,
1225 }),
1226 });
1227 }
1228 Atomic::TConditional { data } => {
1229 let param_name = &data.param_name;
1230 let new_subject = data.subject.substitute_templates(bindings);
1231 let new_if_true = data.if_true.substitute_templates(bindings);
1232 let new_if_false = data.if_false.substitute_templates(bindings);
1233
1234 let resolved = if let Some(name) = param_name {
1238 if let Some(bound) = bindings.get(name) {
1239 if new_subject.types.len() == 1 {
1240 resolve_conditional_branch(
1241 &new_subject.types[0],
1242 bound,
1243 &new_if_true,
1244 &new_if_false,
1245 )
1246 } else {
1247 None
1248 }
1249 } else {
1250 None
1251 }
1252 } else {
1253 None
1254 };
1255
1256 if let Some(branch) = resolved {
1257 for t in branch.types {
1258 result.add_type(t);
1259 }
1260 } else {
1261 result.add_type(Atomic::TConditional {
1262 data: Box::new(crate::atomic::ConditionalData {
1263 param_name: *param_name,
1264 subject: new_subject,
1265 if_true: new_if_true,
1266 if_false: new_if_false,
1267 }),
1268 });
1269 }
1270 }
1271 Atomic::TKeyOf { target } => {
1272 let new_target = target.substitute_templates(bindings);
1273 if let Some(resolved) = eval_key_of_type(&new_target) {
1274 result.merge_with(&resolved);
1275 } else {
1276 result.add_type(Atomic::TKeyOf {
1277 target: Box::new(new_target),
1278 });
1279 }
1280 }
1281 Atomic::TValueOf { target } => {
1282 let new_target = target.substitute_templates(bindings);
1283 if let Some(resolved) = eval_value_of_type(&new_target) {
1284 result.merge_with(&resolved);
1285 } else {
1286 result.add_type(Atomic::TValueOf {
1287 target: Box::new(new_target),
1288 });
1289 }
1290 }
1291 Atomic::TIntersection { parts } => {
1292 result.add_type(Atomic::TIntersection {
1293 parts: vec_to_type_params(
1294 parts
1295 .iter()
1296 .map(|p| p.substitute_templates(bindings))
1297 .collect(),
1298 ),
1299 });
1300 }
1301 Atomic::TNamedObject { fqcn, type_params } => {
1302 if type_params.is_empty() && !fqcn.contains('\\') {
1304 if let Some(resolved) = bindings.get(fqcn) {
1305 for t in &resolved.types {
1306 result.add_type(t.clone());
1307 }
1308 continue;
1309 }
1310 }
1311 let new_params: Vec<Type> = type_params
1312 .iter()
1313 .map(|p| p.substitute_templates(bindings))
1314 .collect();
1315 result.add_type(Atomic::TNamedObject {
1316 fqcn: *fqcn,
1317 type_params: vec_to_type_params(new_params),
1318 });
1319 }
1320 Atomic::TClassString(Some(param_name)) => {
1322 if let Some(resolved) = bindings.get(param_name) {
1323 for r_atomic in &resolved.types {
1324 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1325 Some(*fqcn)
1326 } else {
1327 None
1328 };
1329 result.add_type(Atomic::TClassString(cls_name));
1330 }
1331 } else {
1332 result.add_type(atomic.clone());
1333 }
1334 }
1335 Atomic::TInterfaceString(Some(param_name)) => {
1337 if let Some(resolved) = bindings.get(param_name) {
1338 for r_atomic in &resolved.types {
1339 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1340 Some(*fqcn)
1341 } else {
1342 None
1343 };
1344 result.add_type(Atomic::TInterfaceString(iface_name));
1345 }
1346 } else {
1347 result.add_type(atomic.clone());
1348 }
1349 }
1350 _ => {
1351 result.add_type(atomic.clone());
1352 }
1353 }
1354 }
1355 result
1356 }
1357
1358 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1364 where
1365 F: Fn(&str) -> Option<Type>,
1366 {
1367 self.resolve_conditional_inner(&lookup)
1368 }
1369
1370 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1371 where
1372 F: Fn(&str) -> Option<Type>,
1373 {
1374 let mut result = Type::empty();
1375 for atomic in self.types {
1376 match atomic {
1377 Atomic::TConditional { ref data } => {
1378 let (param_name, subject, if_true, if_false) = (
1379 &data.param_name,
1380 &data.subject,
1381 &data.if_true,
1382 &data.if_false,
1383 );
1384 let resolved = if subject.types.len() == 1 {
1385 if let Some(name) = param_name {
1386 if let Some(arg_ty) = lookup(name.as_ref()) {
1387 resolve_conditional_branch(
1388 &subject.types[0],
1389 &arg_ty,
1390 if_true,
1391 if_false,
1392 )
1393 } else {
1394 None
1395 }
1396 } else {
1397 None
1398 }
1399 } else {
1400 None
1401 };
1402
1403 if let Some(branch) = resolved {
1404 for t in branch.resolve_conditional_inner(lookup).types {
1406 result.add_type(t);
1407 }
1408 } else {
1409 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1412 result.add_type(t);
1413 }
1414 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1415 result.add_type(t);
1416 }
1417 }
1418 }
1419 other => result.add_type(other),
1420 }
1421 }
1422 result
1423 }
1424
1425 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1435 if other.is_mixed() {
1436 return true;
1437 }
1438 if self.is_never() {
1439 return true; }
1441 self.types
1442 .iter()
1443 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1444 }
1445
1446 pub fn is_subtype_by_template_bounds(&self, other: &Type) -> bool {
1449 self.is_subtype_structural(other)
1450 || self.types.iter().all(|a| match a {
1451 Atomic::TTemplateParam { as_type, .. } => {
1452 as_type.is_subtype_by_template_bounds(other)
1453 }
1454 _ => other.accepts_atomic_structural(a),
1455 })
1456 }
1457
1458 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1462 if self.is_mixed() {
1463 return true;
1464 }
1465 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1466 }
1467
1468 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1471 let mut result = Type::empty();
1472 result.possibly_undefined = self.possibly_undefined;
1473 result.from_docblock = self.from_docblock;
1474 for atomic in &self.types {
1475 if f(atomic) {
1476 result.types.push(atomic.clone());
1477 }
1478 }
1479 result
1480 }
1481
1482 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1487 &self,
1488 keep: K,
1489 placeholder: P,
1490 replacement: Atomic,
1491 ) -> Type {
1492 let mut result = Type::empty();
1493 result.possibly_undefined = self.possibly_undefined;
1494 result.from_docblock = self.from_docblock;
1495 for atomic in &self.types {
1496 if keep(atomic) {
1497 result.add_type(atomic.clone());
1498 } else if placeholder(atomic) {
1499 result.add_type(replacement.clone());
1500 }
1501 }
1502 result
1503 }
1504
1505 pub fn possibly_undefined(mut self) -> Self {
1507 self.possibly_undefined = true;
1508 self
1509 }
1510
1511 pub fn from_docblock(mut self) -> Self {
1513 self.from_docblock = true;
1514 self
1515 }
1516
1517 pub fn falsy_stripped(mut self) -> Self {
1520 self.falsy_stripped = true;
1521 self
1522 }
1523
1524 pub fn possibly_absent_offset(mut self) -> Self {
1528 self.possibly_absent_offset = true;
1529 self
1530 }
1531}
1532
1533#[derive(Clone, PartialEq, Eq, Debug)]
1547enum ValueClass {
1548 Int,
1549 Float,
1550 True,
1551 False,
1552 String,
1553 Array,
1554 List,
1556 Object,
1557 Null,
1558 Top,
1561 LitInt(i64),
1564 LitFloat(i64, i64),
1566 LitString(Arc<str>),
1568}
1569
1570impl ValueClass {
1571 fn includes(&self, b: &Self) -> bool {
1576 self == b
1577 || matches!(
1578 (self, b),
1579 (Self::Top, _)
1580 | (Self::Int, Self::LitInt(_))
1581 | (Self::Float, Self::LitFloat(..))
1582 | (Self::String, Self::LitString(_))
1583 | (Self::Array, Self::List)
1584 )
1585 }
1586}
1587
1588fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1594 use ValueClass::*;
1595 match a {
1596 Atomic::TInt
1599 | Atomic::TIntRange { .. }
1600 | Atomic::TPositiveInt
1601 | Atomic::TNegativeInt
1602 | Atomic::TNonNegativeInt => vec![Int],
1603 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1604 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1606 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1607 vec![LitFloat(*int_bits, *frac_bits)]
1608 }
1609 Atomic::TBool => vec![True, False],
1611 Atomic::TTrue => vec![True],
1612 Atomic::TFalse => vec![False],
1613 Atomic::TString
1615 | Atomic::TNonEmptyString
1616 | Atomic::TNumericString
1617 | Atomic::TClassString(_)
1618 | Atomic::TInterfaceString(_)
1619 | Atomic::TEnumString
1620 | Atomic::TTraitString
1621 | Atomic::TCallableString => vec![String],
1622 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1623 Atomic::TArray { .. }
1625 | Atomic::TNonEmptyArray { .. }
1626 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1627 Atomic::TList { .. }
1628 | Atomic::TNonEmptyList { .. }
1629 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1630 Atomic::TObject
1632 | Atomic::TNamedObject { .. }
1633 | Atomic::TStaticObject { .. }
1634 | Atomic::TSelf { .. }
1635 | Atomic::TParent { .. }
1636 | Atomic::TClosure { .. }
1637 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1638 Atomic::TNull => vec![Null],
1641 Atomic::TVoid => vec![Top],
1642 Atomic::TMixed => vec![Top],
1644 Atomic::TScalar => vec![Int, Float, True, False, String],
1645 Atomic::TNumeric => vec![Int, Float],
1648 Atomic::TCallable { .. }
1650 | Atomic::TNever
1651 | Atomic::TTemplateParam { .. }
1652 | Atomic::TKeyOf { .. }
1653 | Atomic::TValueOf { .. }
1654 | Atomic::TConditional { .. }
1655 | Atomic::TIntersection { .. } => Vec::new(),
1656 }
1657}
1658
1659fn subject_is_decidable(subject: &Atomic) -> bool {
1667 matches!(
1668 subject,
1669 Atomic::TNull
1670 | Atomic::TTrue
1671 | Atomic::TFalse
1672 | Atomic::TBool
1673 | Atomic::TString
1674 | Atomic::TInt
1675 | Atomic::TFloat
1676 | Atomic::TLiteralString(_)
1677 | Atomic::TLiteralInt(_)
1678 | Atomic::TLiteralFloat(..)
1679 | Atomic::TArray { .. }
1680 | Atomic::TList { .. }
1681 | Atomic::TObject
1682 | Atomic::TMixed
1683 | Atomic::TScalar
1684 )
1685}
1686
1687fn resolve_conditional_branch(
1709 subject: &Atomic,
1710 arg_ty: &Type,
1711 if_true: &Type,
1712 if_false: &Type,
1713) -> Option<Type> {
1714 if !subject_is_decidable(subject) {
1715 return None;
1716 }
1717 if arg_ty.types.is_empty() {
1718 return None;
1719 }
1720 let subject_classes = value_classes(subject);
1721 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1722 if arg_classes.is_empty() {
1723 return None;
1725 }
1726 let all_match = arg_classes
1727 .iter()
1728 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1729 let any_overlap = arg_classes.iter().any(|c| {
1730 subject_classes
1731 .iter()
1732 .any(|s| s.includes(c) || c.includes(s))
1733 });
1734 if all_match {
1735 Some(if_true.clone())
1736 } else if !any_overlap {
1737 Some(if_false.clone())
1738 } else {
1739 None
1740 }
1741}
1742
1743fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1751 match atomic {
1752 Atomic::TNamedObject { fqcn, type_params } => {
1756 !type_params.is_empty() || !fqcn.contains('\\')
1757 }
1758 Atomic::TTemplateParam { .. }
1759 | Atomic::TKeyOf { .. }
1760 | Atomic::TValueOf { .. }
1761 | Atomic::TArray { .. }
1762 | Atomic::TList { .. }
1763 | Atomic::TNonEmptyArray { .. }
1764 | Atomic::TNonEmptyList { .. }
1765 | Atomic::TKeyedArray { .. }
1766 | Atomic::TCallable { .. }
1767 | Atomic::TClosure { .. }
1768 | Atomic::TConditional { .. }
1769 | Atomic::TIntersection { .. }
1770 | Atomic::TClassString(Some(_))
1771 | Atomic::TInterfaceString(Some(_)) => true,
1772 _ => false,
1773 }
1774}
1775
1776pub fn eval_key_of_type(t: &Type) -> Option<Type> {
1777 let mut result = Type::empty();
1778 for atomic in &t.types {
1779 match atomic {
1780 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1781 for k in &key.types {
1782 result.add_type(k.clone());
1783 }
1784 }
1785 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1786 result.add_type(Atomic::TInt);
1787 }
1788 Atomic::TKeyedArray { properties, .. } => {
1789 for key in properties.keys() {
1790 match key {
1791 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1792 crate::atomic::ArrayKey::String(s) => {
1793 result.add_type(Atomic::TLiteralString(s.clone()))
1794 }
1795 }
1796 }
1797 }
1798 _ => return None,
1799 }
1800 }
1801 (!result.types.is_empty()).then_some(result)
1802}
1803
1804pub fn eval_value_of_type(t: &Type) -> Option<Type> {
1805 let mut result = Type::empty();
1806 for atomic in &t.types {
1807 match atomic {
1808 Atomic::TArray { value, .. }
1809 | Atomic::TNonEmptyArray { value, .. }
1810 | Atomic::TList { value }
1811 | Atomic::TNonEmptyList { value } => {
1812 result.merge_with(value);
1813 }
1814 Atomic::TKeyedArray { properties, .. } => {
1815 for prop in properties.values() {
1816 result.merge_with(&prop.ty);
1817 }
1818 }
1819 _ => return None,
1820 }
1821 }
1822 (!result.types.is_empty()).then_some(result)
1823}
1824
1825fn substitute_in_fn_param(
1826 p: &crate::atomic::FnParam,
1827 bindings: &FxHashMap<Name, Type>,
1828) -> crate::atomic::FnParam {
1829 crate::atomic::FnParam {
1830 name: p.name,
1831 ty: p.ty.as_ref().map(|t| {
1832 let u = t.to_union();
1833 let substituted = u.substitute_templates(bindings);
1834 crate::compact::SimpleType::from_union(substituted)
1835 }),
1836 out_ty: p.out_ty.as_ref().map(|t| {
1837 let u = t.to_union();
1838 let substituted = u.substitute_templates(bindings);
1839 crate::compact::SimpleType::from_union(substituted)
1840 }),
1841 default: p.default.as_ref().map(|d| {
1842 let u = d.to_union();
1843 let substituted = u.substitute_templates(bindings);
1844 crate::compact::SimpleType::from_union(substituted)
1845 }),
1846 is_variadic: p.is_variadic,
1847 is_byref: p.is_byref,
1848 is_optional: p.is_optional,
1849 }
1850}
1851
1852pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1858 if sub == sup {
1859 return true;
1860 }
1861 match (sub, sup) {
1862 (Atomic::TNever, _) => true,
1864 (_, Atomic::TMixed) => true,
1866 (Atomic::TMixed, _) => true,
1867 (_, Atomic::TTemplateParam { as_type, .. }) => {
1872 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1873 }
1874
1875 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1877 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1878 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1879 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1880 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1881 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1882 (Atomic::TPositiveInt, Atomic::TInt) => true,
1883 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1884 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1885 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1886 (Atomic::TNegativeInt, Atomic::TInt) => true,
1887 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1888 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1889 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1890 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1891 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1892 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1893 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1894 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1895 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1897 max.is_none() && min.is_none_or(|m| m <= 1)
1898 }
1899 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1901 min.is_none() && max.is_none_or(|m| m >= -1)
1902 }
1903 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1905 max.is_none() && min.is_none_or(|m| m <= 0)
1906 }
1907 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1909 sub_min.is_some_and(|lo| lo >= 1)
1910 }
1911 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1912 sub_min.is_some_and(|lo| lo >= 0)
1913 }
1914 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1915 sub_max.is_some_and(|hi| hi <= -1)
1916 }
1917 (
1919 Atomic::TIntRange {
1920 min: sub_min,
1921 max: sub_max,
1922 },
1923 Atomic::TIntRange {
1924 min: sup_min,
1925 max: sup_max,
1926 },
1927 ) => {
1928 let lower_ok = match (sub_min, sup_min) {
1929 (_, None) => true,
1930 (None, Some(_)) => false,
1931 (Some(sl), Some(su)) => sl >= su,
1932 };
1933 let upper_ok = match (sub_max, sup_max) {
1934 (None, None) | (Some(_), None) => true,
1935 (None, Some(_)) => false,
1936 (Some(sl), Some(su)) => sl <= su,
1937 };
1938 lower_ok && upper_ok
1939 }
1940
1941 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1942 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1943 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1944
1945 (Atomic::TLiteralString(s), Atomic::TString) => {
1946 let _ = s;
1947 true
1948 }
1949 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1950 let _ = s;
1951 true
1952 }
1953 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1954 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1955 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1958 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1961 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1962 (Atomic::TNonEmptyString, Atomic::TString) => true,
1963 (Atomic::TCallableString, Atomic::TString) => true,
1964 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1966 (Atomic::TNumericString, Atomic::TString) => true,
1967 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1971 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1972 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1973 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1974 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1975 (Atomic::TClassString(_), Atomic::TString) => true,
1976 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1977 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1982 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
1983 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1984 (Atomic::TEnumString, Atomic::TString) => true,
1985 (Atomic::TTraitString, Atomic::TString) => true,
1986
1987 (Atomic::TTrue, Atomic::TBool) => true,
1988 (Atomic::TFalse, Atomic::TBool) => true,
1989
1990 (Atomic::TInt, Atomic::TNumeric) => true,
1991 (Atomic::TFloat, Atomic::TNumeric) => true,
1992 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1993 (Atomic::TNumericString, Atomic::TNumeric) => true,
1994
1995 (Atomic::TInt, Atomic::TScalar) => true,
1996 (Atomic::TFloat, Atomic::TScalar) => true,
1997 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1998 (Atomic::TString, Atomic::TScalar) => true,
1999 (Atomic::TBool, Atomic::TScalar) => true,
2000 (Atomic::TNumeric, Atomic::TScalar) => true,
2001 (Atomic::TTrue, Atomic::TScalar) => true,
2002 (Atomic::TFalse, Atomic::TScalar) => true,
2003 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
2007 (Atomic::TNumericString, Atomic::TScalar) => true,
2008 (Atomic::TCallableString, Atomic::TScalar) => true,
2009 (Atomic::TClassString(_), Atomic::TScalar) => true,
2010 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
2011 (Atomic::TEnumString, Atomic::TScalar) => true,
2012 (Atomic::TTraitString, Atomic::TScalar) => true,
2013
2014 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
2016 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
2017 (Atomic::TSelf { .. }, Atomic::TObject) => true,
2018 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
2020 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2022 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
2023 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
2025 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
2026 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
2029 enum_fqcn == fqcn
2030 }
2031 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TSelf { fqcn })
2032 | (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TStaticObject { fqcn }) => {
2033 enum_fqcn == fqcn
2034 }
2035 (
2039 Atomic::TNamedObject {
2040 fqcn: sub_fqcn,
2041 type_params: sub_params,
2042 },
2043 Atomic::TNamedObject {
2044 fqcn: sup_fqcn,
2045 type_params: sup_params,
2046 },
2047 ) => {
2048 sub_fqcn == sup_fqcn
2049 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
2050 }
2051
2052 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
2054
2055 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
2057 (Atomic::TPositiveInt, Atomic::TFloat) => true,
2058 (Atomic::TNegativeInt, Atomic::TFloat) => true,
2059 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
2060 (Atomic::TInt, Atomic::TFloat) => true,
2061 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
2062
2063 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
2065 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
2066 }
2067
2068 (Atomic::TString, Atomic::TCallable { .. }) => true,
2070 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
2071 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
2072 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
2073 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
2074 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
2075
2076 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
2078 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
2080 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
2090 fn has_nominal_type(t: &Type) -> bool {
2091 t.types.iter().any(|a| {
2092 matches!(
2093 a,
2094 Atomic::TNamedObject { .. }
2095 | Atomic::TSelf { .. }
2096 | Atomic::TStaticObject { .. }
2097 | Atomic::TTemplateParam { .. }
2098 | Atomic::TClosure { .. }
2099 | Atomic::TCallable { .. }
2100 )
2101 })
2102 }
2103 let sub_required = sub
2104 .params
2105 .iter()
2106 .filter(|p| !p.is_optional && !p.is_variadic)
2107 .count();
2108 if sub_required > sup.params.len() {
2109 false
2110 } else {
2111 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2112 let Some(sub_param) = sub.params.get(i) else {
2113 return true;
2114 };
2115 if sub_param.is_optional || sub_param.is_variadic {
2116 return true;
2117 }
2118 let (Some(sub_ty), Some(sup_ty)) =
2119 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2120 else {
2121 return true;
2122 };
2123 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2124 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2125 return true;
2126 }
2127 sup_u.is_subtype_structural(&sub_u)
2130 });
2131 params_ok
2132 && (sub.return_type.is_mixed()
2133 || sup.return_type.is_mixed()
2134 || sup.return_type.is_void()
2138 || has_nominal_type(&sub.return_type)
2139 || has_nominal_type(&sup.return_type)
2140 || sub.return_type.is_subtype_structural(&sup.return_type))
2141 }
2142 }
2143 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2145 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2147 fqcn.as_ref().eq_ignore_ascii_case("closure")
2148 }
2149 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2150 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2152 fqcn.as_ref().eq_ignore_ascii_case("closure")
2153 }
2154 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2156 fqcn.as_ref().eq_ignore_ascii_case("closure")
2157 }
2158
2159 (
2167 Atomic::TIntersection { parts: sub_parts },
2168 Atomic::TIntersection { parts: sup_parts },
2169 ) => sup_parts.iter().all(|sup_part| {
2170 sub_parts
2171 .iter()
2172 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2173 }),
2174
2175 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2177 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2178 }
2179 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2180 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2181 }
2182 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2183 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2184 }
2185 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2186 value.is_subtype_structural(lv)
2187 }
2188 (Atomic::TNonEmptyList { value: v1 }, Atomic::TNonEmptyList { value: v2 }) => {
2189 v1.is_subtype_structural(v2)
2190 }
2191 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2193 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2194 && av.is_subtype_structural(lv)
2195 }
2196 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2197 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2198 && av.is_subtype_structural(lv)
2199 }
2200 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2201 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2202 && av.is_subtype_structural(lv)
2203 }
2204 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2205 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2206 && av.is_subtype_structural(lv)
2207 }
2208 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2210 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2211 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2212 }
2213
2214 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2216 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2217 }
2218
2219 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2228 properties.iter().all(|(prop_key, prop)| {
2229 let key_atomic = match prop_key {
2230 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2231 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2232 };
2233 if !Type::single(key_atomic).is_subtype_structural(key) {
2234 return false; }
2236 let has_named_obj = prop.ty.types.iter().any(|a| {
2238 matches!(
2239 a,
2240 Atomic::TNamedObject { .. }
2241 | Atomic::TSelf { .. }
2242 | Atomic::TStaticObject { .. }
2243 | Atomic::TClosure { .. }
2244 | Atomic::TTemplateParam { .. }
2245 )
2246 });
2247 has_named_obj || prop.ty.is_subtype_structural(value)
2248 })
2249 }
2250 (
2251 Atomic::TKeyedArray {
2252 properties,
2253 is_open,
2254 ..
2255 },
2256 Atomic::TNonEmptyArray { key, value },
2257 ) => {
2258 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2259 && properties.iter().all(|(prop_key, prop)| {
2260 let key_atomic = match prop_key {
2261 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2262 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2263 };
2264 if !Type::single(key_atomic).is_subtype_structural(key) {
2265 return false;
2266 }
2267 let has_named_obj = prop.ty.types.iter().any(|a| {
2268 matches!(
2269 a,
2270 Atomic::TNamedObject { .. }
2271 | Atomic::TSelf { .. }
2272 | Atomic::TStaticObject { .. }
2273 | Atomic::TClosure { .. }
2274 | Atomic::TTemplateParam { .. }
2275 )
2276 });
2277 has_named_obj || prop.ty.is_subtype_structural(value)
2278 })
2279 }
2280
2281 (
2283 Atomic::TKeyedArray {
2284 properties,
2285 is_list,
2286 ..
2287 },
2288 Atomic::TList { value: lv },
2289 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2290 (
2291 Atomic::TKeyedArray {
2292 properties,
2293 is_list,
2294 ..
2295 },
2296 Atomic::TNonEmptyList { value: lv },
2297 ) => {
2298 *is_list
2299 && !properties.is_empty()
2300 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2301 }
2302
2303 (
2308 Atomic::TKeyedArray {
2309 properties: sub_props,
2310 is_open: sub_open,
2311 ..
2312 },
2313 Atomic::TKeyedArray {
2314 properties: sup_props,
2315 is_open: sup_open,
2316 ..
2317 },
2318 ) => {
2319 let keys_satisfied = sup_props
2320 .iter()
2321 .all(|(key, sup_prop)| match sub_props.get(key) {
2322 Some(sub_prop) => {
2323 if !sup_prop.optional && sub_prop.optional {
2327 return false;
2328 }
2329 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2330 matches!(
2331 a,
2332 Atomic::TNamedObject { .. }
2333 | Atomic::TSelf { .. }
2334 | Atomic::TStaticObject { .. }
2335 | Atomic::TClosure { .. }
2336 | Atomic::TTemplateParam { .. }
2337 )
2338 });
2339 has_named_obj || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2340 }
2341 None => sup_prop.optional || *sub_open,
2342 });
2343 let no_undeclared_extras =
2344 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2345 keys_satisfied && no_undeclared_extras
2346 }
2347 (
2352 Atomic::TKeyedArray {
2353 properties,
2354 is_open,
2355 ..
2356 },
2357 Atomic::TIntersection { parts },
2358 ) => parts.iter().all(|part| {
2359 part.types.iter().any(|part_atomic| match part_atomic {
2360 Atomic::TKeyedArray {
2361 properties: sup_props,
2362 is_open: sup_open,
2363 ..
2364 } => {
2365 let keys_satisfied =
2366 sup_props
2367 .iter()
2368 .all(|(key, sup_prop)| match properties.get(key) {
2369 Some(sub_prop) => {
2370 if !sup_prop.optional && sub_prop.optional {
2371 return false;
2372 }
2373 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2374 matches!(
2375 a,
2376 Atomic::TNamedObject { .. }
2377 | Atomic::TSelf { .. }
2378 | Atomic::TStaticObject { .. }
2379 | Atomic::TClosure { .. }
2380 | Atomic::TTemplateParam { .. }
2381 )
2382 });
2383 has_named_obj
2384 || sub_prop.ty.is_subtype_by_template_bounds(&sup_prop.ty)
2385 || bare_array_may_fit(&sub_prop.ty, &sup_prop.ty)
2386 }
2387 None => *is_open || sup_prop.optional,
2388 });
2389 let has_array_part = parts.iter().any(|part| {
2390 part.types
2391 .iter()
2392 .any(|t| matches!(t, Atomic::TArray { .. }))
2393 });
2394 let has_keyed_array_part = parts.iter().any(|part| {
2395 part.types
2396 .iter()
2397 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2398 });
2399 let keys_allowed_by_some_part = if has_array_part {
2400 true
2401 } else if has_keyed_array_part {
2402 properties.keys().all(|k| {
2403 parts.iter().any(|part| {
2404 part.types.iter().any(|t| {
2405 if let Atomic::TKeyedArray {
2406 properties: part_props,
2407 ..
2408 } = t
2409 {
2410 part_props.contains_key(k)
2411 } else {
2412 false
2413 }
2414 })
2415 })
2416 })
2417 } else {
2418 true
2419 };
2420 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2421 keys_satisfied && no_undeclared_extras
2422 }
2423 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2424 let key_atomic = match prop_key {
2425 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2426 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2427 };
2428 if !Type::single(key_atomic).is_subtype_structural(key) {
2429 return false;
2430 }
2431 let has_named_obj = prop.ty.types.iter().any(|a| {
2432 matches!(
2433 a,
2434 Atomic::TNamedObject { .. }
2435 | Atomic::TSelf { .. }
2436 | Atomic::TStaticObject { .. }
2437 | Atomic::TClosure { .. }
2438 | Atomic::TTemplateParam { .. }
2439 )
2440 });
2441 has_named_obj || prop.ty.is_subtype_structural(value)
2442 }),
2443 Atomic::TNamedObject { .. }
2444 | Atomic::TSelf { .. }
2445 | Atomic::TStaticObject { .. }
2446 | Atomic::TClosure { .. }
2447 | Atomic::TTemplateParam { .. } => true,
2448 _ => false,
2449 })
2450 }),
2451
2452 _ => false,
2453 }
2454}
2455
2456fn bare_array_may_fit(sub: &Type, sup: &Type) -> bool {
2458 fn array_like(t: &Atomic) -> bool {
2459 match t {
2460 Atomic::TArray { .. }
2461 | Atomic::TNonEmptyArray { .. }
2462 | Atomic::TList { .. }
2463 | Atomic::TNonEmptyList { .. }
2464 | Atomic::TKeyedArray { .. } => true,
2465 Atomic::TIntersection { parts } => parts.iter().all(|p| p.types.iter().all(array_like)),
2466 _ => false,
2467 }
2468 }
2469 matches!(
2470 sub.types.as_slice(),
2471 [Atomic::TArray { key, value }]
2472 if value.is_mixed()
2473 && (key.is_mixed()
2474 || matches!(key.types.as_slice(), [Atomic::TInt, Atomic::TString] | [Atomic::TString, Atomic::TInt]))
2475 ) && sup.types.iter().all(array_like)
2476}
2477
2478fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2484 if sub.len() != sup.len() {
2485 return false;
2486 }
2487 sub.iter()
2488 .zip(sup.iter())
2489 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2490}
2491
2492fn is_empty_array_literal(t: &Type) -> bool {
2495 !t.types.is_empty()
2496 && t.types.iter().all(
2497 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2498 )
2499}
2500
2501fn is_array_like(t: &Type) -> bool {
2503 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2504}
2505
2506pub struct UnionBuilder {
2513 ty: Type,
2514 strings: std::collections::HashSet<std::sync::Arc<str>>,
2515 ints: std::collections::HashSet<i64>,
2516 literals_independent: bool,
2518}
2519
2520impl Default for UnionBuilder {
2521 fn default() -> Self {
2522 Self::new()
2523 }
2524}
2525
2526impl UnionBuilder {
2527 pub fn new() -> Self {
2528 Self {
2529 ty: Type::empty(),
2530 strings: Default::default(),
2531 ints: Default::default(),
2532 literals_independent: true,
2533 }
2534 }
2535
2536 pub fn is_empty(&self) -> bool {
2537 self.ty.is_empty()
2538 }
2539
2540 pub fn merge(&mut self, other: &Type) {
2541 for a in &other.types {
2542 self.add(a.clone());
2543 }
2544 self.ty.possibly_undefined |= other.possibly_undefined;
2545 }
2546
2547 pub fn add(&mut self, atomic: Atomic) {
2548 let lone_never = matches!(self.ty.types.first(), Some(Atomic::TNever));
2550 if self.literals_independent && !lone_never {
2551 match &atomic {
2552 Atomic::TLiteralString(s) => {
2553 if self.strings.insert(s.clone()) {
2554 self.ty.types.push(atomic);
2555 }
2556 return;
2557 }
2558 Atomic::TLiteralInt(n) => {
2559 if self.ints.insert(*n) {
2560 self.ty.types.push(atomic);
2561 }
2562 return;
2563 }
2564 _ => {}
2565 }
2566 }
2567 let disturbs_literals = matches!(
2568 atomic,
2569 Atomic::TString | Atomic::TMixed | Atomic::TConditional { .. }
2570 ) || int_bounds(&atomic).is_some();
2571 self.ty.add_type(atomic);
2572 if disturbs_literals || lone_never {
2573 self.resync();
2574 }
2575 }
2576
2577 fn resync(&mut self) {
2578 self.strings.clear();
2579 self.ints.clear();
2580 self.literals_independent = true;
2581 for t in &self.ty.types {
2582 match t {
2583 Atomic::TLiteralString(s) => {
2584 self.strings.insert(s.clone());
2585 }
2586 Atomic::TLiteralInt(n) => {
2587 self.ints.insert(*n);
2588 }
2589 Atomic::TMixed | Atomic::TString => self.literals_independent = false,
2590 other if int_bounds(other).is_some() => self.literals_independent = false,
2591 _ => {}
2592 }
2593 }
2594 }
2595
2596 pub fn build(self) -> Type {
2597 self.ty
2598 }
2599}
2600
2601#[cfg(test)]
2602mod tests {
2603 use std::sync::Arc;
2604
2605 use super::*;
2606
2607 fn conditional(
2608 param_name: Option<Name>,
2609 subject: Type,
2610 if_true: Type,
2611 if_false: Type,
2612 ) -> Atomic {
2613 Atomic::TConditional {
2614 data: Box::new(crate::atomic::ConditionalData {
2615 param_name,
2616 subject,
2617 if_true,
2618 if_false,
2619 }),
2620 }
2621 }
2622
2623 #[test]
2624 fn single_is_single() {
2625 let u = Type::single(Atomic::TString);
2626 assert!(u.is_single());
2627 assert!(!u.is_nullable());
2628 }
2629
2630 #[test]
2631 fn nullable_has_null() {
2632 let u = Type::nullable(Atomic::TString);
2633 assert!(u.is_nullable());
2634 assert_eq!(u.types.len(), 2);
2635 }
2636
2637 #[test]
2638 fn add_type_deduplicates() {
2639 let mut u = Type::single(Atomic::TString);
2640 u.add_type(Atomic::TString);
2641 assert_eq!(u.types.len(), 1);
2642 }
2643
2644 #[test]
2645 fn array_key_is_int_string() {
2646 let k = Type::array_key();
2647 assert!(k.is_array_key());
2648 assert_eq!(k.types.len(), 2);
2649 }
2650
2651 #[test]
2652 fn is_array_key_false_for_plain_int() {
2653 assert!(!Type::int().is_array_key());
2654 }
2655
2656 #[test]
2657 fn is_array_key_false_for_mixed() {
2658 assert!(!Type::mixed().is_array_key());
2659 }
2660
2661 #[test]
2662 fn is_array_key_false_for_int_string_null() {
2663 let mut u = Type::array_key();
2664 u.add_type(Atomic::TNull);
2665 assert!(!u.is_array_key());
2666 }
2667
2668 #[test]
2669 fn add_type_literal_subsumed_by_base() {
2670 let mut u = Type::single(Atomic::TInt);
2671 u.add_type(Atomic::TLiteralInt(42));
2672 assert_eq!(u.types.len(), 1);
2673 assert!(matches!(u.types[0], Atomic::TInt));
2674 }
2675
2676 #[test]
2677 fn true_then_false_merges_to_bool() {
2678 let mut u = Type::single(Atomic::TTrue);
2679 u.add_type(Atomic::TFalse);
2680 assert_eq!(u.types.len(), 1);
2681 assert!(matches!(u.types[0], Atomic::TBool));
2682 }
2683
2684 #[test]
2685 fn false_then_true_merges_to_bool() {
2686 let mut u = Type::single(Atomic::TFalse);
2687 u.add_type(Atomic::TTrue);
2688 assert_eq!(u.types.len(), 1);
2689 assert!(matches!(u.types[0], Atomic::TBool));
2690 }
2691
2692 #[test]
2693 fn true_alone_stays_true() {
2694 let u = Type::single(Atomic::TTrue);
2695 assert_eq!(u.types.len(), 1);
2696 assert!(matches!(u.types[0], Atomic::TTrue));
2697 }
2698
2699 #[test]
2700 fn true_false_merge_preserves_other_union_members() {
2701 let mut u = Type::single(Atomic::TTrue);
2702 u.add_type(Atomic::TNull);
2703 u.add_type(Atomic::TFalse);
2704 assert_eq!(u.types.len(), 2);
2705 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2706 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2707 }
2708
2709 #[test]
2710 fn add_type_base_widens_literals() {
2711 let mut u = Type::single(Atomic::TLiteralInt(1));
2712 u.add_type(Atomic::TLiteralInt(2));
2713 u.add_type(Atomic::TInt);
2714 assert_eq!(u.types.len(), 1);
2715 assert!(matches!(u.types[0], Atomic::TInt));
2716 }
2717
2718 #[test]
2719 fn mixed_subsumes_everything() {
2720 let mut u = Type::single(Atomic::TString);
2721 u.add_type(Atomic::TMixed);
2722 assert_eq!(u.types.len(), 1);
2723 assert!(u.is_mixed());
2724 }
2725
2726 #[test]
2727 fn remove_null() {
2728 let u = Type::nullable(Atomic::TString);
2729 let narrowed = u.remove_null();
2730 assert!(!narrowed.is_nullable());
2731 assert_eq!(narrowed.types.len(), 1);
2732 }
2733
2734 #[test]
2735 fn narrow_to_truthy_removes_null_false() {
2736 let mut u = Type::empty();
2737 u.add_type(Atomic::TString);
2738 u.add_type(Atomic::TNull);
2739 u.add_type(Atomic::TFalse);
2740 let truthy = u.narrow_to_truthy();
2741 assert!(!truthy.is_nullable());
2742 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2743 }
2744
2745 #[test]
2746 fn merge_combines_types() {
2747 let a = Type::single(Atomic::TString);
2748 let b = Type::single(Atomic::TInt);
2749 let merged = Type::merge(&a, &b);
2750 assert_eq!(merged.types.len(), 2);
2751 }
2752
2753 #[test]
2754 fn intersect_keeps_narrower_side_not_self() {
2755 let int_ty = Type::single(Atomic::TInt);
2759 let mut literals = Type::empty();
2760 literals.add_type(Atomic::TLiteralInt(1));
2761 literals.add_type(Atomic::TLiteralInt(2));
2762
2763 let narrowed = int_ty.intersect_with(&literals);
2764 assert_eq!(narrowed.types.len(), 2);
2765 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2766 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2767 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2768 }
2769
2770 #[test]
2771 fn subtype_literal_int_under_int() {
2772 let sub = Type::single(Atomic::TLiteralInt(5));
2773 let sup = Type::single(Atomic::TInt);
2774 assert!(sub.is_subtype_structural(&sup));
2775 }
2776
2777 #[test]
2778 fn subtype_never_is_bottom() {
2779 let never = Type::never();
2780 let string = Type::single(Atomic::TString);
2781 assert!(never.is_subtype_structural(&string));
2782 }
2783
2784 #[test]
2785 fn subtype_everything_under_mixed() {
2786 let string = Type::single(Atomic::TString);
2787 let mixed = Type::mixed();
2788 assert!(string.is_subtype_structural(&mixed));
2789 }
2790
2791 #[test]
2792 fn subtype_enum_case_under_own_enum() {
2793 let sub = Type::single(Atomic::TLiteralEnumCase {
2794 enum_fqcn: Name::new("RoundingMode"),
2795 case_name: Name::new("Unnecessary"),
2796 });
2797 let sup = Type::single(Atomic::TNamedObject {
2798 fqcn: Name::new("RoundingMode"),
2799 type_params: empty_type_params(),
2800 });
2801 assert!(sub.is_subtype_structural(&sup));
2802 }
2803
2804 #[test]
2805 fn enum_case_not_subtype_of_unrelated_enum() {
2806 let sub = Type::single(Atomic::TLiteralEnumCase {
2807 enum_fqcn: Name::new("RoundingMode"),
2808 case_name: Name::new("Unnecessary"),
2809 });
2810 let sup = Type::single(Atomic::TNamedObject {
2811 fqcn: Name::new("Suit"),
2812 type_params: empty_type_params(),
2813 });
2814 assert!(!sub.is_subtype_structural(&sup));
2815 }
2816
2817 #[test]
2818 fn subtype_enum_case_under_own_self_and_static() {
2819 let case = |enum_fqcn: &str| {
2820 Type::single(Atomic::TLiteralEnumCase {
2821 enum_fqcn: Name::new(enum_fqcn),
2822 case_name: Name::new("A"),
2823 })
2824 };
2825 let own = Type::single(Atomic::TSelf {
2826 fqcn: Name::new("Suit"),
2827 });
2828 let own_static = Type::single(Atomic::TStaticObject {
2829 fqcn: Name::new("Suit"),
2830 });
2831 assert!(case("Suit").is_subtype_structural(&own));
2832 assert!(case("Suit").is_subtype_structural(&own_static));
2833 assert!(!case("Other").is_subtype_structural(&own));
2834 }
2835
2836 #[test]
2837 fn subtype_enum_case_under_bare_object() {
2838 let sub = Type::single(Atomic::TLiteralEnumCase {
2839 enum_fqcn: Name::new("RoundingMode"),
2840 case_name: Name::new("Unnecessary"),
2841 });
2842 let sup = Type::single(Atomic::TObject);
2843 assert!(sub.is_subtype_structural(&sup));
2844 }
2845
2846 #[test]
2847 fn template_substitution() {
2848 let mut bindings = FxHashMap::default();
2849 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2850
2851 let tmpl = Type::single(Atomic::TTemplateParam {
2852 name: Name::new("T"),
2853 as_type: Box::new(Type::mixed()),
2854 defining_entity: Name::new("MyClass"),
2855 });
2856
2857 let resolved = tmpl.substitute_templates(&bindings);
2858 assert_eq!(resolved.types.len(), 1);
2859 assert!(matches!(resolved.types[0], Atomic::TString));
2860 }
2861
2862 #[test]
2863 fn intersection_is_object() {
2864 let parts = vec![
2865 Type::single(Atomic::TNamedObject {
2866 fqcn: Name::new("Iterator"),
2867 type_params: empty_type_params(),
2868 }),
2869 Type::single(Atomic::TNamedObject {
2870 fqcn: Name::new("Countable"),
2871 type_params: empty_type_params(),
2872 }),
2873 ];
2874 let atomic = Atomic::TIntersection {
2875 parts: vec_to_type_params(parts),
2876 };
2877 assert!(atomic.is_object());
2878 assert!(!atomic.can_be_falsy());
2879 assert!(atomic.can_be_truthy());
2880 }
2881
2882 #[test]
2883 fn intersection_display_two_parts() {
2884 let parts = vec![
2885 Type::single(Atomic::TNamedObject {
2886 fqcn: Name::new("Iterator"),
2887 type_params: empty_type_params(),
2888 }),
2889 Type::single(Atomic::TNamedObject {
2890 fqcn: Name::new("Countable"),
2891 type_params: empty_type_params(),
2892 }),
2893 ];
2894 let u = Type::single(Atomic::TIntersection {
2895 parts: vec_to_type_params(parts),
2896 });
2897 assert_eq!(format!("{u}"), "Iterator&Countable");
2898 }
2899
2900 #[test]
2901 fn intersection_display_three_parts() {
2902 let parts = vec![
2903 Type::single(Atomic::TNamedObject {
2904 fqcn: Name::new("A"),
2905 type_params: empty_type_params(),
2906 }),
2907 Type::single(Atomic::TNamedObject {
2908 fqcn: Name::new("B"),
2909 type_params: empty_type_params(),
2910 }),
2911 Type::single(Atomic::TNamedObject {
2912 fqcn: Name::new("C"),
2913 type_params: empty_type_params(),
2914 }),
2915 ];
2916 let u = Type::single(Atomic::TIntersection {
2917 parts: vec_to_type_params(parts),
2918 });
2919 assert_eq!(format!("{u}"), "A&B&C");
2920 }
2921
2922 #[test]
2923 fn intersection_in_nullable_union_display() {
2924 let intersection = Atomic::TIntersection {
2925 parts: vec_to_type_params(vec![
2926 Type::single(Atomic::TNamedObject {
2927 fqcn: Name::new("Iterator"),
2928 type_params: empty_type_params(),
2929 }),
2930 Type::single(Atomic::TNamedObject {
2931 fqcn: Name::new("Countable"),
2932 type_params: empty_type_params(),
2933 }),
2934 ]),
2935 };
2936 let mut u = Type::single(intersection);
2937 u.add_type(Atomic::TNull);
2938 assert!(u.is_nullable());
2939 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2940 }
2941
2942 fn t_param(name: &str) -> Type {
2945 Type::single(Atomic::TTemplateParam {
2946 name: Name::new(name),
2947 as_type: Box::new(Type::mixed()),
2948 defining_entity: Name::new("Fn"),
2949 })
2950 }
2951
2952 fn bindings_t_string() -> FxHashMap<Name, Type> {
2953 let mut b = FxHashMap::default();
2954 b.insert(Name::new("T"), Type::single(Atomic::TString));
2955 b
2956 }
2957
2958 #[test]
2959 fn substitute_non_empty_array_key_and_value() {
2960 let ty = Type::single(Atomic::TNonEmptyArray {
2961 key: Box::new(t_param("T")),
2962 value: Box::new(t_param("T")),
2963 });
2964 let result = ty.substitute_templates(&bindings_t_string());
2965 assert_eq!(result.types.len(), 1);
2966 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2967 panic!("expected TNonEmptyArray");
2968 };
2969 assert!(matches!(key.types[0], Atomic::TString));
2970 assert!(matches!(value.types[0], Atomic::TString));
2971 }
2972
2973 #[test]
2974 fn substitute_non_empty_list_value() {
2975 let ty = Type::single(Atomic::TNonEmptyList {
2976 value: Box::new(t_param("T")),
2977 });
2978 let result = ty.substitute_templates(&bindings_t_string());
2979 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2980 panic!("expected TNonEmptyList");
2981 };
2982 assert!(matches!(value.types[0], Atomic::TString));
2983 }
2984
2985 #[test]
2986 fn substitute_keyed_array_property_types() {
2987 use crate::atomic::{ArrayKey, KeyedProperty};
2988 use indexmap::IndexMap;
2989 let mut props = IndexMap::new();
2990 props.insert(
2991 ArrayKey::String(Arc::from("name")),
2992 KeyedProperty {
2993 ty: t_param("T"),
2994 optional: false,
2995 },
2996 );
2997 props.insert(
2998 ArrayKey::String(Arc::from("tag")),
2999 KeyedProperty {
3000 ty: t_param("T"),
3001 optional: true,
3002 },
3003 );
3004 let ty = Type::single(Atomic::TKeyedArray {
3005 properties: Box::new(props),
3006 is_open: true,
3007 is_list: false,
3008 });
3009 let result = ty.substitute_templates(&bindings_t_string());
3010 let Atomic::TKeyedArray {
3011 properties,
3012 is_open,
3013 is_list,
3014 } = &result.types[0]
3015 else {
3016 panic!("expected TKeyedArray");
3017 };
3018 assert!(is_open);
3019 assert!(!is_list);
3020 assert!(matches!(
3021 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
3022 Atomic::TString
3023 ));
3024 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
3025 assert!(matches!(
3026 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
3027 Atomic::TString
3028 ));
3029 }
3030
3031 #[test]
3032 fn substitute_callable_params_and_return() {
3033 use crate::atomic::FnParam;
3034 let ty = Type::single(Atomic::TCallable {
3035 params: Some(Box::new(vec![FnParam {
3036 name: Name::new("x"),
3037 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3038 out_ty: None,
3039 default: None,
3040 is_variadic: false,
3041 is_byref: false,
3042 is_optional: false,
3043 }])),
3044 return_type: Some(Box::new(t_param("T"))),
3045 is_pure: false,
3046 });
3047 let result = ty.substitute_templates(&bindings_t_string());
3048 let Atomic::TCallable {
3049 params,
3050 return_type,
3051 ..
3052 } = &result.types[0]
3053 else {
3054 panic!("expected TCallable");
3055 };
3056 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
3057 let param_union = param_ty.to_union();
3058 assert!(matches!(param_union.types[0], Atomic::TString));
3059 let ret = return_type.as_ref().unwrap();
3060 assert!(matches!(ret.types[0], Atomic::TString));
3061 }
3062
3063 #[test]
3064 fn substitute_callable_bare_no_panic() {
3065 let ty = Type::single(Atomic::TCallable {
3067 params: None,
3068 return_type: None,
3069 is_pure: false,
3070 });
3071 let result = ty.substitute_templates(&bindings_t_string());
3072 assert!(matches!(
3073 result.types[0],
3074 Atomic::TCallable {
3075 params: None,
3076 return_type: None,
3077 ..
3078 }
3079 ));
3080 }
3081
3082 #[test]
3083 fn substitute_closure_params_return_and_this() {
3084 use crate::atomic::FnParam;
3085 let ty = Type::single(Atomic::TClosure {
3086 data: Box::new(crate::atomic::ClosureData {
3087 params: Box::new([FnParam {
3088 name: Name::new("a"),
3089 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3090 out_ty: None,
3091 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
3092 is_variadic: true,
3093 is_byref: true,
3094 is_optional: true,
3095 }]),
3096 return_type: t_param("T"),
3097 this_type: Some(t_param("T")),
3098 is_pure: false,
3099 is_static: false,
3100 }),
3101 });
3102 let result = ty.substitute_templates(&bindings_t_string());
3103 let Atomic::TClosure { data } = &result.types[0] else {
3104 panic!("expected TClosure");
3105 };
3106 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
3107 let p = ¶ms[0];
3108 let ty_union = p.ty.as_ref().unwrap().to_union();
3109 let default_union = p.default.as_ref().unwrap().to_union();
3110 assert!(matches!(ty_union.types[0], Atomic::TString));
3111 assert!(matches!(default_union.types[0], Atomic::TString));
3112 assert!(p.is_variadic);
3114 assert!(p.is_byref);
3115 assert!(p.is_optional);
3116 assert!(matches!(return_type.types[0], Atomic::TString));
3117 assert!(matches!(
3118 this_type.as_ref().unwrap().types[0],
3119 Atomic::TString
3120 ));
3121 }
3122
3123 #[test]
3124 fn substitute_conditional_all_branches() {
3125 let ty = Type::single(conditional(
3126 None,
3127 t_param("T"),
3128 t_param("T"),
3129 Type::single(Atomic::TInt),
3130 ));
3131 let result = ty.substitute_templates(&bindings_t_string());
3132 let Atomic::TConditional { data } = &result.types[0] else {
3133 panic!("expected TConditional");
3134 };
3135 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
3136 assert!(matches!(subject.types[0], Atomic::TString));
3137 assert!(matches!(if_true.types[0], Atomic::TString));
3138 assert!(matches!(if_false.types[0], Atomic::TInt));
3139 }
3140
3141 #[test]
3142 fn resolve_conditional_is_null_non_null_arg() {
3143 let ty = Type::single(conditional(
3144 Some(Name::new("x")),
3145 Type::single(Atomic::TNull),
3146 Type::single(Atomic::TInt),
3147 Type::single(Atomic::TString),
3148 ));
3149 let result = ty.resolve_conditional_returns(|name| {
3150 if name == "x" {
3151 Some(Type::single(Atomic::TString)) } else {
3153 None
3154 }
3155 });
3156 assert!(result.types.len() == 1);
3157 assert!(matches!(result.types[0], Atomic::TString));
3158 }
3159
3160 #[test]
3161 fn resolve_conditional_is_null_null_arg() {
3162 let ty = Type::single(conditional(
3163 Some(Name::new("x")),
3164 Type::single(Atomic::TNull),
3165 Type::single(Atomic::TInt),
3166 Type::single(Atomic::TString),
3167 ));
3168 let result = ty.resolve_conditional_returns(|name| {
3169 if name == "x" {
3170 Some(Type::single(Atomic::TNull)) } else {
3172 None
3173 }
3174 });
3175 assert!(result.types.len() == 1);
3176 assert!(matches!(result.types[0], Atomic::TInt));
3177 }
3178
3179 #[test]
3180 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
3181 let mut nullable_str = Type::single(Atomic::TString);
3182 nullable_str.add_type(Atomic::TNull);
3183 let ty = Type::single(conditional(
3184 Some(Name::new("x")),
3185 Type::single(Atomic::TNull),
3186 Type::single(Atomic::TInt),
3187 Type::single(Atomic::TString),
3188 ));
3189 let result = ty.resolve_conditional_returns(|name| {
3190 if name == "x" {
3191 Some(nullable_str.clone())
3192 } else {
3193 None
3194 }
3195 });
3196 assert_eq!(result.types.len(), 2);
3198 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3199 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3200 }
3201
3202 #[test]
3203 fn resolve_conditional_nested_widens_inner_branch() {
3204 let inner = Type::single(conditional(
3207 Some(Name::new("x")),
3208 Type::single(Atomic::TString),
3209 Type::single(Atomic::TString),
3210 Type::single(Atomic::TFloat),
3211 ));
3212 let ty = Type::single(conditional(
3213 Some(Name::new("x")),
3214 Type::single(Atomic::TNull),
3215 Type::single(Atomic::TInt),
3216 inner,
3217 ));
3218 let result = ty.resolve_conditional_returns(|_| None);
3220 assert!(
3221 result
3222 .types
3223 .iter()
3224 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3225 "no TConditional should survive: {:?}",
3226 result.types
3227 );
3228 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
3229 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
3230 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
3231 }
3232
3233 #[test]
3234 fn resolve_conditional_nested_resolves_inner_branch() {
3235 let inner = Type::single(conditional(
3239 Some(Name::new("x")),
3240 Type::single(Atomic::TString),
3241 Type::single(Atomic::TString),
3242 Type::single(Atomic::TFloat),
3243 ));
3244 let ty = Type::single(conditional(
3245 Some(Name::new("x")),
3246 Type::single(Atomic::TNull),
3247 Type::single(Atomic::TInt),
3248 inner,
3249 ));
3250 let result = ty.resolve_conditional_returns(|name| {
3252 if name == "x" {
3253 Some(Type::single(Atomic::TString))
3254 } else {
3255 None
3256 }
3257 });
3258 assert!(
3259 result
3260 .types
3261 .iter()
3262 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3263 "no TConditional should survive: {:?}",
3264 result.types
3265 );
3266 assert_eq!(result.types.len(), 1);
3267 assert!(matches!(result.types[0], Atomic::TString));
3268 }
3269
3270 #[test]
3271 fn resolve_conditional_true_subject_bool_arg_widens() {
3272 let ty = Type::single(conditional(
3276 Some(Name::new("x")),
3277 Type::single(Atomic::TTrue),
3278 Type::single(Atomic::TInt),
3279 Type::single(Atomic::TString),
3280 ));
3281 let result = ty.resolve_conditional_returns(|name| {
3282 if name == "x" {
3283 Some(Type::single(Atomic::TBool))
3284 } else {
3285 None
3286 }
3287 });
3288 assert_eq!(result.types.len(), 2);
3289 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3290 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3291 }
3292
3293 #[test]
3294 fn resolve_conditional_true_subject_mixed_arg_widens() {
3295 let ty = Type::single(conditional(
3298 Some(Name::new("x")),
3299 Type::single(Atomic::TTrue),
3300 Type::single(Atomic::TInt),
3301 Type::single(Atomic::TString),
3302 ));
3303 let result = ty.resolve_conditional_returns(|name| {
3304 if name == "x" {
3305 Some(Type::mixed())
3306 } else {
3307 None
3308 }
3309 });
3310 assert_eq!(result.types.len(), 2);
3311 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3312 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3313 }
3314
3315 #[test]
3316 fn resolve_conditional_true_subject_literal_args() {
3317 let ty = Type::single(conditional(
3320 Some(Name::new("x")),
3321 Type::single(Atomic::TTrue),
3322 Type::single(Atomic::TInt),
3323 Type::single(Atomic::TString),
3324 ));
3325 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3326 if name == "x" {
3327 Some(Type::single(Atomic::TFalse))
3328 } else {
3329 None
3330 }
3331 });
3332 assert_eq!(false_arg.types.len(), 1);
3333 assert!(matches!(false_arg.types[0], Atomic::TString));
3334
3335 let true_arg = ty.resolve_conditional_returns(|name| {
3336 if name == "x" {
3337 Some(Type::single(Atomic::TTrue))
3338 } else {
3339 None
3340 }
3341 });
3342 assert_eq!(true_arg.types.len(), 1);
3343 assert!(matches!(true_arg.types[0], Atomic::TInt));
3344 }
3345
3346 #[test]
3347 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3348 let ty = Type::single(conditional(
3352 Some(Name::new("x")),
3353 Type::single(Atomic::TBool),
3354 Type::single(Atomic::TInt),
3355 Type::single(Atomic::TString),
3356 ));
3357 let result = ty.resolve_conditional_returns(|name| {
3358 if name == "x" {
3359 Some(Type::single(Atomic::TScalar))
3360 } else {
3361 None
3362 }
3363 });
3364 assert_eq!(result.types.len(), 2);
3365 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3366 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3367 }
3368
3369 #[test]
3370 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3371 let ty = Type::single(conditional(
3375 Some(Name::new("x")),
3376 Type::single(Atomic::TString),
3377 Type::single(Atomic::TInt),
3378 Type::single(Atomic::TBool),
3379 ));
3380 let result = ty.resolve_conditional_returns(|name| {
3381 if name == "x" {
3382 Some(Type::single(Atomic::TBool))
3383 } else {
3384 None
3385 }
3386 });
3387 assert_eq!(result.types.len(), 1);
3388 assert!(matches!(result.types[0], Atomic::TBool));
3389 }
3390
3391 #[test]
3392 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3393 let ty = Type::single(conditional(
3396 Some(Name::new("x")),
3397 Type::single(Atomic::TList {
3398 value: Box::new(Type::mixed()),
3399 }),
3400 Type::single(Atomic::TInt),
3401 Type::single(Atomic::TString),
3402 ));
3403 let result = ty.resolve_conditional_returns(|name| {
3404 if name == "x" {
3405 Some(Type::single(Atomic::TArray {
3406 key: Box::new(Type::mixed()),
3407 value: Box::new(Type::mixed()),
3408 }))
3409 } else {
3410 None
3411 }
3412 });
3413 assert_eq!(result.types.len(), 2);
3414 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3415 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3416 }
3417
3418 #[test]
3419 fn resolve_conditional_float_subject_int_literal_false_branch() {
3420 let ty = resolve_conditional_branch(
3423 &Atomic::TFloat,
3424 &Type::single(Atomic::TLiteralInt(42)),
3425 &Type::single(Atomic::TInt),
3426 &Type::single(Atomic::TString),
3427 );
3428 assert!(matches!(
3429 ty.as_ref(),
3430 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3431 ));
3432 }
3433
3434 #[test]
3435 fn resolve_conditional_int_subject_float_literal_false_branch() {
3436 let ty = resolve_conditional_branch(
3437 &Atomic::TInt,
3438 &Type::single(Atomic::TLiteralFloat(1, 0)),
3439 &Type::single(Atomic::TInt),
3440 &Type::single(Atomic::TString),
3441 );
3442 assert!(matches!(
3443 ty.as_ref(),
3444 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3445 ));
3446 }
3447
3448 #[test]
3449 fn resolve_conditional_float_subject_float_literal_then_branch() {
3450 let ty = resolve_conditional_branch(
3451 &Atomic::TFloat,
3452 &Type::single(Atomic::TLiteralFloat(1, 5)),
3453 &Type::single(Atomic::TInt),
3454 &Type::single(Atomic::TString),
3455 );
3456 assert!(matches!(
3457 ty.as_ref(),
3458 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3459 ));
3460 }
3461
3462 #[test]
3463 fn resolve_conditional_int_subject_int_literal_then_branch() {
3464 let ty = resolve_conditional_branch(
3465 &Atomic::TInt,
3466 &Type::single(Atomic::TLiteralInt(7)),
3467 &Type::single(Atomic::TInt),
3468 &Type::single(Atomic::TString),
3469 );
3470 assert!(matches!(
3471 ty.as_ref(),
3472 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3473 ));
3474 }
3475
3476 #[test]
3477 fn resolve_conditional_int_subject_float_arg_false_branch() {
3478 let ty = resolve_conditional_branch(
3481 &Atomic::TInt,
3482 &Type::single(Atomic::TFloat),
3483 &Type::single(Atomic::TInt),
3484 &Type::single(Atomic::TString),
3485 );
3486 assert!(matches!(
3487 ty.as_ref(),
3488 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3489 ));
3490 }
3491
3492 #[test]
3493 fn resolve_conditional_array_subject_string_arg_false_branch() {
3494 let ty = resolve_conditional_branch(
3495 &Atomic::TArray {
3496 key: Box::new(Type::mixed()),
3497 value: Box::new(Type::mixed()),
3498 },
3499 &Type::single(Atomic::TString),
3500 &Type::single(Atomic::TInt),
3501 &Type::single(Atomic::TString),
3502 );
3503 assert!(matches!(
3504 ty.as_ref(),
3505 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3506 ));
3507 }
3508
3509 #[test]
3510 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3511 let ty = resolve_conditional_branch(
3512 &Atomic::TArray {
3513 key: Box::new(Type::mixed()),
3514 value: Box::new(Type::mixed()),
3515 },
3516 &Type::single(Atomic::TBool),
3517 &Type::single(Atomic::TInt),
3518 &Type::single(Atomic::TString),
3519 );
3520 assert!(matches!(
3521 ty.as_ref(),
3522 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3523 ));
3524 }
3525
3526 #[test]
3527 fn resolve_conditional_array_subject_mixed_arg_widens() {
3528 let ty = resolve_conditional_branch(
3532 &Atomic::TArray {
3533 key: Box::new(Type::mixed()),
3534 value: Box::new(Type::mixed()),
3535 },
3536 &Type::mixed(),
3537 &Type::single(Atomic::TInt),
3538 &Type::single(Atomic::TString),
3539 );
3540 assert!(ty.is_none());
3541 }
3542
3543 #[test]
3544 fn resolve_conditional_array_subject_list_arg_then_branch() {
3545 let ty = resolve_conditional_branch(
3548 &Atomic::TArray {
3549 key: Box::new(Type::mixed()),
3550 value: Box::new(Type::mixed()),
3551 },
3552 &Type::single(Atomic::TList {
3553 value: Box::new(Type::mixed()),
3554 }),
3555 &Type::single(Atomic::TInt),
3556 &Type::single(Atomic::TString),
3557 );
3558 assert!(matches!(
3559 ty.as_ref(),
3560 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3561 ));
3562 }
3563
3564 #[test]
3565 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3566 let ty = resolve_conditional_branch(
3568 &Atomic::TList {
3569 value: Box::new(Type::mixed()),
3570 },
3571 &Type::single(Atomic::TKeyedArray {
3572 properties: Box::default(),
3573 is_open: false,
3574 is_list: true,
3575 }),
3576 &Type::single(Atomic::TInt),
3577 &Type::single(Atomic::TString),
3578 );
3579 assert!(matches!(
3580 ty.as_ref(),
3581 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3582 ));
3583 }
3584
3585 #[test]
3586 fn resolve_conditional_list_subject_list_string_union_widens() {
3587 let ty = resolve_conditional_branch(
3591 &Atomic::TList {
3592 value: Box::new(Type::mixed()),
3593 },
3594 &Type::from_vec(vec![
3595 Atomic::TList {
3596 value: Box::new(Type::mixed()),
3597 },
3598 Atomic::TString,
3599 ]),
3600 &Type::single(Atomic::TInt),
3601 &Type::single(Atomic::TString),
3602 );
3603 assert!(ty.is_none());
3604 }
3605
3606 #[test]
3607 fn resolve_conditional_numeric_subject_widens() {
3608 let ty = resolve_conditional_branch(
3612 &Atomic::TNumeric,
3613 &Type::single(Atomic::TLiteralString("123".into())),
3614 &Type::single(Atomic::TInt),
3615 &Type::single(Atomic::TString),
3616 );
3617 assert!(ty.is_none());
3618 }
3619
3620 #[test]
3621 fn resolve_conditional_non_empty_string_subject_widens() {
3622 let ty = resolve_conditional_branch(
3625 &Atomic::TNonEmptyString,
3626 &Type::single(Atomic::TString),
3627 &Type::single(Atomic::TInt),
3628 &Type::single(Atomic::TString),
3629 );
3630 assert!(ty.is_none());
3631 }
3632
3633 #[test]
3634 fn resolve_conditional_template_arg_widens() {
3635 let ty = resolve_conditional_branch(
3638 &Atomic::TString,
3639 &t_param("T"),
3640 &Type::single(Atomic::TInt),
3641 &Type::single(Atomic::TString),
3642 );
3643 assert!(ty.is_none());
3644 }
3645
3646 #[test]
3647 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3648 let ty = resolve_conditional_branch(
3651 &Atomic::TMixed,
3652 &Type::single(Atomic::TBool),
3653 &Type::single(Atomic::TInt),
3654 &Type::single(Atomic::TString),
3655 );
3656 assert!(matches!(
3657 ty.as_ref(),
3658 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3659 ));
3660 }
3661
3662 #[test]
3663 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3664 let ty = resolve_conditional_branch(
3667 &Atomic::TScalar,
3668 &Type::single(Atomic::TNull),
3669 &Type::single(Atomic::TInt),
3670 &Type::single(Atomic::TString),
3671 );
3672 assert!(matches!(
3673 ty.as_ref(),
3674 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3675 ));
3676 }
3677
3678 #[test]
3679 fn resolve_conditional_object_subject_named_arg_then_branch() {
3680 let ty = resolve_conditional_branch(
3682 &Atomic::TObject,
3683 &Type::single(Atomic::TNamedObject {
3684 fqcn: Name::new("Foo"),
3685 type_params: empty_type_params(),
3686 }),
3687 &Type::single(Atomic::TInt),
3688 &Type::single(Atomic::TString),
3689 );
3690 assert!(matches!(
3691 ty.as_ref(),
3692 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3693 ));
3694 }
3695
3696 #[test]
3697 fn resolve_conditional_named_subject_other_class_widens() {
3698 let ty = resolve_conditional_branch(
3702 &Atomic::TNamedObject {
3703 fqcn: Name::new("Foo"),
3704 type_params: empty_type_params(),
3705 },
3706 &Type::single(Atomic::TNamedObject {
3707 fqcn: Name::new("Bar"),
3708 type_params: empty_type_params(),
3709 }),
3710 &Type::single(Atomic::TInt),
3711 &Type::single(Atomic::TString),
3712 );
3713 assert!(ty.is_none());
3714 }
3715
3716 #[test]
3717 fn resolve_conditional_enum_case_subject_widens() {
3718 let ty = resolve_conditional_branch(
3721 &Atomic::TLiteralEnumCase {
3722 enum_fqcn: Name::new("RoundingMode"),
3723 case_name: Name::new("Unnecessary"),
3724 },
3725 &Type::single(Atomic::TInt),
3726 &Type::single(Atomic::TInt),
3727 &Type::single(Atomic::TString),
3728 );
3729 assert!(ty.is_none());
3730 }
3731
3732 #[test]
3733 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3734 let ty = resolve_conditional_branch(
3736 &Atomic::TNull,
3737 &Type::single(Atomic::TBool),
3738 &Type::single(Atomic::TInt),
3739 &Type::single(Atomic::TString),
3740 );
3741 assert!(matches!(
3742 ty.as_ref(),
3743 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3744 ));
3745 }
3746
3747 #[test]
3748 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3749 let ty = resolve_conditional_branch(
3752 &Atomic::TBool,
3753 &Type::single(Atomic::TTrue),
3754 &Type::single(Atomic::TInt),
3755 &Type::single(Atomic::TString),
3756 );
3757 assert!(matches!(
3758 ty.as_ref(),
3759 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3760 ));
3761 }
3762
3763 #[test]
3764 fn substitute_intersection_parts() {
3765 let ty = Type::single(Atomic::TIntersection {
3766 parts: vec_to_type_params(vec![
3767 Type::single(Atomic::TNamedObject {
3768 fqcn: Name::new("Countable"),
3769 type_params: empty_type_params(),
3770 }),
3771 t_param("T"),
3772 ]),
3773 });
3774 let result = ty.substitute_templates(&bindings_t_string());
3775 let Atomic::TIntersection { parts } = &result.types[0] else {
3776 panic!("expected TIntersection");
3777 };
3778 assert_eq!(parts.len(), 2);
3779 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3780 assert!(matches!(parts[1].types[0], Atomic::TString));
3781 }
3782
3783 #[test]
3784 fn substitute_no_template_params_identity() {
3785 let ty = Type::single(Atomic::TInt);
3786 let result = ty.substitute_templates(&bindings_t_string());
3787 assert!(matches!(result.types[0], Atomic::TInt));
3788 }
3789}