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::TTrue | Atomic::TFalse)
362 && self.types.iter().any(|t| matches!(t, Atomic::TBool))
363 {
364 return;
365 }
366 if matches!(atomic, Atomic::TTrue) && self.types.iter().any(|t| matches!(t, Atomic::TFalse))
369 {
370 self.types.retain(|t| !matches!(t, Atomic::TFalse));
371 self.types.push(Atomic::TBool);
372 return;
373 }
374 if matches!(atomic, Atomic::TFalse) && self.types.iter().any(|t| matches!(t, Atomic::TTrue))
375 {
376 self.types.retain(|t| !matches!(t, Atomic::TTrue));
377 self.types.push(Atomic::TBool);
378 return;
379 }
380 if matches!(atomic, Atomic::TString) {
382 self.types
383 .retain(|t| !matches!(t, Atomic::TLiteralString(_)));
384 }
385 if matches!(atomic, Atomic::TBool) {
387 self.types
388 .retain(|t| !matches!(t, Atomic::TTrue | Atomic::TFalse));
389 }
390
391 if matches!(atomic, Atomic::TNever) {
393 if !self.types.is_empty() {
394 return;
395 }
396 } else {
397 self.types.retain(|t| !matches!(t, Atomic::TNever));
398 }
399
400 if let Atomic::TKeyedArray {
406 properties,
407 is_open,
408 ..
409 } = &atomic
410 {
411 if properties.is_empty() && !is_open {
412 for existing in &self.types {
413 match existing {
414 Atomic::TArray { .. }
415 | Atomic::TNonEmptyArray { .. }
416 | Atomic::TList { .. }
417 | Atomic::TNonEmptyList { .. } => {
418 return; }
420 _ => {}
421 }
422 }
423 }
424 }
425
426 let is_generic_array_or_list = matches!(
429 &atomic,
430 Atomic::TArray { .. }
431 | Atomic::TNonEmptyArray { .. }
432 | Atomic::TList { .. }
433 | Atomic::TNonEmptyList { .. }
434 );
435 if is_generic_array_or_list {
436 self.types.retain(|t| {
437 if let Atomic::TKeyedArray {
438 properties,
439 is_open,
440 ..
441 } = t
442 {
443 !properties.is_empty() || *is_open
444 } else {
445 true
446 }
447 });
448 }
449
450 self.types.push(atomic);
451 }
452
453 pub fn remove_null(&self) -> Type {
457 self.filter(|t| !matches!(t, Atomic::TNull))
458 }
459
460 pub fn remove_false(&self) -> Type {
463 let mut result = self.filter(|t| !matches!(t, Atomic::TFalse | Atomic::TBool));
464 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
465 result.add_type(Atomic::TTrue);
466 }
467 result
468 }
469
470 pub fn remove_true(&self) -> Type {
473 let mut result = self.filter(|t| !matches!(t, Atomic::TTrue | Atomic::TBool));
474 if self.types.iter().any(|t| matches!(t, Atomic::TBool)) {
475 result.add_type(Atomic::TFalse);
476 }
477 result
478 }
479
480 pub fn core_type(&self) -> Type {
482 self.remove_null().remove_false()
483 }
484
485 pub fn narrow_to_truthy(&self) -> Type {
487 if self.is_mixed_not_template() {
488 return Type::mixed();
489 }
490 let mut result = Type::empty();
491 result.from_docblock = self.from_docblock;
492 for t in &self.types {
493 match t {
494 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
498 Atomic::TLiteralInt(0)
500 | Atomic::TLiteralFloat(0, 0)
501 | Atomic::TNull
502 | Atomic::TFalse => {}
503 Atomic::TLiteralString(s) if s.as_ref() == "" || s.as_ref() == "0" => {}
504 Atomic::TBool => result.add_type(Atomic::TTrue),
506 Atomic::TArray { key, value } => result.add_type(Atomic::TNonEmptyArray {
508 key: key.clone(),
509 value: value.clone(),
510 }),
511 Atomic::TList { value } => result.add_type(Atomic::TNonEmptyList {
512 value: value.clone(),
513 }),
514 Atomic::TString => result.add_type(Atomic::TNonEmptyString),
518 Atomic::TNonNegativeInt => result.add_type(Atomic::TPositiveInt),
523 Atomic::TIntRange { min: Some(0), max } if max.is_none_or(|m| m >= 1) => {
524 let atom = if max.is_none() {
525 Atomic::TPositiveInt
526 } else {
527 Atomic::TIntRange {
528 min: Some(1),
529 max: *max,
530 }
531 };
532 result.add_type(atom);
533 }
534 Atomic::TIntRange { min, max: Some(0) } => {
536 let atom = match min {
537 None => Atomic::TNegativeInt,
538 Some(n) if *n <= -1 => Atomic::TIntRange {
539 min: *min,
540 max: Some(-1),
541 },
542 _ => continue, };
544 result.add_type(atom);
545 }
546 t if !t.can_be_truthy() => {}
548 _ => result.add_type(t.clone()),
549 }
550 }
551 result
552 }
553
554 pub fn narrow_to_falsy(&self) -> Type {
556 if self.is_mixed_not_template() {
557 return Type::from_vec(vec![
558 Atomic::TNull,
559 Atomic::TFalse,
560 Atomic::TLiteralInt(0),
561 Atomic::TLiteralString("".into()),
562 ]);
563 }
564 let mut result = Type::empty();
565 result.from_docblock = self.from_docblock;
566 for t in &self.types {
567 match t {
568 Atomic::TTemplateParam { .. } => result.add_type(t.clone()),
573 Atomic::TBool => result.add_type(Atomic::TFalse),
575 Atomic::TInt => result.add_type(Atomic::TLiteralInt(0)),
577 Atomic::TFloat => result.add_type(Atomic::TLiteralFloat(0, 0)),
579 Atomic::TString => {
581 result.add_type(Atomic::TLiteralString("".into()));
582 result.add_type(Atomic::TLiteralString("0".into()));
583 }
584 Atomic::TNumericString => result.add_type(Atomic::TLiteralString("0".into())),
586 Atomic::TNonNegativeInt => result.add_type(Atomic::TLiteralInt(0)),
588 Atomic::TIntRange {
590 min: Some(0),
591 max: Some(_) | None,
592 } => result.add_type(Atomic::TLiteralInt(0)),
593 Atomic::TIntRange { max: Some(0), .. } => result.add_type(Atomic::TLiteralInt(0)),
595 t if !t.can_be_falsy() => {} _ => result.add_type(t.clone()),
597 }
598 }
599 result
600 }
601
602 pub fn narrow_instanceof(&self, class: &str) -> Type {
608 let narrowed_ty = Atomic::TNamedObject {
609 fqcn: class.into(),
610 type_params: empty_type_params(),
611 };
612 let has_object = self.types.iter().any(|t| {
614 matches!(
615 t,
616 Atomic::TObject | Atomic::TNamedObject { .. } | Atomic::TMixed | Atomic::TNull )
618 });
619 if has_object || self.is_empty() {
620 Type::single(narrowed_ty)
621 } else {
622 Type::single(narrowed_ty)
625 }
626 }
627
628 pub fn narrow_to_string(&self) -> Type {
632 self.filter_replacing(
633 |t| t.is_string() || matches!(t, Atomic::TTemplateParam { .. }),
634 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
635 Atomic::TString,
636 )
637 }
638
639 pub fn narrow_to_int(&self) -> Type {
641 self.filter_replacing(
642 |t| t.is_int() || matches!(t, Atomic::TTemplateParam { .. }),
643 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
644 Atomic::TInt,
645 )
646 }
647
648 pub fn narrow_to_float(&self) -> Type {
650 self.filter_replacing(
651 |t| {
652 matches!(
653 t,
654 Atomic::TFloat
655 | Atomic::TIntegralFloat
656 | Atomic::TLiteralFloat(..)
657 | Atomic::TTemplateParam { .. }
658 )
659 },
660 |t| matches!(t, Atomic::TMixed | Atomic::TScalar | Atomic::TNumeric),
661 Atomic::TFloat,
662 )
663 }
664
665 pub fn narrow_to_bool(&self) -> Type {
667 self.filter_replacing(
668 |t| {
669 matches!(
670 t,
671 Atomic::TBool | Atomic::TTrue | Atomic::TFalse | Atomic::TTemplateParam { .. }
672 )
673 },
674 |t| matches!(t, Atomic::TMixed | Atomic::TScalar),
675 Atomic::TBool,
676 )
677 }
678
679 pub fn narrow_to_null(&self) -> Type {
681 self.filter_replacing(
682 |t| matches!(t, Atomic::TNull | Atomic::TTemplateParam { .. }),
683 |t| matches!(t, Atomic::TMixed),
684 Atomic::TNull,
685 )
686 }
687
688 pub fn narrow_to_array(&self) -> Type {
690 self.filter_replacing(
691 |t| t.is_array() || matches!(t, Atomic::TTemplateParam { .. }),
692 |t| matches!(t, Atomic::TMixed),
693 Atomic::TArray {
694 key: Box::new(Type::mixed()),
695 value: Box::new(Type::mixed()),
696 },
697 )
698 }
699
700 pub fn narrow_to_non_empty_collection(&self) -> Type {
703 let mut out = Type::empty();
704 out.from_docblock = self.from_docblock;
705 for t in &self.types {
706 match t {
707 Atomic::TArray { key, value } => out.add_type(Atomic::TNonEmptyArray {
708 key: key.clone(),
709 value: value.clone(),
710 }),
711 Atomic::TList { value } => out.add_type(Atomic::TNonEmptyList {
712 value: value.clone(),
713 }),
714 Atomic::TKeyedArray {
715 properties,
716 is_open: false,
717 ..
718 } if properties.is_empty() => {}
719 _ => out.add_type(t.clone()),
720 }
721 }
722 out
723 }
724
725 pub fn narrow_to_empty_collection(&self) -> Type {
735 let mut out = Type::empty();
736 out.from_docblock = self.from_docblock;
737 for t in &self.types {
738 match t {
739 Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => {}
740 Atomic::TArray { .. } | Atomic::TList { .. } => {
741 out.add_type(Atomic::TKeyedArray {
742 properties: Box::default(),
743 is_open: false,
744 is_list: true,
745 });
746 }
747 _ => out.add_type(t.clone()),
748 }
749 }
750 out
751 }
752
753 pub fn narrow_to_list(&self) -> Type {
765 let mut out = Type::empty();
766 out.from_docblock = self.from_docblock;
767 for t in &self.types {
768 match t {
769 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => out.add_type(t.clone()),
770 Atomic::TArray { key, value } if !key.narrow_to_int().is_empty() => {
776 out.add_type(Atomic::TList {
777 value: value.clone(),
778 });
779 }
780 Atomic::TNonEmptyArray { key, value } if !key.narrow_to_int().is_empty() => {
781 out.add_type(Atomic::TNonEmptyList {
782 value: value.clone(),
783 });
784 }
785 Atomic::TKeyedArray { is_list: true, .. } => out.add_type(t.clone()),
786 Atomic::TMixed => out.add_type(Atomic::TList {
787 value: Box::new(Type::mixed()),
788 }),
789 _ => {}
790 }
791 }
792 if out.is_empty() {
793 self.filter(|t| matches!(t, Atomic::TList { .. } | Atomic::TNonEmptyList { .. }))
794 } else {
795 out
796 }
797 }
798
799 pub fn narrow_to_object(&self) -> Type {
804 let mut out = Type::empty();
805 for t in &self.types {
806 if matches!(t, Atomic::TMixed) {
807 out.add_type(Atomic::TObject);
808 } else if t.is_object() || matches!(t, Atomic::TTemplateParam { .. }) {
809 out.add_type(t.clone());
810 }
811 }
812 if out.types.is_empty() {
813 self.filter(|t| t.is_object())
814 } else {
815 out
816 }
817 }
818
819 pub fn narrow_to_callable(&self) -> Type {
826 let narrowed = self.filter(|t| {
827 t.is_callable()
828 || t.is_string()
829 || t.is_array()
830 || t.is_object()
831 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
832 });
833 let mut result = Type::empty();
840 result.possibly_undefined = narrowed.possibly_undefined;
841 result.from_docblock = narrowed.from_docblock;
842 for atomic in narrowed.types {
843 if matches!(atomic, Atomic::TObject) {
844 result.add_type(Atomic::TCallable {
845 params: None,
846 return_type: None,
847 });
848 } else {
849 result.add_type(atomic);
850 }
851 }
852 result
853 }
854
855 pub fn narrow_to_scalar(&self) -> Type {
857 self.filter_replacing(
858 |t| {
859 t.is_string()
860 || t.is_int()
861 || matches!(
862 t,
863 Atomic::TFloat
864 | Atomic::TIntegralFloat
865 | Atomic::TLiteralFloat(..)
866 | Atomic::TBool
867 | Atomic::TTrue
868 | Atomic::TFalse
869 | Atomic::TScalar
870 | Atomic::TNumeric
871 | Atomic::TNumericString
872 | Atomic::TTemplateParam { .. }
873 )
874 },
875 |t| matches!(t, Atomic::TMixed),
876 Atomic::TScalar,
877 )
878 }
879
880 pub fn narrow_to_iterable(&self) -> Type {
883 self.filter(|t| {
884 t.is_array()
885 || t.is_object()
886 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
887 })
888 }
889
890 pub fn narrow_to_countable(&self) -> Type {
893 self.filter(|t| {
894 t.is_array()
895 || t.is_object()
896 || matches!(t, Atomic::TMixed | Atomic::TTemplateParam { .. })
897 })
898 }
899
900 pub fn narrow_to_resource(&self) -> Type {
904 self.filter(|t| matches!(t, Atomic::TMixed))
906 }
907
908 pub fn narrow_to_class_string(&self) -> Type {
913 let mut out = Type::empty();
914 out.from_docblock = self.from_docblock;
915 for t in &self.types {
916 match t {
917 Atomic::TClassString(_) => out.add_type(t.clone()),
918 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
919 out.add_type(Atomic::TClassString(None));
920 }
921 _ => {}
922 }
923 }
924 out
925 }
926
927 pub fn narrow_to_interface_string(&self) -> Type {
932 let mut out = Type::empty();
933 out.from_docblock = self.from_docblock;
934 for t in &self.types {
935 match t {
936 Atomic::TInterfaceString(_) => out.add_type(t.clone()),
937 Atomic::TClassString(name) => {
941 out.add_type(Atomic::TInterfaceString(*name));
942 }
943 _ if t.is_string() || matches!(t, Atomic::TMixed | Atomic::TScalar) => {
944 out.add_type(Atomic::TInterfaceString(None));
945 }
946 _ => {}
947 }
948 }
949 out
950 }
951
952 pub fn merge(a: &Type, b: &Type) -> Type {
957 if b.types.is_empty() {
959 let mut result = a.clone();
960 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
961 return result;
962 }
963 if a.types.is_empty() {
965 let mut result = b.clone();
966 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
967 return result;
968 }
969 if a.types.len() == 1 && matches!(a.types[0], Atomic::TMixed) {
971 let mut result = a.clone();
972 result.possibly_undefined = a.possibly_undefined || b.possibly_undefined;
973 return result;
974 }
975 if b.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
977 return Type {
978 types: smallvec::smallvec![Atomic::TMixed],
979 possibly_undefined: a.possibly_undefined || b.possibly_undefined,
980 from_docblock: a.from_docblock || b.from_docblock,
981 falsy_stripped: false,
982 possibly_absent_offset: false,
983 };
984 }
985 let mut result = a.clone();
986 result.merge_with(b);
987 result
988 }
989
990 pub fn merge_with(&mut self, other: &Type) {
992 if self.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
993 self.possibly_undefined |= other.possibly_undefined;
994 return;
995 }
996 if other.types.iter().any(|t| matches!(t, Atomic::TMixed)) {
997 self.types.clear();
998 self.types.push(Atomic::TMixed);
999 self.possibly_undefined |= other.possibly_undefined;
1000 return;
1001 }
1002 for atomic in &other.types {
1003 self.add_type(atomic.clone());
1004 }
1005 self.possibly_undefined |= other.possibly_undefined;
1006 }
1007
1008 pub fn intersect_with(&self, other: &Type) -> Type {
1012 if self.is_mixed() {
1013 return other.clone();
1014 }
1015 if other.is_mixed() {
1016 return self.clone();
1017 }
1018 let mut result = Type::empty();
1026 for a in &self.types {
1027 for b in &other.types {
1028 if a == b {
1029 result.add_type(a.clone());
1030 } else if atomic_subtype(b, a) {
1031 result.add_type(b.clone());
1032 } else if atomic_subtype(a, b) {
1033 result.add_type(a.clone());
1034 }
1035 }
1036 }
1037 if result.is_empty() {
1038 Type::never()
1039 } else {
1040 result
1041 }
1042 }
1043
1044 pub fn substitute_templates(&self, bindings: &FxHashMap<Name, Type>) -> Type {
1048 if bindings.is_empty() {
1049 return self.clone();
1050 }
1051 if !self.types.iter().any(atomic_may_contain_templates) {
1054 return self.clone();
1055 }
1056 let mut result = Type::empty();
1057 result.possibly_undefined = self.possibly_undefined;
1058 result.from_docblock = self.from_docblock;
1059 for atomic in &self.types {
1060 match atomic {
1061 Atomic::TTemplateParam { name, .. } => {
1062 if let Some(resolved) = bindings.get(name) {
1063 for t in &resolved.types {
1064 result.add_type(t.clone());
1065 }
1066 } else {
1067 result.add_type(atomic.clone());
1068 }
1069 }
1070 Atomic::TArray { key, value } => {
1071 result.add_type(Atomic::TArray {
1072 key: Box::new(key.substitute_templates(bindings)),
1073 value: Box::new(value.substitute_templates(bindings)),
1074 });
1075 }
1076 Atomic::TList { value } => {
1077 result.add_type(Atomic::TList {
1078 value: Box::new(value.substitute_templates(bindings)),
1079 });
1080 }
1081 Atomic::TNonEmptyArray { key, value } => {
1082 result.add_type(Atomic::TNonEmptyArray {
1083 key: Box::new(key.substitute_templates(bindings)),
1084 value: Box::new(value.substitute_templates(bindings)),
1085 });
1086 }
1087 Atomic::TNonEmptyList { value } => {
1088 result.add_type(Atomic::TNonEmptyList {
1089 value: Box::new(value.substitute_templates(bindings)),
1090 });
1091 }
1092 Atomic::TKeyedArray {
1093 properties,
1094 is_open,
1095 is_list,
1096 } => {
1097 use crate::atomic::KeyedProperty;
1098 let new_props = properties
1099 .iter()
1100 .map(|(k, prop)| {
1101 (
1102 k.clone(),
1103 KeyedProperty {
1104 ty: prop.ty.substitute_templates(bindings),
1105 optional: prop.optional,
1106 },
1107 )
1108 })
1109 .collect();
1110 result.add_type(Atomic::TKeyedArray {
1111 properties: Box::new(new_props),
1112 is_open: *is_open,
1113 is_list: *is_list,
1114 });
1115 }
1116 Atomic::TCallable {
1117 params,
1118 return_type,
1119 } => {
1120 result.add_type(Atomic::TCallable {
1121 params: params.as_ref().map(|ps| {
1122 ps.iter()
1123 .map(|p| substitute_in_fn_param(p, bindings))
1124 .collect()
1125 }),
1126 return_type: return_type
1127 .as_ref()
1128 .map(|r| Box::new(r.substitute_templates(bindings))),
1129 });
1130 }
1131 Atomic::TClosure { data } => {
1132 result.add_type(Atomic::TClosure {
1133 data: Box::new(crate::atomic::ClosureData {
1134 params: data
1135 .params
1136 .iter()
1137 .map(|p| substitute_in_fn_param(p, bindings))
1138 .collect(),
1139 return_type: data.return_type.substitute_templates(bindings),
1140 this_type: data
1141 .this_type
1142 .as_ref()
1143 .map(|t| t.substitute_templates(bindings)),
1144 }),
1145 });
1146 }
1147 Atomic::TConditional { data } => {
1148 let param_name = &data.param_name;
1149 let new_subject = data.subject.substitute_templates(bindings);
1150 let new_if_true = data.if_true.substitute_templates(bindings);
1151 let new_if_false = data.if_false.substitute_templates(bindings);
1152
1153 let resolved = if let Some(name) = param_name {
1157 if let Some(bound) = bindings.get(name) {
1158 if new_subject.types.len() == 1 {
1159 resolve_conditional_branch(
1160 &new_subject.types[0],
1161 bound,
1162 &new_if_true,
1163 &new_if_false,
1164 )
1165 } else {
1166 None
1167 }
1168 } else {
1169 None
1170 }
1171 } else {
1172 None
1173 };
1174
1175 if let Some(branch) = resolved {
1176 for t in branch.types {
1177 result.add_type(t);
1178 }
1179 } else {
1180 result.add_type(Atomic::TConditional {
1181 data: Box::new(crate::atomic::ConditionalData {
1182 param_name: *param_name,
1183 subject: new_subject,
1184 if_true: new_if_true,
1185 if_false: new_if_false,
1186 }),
1187 });
1188 }
1189 }
1190 Atomic::TKeyOf { target } => {
1191 let new_target = target.substitute_templates(bindings);
1192 if let Some(resolved) = eval_key_of_type(&new_target) {
1193 result.merge_with(&resolved);
1194 } else {
1195 result.add_type(Atomic::TKeyOf {
1196 target: Box::new(new_target),
1197 });
1198 }
1199 }
1200 Atomic::TValueOf { target } => {
1201 let new_target = target.substitute_templates(bindings);
1202 if let Some(resolved) = eval_value_of_type(&new_target) {
1203 result.merge_with(&resolved);
1204 } else {
1205 result.add_type(Atomic::TValueOf {
1206 target: Box::new(new_target),
1207 });
1208 }
1209 }
1210 Atomic::TIntersection { parts } => {
1211 result.add_type(Atomic::TIntersection {
1212 parts: vec_to_type_params(
1213 parts
1214 .iter()
1215 .map(|p| p.substitute_templates(bindings))
1216 .collect(),
1217 ),
1218 });
1219 }
1220 Atomic::TNamedObject { fqcn, type_params } => {
1221 if type_params.is_empty() && !fqcn.contains('\\') {
1223 if let Some(resolved) = bindings.get(fqcn) {
1224 for t in &resolved.types {
1225 result.add_type(t.clone());
1226 }
1227 continue;
1228 }
1229 }
1230 let new_params: Vec<Type> = type_params
1231 .iter()
1232 .map(|p| p.substitute_templates(bindings))
1233 .collect();
1234 result.add_type(Atomic::TNamedObject {
1235 fqcn: *fqcn,
1236 type_params: vec_to_type_params(new_params),
1237 });
1238 }
1239 Atomic::TClassString(Some(param_name)) => {
1241 if let Some(resolved) = bindings.get(param_name) {
1242 for r_atomic in &resolved.types {
1243 let cls_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1244 Some(*fqcn)
1245 } else {
1246 None
1247 };
1248 result.add_type(Atomic::TClassString(cls_name));
1249 }
1250 } else {
1251 result.add_type(atomic.clone());
1252 }
1253 }
1254 Atomic::TInterfaceString(Some(param_name)) => {
1256 if let Some(resolved) = bindings.get(param_name) {
1257 for r_atomic in &resolved.types {
1258 let iface_name = if let Atomic::TNamedObject { fqcn, .. } = r_atomic {
1259 Some(*fqcn)
1260 } else {
1261 None
1262 };
1263 result.add_type(Atomic::TInterfaceString(iface_name));
1264 }
1265 } else {
1266 result.add_type(atomic.clone());
1267 }
1268 }
1269 _ => {
1270 result.add_type(atomic.clone());
1271 }
1272 }
1273 }
1274 result
1275 }
1276
1277 pub fn resolve_conditional_returns<F>(self, lookup: F) -> Type
1283 where
1284 F: Fn(&str) -> Option<Type>,
1285 {
1286 self.resolve_conditional_inner(&lookup)
1287 }
1288
1289 fn resolve_conditional_inner<F>(self, lookup: &F) -> Type
1290 where
1291 F: Fn(&str) -> Option<Type>,
1292 {
1293 let mut result = Type::empty();
1294 for atomic in self.types {
1295 match atomic {
1296 Atomic::TConditional { ref data } => {
1297 let (param_name, subject, if_true, if_false) = (
1298 &data.param_name,
1299 &data.subject,
1300 &data.if_true,
1301 &data.if_false,
1302 );
1303 let resolved = if subject.types.len() == 1 {
1304 if let Some(name) = param_name {
1305 if let Some(arg_ty) = lookup(name.as_ref()) {
1306 resolve_conditional_branch(
1307 &subject.types[0],
1308 &arg_ty,
1309 if_true,
1310 if_false,
1311 )
1312 } else {
1313 None
1314 }
1315 } else {
1316 None
1317 }
1318 } else {
1319 None
1320 };
1321
1322 if let Some(branch) = resolved {
1323 for t in branch.resolve_conditional_inner(lookup).types {
1325 result.add_type(t);
1326 }
1327 } else {
1328 for t in if_true.clone().resolve_conditional_inner(lookup).types {
1331 result.add_type(t);
1332 }
1333 for t in if_false.clone().resolve_conditional_inner(lookup).types {
1334 result.add_type(t);
1335 }
1336 }
1337 }
1338 other => result.add_type(other),
1339 }
1340 }
1341 result
1342 }
1343
1344 pub fn is_subtype_structural(&self, other: &Type) -> bool {
1354 if other.is_mixed() {
1355 return true;
1356 }
1357 if self.is_never() {
1358 return true; }
1360 self.types
1361 .iter()
1362 .all(|a| other.types.iter().any(|b| atomic_subtype(a, b)))
1363 }
1364
1365 pub fn accepts_atomic_structural(&self, sub: &Atomic) -> bool {
1369 if self.is_mixed() {
1370 return true;
1371 }
1372 matches!(sub, Atomic::TNever) || self.types.iter().any(|b| atomic_subtype(sub, b))
1373 }
1374
1375 fn filter<F: Fn(&Atomic) -> bool>(&self, f: F) -> Type {
1378 let mut result = Type::empty();
1379 result.possibly_undefined = self.possibly_undefined;
1380 result.from_docblock = self.from_docblock;
1381 for atomic in &self.types {
1382 if f(atomic) {
1383 result.types.push(atomic.clone());
1384 }
1385 }
1386 result
1387 }
1388
1389 fn filter_replacing<K: Fn(&Atomic) -> bool, P: Fn(&Atomic) -> bool>(
1394 &self,
1395 keep: K,
1396 placeholder: P,
1397 replacement: Atomic,
1398 ) -> Type {
1399 let mut result = Type::empty();
1400 result.possibly_undefined = self.possibly_undefined;
1401 result.from_docblock = self.from_docblock;
1402 for atomic in &self.types {
1403 if keep(atomic) {
1404 result.add_type(atomic.clone());
1405 } else if placeholder(atomic) {
1406 result.add_type(replacement.clone());
1407 }
1408 }
1409 result
1410 }
1411
1412 pub fn possibly_undefined(mut self) -> Self {
1414 self.possibly_undefined = true;
1415 self
1416 }
1417
1418 pub fn from_docblock(mut self) -> Self {
1420 self.from_docblock = true;
1421 self
1422 }
1423
1424 pub fn falsy_stripped(mut self) -> Self {
1427 self.falsy_stripped = true;
1428 self
1429 }
1430
1431 pub fn possibly_absent_offset(mut self) -> Self {
1435 self.possibly_absent_offset = true;
1436 self
1437 }
1438}
1439
1440#[derive(Clone, PartialEq, Eq, Debug)]
1454enum ValueClass {
1455 Int,
1456 Float,
1457 True,
1458 False,
1459 String,
1460 Array,
1461 List,
1463 Object,
1464 Null,
1465 Top,
1468 LitInt(i64),
1471 LitFloat(i64, i64),
1473 LitString(Arc<str>),
1475}
1476
1477impl ValueClass {
1478 fn includes(&self, b: &Self) -> bool {
1483 self == b
1484 || matches!(
1485 (self, b),
1486 (Self::Top, _)
1487 | (Self::Int, Self::LitInt(_))
1488 | (Self::Float, Self::LitFloat(..))
1489 | (Self::String, Self::LitString(_))
1490 | (Self::Array, Self::List)
1491 )
1492 }
1493}
1494
1495fn value_classes(a: &Atomic) -> Vec<ValueClass> {
1501 use ValueClass::*;
1502 match a {
1503 Atomic::TInt
1506 | Atomic::TIntRange { .. }
1507 | Atomic::TPositiveInt
1508 | Atomic::TNegativeInt
1509 | Atomic::TNonNegativeInt => vec![Int],
1510 Atomic::TLiteralInt(v) => vec![LitInt(*v)],
1511 Atomic::TFloat | Atomic::TIntegralFloat => vec![Float],
1513 Atomic::TLiteralFloat(int_bits, frac_bits) => {
1514 vec![LitFloat(*int_bits, *frac_bits)]
1515 }
1516 Atomic::TBool => vec![True, False],
1518 Atomic::TTrue => vec![True],
1519 Atomic::TFalse => vec![False],
1520 Atomic::TString
1522 | Atomic::TNonEmptyString
1523 | Atomic::TNumericString
1524 | Atomic::TClassString(_)
1525 | Atomic::TInterfaceString(_)
1526 | Atomic::TEnumString
1527 | Atomic::TTraitString
1528 | Atomic::TCallableString => vec![String],
1529 Atomic::TLiteralString(s) => vec![LitString(s.clone())],
1530 Atomic::TArray { .. }
1532 | Atomic::TNonEmptyArray { .. }
1533 | Atomic::TKeyedArray { is_list: false, .. } => vec![Array],
1534 Atomic::TList { .. }
1535 | Atomic::TNonEmptyList { .. }
1536 | Atomic::TKeyedArray { is_list: true, .. } => vec![List],
1537 Atomic::TObject
1539 | Atomic::TNamedObject { .. }
1540 | Atomic::TStaticObject { .. }
1541 | Atomic::TSelf { .. }
1542 | Atomic::TParent { .. }
1543 | Atomic::TClosure { .. }
1544 | Atomic::TLiteralEnumCase { .. } => vec![Object],
1545 Atomic::TNull => vec![Null],
1548 Atomic::TVoid => vec![Top],
1549 Atomic::TMixed => vec![Top],
1551 Atomic::TScalar => vec![Int, Float, True, False, String],
1552 Atomic::TNumeric => vec![Int, Float],
1555 Atomic::TCallable { .. }
1557 | Atomic::TNever
1558 | Atomic::TTemplateParam { .. }
1559 | Atomic::TKeyOf { .. }
1560 | Atomic::TValueOf { .. }
1561 | Atomic::TConditional { .. }
1562 | Atomic::TIntersection { .. } => Vec::new(),
1563 }
1564}
1565
1566fn subject_is_decidable(subject: &Atomic) -> bool {
1574 matches!(
1575 subject,
1576 Atomic::TNull
1577 | Atomic::TTrue
1578 | Atomic::TFalse
1579 | Atomic::TBool
1580 | Atomic::TString
1581 | Atomic::TInt
1582 | Atomic::TFloat
1583 | Atomic::TArray { .. }
1584 | Atomic::TList { .. }
1585 | Atomic::TObject
1586 | Atomic::TMixed
1587 | Atomic::TScalar
1588 )
1589}
1590
1591fn resolve_conditional_branch(
1613 subject: &Atomic,
1614 arg_ty: &Type,
1615 if_true: &Type,
1616 if_false: &Type,
1617) -> Option<Type> {
1618 if !subject_is_decidable(subject) {
1619 return None;
1620 }
1621 if arg_ty.types.is_empty() {
1622 return None;
1623 }
1624 let subject_classes = value_classes(subject);
1625 let arg_classes: Vec<ValueClass> = arg_ty.types.iter().flat_map(value_classes).collect();
1626 if arg_classes.is_empty() {
1627 return None;
1629 }
1630 let all_match = arg_classes
1631 .iter()
1632 .all(|c| subject_classes.iter().any(|s| s.includes(c)));
1633 let any_overlap = arg_classes.iter().any(|c| {
1634 subject_classes
1635 .iter()
1636 .any(|s| s.includes(c) || c.includes(s))
1637 });
1638 if all_match {
1639 Some(if_true.clone())
1640 } else if !any_overlap {
1641 Some(if_false.clone())
1642 } else {
1643 None
1644 }
1645}
1646
1647fn atomic_may_contain_templates(atomic: &Atomic) -> bool {
1655 match atomic {
1656 Atomic::TNamedObject { fqcn, type_params } => {
1660 !type_params.is_empty() || !fqcn.contains('\\')
1661 }
1662 Atomic::TTemplateParam { .. }
1663 | Atomic::TKeyOf { .. }
1664 | Atomic::TValueOf { .. }
1665 | Atomic::TArray { .. }
1666 | Atomic::TList { .. }
1667 | Atomic::TNonEmptyArray { .. }
1668 | Atomic::TNonEmptyList { .. }
1669 | Atomic::TKeyedArray { .. }
1670 | Atomic::TCallable { .. }
1671 | Atomic::TClosure { .. }
1672 | Atomic::TConditional { .. }
1673 | Atomic::TIntersection { .. }
1674 | Atomic::TClassString(Some(_))
1675 | Atomic::TInterfaceString(Some(_)) => true,
1676 _ => false,
1677 }
1678}
1679
1680fn eval_key_of_type(t: &Type) -> Option<Type> {
1681 let mut result = Type::empty();
1682 for atomic in &t.types {
1683 match atomic {
1684 Atomic::TArray { key, .. } | Atomic::TNonEmptyArray { key, .. } => {
1685 for k in &key.types {
1686 result.add_type(k.clone());
1687 }
1688 }
1689 Atomic::TList { .. } | Atomic::TNonEmptyList { .. } => {
1690 result.add_type(Atomic::TInt);
1691 }
1692 Atomic::TKeyedArray { properties, .. } => {
1693 for key in properties.keys() {
1694 match key {
1695 crate::atomic::ArrayKey::Int(n) => result.add_type(Atomic::TLiteralInt(*n)),
1696 crate::atomic::ArrayKey::String(s) => {
1697 result.add_type(Atomic::TLiteralString(s.clone()))
1698 }
1699 }
1700 }
1701 }
1702 _ => return None,
1703 }
1704 }
1705 (!result.types.is_empty()).then_some(result)
1706}
1707
1708fn eval_value_of_type(t: &Type) -> Option<Type> {
1709 let mut result = Type::empty();
1710 for atomic in &t.types {
1711 match atomic {
1712 Atomic::TArray { value, .. }
1713 | Atomic::TNonEmptyArray { value, .. }
1714 | Atomic::TList { value }
1715 | Atomic::TNonEmptyList { value } => {
1716 result.merge_with(value);
1717 }
1718 Atomic::TKeyedArray { properties, .. } => {
1719 for prop in properties.values() {
1720 result.merge_with(&prop.ty);
1721 }
1722 }
1723 _ => return None,
1724 }
1725 }
1726 (!result.types.is_empty()).then_some(result)
1727}
1728
1729fn substitute_in_fn_param(
1730 p: &crate::atomic::FnParam,
1731 bindings: &FxHashMap<Name, Type>,
1732) -> crate::atomic::FnParam {
1733 crate::atomic::FnParam {
1734 name: p.name,
1735 ty: p.ty.as_ref().map(|t| {
1736 let u = t.to_union();
1737 let substituted = u.substitute_templates(bindings);
1738 crate::compact::SimpleType::from_union(substituted)
1739 }),
1740 out_ty: p.out_ty.as_ref().map(|t| {
1741 let u = t.to_union();
1742 let substituted = u.substitute_templates(bindings);
1743 crate::compact::SimpleType::from_union(substituted)
1744 }),
1745 default: p.default.as_ref().map(|d| {
1746 let u = d.to_union();
1747 let substituted = u.substitute_templates(bindings);
1748 crate::compact::SimpleType::from_union(substituted)
1749 }),
1750 is_variadic: p.is_variadic,
1751 is_byref: p.is_byref,
1752 is_optional: p.is_optional,
1753 }
1754}
1755
1756pub fn atomic_subtype(sub: &Atomic, sup: &Atomic) -> bool {
1762 if sub == sup {
1763 return true;
1764 }
1765 match (sub, sup) {
1766 (Atomic::TNever, _) => true,
1768 (_, Atomic::TMixed) => true,
1770 (Atomic::TMixed, _) => true,
1771 (_, Atomic::TTemplateParam { as_type, .. }) => {
1776 as_type.is_mixed() || as_type.types.iter().any(|b| atomic_subtype(sub, b))
1777 }
1778
1779 (Atomic::TLiteralInt(_), Atomic::TInt) => true,
1781 (Atomic::TLiteralInt(_), Atomic::TNumeric) => true,
1782 (Atomic::TLiteralInt(_), Atomic::TScalar) => true,
1783 (Atomic::TLiteralInt(n), Atomic::TPositiveInt) => *n > 0,
1784 (Atomic::TLiteralInt(n), Atomic::TNonNegativeInt) => *n >= 0,
1785 (Atomic::TLiteralInt(n), Atomic::TNegativeInt) => *n < 0,
1786 (Atomic::TPositiveInt, Atomic::TInt) => true,
1787 (Atomic::TPositiveInt, Atomic::TNonNegativeInt) => true,
1788 (Atomic::TPositiveInt, Atomic::TNumeric) => true,
1789 (Atomic::TPositiveInt, Atomic::TScalar) => true,
1790 (Atomic::TNegativeInt, Atomic::TInt) => true,
1791 (Atomic::TNegativeInt, Atomic::TNumeric) => true,
1792 (Atomic::TNegativeInt, Atomic::TScalar) => true,
1793 (Atomic::TNonNegativeInt, Atomic::TInt) => true,
1794 (Atomic::TNonNegativeInt, Atomic::TNumeric) => true,
1795 (Atomic::TNonNegativeInt, Atomic::TScalar) => true,
1796 (Atomic::TIntRange { .. }, Atomic::TInt) => true,
1797 (Atomic::TIntRange { .. }, Atomic::TNumeric) => true,
1798 (Atomic::TIntRange { .. }, Atomic::TScalar) => true,
1799 (Atomic::TPositiveInt, Atomic::TIntRange { min, max }) => {
1801 max.is_none() && min.is_none_or(|m| m <= 1)
1802 }
1803 (Atomic::TNegativeInt, Atomic::TIntRange { min, max }) => {
1805 min.is_none() && max.is_none_or(|m| m >= -1)
1806 }
1807 (Atomic::TNonNegativeInt, Atomic::TIntRange { min, max }) => {
1809 max.is_none() && min.is_none_or(|m| m <= 0)
1810 }
1811 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TPositiveInt) => {
1813 sub_min.is_some_and(|lo| lo >= 1)
1814 }
1815 (Atomic::TIntRange { min: sub_min, .. }, Atomic::TNonNegativeInt) => {
1816 sub_min.is_some_and(|lo| lo >= 0)
1817 }
1818 (Atomic::TIntRange { max: sub_max, .. }, Atomic::TNegativeInt) => {
1819 sub_max.is_some_and(|hi| hi <= -1)
1820 }
1821 (
1823 Atomic::TIntRange {
1824 min: sub_min,
1825 max: sub_max,
1826 },
1827 Atomic::TIntRange {
1828 min: sup_min,
1829 max: sup_max,
1830 },
1831 ) => {
1832 let lower_ok = match (sub_min, sup_min) {
1833 (_, None) => true,
1834 (None, Some(_)) => false,
1835 (Some(sl), Some(su)) => sl >= su,
1836 };
1837 let upper_ok = match (sub_max, sup_max) {
1838 (None, None) | (Some(_), None) => true,
1839 (None, Some(_)) => false,
1840 (Some(sl), Some(su)) => sl <= su,
1841 };
1842 lower_ok && upper_ok
1843 }
1844
1845 (Atomic::TLiteralFloat(..), Atomic::TFloat) => true,
1846 (Atomic::TLiteralFloat(..), Atomic::TNumeric) => true,
1847 (Atomic::TLiteralFloat(..), Atomic::TScalar) => true,
1848
1849 (Atomic::TLiteralString(s), Atomic::TString) => {
1850 let _ = s;
1851 true
1852 }
1853 (Atomic::TLiteralString(s), Atomic::TCallableString) => {
1854 let _ = s;
1855 true
1856 }
1857 (Atomic::TLiteralString(s), Atomic::TNonEmptyString) => !s.is_empty(),
1858 (Atomic::TLiteralString(s), Atomic::TNumericString) => s.parse::<f64>().is_ok(),
1859 (Atomic::TLiteralString(_), Atomic::TClassString(_)) => true,
1862 (Atomic::TLiteralString(_), Atomic::TInterfaceString(_)) => true,
1865 (Atomic::TLiteralString(_), Atomic::TScalar) => true,
1866 (Atomic::TNonEmptyString, Atomic::TString) => true,
1867 (Atomic::TCallableString, Atomic::TString) => true,
1868 (Atomic::TNumericString, Atomic::TNonEmptyString) => true,
1870 (Atomic::TNumericString, Atomic::TString) => true,
1871 (Atomic::TClassString(_), Atomic::TNonEmptyString) => true,
1875 (Atomic::TInterfaceString(_), Atomic::TNonEmptyString) => true,
1876 (Atomic::TCallableString, Atomic::TNonEmptyString) => true,
1877 (Atomic::TEnumString, Atomic::TNonEmptyString) => true,
1878 (Atomic::TTraitString, Atomic::TNonEmptyString) => true,
1879 (Atomic::TClassString(_), Atomic::TString) => true,
1880 (Atomic::TInterfaceString(_), Atomic::TString) => true,
1881 (Atomic::TInterfaceString(_), Atomic::TClassString(None)) => true,
1886 (Atomic::TClassString(Some(_)), Atomic::TClassString(None)) => true,
1887 (Atomic::TInterfaceString(Some(a)), Atomic::TClassString(Some(b))) => a == b,
1888 (Atomic::TEnumString, Atomic::TString) => true,
1889 (Atomic::TTraitString, Atomic::TString) => true,
1890
1891 (Atomic::TTrue, Atomic::TBool) => true,
1892 (Atomic::TFalse, Atomic::TBool) => true,
1893
1894 (Atomic::TInt, Atomic::TNumeric) => true,
1895 (Atomic::TFloat, Atomic::TNumeric) => true,
1896 (Atomic::TIntegralFloat, Atomic::TNumeric) => true,
1897 (Atomic::TNumericString, Atomic::TNumeric) => true,
1898
1899 (Atomic::TInt, Atomic::TScalar) => true,
1900 (Atomic::TFloat, Atomic::TScalar) => true,
1901 (Atomic::TIntegralFloat, Atomic::TScalar) => true,
1902 (Atomic::TString, Atomic::TScalar) => true,
1903 (Atomic::TBool, Atomic::TScalar) => true,
1904 (Atomic::TNumeric, Atomic::TScalar) => true,
1905 (Atomic::TTrue, Atomic::TScalar) => true,
1906 (Atomic::TFalse, Atomic::TScalar) => true,
1907 (Atomic::TNonEmptyString, Atomic::TScalar) => true,
1911 (Atomic::TNumericString, Atomic::TScalar) => true,
1912 (Atomic::TCallableString, Atomic::TScalar) => true,
1913 (Atomic::TClassString(_), Atomic::TScalar) => true,
1914 (Atomic::TInterfaceString(_), Atomic::TScalar) => true,
1915 (Atomic::TEnumString, Atomic::TScalar) => true,
1916 (Atomic::TTraitString, Atomic::TScalar) => true,
1917
1918 (Atomic::TNamedObject { .. }, Atomic::TObject) => true,
1920 (Atomic::TStaticObject { .. }, Atomic::TObject) => true,
1921 (Atomic::TSelf { .. }, Atomic::TObject) => true,
1922 (Atomic::TLiteralEnumCase { .. }, Atomic::TObject) => true,
1924 (Atomic::TSelf { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1926 (Atomic::TStaticObject { fqcn: a }, Atomic::TNamedObject { fqcn: b, .. }) => a == b,
1927 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TSelf { fqcn: b }) => a == b,
1929 (Atomic::TNamedObject { fqcn: a, .. }, Atomic::TStaticObject { fqcn: b }) => a == b,
1930 (Atomic::TLiteralEnumCase { enum_fqcn, .. }, Atomic::TNamedObject { fqcn, .. }) => {
1933 enum_fqcn == fqcn
1934 }
1935 (
1939 Atomic::TNamedObject {
1940 fqcn: sub_fqcn,
1941 type_params: sub_params,
1942 },
1943 Atomic::TNamedObject {
1944 fqcn: sup_fqcn,
1945 type_params: sup_params,
1946 },
1947 ) => {
1948 sub_fqcn == sup_fqcn
1949 && (sup_params.is_empty() || type_params_compatible(sub_params, sup_params))
1950 }
1951
1952 (Atomic::TIntegralFloat, Atomic::TFloat) => true,
1954
1955 (Atomic::TLiteralInt(_), Atomic::TFloat) => true,
1957 (Atomic::TPositiveInt, Atomic::TFloat) => true,
1958 (Atomic::TNegativeInt, Atomic::TFloat) => true,
1959 (Atomic::TNonNegativeInt, Atomic::TFloat) => true,
1960 (Atomic::TInt, Atomic::TFloat) => true,
1961 (Atomic::TIntRange { .. }, Atomic::TFloat) => true,
1962
1963 (Atomic::TLiteralInt(n), Atomic::TIntRange { min, max }) => {
1965 min.is_none_or(|lo| *n >= lo) && max.is_none_or(|hi| *n <= hi)
1966 }
1967
1968 (Atomic::TString, Atomic::TCallable { .. }) => true,
1970 (Atomic::TNonEmptyString, Atomic::TCallable { .. }) => true,
1971 (Atomic::TLiteralString(_), Atomic::TCallable { .. }) => true,
1972 (Atomic::TArray { .. }, Atomic::TCallable { .. }) => true,
1973 (Atomic::TNonEmptyArray { .. }, Atomic::TCallable { .. }) => true,
1974 (Atomic::TKeyedArray { .. }, Atomic::TCallable { .. }) => true,
1975
1976 (Atomic::TClosure { .. }, Atomic::TCallable { .. }) => true,
1978 (Atomic::TCallable { .. }, Atomic::TClosure { .. }) => true,
1980 (Atomic::TClosure { data: sub }, Atomic::TClosure { data: sup }) => {
1990 fn has_nominal_type(t: &Type) -> bool {
1991 t.types.iter().any(|a| {
1992 matches!(
1993 a,
1994 Atomic::TNamedObject { .. }
1995 | Atomic::TSelf { .. }
1996 | Atomic::TStaticObject { .. }
1997 | Atomic::TTemplateParam { .. }
1998 | Atomic::TClosure { .. }
1999 | Atomic::TCallable { .. }
2000 )
2001 })
2002 }
2003 let sub_required = sub
2004 .params
2005 .iter()
2006 .filter(|p| !p.is_optional && !p.is_variadic)
2007 .count();
2008 if sub_required > sup.params.len() {
2009 false
2010 } else {
2011 let params_ok = sup.params.iter().enumerate().all(|(i, sup_param)| {
2012 let Some(sub_param) = sub.params.get(i) else {
2013 return true;
2014 };
2015 if sub_param.is_optional || sub_param.is_variadic {
2016 return true;
2017 }
2018 let (Some(sub_ty), Some(sup_ty)) =
2019 (sub_param.ty.as_ref(), sup_param.ty.as_ref())
2020 else {
2021 return true;
2022 };
2023 let (sub_u, sup_u) = (sub_ty.to_union(), sup_ty.to_union());
2024 if has_nominal_type(&sub_u) || has_nominal_type(&sup_u) {
2025 return true;
2026 }
2027 sup_u.is_subtype_structural(&sub_u)
2030 });
2031 params_ok
2032 && (sub.return_type.is_mixed()
2033 || sup.return_type.is_mixed()
2034 || sup.return_type.is_void()
2038 || has_nominal_type(&sub.return_type)
2039 || has_nominal_type(&sup.return_type)
2040 || sub.return_type.is_subtype_structural(&sup.return_type))
2041 }
2042 }
2043 (Atomic::TCallable { .. }, Atomic::TCallable { .. }) => true,
2045 (Atomic::TClosure { .. }, Atomic::TNamedObject { fqcn, .. }) => {
2047 fqcn.as_ref().eq_ignore_ascii_case("closure")
2048 }
2049 (Atomic::TClosure { .. }, Atomic::TObject) => true,
2050 (Atomic::TNamedObject { fqcn, .. }, Atomic::TClosure { .. }) => {
2052 fqcn.as_ref().eq_ignore_ascii_case("closure")
2053 }
2054 (Atomic::TNamedObject { fqcn, .. }, Atomic::TCallable { .. }) => {
2056 fqcn.as_ref().eq_ignore_ascii_case("closure")
2057 }
2058
2059 (
2067 Atomic::TIntersection { parts: sub_parts },
2068 Atomic::TIntersection { parts: sup_parts },
2069 ) => sup_parts.iter().all(|sup_part| {
2070 sub_parts
2071 .iter()
2072 .any(|sub_part| sub_part.is_subtype_structural(sup_part))
2073 }),
2074
2075 (Atomic::TList { value }, Atomic::TArray { key, value: av }) => {
2077 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2078 }
2079 (Atomic::TNonEmptyList { value }, Atomic::TArray { key, value: av }) => {
2080 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2081 }
2082 (Atomic::TNonEmptyList { value }, Atomic::TNonEmptyArray { key, value: av }) => {
2083 Type::single(Atomic::TInt).is_subtype_structural(key) && value.is_subtype_structural(av)
2084 }
2085 (Atomic::TNonEmptyList { value }, Atomic::TList { value: lv }) => {
2086 value.is_subtype_structural(lv)
2087 }
2088 (Atomic::TArray { key, value: av }, Atomic::TList { value: lv }) => {
2090 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2091 && av.is_subtype_structural(lv)
2092 }
2093 (Atomic::TArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2094 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2095 && av.is_subtype_structural(lv)
2096 }
2097 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TList { value: lv }) => {
2098 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2099 && av.is_subtype_structural(lv)
2100 }
2101 (Atomic::TNonEmptyArray { key, value: av }, Atomic::TNonEmptyList { value: lv }) => {
2102 matches!(key.types.as_slice(), [Atomic::TInt | Atomic::TMixed])
2103 && av.is_subtype_structural(lv)
2104 }
2105 (Atomic::TList { value: v1 }, Atomic::TList { value: v2 }) => v1.is_subtype_structural(v2),
2107 (Atomic::TNonEmptyArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2108 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2109 }
2110
2111 (Atomic::TArray { key: k1, value: v1 }, Atomic::TArray { key: k2, value: v2 }) => {
2113 k1.is_subtype_structural(k2) && v1.is_subtype_structural(v2)
2114 }
2115
2116 (Atomic::TKeyedArray { properties, .. }, Atomic::TArray { key, value }) => {
2125 properties.iter().all(|(prop_key, prop)| {
2126 let key_atomic = match prop_key {
2127 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2128 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2129 };
2130 if !Type::single(key_atomic).is_subtype_structural(key) {
2131 return false; }
2133 let has_named_obj = prop.ty.types.iter().any(|a| {
2135 matches!(
2136 a,
2137 Atomic::TNamedObject { .. }
2138 | Atomic::TSelf { .. }
2139 | Atomic::TStaticObject { .. }
2140 | Atomic::TClosure { .. }
2141 | Atomic::TTemplateParam { .. }
2142 )
2143 });
2144 has_named_obj || prop.ty.is_subtype_structural(value)
2145 })
2146 }
2147 (
2148 Atomic::TKeyedArray {
2149 properties,
2150 is_open,
2151 ..
2152 },
2153 Atomic::TNonEmptyArray { key, value },
2154 ) => {
2155 (*is_open || properties.iter().any(|(_, p)| !p.optional))
2156 && properties.iter().all(|(prop_key, prop)| {
2157 let key_atomic = match prop_key {
2158 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2159 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2160 };
2161 if !Type::single(key_atomic).is_subtype_structural(key) {
2162 return false;
2163 }
2164 let has_named_obj = prop.ty.types.iter().any(|a| {
2165 matches!(
2166 a,
2167 Atomic::TNamedObject { .. }
2168 | Atomic::TSelf { .. }
2169 | Atomic::TStaticObject { .. }
2170 | Atomic::TClosure { .. }
2171 | Atomic::TTemplateParam { .. }
2172 )
2173 });
2174 has_named_obj || prop.ty.is_subtype_structural(value)
2175 })
2176 }
2177
2178 (
2180 Atomic::TKeyedArray {
2181 properties,
2182 is_list,
2183 ..
2184 },
2185 Atomic::TList { value: lv },
2186 ) => *is_list && properties.values().all(|p| p.ty.is_subtype_structural(lv)),
2187 (
2188 Atomic::TKeyedArray {
2189 properties,
2190 is_list,
2191 ..
2192 },
2193 Atomic::TNonEmptyList { value: lv },
2194 ) => {
2195 *is_list
2196 && !properties.is_empty()
2197 && properties.values().all(|p| p.ty.is_subtype_structural(lv))
2198 }
2199
2200 (
2205 Atomic::TKeyedArray {
2206 properties: sub_props,
2207 is_open: sub_open,
2208 ..
2209 },
2210 Atomic::TKeyedArray {
2211 properties: sup_props,
2212 is_open: sup_open,
2213 ..
2214 },
2215 ) => {
2216 let keys_satisfied = sup_props
2217 .iter()
2218 .all(|(key, sup_prop)| match sub_props.get(key) {
2219 Some(sub_prop) => {
2220 if !sup_prop.optional && sub_prop.optional {
2224 return false;
2225 }
2226 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2227 matches!(
2228 a,
2229 Atomic::TNamedObject { .. }
2230 | Atomic::TSelf { .. }
2231 | Atomic::TStaticObject { .. }
2232 | Atomic::TClosure { .. }
2233 | Atomic::TTemplateParam { .. }
2234 )
2235 });
2236 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2237 }
2238 None => sup_prop.optional || *sub_open,
2239 });
2240 let no_undeclared_extras =
2241 *sup_open || sub_props.keys().all(|k| sup_props.contains_key(k));
2242 keys_satisfied && no_undeclared_extras
2243 }
2244 (
2249 Atomic::TKeyedArray {
2250 properties,
2251 is_open,
2252 ..
2253 },
2254 Atomic::TIntersection { parts },
2255 ) => parts.iter().all(|part| {
2256 part.types.iter().any(|part_atomic| match part_atomic {
2257 Atomic::TKeyedArray {
2258 properties: sup_props,
2259 is_open: sup_open,
2260 ..
2261 } => {
2262 let keys_satisfied =
2263 sup_props
2264 .iter()
2265 .all(|(key, sup_prop)| match properties.get(key) {
2266 Some(sub_prop) => {
2267 if !sup_prop.optional && sub_prop.optional {
2268 return false;
2269 }
2270 let has_named_obj = sup_prop.ty.types.iter().any(|a| {
2271 matches!(
2272 a,
2273 Atomic::TNamedObject { .. }
2274 | Atomic::TSelf { .. }
2275 | Atomic::TStaticObject { .. }
2276 | Atomic::TClosure { .. }
2277 | Atomic::TTemplateParam { .. }
2278 )
2279 });
2280 has_named_obj || sub_prop.ty.is_subtype_structural(&sup_prop.ty)
2281 }
2282 None => *is_open || sup_prop.optional,
2283 });
2284 let has_array_part = parts.iter().any(|part| {
2285 part.types
2286 .iter()
2287 .any(|t| matches!(t, Atomic::TArray { .. }))
2288 });
2289 let has_keyed_array_part = parts.iter().any(|part| {
2290 part.types
2291 .iter()
2292 .any(|t| matches!(t, Atomic::TKeyedArray { .. }))
2293 });
2294 let keys_allowed_by_some_part = if has_array_part {
2295 true
2296 } else if has_keyed_array_part {
2297 properties.keys().all(|k| {
2298 parts.iter().any(|part| {
2299 part.types.iter().any(|t| {
2300 if let Atomic::TKeyedArray {
2301 properties: part_props,
2302 ..
2303 } = t
2304 {
2305 part_props.contains_key(k)
2306 } else {
2307 false
2308 }
2309 })
2310 })
2311 })
2312 } else {
2313 true
2314 };
2315 let no_undeclared_extras = *sup_open || keys_allowed_by_some_part;
2316 keys_satisfied && no_undeclared_extras
2317 }
2318 Atomic::TArray { key, value } => properties.iter().all(|(prop_key, prop)| {
2319 let key_atomic = match prop_key {
2320 crate::atomic::ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
2321 crate::atomic::ArrayKey::Int(n) => Atomic::TLiteralInt(*n),
2322 };
2323 if !Type::single(key_atomic).is_subtype_structural(key) {
2324 return false;
2325 }
2326 let has_named_obj = prop.ty.types.iter().any(|a| {
2327 matches!(
2328 a,
2329 Atomic::TNamedObject { .. }
2330 | Atomic::TSelf { .. }
2331 | Atomic::TStaticObject { .. }
2332 | Atomic::TClosure { .. }
2333 | Atomic::TTemplateParam { .. }
2334 )
2335 });
2336 has_named_obj || prop.ty.is_subtype_structural(value)
2337 }),
2338 Atomic::TNamedObject { .. }
2339 | Atomic::TSelf { .. }
2340 | Atomic::TStaticObject { .. }
2341 | Atomic::TClosure { .. }
2342 | Atomic::TTemplateParam { .. } => true,
2343 _ => false,
2344 })
2345 }),
2346
2347 _ => false,
2348 }
2349}
2350
2351fn type_params_compatible(sub: &[Type], sup: &[Type]) -> bool {
2357 if sub.len() != sup.len() {
2358 return false;
2359 }
2360 sub.iter()
2361 .zip(sup.iter())
2362 .all(|(a, b)| a == b || (is_empty_array_literal(a) && is_array_like(b)))
2363}
2364
2365fn is_empty_array_literal(t: &Type) -> bool {
2368 !t.types.is_empty()
2369 && t.types.iter().all(
2370 |atom| matches!(atom, Atomic::TKeyedArray { properties, .. } if properties.is_empty()),
2371 )
2372}
2373
2374fn is_array_like(t: &Type) -> bool {
2376 !t.types.is_empty() && t.types.iter().all(|atom| atom.is_array())
2377}
2378
2379#[cfg(test)]
2384mod tests {
2385 use std::sync::Arc;
2386
2387 use super::*;
2388
2389 fn conditional(
2390 param_name: Option<Name>,
2391 subject: Type,
2392 if_true: Type,
2393 if_false: Type,
2394 ) -> Atomic {
2395 Atomic::TConditional {
2396 data: Box::new(crate::atomic::ConditionalData {
2397 param_name,
2398 subject,
2399 if_true,
2400 if_false,
2401 }),
2402 }
2403 }
2404
2405 #[test]
2406 fn single_is_single() {
2407 let u = Type::single(Atomic::TString);
2408 assert!(u.is_single());
2409 assert!(!u.is_nullable());
2410 }
2411
2412 #[test]
2413 fn nullable_has_null() {
2414 let u = Type::nullable(Atomic::TString);
2415 assert!(u.is_nullable());
2416 assert_eq!(u.types.len(), 2);
2417 }
2418
2419 #[test]
2420 fn add_type_deduplicates() {
2421 let mut u = Type::single(Atomic::TString);
2422 u.add_type(Atomic::TString);
2423 assert_eq!(u.types.len(), 1);
2424 }
2425
2426 #[test]
2427 fn array_key_is_int_string() {
2428 let k = Type::array_key();
2429 assert!(k.is_array_key());
2430 assert_eq!(k.types.len(), 2);
2431 }
2432
2433 #[test]
2434 fn is_array_key_false_for_plain_int() {
2435 assert!(!Type::int().is_array_key());
2436 }
2437
2438 #[test]
2439 fn is_array_key_false_for_mixed() {
2440 assert!(!Type::mixed().is_array_key());
2441 }
2442
2443 #[test]
2444 fn is_array_key_false_for_int_string_null() {
2445 let mut u = Type::array_key();
2446 u.add_type(Atomic::TNull);
2447 assert!(!u.is_array_key());
2448 }
2449
2450 #[test]
2451 fn add_type_literal_subsumed_by_base() {
2452 let mut u = Type::single(Atomic::TInt);
2453 u.add_type(Atomic::TLiteralInt(42));
2454 assert_eq!(u.types.len(), 1);
2455 assert!(matches!(u.types[0], Atomic::TInt));
2456 }
2457
2458 #[test]
2459 fn true_then_false_merges_to_bool() {
2460 let mut u = Type::single(Atomic::TTrue);
2461 u.add_type(Atomic::TFalse);
2462 assert_eq!(u.types.len(), 1);
2463 assert!(matches!(u.types[0], Atomic::TBool));
2464 }
2465
2466 #[test]
2467 fn false_then_true_merges_to_bool() {
2468 let mut u = Type::single(Atomic::TFalse);
2469 u.add_type(Atomic::TTrue);
2470 assert_eq!(u.types.len(), 1);
2471 assert!(matches!(u.types[0], Atomic::TBool));
2472 }
2473
2474 #[test]
2475 fn true_alone_stays_true() {
2476 let u = Type::single(Atomic::TTrue);
2477 assert_eq!(u.types.len(), 1);
2478 assert!(matches!(u.types[0], Atomic::TTrue));
2479 }
2480
2481 #[test]
2482 fn true_false_merge_preserves_other_union_members() {
2483 let mut u = Type::single(Atomic::TTrue);
2484 u.add_type(Atomic::TNull);
2485 u.add_type(Atomic::TFalse);
2486 assert_eq!(u.types.len(), 2);
2487 assert!(u.contains(|t| matches!(t, Atomic::TBool)));
2488 assert!(u.contains(|t| matches!(t, Atomic::TNull)));
2489 }
2490
2491 #[test]
2492 fn add_type_base_widens_literals() {
2493 let mut u = Type::single(Atomic::TLiteralInt(1));
2494 u.add_type(Atomic::TLiteralInt(2));
2495 u.add_type(Atomic::TInt);
2496 assert_eq!(u.types.len(), 1);
2497 assert!(matches!(u.types[0], Atomic::TInt));
2498 }
2499
2500 #[test]
2501 fn mixed_subsumes_everything() {
2502 let mut u = Type::single(Atomic::TString);
2503 u.add_type(Atomic::TMixed);
2504 assert_eq!(u.types.len(), 1);
2505 assert!(u.is_mixed());
2506 }
2507
2508 #[test]
2509 fn remove_null() {
2510 let u = Type::nullable(Atomic::TString);
2511 let narrowed = u.remove_null();
2512 assert!(!narrowed.is_nullable());
2513 assert_eq!(narrowed.types.len(), 1);
2514 }
2515
2516 #[test]
2517 fn narrow_to_truthy_removes_null_false() {
2518 let mut u = Type::empty();
2519 u.add_type(Atomic::TString);
2520 u.add_type(Atomic::TNull);
2521 u.add_type(Atomic::TFalse);
2522 let truthy = u.narrow_to_truthy();
2523 assert!(!truthy.is_nullable());
2524 assert!(!truthy.contains(|t| matches!(t, Atomic::TFalse)));
2525 }
2526
2527 #[test]
2528 fn merge_combines_types() {
2529 let a = Type::single(Atomic::TString);
2530 let b = Type::single(Atomic::TInt);
2531 let merged = Type::merge(&a, &b);
2532 assert_eq!(merged.types.len(), 2);
2533 }
2534
2535 #[test]
2536 fn intersect_keeps_narrower_side_not_self() {
2537 let int_ty = Type::single(Atomic::TInt);
2541 let mut literals = Type::empty();
2542 literals.add_type(Atomic::TLiteralInt(1));
2543 literals.add_type(Atomic::TLiteralInt(2));
2544
2545 let narrowed = int_ty.intersect_with(&literals);
2546 assert_eq!(narrowed.types.len(), 2);
2547 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(1))));
2548 assert!(narrowed.contains(|t| matches!(t, Atomic::TLiteralInt(2))));
2549 assert!(!narrowed.contains(|t| matches!(t, Atomic::TInt)));
2550 }
2551
2552 #[test]
2553 fn subtype_literal_int_under_int() {
2554 let sub = Type::single(Atomic::TLiteralInt(5));
2555 let sup = Type::single(Atomic::TInt);
2556 assert!(sub.is_subtype_structural(&sup));
2557 }
2558
2559 #[test]
2560 fn subtype_never_is_bottom() {
2561 let never = Type::never();
2562 let string = Type::single(Atomic::TString);
2563 assert!(never.is_subtype_structural(&string));
2564 }
2565
2566 #[test]
2567 fn subtype_everything_under_mixed() {
2568 let string = Type::single(Atomic::TString);
2569 let mixed = Type::mixed();
2570 assert!(string.is_subtype_structural(&mixed));
2571 }
2572
2573 #[test]
2574 fn subtype_enum_case_under_own_enum() {
2575 let sub = Type::single(Atomic::TLiteralEnumCase {
2576 enum_fqcn: Name::new("RoundingMode"),
2577 case_name: Name::new("Unnecessary"),
2578 });
2579 let sup = Type::single(Atomic::TNamedObject {
2580 fqcn: Name::new("RoundingMode"),
2581 type_params: empty_type_params(),
2582 });
2583 assert!(sub.is_subtype_structural(&sup));
2584 }
2585
2586 #[test]
2587 fn enum_case_not_subtype_of_unrelated_enum() {
2588 let sub = Type::single(Atomic::TLiteralEnumCase {
2589 enum_fqcn: Name::new("RoundingMode"),
2590 case_name: Name::new("Unnecessary"),
2591 });
2592 let sup = Type::single(Atomic::TNamedObject {
2593 fqcn: Name::new("Suit"),
2594 type_params: empty_type_params(),
2595 });
2596 assert!(!sub.is_subtype_structural(&sup));
2597 }
2598
2599 #[test]
2600 fn subtype_enum_case_under_bare_object() {
2601 let sub = Type::single(Atomic::TLiteralEnumCase {
2602 enum_fqcn: Name::new("RoundingMode"),
2603 case_name: Name::new("Unnecessary"),
2604 });
2605 let sup = Type::single(Atomic::TObject);
2606 assert!(sub.is_subtype_structural(&sup));
2607 }
2608
2609 #[test]
2610 fn template_substitution() {
2611 let mut bindings = FxHashMap::default();
2612 bindings.insert(Name::new("T"), Type::single(Atomic::TString));
2613
2614 let tmpl = Type::single(Atomic::TTemplateParam {
2615 name: Name::new("T"),
2616 as_type: Box::new(Type::mixed()),
2617 defining_entity: Name::new("MyClass"),
2618 });
2619
2620 let resolved = tmpl.substitute_templates(&bindings);
2621 assert_eq!(resolved.types.len(), 1);
2622 assert!(matches!(resolved.types[0], Atomic::TString));
2623 }
2624
2625 #[test]
2626 fn intersection_is_object() {
2627 let parts = vec![
2628 Type::single(Atomic::TNamedObject {
2629 fqcn: Name::new("Iterator"),
2630 type_params: empty_type_params(),
2631 }),
2632 Type::single(Atomic::TNamedObject {
2633 fqcn: Name::new("Countable"),
2634 type_params: empty_type_params(),
2635 }),
2636 ];
2637 let atomic = Atomic::TIntersection {
2638 parts: vec_to_type_params(parts),
2639 };
2640 assert!(atomic.is_object());
2641 assert!(!atomic.can_be_falsy());
2642 assert!(atomic.can_be_truthy());
2643 }
2644
2645 #[test]
2646 fn intersection_display_two_parts() {
2647 let parts = vec![
2648 Type::single(Atomic::TNamedObject {
2649 fqcn: Name::new("Iterator"),
2650 type_params: empty_type_params(),
2651 }),
2652 Type::single(Atomic::TNamedObject {
2653 fqcn: Name::new("Countable"),
2654 type_params: empty_type_params(),
2655 }),
2656 ];
2657 let u = Type::single(Atomic::TIntersection {
2658 parts: vec_to_type_params(parts),
2659 });
2660 assert_eq!(format!("{u}"), "Iterator&Countable");
2661 }
2662
2663 #[test]
2664 fn intersection_display_three_parts() {
2665 let parts = vec![
2666 Type::single(Atomic::TNamedObject {
2667 fqcn: Name::new("A"),
2668 type_params: empty_type_params(),
2669 }),
2670 Type::single(Atomic::TNamedObject {
2671 fqcn: Name::new("B"),
2672 type_params: empty_type_params(),
2673 }),
2674 Type::single(Atomic::TNamedObject {
2675 fqcn: Name::new("C"),
2676 type_params: empty_type_params(),
2677 }),
2678 ];
2679 let u = Type::single(Atomic::TIntersection {
2680 parts: vec_to_type_params(parts),
2681 });
2682 assert_eq!(format!("{u}"), "A&B&C");
2683 }
2684
2685 #[test]
2686 fn intersection_in_nullable_union_display() {
2687 let intersection = Atomic::TIntersection {
2688 parts: vec_to_type_params(vec![
2689 Type::single(Atomic::TNamedObject {
2690 fqcn: Name::new("Iterator"),
2691 type_params: empty_type_params(),
2692 }),
2693 Type::single(Atomic::TNamedObject {
2694 fqcn: Name::new("Countable"),
2695 type_params: empty_type_params(),
2696 }),
2697 ]),
2698 };
2699 let mut u = Type::single(intersection);
2700 u.add_type(Atomic::TNull);
2701 assert!(u.is_nullable());
2702 assert!(u.contains(|t| matches!(t, Atomic::TIntersection { .. })));
2703 }
2704
2705 fn t_param(name: &str) -> Type {
2708 Type::single(Atomic::TTemplateParam {
2709 name: Name::new(name),
2710 as_type: Box::new(Type::mixed()),
2711 defining_entity: Name::new("Fn"),
2712 })
2713 }
2714
2715 fn bindings_t_string() -> FxHashMap<Name, Type> {
2716 let mut b = FxHashMap::default();
2717 b.insert(Name::new("T"), Type::single(Atomic::TString));
2718 b
2719 }
2720
2721 #[test]
2722 fn substitute_non_empty_array_key_and_value() {
2723 let ty = Type::single(Atomic::TNonEmptyArray {
2724 key: Box::new(t_param("T")),
2725 value: Box::new(t_param("T")),
2726 });
2727 let result = ty.substitute_templates(&bindings_t_string());
2728 assert_eq!(result.types.len(), 1);
2729 let Atomic::TNonEmptyArray { key, value } = &result.types[0] else {
2730 panic!("expected TNonEmptyArray");
2731 };
2732 assert!(matches!(key.types[0], Atomic::TString));
2733 assert!(matches!(value.types[0], Atomic::TString));
2734 }
2735
2736 #[test]
2737 fn substitute_non_empty_list_value() {
2738 let ty = Type::single(Atomic::TNonEmptyList {
2739 value: Box::new(t_param("T")),
2740 });
2741 let result = ty.substitute_templates(&bindings_t_string());
2742 let Atomic::TNonEmptyList { value } = &result.types[0] else {
2743 panic!("expected TNonEmptyList");
2744 };
2745 assert!(matches!(value.types[0], Atomic::TString));
2746 }
2747
2748 #[test]
2749 fn substitute_keyed_array_property_types() {
2750 use crate::atomic::{ArrayKey, KeyedProperty};
2751 use indexmap::IndexMap;
2752 let mut props = IndexMap::new();
2753 props.insert(
2754 ArrayKey::String(Arc::from("name")),
2755 KeyedProperty {
2756 ty: t_param("T"),
2757 optional: false,
2758 },
2759 );
2760 props.insert(
2761 ArrayKey::String(Arc::from("tag")),
2762 KeyedProperty {
2763 ty: t_param("T"),
2764 optional: true,
2765 },
2766 );
2767 let ty = Type::single(Atomic::TKeyedArray {
2768 properties: Box::new(props),
2769 is_open: true,
2770 is_list: false,
2771 });
2772 let result = ty.substitute_templates(&bindings_t_string());
2773 let Atomic::TKeyedArray {
2774 properties,
2775 is_open,
2776 is_list,
2777 } = &result.types[0]
2778 else {
2779 panic!("expected TKeyedArray");
2780 };
2781 assert!(is_open);
2782 assert!(!is_list);
2783 assert!(matches!(
2784 properties[&ArrayKey::String(Arc::from("name"))].ty.types[0],
2785 Atomic::TString
2786 ));
2787 assert!(properties[&ArrayKey::String(Arc::from("tag"))].optional);
2788 assert!(matches!(
2789 properties[&ArrayKey::String(Arc::from("tag"))].ty.types[0],
2790 Atomic::TString
2791 ));
2792 }
2793
2794 #[test]
2795 fn substitute_callable_params_and_return() {
2796 use crate::atomic::FnParam;
2797 let ty = Type::single(Atomic::TCallable {
2798 params: Some(Box::new([FnParam {
2799 name: Name::new("x"),
2800 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2801 out_ty: None,
2802 default: None,
2803 is_variadic: false,
2804 is_byref: false,
2805 is_optional: false,
2806 }])),
2807 return_type: Some(Box::new(t_param("T"))),
2808 });
2809 let result = ty.substitute_templates(&bindings_t_string());
2810 let Atomic::TCallable {
2811 params,
2812 return_type,
2813 } = &result.types[0]
2814 else {
2815 panic!("expected TCallable");
2816 };
2817 let param_ty = params.as_ref().unwrap()[0].ty.as_ref().unwrap();
2818 let param_union = param_ty.to_union();
2819 assert!(matches!(param_union.types[0], Atomic::TString));
2820 let ret = return_type.as_ref().unwrap();
2821 assert!(matches!(ret.types[0], Atomic::TString));
2822 }
2823
2824 #[test]
2825 fn substitute_callable_bare_no_panic() {
2826 let ty = Type::single(Atomic::TCallable {
2828 params: None,
2829 return_type: None,
2830 });
2831 let result = ty.substitute_templates(&bindings_t_string());
2832 assert!(matches!(
2833 result.types[0],
2834 Atomic::TCallable {
2835 params: None,
2836 return_type: None
2837 }
2838 ));
2839 }
2840
2841 #[test]
2842 fn substitute_closure_params_return_and_this() {
2843 use crate::atomic::FnParam;
2844 let ty = Type::single(Atomic::TClosure {
2845 data: Box::new(crate::atomic::ClosureData {
2846 params: Box::new([FnParam {
2847 name: Name::new("a"),
2848 ty: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2849 out_ty: None,
2850 default: Some(crate::compact::SimpleType::from_union(t_param("T"))),
2851 is_variadic: true,
2852 is_byref: true,
2853 is_optional: true,
2854 }]),
2855 return_type: t_param("T"),
2856 this_type: Some(t_param("T")),
2857 }),
2858 });
2859 let result = ty.substitute_templates(&bindings_t_string());
2860 let Atomic::TClosure { data } = &result.types[0] else {
2861 panic!("expected TClosure");
2862 };
2863 let (params, return_type, this_type) = (&data.params, &data.return_type, &data.this_type);
2864 let p = ¶ms[0];
2865 let ty_union = p.ty.as_ref().unwrap().to_union();
2866 let default_union = p.default.as_ref().unwrap().to_union();
2867 assert!(matches!(ty_union.types[0], Atomic::TString));
2868 assert!(matches!(default_union.types[0], Atomic::TString));
2869 assert!(p.is_variadic);
2871 assert!(p.is_byref);
2872 assert!(p.is_optional);
2873 assert!(matches!(return_type.types[0], Atomic::TString));
2874 assert!(matches!(
2875 this_type.as_ref().unwrap().types[0],
2876 Atomic::TString
2877 ));
2878 }
2879
2880 #[test]
2881 fn substitute_conditional_all_branches() {
2882 let ty = Type::single(conditional(
2883 None,
2884 t_param("T"),
2885 t_param("T"),
2886 Type::single(Atomic::TInt),
2887 ));
2888 let result = ty.substitute_templates(&bindings_t_string());
2889 let Atomic::TConditional { data } = &result.types[0] else {
2890 panic!("expected TConditional");
2891 };
2892 let (subject, if_true, if_false) = (&data.subject, &data.if_true, &data.if_false);
2893 assert!(matches!(subject.types[0], Atomic::TString));
2894 assert!(matches!(if_true.types[0], Atomic::TString));
2895 assert!(matches!(if_false.types[0], Atomic::TInt));
2896 }
2897
2898 #[test]
2899 fn resolve_conditional_is_null_non_null_arg() {
2900 let ty = Type::single(conditional(
2901 Some(Name::new("x")),
2902 Type::single(Atomic::TNull),
2903 Type::single(Atomic::TInt),
2904 Type::single(Atomic::TString),
2905 ));
2906 let result = ty.resolve_conditional_returns(|name| {
2907 if name == "x" {
2908 Some(Type::single(Atomic::TString)) } else {
2910 None
2911 }
2912 });
2913 assert!(result.types.len() == 1);
2914 assert!(matches!(result.types[0], Atomic::TString));
2915 }
2916
2917 #[test]
2918 fn resolve_conditional_is_null_null_arg() {
2919 let ty = Type::single(conditional(
2920 Some(Name::new("x")),
2921 Type::single(Atomic::TNull),
2922 Type::single(Atomic::TInt),
2923 Type::single(Atomic::TString),
2924 ));
2925 let result = ty.resolve_conditional_returns(|name| {
2926 if name == "x" {
2927 Some(Type::single(Atomic::TNull)) } else {
2929 None
2930 }
2931 });
2932 assert!(result.types.len() == 1);
2933 assert!(matches!(result.types[0], Atomic::TInt));
2934 }
2935
2936 #[test]
2937 fn resolve_conditional_is_null_nullable_arg_widens_to_branch_union() {
2938 let mut nullable_str = Type::single(Atomic::TString);
2939 nullable_str.add_type(Atomic::TNull);
2940 let ty = Type::single(conditional(
2941 Some(Name::new("x")),
2942 Type::single(Atomic::TNull),
2943 Type::single(Atomic::TInt),
2944 Type::single(Atomic::TString),
2945 ));
2946 let result = ty.resolve_conditional_returns(|name| {
2947 if name == "x" {
2948 Some(nullable_str.clone())
2949 } else {
2950 None
2951 }
2952 });
2953 assert_eq!(result.types.len(), 2);
2955 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2956 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2957 }
2958
2959 #[test]
2960 fn resolve_conditional_nested_widens_inner_branch() {
2961 let inner = Type::single(conditional(
2964 Some(Name::new("x")),
2965 Type::single(Atomic::TString),
2966 Type::single(Atomic::TString),
2967 Type::single(Atomic::TFloat),
2968 ));
2969 let ty = Type::single(conditional(
2970 Some(Name::new("x")),
2971 Type::single(Atomic::TNull),
2972 Type::single(Atomic::TInt),
2973 inner,
2974 ));
2975 let result = ty.resolve_conditional_returns(|_| None);
2977 assert!(
2978 result
2979 .types
2980 .iter()
2981 .all(|t| !matches!(t, Atomic::TConditional { .. })),
2982 "no TConditional should survive: {:?}",
2983 result.types
2984 );
2985 assert!(result.types.iter().any(|t| matches!(t, Atomic::TInt)));
2986 assert!(result.types.iter().any(|t| matches!(t, Atomic::TString)));
2987 assert!(result.types.iter().any(|t| matches!(t, Atomic::TFloat)));
2988 }
2989
2990 #[test]
2991 fn resolve_conditional_nested_resolves_inner_branch() {
2992 let inner = Type::single(conditional(
2996 Some(Name::new("x")),
2997 Type::single(Atomic::TString),
2998 Type::single(Atomic::TString),
2999 Type::single(Atomic::TFloat),
3000 ));
3001 let ty = Type::single(conditional(
3002 Some(Name::new("x")),
3003 Type::single(Atomic::TNull),
3004 Type::single(Atomic::TInt),
3005 inner,
3006 ));
3007 let result = ty.resolve_conditional_returns(|name| {
3009 if name == "x" {
3010 Some(Type::single(Atomic::TString))
3011 } else {
3012 None
3013 }
3014 });
3015 assert!(
3016 result
3017 .types
3018 .iter()
3019 .all(|t| !matches!(t, Atomic::TConditional { .. })),
3020 "no TConditional should survive: {:?}",
3021 result.types
3022 );
3023 assert_eq!(result.types.len(), 1);
3024 assert!(matches!(result.types[0], Atomic::TString));
3025 }
3026
3027 #[test]
3028 fn resolve_conditional_true_subject_bool_arg_widens() {
3029 let ty = Type::single(conditional(
3033 Some(Name::new("x")),
3034 Type::single(Atomic::TTrue),
3035 Type::single(Atomic::TInt),
3036 Type::single(Atomic::TString),
3037 ));
3038 let result = ty.resolve_conditional_returns(|name| {
3039 if name == "x" {
3040 Some(Type::single(Atomic::TBool))
3041 } else {
3042 None
3043 }
3044 });
3045 assert_eq!(result.types.len(), 2);
3046 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3047 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3048 }
3049
3050 #[test]
3051 fn resolve_conditional_true_subject_mixed_arg_widens() {
3052 let ty = Type::single(conditional(
3055 Some(Name::new("x")),
3056 Type::single(Atomic::TTrue),
3057 Type::single(Atomic::TInt),
3058 Type::single(Atomic::TString),
3059 ));
3060 let result = ty.resolve_conditional_returns(|name| {
3061 if name == "x" {
3062 Some(Type::mixed())
3063 } else {
3064 None
3065 }
3066 });
3067 assert_eq!(result.types.len(), 2);
3068 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3069 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3070 }
3071
3072 #[test]
3073 fn resolve_conditional_true_subject_literal_args() {
3074 let ty = Type::single(conditional(
3077 Some(Name::new("x")),
3078 Type::single(Atomic::TTrue),
3079 Type::single(Atomic::TInt),
3080 Type::single(Atomic::TString),
3081 ));
3082 let false_arg = ty.clone().resolve_conditional_returns(|name| {
3083 if name == "x" {
3084 Some(Type::single(Atomic::TFalse))
3085 } else {
3086 None
3087 }
3088 });
3089 assert_eq!(false_arg.types.len(), 1);
3090 assert!(matches!(false_arg.types[0], Atomic::TString));
3091
3092 let true_arg = ty.resolve_conditional_returns(|name| {
3093 if name == "x" {
3094 Some(Type::single(Atomic::TTrue))
3095 } else {
3096 None
3097 }
3098 });
3099 assert_eq!(true_arg.types.len(), 1);
3100 assert!(matches!(true_arg.types[0], Atomic::TInt));
3101 }
3102
3103 #[test]
3104 fn resolve_conditional_bool_subject_scalar_arg_widens() {
3105 let ty = Type::single(conditional(
3109 Some(Name::new("x")),
3110 Type::single(Atomic::TBool),
3111 Type::single(Atomic::TInt),
3112 Type::single(Atomic::TString),
3113 ));
3114 let result = ty.resolve_conditional_returns(|name| {
3115 if name == "x" {
3116 Some(Type::single(Atomic::TScalar))
3117 } else {
3118 None
3119 }
3120 });
3121 assert_eq!(result.types.len(), 2);
3122 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3123 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3124 }
3125
3126 #[test]
3127 fn resolve_conditional_string_subject_bool_arg_false_branch() {
3128 let ty = Type::single(conditional(
3132 Some(Name::new("x")),
3133 Type::single(Atomic::TString),
3134 Type::single(Atomic::TInt),
3135 Type::single(Atomic::TBool),
3136 ));
3137 let result = ty.resolve_conditional_returns(|name| {
3138 if name == "x" {
3139 Some(Type::single(Atomic::TBool))
3140 } else {
3141 None
3142 }
3143 });
3144 assert_eq!(result.types.len(), 1);
3145 assert!(matches!(result.types[0], Atomic::TBool));
3146 }
3147
3148 #[test]
3149 fn resolve_conditional_list_subject_bare_array_arg_widens() {
3150 let ty = Type::single(conditional(
3153 Some(Name::new("x")),
3154 Type::single(Atomic::TList {
3155 value: Box::new(Type::mixed()),
3156 }),
3157 Type::single(Atomic::TInt),
3158 Type::single(Atomic::TString),
3159 ));
3160 let result = ty.resolve_conditional_returns(|name| {
3161 if name == "x" {
3162 Some(Type::single(Atomic::TArray {
3163 key: Box::new(Type::mixed()),
3164 value: Box::new(Type::mixed()),
3165 }))
3166 } else {
3167 None
3168 }
3169 });
3170 assert_eq!(result.types.len(), 2);
3171 assert!(result.contains(|t| matches!(t, Atomic::TInt)));
3172 assert!(result.contains(|t| matches!(t, Atomic::TString)));
3173 }
3174
3175 #[test]
3176 fn resolve_conditional_float_subject_int_literal_false_branch() {
3177 let ty = resolve_conditional_branch(
3180 &Atomic::TFloat,
3181 &Type::single(Atomic::TLiteralInt(42)),
3182 &Type::single(Atomic::TInt),
3183 &Type::single(Atomic::TString),
3184 );
3185 assert!(matches!(
3186 ty.as_ref(),
3187 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3188 ));
3189 }
3190
3191 #[test]
3192 fn resolve_conditional_int_subject_float_literal_false_branch() {
3193 let ty = resolve_conditional_branch(
3194 &Atomic::TInt,
3195 &Type::single(Atomic::TLiteralFloat(1, 0)),
3196 &Type::single(Atomic::TInt),
3197 &Type::single(Atomic::TString),
3198 );
3199 assert!(matches!(
3200 ty.as_ref(),
3201 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3202 ));
3203 }
3204
3205 #[test]
3206 fn resolve_conditional_float_subject_float_literal_then_branch() {
3207 let ty = resolve_conditional_branch(
3208 &Atomic::TFloat,
3209 &Type::single(Atomic::TLiteralFloat(1, 5)),
3210 &Type::single(Atomic::TInt),
3211 &Type::single(Atomic::TString),
3212 );
3213 assert!(matches!(
3214 ty.as_ref(),
3215 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3216 ));
3217 }
3218
3219 #[test]
3220 fn resolve_conditional_int_subject_int_literal_then_branch() {
3221 let ty = resolve_conditional_branch(
3222 &Atomic::TInt,
3223 &Type::single(Atomic::TLiteralInt(7)),
3224 &Type::single(Atomic::TInt),
3225 &Type::single(Atomic::TString),
3226 );
3227 assert!(matches!(
3228 ty.as_ref(),
3229 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3230 ));
3231 }
3232
3233 #[test]
3234 fn resolve_conditional_int_subject_float_arg_false_branch() {
3235 let ty = resolve_conditional_branch(
3238 &Atomic::TInt,
3239 &Type::single(Atomic::TFloat),
3240 &Type::single(Atomic::TInt),
3241 &Type::single(Atomic::TString),
3242 );
3243 assert!(matches!(
3244 ty.as_ref(),
3245 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3246 ));
3247 }
3248
3249 #[test]
3250 fn resolve_conditional_array_subject_string_arg_false_branch() {
3251 let ty = resolve_conditional_branch(
3252 &Atomic::TArray {
3253 key: Box::new(Type::mixed()),
3254 value: Box::new(Type::mixed()),
3255 },
3256 &Type::single(Atomic::TString),
3257 &Type::single(Atomic::TInt),
3258 &Type::single(Atomic::TString),
3259 );
3260 assert!(matches!(
3261 ty.as_ref(),
3262 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3263 ));
3264 }
3265
3266 #[test]
3267 fn resolve_conditional_array_subject_bool_arg_false_branch() {
3268 let ty = resolve_conditional_branch(
3269 &Atomic::TArray {
3270 key: Box::new(Type::mixed()),
3271 value: Box::new(Type::mixed()),
3272 },
3273 &Type::single(Atomic::TBool),
3274 &Type::single(Atomic::TInt),
3275 &Type::single(Atomic::TString),
3276 );
3277 assert!(matches!(
3278 ty.as_ref(),
3279 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3280 ));
3281 }
3282
3283 #[test]
3284 fn resolve_conditional_array_subject_mixed_arg_widens() {
3285 let ty = resolve_conditional_branch(
3289 &Atomic::TArray {
3290 key: Box::new(Type::mixed()),
3291 value: Box::new(Type::mixed()),
3292 },
3293 &Type::mixed(),
3294 &Type::single(Atomic::TInt),
3295 &Type::single(Atomic::TString),
3296 );
3297 assert!(ty.is_none());
3298 }
3299
3300 #[test]
3301 fn resolve_conditional_array_subject_list_arg_then_branch() {
3302 let ty = resolve_conditional_branch(
3305 &Atomic::TArray {
3306 key: Box::new(Type::mixed()),
3307 value: Box::new(Type::mixed()),
3308 },
3309 &Type::single(Atomic::TList {
3310 value: Box::new(Type::mixed()),
3311 }),
3312 &Type::single(Atomic::TInt),
3313 &Type::single(Atomic::TString),
3314 );
3315 assert!(matches!(
3316 ty.as_ref(),
3317 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3318 ));
3319 }
3320
3321 #[test]
3322 fn resolve_conditional_list_subject_keyed_list_arg_then_branch() {
3323 let ty = resolve_conditional_branch(
3325 &Atomic::TList {
3326 value: Box::new(Type::mixed()),
3327 },
3328 &Type::single(Atomic::TKeyedArray {
3329 properties: Box::default(),
3330 is_open: false,
3331 is_list: true,
3332 }),
3333 &Type::single(Atomic::TInt),
3334 &Type::single(Atomic::TString),
3335 );
3336 assert!(matches!(
3337 ty.as_ref(),
3338 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3339 ));
3340 }
3341
3342 #[test]
3343 fn resolve_conditional_list_subject_list_string_union_widens() {
3344 let ty = resolve_conditional_branch(
3348 &Atomic::TList {
3349 value: Box::new(Type::mixed()),
3350 },
3351 &Type::from_vec(vec![
3352 Atomic::TList {
3353 value: Box::new(Type::mixed()),
3354 },
3355 Atomic::TString,
3356 ]),
3357 &Type::single(Atomic::TInt),
3358 &Type::single(Atomic::TString),
3359 );
3360 assert!(ty.is_none());
3361 }
3362
3363 #[test]
3364 fn resolve_conditional_numeric_subject_widens() {
3365 let ty = resolve_conditional_branch(
3369 &Atomic::TNumeric,
3370 &Type::single(Atomic::TLiteralString("123".into())),
3371 &Type::single(Atomic::TInt),
3372 &Type::single(Atomic::TString),
3373 );
3374 assert!(ty.is_none());
3375 }
3376
3377 #[test]
3378 fn resolve_conditional_non_empty_string_subject_widens() {
3379 let ty = resolve_conditional_branch(
3382 &Atomic::TNonEmptyString,
3383 &Type::single(Atomic::TString),
3384 &Type::single(Atomic::TInt),
3385 &Type::single(Atomic::TString),
3386 );
3387 assert!(ty.is_none());
3388 }
3389
3390 #[test]
3391 fn resolve_conditional_template_arg_widens() {
3392 let ty = resolve_conditional_branch(
3395 &Atomic::TString,
3396 &t_param("T"),
3397 &Type::single(Atomic::TInt),
3398 &Type::single(Atomic::TString),
3399 );
3400 assert!(ty.is_none());
3401 }
3402
3403 #[test]
3404 fn resolve_conditional_mixed_subject_bool_arg_then_branch() {
3405 let ty = resolve_conditional_branch(
3408 &Atomic::TMixed,
3409 &Type::single(Atomic::TBool),
3410 &Type::single(Atomic::TInt),
3411 &Type::single(Atomic::TString),
3412 );
3413 assert!(matches!(
3414 ty.as_ref(),
3415 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3416 ));
3417 }
3418
3419 #[test]
3420 fn resolve_conditional_scalar_subject_null_arg_false_branch() {
3421 let ty = resolve_conditional_branch(
3424 &Atomic::TScalar,
3425 &Type::single(Atomic::TNull),
3426 &Type::single(Atomic::TInt),
3427 &Type::single(Atomic::TString),
3428 );
3429 assert!(matches!(
3430 ty.as_ref(),
3431 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3432 ));
3433 }
3434
3435 #[test]
3436 fn resolve_conditional_object_subject_named_arg_then_branch() {
3437 let ty = resolve_conditional_branch(
3439 &Atomic::TObject,
3440 &Type::single(Atomic::TNamedObject {
3441 fqcn: Name::new("Foo"),
3442 type_params: empty_type_params(),
3443 }),
3444 &Type::single(Atomic::TInt),
3445 &Type::single(Atomic::TString),
3446 );
3447 assert!(matches!(
3448 ty.as_ref(),
3449 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3450 ));
3451 }
3452
3453 #[test]
3454 fn resolve_conditional_named_subject_other_class_widens() {
3455 let ty = resolve_conditional_branch(
3459 &Atomic::TNamedObject {
3460 fqcn: Name::new("Foo"),
3461 type_params: empty_type_params(),
3462 },
3463 &Type::single(Atomic::TNamedObject {
3464 fqcn: Name::new("Bar"),
3465 type_params: empty_type_params(),
3466 }),
3467 &Type::single(Atomic::TInt),
3468 &Type::single(Atomic::TString),
3469 );
3470 assert!(ty.is_none());
3471 }
3472
3473 #[test]
3474 fn resolve_conditional_enum_case_subject_widens() {
3475 let ty = resolve_conditional_branch(
3478 &Atomic::TLiteralEnumCase {
3479 enum_fqcn: Name::new("RoundingMode"),
3480 case_name: Name::new("Unnecessary"),
3481 },
3482 &Type::single(Atomic::TInt),
3483 &Type::single(Atomic::TInt),
3484 &Type::single(Atomic::TString),
3485 );
3486 assert!(ty.is_none());
3487 }
3488
3489 #[test]
3490 fn resolve_conditional_null_subject_bool_arg_false_branch() {
3491 let ty = resolve_conditional_branch(
3493 &Atomic::TNull,
3494 &Type::single(Atomic::TBool),
3495 &Type::single(Atomic::TInt),
3496 &Type::single(Atomic::TString),
3497 );
3498 assert!(matches!(
3499 ty.as_ref(),
3500 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TString)
3501 ));
3502 }
3503
3504 #[test]
3505 fn resolve_conditional_bool_subject_true_arg_then_branch() {
3506 let ty = resolve_conditional_branch(
3509 &Atomic::TBool,
3510 &Type::single(Atomic::TTrue),
3511 &Type::single(Atomic::TInt),
3512 &Type::single(Atomic::TString),
3513 );
3514 assert!(matches!(
3515 ty.as_ref(),
3516 Some(t) if t.types.len() == 1 && matches!(t.types[0], Atomic::TInt)
3517 ));
3518 }
3519
3520 #[test]
3521 fn substitute_intersection_parts() {
3522 let ty = Type::single(Atomic::TIntersection {
3523 parts: vec_to_type_params(vec![
3524 Type::single(Atomic::TNamedObject {
3525 fqcn: Name::new("Countable"),
3526 type_params: empty_type_params(),
3527 }),
3528 t_param("T"),
3529 ]),
3530 });
3531 let result = ty.substitute_templates(&bindings_t_string());
3532 let Atomic::TIntersection { parts } = &result.types[0] else {
3533 panic!("expected TIntersection");
3534 };
3535 assert_eq!(parts.len(), 2);
3536 assert!(matches!(parts[0].types[0], Atomic::TNamedObject { .. }));
3537 assert!(matches!(parts[1].types[0], Atomic::TString));
3538 }
3539
3540 #[test]
3541 fn substitute_no_template_params_identity() {
3542 let ty = Type::single(Atomic::TInt);
3543 let result = ty.substitute_templates(&bindings_t_string());
3544 assert!(matches!(result.types[0], Atomic::TInt));
3545 }
3546}